mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-11-04 07:32:59 +00:00 
			
		
		
		
	Compare commits
	
		
			62 Commits
		
	
	
		
			mc-1.20.x
			...
			v1.21.1-1.
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					4710ee5bcc | ||
| 
						 | 
					4a532952d4 | ||
| 
						 | 
					7337b91692 | ||
| 
						 | 
					f04c699df6 | ||
| 
						 | 
					a3f8e653d4 | ||
| 
						 | 
					b7396f3796 | ||
| 
						 | 
					5eb50ecb06 | ||
| 
						 | 
					7af2c14327 | ||
| 
						 | 
					0056709999 | ||
| 
						 | 
					0c8e757314 | ||
| 
						 | 
					f39e86bb10 | ||
| 
						 | 
					63181e73a1 | ||
| 
						 | 
					4f3247a0e2 | ||
| 
						 | 
					4f15f4197b | ||
| 
						 | 
					0d8ac304c7 | ||
| 
						 | 
					fdd5f49369 | ||
| 
						 | 
					8bd4c3370e | ||
| 
						 | 
					3eb84ffedd | ||
| 
						 | 
					dad6874638 | ||
| 
						 | 
					45cb597ecc | ||
| 
						 | 
					16577783d3 | ||
| 
						 | 
					c179da28f0 | ||
| 
						 | 
					2765abf971 | ||
| 
						 | 
					2c740bb904 | ||
| 
						 | 
					0e4710a956 | ||
| 
						 | 
					aca1d43550 | ||
| 
						 | 
					f10e401aea | ||
| 
						 | 
					1a1623075f | ||
| 
						 | 
					54a95e07a4 | ||
| 
						 | 
					efd9a0f315 | ||
| 
						 | 
					28f75a0687 | ||
| 
						 | 
					4b102f16b3 | ||
| 
						 | 
					bb933d0100 | ||
| 
						 | 
					de078e3037 | ||
| 
						 | 
					eb584aa94d | ||
| 
						 | 
					ad70e2ad90 | ||
| 
						 | 
					2c0d8263d3 | ||
| 
						 | 
					94c864759d | ||
| 
						 | 
					2226df7224 | ||
| 
						 | 
					959bdaeb61 | ||
| 
						 | 
					06ac373e83 | ||
| 
						 | 
					0aca6a4dc9 | ||
| 
						 | 
					bf203bb1f3 | ||
| 
						 | 
					cd9840d1c1 | ||
| 
						 | 
					b9a002586c | ||
| 
						 | 
					a3b07909b0 | ||
| 
						 | 
					d7786ee4b9 | ||
| 
						 | 
					188806e8b0 | ||
| 
						 | 
					01407544c9 | ||
| 
						 | 
					bd2fd9d4c8 | ||
| 
						 | 
					5c457950d8 | ||
| 
						 | 
					75f3ecce18 | ||
| 
						 | 
					688fdc40a6 | ||
| 
						 | 
					22bd5309ba | ||
| 
						 | 
					c50d56d9fa | ||
| 
						 | 
					7b9a156abc | ||
| 
						 | 
					0a9e5c78f3 | ||
| 
						 | 
					da5885ef35 | ||
| 
						 | 
					240528cce5 | ||
| 
						 | 
					83f1f86888 | ||
| 
						 | 
					9c202bd1c2 | ||
| 
						 | 
					fc834cd97f | 
@@ -27,7 +27,7 @@ indent_size = 2
 | 
			
		||||
[*.yml]
 | 
			
		||||
indent_size = 2
 | 
			
		||||
 | 
			
		||||
[*.{kt,kts}]
 | 
			
		||||
[{*.kt,*.kts}]
 | 
			
		||||
ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL
 | 
			
		||||
ij_kotlin_continuation_indent_size = 4
 | 
			
		||||
ij_kotlin_spaces_around_equality_operators = true
 | 
			
		||||
@@ -39,14 +39,3 @@ ij_kotlin_allow_trailing_comma_on_call_site = true
 | 
			
		||||
ij_kotlin_method_parameters_wrap = off
 | 
			
		||||
ij_kotlin_call_parameters_wrap = off
 | 
			
		||||
ij_kotlin_extends_list_wrap = off
 | 
			
		||||
 | 
			
		||||
ktlint_code_style = intellij_idea
 | 
			
		||||
ktlint_standard_class-naming = disabled
 | 
			
		||||
ktlint_standard_class-signature = disabled
 | 
			
		||||
ktlint_standard_function-naming = disabled
 | 
			
		||||
ktlint_standard_no-wildcard-imports = disabled
 | 
			
		||||
 | 
			
		||||
# FIXME: These two are disable right now as they're over-eager in putting things
 | 
			
		||||
#  on the same line. We should set max_line_length and handle this properly.
 | 
			
		||||
ktlint_standard_function-signature = disabled
 | 
			
		||||
ktlint_standard_function-expression-body = disabled
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								.github/ISSUE_TEMPLATE/bug_report.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/ISSUE_TEMPLATE/bug_report.yaml
									
									
									
									
										vendored
									
									
								
							@@ -1,7 +1,6 @@
 | 
			
		||||
name: Bug report
 | 
			
		||||
description: Report some misbehaviour in the mod
 | 
			
		||||
labels: [ bug ]
 | 
			
		||||
type: bug
 | 
			
		||||
body:
 | 
			
		||||
- type: dropdown
 | 
			
		||||
  id: mc-version
 | 
			
		||||
@@ -11,8 +10,7 @@ body:
 | 
			
		||||
        What version of Minecraft are you using? If your version is not listed, please try to reproduce on one of the supported versions.
 | 
			
		||||
    options:
 | 
			
		||||
      - 1.20.1
 | 
			
		||||
      - 1.21.1
 | 
			
		||||
      - 1.21.7
 | 
			
		||||
      - 1.21.x
 | 
			
		||||
  validations:
 | 
			
		||||
    required: true
 | 
			
		||||
- type: input
 | 
			
		||||
@@ -31,5 +29,3 @@ body:
 | 
			
		||||
      Description of the bug. Please include the following:
 | 
			
		||||
      - Logs: These will be located in the `logs/` directory of your Minecraft instance. This is always useful, even if it doesn't include errors, so please upload this!
 | 
			
		||||
      - Detailed reproduction steps: sometimes I can spot a bug pretty easily, but often it's much more obscure. The more information I have to help reproduce it, the quicker it'll get fixed.
 | 
			
		||||
 | 
			
		||||
      
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								.github/ISSUE_TEMPLATE/feature_request.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/ISSUE_TEMPLATE/feature_request.md
									
									
									
									
										vendored
									
									
								
							@@ -2,7 +2,6 @@
 | 
			
		||||
name: Feature request
 | 
			
		||||
about: Suggest an idea or improvement
 | 
			
		||||
labels: enhancement
 | 
			
		||||
type: feature
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
<!--
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@
 | 
			
		||||
# See https://pre-commit.com/hooks.html for more hooks
 | 
			
		||||
repos:
 | 
			
		||||
- repo: https://github.com/pre-commit/pre-commit-hooks
 | 
			
		||||
  rev: v5.0.0
 | 
			
		||||
  rev: v4.4.0
 | 
			
		||||
  hooks:
 | 
			
		||||
  - id: trailing-whitespace
 | 
			
		||||
  - id: end-of-file-fixer
 | 
			
		||||
 
 | 
			
		||||
@@ -88,8 +88,8 @@ You'll first need to [set up a development environment as above](#setting-up-a-d
 | 
			
		||||
 | 
			
		||||
Once this is set up, you can now run `./gradlew docWebsite`. This generates documentation from our Lua and Java code,
 | 
			
		||||
writing the resulting HTML into `./projects/web/build/site`, which can then be opened in a browser. When iterating on
 | 
			
		||||
documentation, you can instead run `./gradlew :web:assemble -x :web:compileTeaVM -t`, which will rebuild documentation
 | 
			
		||||
every time you change a file.
 | 
			
		||||
documentation, you can instead run `./gradlew docWebsite -t`, which will rebuild documentation every time you change a
 | 
			
		||||
file.
 | 
			
		||||
 | 
			
		||||
Documentation is built using [illuaminate] which, while not currently documented (somewhat ironic), is largely the same
 | 
			
		||||
as [ldoc][ldoc]. Documentation comments are written in Markdown, though note that we do not support many GitHub-specific
 | 
			
		||||
@@ -101,7 +101,7 @@ about how you can build on that until you've covered everything!
 | 
			
		||||
 | 
			
		||||
[new-issue]: https://github.com/cc-tweaked/CC-Tweaked/issues/new/choose "Create a new issue"
 | 
			
		||||
[community]: README.md#community "Get in touch with the community."
 | 
			
		||||
[Adoptium]: https://adoptium.net/temurin/releases?version=21 "Download OpenJDK 21"
 | 
			
		||||
[Adoptium]: https://adoptium.net/temurin/releases?version=17 "Download OpenJDK 17"
 | 
			
		||||
[illuaminate]: https://github.com/SquidDev/illuaminate/ "Illuaminate on GitHub"
 | 
			
		||||
[docs]: https://tweaked.cc/ "CC: Tweaked documentation"
 | 
			
		||||
[ldoc]: http://stevedonovan.github.io/ldoc/ "ldoc, a Lua documentation generator."
 | 
			
		||||
 
 | 
			
		||||
@@ -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")
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@ path = [
 | 
			
		||||
    "gradle/gradle-daemon-jvm.properties",
 | 
			
		||||
    "projects/common/src/main/resources/assets/computercraft/sounds.json",
 | 
			
		||||
    "projects/common/src/main/resources/assets/computercraft/sounds/empty.ogg",
 | 
			
		||||
    "projects/common/src/testMod/resources/data/cctest/computercraft/turtle_upgrades/**",
 | 
			
		||||
    "projects/common/src/testMod/resources/data/cctest/computercraft/turtle_upgrade/**",
 | 
			
		||||
    "projects/common/src/testMod/resources/data/cctest/structures/**",
 | 
			
		||||
    "projects/*/src/generated/**",
 | 
			
		||||
    "projects/web/src/htmlTransform/export/index.json",
 | 
			
		||||
 
 | 
			
		||||
@@ -4,11 +4,13 @@
 | 
			
		||||
 | 
			
		||||
import cc.tweaked.gradle.JUnitExt
 | 
			
		||||
import net.fabricmc.loom.api.LoomGradleExtensionAPI
 | 
			
		||||
import net.fabricmc.loom.util.gradle.SourceSetHelper
 | 
			
		||||
import org.jetbrains.gradle.ext.*
 | 
			
		||||
import org.jetbrains.gradle.ext.Application
 | 
			
		||||
 | 
			
		||||
plugins {
 | 
			
		||||
    publishing
 | 
			
		||||
    alias(libs.plugins.taskTree)
 | 
			
		||||
    alias(libs.plugins.githubRelease)
 | 
			
		||||
    alias(libs.plugins.gradleVersions)
 | 
			
		||||
    alias(libs.plugins.versionCatalogUpdate)
 | 
			
		||||
@@ -68,7 +70,7 @@ idea.project.settings.runConfigurations {
 | 
			
		||||
        val fabricProject = evaluationDependsOn(":fabric")
 | 
			
		||||
        val classPathGroup = fabricProject.extensions.getByType<LoomGradleExtensionAPI>().mods
 | 
			
		||||
            .joinToString(File.pathSeparator + File.pathSeparator) { modSettings ->
 | 
			
		||||
                modSettings.modFiles.joinToString(File.pathSeparator) { it.absolutePath }
 | 
			
		||||
                SourceSetHelper.getClasspath(modSettings, project).joinToString(File.pathSeparator) { it.absolutePath }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        vmParameters = "-ea -Dfabric.classPathGroups=$classPathGroup"
 | 
			
		||||
@@ -113,12 +115,8 @@ idea.project.settings.compiler.javac {
 | 
			
		||||
        .toMap()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
repositories() {
 | 
			
		||||
    mavenCentral()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
versionCatalogUpdate {
 | 
			
		||||
    sortByKey = false
 | 
			
		||||
    pin { versions.addAll("fastutil", "guava", "netty", "slf4j") }
 | 
			
		||||
    keep { keepUnusedVersions = true }
 | 
			
		||||
    keep { keepUnusedLibraries = true }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,6 @@ repositories {
 | 
			
		||||
        name = "Fabric"
 | 
			
		||||
        content {
 | 
			
		||||
            includeGroup("net.fabricmc")
 | 
			
		||||
            includeGroup("net.fabricmc.unpick")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -70,6 +69,6 @@ gradlePlugin {
 | 
			
		||||
 | 
			
		||||
versionCatalogUpdate {
 | 
			
		||||
    sortByKey = false
 | 
			
		||||
    keep { keepUnusedVersions = true }
 | 
			
		||||
    keep { keepUnusedLibraries = true }
 | 
			
		||||
    catalogFile = file("../gradle/libs.versions.toml")
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,10 @@
 | 
			
		||||
 | 
			
		||||
/** Default configuration for Fabric projects. */
 | 
			
		||||
 | 
			
		||||
import cc.tweaked.gradle.*
 | 
			
		||||
import cc.tweaked.gradle.CCTweakedExtension
 | 
			
		||||
import cc.tweaked.gradle.CCTweakedPlugin
 | 
			
		||||
import cc.tweaked.gradle.IdeaRunConfigurations
 | 
			
		||||
import cc.tweaked.gradle.MinecraftConfigurations
 | 
			
		||||
 | 
			
		||||
plugins {
 | 
			
		||||
    `java-library`
 | 
			
		||||
@@ -64,19 +67,3 @@ dependencies {
 | 
			
		||||
tasks.ideaSyncTask {
 | 
			
		||||
    doLast { IdeaRunConfigurations(project).patch() }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.named("checkDependencyConsistency", DependencyCheck::class.java) {
 | 
			
		||||
    val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
 | 
			
		||||
    // Minecraft depends on lwjgl, but Fabric forces it to a more recent version
 | 
			
		||||
    for (lwjgl in listOf(
 | 
			
		||||
        "lwjgl",
 | 
			
		||||
        "lwjgl-glfw",
 | 
			
		||||
        "lwjgl-jemalloc",
 | 
			
		||||
        "lwjgl-openal",
 | 
			
		||||
        "lwjgl-opengl",
 | 
			
		||||
        "lwjgl-stb",
 | 
			
		||||
        "lwjgl-tinyfd",
 | 
			
		||||
    )) {
 | 
			
		||||
        override("org.lwjgl", lwjgl, "3.3.2")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,20 +6,21 @@
 | 
			
		||||
 | 
			
		||||
import cc.tweaked.gradle.CCTweakedExtension
 | 
			
		||||
import cc.tweaked.gradle.CCTweakedPlugin
 | 
			
		||||
import cc.tweaked.gradle.IdeaRunConfigurations
 | 
			
		||||
import cc.tweaked.gradle.MinecraftConfigurations
 | 
			
		||||
 | 
			
		||||
plugins {
 | 
			
		||||
    id("cc-tweaked.java-convention")
 | 
			
		||||
    id("net.neoforged.moddev.legacyforge")
 | 
			
		||||
    id("net.neoforged.moddev")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
plugins.apply(CCTweakedPlugin::class.java)
 | 
			
		||||
 | 
			
		||||
val mcVersion: String by extra
 | 
			
		||||
 | 
			
		||||
legacyForge {
 | 
			
		||||
neoForge {
 | 
			
		||||
    val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
 | 
			
		||||
    version = "$mcVersion-${libs.findVersion("forge").get()}"
 | 
			
		||||
    version = libs.findVersion("neoForge").get().toString()
 | 
			
		||||
 | 
			
		||||
    parchment {
 | 
			
		||||
        minecraftVersion = libs.findVersion("parchmentMc").get().toString()
 | 
			
		||||
 
 | 
			
		||||
@@ -17,8 +17,6 @@ plugins {
 | 
			
		||||
    checkstyle
 | 
			
		||||
    id("com.diffplug.spotless")
 | 
			
		||||
    id("net.ltgt.errorprone")
 | 
			
		||||
    // Required for cross-project dependencies in Fabric
 | 
			
		||||
    id("net.fabricmc.fabric-loom-companion")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
val modVersion: String by extra
 | 
			
		||||
@@ -30,9 +28,9 @@ version = modVersion
 | 
			
		||||
base.archivesName.convention("cc-tweaked-$mcVersion-${project.name}")
 | 
			
		||||
 | 
			
		||||
java {
 | 
			
		||||
    toolchain { languageVersion = CCTweakedPlugin.JDK_VERSION }
 | 
			
		||||
    sourceCompatibility = CCTweakedPlugin.JAVA_VERSION
 | 
			
		||||
    targetCompatibility = CCTweakedPlugin.JAVA_VERSION
 | 
			
		||||
    toolchain {
 | 
			
		||||
        languageVersion= CCTweakedPlugin.JAVA_VERSION
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    withSourcesJar()
 | 
			
		||||
}
 | 
			
		||||
@@ -59,7 +57,6 @@ repositories {
 | 
			
		||||
            includeGroup("mezz.jei")
 | 
			
		||||
            includeGroup("org.teavm")
 | 
			
		||||
            includeModule("com.terraformersmc", "modmenu")
 | 
			
		||||
            includeModule("me.lucko", "fabric-permissions-api")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -81,8 +78,6 @@ dependencies {
 | 
			
		||||
// Configure default JavaCompile tasks with our arguments.
 | 
			
		||||
sourceSets.all {
 | 
			
		||||
    tasks.named(compileJavaTaskName, JavaCompile::class.java) {
 | 
			
		||||
        // Explicitly set release, as that limits the APIs we can use to the right version of Java.
 | 
			
		||||
        options.release = CCTweakedPlugin.JAVA_TARGET.asInt()
 | 
			
		||||
 | 
			
		||||
        options.compilerArgs.addAll(
 | 
			
		||||
            listOf(
 | 
			
		||||
@@ -96,16 +91,15 @@ sourceSets.all {
 | 
			
		||||
 | 
			
		||||
        options.errorprone {
 | 
			
		||||
            check("InvalidBlockTag", CheckSeverity.OFF) // Broken by @cc.xyz
 | 
			
		||||
            check("InvalidParam", CheckSeverity.OFF) // Broken by records.
 | 
			
		||||
            check("InlineMeSuggester", CheckSeverity.OFF) // Minecraft uses @Deprecated liberally
 | 
			
		||||
            // Too many false positives right now. Maybe we need an indirection for it later on.
 | 
			
		||||
            check("AssignmentExpression", CheckSeverity.OFF) // I'm a bad person.
 | 
			
		||||
            check("ReferenceEquality", CheckSeverity.OFF)
 | 
			
		||||
            check("EnumOrdinal", CheckSeverity.OFF) // For now. We could replace most of these with EnumMap.
 | 
			
		||||
            check("OperatorPrecedence", CheckSeverity.OFF) // For now.
 | 
			
		||||
            check("NonOverridingEquals", CheckSeverity.OFF) // Peripheral.equals makes this hard to avoid
 | 
			
		||||
            check("FutureReturnValueIgnored", CheckSeverity.OFF) // Too many false positives with Netty
 | 
			
		||||
            check("InvalidInlineTag", CheckSeverity.OFF) // Triggered by @snippet. Can be removed on Java 21.
 | 
			
		||||
            option("UnusedMethod:ExemptingMethodAnnotations", "dan200.computercraft.api.lua.LuaFunction")
 | 
			
		||||
 | 
			
		||||
            check("NullAway", CheckSeverity.ERROR)
 | 
			
		||||
            option(
 | 
			
		||||
@@ -128,6 +122,7 @@ tasks.compileTestJava {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
tasks.withType(JavaCompile::class.java).configureEach {
 | 
			
		||||
    options.encoding = "UTF-8"
 | 
			
		||||
}
 | 
			
		||||
@@ -176,7 +171,7 @@ tasks.test {
 | 
			
		||||
 | 
			
		||||
tasks.withType(JacocoReport::class.java).configureEach {
 | 
			
		||||
    reports.xml.required = true
 | 
			
		||||
    reports.html.required = true
 | 
			
		||||
    reports.html.required =true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
project.plugins.withType(CCTweakedPlugin::class.java) {
 | 
			
		||||
@@ -200,23 +195,30 @@ spotless {
 | 
			
		||||
    fun FormatExtension.defaults() {
 | 
			
		||||
        endWithNewline()
 | 
			
		||||
        trimTrailingWhitespace()
 | 
			
		||||
        leadingTabsToSpaces(4)
 | 
			
		||||
        indentWithSpaces(4)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    java {
 | 
			
		||||
        defaults()
 | 
			
		||||
        importOrder("", "javax|java", "\\#")
 | 
			
		||||
        removeUnusedImports()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val ktlintConfig = mapOf(
 | 
			
		||||
        "ktlint_standard_no-wildcard-imports" to "disabled",
 | 
			
		||||
        "ktlint_standard_class-naming" to "disabled",
 | 
			
		||||
        "ktlint_standard_function-naming" to "disabled",
 | 
			
		||||
        "ij_kotlin_allow_trailing_comma" to "true",
 | 
			
		||||
        "ij_kotlin_allow_trailing_comma_on_call_site" to "true",
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    kotlinGradle {
 | 
			
		||||
        defaults()
 | 
			
		||||
        ktlint()
 | 
			
		||||
        ktlint().editorConfigOverride(ktlintConfig)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    kotlin {
 | 
			
		||||
        defaults()
 | 
			
		||||
        ktlint()
 | 
			
		||||
        ktlint().editorConfigOverride(ktlintConfig)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,6 @@
 | 
			
		||||
 * See notes in [cc.tweaked.gradle.MinecraftConfigurations] for the general design behind these cursed ideas.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import cc.tweaked.gradle.CCTweakedPlugin
 | 
			
		||||
import cc.tweaked.gradle.MinecraftConfigurations
 | 
			
		||||
import cc.tweaked.gradle.clientClasses
 | 
			
		||||
import cc.tweaked.gradle.commonClasses
 | 
			
		||||
@@ -53,5 +52,3 @@ dependencies {
 | 
			
		||||
 | 
			
		||||
    testImplementation(testFixtures(project))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
kotlin.compilerOptions.jvmTarget = CCTweakedPlugin.KOTLIN_TARGET
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,9 @@ import org.gradle.api.GradleException
 | 
			
		||||
import org.gradle.api.NamedDomainObjectProvider
 | 
			
		||||
import org.gradle.api.Project
 | 
			
		||||
import org.gradle.api.Task
 | 
			
		||||
import org.gradle.api.artifacts.Dependency
 | 
			
		||||
import org.gradle.api.plugins.JavaPluginExtension
 | 
			
		||||
import org.gradle.api.provider.ListProperty
 | 
			
		||||
import org.gradle.api.provider.Provider
 | 
			
		||||
import org.gradle.api.provider.SetProperty
 | 
			
		||||
import org.gradle.api.tasks.SourceSet
 | 
			
		||||
@@ -31,10 +33,6 @@ import java.net.URI
 | 
			
		||||
import java.util.regex.Pattern
 | 
			
		||||
 | 
			
		||||
abstract class CCTweakedExtension(private val project: Project) {
 | 
			
		||||
    /** Get the hash of the latest git commit. */
 | 
			
		||||
    val gitHash: Provider<String> =
 | 
			
		||||
        gitProvider("<no git commit>", listOf("rev-parse", "HEAD")) { it.trim() }
 | 
			
		||||
 | 
			
		||||
    /** Get the current git branch. */
 | 
			
		||||
    val gitBranch: Provider<String> =
 | 
			
		||||
        gitProvider("<no git branch>", listOf("rev-parse", "--abbrev-ref", "HEAD")) { it.trim() }
 | 
			
		||||
@@ -166,7 +164,6 @@ abstract class CCTweakedExtension(private val project: Project) {
 | 
			
		||||
            jacoco.applyTo(this)
 | 
			
		||||
 | 
			
		||||
            extensions.configure(JacocoTaskExtension::class.java) {
 | 
			
		||||
                includes = listOf("dan200.computercraft.*")
 | 
			
		||||
                excludes = listOf(
 | 
			
		||||
                    "dan200.computercraft.mixin.*", // Exclude mixins, as they're not executed at runtime.
 | 
			
		||||
                    "dan200.computercraft.shared.Capabilities$*", // Exclude capability tokens, as Forge rewrites them.
 | 
			
		||||
@@ -223,7 +220,7 @@ abstract class CCTweakedExtension(private val project: Project) {
 | 
			
		||||
        ).resolve().single()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun <T: Any> gitProvider(default: T, command: List<String>, process: (String) -> T): Provider<T> {
 | 
			
		||||
    private fun <T> gitProvider(default: T, command: List<String>, process: (String) -> T): Provider<T> {
 | 
			
		||||
        val baseResult = project.providers.exec {
 | 
			
		||||
            commandLine = listOf("git", "-C", project.rootDir.absolutePath) + command
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,6 @@
 | 
			
		||||
 | 
			
		||||
package cc.tweaked.gradle
 | 
			
		||||
 | 
			
		||||
import org.gradle.api.JavaVersion
 | 
			
		||||
import org.gradle.api.Plugin
 | 
			
		||||
import org.gradle.api.Project
 | 
			
		||||
import org.gradle.api.plugins.JavaPlugin
 | 
			
		||||
@@ -14,7 +13,6 @@ import org.gradle.plugins.ide.idea.model.IdeaModel
 | 
			
		||||
import org.jetbrains.gradle.ext.IdeaExtPlugin
 | 
			
		||||
import org.jetbrains.gradle.ext.runConfigurations
 | 
			
		||||
import org.jetbrains.gradle.ext.settings
 | 
			
		||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Configures projects to match a shared configuration.
 | 
			
		||||
@@ -44,17 +42,6 @@ class CCTweakedPlugin : Plugin<Project> {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        /**
 | 
			
		||||
         * The version we run with. We use Java 21 here, as our Gradle build requires that.
 | 
			
		||||
         */
 | 
			
		||||
        val JDK_VERSION = JavaLanguageVersion.of(21)
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The Java version we target. Should be the same as what Minecraft uses.
 | 
			
		||||
         */
 | 
			
		||||
        val JAVA_TARGET = JavaLanguageVersion.of(17)
 | 
			
		||||
 | 
			
		||||
        val JAVA_VERSION = JavaVersion.toVersion(JAVA_TARGET.asInt())
 | 
			
		||||
        val KOTLIN_TARGET = JvmTarget.fromTarget(JAVA_TARGET.toString())
 | 
			
		||||
        val JAVA_VERSION = JavaLanguageVersion.of(21)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -45,10 +45,6 @@ abstract class DependencyCheck : DefaultTask() {
 | 
			
		||||
        overrides.putAll(project.provider { mutableMapOf(module.get().module.toString() to version) })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun override(group: String, module: String, version: String) {
 | 
			
		||||
        overrides.put("$group:$module", version)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add a configuration to check.
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -5,8 +5,10 @@
 | 
			
		||||
package cc.tweaked.gradle
 | 
			
		||||
 | 
			
		||||
import org.gradle.api.file.DirectoryProperty
 | 
			
		||||
import org.gradle.api.provider.Property
 | 
			
		||||
import org.gradle.api.tasks.AbstractExecTask
 | 
			
		||||
import org.gradle.api.tasks.OutputDirectory
 | 
			
		||||
import java.io.File
 | 
			
		||||
 | 
			
		||||
abstract class ExecToDir : AbstractExecTask<ExecToDir>(ExecToDir::class.java) {
 | 
			
		||||
    @get:OutputDirectory
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@ import org.gradle.api.file.FileSystemLocation
 | 
			
		||||
import org.gradle.api.provider.Property
 | 
			
		||||
import org.gradle.api.provider.Provider
 | 
			
		||||
import org.gradle.api.tasks.JavaExec
 | 
			
		||||
import org.gradle.kotlin.dsl.getByName
 | 
			
		||||
import org.gradle.process.BaseExecSpec
 | 
			
		||||
import org.gradle.process.JavaExecSpec
 | 
			
		||||
import org.gradle.process.ProcessForkOptions
 | 
			
		||||
@@ -46,6 +47,21 @@ fun JavaExec.copyToFull(spec: JavaExec) {
 | 
			
		||||
    copyToExec(spec)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Base this [JavaExec] task on an existing task.
 | 
			
		||||
 */
 | 
			
		||||
fun JavaExec.copyFromTask(task: JavaExec) {
 | 
			
		||||
    for (dep in task.dependsOn) dependsOn(dep)
 | 
			
		||||
    task.copyToFull(this)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Base this [JavaExec] task on an existing task.
 | 
			
		||||
 */
 | 
			
		||||
fun JavaExec.copyFromTask(task: String) {
 | 
			
		||||
    copyFromTask(project.tasks.getByName<JavaExec>(task))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Copy additional [BaseExecSpec] options which aren't handled by [ProcessForkOptions.copyTo].
 | 
			
		||||
 */
 | 
			
		||||
@@ -124,7 +140,7 @@ class CloseScope : AutoCloseable {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** Proxy method to avoid overload ambiguity. */
 | 
			
		||||
fun <T: Any> Property<T >.setProvider(provider: Provider<out T>) = set(provider)
 | 
			
		||||
fun <T> Property<T>.setProvider(provider: Provider<out T>) = set(provider)
 | 
			
		||||
 | 
			
		||||
/** Short-cut method to get the absolute path of a [FileSystemLocation] provider. */
 | 
			
		||||
fun Provider<out FileSystemLocation>.getAbsolutePath(): String = get().asFile.absolutePath
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,7 @@ import javax.xml.xpath.XPathFactory
 | 
			
		||||
 * Would be good to PR some (or all) of these changes upstream at some point.
 | 
			
		||||
 *
 | 
			
		||||
 * @see net.fabricmc.loom.configuration.ide.idea.IdeaSyncTask
 | 
			
		||||
 * @see net.minecraftforge.gradle.common.util.runs.IntellijRunGenerator
 | 
			
		||||
 */
 | 
			
		||||
internal class IdeaRunConfigurations(project: Project) {
 | 
			
		||||
    private val rootProject = project.rootProject
 | 
			
		||||
@@ -34,6 +35,22 @@ internal class IdeaRunConfigurations(project: Project) {
 | 
			
		||||
    private val writer = TransformerFactory.newInstance().newTransformer()
 | 
			
		||||
 | 
			
		||||
    private val ideaDir = rootProject.file(".idea/")
 | 
			
		||||
    private val buildDir: Lazy<String?> = lazy {
 | 
			
		||||
        val ideaMisc = ideaDir.resolve("misc.xml")
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            val doc = Files.newBufferedReader(ideaMisc.toPath()).use {
 | 
			
		||||
                documentBuilder.parse(InputSource(it))
 | 
			
		||||
            }
 | 
			
		||||
            val node =
 | 
			
		||||
                xpath.evaluate("//component[@name=\"ProjectRootManager\"]/output", doc, XPathConstants.NODE) as Node
 | 
			
		||||
            val attr = node.attributes.getNamedItem("url") as Attr
 | 
			
		||||
            attr.value.removePrefix("file://")
 | 
			
		||||
        } catch (e: Exception) {
 | 
			
		||||
            LOGGER.error("Failed to find root directory", e)
 | 
			
		||||
            null
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun patch() = synchronized(LOCK) {
 | 
			
		||||
        val runConfigDir = ideaDir.resolve("runConfigurations")
 | 
			
		||||
@@ -41,9 +58,10 @@ internal class IdeaRunConfigurations(project: Project) {
 | 
			
		||||
 | 
			
		||||
        Files.list(runConfigDir.toPath()).use {
 | 
			
		||||
            for (configuration in it) {
 | 
			
		||||
                val filename = configuration.fileName.toString()
 | 
			
		||||
                val filename = configuration.fileName.toString();
 | 
			
		||||
                when {
 | 
			
		||||
                    filename.endsWith("_fabric.xml") -> patchFabric(configuration)
 | 
			
		||||
                    filename.startsWith("forge_") && filename.endsWith(".xml") -> patchForge(configuration)
 | 
			
		||||
                    else -> {}
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
@@ -54,6 +72,65 @@ internal class IdeaRunConfigurations(project: Project) {
 | 
			
		||||
        setXml("//configuration", "folderName") { "Fabric" }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun patchForge(path: Path) = withXml(path) {
 | 
			
		||||
        val configId = path.fileName.toString().removePrefix("forge_").removeSuffix(".xml")
 | 
			
		||||
        val sourceSet = forgeConfigs[configId]
 | 
			
		||||
        if (sourceSet == null) {
 | 
			
		||||
            LOGGER.error("[{}] Cannot map run configuration to a known source set", path)
 | 
			
		||||
            return@withXml
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        setXml("//configuration", "folderName") { "Forge" }
 | 
			
		||||
        setXml("//configuration/module", "name") { "${rootProject.name}.forge.$sourceSet" }
 | 
			
		||||
 | 
			
		||||
        if (buildDir.value == null) return@withXml
 | 
			
		||||
        setXml("//configuration/envs/env[@name=\"MOD_CLASSES\"]", "value") { classpath ->
 | 
			
		||||
            val classes = classpath!!.split(':')
 | 
			
		||||
            val newClasses = mutableListOf<String>()
 | 
			
		||||
            fun appendUnique(x: String) {
 | 
			
		||||
                if (!newClasses.contains(x)) newClasses.add(x)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            for (entry in classes) {
 | 
			
		||||
                if (!entry.contains("/out/")) {
 | 
			
		||||
                    appendUnique(entry)
 | 
			
		||||
                    continue
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                val match = CLASSPATH_ENTRY.matchEntire(entry)
 | 
			
		||||
                if (match != null) {
 | 
			
		||||
                    val modId = match.groups["modId"]!!.value
 | 
			
		||||
                    val proj = match.groups["proj"]!!.value
 | 
			
		||||
                    var component = match.groups["component"]!!.value
 | 
			
		||||
                    if (component == "production") component = "main"
 | 
			
		||||
 | 
			
		||||
                    appendUnique(forgeModEntry(modId, proj, component))
 | 
			
		||||
                } else {
 | 
			
		||||
                    LOGGER.warn("[{}] Unknown classpath entry {}", path, entry)
 | 
			
		||||
                    appendUnique(entry)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Ensure common code is on the classpath
 | 
			
		||||
            for (proj in listOf("common", "common-api")) {
 | 
			
		||||
                for (component in listOf("main", "client")) {
 | 
			
		||||
                    appendUnique(forgeModEntry("computercraft", proj, component))
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (newClasses.any { it.startsWith("cctest%%") }) {
 | 
			
		||||
                appendUnique(forgeModEntry("cctest", "core", "testFixtures"))
 | 
			
		||||
                appendUnique(forgeModEntry("cctest", "common", "testFixtures"))
 | 
			
		||||
                appendUnique(forgeModEntry("cctest", "common", "testMod"))
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            newClasses.joinToString(":")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun forgeModEntry(mod: String, project: String, component: String) =
 | 
			
		||||
        "$mod%%${buildDir.value}/production/${rootProject.name}.$project.$component"
 | 
			
		||||
 | 
			
		||||
    private fun LocatedDocument.setXml(xpath: String, attribute: String, value: (String?) -> String) {
 | 
			
		||||
        val node = this@IdeaRunConfigurations.xpath.evaluate(xpath, document, XPathConstants.NODE) as Node?
 | 
			
		||||
        if (node == null) {
 | 
			
		||||
@@ -82,5 +159,16 @@ internal class IdeaRunConfigurations(project: Project) {
 | 
			
		||||
    companion object {
 | 
			
		||||
        private val LOGGER = Logging.getLogger(IdeaRunConfigurations::class.java)
 | 
			
		||||
        private val LOCK = Any()
 | 
			
		||||
 | 
			
		||||
        private val CLASSPATH_ENTRY =
 | 
			
		||||
            Regex("(?<modId>[a-z]+)%%\\\$PROJECT_DIR\\\$/projects/(?<proj>[a-z-]+)/out/(?<component>\\w+)/(?<type>[a-z]+)\$")
 | 
			
		||||
 | 
			
		||||
        private val forgeConfigs = mapOf(
 | 
			
		||||
            "runClient" to "client",
 | 
			
		||||
            "runData" to "main",
 | 
			
		||||
            "runGameTestServer" to "testMod",
 | 
			
		||||
            "runServer" to "main",
 | 
			
		||||
            "runTestClient" to "testMod",
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -40,6 +40,7 @@ class MinecraftConfigurations private constructor(private val project: Project)
 | 
			
		||||
 | 
			
		||||
        // Set up an API configuration for clients (to ensure it's consistent with the main source set).
 | 
			
		||||
        val clientApi = configurations.maybeCreate(client.apiConfigurationName).apply {
 | 
			
		||||
            isVisible = false
 | 
			
		||||
            isCanBeConsumed = false
 | 
			
		||||
            isCanBeResolved = false
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@ import java.nio.file.Files
 | 
			
		||||
import java.util.concurrent.TimeUnit
 | 
			
		||||
import java.util.function.Supplier
 | 
			
		||||
import javax.inject.Inject
 | 
			
		||||
import kotlin.collections.set
 | 
			
		||||
import kotlin.random.Random
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -125,6 +126,23 @@ abstract class ClientJavaExec : JavaExec() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Configure Iris to use Complementary Shaders.
 | 
			
		||||
     */
 | 
			
		||||
    fun withComplementaryShaders() {
 | 
			
		||||
        val cct = project.extensions.getByType(CCTweakedExtension::class.java)
 | 
			
		||||
 | 
			
		||||
        withFileFrom(workingDir.resolve("shaderpacks/ComplementaryShaders_v4.6.zip")) {
 | 
			
		||||
            cct.downloadFile("Complementary Shaders", "https://edge.forgecdn.net/files/3951/170/ComplementaryShaders_v4.6.zip")
 | 
			
		||||
        }
 | 
			
		||||
        withFileContents(workingDir.resolve("config/iris.properties")) {
 | 
			
		||||
            """
 | 
			
		||||
            enableShaders=true
 | 
			
		||||
            shaderPack=ComplementaryShaders_v4.6.zip
 | 
			
		||||
            """.trimIndent()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @TaskAction
 | 
			
		||||
    override fun exec() {
 | 
			
		||||
        Files.createDirectories(workingDir.toPath())
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										16
									
								
								buildSrc/src/main/kotlin/cc/tweaked/gradle/XmlUtil.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								buildSrc/src/main/kotlin/cc/tweaked/gradle/XmlUtil.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package cc.tweaked.gradle
 | 
			
		||||
 | 
			
		||||
import groovy.util.Node
 | 
			
		||||
import groovy.util.NodeList
 | 
			
		||||
 | 
			
		||||
object XmlUtil {
 | 
			
		||||
    fun findChild(node: Node, name: String): Node? = when (val child = node.get(name)) {
 | 
			
		||||
        is Node -> child
 | 
			
		||||
        is NodeList -> child.singleOrNull() as Node?
 | 
			
		||||
        else -> null
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -21,6 +21,15 @@ SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
        <property name="file" value="${config_loc}/suppressions.xml" />
 | 
			
		||||
    </module>
 | 
			
		||||
 | 
			
		||||
    <!--
 | 
			
		||||
        Checkstyle doesn't support @snippet (https://github.com/checkstyle/checkstyle/issues/11455),
 | 
			
		||||
        so suppress warnings nearby
 | 
			
		||||
    -->
 | 
			
		||||
    <module name="SuppressWithNearbyTextFilter">
 | 
			
		||||
        <property name="nearbyTextPattern" value="@snippet" />
 | 
			
		||||
        <property name="lineRange" value="20" />
 | 
			
		||||
    </module>
 | 
			
		||||
 | 
			
		||||
    <module name="BeforeExecutionExclusionFileFilter">
 | 
			
		||||
        <property name="fileNamePattern" value="render_old"/>
 | 
			
		||||
    </module>
 | 
			
		||||
@@ -92,10 +101,7 @@ SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
        <module name="InvalidJavadocPosition" />
 | 
			
		||||
        <module name="JavadocBlockTagLocation" />
 | 
			
		||||
        <module name="JavadocMethod"/>
 | 
			
		||||
        <module name="JavadocType">
 | 
			
		||||
            <!-- Seems to complain about @hidden!? -->
 | 
			
		||||
            <property name="allowUnknownTags" value="true" />
 | 
			
		||||
        </module>
 | 
			
		||||
        <module name="JavadocType"/>
 | 
			
		||||
        <module name="JavadocStyle">
 | 
			
		||||
            <property name="checkHtml" value="false" />
 | 
			
		||||
        </module>
 | 
			
		||||
@@ -146,10 +152,7 @@ SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
        <module name="NoWhitespaceAfter">
 | 
			
		||||
            <property name="tokens" value="AT,INC,DEC,UNARY_MINUS,UNARY_PLUS,BNOT,LNOT,DOT,ARRAY_DECLARATOR,INDEX_OP,METHOD_REF" />
 | 
			
		||||
        </module>
 | 
			
		||||
        <module name="NoWhitespaceBefore">
 | 
			
		||||
            <!-- Allow whitespace before "..." for @Nullable annotations -->
 | 
			
		||||
            <property name="tokens" value="COMMA,SEMI,POST_INC,POST_DEC,LABELED_STAT" />
 | 
			
		||||
        </module>
 | 
			
		||||
        <module name="NoWhitespaceBefore" />
 | 
			
		||||
        <!-- TODO: Decide on an OperatorWrap style. -->
 | 
			
		||||
        <module name="ParenPad" />
 | 
			
		||||
        <module name="SeparatorWrap">
 | 
			
		||||
 
 | 
			
		||||
@@ -22,4 +22,7 @@ SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
    <!-- Allow underscores in our test classes. -->
 | 
			
		||||
    <suppress checks="MethodName" files=".*(Contract|Test).java" />
 | 
			
		||||
 | 
			
		||||
    <!-- Allow underscores in Mixin classes -->
 | 
			
		||||
    <suppress checks="TypeName" files=".*[\\/]mixin[\\/].*.java" />
 | 
			
		||||
</suppressions>
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,6 @@ files:
 | 
			
		||||
        de: de_de    # German
 | 
			
		||||
        es-ES: es_es # Spanish
 | 
			
		||||
        fr: fr_fr    # French
 | 
			
		||||
        hu: hu_hu    # Hungarian
 | 
			
		||||
        it: it_it    # Italian
 | 
			
		||||
        ja: ja_jp    # Japanese
 | 
			
		||||
        ko: ko_kr    # Korean
 | 
			
		||||
 
 | 
			
		||||
@@ -8,9 +8,10 @@ org.gradle.parallel=true
 | 
			
		||||
kotlin.stdlib.default.dependency=false
 | 
			
		||||
kotlin.jvm.target.validation.mode=error
 | 
			
		||||
 | 
			
		||||
# Mod properties
 | 
			
		||||
isUnstable=false
 | 
			
		||||
modVersion=1.116.2
 | 
			
		||||
neogradle.subsystems.conventions.runs.enabled=false
 | 
			
		||||
 | 
			
		||||
isUnstable=true
 | 
			
		||||
modVersion=1.114.3
 | 
			
		||||
 | 
			
		||||
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
 | 
			
		||||
mcVersion=1.20.1
 | 
			
		||||
mcVersion=1.21.1
 | 
			
		||||
 
 | 
			
		||||
@@ -6,75 +6,77 @@
 | 
			
		||||
 | 
			
		||||
# 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"
 | 
			
		||||
# Remember to update corresponding versions in fabric.mod.json/neoforge.mods.toml
 | 
			
		||||
fabric-api = "0.102.1+1.21.1"
 | 
			
		||||
fabric-loader = "0.15.11"
 | 
			
		||||
neoForge = "21.1.9"
 | 
			
		||||
neoForgeSpi = "8.0.1"
 | 
			
		||||
mixin = "0.8.5"
 | 
			
		||||
parchment = "2023.08.20"
 | 
			
		||||
parchmentMc = "1.20.1"
 | 
			
		||||
yarn = "1.20.1+build.10"
 | 
			
		||||
parchment = "2024.07.28"
 | 
			
		||||
parchmentMc = "1.21"
 | 
			
		||||
yarn = "1.21.1+build.1"
 | 
			
		||||
 | 
			
		||||
# 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.9"
 | 
			
		||||
 | 
			
		||||
# Core dependencies (independent of Minecraft)
 | 
			
		||||
asm = "9.9"
 | 
			
		||||
asm = "9.6"
 | 
			
		||||
autoService = "1.1.1"
 | 
			
		||||
checkerFramework = "3.51.1"
 | 
			
		||||
cobalt = { strictly = "0.9.7" }
 | 
			
		||||
commonsCli = "1.10.0"
 | 
			
		||||
jetbrainsAnnotations = "26.0.2-1"
 | 
			
		||||
jspecify = "1.0.0"
 | 
			
		||||
checkerFramework = "3.42.0"
 | 
			
		||||
cobalt = { strictly = "0.9.5" }
 | 
			
		||||
commonsCli = "1.6.0"
 | 
			
		||||
jetbrainsAnnotations = "24.1.0"
 | 
			
		||||
jsr305 = "3.0.2"
 | 
			
		||||
jzlib = "1.1.3"
 | 
			
		||||
kotlin = "2.2.21"
 | 
			
		||||
kotlin-coroutines = "1.10.2"
 | 
			
		||||
nightConfig = "3.8.3"
 | 
			
		||||
kotlin = "2.1.0"
 | 
			
		||||
kotlin-coroutines = "1.10.1"
 | 
			
		||||
nightConfig = "3.8.1"
 | 
			
		||||
 | 
			
		||||
# Minecraft mods
 | 
			
		||||
emi = "1.0.8+1.20.1"
 | 
			
		||||
fabricPermissions = "0.3.20230723"
 | 
			
		||||
iris = "1.6.4+1.20"
 | 
			
		||||
jei = "15.2.0.22"
 | 
			
		||||
modmenu = "7.1.0"
 | 
			
		||||
emi = "1.1.7+1.21"
 | 
			
		||||
fabricPermissions = "0.3.1"
 | 
			
		||||
iris-fabric = "1.8.0-beta.3+1.21-fabric"
 | 
			
		||||
iris-forge = "1.8.0-beta.3+1.21-neoforge"
 | 
			
		||||
jei = "19.8.2.99"
 | 
			
		||||
modmenu = "11.0.0-rc.4"
 | 
			
		||||
moreRed = "4.0.0.4"
 | 
			
		||||
oculus = "1.2.5"
 | 
			
		||||
rei = "12.0.626"
 | 
			
		||||
rubidium = "0.6.1"
 | 
			
		||||
sodium = "mc1.20-0.4.10"
 | 
			
		||||
create-forge = "6.0.0-9"
 | 
			
		||||
create-fabric = "6.0.7.0+mc1.20.1-build.1716"
 | 
			
		||||
rei = "16.0.729"
 | 
			
		||||
sodium-fabric = "mc1.21-0.6.0-beta.1-fabric"
 | 
			
		||||
sodium-forge = "mc1.21-0.6.0-beta.1-neoforge"
 | 
			
		||||
mixinExtra = "0.3.5"
 | 
			
		||||
create-forge = "0.5.1.f-33"
 | 
			
		||||
create-fabric = "0.5.1-f-build.1467+mc1.20.1"
 | 
			
		||||
 | 
			
		||||
# Testing
 | 
			
		||||
hamcrest = "3.0"
 | 
			
		||||
jqwik = "1.9.3"
 | 
			
		||||
junit = "6.0.1"
 | 
			
		||||
junitPlatform = "6.0.1"
 | 
			
		||||
hamcrest = "2.2"
 | 
			
		||||
jqwik = "1.8.2"
 | 
			
		||||
junit = "5.11.4"
 | 
			
		||||
junitPlatform = "1.11.4"
 | 
			
		||||
jmh = "1.37"
 | 
			
		||||
 | 
			
		||||
# Build tools
 | 
			
		||||
cctJavadoc = "1.8.4"
 | 
			
		||||
checkstyle = "12.1.1"
 | 
			
		||||
errorProne-core = "2.43.0"
 | 
			
		||||
errorProne-plugin = "4.3.0"
 | 
			
		||||
fabric-loom = "1.12.3"
 | 
			
		||||
cctJavadoc = "1.8.3"
 | 
			
		||||
checkstyle = "10.20.1"
 | 
			
		||||
errorProne-core = "2.27.0"
 | 
			
		||||
errorProne-plugin = "3.1.0"
 | 
			
		||||
fabric-loom = "1.9.2"
 | 
			
		||||
githubRelease = "2.5.2"
 | 
			
		||||
gradleVersions = "0.53.0"
 | 
			
		||||
ideaExt = "1.3"
 | 
			
		||||
illuaminate = "0.1.0-83-g1131f68"
 | 
			
		||||
lwjgl = "3.3.6"
 | 
			
		||||
gradleVersions = "0.50.0"
 | 
			
		||||
ideaExt = "1.1.7"
 | 
			
		||||
illuaminate = "0.1.0-74-gf1551d5"
 | 
			
		||||
lwjgl = "3.3.3"
 | 
			
		||||
minotaur = "2.8.7"
 | 
			
		||||
modDevGradle = "2.0.116"
 | 
			
		||||
nullAway = "0.12.11"
 | 
			
		||||
shadow = "9.2.2"
 | 
			
		||||
spotless = "8.0.0"
 | 
			
		||||
teavm = "0.13.0-SQUID.2"
 | 
			
		||||
vanillaExtract = "0.2.1"
 | 
			
		||||
versionCatalogUpdate = "1.0.1"
 | 
			
		||||
modDevGradle = "2.0.74"
 | 
			
		||||
nullAway = "0.10.25"
 | 
			
		||||
shadow = "8.3.1"
 | 
			
		||||
spotless = "6.23.3"
 | 
			
		||||
taskTree = "2.1.1"
 | 
			
		||||
teavm = "0.11.0-SQUID.1"
 | 
			
		||||
vanillaExtract = "0.2.0"
 | 
			
		||||
versionCatalogUpdate = "0.8.1"
 | 
			
		||||
 | 
			
		||||
[libraries]
 | 
			
		||||
# Normal dependencies
 | 
			
		||||
@@ -85,10 +87,10 @@ 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" }
 | 
			
		||||
jspecify = { module = "org.jspecify:jspecify", version.ref = "jspecify" }
 | 
			
		||||
jsr305 = { module = "com.google.code.findbugs:jsr305", version.ref = "jsr305" }
 | 
			
		||||
jzlib = { module = "com.jcraft:jzlib", version.ref = "jzlib" }
 | 
			
		||||
kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlin-coroutines" }
 | 
			
		||||
kotlin-platform = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "kotlin" }
 | 
			
		||||
@@ -109,19 +111,20 @@ fabric-api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fab
 | 
			
		||||
fabric-junit = { module = "net.fabricmc:fabric-loader-junit", version.ref = "fabric-loader" }
 | 
			
		||||
fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" }
 | 
			
		||||
fabricPermissions = { module = "me.lucko:fabric-permissions-api", version.ref = "fabricPermissions" }
 | 
			
		||||
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" }
 | 
			
		||||
iris-fabric = { module = "maven.modrinth:iris", version.ref = "iris-fabric" }
 | 
			
		||||
iris-forge = { module = "maven.modrinth:iris", version.ref = "iris-forge" }
 | 
			
		||||
jei-api = { module = "mezz.jei:jei-1.21-common-api", version.ref = "jei" }
 | 
			
		||||
jei-fabric = { module = "mezz.jei:jei-1.21-fabric", version.ref = "jei" }
 | 
			
		||||
jei-forge = { module = "mezz.jei:jei-1.21-neoforge", version.ref = "jei" }
 | 
			
		||||
mixin = { module = "org.spongepowered:mixin", version.ref = "mixin" }
 | 
			
		||||
mixinExtra = { module = "io.github.llamalad7:mixinextras-common", version.ref = "mixinExtra" }
 | 
			
		||||
modmenu = { module = "com.terraformersmc:modmenu", version.ref = "modmenu" }
 | 
			
		||||
moreRed = { module = "commoble.morered:morered-1.20.1", version.ref = "moreRed" }
 | 
			
		||||
oculus = { module = "maven.modrinth:oculus", version.ref = "oculus" }
 | 
			
		||||
rei-api = { module = "me.shedaniel:RoughlyEnoughItems-api", version.ref = "rei" }
 | 
			
		||||
rei-builtin = { module = "me.shedaniel:RoughlyEnoughItems-default-plugin", version.ref = "rei" }
 | 
			
		||||
rei-fabric = { module = "me.shedaniel:RoughlyEnoughItems-fabric", version.ref = "rei" }
 | 
			
		||||
rubidium = { module = "maven.modrinth:rubidium", version.ref = "rubidium" }
 | 
			
		||||
sodium = { module = "maven.modrinth:sodium", version.ref = "sodium" }
 | 
			
		||||
sodium-fabric = { module = "maven.modrinth:sodium", version.ref = "sodium.fabric" }
 | 
			
		||||
sodium-forge = { module = "maven.modrinth:sodium", version.ref = "sodium.forge" }
 | 
			
		||||
 | 
			
		||||
# Testing
 | 
			
		||||
hamcrest = { module = "org.hamcrest:hamcrest", version.ref = "hamcrest" }
 | 
			
		||||
@@ -173,17 +176,18 @@ githubRelease = { id = "com.github.breadmoirai.github-release", version.ref = "g
 | 
			
		||||
gradleVersions = { id = "com.github.ben-manes.versions", version.ref = "gradleVersions" }
 | 
			
		||||
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
 | 
			
		||||
shadow = { id = "com.gradleup.shadow", version.ref = "shadow" }
 | 
			
		||||
taskTree = { id = "com.dorongold.task-tree", version.ref = "taskTree" }
 | 
			
		||||
versionCatalogUpdate = { id = "nl.littlerobots.version-catalog-update", version.ref = "versionCatalogUpdate" }
 | 
			
		||||
 | 
			
		||||
[bundles]
 | 
			
		||||
annotations = ["checkerFramework", "jetbrainsAnnotations", "jspecify"]
 | 
			
		||||
annotations = ["jsr305", "checkerFramework", "jetbrainsAnnotations"]
 | 
			
		||||
kotlin = ["kotlin-stdlib", "kotlin-coroutines"]
 | 
			
		||||
 | 
			
		||||
# Minecraft
 | 
			
		||||
externalMods-common = ["jei-api", "nightConfig-core", "nightConfig-toml"]
 | 
			
		||||
externalMods-forge-compile = ["moreRed", "oculus", "jei-api"]
 | 
			
		||||
externalMods-common = ["iris-forge", "jei-api", "nightConfig-core", "nightConfig-toml"]
 | 
			
		||||
externalMods-forge-compile = ["moreRed", "iris-forge", "jei-api"]
 | 
			
		||||
externalMods-forge-runtime = ["jei-forge"]
 | 
			
		||||
externalMods-fabric-compile = ["fabricPermissions", "iris", "jei-api", "rei-api", "rei-builtin"]
 | 
			
		||||
externalMods-fabric-compile = ["fabricPermissions", "iris-fabric", "jei-api", "rei-api", "rei-builtin"]
 | 
			
		||||
externalMods-fabric-runtime = ["jei-fabric", "modmenu"]
 | 
			
		||||
 | 
			
		||||
# Testing
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							@@ -1,6 +1,6 @@
 | 
			
		||||
distributionBase=GRADLE_USER_HOME
 | 
			
		||||
distributionPath=wrapper/dists
 | 
			
		||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip
 | 
			
		||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
 | 
			
		||||
networkTimeout=10000
 | 
			
		||||
validateDistributionUrl=true
 | 
			
		||||
zipStoreBase=GRADLE_USER_HOME
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1064
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1064
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -12,11 +12,11 @@
 | 
			
		||||
    "tslib": "^2.0.3"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@rollup/plugin-node-resolve": "^16.0.0",
 | 
			
		||||
    "@rollup/plugin-node-resolve": "^15.2.1",
 | 
			
		||||
    "@rollup/plugin-typescript": "^12.0.0",
 | 
			
		||||
    "@rollup/plugin-url": "^8.0.1",
 | 
			
		||||
    "@swc/core": "^1.3.92",
 | 
			
		||||
    "@types/node": "^24.0.0",
 | 
			
		||||
    "@types/node": "^22.0.0",
 | 
			
		||||
    "lightningcss": "^1.22.0",
 | 
			
		||||
    "preact-render-to-string": "^6.2.1",
 | 
			
		||||
    "rehype": "^13.0.0",
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,84 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2024 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.impl.client.ClientPlatformHelper;
 | 
			
		||||
import net.minecraft.client.resources.model.BakedModel;
 | 
			
		||||
import net.minecraft.client.resources.model.ModelManager;
 | 
			
		||||
import net.minecraft.client.resources.model.ModelResourceLocation;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import org.jetbrains.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import java.util.stream.Stream;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The location of a model to load. This may either be:
 | 
			
		||||
 *
 | 
			
		||||
 * <ul>
 | 
			
		||||
 *     <li>A {@link ModelResourceLocation}, referencing an already baked model (such as {@code minecraft:dirt#inventory}).</li>
 | 
			
		||||
 *     <li>
 | 
			
		||||
 *         A {@link ResourceLocation}, referencing a path to a model resource (such as {@code minecraft:item/dirt}.
 | 
			
		||||
 *         These models will be baked and stored in the {@link ModelManager} in a loader-specific way.
 | 
			
		||||
 *     </li>
 | 
			
		||||
 * </ul>
 | 
			
		||||
 */
 | 
			
		||||
public final class ModelLocation {
 | 
			
		||||
    /**
 | 
			
		||||
     * The location of the model.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * When {@link #resourceLocation} is null, this is the location of the model to load. When {@link #resourceLocation}
 | 
			
		||||
     * is non-null, this is the "standalone" variant of the model resource — this is used by NeoForge's implementation
 | 
			
		||||
     * of {@link ClientPlatformHelper#getModel(ModelManager, ModelResourceLocation, ResourceLocation)} to fetch the
 | 
			
		||||
     * model from the model manger. It is not used on Fabric.
 | 
			
		||||
     */
 | 
			
		||||
    private final ModelResourceLocation modelLocation;
 | 
			
		||||
    private final @Nullable ResourceLocation resourceLocation;
 | 
			
		||||
 | 
			
		||||
    private ModelLocation(ModelResourceLocation modelLocation, @Nullable ResourceLocation resourceLocation) {
 | 
			
		||||
        this.modelLocation = modelLocation;
 | 
			
		||||
        this.resourceLocation = resourceLocation;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a {@link ModelLocation} from model in the model manager.
 | 
			
		||||
     *
 | 
			
		||||
     * @param location The name of the model to load.
 | 
			
		||||
     * @return The new {@link ModelLocation} instance.
 | 
			
		||||
     */
 | 
			
		||||
    public static ModelLocation ofModel(ModelResourceLocation location) {
 | 
			
		||||
        return new ModelLocation(location, null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a {@link ModelLocation} from a resource.
 | 
			
		||||
     *
 | 
			
		||||
     * @param location The location of the model resource, such as {@code minecraft:item/dirt}.
 | 
			
		||||
     * @return The new {@link ModelLocation} instance.
 | 
			
		||||
     */
 | 
			
		||||
    public static ModelLocation ofResource(ResourceLocation location) {
 | 
			
		||||
        return new ModelLocation(new ModelResourceLocation(location, "standalone"), location);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get this model from the provided model manager.
 | 
			
		||||
     *
 | 
			
		||||
     * @param manager The model manger.
 | 
			
		||||
     * @return This model, or the missing model if it could not be found.
 | 
			
		||||
     */
 | 
			
		||||
    public BakedModel getModel(ModelManager manager) {
 | 
			
		||||
        return ClientPlatformHelper.get().getModel(manager, modelLocation, resourceLocation);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the models this model location depends on.
 | 
			
		||||
     *
 | 
			
		||||
     * @return A list of models that this model location depends on.
 | 
			
		||||
     * @see TurtleUpgradeModeller#getDependencies()
 | 
			
		||||
     */
 | 
			
		||||
    public Stream<ResourceLocation> getDependencies() {
 | 
			
		||||
        return resourceLocation == null ? Stream.empty() : Stream.of(resourceLocation);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -13,30 +13,47 @@ import net.minecraft.client.resources.model.ModelResourceLocation;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A model to render, combined with a transformation matrix to apply.
 | 
			
		||||
 *
 | 
			
		||||
 * @param model  The model.
 | 
			
		||||
 * @param matrix The transformation matrix.
 | 
			
		||||
 */
 | 
			
		||||
public final class TransformedModel {
 | 
			
		||||
    private final BakedModel model;
 | 
			
		||||
    private final Transformation matrix;
 | 
			
		||||
 | 
			
		||||
    public TransformedModel(BakedModel model, Transformation matrix) {
 | 
			
		||||
        this.model = Objects.requireNonNull(model);
 | 
			
		||||
        this.matrix = Objects.requireNonNull(matrix);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
public record TransformedModel(BakedModel model, Transformation matrix) {
 | 
			
		||||
    public TransformedModel(BakedModel model) {
 | 
			
		||||
        this.model = Objects.requireNonNull(model);
 | 
			
		||||
        matrix = Transformation.identity();
 | 
			
		||||
        this(model, Transformation.identity());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Look up a model in the model bakery and construct a {@link TransformedModel} with no transformation.
 | 
			
		||||
     *
 | 
			
		||||
     * @param location The location of the model to load.
 | 
			
		||||
     * @return The new {@link TransformedModel} instance.
 | 
			
		||||
     */
 | 
			
		||||
    public static TransformedModel of(ModelLocation location) {
 | 
			
		||||
        var modelManager = Minecraft.getInstance().getModelManager();
 | 
			
		||||
        return new TransformedModel(location.getModel(modelManager));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Look up a model in the model bakery and construct a {@link TransformedModel} with no transformation.
 | 
			
		||||
     *
 | 
			
		||||
     * @param location The location of the model to load.
 | 
			
		||||
     * @return The new {@link TransformedModel} instance.
 | 
			
		||||
     * @see ModelLocation#ofModel(ModelResourceLocation)
 | 
			
		||||
     */
 | 
			
		||||
    public static TransformedModel of(ModelResourceLocation location) {
 | 
			
		||||
        var modelManager = Minecraft.getInstance().getModelManager();
 | 
			
		||||
        return new TransformedModel(modelManager.getModel(location));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Look up a model in the model bakery and construct a {@link TransformedModel} with no transformation.
 | 
			
		||||
     *
 | 
			
		||||
     * @param location The location of the model to load.
 | 
			
		||||
     * @return The new {@link TransformedModel} instance.
 | 
			
		||||
     * @see ModelLocation#ofResource(ResourceLocation)
 | 
			
		||||
     */
 | 
			
		||||
    public static TransformedModel of(ResourceLocation location) {
 | 
			
		||||
        var modelManager = Minecraft.getInstance().getModelManager();
 | 
			
		||||
        return new TransformedModel(ClientPlatformHelper.get().getModel(modelManager, location));
 | 
			
		||||
@@ -46,12 +63,4 @@ public final class TransformedModel {
 | 
			
		||||
        var model = Minecraft.getInstance().getItemRenderer().getItemModelShaper().getItemModel(item);
 | 
			
		||||
        return new TransformedModel(model, transform);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public BakedModel getModel() {
 | 
			
		||||
        return model;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Transformation getMatrix() {
 | 
			
		||||
        return matrix;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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.UpgradeType;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A functional interface to register a {@link TurtleUpgradeModeller} for a class of turtle upgrades.
 | 
			
		||||
@@ -18,9 +18,9 @@ public interface RegisterTurtleUpgradeModeller {
 | 
			
		||||
    /**
 | 
			
		||||
     * Register a {@link TurtleUpgradeModeller}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param serialiser The turtle upgrade serialiser.
 | 
			
		||||
     * @param type     The turtle upgrade type.
 | 
			
		||||
     * @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(UpgradeType<T> type, TurtleUpgradeModeller<T> modeller);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,18 +4,17 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.api.client.turtle;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.client.ModelLocation;
 | 
			
		||||
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.core.component.DataComponentPatch;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import org.jspecify.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.stream.Stream;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides models for a {@link ITurtleUpgrade}.
 | 
			
		||||
@@ -38,47 +37,32 @@ 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, DataComponentPatch data);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get a list of models that this turtle modeller depends on.
 | 
			
		||||
     * Get the models that this turtle modeller depends on.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * Models included in this list will be loaded and baked alongside item and block models, and so may be referenced
 | 
			
		||||
     * Models included in this stream will be loaded and baked alongside item and block models, and so may be referenced
 | 
			
		||||
     * by {@link TransformedModel#of(ResourceLocation)}. You do not need to override this method if you will load models
 | 
			
		||||
     * by other means.
 | 
			
		||||
     *
 | 
			
		||||
     * @return A list of models that this modeller depends on.
 | 
			
		||||
     * @see UnbakedModel#getDependencies()
 | 
			
		||||
     */
 | 
			
		||||
    default Collection<ResourceLocation> getDependencies() {
 | 
			
		||||
        return List.of();
 | 
			
		||||
    default Stream<ResourceLocation> getDependencies() {
 | 
			
		||||
        return Stream.of();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A basic {@link TurtleUpgradeModeller} which renders using the upgrade's {@linkplain ITurtleUpgrade#getUpgradeItem(CompoundTag)}
 | 
			
		||||
     * A basic {@link TurtleUpgradeModeller} which renders using the upgrade's {@linkplain ITurtleUpgrade#getUpgradeItem(DataComponentPatch)}
 | 
			
		||||
     * upgrade item}.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This uses appropriate transformations for "flat" items, namely those extending the {@literal minecraft:item/generated}
 | 
			
		||||
@@ -100,9 +84,8 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
 | 
			
		||||
     * @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);
 | 
			
		||||
    static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ResourceLocation left, ResourceLocation right) {
 | 
			
		||||
        return sided(ModelLocation.ofResource(left), ModelLocation.ofResource(right));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -113,16 +96,16 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
 | 
			
		||||
     * @param <T>   The type of the turtle upgrade.
 | 
			
		||||
     * @return The constructed modeller.
 | 
			
		||||
     */
 | 
			
		||||
    static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ResourceLocation left, ResourceLocation right) {
 | 
			
		||||
    static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ModelLocation left, ModelLocation 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, DataComponentPatch data) {
 | 
			
		||||
                return TransformedModel.of(side == TurtleSide.LEFT ? left : right);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public Collection<ResourceLocation> getDependencies() {
 | 
			
		||||
                return List.of(left, right);
 | 
			
		||||
            public Stream<ResourceLocation> getDependencies() {
 | 
			
		||||
                return Stream.of(left, right).flatMap(ModelLocation::getDependencies);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -11,10 +11,10 @@ import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
			
		||||
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 net.minecraft.core.component.DataComponentPatch;
 | 
			
		||||
import org.joml.Matrix4f;
 | 
			
		||||
import org.jspecify.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
final class TurtleUpgradeModellers {
 | 
			
		||||
    private static final Transformation leftTransform = getMatrixFor(-0.4065f);
 | 
			
		||||
@@ -36,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, DataComponentPatch 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);
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.impl.client;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.client.ModelLocation;
 | 
			
		||||
import dan200.computercraft.impl.Services;
 | 
			
		||||
import net.minecraft.client.renderer.RenderType;
 | 
			
		||||
import net.minecraft.client.resources.model.BakedModel;
 | 
			
		||||
@@ -11,18 +12,34 @@ import net.minecraft.client.resources.model.ModelManager;
 | 
			
		||||
import net.minecraft.client.resources.model.ModelResourceLocation;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import org.jetbrains.annotations.ApiStatus;
 | 
			
		||||
import org.jspecify.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
@ApiStatus.Internal
 | 
			
		||||
public interface ClientPlatformHelper {
 | 
			
		||||
    /**
 | 
			
		||||
     * Equivalent to {@link ModelManager#getModel(ModelResourceLocation)} but for arbitrary {@link ResourceLocation}s.
 | 
			
		||||
     * Get a model from a resource.
 | 
			
		||||
     *
 | 
			
		||||
     * @param manager          The model manager.
 | 
			
		||||
     * @param location The model location.
 | 
			
		||||
     * @param resourceLocation The model resourceLocation.
 | 
			
		||||
     * @return The baked model.
 | 
			
		||||
     * @see ModelLocation
 | 
			
		||||
     */
 | 
			
		||||
    BakedModel getModel(ModelManager manager, ResourceLocation location);
 | 
			
		||||
    BakedModel getModel(ModelManager manager, ResourceLocation resourceLocation);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set a model from a {@link ModelResourceLocation} or {@link ResourceLocation}.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This is largely equivalent to {@code resourceLocation == null ? manager.getModel(modelLocation) : getModel(manager, resourceLocation)},
 | 
			
		||||
     * but allows pre-computing {@code modelLocation} (if needed).
 | 
			
		||||
     *
 | 
			
		||||
     * @param manager          The model manager.
 | 
			
		||||
     * @param modelLocation    The location of the model to load.
 | 
			
		||||
     * @param resourceLocation The location of the resource, if trying to load from a resource.
 | 
			
		||||
     * @return The baked model.
 | 
			
		||||
     * @see ModelLocation
 | 
			
		||||
     */
 | 
			
		||||
    BakedModel getModel(ModelManager manager, ModelResourceLocation modelLocation, @Nullable ResourceLocation resourceLocation);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Wrap this model in a version which renders a foil/enchantment glint.
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,8 @@ import net.minecraft.core.Direction;
 | 
			
		||||
import net.minecraft.server.MinecraftServer;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.level.Level;
 | 
			
		||||
import org.jspecify.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The static entry point to the ComputerCraft API.
 | 
			
		||||
 
 | 
			
		||||
@@ -6,10 +6,12 @@ package dan200.computercraft.api;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.core.registries.Registries;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.tags.ItemTags;
 | 
			
		||||
import net.minecraft.tags.TagKey;
 | 
			
		||||
import net.minecraft.world.InteractionHand;
 | 
			
		||||
import net.minecraft.world.entity.player.Player;
 | 
			
		||||
import net.minecraft.world.item.Item;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.item.context.UseOnContext;
 | 
			
		||||
import net.minecraft.world.level.Level;
 | 
			
		||||
import net.minecraft.world.level.block.Block;
 | 
			
		||||
@@ -26,20 +28,6 @@ public class ComputerCraftTags {
 | 
			
		||||
        public static final TagKey<Item> WIRED_MODEM = make("wired_modem");
 | 
			
		||||
        public static final TagKey<Item> MONITOR = make("monitor");
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Floppy disks. Both the read/write version, and treasure disks.
 | 
			
		||||
         *
 | 
			
		||||
         * @since 1.116.0
 | 
			
		||||
         */
 | 
			
		||||
        public static final TagKey<Item> DISKS = make("disks");
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * All pocket computers.
 | 
			
		||||
         *
 | 
			
		||||
         * @since 1.116.0
 | 
			
		||||
         */
 | 
			
		||||
        public static final TagKey<Item> POCKET_COMPUTERS = make("pocket_computers");
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Items which can be {@linkplain Item#use(Level, Player, InteractionHand) used} when calling
 | 
			
		||||
         * {@code turtle.place()}.
 | 
			
		||||
@@ -49,8 +37,16 @@ public class ComputerCraftTags {
 | 
			
		||||
         */
 | 
			
		||||
        public static final TagKey<Item> TURTLE_CAN_PLACE = make("turtle_can_place");
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Items which can be dyed.
 | 
			
		||||
         * <p>
 | 
			
		||||
         * This is similar to {@link ItemTags#DYEABLE}, but allows cleaning the item with a sponge, rather than in a
 | 
			
		||||
         * cauldron.
 | 
			
		||||
         */
 | 
			
		||||
        public static final TagKey<Item> DYEABLE = make("dyeable");
 | 
			
		||||
 | 
			
		||||
        private static TagKey<Item> make(String name) {
 | 
			
		||||
            return TagKey.create(Registries.ITEM, new ResourceLocation(ComputerCraftAPI.MOD_ID, name));
 | 
			
		||||
            return TagKey.create(Registries.ITEM, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, name));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -89,13 +85,13 @@ public class ComputerCraftTags {
 | 
			
		||||
        public static final TagKey<Block> TURTLE_HOE_BREAKABLE = make("turtle_hoe_harvestable");
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Block which can be {@linkplain BlockState#use(Level, Player, InteractionHand, BlockHitResult) used} when
 | 
			
		||||
         * calling {@code turtle.place()}.
 | 
			
		||||
         * Block which can be {@linkplain BlockState#useItemOn(ItemStack, Level, Player, InteractionHand, BlockHitResult) used}
 | 
			
		||||
         * when calling {@code turtle.place()}.
 | 
			
		||||
         */
 | 
			
		||||
        public static final TagKey<Block> TURTLE_CAN_USE = make("turtle_can_use");
 | 
			
		||||
 | 
			
		||||
        private static TagKey<Block> make(String name) {
 | 
			
		||||
            return TagKey.create(Registries.BLOCK, new ResourceLocation(ComputerCraftAPI.MOD_ID, name));
 | 
			
		||||
            return TagKey.create(Registries.BLOCK, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, name));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,8 +6,8 @@ package dan200.computercraft.api.detail;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.world.item.Item;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import org.jspecify.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,8 @@ import net.minecraft.core.BlockPos;
 | 
			
		||||
import net.minecraft.world.level.Level;
 | 
			
		||||
import net.minecraft.world.level.block.entity.BlockEntity;
 | 
			
		||||
import net.minecraft.world.level.block.state.BlockState;
 | 
			
		||||
import org.jspecify.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A reference to a block in the world, used by block detail providers.
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,72 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.api.detail;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.core.component.DataComponentHolder;
 | 
			
		||||
import net.minecraft.core.component.DataComponentType;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An item detail provider for a specific {@linkplain DataComponentType data component} on {@link ItemStack}s or
 | 
			
		||||
 * other {@link DataComponentHolder}.
 | 
			
		||||
 *
 | 
			
		||||
 * @param <T> The type of the component's contents.
 | 
			
		||||
 */
 | 
			
		||||
public abstract class ComponentDetailProvider<T> implements DetailProvider<DataComponentHolder> {
 | 
			
		||||
    private final DataComponentType<T> component;
 | 
			
		||||
    private final @Nullable String namespace;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a new component detail provider. Details will be inserted into a new sub-map named as per {@code namespace}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param component The data component to provide details for.
 | 
			
		||||
     * @param namespace The namespace to use for this provider.
 | 
			
		||||
     */
 | 
			
		||||
    public ComponentDetailProvider(@Nullable String namespace, DataComponentType<T> component) {
 | 
			
		||||
        Objects.requireNonNull(component);
 | 
			
		||||
        this.component = component;
 | 
			
		||||
        this.namespace = namespace;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a new component detail provider. Details will be inserted directly into the results.
 | 
			
		||||
     *
 | 
			
		||||
     * @param component The data component to provide details for.
 | 
			
		||||
     */
 | 
			
		||||
    public ComponentDetailProvider(DataComponentType<T> component) {
 | 
			
		||||
        this(null, component);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Provide additional details for the given data component. This method is called by {@code turtle.getItemDetail()}.
 | 
			
		||||
     * New properties should be added to the given {@link Map}, {@code data}.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This method is always called on the server thread, so it is safe to interact with the world here, but you should
 | 
			
		||||
     * take care to avoid long blocking operations as this will stall the server and other computers.
 | 
			
		||||
     *
 | 
			
		||||
     * @param data The full details to be returned for this item stack. New properties should be added to this map.
 | 
			
		||||
     * @param item The component to provide details for.
 | 
			
		||||
     */
 | 
			
		||||
    public abstract void provideComponentDetails(Map<? super String, Object> data, T item);
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final void provideDetails(Map<? super String, Object> data, DataComponentHolder holder) {
 | 
			
		||||
        var value = holder.get(component);
 | 
			
		||||
        if (value == null) return;
 | 
			
		||||
 | 
			
		||||
        if (namespace == null) {
 | 
			
		||||
            provideComponentDetails(data, value);
 | 
			
		||||
        } else {
 | 
			
		||||
            Map<? super String, Object> child = new HashMap<>();
 | 
			
		||||
            provideComponentDetails(child, value);
 | 
			
		||||
            data.put(namespace, child);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -14,7 +14,6 @@ import java.util.Map;
 | 
			
		||||
 *
 | 
			
		||||
 * @param <T> The type of object that this provider can provide details for.
 | 
			
		||||
 * @see DetailRegistry
 | 
			
		||||
 * @see dan200.computercraft.api.detail An overview of the detail system.
 | 
			
		||||
 */
 | 
			
		||||
@FunctionalInterface
 | 
			
		||||
public interface DetailProvider<T> {
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,6 @@ import java.util.Map;
 | 
			
		||||
 * also in this package.
 | 
			
		||||
 *
 | 
			
		||||
 * @param <T> The type of object that this registry provides details for.
 | 
			
		||||
 * @see dan200.computercraft.api.detail An overview of the detail system.
 | 
			
		||||
 */
 | 
			
		||||
@ApiStatus.NonExtendable
 | 
			
		||||
public interface DetailRegistry<T> {
 | 
			
		||||
 
 | 
			
		||||
@@ -17,9 +17,6 @@ public class VanillaDetailRegistries {
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This instance's {@link DetailRegistry#getBasicDetails(Object)} is thread safe (assuming the stack is immutable)
 | 
			
		||||
     * and may be called from the computer thread.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This does not have special handling for {@linkplain ItemStack#isEmpty() empty item stacks}, and so the returned
 | 
			
		||||
     * details will be an empty stack of air. Callers should generally check for empty stacks before calling this.
 | 
			
		||||
     */
 | 
			
		||||
    public static final DetailRegistry<ItemStack> ITEM_STACK = ComputerCraftAPIService.get().getItemStackDetailRegistry();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,48 +0,0 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The detail system provides a standard way for mods to return descriptions of common game objects, such as blocks or
 | 
			
		||||
 * items, as well as registering additional detail to be included in those descriptions.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * For instance, the built-in {@code turtle.getItemDetail()} method uses
 | 
			
		||||
 * {@linkplain dan200.computercraft.api.detail.VanillaDetailRegistries#ITEM_STACK in order to provide information about}
 | 
			
		||||
 * the selected item:
 | 
			
		||||
 *
 | 
			
		||||
 * <pre class="language language-lua">{@code
 | 
			
		||||
 * local item = turtle.getItemDetail(nil, true)
 | 
			
		||||
 * --[[
 | 
			
		||||
 * item = {
 | 
			
		||||
 *   name = "minecraft:wheat",
 | 
			
		||||
 *   displayName = "Wheat",
 | 
			
		||||
 *   count = 1,
 | 
			
		||||
 *   maxCount = 64,
 | 
			
		||||
 *   tags = {},
 | 
			
		||||
 * }
 | 
			
		||||
 * ]]
 | 
			
		||||
 * }</pre>
 | 
			
		||||
 *
 | 
			
		||||
 * <h2>Built-in detail providers</h2>
 | 
			
		||||
 * While you can define your own detail providers (perhaps for types from your own mod), CC comes with several built-in
 | 
			
		||||
 * detail registries for vanilla and mod-loader objects:
 | 
			
		||||
 *
 | 
			
		||||
 * <ul>
 | 
			
		||||
 *     <li>{@link dan200.computercraft.api.detail.VanillaDetailRegistries}, for vanilla objects</li>
 | 
			
		||||
 *     <li>{@code dan200.computercraft.api.detail.ForgeDetailRegistries} for Forge-specific objects</li>
 | 
			
		||||
 *     <li>{@code dan200.computercraft.api.detail.FabricDetailRegistries} for Fabric-specific objects</li>
 | 
			
		||||
 * </ul>
 | 
			
		||||
 *
 | 
			
		||||
 * <h2>Example: Returning details from methods</h2>
 | 
			
		||||
 * Here we define a {@code getHeldItem()} method for pocket computers which finds the currently held item of the player
 | 
			
		||||
 * and returns it to the user using {@link dan200.computercraft.api.detail.VanillaDetailRegistries#ITEM_STACK} and
 | 
			
		||||
 * {@link dan200.computercraft.api.detail.DetailRegistry#getDetails(java.lang.Object)}.
 | 
			
		||||
 *
 | 
			
		||||
 * {@snippet class=com.example.examplemod.ExamplePocketPeripheral region=details}
 | 
			
		||||
 *
 | 
			
		||||
 * <h2>Example: Registering custom detail registries</h2>
 | 
			
		||||
 * Here we define a new detail provider for items that includes the nutrition and saturation values in the returned object.
 | 
			
		||||
 *
 | 
			
		||||
 * {@snippet class=com.example.examplemod.ExampleMod region=details}
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.api.detail;
 | 
			
		||||
@@ -9,7 +9,8 @@ import dan200.computercraft.api.peripheral.IComputerAccess;
 | 
			
		||||
import net.minecraft.core.BlockPos;
 | 
			
		||||
import net.minecraft.server.level.ServerLevel;
 | 
			
		||||
import org.jetbrains.annotations.ApiStatus;
 | 
			
		||||
import org.jspecify.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An interface passed to {@link ILuaAPIFactory} in order to provide additional information
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,8 @@
 | 
			
		||||
package dan200.computercraft.api.lua;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import org.jspecify.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Construct an {@link ILuaAPI} for a computer.
 | 
			
		||||
 
 | 
			
		||||
@@ -7,12 +7,15 @@ package dan200.computercraft.api.media;
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.api.filesystem.Mount;
 | 
			
		||||
import dan200.computercraft.api.filesystem.WritableMount;
 | 
			
		||||
import net.minecraft.core.Holder;
 | 
			
		||||
import net.minecraft.core.HolderLookup;
 | 
			
		||||
import net.minecraft.server.MinecraftServer;
 | 
			
		||||
import net.minecraft.server.level.ServerLevel;
 | 
			
		||||
import net.minecraft.sounds.SoundEvent;
 | 
			
		||||
import net.minecraft.world.item.Item;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import org.jspecify.annotations.Nullable;
 | 
			
		||||
import net.minecraft.world.item.JukeboxSong;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents an item that can be placed in a disk drive and used by a Computer.
 | 
			
		||||
@@ -24,11 +27,12 @@ public interface IMedia {
 | 
			
		||||
    /**
 | 
			
		||||
     * Get a string representing the label of this item. Will be called via {@code disk.getLabel()} in lua.
 | 
			
		||||
     *
 | 
			
		||||
     * @param registries The currently loaded registries.
 | 
			
		||||
     * @param stack      The {@link ItemStack} to inspect.
 | 
			
		||||
     * @return The label. ie: "Dan's Programs".
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    String getLabel(ItemStack stack);
 | 
			
		||||
    String getLabel(HolderLookup.Provider registries, ItemStack stack);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set a string representing the label of this item. Will be called vi {@code disk.setLabel()} in lua.
 | 
			
		||||
@@ -42,26 +46,15 @@ public interface IMedia {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * If this disk represents an item with audio (like a record), get the readable name of the audio track. ie:
 | 
			
		||||
     * "Jonathan Coulton - Still Alive"
 | 
			
		||||
     * If this disk represents an item with audio (like a record), get the corresponding {@link JukeboxSong}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param stack The {@link ItemStack} to modify.
 | 
			
		||||
     * @return The name, or null if this item does not represent an item with audio.
 | 
			
		||||
     * @param registries The currently loaded registries.
 | 
			
		||||
     * @param stack      The {@link ItemStack} to query.
 | 
			
		||||
     * @return The song, or null if this item does not represent an item with audio.
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    default String getAudioTitle(ItemStack stack) {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * If this disk represents an item with audio (like a record), get the resource name of the audio track to play.
 | 
			
		||||
     *
 | 
			
		||||
     * @param stack The {@link ItemStack} to modify.
 | 
			
		||||
     * @return The name, or null if this item does not represent an item with audio.
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    default SoundEvent getAudio(ItemStack stack) {
 | 
			
		||||
        return null;
 | 
			
		||||
    default Holder<JukeboxSong> getAudio(HolderLookup.Provider registries, ItemStack stack) {
 | 
			
		||||
        return JukeboxSong.fromStack(registries, stack).orElse(null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,8 @@
 | 
			
		||||
package dan200.computercraft.api.media;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import org.jspecify.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This interface is used to provide {@link IMedia} implementations for {@link ItemStack}.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,46 +0,0 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.api.media;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.impl.ComputerCraftAPIService;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import org.jspecify.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import java.util.stream.Stream;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The contents of a page (or book) created by a ComputerCraft printer.
 | 
			
		||||
 *
 | 
			
		||||
 * @since 1.115
 | 
			
		||||
 */
 | 
			
		||||
@Nullable
 | 
			
		||||
public interface PrintoutContents {
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the (possibly empty) title for this printout.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The title of this printout.
 | 
			
		||||
     */
 | 
			
		||||
    String getTitle();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the text contents of this printout, as a sequence of lines.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * The lines in the printout may include blank lines at the end of the document, as well as trailing spaces on each
 | 
			
		||||
     * line.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The text contents of this printout.
 | 
			
		||||
     */
 | 
			
		||||
    Stream<String> getTextLines();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the printout contents for a particular stack.
 | 
			
		||||
     *
 | 
			
		||||
     * @param stack The stack to get the contents for.
 | 
			
		||||
     * @return The printout contents, or {@code null} if this is not a printout item.
 | 
			
		||||
     */
 | 
			
		||||
    static @Nullable PrintoutContents get(ItemStack stack) {
 | 
			
		||||
        return ComputerCraftAPIService.get().getPrintoutContents(stack);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -19,8 +19,8 @@ import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
 */
 | 
			
		||||
public interface WiredElement extends WiredSender {
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when peripherals on the network change. This may occur when network nodes are added or removed, or when
 | 
			
		||||
     * peripherals are attached or detached from a modem.
 | 
			
		||||
     * Called when objects on the network change. This may occur when network nodes are added or removed, or when
 | 
			
		||||
     * peripherals change.
 | 
			
		||||
     *
 | 
			
		||||
     * @param change The change which occurred.
 | 
			
		||||
     * @see WiredNetworkChange
 | 
			
		||||
 
 | 
			
		||||
@@ -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,8 +4,7 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.api.pocket;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeBase;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -15,27 +14,20 @@ import net.minecraft.world.item.ItemStack;
 | 
			
		||||
 * One does not have to use this, but it does provide a convenient template.
 | 
			
		||||
 */
 | 
			
		||||
public abstract class AbstractPocketUpgrade implements IPocketUpgrade {
 | 
			
		||||
    private final ResourceLocation id;
 | 
			
		||||
    private final String adjective;
 | 
			
		||||
    private final Component adjective;
 | 
			
		||||
    private final ItemStack stack;
 | 
			
		||||
 | 
			
		||||
    protected AbstractPocketUpgrade(ResourceLocation id, String adjective, ItemStack stack) {
 | 
			
		||||
        this.id = id;
 | 
			
		||||
    protected AbstractPocketUpgrade(Component adjective, ItemStack stack) {
 | 
			
		||||
        this.adjective = adjective;
 | 
			
		||||
        this.stack = stack;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected AbstractPocketUpgrade(ResourceLocation id, ItemStack stack) {
 | 
			
		||||
        this(id, UpgradeBase.getDefaultAdjective(id), stack);
 | 
			
		||||
    protected AbstractPocketUpgrade(String adjective, ItemStack stack) {
 | 
			
		||||
        this(Component.translatable(adjective), stack);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final ResourceLocation getUpgradeID() {
 | 
			
		||||
        return id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final String getUnlocalisedAdjective() {
 | 
			
		||||
    public final Component getAdjective() {
 | 
			
		||||
        return adjective;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,19 +4,16 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.api.pocket;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.peripheral.IPeripheral;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeBase;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeData;
 | 
			
		||||
import net.minecraft.nbt.CompoundTag;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.core.component.DataComponentPatch;
 | 
			
		||||
import net.minecraft.server.level.ServerLevel;
 | 
			
		||||
import net.minecraft.world.entity.Entity;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.phys.Vec3;
 | 
			
		||||
import org.jetbrains.annotations.ApiStatus;
 | 
			
		||||
import org.jspecify.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Wrapper class for pocket computers.
 | 
			
		||||
@@ -87,7 +84,7 @@ public interface IPocketAccess {
 | 
			
		||||
     * Get the currently equipped upgrade.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The currently equipped upgrade.
 | 
			
		||||
     * @see #getUpgradeNBTData()
 | 
			
		||||
     * @see #getUpgradeData()
 | 
			
		||||
     * @see #setUpgrade(UpgradeData)
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
@@ -109,19 +106,20 @@ public interface IPocketAccess {
 | 
			
		||||
     * This is persisted between computer reboots and chunk loads.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The upgrade's NBT.
 | 
			
		||||
     * @see #updateUpgradeNBTData()
 | 
			
		||||
     * @see UpgradeBase#getUpgradeItem(CompoundTag)
 | 
			
		||||
     * @see #setUpgradeData(DataComponentPatch)
 | 
			
		||||
     * @see UpgradeBase#getUpgradeItem(DataComponentPatch)
 | 
			
		||||
     * @see UpgradeBase#getUpgradeData(ItemStack)
 | 
			
		||||
     * @see #getUpgrade()
 | 
			
		||||
     */
 | 
			
		||||
    CompoundTag getUpgradeNBTData();
 | 
			
		||||
    DataComponentPatch getUpgradeData();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Mark the upgrade-specific NBT as dirty.
 | 
			
		||||
     * Update the upgrade-specific data.
 | 
			
		||||
     *
 | 
			
		||||
     * @see #getUpgradeNBTData()
 | 
			
		||||
     * @param data The new upgrade data.
 | 
			
		||||
     * @see #getUpgradeData()
 | 
			
		||||
     */
 | 
			
		||||
    void updateUpgradeNBTData();
 | 
			
		||||
    void setUpgradeData(DataComponentPatch data);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Remove the current peripheral and create a new one.
 | 
			
		||||
@@ -130,13 +128,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();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,21 +4,47 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.api.pocket;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.api.peripheral.IPeripheral;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeBase;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeType;
 | 
			
		||||
import dan200.computercraft.impl.ComputerCraftAPIService;
 | 
			
		||||
import net.minecraft.core.Registry;
 | 
			
		||||
import net.minecraft.resources.ResourceKey;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.level.Level;
 | 
			
		||||
import org.jspecify.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
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 Minecraft registry.
 | 
			
		||||
 * Pocket upgrades are defined in two stages. First, one creates a {@link IPocketUpgrade} subclass and corresponding
 | 
			
		||||
 * {@link UpgradeType} instance, which are then registered in a Minecraft 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.
 | 
			
		||||
 */
 | 
			
		||||
public interface IPocketUpgrade extends UpgradeBase {
 | 
			
		||||
    ResourceKey<Registry<IPocketUpgrade>> REGISTRY = ResourceKey.createRegistryKey(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "pocket_upgrade"));
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The registry key for pocket upgrade types.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The registry key.
 | 
			
		||||
     */
 | 
			
		||||
    static ResourceKey<Registry<UpgradeType<? extends IPocketUpgrade>>> typeRegistry() {
 | 
			
		||||
        return ComputerCraftAPIService.get().pocketUpgradeRegistryId();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the type of this upgrade.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The type of this upgrade.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    UpgradeType<? extends IPocketUpgrade> getType();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a peripheral for the pocket computer.
 | 
			
		||||
     * <p>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,26 +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.UpgradeDataProvider;
 | 
			
		||||
import net.minecraft.data.DataGenerator;
 | 
			
		||||
import net.minecraft.data.PackOutput;
 | 
			
		||||
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A data provider to generate pocket computer upgrades.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * This should be subclassed and registered to a {@link DataGenerator.PackGenerator}. Override the
 | 
			
		||||
 * {@link #addUpgrades(Consumer)} function, construct each upgrade, and pass them off to the provided consumer to
 | 
			
		||||
 * generate them.
 | 
			
		||||
 *
 | 
			
		||||
 * @see PocketUpgradeSerialiser
 | 
			
		||||
 */
 | 
			
		||||
public abstract class PocketUpgradeDataProvider extends UpgradeDataProvider<IPocketUpgrade, PocketUpgradeSerialiser<?>> {
 | 
			
		||||
    public PocketUpgradeDataProvider(PackOutput output) {
 | 
			
		||||
        super(output, "Pocket Computer Upgrades", "computercraft/pocket_upgrades", PocketUpgradeSerialiser.registryId());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -4,8 +4,7 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.api.turtle;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeBase;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -15,34 +14,27 @@ import net.minecraft.world.item.ItemStack;
 | 
			
		||||
 * One does not have to use this, but it does provide a convenient template.
 | 
			
		||||
 */
 | 
			
		||||
public abstract class AbstractTurtleUpgrade implements ITurtleUpgrade {
 | 
			
		||||
    private final ResourceLocation id;
 | 
			
		||||
    private final TurtleUpgradeType type;
 | 
			
		||||
    private final String adjective;
 | 
			
		||||
    private final Component adjective;
 | 
			
		||||
    private final ItemStack stack;
 | 
			
		||||
 | 
			
		||||
    protected AbstractTurtleUpgrade(ResourceLocation id, TurtleUpgradeType type, String adjective, ItemStack stack) {
 | 
			
		||||
        this.id = id;
 | 
			
		||||
    protected AbstractTurtleUpgrade(TurtleUpgradeType type, Component adjective, ItemStack stack) {
 | 
			
		||||
        this.type = type;
 | 
			
		||||
        this.adjective = adjective;
 | 
			
		||||
        this.stack = stack;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected AbstractTurtleUpgrade(ResourceLocation id, TurtleUpgradeType type, ItemStack stack) {
 | 
			
		||||
        this(id, type, UpgradeBase.getDefaultAdjective(id), stack);
 | 
			
		||||
    protected AbstractTurtleUpgrade(TurtleUpgradeType type, String adjective, ItemStack stack) {
 | 
			
		||||
        this(type, Component.translatable(adjective), stack);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final ResourceLocation getUpgradeID() {
 | 
			
		||||
        return id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final String getUnlocalisedAdjective() {
 | 
			
		||||
    public final Component getAdjective() {
 | 
			
		||||
        return adjective;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final TurtleUpgradeType getType() {
 | 
			
		||||
    public final TurtleUpgradeType getUpgradeType() {
 | 
			
		||||
        return type;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -12,12 +12,13 @@ import dan200.computercraft.api.upgrades.UpgradeBase;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeData;
 | 
			
		||||
import net.minecraft.core.BlockPos;
 | 
			
		||||
import net.minecraft.core.Direction;
 | 
			
		||||
import net.minecraft.nbt.CompoundTag;
 | 
			
		||||
import net.minecraft.core.component.DataComponentPatch;
 | 
			
		||||
import net.minecraft.world.Container;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.level.Level;
 | 
			
		||||
import org.jetbrains.annotations.ApiStatus;
 | 
			
		||||
import org.jspecify.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The interface passed to turtle by turtles, providing methods that they can call.
 | 
			
		||||
@@ -228,37 +229,22 @@ public interface ITurtleAccess {
 | 
			
		||||
     * @param side The side to get the upgrade from.
 | 
			
		||||
     * @return The upgrade on the specified side of the turtle, if there is one.
 | 
			
		||||
     * @see #getUpgradeWithData(TurtleSide)
 | 
			
		||||
     * @see #setUpgradeWithData(TurtleSide, UpgradeData)
 | 
			
		||||
     * @see #setUpgrade(TurtleSide, UpgradeData)
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    ITurtleUpgrade getUpgrade(TurtleSide side);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the upgrade on the specified side of the turtle, along with its {@linkplain #getUpgradeNBTData(TurtleSide)
 | 
			
		||||
     * Returns the upgrade on the specified side of the turtle, along with its {@linkplain #getUpgradeData(TurtleSide)
 | 
			
		||||
     * update data}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param side The side to get the upgrade from.
 | 
			
		||||
     * @return The upgrade on the specified side of the turtle, along with its upgrade data, if there is one.
 | 
			
		||||
     * @see #getUpgradeWithData(TurtleSide)
 | 
			
		||||
     * @see #setUpgradeWithData(TurtleSide, UpgradeData)
 | 
			
		||||
     * @see #setUpgrade(TurtleSide, UpgradeData)
 | 
			
		||||
     */
 | 
			
		||||
    default @Nullable UpgradeData<ITurtleUpgrade> getUpgradeWithData(TurtleSide side) {
 | 
			
		||||
        var upgrade = getUpgrade(side);
 | 
			
		||||
        return upgrade == null ? null : UpgradeData.of(upgrade, getUpgradeNBTData(side));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the upgrade for a given side, resetting peripherals and clearing upgrade specific data.
 | 
			
		||||
     *
 | 
			
		||||
     * @param side    The side to set the upgrade on.
 | 
			
		||||
     * @param upgrade The upgrade to set, may be {@code null} to clear.
 | 
			
		||||
     * @see #getUpgrade(TurtleSide)
 | 
			
		||||
     * @deprecated Use {@link #setUpgradeWithData(TurtleSide, UpgradeData)}
 | 
			
		||||
     */
 | 
			
		||||
    @Deprecated
 | 
			
		||||
    default void setUpgrade(TurtleSide side, @Nullable ITurtleUpgrade upgrade) {
 | 
			
		||||
        setUpgradeWithData(side, upgrade == null ? null : UpgradeData.ofDefault(upgrade));
 | 
			
		||||
    }
 | 
			
		||||
    @Nullable
 | 
			
		||||
    UpgradeData<ITurtleUpgrade> getUpgradeWithData(TurtleSide side);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the upgrade for a given side and its upgrade data.
 | 
			
		||||
@@ -267,7 +253,7 @@ public interface ITurtleAccess {
 | 
			
		||||
     * @param upgrade The upgrade to set, may be {@code null} to clear.
 | 
			
		||||
     * @see #getUpgradeWithData(TurtleSide)
 | 
			
		||||
     */
 | 
			
		||||
    void setUpgradeWithData(TurtleSide side, @Nullable UpgradeData<ITurtleUpgrade> upgrade);
 | 
			
		||||
    void setUpgrade(TurtleSide side, @Nullable UpgradeData<ITurtleUpgrade> upgrade);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the peripheral created by the upgrade on the specified side of the turtle, if there is one.
 | 
			
		||||
@@ -281,23 +267,23 @@ public interface ITurtleAccess {
 | 
			
		||||
    /**
 | 
			
		||||
     * Get an upgrade-specific NBT compound, which can be used to store arbitrary data.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This will be persisted across turtle restarts and chunk loads, as well as being synced to the client. You must
 | 
			
		||||
     * call {@link #updateUpgradeNBTData(TurtleSide)} after modifying it.
 | 
			
		||||
     * This will be persisted across turtle restarts and chunk loads, as well as being synced to the client. You can
 | 
			
		||||
     * call {@link #setUpgrade(TurtleSide, UpgradeData)} to modify it.
 | 
			
		||||
     *
 | 
			
		||||
     * @param side The side to get the upgrade data for.
 | 
			
		||||
     * @return The upgrade-specific data.
 | 
			
		||||
     * @see #updateUpgradeNBTData(TurtleSide)
 | 
			
		||||
     * @see UpgradeBase#getUpgradeItem(CompoundTag)
 | 
			
		||||
     * @see #setUpgradeData(TurtleSide, DataComponentPatch)
 | 
			
		||||
     * @see UpgradeBase#getUpgradeItem(DataComponentPatch)
 | 
			
		||||
     * @see UpgradeBase#getUpgradeData(ItemStack)
 | 
			
		||||
     */
 | 
			
		||||
    CompoundTag getUpgradeNBTData(TurtleSide side);
 | 
			
		||||
    DataComponentPatch getUpgradeData(TurtleSide side);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Mark the upgrade-specific data as dirty on a specific side. This is required for the data to be synced to the
 | 
			
		||||
     * client and persisted.
 | 
			
		||||
     * Update the upgrade-specific data.
 | 
			
		||||
     *
 | 
			
		||||
     * @param side The side to mark dirty.
 | 
			
		||||
     * @see #updateUpgradeNBTData(TurtleSide)
 | 
			
		||||
     * @param side The side to set the upgrade data for.
 | 
			
		||||
     * @param data The new upgrade data.
 | 
			
		||||
     * @see #getUpgradeData(TurtleSide)
 | 
			
		||||
     */
 | 
			
		||||
    void updateUpgradeNBTData(TurtleSide side);
 | 
			
		||||
    void setUpgradeData(TurtleSide side, DataComponentPatch data);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,38 +4,45 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.api.turtle;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.api.peripheral.IPeripheral;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeBase;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeType;
 | 
			
		||||
import dan200.computercraft.impl.ComputerCraftAPIService;
 | 
			
		||||
import net.minecraft.core.Direction;
 | 
			
		||||
import net.minecraft.nbt.CompoundTag;
 | 
			
		||||
import net.minecraft.core.Registry;
 | 
			
		||||
import net.minecraft.core.RegistrySetBuilder.PatchedRegistries;
 | 
			
		||||
import net.minecraft.core.component.DataComponentPatch;
 | 
			
		||||
import net.minecraft.resources.ResourceKey;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.item.Items;
 | 
			
		||||
import org.jspecify.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import java.util.function.BiFunction;
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The primary interface for defining an update for Turtles. A turtle update can either be a new tool, or a new
 | 
			
		||||
 * 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 Minecraft registry.
 | 
			
		||||
 * {@link UpgradeType} instance, which are then registered in a Minecraft 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 automatically registered.
 | 
			
		||||
 *
 | 
			
		||||
 * <h2>Example</h2>
 | 
			
		||||
 * <h3>Registering the upgrade serialiser</h3>
 | 
			
		||||
 * <h3>Registering the upgrade type</h3>
 | 
			
		||||
 * First, let's create a new class that implements {@link ITurtleUpgrade}. It is recommended to subclass
 | 
			
		||||
 * {@link AbstractTurtleUpgrade}, as that provides a default implementation of most methods.
 | 
			
		||||
 *
 | 
			
		||||
 * {@snippet class=com.example.examplemod.ExampleTurtleUpgrade region=body}
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Now we must construct a new upgrade serialiser. In most cases, you can use one of the helper methods
 | 
			
		||||
 * (e.g. {@link TurtleUpgradeSerialiser#simpleWithCustomItem(BiFunction)}), rather than defining your own implementation.
 | 
			
		||||
 *
 | 
			
		||||
 * Now we must construct a new upgrade type. In most cases, you can use one of the helper methods (e.g.
 | 
			
		||||
 * {@link UpgradeType#simpleWithCustomItem(Function)}), rather than defining your own implementation.
 | 
			
		||||
 *
 | 
			
		||||
 * {@snippet class=com.example.examplemod.ExampleMod region=turtle_upgrades}
 | 
			
		||||
 *
 | 
			
		||||
 * We now must register this upgrade serialiser. This is done the same way as you'd register blocks, items, or other
 | 
			
		||||
 * We now must register this upgrade type. This is done the same way as you'd register blocks, items, or other
 | 
			
		||||
 * Minecraft objects. The approach to do this will depend on mod-loader.
 | 
			
		||||
 *
 | 
			
		||||
 * <h4>Fabric</h4>
 | 
			
		||||
@@ -46,40 +53,80 @@ import java.util.function.BiFunction;
 | 
			
		||||
 *
 | 
			
		||||
 * <h3>Rendering the upgrade</h3>
 | 
			
		||||
 * Next, we need to register a model for our upgrade. This is done by registering a
 | 
			
		||||
 * {@link dan200.computercraft.api.client.turtle.TurtleUpgradeModeller} for your upgrade serialiser.
 | 
			
		||||
 * {@link dan200.computercraft.api.client.turtle.TurtleUpgradeModeller} for your upgrade type.
 | 
			
		||||
 *
 | 
			
		||||
 * <h4>Fabric</h4>
 | 
			
		||||
 * {@snippet class=com.example.examplemod.FabricExampleModClient region=turtle_modellers}
 | 
			
		||||
 *
 | 
			
		||||
 *
 | 
			
		||||
 * <h4>Forge</h4>
 | 
			
		||||
 * {@snippet class=com.example.examplemod.FabricExampleModClient region=turtle_modellers}
 | 
			
		||||
 *
 | 
			
		||||
 * <h3>Registering the upgrade itself</h3>
 | 
			
		||||
 * <h3 id="datagen">Registering the upgrade itself</h3>
 | 
			
		||||
 * Upgrades themselves are loaded from datapacks when a level is loaded. In order to register our new upgrade, we must
 | 
			
		||||
 * create a new JSON file at {@code data/<my_mod>/computercraft/turtle_upgrades/<my_upgrade_id>.json}.
 | 
			
		||||
 * create a new JSON file at {@code data/<my_mod>/computercraft/turtle_upgrade/<my_upgrade_id>.json}.
 | 
			
		||||
 *
 | 
			
		||||
 * {@snippet file = data/examplemod/computercraft/turtle_upgrades/example_turtle_upgrade.json}
 | 
			
		||||
 * {@snippet file=data/examplemod/computercraft/turtle_upgrade/example_turtle_upgrade.json}
 | 
			
		||||
 *
 | 
			
		||||
 * The {@code "type"} field points to the ID of the upgrade serialiser we've just registered, while the other fields
 | 
			
		||||
 * are read by the serialiser itself. As our upgrade was defined with {@link TurtleUpgradeSerialiser#simpleWithCustomItem(BiFunction)}, the
 | 
			
		||||
 * The {@code "type"} field points to the ID of the upgrade type we've just registered, while the other fields are read
 | 
			
		||||
 * by the type itself. As our upgrade was defined with {@link UpgradeType#simpleWithCustomItem(Function)}, the
 | 
			
		||||
 * {@code "item"} field will construct our upgrade with {@link Items#COMPASS}.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Rather than manually creating the file, it is recommended to data-generators to generate this file. This can be done
 | 
			
		||||
 * with {@link TurtleUpgradeDataProvider}.
 | 
			
		||||
 * Rather than manually creating the file, it is recommended to use data-generators to generate this file. First, we
 | 
			
		||||
 * register our new upgrades into a {@linkplain PatchedRegistries patched registry}.
 | 
			
		||||
 *
 | 
			
		||||
 * {@snippet class=com.example.examplemod.data.TurtleDataProvider region=body}
 | 
			
		||||
 * {@snippet class=com.example.examplemod.data.TurtleUpgradeProvider region=body}
 | 
			
		||||
 *
 | 
			
		||||
 * @see TurtleUpgradeSerialiser Registering a turtle upgrade.
 | 
			
		||||
 * Next, we must write these upgrades to disk. Vanilla does not have complete support for this yet, so this must be done
 | 
			
		||||
 * with mod-loader-specific APIs.
 | 
			
		||||
 *
 | 
			
		||||
 * <h4>Fabric</h4>
 | 
			
		||||
 * {@snippet class=com.example.examplemod.FabricExampleModDataGenerator region=turtle_upgrades}
 | 
			
		||||
 *
 | 
			
		||||
 * <h4>Forge</h4>
 | 
			
		||||
 * {@snippet class=com.example.examplemod.ForgeExampleModDataGenerator region=turtle_upgrades}
 | 
			
		||||
 */
 | 
			
		||||
public interface ITurtleUpgrade extends UpgradeBase {
 | 
			
		||||
    /**
 | 
			
		||||
     * The registry in which turtle upgrades are stored.
 | 
			
		||||
     */
 | 
			
		||||
    ResourceKey<Registry<ITurtleUpgrade>> REGISTRY = ResourceKey.createRegistryKey(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "turtle_upgrade"));
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a {@link ResourceKey} for a turtle upgrade given a {@link ResourceLocation}.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This should only be called from within data generation code. Do not hard code references to your upgrades!
 | 
			
		||||
     *
 | 
			
		||||
     * @param id The id of the turtle upgrade.
 | 
			
		||||
     * @return The upgrade registry key.
 | 
			
		||||
     */
 | 
			
		||||
    static ResourceKey<ITurtleUpgrade> createKey(ResourceLocation id) {
 | 
			
		||||
        return ResourceKey.create(REGISTRY, id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The registry key for turtle upgrade types.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The registry key.
 | 
			
		||||
     */
 | 
			
		||||
    static ResourceKey<Registry<UpgradeType<? extends ITurtleUpgrade>>> typeRegistry() {
 | 
			
		||||
        return ComputerCraftAPIService.get().turtleUpgradeRegistryId();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the type of this upgrade.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The type of this upgrade.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    UpgradeType<? extends ITurtleUpgrade> getType();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Return whether this turtle adds a tool or a peripheral to the turtle.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The type of upgrade this is.
 | 
			
		||||
     * @see TurtleUpgradeType for the differences between them.
 | 
			
		||||
     */
 | 
			
		||||
    TurtleUpgradeType getType();
 | 
			
		||||
    TurtleUpgradeType getUpgradeType();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Will only be called for peripheral upgrades. Creates a peripheral for a turtle being placed using this upgrade.
 | 
			
		||||
@@ -138,7 +185,7 @@ public interface ITurtleUpgrade extends UpgradeBase {
 | 
			
		||||
     * @param upgradeData Data that currently stored for this upgrade
 | 
			
		||||
     * @return Filtered version of this data.
 | 
			
		||||
     */
 | 
			
		||||
    default CompoundTag getPersistedData(CompoundTag upgradeData) {
 | 
			
		||||
    default DataComponentPatch getPersistedData(DataComponentPatch upgradeData) {
 | 
			
		||||
        return upgradeData;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,8 @@
 | 
			
		||||
package dan200.computercraft.api.turtle;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.core.Direction;
 | 
			
		||||
import org.jspecify.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Used to indicate the result of executing a turtle command.
 | 
			
		||||
@@ -59,9 +60,9 @@ public final class TurtleCommandResult {
 | 
			
		||||
 | 
			
		||||
    private final boolean success;
 | 
			
		||||
    private final @Nullable String errorMessage;
 | 
			
		||||
    private final @Nullable Object @Nullable [] results;
 | 
			
		||||
    private final @Nullable Object[] results;
 | 
			
		||||
 | 
			
		||||
    private TurtleCommandResult(boolean success, @Nullable String errorMessage, @Nullable Object @Nullable [] results) {
 | 
			
		||||
    private TurtleCommandResult(boolean success, @Nullable String errorMessage, @Nullable Object[] results) {
 | 
			
		||||
        this.success = success;
 | 
			
		||||
        this.errorMessage = errorMessage;
 | 
			
		||||
        this.results = results;
 | 
			
		||||
@@ -91,7 +92,8 @@ public final class TurtleCommandResult {
 | 
			
		||||
     *
 | 
			
		||||
     * @return The command's result, or {@code null} if it was a failure.
 | 
			
		||||
     */
 | 
			
		||||
    public @Nullable Object @Nullable [] getResults() {
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public Object[] getResults() {
 | 
			
		||||
        return results;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,149 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.api.turtle;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftTags;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeBase;
 | 
			
		||||
import dan200.computercraft.impl.ComputerCraftAPIService;
 | 
			
		||||
import dan200.computercraft.impl.upgrades.TurtleToolSpec;
 | 
			
		||||
import net.minecraft.core.component.DataComponents;
 | 
			
		||||
import net.minecraft.data.worldgen.BootstrapContext;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
import net.minecraft.resources.ResourceKey;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.tags.TagKey;
 | 
			
		||||
import net.minecraft.world.entity.ai.attributes.Attributes;
 | 
			
		||||
import net.minecraft.world.item.Item;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.level.block.Block;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A builder for custom turtle tool upgrades.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * This can be used from your <a href="./ITurtleUpgrade.html#datagen">data generator</a> code in order to
 | 
			
		||||
 * register turtle tools for your mod's tools.
 | 
			
		||||
 *
 | 
			
		||||
 * <h2>Example</h2>
 | 
			
		||||
 * {@snippet class=com.example.examplemod.data.TurtleToolProvider region=body}
 | 
			
		||||
 */
 | 
			
		||||
public final class TurtleToolBuilder {
 | 
			
		||||
    private final ResourceKey<ITurtleUpgrade> id;
 | 
			
		||||
    private final Item item;
 | 
			
		||||
    private Component adjective;
 | 
			
		||||
    private float damageMultiplier = TurtleToolSpec.DEFAULT_DAMAGE_MULTIPLIER;
 | 
			
		||||
    private @Nullable TagKey<Block> breakable;
 | 
			
		||||
    private boolean allowEnchantments = false;
 | 
			
		||||
    private TurtleToolDurability consumeDurability = TurtleToolDurability.NEVER;
 | 
			
		||||
 | 
			
		||||
    private TurtleToolBuilder(ResourceKey<ITurtleUpgrade> id, Item item) {
 | 
			
		||||
        this.id = id;
 | 
			
		||||
        adjective = Component.translatable(UpgradeBase.getDefaultAdjective(id.location()));
 | 
			
		||||
        this.item = item;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static TurtleToolBuilder tool(ResourceLocation id, Item item) {
 | 
			
		||||
        return new TurtleToolBuilder(ITurtleUpgrade.createKey(id), item);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static TurtleToolBuilder tool(ResourceKey<ITurtleUpgrade> id, Item item) {
 | 
			
		||||
        return new TurtleToolBuilder(id, item);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the id for this turtle tool.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The upgrade id.
 | 
			
		||||
     */
 | 
			
		||||
    public ResourceKey<ITurtleUpgrade> id() {
 | 
			
		||||
        return id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Specify a custom adjective for this tool. By default this takes its adjective from the upgrade id.
 | 
			
		||||
     *
 | 
			
		||||
     * @param adjective The new adjective to use.
 | 
			
		||||
     * @return The tool builder, for further use.
 | 
			
		||||
     */
 | 
			
		||||
    public TurtleToolBuilder adjective(Component adjective) {
 | 
			
		||||
        this.adjective = adjective;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The amount of damage a swing of this tool will do. This is multiplied by {@link Attributes#ATTACK_DAMAGE} to
 | 
			
		||||
     * get the final damage.
 | 
			
		||||
     *
 | 
			
		||||
     * @param damageMultiplier The damage multiplier.
 | 
			
		||||
     * @return The tool builder, for further use.
 | 
			
		||||
     */
 | 
			
		||||
    public TurtleToolBuilder damageMultiplier(float damageMultiplier) {
 | 
			
		||||
        this.damageMultiplier = damageMultiplier;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Indicate that this upgrade allows items which have been {@linkplain ItemStack#isEnchanted() enchanted} or have
 | 
			
		||||
     * {@linkplain DataComponents#ATTRIBUTE_MODIFIERS custom attribute modifiers}.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The tool builder, for further use.
 | 
			
		||||
     */
 | 
			
		||||
    public TurtleToolBuilder allowEnchantments() {
 | 
			
		||||
        allowEnchantments = true;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set when the tool will consume durability.
 | 
			
		||||
     *
 | 
			
		||||
     * @param durability The durability predicate.
 | 
			
		||||
     * @return The tool builder, for further use.
 | 
			
		||||
     */
 | 
			
		||||
    public TurtleToolBuilder consumeDurability(TurtleToolDurability durability) {
 | 
			
		||||
        consumeDurability = durability;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Provide a list of breakable blocks. If not given, the tool can break all blocks. If given, only blocks
 | 
			
		||||
     * in this tag, those in {@link ComputerCraftTags.Blocks#TURTLE_ALWAYS_BREAKABLE} and "insta-mine" ones can
 | 
			
		||||
     * be broken.
 | 
			
		||||
     *
 | 
			
		||||
     * @param breakable The tag containing all blocks breakable by this item.
 | 
			
		||||
     * @return The tool builder, for further use.
 | 
			
		||||
     * @see ComputerCraftTags.Blocks
 | 
			
		||||
     */
 | 
			
		||||
    public TurtleToolBuilder breakable(TagKey<Block> breakable) {
 | 
			
		||||
        this.breakable = breakable;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Build the turtle tool upgrade.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The constructed upgrade.
 | 
			
		||||
     */
 | 
			
		||||
    public ITurtleUpgrade build() {
 | 
			
		||||
        return ComputerCraftAPIService.get().createTurtleTool(new TurtleToolSpec(
 | 
			
		||||
            adjective,
 | 
			
		||||
            item,
 | 
			
		||||
            damageMultiplier,
 | 
			
		||||
            allowEnchantments,
 | 
			
		||||
            consumeDurability,
 | 
			
		||||
            Optional.ofNullable(breakable)
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Build this upgrade and register it for datagen.
 | 
			
		||||
     *
 | 
			
		||||
     * @param upgrades The registry this upgrade should be added to.
 | 
			
		||||
     */
 | 
			
		||||
    public void register(BootstrapContext<ITurtleUpgrade> upgrades) {
 | 
			
		||||
        upgrades.register(id(), build());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -4,14 +4,14 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.api.turtle;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.core.component.DataComponents;
 | 
			
		||||
import net.minecraft.util.StringRepresentable;
 | 
			
		||||
import net.minecraft.world.entity.EquipmentSlot;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Indicates if an equipped turtle item will consume durability.
 | 
			
		||||
 *
 | 
			
		||||
 * @see TurtleUpgradeDataProvider.ToolBuilder#consumeDurability(TurtleToolDurability)
 | 
			
		||||
 * @see TurtleToolBuilder#consumeDurability(TurtleToolDurability)
 | 
			
		||||
 */
 | 
			
		||||
public enum TurtleToolDurability implements StringRepresentable {
 | 
			
		||||
    /**
 | 
			
		||||
@@ -21,7 +21,7 @@ public enum TurtleToolDurability implements StringRepresentable {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The equipped tool consumes durability if it is {@linkplain ItemStack#isEnchanted() enchanted} or has
 | 
			
		||||
     * {@linkplain ItemStack#getAttributeModifiers(EquipmentSlot) custom attribute modifiers}.
 | 
			
		||||
     * {@linkplain DataComponents#ATTRIBUTE_MODIFIERS custom attribute modifiers}.
 | 
			
		||||
     */
 | 
			
		||||
    WHEN_ENCHANTED("when_enchanted"),
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,171 +0,0 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
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 net.minecraft.data.DataGenerator;
 | 
			
		||||
import net.minecraft.data.PackOutput;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.tags.TagKey;
 | 
			
		||||
import net.minecraft.world.entity.EquipmentSlot;
 | 
			
		||||
import net.minecraft.world.entity.ai.attributes.Attributes;
 | 
			
		||||
import net.minecraft.world.item.Item;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.level.block.Block;
 | 
			
		||||
import org.jspecify.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A data provider to generate turtle upgrades.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * This should be subclassed and registered to a {@link DataGenerator.PackGenerator}. Override the
 | 
			
		||||
 * {@link #addUpgrades(Consumer)} function, construct each upgrade, and pass them off to the provided consumer to
 | 
			
		||||
 * generate them.
 | 
			
		||||
 *
 | 
			
		||||
 * <h2>Example</h2>
 | 
			
		||||
 * {@snippet class=com.example.examplemod.data.TurtleDataProvider region=body}
 | 
			
		||||
 *
 | 
			
		||||
 * @see TurtleUpgradeSerialiser
 | 
			
		||||
 */
 | 
			
		||||
public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITurtleUpgrade, TurtleUpgradeSerialiser<?>> {
 | 
			
		||||
    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());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a new turtle tool upgrade, such as a pickaxe or shovel.
 | 
			
		||||
     *
 | 
			
		||||
     * @param id   The ID of this tool.
 | 
			
		||||
     * @param item The item used for tool actions. Note, this doesn't inherit all properties of the tool, you may need
 | 
			
		||||
     *             to specify {@link ToolBuilder#damageMultiplier(float)} and {@link ToolBuilder#breakable(TagKey)}.
 | 
			
		||||
     * @return A tool builder,
 | 
			
		||||
     */
 | 
			
		||||
    public final ToolBuilder tool(ResourceLocation id, Item item) {
 | 
			
		||||
        return new ToolBuilder(id, existingSerialiser(TOOL_ID), item);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A builder for custom turtle tool upgrades.
 | 
			
		||||
     *
 | 
			
		||||
     * @see #tool(ResourceLocation, Item)
 | 
			
		||||
     */
 | 
			
		||||
    public static class ToolBuilder {
 | 
			
		||||
        private final ResourceLocation id;
 | 
			
		||||
        private final TurtleUpgradeSerialiser<?> serialiser;
 | 
			
		||||
        private final Item toolItem;
 | 
			
		||||
        private @Nullable String adjective;
 | 
			
		||||
        private @Nullable Item craftingItem;
 | 
			
		||||
        private @Nullable Float damageMultiplier = null;
 | 
			
		||||
        private @Nullable TagKey<Block> breakable;
 | 
			
		||||
        private boolean allowEnchantments = false;
 | 
			
		||||
        private TurtleToolDurability consumeDurability = TurtleToolDurability.NEVER;
 | 
			
		||||
 | 
			
		||||
        ToolBuilder(ResourceLocation id, TurtleUpgradeSerialiser<?> serialiser, Item toolItem) {
 | 
			
		||||
            this.id = id;
 | 
			
		||||
            this.serialiser = serialiser;
 | 
			
		||||
            this.toolItem = toolItem;
 | 
			
		||||
            craftingItem = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Specify a custom adjective for this tool. By default this takes its adjective from the tool item.
 | 
			
		||||
         *
 | 
			
		||||
         * @param adjective The new adjective to use.
 | 
			
		||||
         * @return The tool builder, for further use.
 | 
			
		||||
         */
 | 
			
		||||
        public ToolBuilder adjective(String adjective) {
 | 
			
		||||
            this.adjective = adjective;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Specify a custom item which is used to craft this upgrade. By default this is the same as the provided tool
 | 
			
		||||
         * item, but you may wish to override it.
 | 
			
		||||
         *
 | 
			
		||||
         * @param craftingItem The item used to craft this upgrade.
 | 
			
		||||
         * @return The tool builder, for further use.
 | 
			
		||||
         */
 | 
			
		||||
        public ToolBuilder craftingItem(Item craftingItem) {
 | 
			
		||||
            this.craftingItem = craftingItem;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The amount of damage a swing of this tool will do. This is multiplied by {@link Attributes#ATTACK_DAMAGE} to
 | 
			
		||||
         * get the final damage.
 | 
			
		||||
         *
 | 
			
		||||
         * @param damageMultiplier The damage multiplier.
 | 
			
		||||
         * @return The tool builder, for further use.
 | 
			
		||||
         */
 | 
			
		||||
        public ToolBuilder damageMultiplier(float damageMultiplier) {
 | 
			
		||||
            this.damageMultiplier = damageMultiplier;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Indicate that this upgrade allows items which have been {@linkplain ItemStack#isEnchanted() enchanted} or have
 | 
			
		||||
         * {@linkplain ItemStack#getAttributeModifiers(EquipmentSlot) custom attribute modifiers}.
 | 
			
		||||
         *
 | 
			
		||||
         * @return The tool builder, for further use.
 | 
			
		||||
         */
 | 
			
		||||
        public ToolBuilder allowEnchantments() {
 | 
			
		||||
            allowEnchantments = true;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Set when the tool will consume durability.
 | 
			
		||||
         *
 | 
			
		||||
         * @param durability The durability predicate.
 | 
			
		||||
         * @return The tool builder, for further use.
 | 
			
		||||
         */
 | 
			
		||||
        public ToolBuilder consumeDurability(TurtleToolDurability durability) {
 | 
			
		||||
            consumeDurability = durability;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Provide a list of breakable blocks. If not given, the tool can break all blocks. If given, only blocks
 | 
			
		||||
         * in this tag, those in {@link ComputerCraftTags.Blocks#TURTLE_ALWAYS_BREAKABLE} and "insta-mine" ones can
 | 
			
		||||
         * be broken.
 | 
			
		||||
         *
 | 
			
		||||
         * @param breakable The tag containing all blocks breakable by this item.
 | 
			
		||||
         * @return The tool builder, for further use.
 | 
			
		||||
         * @see ComputerCraftTags.Blocks
 | 
			
		||||
         */
 | 
			
		||||
        public ToolBuilder breakable(TagKey<Block> breakable) {
 | 
			
		||||
            this.breakable = breakable;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Register this as an upgrade.
 | 
			
		||||
         *
 | 
			
		||||
         * @param add The callback given to {@link #addUpgrades(Consumer)}.
 | 
			
		||||
         */
 | 
			
		||||
        public void add(Consumer<Upgrade<TurtleUpgradeSerialiser<?>>> add) {
 | 
			
		||||
            add.accept(new Upgrade<>(id, serialiser, s -> {
 | 
			
		||||
                s.addProperty("item", PlatformHelper.get().getRegistryKey(Registries.ITEM, toolItem).toString());
 | 
			
		||||
                if (adjective != null) s.addProperty("adjective", adjective);
 | 
			
		||||
                if (craftingItem != null) {
 | 
			
		||||
                    s.addProperty("craftItem", PlatformHelper.get().getRegistryKey(Registries.ITEM, craftingItem).toString());
 | 
			
		||||
                }
 | 
			
		||||
                if (damageMultiplier != null) s.addProperty("damageMultiplier", damageMultiplier);
 | 
			
		||||
                if (breakable != null) s.addProperty("breakable", breakable.location().toString());
 | 
			
		||||
                if (allowEnchantments) s.addProperty("allowEnchantments", true);
 | 
			
		||||
                if (consumeDurability != TurtleToolDurability.NEVER) {
 | 
			
		||||
                    s.addProperty("consumeDurability", consumeDurability.getSerializedName());
 | 
			
		||||
                }
 | 
			
		||||
            }));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,83 +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.
 | 
			
		||||
 *
 | 
			
		||||
 * @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,37 +9,34 @@ 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.core.component.DataComponentPatch;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Common functionality between {@link ITurtleUpgrade} and {@link IPocketUpgrade}.
 | 
			
		||||
 */
 | 
			
		||||
public interface UpgradeBase {
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets a unique identifier representing this type of turtle upgrade. eg: "computercraft:wireless_modem"
 | 
			
		||||
     * or "my_mod:my_upgrade".
 | 
			
		||||
     * <p>
 | 
			
		||||
     * You should use a unique resource domain to ensure this upgrade is uniquely identified.
 | 
			
		||||
     * The upgrade will fail registration if an already used ID is specified.
 | 
			
		||||
     * Get the type of this upgrade.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The unique ID for this upgrade.
 | 
			
		||||
     * @return The type of this upgrade.
 | 
			
		||||
     */
 | 
			
		||||
    ResourceLocation getUpgradeID();
 | 
			
		||||
    UpgradeType<?> getType();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Return an unlocalised string to describe this type of computer in item names.
 | 
			
		||||
     * A description of this upgrade for use in item names.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This should typically be a {@linkplain Component#translatable(String) translation key}, rather than a hard coded
 | 
			
		||||
     * string.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * Examples of built-in adjectives are "Wireless", "Mining" and "Crafty".
 | 
			
		||||
     *
 | 
			
		||||
     * @return The localisation key for this upgrade's adjective.
 | 
			
		||||
     * @return The text component for this upgrade's adjective.
 | 
			
		||||
     */
 | 
			
		||||
    String getUnlocalisedAdjective();
 | 
			
		||||
    Component getAdjective();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Return an item stack representing the type of item that a computer must be crafted
 | 
			
		||||
@@ -57,8 +54,8 @@ public interface UpgradeBase {
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the item stack representing a currently equipped turtle upgrade.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * While upgrades can store upgrade data ({@link ITurtleAccess#getUpgradeNBTData(TurtleSide)} and
 | 
			
		||||
     * {@link IPocketAccess#getUpgradeNBTData()}}, by default this data is discarded when an upgrade is unequipped,
 | 
			
		||||
     * While upgrades can store upgrade data ({@link ITurtleAccess#getUpgradeData(TurtleSide)} and
 | 
			
		||||
     * {@link IPocketAccess#getUpgradeData()}}, by default this data is discarded when an upgrade is unequipped,
 | 
			
		||||
     * and the original item stack is returned.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * By overriding this method, you can create a new {@link ItemStack} which contains enough data to
 | 
			
		||||
@@ -70,24 +67,24 @@ public interface UpgradeBase {
 | 
			
		||||
     * @param upgradeData The current upgrade data. This should <strong>NOT</strong> be mutated.
 | 
			
		||||
     * @return The item stack returned when unequipping.
 | 
			
		||||
     */
 | 
			
		||||
    default ItemStack getUpgradeItem(CompoundTag upgradeData) {
 | 
			
		||||
    default ItemStack getUpgradeItem(DataComponentPatch upgradeData) {
 | 
			
		||||
        return getCraftingItem();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Extract upgrade data from an {@link ItemStack}.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This upgrade data will be available with {@link ITurtleAccess#getUpgradeNBTData(TurtleSide)} or
 | 
			
		||||
     * {@link IPocketAccess#getUpgradeNBTData()}.
 | 
			
		||||
     * This upgrade data will be available with {@link ITurtleAccess#getUpgradeData(TurtleSide)} or
 | 
			
		||||
     * {@link IPocketAccess#getUpgradeData()}.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This should be an inverse to {@link #getUpgradeItem(CompoundTag)}.
 | 
			
		||||
     * This should be an inverse to {@link #getUpgradeItem(DataComponentPatch)}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param stack The stack that was equipped by the turtle or pocket computer. This will have the same item as
 | 
			
		||||
     *              {@link #getCraftingItem()}.
 | 
			
		||||
     * @return The upgrade data that should be set on the turtle or pocket computer.
 | 
			
		||||
     */
 | 
			
		||||
    default CompoundTag getUpgradeData(ItemStack stack) {
 | 
			
		||||
        return new CompoundTag();
 | 
			
		||||
    default DataComponentPatch getUpgradeData(ItemStack stack) {
 | 
			
		||||
        return DataComponentPatch.EMPTY;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -97,26 +94,15 @@ public interface UpgradeBase {
 | 
			
		||||
     * the original stack. In order to prevent people losing items with enchantments (or
 | 
			
		||||
     * repairing items with non-0 damage), we impose additional checks on the item.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * 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.
 | 
			
		||||
     * The default check requires that any NBT is exactly the same as the crafting item,
 | 
			
		||||
     * but this may be relaxed for your upgrade.
 | 
			
		||||
     *
 | 
			
		||||
     * @param stack The stack to check. This is guaranteed to be non-empty and have the same item as
 | 
			
		||||
     *              {@link #getCraftingItem()}.
 | 
			
		||||
     * @return If this stack may be used to equip this upgrade.
 | 
			
		||||
     */
 | 
			
		||||
    default boolean isItemSuitable(ItemStack stack) {
 | 
			
		||||
        var crafting = getCraftingItem();
 | 
			
		||||
 | 
			
		||||
        // 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);
 | 
			
		||||
        return ItemStack.isSameItemSameComponents(getCraftingItem(), stack);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -125,7 +111,7 @@ public interface UpgradeBase {
 | 
			
		||||
     *
 | 
			
		||||
     * @param id The upgrade ID.
 | 
			
		||||
     * @return The  generated adjective.
 | 
			
		||||
     * @see #getUnlocalisedAdjective()
 | 
			
		||||
     * @see #getAdjective()
 | 
			
		||||
     */
 | 
			
		||||
    static String getDefaultAdjective(ResourceLocation id) {
 | 
			
		||||
        return Util.makeDescriptionId("upgrade", id) + ".adjective";
 | 
			
		||||
 
 | 
			
		||||
@@ -6,59 +6,57 @@ package dan200.computercraft.api.upgrades;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
 | 
			
		||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
			
		||||
import net.minecraft.nbt.CompoundTag;
 | 
			
		||||
import net.minecraft.core.Holder;
 | 
			
		||||
import net.minecraft.core.component.DataComponentPatch;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import org.jetbrains.annotations.Contract;
 | 
			
		||||
import org.jspecify.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An upgrade (i.e. a {@link ITurtleUpgrade}) and its current upgrade data.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * <strong>IMPORTANT:</strong> The {@link #data()} in an upgrade data is often a reference to the original upgrade data.
 | 
			
		||||
 * Be careful to take a {@linkplain #copy() defensive copy} if you plan to use the data in this upgrade.
 | 
			
		||||
 *
 | 
			
		||||
 * @param upgrade The current upgrade.
 | 
			
		||||
 * @param holder The current upgrade holder.
 | 
			
		||||
 * @param data   The upgrade's data.
 | 
			
		||||
 * @param <T>    The type of upgrade, either {@link ITurtleUpgrade} or {@link IPocketUpgrade}.
 | 
			
		||||
 */
 | 
			
		||||
public record UpgradeData<T extends UpgradeBase>(T upgrade, CompoundTag data) {
 | 
			
		||||
public record UpgradeData<T extends UpgradeBase>(Holder.Reference<T> holder, DataComponentPatch data) {
 | 
			
		||||
    /**
 | 
			
		||||
     * A utility method to construct a new {@link UpgradeData} instance.
 | 
			
		||||
     *
 | 
			
		||||
     * @param upgrade An upgrade.
 | 
			
		||||
     * @param holder An upgrade.
 | 
			
		||||
     * @param data   The upgrade's data.
 | 
			
		||||
     * @param <T>    The type of upgrade.
 | 
			
		||||
     * @return The new {@link UpgradeData} instance.
 | 
			
		||||
     */
 | 
			
		||||
    public static <T extends UpgradeBase> UpgradeData<T> of(T upgrade, CompoundTag data) {
 | 
			
		||||
        return new UpgradeData<>(upgrade, data);
 | 
			
		||||
    public static <T extends UpgradeBase> UpgradeData<T> of(Holder.Reference<T> holder, DataComponentPatch data) {
 | 
			
		||||
        return new UpgradeData<>(holder, data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create an {@link UpgradeData} containing the default {@linkplain #data() data} for an upgrade.
 | 
			
		||||
     *
 | 
			
		||||
     * @param upgrade The upgrade instance.
 | 
			
		||||
     * @param holder The upgrade instance.
 | 
			
		||||
     * @param <T>    The type of upgrade.
 | 
			
		||||
     * @return The default upgrade data.
 | 
			
		||||
     */
 | 
			
		||||
    public static <T extends UpgradeBase> UpgradeData<T> ofDefault(T upgrade) {
 | 
			
		||||
        return of(upgrade, upgrade.getUpgradeData(upgrade.getCraftingItem()));
 | 
			
		||||
    public static <T extends UpgradeBase> UpgradeData<T> ofDefault(Holder.Reference<T> holder) {
 | 
			
		||||
        var upgrade = holder.value();
 | 
			
		||||
        return of(holder, upgrade.getUpgradeData(upgrade.getCraftingItem()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public UpgradeData {
 | 
			
		||||
        if (!holder.isBound()) throw new IllegalArgumentException("Holder is not bound");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Take a copy of a (possibly {@code null}) {@link UpgradeData} instance.
 | 
			
		||||
     * Get the current upgrade.
 | 
			
		||||
     *
 | 
			
		||||
     * @param upgrade The copied upgrade data.
 | 
			
		||||
     * @param <T>     The type of upgrade.
 | 
			
		||||
     * @return The newly created upgrade data.
 | 
			
		||||
     * @return The current upgrade.
 | 
			
		||||
     */
 | 
			
		||||
    @Contract("!null -> !null; null -> null")
 | 
			
		||||
    public static <T extends UpgradeBase> @Nullable UpgradeData<T> copyOf(@Nullable UpgradeData<T> upgrade) {
 | 
			
		||||
        return upgrade == null ? null : upgrade.copy();
 | 
			
		||||
    public T upgrade() {
 | 
			
		||||
        return holder().value();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the {@linkplain UpgradeBase#getUpgradeItem(CompoundTag) upgrade item} for this upgrade.
 | 
			
		||||
     * Get the {@linkplain UpgradeBase#getUpgradeItem(DataComponentPatch) upgrade item} for this upgrade.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This returns a defensive copy of the item, to prevent accidental mutation of the upgrade data or original
 | 
			
		||||
     * {@linkplain UpgradeBase#getCraftingItem() upgrade stack}.
 | 
			
		||||
@@ -66,16 +64,6 @@ public record UpgradeData<T extends UpgradeBase>(T upgrade, CompoundTag data) {
 | 
			
		||||
     * @return This upgrade's item.
 | 
			
		||||
     */
 | 
			
		||||
    public ItemStack getUpgradeItem() {
 | 
			
		||||
        return upgrade.getUpgradeItem(data).copy();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Take a copy of this {@link UpgradeData}. This returns a new instance with the same upgrade and a fresh copy of
 | 
			
		||||
     * the upgrade data.
 | 
			
		||||
     *
 | 
			
		||||
     * @return A copy of the current instance.
 | 
			
		||||
     */
 | 
			
		||||
    public UpgradeData<T> copy() {
 | 
			
		||||
        return new UpgradeData<>(upgrade(), data().copy());
 | 
			
		||||
        return upgrade().getUpgradeItem(data).copy();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,177 +0,0 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
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.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.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.jspecify.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import java.util.*;
 | 
			
		||||
import java.util.concurrent.CompletableFuture;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A data generator/provider for turtle and pocket computer upgrades. This should not be extended directly, instead see
 | 
			
		||||
 * the other subclasses.
 | 
			
		||||
 *
 | 
			
		||||
 * @param <T> The base class of upgrades.
 | 
			
		||||
 * @param <R> The upgrade serialiser to register for.
 | 
			
		||||
 * @see dan200.computercraft.api.turtle.TurtleUpgradeDataProvider
 | 
			
		||||
 * @see dan200.computercraft.api.pocket.PocketUpgradeDataProvider
 | 
			
		||||
 */
 | 
			
		||||
public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends UpgradeSerialiser<? extends T>> implements DataProvider {
 | 
			
		||||
    private final PackOutput output;
 | 
			
		||||
    private final String name;
 | 
			
		||||
    private final String folder;
 | 
			
		||||
    private final ResourceKey<Registry<R>> registry;
 | 
			
		||||
 | 
			
		||||
    private @Nullable List<T> upgrades;
 | 
			
		||||
 | 
			
		||||
    protected UpgradeDataProvider(PackOutput output, String name, String folder, ResourceKey<Registry<R>> registry) {
 | 
			
		||||
        this.output = output;
 | 
			
		||||
        this.name = name;
 | 
			
		||||
        this.folder = folder;
 | 
			
		||||
        this.registry = registry;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Register an upgrade using a "simple" serialiser (e.g. {@link TurtleUpgradeSerialiser#simple(Function)}).
 | 
			
		||||
     *
 | 
			
		||||
     * @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) {
 | 
			
		||||
        if (!(serialiser instanceof SimpleSerialiser)) {
 | 
			
		||||
            throw new IllegalStateException(serialiser + " must be a simple() seriaiser.");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return new Upgrade<>(id, serialiser, s -> {
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Register an upgrade using a "simple" serialiser (e.g. {@link TurtleUpgradeSerialiser#simple(Function)}).
 | 
			
		||||
     *
 | 
			
		||||
     * @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) {
 | 
			
		||||
        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())
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add all turtle or pocket computer upgrades.
 | 
			
		||||
     *
 | 
			
		||||
     * <h4>Example</h4>
 | 
			
		||||
     * {@snippet class=com.example.examplemod.data.TurtleDataProvider region=body}
 | 
			
		||||
     *
 | 
			
		||||
     * @param addUpgrade A callback used to register an upgrade.
 | 
			
		||||
     */
 | 
			
		||||
    protected abstract void addUpgrades(Consumer<Upgrade<R>> addUpgrade);
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public CompletableFuture<?> run(CachedOutput cache) {
 | 
			
		||||
        var base = output.getOutputFolder().resolve("data");
 | 
			
		||||
 | 
			
		||||
        Set<ResourceLocation> seen = new HashSet<>();
 | 
			
		||||
        List<T> upgrades = new ArrayList<>();
 | 
			
		||||
        List<CompletableFuture<?>> futures = new ArrayList<>();
 | 
			
		||||
        addUpgrades(upgrade -> {
 | 
			
		||||
            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());
 | 
			
		||||
            upgrade.serialise().accept(json);
 | 
			
		||||
 | 
			
		||||
            futures.add(DataProvider.saveStable(cache, json, base.resolve(upgrade.id().getNamespace() + "/" + folder + "/" + upgrade.id().getPath() + ".json")));
 | 
			
		||||
 | 
			
		||||
            try {
 | 
			
		||||
                var result = upgrade.serialiser().fromJson(upgrade.id(), json);
 | 
			
		||||
                upgrades.add(result);
 | 
			
		||||
            } catch (IllegalArgumentException | JsonParseException e) {
 | 
			
		||||
                LOGGER.error("Failed to parse {} {}", name, upgrade.id(), e);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.upgrades = Collections.unmodifiableList(upgrades);
 | 
			
		||||
        return Util.sequenceFailFast(futures);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final String getName() {
 | 
			
		||||
        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);
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public List<T> getGeneratedUpgrades() {
 | 
			
		||||
        if (upgrades == null) throw new IllegalStateException("Upgrades have not been generated yet");
 | 
			
		||||
        return upgrades;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A constructed upgrade instance, produced {@link #addUpgrades(Consumer)}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param id         The ID for this upgrade.
 | 
			
		||||
     * @param serialiser The serialiser which reads and writes this upgrade.
 | 
			
		||||
     * @param serialise  Augment the generated JSON with additional fields.
 | 
			
		||||
     * @param <R>        The type of upgrade serialiser.
 | 
			
		||||
     */
 | 
			
		||||
    public record Upgrade<R extends UpgradeSerialiser<?>>(
 | 
			
		||||
        ResourceLocation id, R serialiser, Consumer<JsonObject> serialise
 | 
			
		||||
    ) {
 | 
			
		||||
        /**
 | 
			
		||||
         * Convenience method for registering an upgrade.
 | 
			
		||||
         *
 | 
			
		||||
         * @param add The callback given to {@link #addUpgrades(Consumer)}
 | 
			
		||||
         */
 | 
			
		||||
        public void add(Consumer<Upgrade<R>> add) {
 | 
			
		||||
            add.accept(this);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Return a new {@link Upgrade} which requires the given mod to be present.
 | 
			
		||||
         * <p>
 | 
			
		||||
         * This uses mod-loader-specific hooks (Forge's crafting conditions and Fabric's resource conditions). If using
 | 
			
		||||
         * this in a multi-loader setup, you must generate resources separately for the two loaders.
 | 
			
		||||
         *
 | 
			
		||||
         * @param modId The id of the mod.
 | 
			
		||||
         * @return A new upgrade instance.
 | 
			
		||||
         */
 | 
			
		||||
        public Upgrade<R> requireMod(String modId) {
 | 
			
		||||
            return new Upgrade<>(id, serialiser, json -> {
 | 
			
		||||
                PlatformHelper.get().addRequiredModCondition(json, modId);
 | 
			
		||||
                serialise.accept(json);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,52 +0,0 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.api.upgrades;
 | 
			
		||||
 | 
			
		||||
import com.google.gson.JsonObject;
 | 
			
		||||
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
 | 
			
		||||
import net.minecraft.network.FriendlyByteBuf;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Base interface for upgrade serialisers. This should generally not be implemented directly, instead implementing one
 | 
			
		||||
 * of {@link TurtleUpgradeSerialiser} or {@link PocketUpgradeSerialiser}.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * However, it may sometimes be useful to implement this if you have some shared logic between upgrade types.
 | 
			
		||||
 *
 | 
			
		||||
 * @param <T> The upgrade that this class can serialise and deserialise.
 | 
			
		||||
 * @see TurtleUpgradeSerialiser
 | 
			
		||||
 * @see PocketUpgradeSerialiser
 | 
			
		||||
 */
 | 
			
		||||
public interface UpgradeSerialiser<T extends UpgradeBase> {
 | 
			
		||||
    /**
 | 
			
		||||
     * Read this upgrade from a JSON file in a datapack.
 | 
			
		||||
     *
 | 
			
		||||
     * @param id     The ID of this upgrade.
 | 
			
		||||
     * @param object The JSON object to load this upgrade from.
 | 
			
		||||
     * @return The constructed upgrade, with a {@link UpgradeBase#getUpgradeID()} equal to {@code id}.
 | 
			
		||||
     * @see net.minecraft.util.GsonHelper For additional JSON helper methods.
 | 
			
		||||
     */
 | 
			
		||||
    T fromJson(ResourceLocation id, JsonObject object);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Read this upgrade from a network packet, sent from the server.
 | 
			
		||||
     *
 | 
			
		||||
     * @param id     The ID of this upgrade.
 | 
			
		||||
     * @param buffer The buffer object to read this upgrade from.
 | 
			
		||||
     * @return The constructed upgrade, with a {@link UpgradeBase#getUpgradeID()} equal to {@code id}.
 | 
			
		||||
     */
 | 
			
		||||
    T fromNetwork(ResourceLocation id, FriendlyByteBuf buffer);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Write this upgrade to a network packet, to be sent to the client.
 | 
			
		||||
     *
 | 
			
		||||
     * @param buffer  The buffer object to write this upgrade to
 | 
			
		||||
     * @param upgrade The upgrade to write.
 | 
			
		||||
     */
 | 
			
		||||
    void toNetwork(FriendlyByteBuf buffer, T upgrade);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,88 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.api.upgrades;
 | 
			
		||||
 | 
			
		||||
import com.mojang.serialization.MapCodec;
 | 
			
		||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
 | 
			
		||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
			
		||||
import net.minecraft.core.registries.BuiltInRegistries;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.item.crafting.Recipe;
 | 
			
		||||
import net.minecraft.world.level.storage.loot.functions.LootItemFunction;
 | 
			
		||||
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The type of a {@linkplain ITurtleUpgrade turtle} or {@linkplain IPocketUpgrade pocket} upgrade.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Turtle and pocket computer upgrades are registered using Minecraft's dynamic registry system. As a result, they
 | 
			
		||||
 * follow a similar design to other dynamic content, such as {@linkplain Recipe recipes} or {@link LootItemFunction
 | 
			
		||||
 * loot functions}.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * While the {@link ITurtleUpgrade}/{@link IPocketUpgrade} class should contain the core logic of the upgrade, they are
 | 
			
		||||
 * not registered directly. Instead, each upgrade class has a corresponding {@link UpgradeType}, which is responsible
 | 
			
		||||
 * for loading the upgrade from a datapack. The upgrade type should then be registered in its appropriate registry
 | 
			
		||||
 * ({@link ITurtleUpgrade#typeRegistry()}, {@link IPocketUpgrade#typeRegistry()}).
 | 
			
		||||
 * <p>
 | 
			
		||||
 * In order to register the actual upgrade, a JSON file referencing your upgrade type should be added to a datapack. It
 | 
			
		||||
 * is recommended to do this via the data generators.
 | 
			
		||||
 *
 | 
			
		||||
 * <h2 id="datagen">Data Generation</h2>
 | 
			
		||||
 * As turtle and pocket upgrades are just loaded using vanilla's dynamic loaders, one may use the same data generation
 | 
			
		||||
 * tools as you would for any other dynamic registry.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * See <a href="../turtle/ITurtleUpgrade.html#datagen">the turtle upgrade docs</a> for a concrete example.
 | 
			
		||||
 *
 | 
			
		||||
 * @param <T> The upgrade subclass that this upgrade type represents.
 | 
			
		||||
 * @see ITurtleUpgrade
 | 
			
		||||
 * @see IPocketUpgrade
 | 
			
		||||
 */
 | 
			
		||||
public interface UpgradeType<T extends UpgradeBase> {
 | 
			
		||||
    /**
 | 
			
		||||
     * The codec to read and write this upgrade from a datapack.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The codec for this upgrade.
 | 
			
		||||
     */
 | 
			
		||||
    MapCodec<T> codec();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a new upgrade type.
 | 
			
		||||
     *
 | 
			
		||||
     * @param codec The codec
 | 
			
		||||
     * @param <T>   The type of the generated upgrade.
 | 
			
		||||
     * @return The newly created upgrade type.
 | 
			
		||||
     */
 | 
			
		||||
    static <T extends UpgradeBase> UpgradeType<T> create(MapCodec<T> codec) {
 | 
			
		||||
        return new UpgradeTypeImpl<>(codec);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create an upgrade type for an upgrade that takes no arguments.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * If you might want to vary the item, it's suggested you use {@link #simpleWithCustomItem(Function)} instead.
 | 
			
		||||
     *
 | 
			
		||||
     * @param instance Generate a new upgrade with a specific ID.
 | 
			
		||||
     * @param <T>      The type of the generated upgrade.
 | 
			
		||||
     * @return A new upgrade type.
 | 
			
		||||
     */
 | 
			
		||||
    static <T extends UpgradeBase> UpgradeType<T> simple(T instance) {
 | 
			
		||||
        return create(MapCodec.unit(instance));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create an upgrade type 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 A new upgrade type.
 | 
			
		||||
     * @see #simple(UpgradeBase)  For upgrades whose crafting stack should not vary.
 | 
			
		||||
     */
 | 
			
		||||
    static <T extends UpgradeBase> UpgradeType<T> simpleWithCustomItem(Function<ItemStack, T> factory) {
 | 
			
		||||
        return create(BuiltInRegistries.ITEM.byNameCodec()
 | 
			
		||||
            .xmap(x -> factory.apply(new ItemStack(x)), x -> x.getCraftingItem().getItem())
 | 
			
		||||
            .fieldOf("item"));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,16 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.api.upgrades;
 | 
			
		||||
 | 
			
		||||
import com.mojang.serialization.MapCodec;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Simple implementation of {@link UpgradeType}.
 | 
			
		||||
 *
 | 
			
		||||
 * @param codec The codec to read/write upgrades with.
 | 
			
		||||
 * @param <T>   The upgrade subclass that this upgrade type represents.
 | 
			
		||||
 */
 | 
			
		||||
record UpgradeTypeImpl<T extends UpgradeBase>(MapCodec<T> codec) implements UpgradeType<T> {
 | 
			
		||||
}
 | 
			
		||||
@@ -4,6 +4,7 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.impl;
 | 
			
		||||
 | 
			
		||||
import com.mojang.serialization.Codec;
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.api.detail.BlockReference;
 | 
			
		||||
import dan200.computercraft.api.detail.DetailRegistry;
 | 
			
		||||
@@ -12,14 +13,15 @@ import dan200.computercraft.api.filesystem.WritableMount;
 | 
			
		||||
import dan200.computercraft.api.lua.GenericSource;
 | 
			
		||||
import dan200.computercraft.api.lua.ILuaAPIFactory;
 | 
			
		||||
import dan200.computercraft.api.media.MediaProvider;
 | 
			
		||||
import dan200.computercraft.api.media.PrintoutContents;
 | 
			
		||||
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.UpgradeType;
 | 
			
		||||
import dan200.computercraft.impl.upgrades.TurtleToolSpec;
 | 
			
		||||
import net.minecraft.core.BlockPos;
 | 
			
		||||
import net.minecraft.core.Direction;
 | 
			
		||||
import net.minecraft.core.Registry;
 | 
			
		||||
@@ -28,7 +30,8 @@ import net.minecraft.server.MinecraftServer;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.level.Level;
 | 
			
		||||
import org.jetbrains.annotations.ApiStatus;
 | 
			
		||||
import org.jspecify.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Backing interface for {@link ComputerCraftAPI}
 | 
			
		||||
@@ -67,17 +70,20 @@ public interface ComputerCraftAPIService {
 | 
			
		||||
 | 
			
		||||
    void registerRefuelHandler(TurtleRefuelHandler handler);
 | 
			
		||||
 | 
			
		||||
    ResourceKey<Registry<TurtleUpgradeSerialiser<?>>> turtleUpgradeRegistryId();
 | 
			
		||||
    ResourceKey<Registry<UpgradeType<? extends ITurtleUpgrade>>> turtleUpgradeRegistryId();
 | 
			
		||||
 | 
			
		||||
    ResourceKey<Registry<PocketUpgradeSerialiser<?>>> pocketUpgradeRegistryId();
 | 
			
		||||
    Codec<ITurtleUpgrade> turtleUpgradeCodec();
 | 
			
		||||
 | 
			
		||||
    ResourceKey<Registry<UpgradeType<? extends IPocketUpgrade>>> pocketUpgradeRegistryId();
 | 
			
		||||
 | 
			
		||||
    ITurtleUpgrade createTurtleTool(TurtleToolSpec spec);
 | 
			
		||||
 | 
			
		||||
    Codec<IPocketUpgrade> pocketUpgradeCodec();
 | 
			
		||||
 | 
			
		||||
    DetailRegistry<ItemStack> getItemStackDetailRegistry();
 | 
			
		||||
 | 
			
		||||
    DetailRegistry<BlockReference> getBlockInWorldDetailRegistry();
 | 
			
		||||
 | 
			
		||||
    @Nullable
 | 
			
		||||
    PrintoutContents getPrintoutContents(ItemStack stack);
 | 
			
		||||
 | 
			
		||||
    final class Instance {
 | 
			
		||||
        static final @Nullable ComputerCraftAPIService INSTANCE;
 | 
			
		||||
        static final @Nullable Throwable ERROR;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,91 +0,0 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
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 org.jspecify.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Abstraction layer for Forge and Fabric. See implementations for more details.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Do <strong>NOT</strong> directly reference this class. It exists for internal use by the API.
 | 
			
		||||
 */
 | 
			
		||||
@ApiStatus.Internal
 | 
			
		||||
public interface PlatformHelper {
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the current {@link PlatformHelper} instance.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The current instance.
 | 
			
		||||
     */
 | 
			
		||||
    static PlatformHelper get() {
 | 
			
		||||
        var instance = Instance.INSTANCE;
 | 
			
		||||
        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}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param object The JSON object we're generating.
 | 
			
		||||
     * @param modId  The mod ID that we require.
 | 
			
		||||
     */
 | 
			
		||||
    void addRequiredModCondition(JsonObject object, String modId);
 | 
			
		||||
 | 
			
		||||
    final class Instance {
 | 
			
		||||
        static final @Nullable PlatformHelper INSTANCE;
 | 
			
		||||
        static final @Nullable Throwable ERROR;
 | 
			
		||||
 | 
			
		||||
        static {
 | 
			
		||||
            // We don't want class initialisation to fail here (as that results in confusing errors). Instead, capture
 | 
			
		||||
            // the error and rethrow it when accessing. This should be JITted away in the common case.
 | 
			
		||||
            var helper = Services.tryLoad(PlatformHelper.class);
 | 
			
		||||
            INSTANCE = helper.instance();
 | 
			
		||||
            ERROR = helper.error();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private Instance() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -5,8 +5,8 @@
 | 
			
		||||
package dan200.computercraft.impl;
 | 
			
		||||
 | 
			
		||||
import org.jetbrains.annotations.ApiStatus;
 | 
			
		||||
import org.jspecify.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.io.Serial;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 
 | 
			
		||||
@@ -5,8 +5,8 @@
 | 
			
		||||
package dan200.computercraft.impl;
 | 
			
		||||
 | 
			
		||||
import org.jetbrains.annotations.ApiStatus;
 | 
			
		||||
import org.jspecify.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.ServiceLoader;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,49 +0,0 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.impl.upgrades;
 | 
			
		||||
 | 
			
		||||
import com.google.gson.JsonObject;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeBase;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
 | 
			
		||||
import net.minecraft.network.FriendlyByteBuf;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.util.GsonHelper;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import org.jetbrains.annotations.ApiStatus;
 | 
			
		||||
 | 
			
		||||
import java.util.function.BiFunction;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Simple serialiser which returns a constant upgrade with a custom crafting item.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Do <strong>NOT</strong> directly reference this class. It exists for internal use by the API.
 | 
			
		||||
 *
 | 
			
		||||
 * @param <T> The upgrade that this class can serialise and deserialise.
 | 
			
		||||
 */
 | 
			
		||||
@ApiStatus.Internal
 | 
			
		||||
public abstract class SerialiserWithCraftingItem<T extends UpgradeBase> implements UpgradeSerialiser<T> {
 | 
			
		||||
    private final BiFunction<ResourceLocation, ItemStack, T> factory;
 | 
			
		||||
 | 
			
		||||
    protected SerialiserWithCraftingItem(BiFunction<ResourceLocation, ItemStack, T> factory) {
 | 
			
		||||
        this.factory = factory;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final 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) {
 | 
			
		||||
        var item = buffer.readItem();
 | 
			
		||||
        return factory.apply(id, item);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final void toNetwork(FriendlyByteBuf buffer, T upgrade) {
 | 
			
		||||
        buffer.writeItem(upgrade.getCraftingItem());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,44 +0,0 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.impl.upgrades;
 | 
			
		||||
 | 
			
		||||
import com.google.gson.JsonObject;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeBase;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
 | 
			
		||||
import net.minecraft.network.FriendlyByteBuf;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import org.jetbrains.annotations.ApiStatus;
 | 
			
		||||
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Simple serialiser which returns a constant upgrade.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Do <strong>NOT</strong> directly reference this class. It exists for internal use by the API.
 | 
			
		||||
 *
 | 
			
		||||
 * @param <T> The upgrade that this class can serialise and deserialise.
 | 
			
		||||
 */
 | 
			
		||||
@ApiStatus.Internal
 | 
			
		||||
public abstract class SimpleSerialiser<T extends UpgradeBase> implements UpgradeSerialiser<T> {
 | 
			
		||||
    private final Function<ResourceLocation, T> constructor;
 | 
			
		||||
 | 
			
		||||
    public SimpleSerialiser(Function<ResourceLocation, T> constructor) {
 | 
			
		||||
        this.constructor = constructor;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final T fromJson(ResourceLocation id, JsonObject object) {
 | 
			
		||||
        return constructor.apply(id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final T fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) {
 | 
			
		||||
        return constructor.apply(id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final void toNetwork(FriendlyByteBuf buffer, T upgrade) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,49 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.impl.upgrades;
 | 
			
		||||
 | 
			
		||||
import com.mojang.serialization.Codec;
 | 
			
		||||
import com.mojang.serialization.MapCodec;
 | 
			
		||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleToolDurability;
 | 
			
		||||
import net.minecraft.core.registries.BuiltInRegistries;
 | 
			
		||||
import net.minecraft.core.registries.Registries;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
import net.minecraft.network.chat.ComponentSerialization;
 | 
			
		||||
import net.minecraft.tags.TagKey;
 | 
			
		||||
import net.minecraft.world.item.Item;
 | 
			
		||||
import net.minecraft.world.level.block.Block;
 | 
			
		||||
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The template for a turtle tool.
 | 
			
		||||
 *
 | 
			
		||||
 * @param adjective         The adjective for this tool.
 | 
			
		||||
 * @param item              The tool used.
 | 
			
		||||
 * @param damageMultiplier  The damage multiplier for this tool.
 | 
			
		||||
 * @param allowEnchantments Whether to allow enchantments.
 | 
			
		||||
 * @param consumeDurability When to consume durability.
 | 
			
		||||
 * @param breakable         The items breakable by this tool.
 | 
			
		||||
 */
 | 
			
		||||
public record TurtleToolSpec(
 | 
			
		||||
    Component adjective,
 | 
			
		||||
    Item item,
 | 
			
		||||
    float damageMultiplier,
 | 
			
		||||
    boolean allowEnchantments,
 | 
			
		||||
    TurtleToolDurability consumeDurability,
 | 
			
		||||
    Optional<TagKey<Block>> breakable
 | 
			
		||||
) {
 | 
			
		||||
    public static final float DEFAULT_DAMAGE_MULTIPLIER = 3.0f;
 | 
			
		||||
 | 
			
		||||
    public static final MapCodec<TurtleToolSpec> CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
 | 
			
		||||
        ComponentSerialization.CODEC.fieldOf("adjective").forGetter(TurtleToolSpec::adjective),
 | 
			
		||||
        BuiltInRegistries.ITEM.byNameCodec().fieldOf("item").forGetter(TurtleToolSpec::item),
 | 
			
		||||
        Codec.FLOAT.optionalFieldOf("damageMultiplier", DEFAULT_DAMAGE_MULTIPLIER).forGetter(TurtleToolSpec::damageMultiplier),
 | 
			
		||||
        Codec.BOOL.optionalFieldOf("allowEnchantments", false).forGetter(TurtleToolSpec::allowEnchantments),
 | 
			
		||||
        TurtleToolDurability.CODEC.optionalFieldOf("consumeDurability", TurtleToolDurability.NEVER).forGetter(TurtleToolSpec::consumeDurability),
 | 
			
		||||
        TagKey.codec(Registries.BLOCK).optionalFieldOf("breakable").forGetter(TurtleToolSpec::breakable)
 | 
			
		||||
    ).apply(instance, TurtleToolSpec::new));
 | 
			
		||||
}
 | 
			
		||||
@@ -23,7 +23,7 @@ configurations {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
repositories {
 | 
			
		||||
    maven("https://maven.minecraftforge.net/") {
 | 
			
		||||
    maven("https://maven.neoforged.net/") {
 | 
			
		||||
        content {
 | 
			
		||||
            includeModule("org.spongepowered", "mixin")
 | 
			
		||||
        }
 | 
			
		||||
@@ -36,7 +36,10 @@ dependencies {
 | 
			
		||||
    api(commonClasses(project(":common-api")))
 | 
			
		||||
    clientApi(clientClasses(project(":common-api")))
 | 
			
		||||
 | 
			
		||||
    compileOnly(libs.mixin)
 | 
			
		||||
    compileOnly(libs.mixinExtra)
 | 
			
		||||
    compileOnly(libs.bundles.externalMods.common)
 | 
			
		||||
    compileOnly(variantOf(libs.create.forge) { classifier("slim") }) { isTransitive = false }
 | 
			
		||||
    clientCompileOnly(variantOf(libs.emi) { classifier("api") })
 | 
			
		||||
 | 
			
		||||
    annotationProcessorEverywhere(libs.autoService)
 | 
			
		||||
@@ -81,7 +84,7 @@ val luaJavadoc by tasks.registering(Javadoc::class) {
 | 
			
		||||
    options.addStringOption("project-root", rootProject.file(".").absolutePath)
 | 
			
		||||
    options.noTimestamp(false)
 | 
			
		||||
 | 
			
		||||
    javadocTool = javaToolchains.javadocToolFor { languageVersion = CCTweakedPlugin.JDK_VERSION }
 | 
			
		||||
    javadocTool = javaToolchains.javadocToolFor { languageVersion = CCTweakedPlugin.JAVA_VERSION }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
val lintLua by tasks.registering(IlluaminateExec::class) {
 | 
			
		||||
 
 | 
			
		||||
@@ -38,8 +38,8 @@ import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.level.block.state.BlockState;
 | 
			
		||||
import net.minecraft.world.phys.BlockHitResult;
 | 
			
		||||
import net.minecraft.world.phys.HitResult;
 | 
			
		||||
import org.jspecify.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -111,7 +111,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());
 | 
			
		||||
@@ -131,8 +131,8 @@ public final class ClientHooks {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void addTurtleUpgrade(Consumer<String> out, TurtleBlockEntity turtle, TurtleSide side) {
 | 
			
		||||
        var upgrade = turtle.getUpgrade(side);
 | 
			
		||||
        if (upgrade != null) out.accept(String.format("Upgrade[%s]: %s", side, upgrade.getUpgradeID()));
 | 
			
		||||
        var upgrade = turtle.getAccess().getUpgradeWithData(side);
 | 
			
		||||
        if (upgrade != null) out.accept(String.format("Upgrade[%s]: %s", side, upgrade.holder().key().location()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -141,7 +141,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());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -22,16 +22,17 @@ import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
 | 
			
		||||
import dan200.computercraft.core.util.Colour;
 | 
			
		||||
import dan200.computercraft.shared.ModRegistry;
 | 
			
		||||
import dan200.computercraft.shared.command.CommandComputerCraft;
 | 
			
		||||
import dan200.computercraft.shared.common.IColouredItem;
 | 
			
		||||
import dan200.computercraft.shared.computer.core.ComputerState;
 | 
			
		||||
import dan200.computercraft.shared.computer.core.ServerContext;
 | 
			
		||||
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
 | 
			
		||||
import dan200.computercraft.shared.media.items.DiskItem;
 | 
			
		||||
import dan200.computercraft.shared.media.items.TreasureDiskItem;
 | 
			
		||||
import dan200.computercraft.shared.turtle.TurtleOverlay;
 | 
			
		||||
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;
 | 
			
		||||
@@ -42,16 +43,19 @@ import net.minecraft.network.chat.Component;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.server.packs.resources.PreparableReloadListener;
 | 
			
		||||
import net.minecraft.server.packs.resources.ResourceProvider;
 | 
			
		||||
import net.minecraft.util.FastColor;
 | 
			
		||||
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.item.component.DyedItemColor;
 | 
			
		||||
import net.minecraft.world.level.ItemLike;
 | 
			
		||||
import org.jspecify.annotations.Nullable;
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import java.util.function.BiConsumer;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
import java.util.function.Supplier;
 | 
			
		||||
@@ -65,8 +69,6 @@ import java.util.function.Supplier;
 | 
			
		||||
 * @see ModRegistry The common registry for actual game objects.
 | 
			
		||||
 */
 | 
			
		||||
public final class ClientRegistry {
 | 
			
		||||
    private static final Logger LOG = LoggerFactory.getLogger(ClientRegistry.class);
 | 
			
		||||
 | 
			
		||||
    private ClientRegistry() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -87,14 +89,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, 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);
 | 
			
		||||
 | 
			
		||||
        registerItemProperty(itemProperties, "state",
 | 
			
		||||
            new UnclampedPropertyFunction((stack, world, player, random) -> {
 | 
			
		||||
                var computer = ClientPocketComputers.get(stack);
 | 
			
		||||
@@ -103,28 +97,41 @@ public final class ClientRegistry {
 | 
			
		||||
            ModRegistry.Items.POCKET_COMPUTER_NORMAL, ModRegistry.Items.POCKET_COMPUTER_ADVANCED
 | 
			
		||||
        );
 | 
			
		||||
        registerItemProperty(itemProperties, "coloured",
 | 
			
		||||
            (stack, world, player, random) -> IColouredItem.getColourBasic(stack) != -1 ? 1 : 0,
 | 
			
		||||
            (stack, world, player, random) -> DyedItemColor.getOrDefault(stack, -1) != -1 ? 1 : 0,
 | 
			
		||||
            ModRegistry.Items.POCKET_COMPUTER_NORMAL, ModRegistry.Items.POCKET_COMPUTER_ADVANCED
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void registerMenuScreens(RegisterMenuScreen register) {
 | 
			
		||||
        register.<AbstractComputerMenu, ComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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"),
 | 
			
		||||
            new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_right")
 | 
			
		||||
        register.register(ModRegistry.TurtleUpgradeTypes.SPEAKER.get(), TurtleUpgradeModeller.sided(
 | 
			
		||||
            ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_left"),
 | 
			
		||||
            ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_right")
 | 
			
		||||
        ));
 | 
			
		||||
        register.register(ModRegistry.TurtleSerialisers.WORKBENCH.get(), TurtleUpgradeModeller.sided(
 | 
			
		||||
            new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_left"),
 | 
			
		||||
            new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_right")
 | 
			
		||||
        register.register(ModRegistry.TurtleUpgradeTypes.WORKBENCH.get(), TurtleUpgradeModeller.sided(
 | 
			
		||||
            ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_left"),
 | 
			
		||||
            ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_right")
 | 
			
		||||
        ));
 | 
			
		||||
        register.register(ModRegistry.TurtleSerialisers.WIRELESS_MODEM_NORMAL.get(), new TurtleModemModeller(false));
 | 
			
		||||
        register.register(ModRegistry.TurtleSerialisers.WIRELESS_MODEM_ADVANCED.get(), new TurtleModemModeller(true));
 | 
			
		||||
        register.register(ModRegistry.TurtleSerialisers.TOOL.get(), TurtleUpgradeModeller.flatItem());
 | 
			
		||||
        register.register(ModRegistry.TurtleUpgradeTypes.WIRELESS_MODEM.get(), new TurtleModemModeller());
 | 
			
		||||
        register.register(ModRegistry.TurtleUpgradeTypes.TOOL.get(), TurtleUpgradeModeller.flatItem());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SafeVarargs
 | 
			
		||||
    private static void registerItemProperty(RegisterItemProperty itemProperties, String name, ClampedItemPropertyFunction getter, Supplier<? extends Item>... items) {
 | 
			
		||||
        var id = new ResourceLocation(ComputerCraftAPI.MOD_ID, name);
 | 
			
		||||
        var id = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, name);
 | 
			
		||||
        for (var item : items) itemProperties.register(item.get(), id, getter);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -140,26 +147,25 @@ public final class ClientRegistry {
 | 
			
		||||
        register.accept(GuiSprites.initialise(minecraft.getTextureManager()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static final String[] EXTRA_MODELS = new String[]{
 | 
			
		||||
        "block/turtle_colour",
 | 
			
		||||
        "block/turtle_elf_overlay",
 | 
			
		||||
        "block/turtle_rainbow_overlay",
 | 
			
		||||
        "block/turtle_trans_overlay",
 | 
			
		||||
    private static final ResourceLocation[] EXTRA_MODELS = {
 | 
			
		||||
        TurtleOverlay.ELF_MODEL,
 | 
			
		||||
        TurtleBlockEntityRenderer.COLOUR_TURTLE_MODEL,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    public static void registerExtraModels(Consumer<ResourceLocation> register) {
 | 
			
		||||
        for (var model : EXTRA_MODELS) register.accept(new ResourceLocation(ComputerCraftAPI.MOD_ID, model));
 | 
			
		||||
    public static void registerExtraModels(Consumer<ResourceLocation> register, Collection<ResourceLocation> extraModels) {
 | 
			
		||||
        for (var model : EXTRA_MODELS) register.accept(model);
 | 
			
		||||
        extraModels.forEach(register);
 | 
			
		||||
        TurtleUpgradeModellers.getDependencies().forEach(register);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void registerItemColours(BiConsumer<ItemColor, ItemLike> register) {
 | 
			
		||||
        register.accept(
 | 
			
		||||
            (stack, layer) -> layer == 1 ? ((DiskItem) stack.getItem()).getColour(stack) : 0xFFFFFF,
 | 
			
		||||
            (stack, layer) -> layer == 1 ? DiskItem.getColour(stack) : -1,
 | 
			
		||||
            ModRegistry.Items.DISK.get()
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        register.accept(
 | 
			
		||||
            (stack, layer) -> layer == 1 ? TreasureDiskItem.getColour(stack) : 0xFFFFFF,
 | 
			
		||||
            (stack, layer) -> layer == 1 ? DyedItemColor.getOrDefault(stack, Colour.BLUE.getARGB()) : -1,
 | 
			
		||||
            ModRegistry.Items.TREASURE_DISK.get()
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
@@ -172,32 +178,21 @@ public final class ClientRegistry {
 | 
			
		||||
 | 
			
		||||
    private static int getPocketColour(ItemStack stack, int layer) {
 | 
			
		||||
        return switch (layer) {
 | 
			
		||||
            default -> 0xFFFFFF;
 | 
			
		||||
            case 1 -> IColouredItem.getColourBasic(stack); // Frame colour
 | 
			
		||||
            default -> -1;
 | 
			
		||||
            case 1 -> DyedItemColor.getOrDefault(stack, -1); // Frame colour
 | 
			
		||||
            case 2 -> { // Light colour
 | 
			
		||||
                var computer = ClientPocketComputers.get(stack);
 | 
			
		||||
                yield computer == null || computer.getLightState() == -1 ? Colour.BLACK.getHex() : computer.getLightState();
 | 
			
		||||
                yield computer == null || computer.getLightState() == -1 ? Colour.BLACK.getARGB() : FastColor.ARGB32.opaque(computer.getLightState());
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static int getTurtleColour(ItemStack stack, int layer) {
 | 
			
		||||
        return layer == 0 ? ((IColouredItem) stack.getItem()).getColour(stack) : 0xFFFFFF;
 | 
			
		||||
        return layer == 0 ? DyedItemColor.getOrDefault(stack, -1) : -1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void registerShaders(ResourceProvider resources, BiConsumer<ShaderInstance, Consumer<ShaderInstance>> load) throws IOException {
 | 
			
		||||
        RenderTypes.registerShaders(resources, (name, create, onLoaded) -> {
 | 
			
		||||
            ShaderInstance shader;
 | 
			
		||||
            try {
 | 
			
		||||
                shader = create.get();
 | 
			
		||||
            } catch (Exception e) {
 | 
			
		||||
                LOG.error("Failed to load {}", name, e);
 | 
			
		||||
                onLoaded.accept(null);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            load.accept(shader, onLoaded);
 | 
			
		||||
        });
 | 
			
		||||
        RenderTypes.registerShaders(resources, load);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private record UnclampedPropertyFunction(
 | 
			
		||||
 
 | 
			
		||||
@@ -15,8 +15,8 @@ import net.minecraft.client.gui.components.ChatComponent;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
import net.minecraft.util.Mth;
 | 
			
		||||
import org.apache.commons.lang3.StringUtils;
 | 
			
		||||
import org.jspecify.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -75,7 +75,7 @@ public class ClientTableFormatter implements TableFormatter {
 | 
			
		||||
 | 
			
		||||
        var tag = createTag(table.getId());
 | 
			
		||||
        if (chat.allMessages.removeIf(guiMessage -> guiMessage.tag() != null && Objects.equals(guiMessage.tag().logTag(), tag.logTag()))) {
 | 
			
		||||
            chat.refreshTrimmedMessage();
 | 
			
		||||
            chat.rescaleChat();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        TableFormatter.super.display(table);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,20 +0,0 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.client;
 | 
			
		||||
 | 
			
		||||
import com.google.auto.service.AutoService;
 | 
			
		||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
 | 
			
		||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
 | 
			
		||||
import dan200.computercraft.impl.client.ComputerCraftAPIClientService;
 | 
			
		||||
 | 
			
		||||
@AutoService(ComputerCraftAPIClientService.class)
 | 
			
		||||
public final class ComputerCraftAPIClientImpl implements ComputerCraftAPIClientService {
 | 
			
		||||
    @Override
 | 
			
		||||
    public <T extends ITurtleUpgrade> void registerTurtleUpgradeModeller(TurtleUpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller) {
 | 
			
		||||
        TurtleUpgradeModellers.register(serialiser, modeller);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -26,11 +26,11 @@ import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
import net.minecraft.world.entity.player.Inventory;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import org.jspecify.annotations.Nullable;
 | 
			
		||||
import org.lwjgl.glfw.GLFW;
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.nio.ByteBuffer;
 | 
			
		||||
import java.nio.file.Files;
 | 
			
		||||
@@ -127,7 +127,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);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -10,10 +10,10 @@ import dan200.computercraft.shared.computer.menu.ComputerMenu;
 | 
			
		||||
import dan200.computercraft.shared.network.server.ComputerActionServerMessage;
 | 
			
		||||
import dan200.computercraft.shared.network.server.KeyEventServerMessage;
 | 
			
		||||
import dan200.computercraft.shared.network.server.MouseEventServerMessage;
 | 
			
		||||
import dan200.computercraft.shared.network.server.PasteEventComputerMessage;
 | 
			
		||||
import dan200.computercraft.shared.network.server.QueueEventServerMessage;
 | 
			
		||||
import net.minecraft.world.inventory.AbstractContainerMenu;
 | 
			
		||||
 | 
			
		||||
import java.nio.ByteBuffer;
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An {@link InputHandler} for use on the client.
 | 
			
		||||
@@ -27,11 +27,6 @@ public final class ClientInputHandler implements InputHandler {
 | 
			
		||||
        this.menu = menu;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void terminate() {
 | 
			
		||||
        ClientNetworking.sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.TERMINATE));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void turnOn() {
 | 
			
		||||
        ClientNetworking.sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.TURN_ON));
 | 
			
		||||
@@ -47,6 +42,11 @@ public final class ClientInputHandler implements InputHandler {
 | 
			
		||||
        ClientNetworking.sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.REBOOT));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void queueEvent(String event, @Nullable Object[] arguments) {
 | 
			
		||||
        ClientNetworking.sendToServer(new QueueEventServerMessage(menu, event, arguments));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void keyDown(int key, boolean repeat) {
 | 
			
		||||
        ClientNetworking.sendToServer(new KeyEventServerMessage(menu, repeat ? KeyEventServerMessage.Action.REPEAT : KeyEventServerMessage.Action.DOWN, key));
 | 
			
		||||
@@ -57,16 +57,6 @@ public final class ClientInputHandler implements InputHandler {
 | 
			
		||||
        ClientNetworking.sendToServer(new KeyEventServerMessage(menu, KeyEventServerMessage.Action.UP, key));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void charTyped(byte chr) {
 | 
			
		||||
        ClientNetworking.sendToServer(new KeyEventServerMessage(menu, KeyEventServerMessage.Action.CHAR, chr));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void paste(ByteBuffer contents) {
 | 
			
		||||
        ClientNetworking.sendToServer(new PasteEventComputerMessage(menu, contents));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void mouseClick(int button, int x, int y) {
 | 
			
		||||
        ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.Action.CLICK, button, x, y));
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@ import net.minecraft.world.entity.player.Inventory;
 | 
			
		||||
 * The GUI for disk drives.
 | 
			
		||||
 */
 | 
			
		||||
public class DiskDriveScreen extends AbstractContainerScreen<DiskDriveMenu> {
 | 
			
		||||
    private static final ResourceLocation BACKGROUND = new ResourceLocation("computercraft", "textures/gui/disk_drive.png");
 | 
			
		||||
    private static final ResourceLocation BACKGROUND = ResourceLocation.fromNamespaceAndPath("computercraft", "textures/gui/disk_drive.png");
 | 
			
		||||
 | 
			
		||||
    public DiskDriveScreen(DiskDriveMenu container, Inventory player, Component title) {
 | 
			
		||||
        super(container, player, title);
 | 
			
		||||
@@ -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);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -11,8 +11,8 @@ import net.minecraft.client.renderer.texture.TextureAtlasSprite;
 | 
			
		||||
import net.minecraft.client.renderer.texture.TextureManager;
 | 
			
		||||
import net.minecraft.client.resources.TextureAtlasHolder;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import org.jspecify.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
import java.util.stream.Stream;
 | 
			
		||||
 | 
			
		||||
@@ -20,7 +20,7 @@ import java.util.stream.Stream;
 | 
			
		||||
 * Sprite sheet for all GUI texutres in the mod.
 | 
			
		||||
 */
 | 
			
		||||
public final class GuiSprites extends TextureAtlasHolder {
 | 
			
		||||
    public static final ResourceLocation SPRITE_SHEET = new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui");
 | 
			
		||||
    public static final ResourceLocation SPRITE_SHEET = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "gui");
 | 
			
		||||
    public static final ResourceLocation TEXTURE = SPRITE_SHEET.withPath(x -> "textures/atlas/" + x + ".png");
 | 
			
		||||
 | 
			
		||||
    public static final ButtonTextures TURNED_OFF = button("turned_off");
 | 
			
		||||
@@ -32,21 +32,18 @@ public final class GuiSprites extends TextureAtlasHolder {
 | 
			
		||||
    public static final ComputerTextures COMPUTER_COMMAND = computer("command", false, true);
 | 
			
		||||
    public static final ComputerTextures COMPUTER_COLOUR = computer("colour", true, false);
 | 
			
		||||
 | 
			
		||||
    public static final ResourceLocation TURTLE_NORMAL_SELECTED_SLOT = new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/sprites/turtle_normal_selected_slot");
 | 
			
		||||
    public static final ResourceLocation TURTLE_ADVANCED_SELECTED_SLOT = new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/sprites/turtle_advanced_selected_slot");
 | 
			
		||||
 | 
			
		||||
    private static ButtonTextures button(String name) {
 | 
			
		||||
        return new ButtonTextures(
 | 
			
		||||
            new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/sprites/buttons/" + name),
 | 
			
		||||
            new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/sprites/buttons/" + name + "_hover")
 | 
			
		||||
            ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "gui/buttons/" + name),
 | 
			
		||||
            ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "gui/buttons/" + name + "_hover")
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static ComputerTextures computer(String name, boolean pocket, boolean sidebar) {
 | 
			
		||||
        return new ComputerTextures(
 | 
			
		||||
            new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/border_" + name),
 | 
			
		||||
            pocket ? new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/pocket_bottom_" + name) : null,
 | 
			
		||||
            sidebar ? new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/sidebar_" + name) : null
 | 
			
		||||
            ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "gui/border_" + name),
 | 
			
		||||
            pocket ? ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "gui/pocket_bottom_" + name) : null,
 | 
			
		||||
            sidebar ? ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "gui/sidebar_" + name) : null
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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 = ResourceLocation.withDefaultNamespace("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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,40 +0,0 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.client.gui;
 | 
			
		||||
 | 
			
		||||
import org.lwjgl.glfw.GLFW;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Supports for converting/translating key codes.
 | 
			
		||||
 */
 | 
			
		||||
public class KeyConverter {
 | 
			
		||||
    /**
 | 
			
		||||
     * GLFW's key events refer to the physical key code, rather than the "actual" key code (with keyboard layout
 | 
			
		||||
     * applied).
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This makes sense for WASD-style input, but is a right pain for keyboard shortcuts — this function attempts to
 | 
			
		||||
     * translate those keys back to their "actual" key code. See also
 | 
			
		||||
     * <a href="https://github.com/glfw/glfw/issues/1502"> this discussion on GLFW's GitHub.</a>
 | 
			
		||||
     *
 | 
			
		||||
     * @param key      The current key code.
 | 
			
		||||
     * @param scanCode The current scan code.
 | 
			
		||||
     * @return The translated key code.
 | 
			
		||||
     */
 | 
			
		||||
    public static int physicalToActual(int key, int scanCode) {
 | 
			
		||||
        var name = GLFW.glfwGetKeyName(key, scanCode);
 | 
			
		||||
        if (name == null || name.length() != 1) return key;
 | 
			
		||||
 | 
			
		||||
        // If we've got a single character as the key name, treat that as the ASCII value of the key,
 | 
			
		||||
        // and map that back to a key code.
 | 
			
		||||
        var character = name.charAt(0);
 | 
			
		||||
 | 
			
		||||
        // 0-9 and A-Z map directly to their GLFW key (they're the same ASCII code).
 | 
			
		||||
        if ((character >= '0' && character <= '9') || (character >= 'A' && character <= 'Z')) return character;
 | 
			
		||||
        // a-z map to GLFW_KEY_{A,Z}
 | 
			
		||||
        if (character >= 'a' && character <= 'z') return GLFW.GLFW_KEY_A + (character - 'a');
 | 
			
		||||
 | 
			
		||||
        return key;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -15,9 +15,9 @@ import net.minecraft.client.gui.screens.Screen;
 | 
			
		||||
import net.minecraft.client.gui.screens.inventory.MenuAccess;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
import net.minecraft.world.entity.player.Inventory;
 | 
			
		||||
import org.jspecify.annotations.Nullable;
 | 
			
		||||
import org.lwjgl.glfw.GLFW;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
 | 
			
		||||
import static dan200.computercraft.core.util.Nullability.assertNonNull;
 | 
			
		||||
@@ -66,9 +66,9 @@ public class NoTermComputerScreen<T extends AbstractComputerMenu> extends Screen
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean mouseScrolled(double pMouseX, double pMouseY, double pDelta) {
 | 
			
		||||
        Objects.requireNonNull(minecraft().player).getInventory().swapPaint(pDelta);
 | 
			
		||||
        return super.mouseScrolled(pMouseX, pMouseY, pDelta);
 | 
			
		||||
    public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, double scrollY) {
 | 
			
		||||
        Objects.requireNonNull(minecraft().player).getInventory().swapPaint(scrollY);
 | 
			
		||||
        return super.mouseScrolled(mouseX, mouseY, scrollX, scrollY);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
@@ -105,6 +105,11 @@ public class NoTermComputerScreen<T extends AbstractComputerMenu> extends Screen
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void renderBackground(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) {
 | 
			
		||||
        // Skip rendering the background.
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Minecraft minecraft() {
 | 
			
		||||
        return Nullability.assertNonNull(minecraft);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -12,8 +12,8 @@ import net.minecraft.client.gui.components.MultiLineLabel;
 | 
			
		||||
import net.minecraft.client.gui.screens.Screen;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import org.jspecify.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import static dan200.computercraft.core.util.Nullability.assertNonNull;
 | 
			
		||||
@@ -24,7 +24,7 @@ import static dan200.computercraft.core.util.Nullability.assertNonNull;
 | 
			
		||||
 * When closed, it returns to the previous screen.
 | 
			
		||||
 */
 | 
			
		||||
public final class OptionScreen extends Screen {
 | 
			
		||||
    private static final ResourceLocation BACKGROUND = new ResourceLocation("computercraft", "textures/gui/blank_screen.png");
 | 
			
		||||
    private static final ResourceLocation BACKGROUND = ResourceLocation.fromNamespaceAndPath("computercraft", "textures/gui/blank_screen.png");
 | 
			
		||||
 | 
			
		||||
    public static final int BUTTON_WIDTH = 100;
 | 
			
		||||
    public static final int BUTTON_HEIGHT = 20;
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@ import net.minecraft.world.entity.player.Inventory;
 | 
			
		||||
 * The GUI for printers.
 | 
			
		||||
 */
 | 
			
		||||
public class PrinterScreen extends AbstractContainerScreen<PrinterMenu> {
 | 
			
		||||
    private static final ResourceLocation BACKGROUND = new ResourceLocation("computercraft", "textures/gui/printer.png");
 | 
			
		||||
    private static final ResourceLocation BACKGROUND = ResourceLocation.fromNamespaceAndPath("computercraft", "textures/gui/printer.png");
 | 
			
		||||
 | 
			
		||||
    public PrinterScreen(PrinterMenu container, Inventory player, Component title) {
 | 
			
		||||
        super(container, player, title);
 | 
			
		||||
@@ -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);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -4,14 +4,12 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.client.gui;
 | 
			
		||||
 | 
			
		||||
import com.mojang.blaze3d.vertex.Tesselator;
 | 
			
		||||
import dan200.computercraft.core.terminal.TextBuffer;
 | 
			
		||||
import dan200.computercraft.shared.ModRegistry;
 | 
			
		||||
import dan200.computercraft.shared.media.PrintoutMenu;
 | 
			
		||||
import dan200.computercraft.shared.media.items.PrintoutItem;
 | 
			
		||||
import dan200.computercraft.shared.media.items.PrintoutData;
 | 
			
		||||
import net.minecraft.client.gui.GuiGraphics;
 | 
			
		||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
 | 
			
		||||
import net.minecraft.client.renderer.MultiBufferSource;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
import net.minecraft.world.entity.player.Inventory;
 | 
			
		||||
import net.minecraft.world.inventory.AbstractContainerMenu;
 | 
			
		||||
@@ -19,7 +17,6 @@ import net.minecraft.world.inventory.ContainerListener;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import org.lwjgl.glfw.GLFW;
 | 
			
		||||
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
 | 
			
		||||
import static dan200.computercraft.client.render.PrintoutRenderer.*;
 | 
			
		||||
@@ -40,18 +37,8 @@ public final class PrintoutScreen extends AbstractContainerScreen<PrintoutMenu>
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void setPrintout(ItemStack stack) {
 | 
			
		||||
        var text = PrintoutItem.getText(stack);
 | 
			
		||||
        var textBuffers = new TextBuffer[text.length];
 | 
			
		||||
        for (var i = 0; i < textBuffers.length; i++) textBuffers[i] = new TextBuffer(text[i]);
 | 
			
		||||
 | 
			
		||||
        var colours = PrintoutItem.getColours(stack);
 | 
			
		||||
        var colourBuffers = new TextBuffer[colours.length];
 | 
			
		||||
        for (var i = 0; i < colours.length; i++) colourBuffers[i] = new TextBuffer(colours[i]);
 | 
			
		||||
 | 
			
		||||
        var pages = Math.max(text.length / PrintoutItem.LINES_PER_PAGE, 1);
 | 
			
		||||
        var book = stack.is(ModRegistry.Items.PRINTED_BOOK.get());
 | 
			
		||||
 | 
			
		||||
        printout = new PrintoutInfo(pages, book, textBuffers, colourBuffers);
 | 
			
		||||
        page = 0;
 | 
			
		||||
        printout = PrintoutInfo.of(PrintoutData.getOrEmpty(stack), stack.is(ModRegistry.Items.PRINTED_BOOK.get()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
@@ -106,15 +93,15 @@ public final class PrintoutScreen extends AbstractContainerScreen<PrintoutMenu>
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @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 (deltaY < 0) {
 | 
			
		||||
            // Scroll up goes to the next page
 | 
			
		||||
            nextPage();
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (delta > 0) {
 | 
			
		||||
        if (deltaY > 0) {
 | 
			
		||||
            // Scroll down goes to the previous page
 | 
			
		||||
            previousPage();
 | 
			
		||||
            return true;
 | 
			
		||||
@@ -125,23 +112,14 @@ public final class PrintoutScreen extends AbstractContainerScreen<PrintoutMenu>
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void renderBg(GuiGraphics graphics, float partialTicks, int mouseX, int mouseY) {
 | 
			
		||||
        // Draw the printout
 | 
			
		||||
        var renderer = MultiBufferSource.immediate(Tesselator.getInstance().getBuilder());
 | 
			
		||||
 | 
			
		||||
        drawBorder(graphics.pose(), renderer, leftPos, topPos, 0, page, printout.pages(), printout.book(), FULL_BRIGHT_LIGHTMAP);
 | 
			
		||||
        drawText(graphics.pose(), renderer, leftPos + X_TEXT_MARGIN, topPos + Y_TEXT_MARGIN, PrintoutItem.LINES_PER_PAGE * page, FULL_BRIGHT_LIGHTMAP, printout.text(), printout.colour());
 | 
			
		||||
        renderer.endBatch();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
 | 
			
		||||
        // We must take the background further back in order to not overlap with our printed pages.
 | 
			
		||||
        // Push the printout slightly forward, to avoid clipping into the background.
 | 
			
		||||
        graphics.pose().pushPose();
 | 
			
		||||
        graphics.pose().translate(0, 0, -1);
 | 
			
		||||
        renderBackground(graphics);
 | 
			
		||||
        graphics.pose().popPose();
 | 
			
		||||
        graphics.pose().translate(0, 0, 1);
 | 
			
		||||
 | 
			
		||||
        super.render(graphics, mouseX, mouseY, partialTicks);
 | 
			
		||||
        drawBorder(graphics.pose(), graphics.bufferSource(), leftPos, topPos, 0, page, printout.pages(), printout.book(), FULL_BRIGHT_LIGHTMAP);
 | 
			
		||||
        drawText(graphics.pose(), graphics.bufferSource(), leftPos + X_TEXT_MARGIN, topPos + Y_TEXT_MARGIN, PrintoutData.LINES_PER_PAGE * page, FULL_BRIGHT_LIGHTMAP, printout.text(), printout.colour());
 | 
			
		||||
 | 
			
		||||
        graphics.pose().popPose();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
@@ -149,18 +127,20 @@ public final class PrintoutScreen extends AbstractContainerScreen<PrintoutMenu>
 | 
			
		||||
        // Skip rendering labels.
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings("ArrayRecordComponent")
 | 
			
		||||
    record PrintoutInfo(int pages, boolean book, TextBuffer[] text, TextBuffer[] colour) {
 | 
			
		||||
        public static final PrintoutInfo DEFAULT;
 | 
			
		||||
        public static final PrintoutInfo DEFAULT = of(PrintoutData.EMPTY, false);
 | 
			
		||||
 | 
			
		||||
        static {
 | 
			
		||||
            var textLines = new TextBuffer[PrintoutItem.LINES_PER_PAGE];
 | 
			
		||||
            Arrays.fill(textLines, new TextBuffer(" ".repeat(PrintoutItem.LINE_MAX_LENGTH)));
 | 
			
		||||
        public static PrintoutInfo of(PrintoutData printout, boolean book) {
 | 
			
		||||
            var text = new TextBuffer[printout.lines().size()];
 | 
			
		||||
            var colours = new TextBuffer[printout.lines().size()];
 | 
			
		||||
            for (var i = 0; i < text.length; i++) {
 | 
			
		||||
                var line = printout.lines().get(i);
 | 
			
		||||
                text[i] = new TextBuffer(line.text());
 | 
			
		||||
                colours[i] = new TextBuffer(line.foreground());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var colourLines = new TextBuffer[PrintoutItem.LINES_PER_PAGE];
 | 
			
		||||
            Arrays.fill(colourLines, new TextBuffer("f".repeat(PrintoutItem.LINE_MAX_LENGTH)));
 | 
			
		||||
 | 
			
		||||
            DEFAULT = new PrintoutInfo(1, false, textLines, colourLines);
 | 
			
		||||
            var pages = Math.max(text.length / PrintoutData.LINES_PER_PAGE, 1);
 | 
			
		||||
            return new PrintoutInfo(pages, book, text, colours);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -23,8 +23,8 @@ import static dan200.computercraft.shared.turtle.inventory.TurtleMenu.*;
 | 
			
		||||
 * The GUI for turtles.
 | 
			
		||||
 */
 | 
			
		||||
public class TurtleScreen extends AbstractComputerScreen<TurtleMenu> {
 | 
			
		||||
    private static final ResourceLocation BACKGROUND_NORMAL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "textures/gui/turtle_normal.png");
 | 
			
		||||
    private static final ResourceLocation BACKGROUND_ADVANCED = new ResourceLocation(ComputerCraftAPI.MOD_ID, "textures/gui/turtle_advanced.png");
 | 
			
		||||
    private static final ResourceLocation BACKGROUND_NORMAL = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "textures/gui/turtle_normal.png");
 | 
			
		||||
    private static final ResourceLocation BACKGROUND_ADVANCED = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "textures/gui/turtle_advanced.png");
 | 
			
		||||
 | 
			
		||||
    private static final int TEX_WIDTH = 278;
 | 
			
		||||
    private static final int TEX_HEIGHT = 217;
 | 
			
		||||
@@ -54,9 +54,9 @@ public class TurtleScreen extends AbstractComputerScreen<TurtleMenu> {
 | 
			
		||||
        if (slot >= 0) {
 | 
			
		||||
            var slotX = slot % 4;
 | 
			
		||||
            var slotY = slot / 4;
 | 
			
		||||
            graphics.blit(
 | 
			
		||||
                leftPos + TURTLE_START_X - 2 + slotX * 18, topPos + PLAYER_START_Y - 2 + slotY * 18, 0, 22, 22,
 | 
			
		||||
                GuiSprites.get(advanced ? GuiSprites.TURTLE_ADVANCED_SELECTED_SLOT : GuiSprites.TURTLE_NORMAL_SELECTED_SLOT)
 | 
			
		||||
            graphics.blit(texture,
 | 
			
		||||
                leftPos + TURTLE_START_X - 2 + slotX * 18, topPos + PLAYER_START_Y - 2 + slotY * 18, 0,
 | 
			
		||||
                0, 217, 24, 24, FULL_TEX_SIZE, FULL_TEX_SIZE
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -55,7 +55,7 @@ public final class ComputerSidebar {
 | 
			
		||||
        add.accept(new DynamicImageButton(
 | 
			
		||||
            x, y, ICON_WIDTH, ICON_HEIGHT,
 | 
			
		||||
            GuiSprites.TERMINATE::get,
 | 
			
		||||
            b -> input.terminate(),
 | 
			
		||||
            b -> input.queueEvent("terminate"),
 | 
			
		||||
            new HintedMessage(
 | 
			
		||||
                Component.translatable("gui.computercraft.tooltip.terminate"),
 | 
			
		||||
                Component.translatable("gui.computercraft.tooltip.terminate.key")
 | 
			
		||||
 
 | 
			
		||||
@@ -12,8 +12,8 @@ import net.minecraft.client.gui.components.Button;
 | 
			
		||||
import net.minecraft.client.gui.components.Tooltip;
 | 
			
		||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
import org.jspecify.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.function.Supplier;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -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(
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,6 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.client.gui.widgets;
 | 
			
		||||
 | 
			
		||||
import com.mojang.blaze3d.vertex.Tesselator;
 | 
			
		||||
import dan200.computercraft.client.gui.KeyConverter;
 | 
			
		||||
import dan200.computercraft.client.render.RenderTypes;
 | 
			
		||||
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
 | 
			
		||||
import dan200.computercraft.core.terminal.Terminal;
 | 
			
		||||
@@ -17,7 +15,6 @@ import net.minecraft.client.gui.components.AbstractWidget;
 | 
			
		||||
import net.minecraft.client.gui.narration.NarratedElementType;
 | 
			
		||||
import net.minecraft.client.gui.narration.NarrationElementOutput;
 | 
			
		||||
import net.minecraft.client.gui.screens.Screen;
 | 
			
		||||
import net.minecraft.client.renderer.MultiBufferSource;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
import org.lwjgl.glfw.GLFW;
 | 
			
		||||
 | 
			
		||||
@@ -72,8 +69,11 @@ public class TerminalWidget extends AbstractWidget {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean charTyped(char ch, int modifiers) {
 | 
			
		||||
        var terminalChar = StringUtil.unicodeToTerminal(ch);
 | 
			
		||||
        if (StringUtil.isTypableChar(terminalChar)) computer.charTyped((byte) terminalChar);
 | 
			
		||||
        if (ch >= 32 && ch <= 126 || ch >= 160 && ch <= 255) {
 | 
			
		||||
            // Queue the char event for any printable chars in byte range
 | 
			
		||||
            computer.queueEvent("char", new Object[]{ Character.toString(ch) });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -86,7 +86,7 @@ public class TerminalWidget extends AbstractWidget {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ((modifiers & GLFW.GLFW_MOD_CONTROL) != 0) {
 | 
			
		||||
            switch (KeyConverter.physicalToActual(key, scancode)) {
 | 
			
		||||
            switch (key) {
 | 
			
		||||
                case GLFW.GLFW_KEY_T -> {
 | 
			
		||||
                    if (terminateTimer < 0) terminateTimer = 0;
 | 
			
		||||
                }
 | 
			
		||||
@@ -110,8 +110,8 @@ public class TerminalWidget extends AbstractWidget {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void paste() {
 | 
			
		||||
        var clipboard = StringUtil.getClipboardString(Minecraft.getInstance().keyboardHandler.getClipboard());
 | 
			
		||||
        if (clipboard.remaining() > 0) computer.paste(clipboard);
 | 
			
		||||
        var clipboard = StringUtil.normaliseClipboardString(Minecraft.getInstance().keyboardHandler.getClipboard());
 | 
			
		||||
        if (!clipboard.isEmpty()) computer.queueEvent("paste", new Object[]{ clipboard });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
@@ -122,7 +122,7 @@ public class TerminalWidget extends AbstractWidget {
 | 
			
		||||
            computer.keyUp(key);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        switch (KeyConverter.physicalToActual(key, scancode)) {
 | 
			
		||||
        switch (key) {
 | 
			
		||||
            case GLFW.GLFW_KEY_T -> terminateTimer = -1;
 | 
			
		||||
            case GLFW.GLFW_KEY_R -> rebootTimer = -1;
 | 
			
		||||
            case GLFW.GLFW_KEY_S -> shutdownTimer = -1;
 | 
			
		||||
@@ -193,16 +193,16 @@ public class TerminalWidget extends AbstractWidget {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean mouseScrolled(double mouseX, double mouseY, double delta) {
 | 
			
		||||
    public boolean mouseScrolled(double mouseX, double mouseY, double deltaX, double deltaY) {
 | 
			
		||||
        if (!inTermRegion(mouseX, mouseY)) return false;
 | 
			
		||||
        if (!hasMouseSupport() || delta == 0) return false;
 | 
			
		||||
        if (!hasMouseSupport() || deltaY == 0) return false;
 | 
			
		||||
 | 
			
		||||
        var charX = (int) ((mouseX - innerX) / FONT_WIDTH);
 | 
			
		||||
        var charY = (int) ((mouseY - innerY) / FONT_HEIGHT);
 | 
			
		||||
        charX = Math.min(Math.max(charX, 0), terminal.getWidth() - 1);
 | 
			
		||||
        charY = Math.min(Math.max(charY, 0), terminal.getHeight() - 1);
 | 
			
		||||
 | 
			
		||||
        computer.mouseScroll(delta < 0 ? 1 : -1, charX + 1, charY + 1);
 | 
			
		||||
        computer.mouseScroll(deltaY < 0 ? 1 : -1, charX + 1, charY + 1);
 | 
			
		||||
 | 
			
		||||
        lastMouseX = charX;
 | 
			
		||||
        lastMouseY = charY;
 | 
			
		||||
@@ -220,7 +220,7 @@ public class TerminalWidget extends AbstractWidget {
 | 
			
		||||
 | 
			
		||||
    public void update() {
 | 
			
		||||
        if (terminateTimer >= 0 && terminateTimer < TERMINATE_TIME && (terminateTimer += 0.05f) > TERMINATE_TIME) {
 | 
			
		||||
            computer.terminate();
 | 
			
		||||
            computer.queueEvent("terminate");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (shutdownTimer >= 0 && shutdownTimer < TERMINATE_TIME && (shutdownTimer += 0.05f) > TERMINATE_TIME) {
 | 
			
		||||
@@ -257,15 +257,12 @@ public class TerminalWidget extends AbstractWidget {
 | 
			
		||||
    public void renderWidget(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
 | 
			
		||||
        if (!visible) return;
 | 
			
		||||
 | 
			
		||||
        var bufferSource = MultiBufferSource.immediate(Tesselator.getInstance().getBuilder());
 | 
			
		||||
        var emitter = FixedWidthFontRenderer.toVertexConsumer(graphics.pose(), bufferSource.getBuffer(RenderTypes.TERMINAL));
 | 
			
		||||
        var emitter = FixedWidthFontRenderer.toVertexConsumer(graphics.pose(), graphics.bufferSource().getBuffer(RenderTypes.TERMINAL));
 | 
			
		||||
 | 
			
		||||
        FixedWidthFontRenderer.drawTerminal(
 | 
			
		||||
            emitter,
 | 
			
		||||
            (float) innerX, (float) innerY, terminal, (float) MARGIN, (float) MARGIN, (float) MARGIN, (float) MARGIN
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        bufferSource.endBatch();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user