mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-11-03 23:22:59 +00:00 
			
		
		
		
	Compare commits
	
		
			133 Commits
		
	
	
		
			mc-1.19.x
			...
			v1.20.1-1.
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					a0f759527d | ||
| 
						 | 
					385e4210fa | ||
| 
						 | 
					d2896473f2 | ||
| 
						 | 
					f14cb2a3d1 | ||
| 
						 | 
					8db5c6bc3a | ||
| 
						 | 
					f26e443e81 | ||
| 
						 | 
					033378333f | ||
| 
						 | 
					ebeaa757a9 | ||
| 
						 | 
					57b1a65db3 | ||
| 
						 | 
					27c72a4571 | ||
| 
						 | 
					f284328656 | ||
| 
						 | 
					6b83c63991 | ||
| 
						 | 
					b27526bd21 | ||
| 
						 | 
					cb25f6c08a | ||
| 
						 | 
					d38b1d04e7 | ||
| 
						 | 
					9ccee75a99 | ||
| 
						 | 
					359c8d6652 | ||
| 
						 | 
					1788afacfc | ||
| 
						 | 
					f695f22d8a | ||
| 
						 | 
					bc03090ca4 | ||
| 
						 | 
					a617d0d566 | ||
| 
						 | 
					36599b321e | ||
| 
						 | 
					1d6e3f4fc0 | ||
| 
						 | 
					30dc4cb38c | ||
| 
						 | 
					be4512d1c3 | ||
| 
						 | 
					e6ee292850 | ||
| 
						 | 
					9d36f72bad | ||
| 
						 | 
					b5923c4462 | ||
| 
						 | 
					4d1e689719 | ||
| 
						 | 
					9d4af07568 | ||
| 
						 | 
					89294f4a22 | ||
| 
						 | 
					133b51b092 | ||
| 
						 | 
					272010e945 | ||
| 
						 | 
					e0889c613a | ||
| 
						 | 
					f115d43d07 | ||
| 
						 | 
					8be6b1b772 | ||
| 
						 | 
					104d5e70de | ||
| 
						 | 
					e3bda2f763 | ||
| 
						 | 
					234f69e8e5 | ||
| 
						 | 
					ed3a17f9b9 | ||
| 
						 | 
					0349c2b1f9 | ||
| 
						 | 
					03f9e6bd6d | ||
| 
						 | 
					9d8c933a14 | ||
| 
						 | 
					78bb3da58c | ||
| 
						 | 
					39a5e40c92 | ||
| 
						 | 
					763ba51919 | ||
| 
						 | 
					cf6ec8c28f | ||
| 
						 | 
					95d3b646b2 | ||
| 
						 | 
					488f66eead | ||
| 
						 | 
					1f7d245876 | ||
| 
						 | 
					af12b3a0ea | ||
| 
						 | 
					eb3e8ba677 | ||
| 
						 | 
					2043939531 | ||
| 
						 | 
					84a799d27a | ||
| 
						 | 
					fe826f5c9c | ||
| 
						 | 
					f8b7422294 | ||
| 
						 | 
					b343c01216 | ||
| 
						 | 
					76968f2f28 | ||
| 
						 | 
					1d365f5a0b | ||
| 
						 | 
					7b240cbf7e | ||
| 
						 | 
					d272a327c7 | ||
| 
						 | 
					0c0556a5bc | ||
| 
						 | 
					87345c6b2e | ||
| 
						 | 
					784e623776 | ||
| 
						 | 
					bcb3e9bd53 | ||
| 
						 | 
					c30bffbd0f | ||
| 
						 | 
					91c41856c5 | ||
| 
						 | 
					18c9723308 | ||
| 
						 | 
					aee382ed70 | ||
| 
						 | 
					6656da5877 | ||
| 
						 | 
					09e521727f | ||
| 
						 | 
					cab66a2d6e | ||
| 
						 | 
					8eabd4f303 | ||
| 
						 | 
					e3ced84885 | ||
| 
						 | 
					0929ab577d | ||
| 
						 | 
					2228733abc | ||
| 
						 | 
					e67c94d1bd | ||
| 
						 | 
					ae5a661a47 | ||
| 
						 | 
					0ff58cdc3e | ||
| 
						 | 
					1747c74770 | ||
| 
						 | 
					71669cf49c | ||
| 
						 | 
					bd327e37eb | ||
| 
						 | 
					bdce9a8170 | ||
| 
						 | 
					7e5598d084 | ||
| 
						 | 
					440fca6535 | ||
| 
						 | 
					6635edd35c | ||
| 
						 | 
					93ad40efbb | ||
| 
						 | 
					27dc8b5b2c | ||
| 
						 | 
					3ebdf7ef5e | ||
| 
						 | 
					905d4cb091 | ||
| 
						 | 
					e7ab05d064 | ||
| 
						 | 
					6ec34b42e5 | ||
| 
						 | 
					ab785a0906 | ||
| 
						 | 
					4541decd40 | ||
| 
						 | 
					747a5a53b4 | ||
| 
						 | 
					c0643fadca | ||
| 
						 | 
					0a31de43c2 | ||
| 
						 | 
					96b6947ef2 | ||
| 
						 | 
					e7a1065bfc | ||
| 
						 | 
					663eecff0c | ||
| 
						 | 
					e6125bcf60 | ||
| 
						 | 
					0d6c6e7ae7 | ||
| 
						 | 
					ae71eb3cae | ||
| 
						 | 
					3188197447 | ||
| 
						 | 
					6c8b391dab | ||
| 
						 | 
					b1248e4901 | ||
| 
						 | 
					56d97630e8 | ||
| 
						 | 
					e660192f08 | ||
| 
						 | 
					4e82bd352d | ||
| 
						 | 
					07113c3e9b | ||
| 
						 | 
					d562a051c7 | ||
| 
						 | 
					6ac09742fc | ||
| 
						 | 
					5dd6b9a637 | ||
| 
						 | 
					8fb1dd346c | ||
| 
						 | 
					0f6ea3deaf | ||
| 
						 | 
					13ed422bd5 | ||
| 
						 | 
					5b58271b92 | ||
| 
						 | 
					4e42394f33 | ||
| 
						 | 
					3371c4651c | ||
| 
						 | 
					e6bc1e4e27 | ||
| 
						 | 
					ae50f900af | ||
| 
						 | 
					48889ceb89 | ||
| 
						 | 
					8f1bf4341c | ||
| 
						 | 
					9a48b53a83 | ||
| 
						 | 
					9519448e43 | ||
| 
						 | 
					915b6f9d81 | ||
| 
						 | 
					a98f3b2a4c | ||
| 
						 | 
					ebaf49508f | ||
| 
						 | 
					c45fc94752 | ||
| 
						 | 
					fd1f6dda32 | ||
| 
						 | 
					5d6389dc50 | ||
| 
						 | 
					1ece2aa23b | ||
| 
						 | 
					ff1e5f6823 | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -7,6 +7,7 @@
 | 
			
		||||
/logs
 | 
			
		||||
/build
 | 
			
		||||
/projects/*/logs
 | 
			
		||||
/projects/fabric/fabricloader.log
 | 
			
		||||
/projects/*/build
 | 
			
		||||
/buildSrc/build
 | 
			
		||||
/out
 | 
			
		||||
 
 | 
			
		||||
@@ -44,7 +44,7 @@ repos:
 | 
			
		||||
    name: Check Java codestyle
 | 
			
		||||
    files: ".*\\.java$"
 | 
			
		||||
    language: system
 | 
			
		||||
    entry: ./gradlew checkstyleMain checkstyleTest
 | 
			
		||||
    entry: ./gradlew checkstyle
 | 
			
		||||
    pass_filenames: false
 | 
			
		||||
    require_serial: true
 | 
			
		||||
  - id: illuaminate
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										29
									
								
								.reuse/dep5
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								.reuse/dep5
									
									
									
									
									
								
							@@ -10,8 +10,8 @@ Files:
 | 
			
		||||
  projects/common/src/testMod/resources/data/cctest/structures/*
 | 
			
		||||
  projects/fabric/src/generated/*
 | 
			
		||||
  projects/forge/src/generated/*
 | 
			
		||||
  projects/web/src/export/index.json
 | 
			
		||||
  projects/web/src/export/items/minecraft/*
 | 
			
		||||
  projects/web/src/htmlTransform/export/index.json
 | 
			
		||||
  projects/web/src/htmlTransform/export/items/minecraft/*
 | 
			
		||||
Comment: Generated/data files are CC0.
 | 
			
		||||
Copyright: The CC: Tweaked Developers
 | 
			
		||||
License: CC0-1.0
 | 
			
		||||
@@ -37,10 +37,10 @@ Files:
 | 
			
		||||
  projects/fabric/src/testMod/resources/computercraft-gametest.fabric.mixins.json
 | 
			
		||||
  projects/fabric/src/testMod/resources/fabric.mod.json
 | 
			
		||||
  projects/forge/src/client/resources/computercraft-client.forge.mixins.json
 | 
			
		||||
  projects/web/src/mount/.settings
 | 
			
		||||
  projects/web/src/mount/example.nfp
 | 
			
		||||
  projects/web/src/mount/example.nft
 | 
			
		||||
  projects/web/src/mount/expr_template.lua
 | 
			
		||||
  projects/web/src/frontend/mount/.settings
 | 
			
		||||
  projects/web/src/frontend/mount/example.nfp
 | 
			
		||||
  projects/web/src/frontend/mount/example.nft
 | 
			
		||||
  projects/web/src/frontend/mount/expr_template.lua
 | 
			
		||||
  projects/web/tsconfig.json
 | 
			
		||||
Comment: Several assets where it's inconvenient to create a .license file.
 | 
			
		||||
Copyright: The CC: Tweaked Developers
 | 
			
		||||
@@ -53,19 +53,32 @@ Files:
 | 
			
		||||
  projects/common/src/main/resources/assets/computercraft/textures/*
 | 
			
		||||
  projects/common/src/main/resources/pack.mcmeta
 | 
			
		||||
  projects/common/src/main/resources/pack.png
 | 
			
		||||
  projects/core/src/main/resources/assets/computercraft/textures/gui/term_font.png
 | 
			
		||||
  projects/core/src/main/resources/data/computercraft/lua/rom/autorun/.ignoreme
 | 
			
		||||
  projects/core/src/main/resources/data/computercraft/lua/rom/help/*
 | 
			
		||||
  projects/core/src/main/resources/data/computercraft/lua/rom/programs/fun/advanced/levels/*
 | 
			
		||||
  projects/web/src/export/items/computercraft/*
 | 
			
		||||
  projects/web/src/htmlTransform/export/items/computercraft/*
 | 
			
		||||
Comment: Bulk-license original assets as CCPL.
 | 
			
		||||
Copyright: 2011 Daniel Ratcliffe
 | 
			
		||||
License: LicenseRef-CCPL
 | 
			
		||||
 | 
			
		||||
Files:
 | 
			
		||||
  projects/common/src/main/resources/assets/computercraft/lang/cs_cz.json
 | 
			
		||||
  projects/common/src/main/resources/assets/computercraft/lang/ko_kr.json
 | 
			
		||||
  projects/common/src/main/resources/assets/computercraft/lang/pl_pl.json
 | 
			
		||||
  projects/common/src/main/resources/assets/computercraft/lang/pt_br.json
 | 
			
		||||
  projects/common/src/main/resources/assets/computercraft/lang/ru_ru.json
 | 
			
		||||
  projects/common/src/main/resources/assets/computercraft/lang/uk_ua.json
 | 
			
		||||
  projects/common/src/main/resources/assets/computercraft/lang/zh_cn.json
 | 
			
		||||
Comment: Community-contributed license files
 | 
			
		||||
Copyright: 2017 The CC: Tweaked Developers
 | 
			
		||||
License: LicenseRef-CCPL
 | 
			
		||||
 | 
			
		||||
Files:
 | 
			
		||||
  projects/common/src/main/resources/assets/computercraft/lang/*
 | 
			
		||||
Comment: Community-contributed license files
 | 
			
		||||
Copyright: 2017 The CC: Tweaked Developers
 | 
			
		||||
License: LicenseRef-CCPL
 | 
			
		||||
License: MPL-2.0
 | 
			
		||||
 | 
			
		||||
Files:
 | 
			
		||||
  .github/*
 | 
			
		||||
 
 | 
			
		||||
@@ -100,7 +100,6 @@ 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=17 "Download OpenJDK 17"
 | 
			
		||||
[checkstyle]: https://checkstyle.org/
 | 
			
		||||
[illuaminate]: https://github.com/SquidDev/illuaminate/ "Illuaminate on GitHub"
 | 
			
		||||
[weblate]: https://i18n.tweaked.cc/projects/cc-tweaked/minecraft/ "CC: Tweaked weblate instance"
 | 
			
		||||
[docs]: https://tweaked.cc/ "CC: Tweaked documentation"
 | 
			
		||||
 
 | 
			
		||||
@@ -42,7 +42,6 @@ repositories {
 | 
			
		||||
    url "https://squiddev.cc/maven/"
 | 
			
		||||
    content {
 | 
			
		||||
      includeGroup("cc.tweaked")
 | 
			
		||||
      includeModule("org.squiddev", "Cobalt")
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -76,8 +75,8 @@ minecraft {
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
You should also be careful to only use classes within the `dan200.computercraft.api` package. Non-API classes are
 | 
			
		||||
subject to change at any point. If you depend on functionality outside the API, file an issue, and we can look into
 | 
			
		||||
exposing more features.
 | 
			
		||||
subject to change at any point. If you depend on functionality outside the API (or need to mixin to CC:T), please file
 | 
			
		||||
an issue to let me know!
 | 
			
		||||
 | 
			
		||||
We bundle the API sources with the jar, so documentation should be easily viewable within your editor. Alternatively,
 | 
			
		||||
the generated documentation [can be browsed online](https://tweaked.cc/javadoc/).
 | 
			
		||||
 
 | 
			
		||||
@@ -2,13 +2,19 @@
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
import cc.tweaked.gradle.JUnitExt
 | 
			
		||||
import net.fabricmc.loom.api.LoomGradleExtensionAPI
 | 
			
		||||
import net.fabricmc.loom.util.gradle.SourceSetHelper
 | 
			
		||||
import org.jetbrains.gradle.ext.compiler
 | 
			
		||||
import org.jetbrains.gradle.ext.runConfigurations
 | 
			
		||||
import org.jetbrains.gradle.ext.settings
 | 
			
		||||
 | 
			
		||||
plugins {
 | 
			
		||||
    publishing
 | 
			
		||||
    alias(libs.plugins.taskTree)
 | 
			
		||||
    alias(libs.plugins.githubRelease)
 | 
			
		||||
    alias(libs.plugins.gradleVersions)
 | 
			
		||||
    alias(libs.plugins.versionCatalogUpdate)
 | 
			
		||||
    id("org.jetbrains.gradle.plugin.idea-ext")
 | 
			
		||||
    id("cc-tweaked")
 | 
			
		||||
}
 | 
			
		||||
@@ -38,6 +44,50 @@ githubRelease {
 | 
			
		||||
 | 
			
		||||
tasks.publish { dependsOn(tasks.githubRelease) }
 | 
			
		||||
 | 
			
		||||
idea.project.settings.runConfigurations {
 | 
			
		||||
    register<JUnitExt>("Core Tests") {
 | 
			
		||||
        vmParameters = "-ea"
 | 
			
		||||
        moduleName = "${idea.project.name}.core.test"
 | 
			
		||||
        packageName = ""
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    register<JUnitExt>("CraftOS Tests") {
 | 
			
		||||
        vmParameters = "-ea"
 | 
			
		||||
        moduleName = "${idea.project.name}.core.test"
 | 
			
		||||
        className = "dan200.computercraft.core.ComputerTestDelegate"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    register<JUnitExt>("CraftOS Tests (Fast)") {
 | 
			
		||||
        vmParameters = "-ea -Dcc.skip_keywords=slow"
 | 
			
		||||
        moduleName = "${idea.project.name}.core.test"
 | 
			
		||||
        className = "dan200.computercraft.core.ComputerTestDelegate"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    register<JUnitExt>("Common Tests") {
 | 
			
		||||
        vmParameters = "-ea"
 | 
			
		||||
        moduleName = "${idea.project.name}.common.test"
 | 
			
		||||
        packageName = ""
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    register<JUnitExt>("Fabric Tests") {
 | 
			
		||||
        val fabricProject = evaluationDependsOn(":fabric")
 | 
			
		||||
        val classPathGroup = fabricProject.extensions.getByType<LoomGradleExtensionAPI>().mods
 | 
			
		||||
            .joinToString(File.pathSeparator + File.pathSeparator) { modSettings ->
 | 
			
		||||
                SourceSetHelper.getClasspath(modSettings, project).joinToString(File.pathSeparator) { it.absolutePath }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        vmParameters = "-ea -Dfabric.classPathGroups=$classPathGroup"
 | 
			
		||||
        moduleName = "${idea.project.name}.fabric.test"
 | 
			
		||||
        packageName = ""
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    register<JUnitExt>("Forge Tests") {
 | 
			
		||||
        vmParameters = "-ea"
 | 
			
		||||
        moduleName = "${idea.project.name}.forge.test"
 | 
			
		||||
        packageName = ""
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
idea.project.settings.compiler.javac {
 | 
			
		||||
    // We want ErrorProne to be present when compiling via IntelliJ, as it offers some helpful warnings
 | 
			
		||||
    // and errors. Loop through our source sets and find the appropriate flags.
 | 
			
		||||
@@ -54,3 +104,9 @@ idea.project.settings.compiler.javac {
 | 
			
		||||
        }
 | 
			
		||||
        .toMap()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
versionCatalogUpdate {
 | 
			
		||||
    sortByKey.set(false)
 | 
			
		||||
    pin { versions.addAll("fastutil", "guava", "netty", "slf4j") }
 | 
			
		||||
    keep { keepUnusedLibraries.set(true) }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,8 @@
 | 
			
		||||
plugins {
 | 
			
		||||
    `java-gradle-plugin`
 | 
			
		||||
    `kotlin-dsl`
 | 
			
		||||
    alias(libs.plugins.gradleVersions)
 | 
			
		||||
    alias(libs.plugins.versionCatalogUpdate)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Duplicated in settings.gradle.kts
 | 
			
		||||
@@ -27,19 +29,19 @@ repositories {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    maven("https://repo.spongepowered.org/repository/maven-public/") {
 | 
			
		||||
        name = "Sponge"
 | 
			
		||||
        content {
 | 
			
		||||
            includeGroup("org.spongepowered")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    maven("https://maven.fabricmc.net/") {
 | 
			
		||||
        name = "Fabric"
 | 
			
		||||
        content {
 | 
			
		||||
            includeGroup("net.fabricmc")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    maven("https://squiddev.cc/maven") {
 | 
			
		||||
        name = "SquidDev"
 | 
			
		||||
        content {
 | 
			
		||||
            includeGroup("cc.tweaked.vanilla-extract")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
@@ -50,10 +52,10 @@ dependencies {
 | 
			
		||||
    implementation(libs.curseForgeGradle)
 | 
			
		||||
    implementation(libs.fabric.loom)
 | 
			
		||||
    implementation(libs.forgeGradle)
 | 
			
		||||
    implementation(libs.ideaExt)
 | 
			
		||||
    implementation(libs.librarian)
 | 
			
		||||
    implementation(libs.minotaur)
 | 
			
		||||
    implementation(libs.quiltflower)
 | 
			
		||||
    implementation(libs.vanillaGradle)
 | 
			
		||||
    implementation(libs.vanillaExtract)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
gradlePlugin {
 | 
			
		||||
@@ -74,3 +76,9 @@ gradlePlugin {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
versionCatalogUpdate {
 | 
			
		||||
    sortByKey.set(false)
 | 
			
		||||
    keep { keepUnusedLibraries.set(true) }
 | 
			
		||||
    catalogFile.set(file("../gradle/libs.versions.toml"))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,6 @@ import cc.tweaked.gradle.MinecraftConfigurations
 | 
			
		||||
plugins {
 | 
			
		||||
    `java-library`
 | 
			
		||||
    id("fabric-loom")
 | 
			
		||||
    id("io.github.juuxel.loom-quiltflower")
 | 
			
		||||
    id("cc-tweaked.java-convention")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -57,14 +57,15 @@ repositories {
 | 
			
		||||
 | 
			
		||||
        filter {
 | 
			
		||||
            includeGroup("cc.tweaked")
 | 
			
		||||
            includeModule("org.squiddev", "Cobalt")
 | 
			
		||||
            // Things we mirror
 | 
			
		||||
            includeGroup("commoble.morered")
 | 
			
		||||
            includeGroup("dev.architectury")
 | 
			
		||||
            includeGroup("dev.emi")
 | 
			
		||||
            includeGroup("maven.modrinth")
 | 
			
		||||
            includeGroup("me.shedaniel.cloth")
 | 
			
		||||
            includeGroup("me.shedaniel")
 | 
			
		||||
            includeGroup("mezz.jei")
 | 
			
		||||
            includeGroup("org.teavm")
 | 
			
		||||
            includeModule("com.terraformersmc", "modmenu")
 | 
			
		||||
            includeModule("me.lucko", "fabric-permissions-api")
 | 
			
		||||
        }
 | 
			
		||||
@@ -75,6 +76,12 @@ dependencies {
 | 
			
		||||
    val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
 | 
			
		||||
    checkstyle(libs.findLibrary("checkstyle").get())
 | 
			
		||||
 | 
			
		||||
    constraints {
 | 
			
		||||
        checkstyle("org.codehaus.plexus:plexus-container-default:2.1.1") {
 | 
			
		||||
            because("2.1.0 depends on deprecated Google collections module")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    errorprone(libs.findLibrary("errorProne-core").get())
 | 
			
		||||
    errorprone(libs.findLibrary("nullAway").get())
 | 
			
		||||
}
 | 
			
		||||
@@ -98,7 +105,10 @@ sourceSets.all {
 | 
			
		||||
            check("FutureReturnValueIgnored", CheckSeverity.OFF) // Too many false positives with Netty
 | 
			
		||||
 | 
			
		||||
            check("NullAway", CheckSeverity.ERROR)
 | 
			
		||||
            option("NullAway:AnnotatedPackages", listOf("dan200.computercraft", "net.fabricmc.fabric.api").joinToString(","))
 | 
			
		||||
            option(
 | 
			
		||||
                "NullAway:AnnotatedPackages",
 | 
			
		||||
                listOf("dan200.computercraft", "cc.tweaked", "net.fabricmc.fabric.api").joinToString(","),
 | 
			
		||||
            )
 | 
			
		||||
            option("NullAway:ExcludedFieldAnnotations", listOf("org.spongepowered.asm.mixin.Shadow").joinToString(","))
 | 
			
		||||
            option("NullAway:CastToNonNullMethod", "dan200.computercraft.core.util.Nullability.assertNonNull")
 | 
			
		||||
            option("NullAway:CheckOptionalEmptiness")
 | 
			
		||||
@@ -173,6 +183,12 @@ project.plugins.withType(CCTweakedPlugin::class.java) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.register("checkstyle") {
 | 
			
		||||
    description = "Run Checkstyle on all sources"
 | 
			
		||||
    group = LifecycleBasePlugin.VERIFICATION_GROUP
 | 
			
		||||
    dependsOn(tasks.withType(Checkstyle::class.java))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
spotless {
 | 
			
		||||
    encoding = StandardCharsets.UTF_8
 | 
			
		||||
    lineEndings = LineEnding.UNIX
 | 
			
		||||
@@ -190,6 +206,8 @@ spotless {
 | 
			
		||||
 | 
			
		||||
    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",
 | 
			
		||||
    )
 | 
			
		||||
 
 | 
			
		||||
@@ -10,25 +10,31 @@ import cc.tweaked.gradle.MinecraftConfigurations
 | 
			
		||||
 | 
			
		||||
plugins {
 | 
			
		||||
    id("cc-tweaked.java-convention")
 | 
			
		||||
    id("org.spongepowered.gradle.vanilla")
 | 
			
		||||
    id("cc.tweaked.vanilla-extract")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
plugins.apply(CCTweakedPlugin::class.java)
 | 
			
		||||
 | 
			
		||||
val mcVersion: String by extra
 | 
			
		||||
 | 
			
		||||
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
 | 
			
		||||
 | 
			
		||||
minecraft {
 | 
			
		||||
    version(mcVersion)
 | 
			
		||||
 | 
			
		||||
    mappings {
 | 
			
		||||
        parchment(libs.findVersion("parchmentMc").get().toString(), libs.findVersion("parchment").get().toString())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    unpick(libs.findLibrary("yarn").get())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
 | 
			
		||||
 | 
			
		||||
    // Depend on error prone annotations to silence a lot of compile warnings.
 | 
			
		||||
    compileOnlyApi(libs.findLibrary("errorProne.annotations").get())
 | 
			
		||||
    compileOnly(libs.findLibrary("errorProne.annotations").get())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MinecraftConfigurations.setup(project)
 | 
			
		||||
MinecraftConfigurations.setupBasic(project)
 | 
			
		||||
 | 
			
		||||
extensions.configure(CCTweakedExtension::class.java) {
 | 
			
		||||
    linters(minecraft = true, loader = null)
 | 
			
		||||
 
 | 
			
		||||
@@ -10,9 +10,11 @@ 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.attributes.TestSuiteType
 | 
			
		||||
import org.gradle.api.file.FileSystemOperations
 | 
			
		||||
import org.gradle.api.plugins.JavaPluginExtension
 | 
			
		||||
import org.gradle.api.provider.ListProperty
 | 
			
		||||
import org.gradle.api.provider.Provider
 | 
			
		||||
import org.gradle.api.provider.SetProperty
 | 
			
		||||
import org.gradle.api.reporting.ReportingExtension
 | 
			
		||||
@@ -73,11 +75,17 @@ abstract class CCTweakedExtension(
 | 
			
		||||
     */
 | 
			
		||||
    val sourceDirectories: SetProperty<SourceSetReference> = project.objects.setProperty(SourceSetReference::class.java)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Dependencies excluded from published artifacts.
 | 
			
		||||
     */
 | 
			
		||||
    private val excludedDeps: ListProperty<Dependency> = project.objects.listProperty(Dependency::class.java)
 | 
			
		||||
 | 
			
		||||
    /** All source sets referenced by this project. */
 | 
			
		||||
    val sourceSets = sourceDirectories.map { x -> x.map { it.sourceSet } }
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        sourceDirectories.finalizeValueOnRead()
 | 
			
		||||
        excludedDeps.finalizeValueOnRead()
 | 
			
		||||
        project.afterEvaluate { sourceDirectories.disallowChanges() }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -173,7 +181,7 @@ abstract class CCTweakedExtension(
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun <T> jacoco(task: NamedDomainObjectProvider<T>) where T : Task, T : JavaForkOptions {
 | 
			
		||||
        val classDump = project.buildDir.resolve("jacocoClassDump/${task.name}")
 | 
			
		||||
        val classDump = project.layout.buildDirectory.dir("jacocoClassDump/${task.name}")
 | 
			
		||||
        val reportTaskName = "jacoco${task.name.capitalized()}Report"
 | 
			
		||||
 | 
			
		||||
        val jacoco = project.extensions.getByType(JacocoPluginExtension::class.java)
 | 
			
		||||
@@ -185,7 +193,7 @@ abstract class CCTweakedExtension(
 | 
			
		||||
            jacoco.applyTo(this)
 | 
			
		||||
            extensions.configure(JacocoTaskExtension::class.java) {
 | 
			
		||||
                includes = listOf("dan200.computercraft.*")
 | 
			
		||||
                classDumpDir = classDump
 | 
			
		||||
                classDumpDir = classDump.get().asFile
 | 
			
		||||
 | 
			
		||||
                // Older versions of modlauncher don't include a protection domain (and thus no code
 | 
			
		||||
                // source). Jacoco skips such classes by default, so we need to explicitly include them.
 | 
			
		||||
@@ -246,6 +254,20 @@ abstract class CCTweakedExtension(
 | 
			
		||||
        ).resolve().single()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Exclude a dependency from being published in Maven.
 | 
			
		||||
     */
 | 
			
		||||
    fun exclude(dep: Dependency) {
 | 
			
		||||
        excludedDeps.add(dep)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Configure a [MavenDependencySpec].
 | 
			
		||||
     */
 | 
			
		||||
    fun configureExcludes(spec: MavenDependencySpec) {
 | 
			
		||||
        for (dep in excludedDeps.get()) spec.exclude(dep)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        private val COMMIT_COUNTS = Pattern.compile("""^\s*[0-9]+\s+(.*)$""")
 | 
			
		||||
        private val IGNORED_USERS = setOf(
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,10 @@ import org.gradle.api.Project
 | 
			
		||||
import org.gradle.api.plugins.JavaPlugin
 | 
			
		||||
import org.gradle.api.plugins.JavaPluginExtension
 | 
			
		||||
import org.gradle.jvm.toolchain.JavaLanguageVersion
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Configures projects to match a shared configuration.
 | 
			
		||||
@@ -21,6 +25,20 @@ class CCTweakedPlugin : Plugin<Project> {
 | 
			
		||||
            val sourceSets = project.extensions.getByType(JavaPluginExtension::class.java).sourceSets
 | 
			
		||||
            cct.sourceDirectories.add(SourceSetReference.internal(sourceSets.getByName("main")))
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        project.plugins.withType(IdeaExtPlugin::class.java) { extendIdea(project) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Extend the [IdeaExtPlugin] plugin's `runConfiguration` container to also support [JUnitExt].
 | 
			
		||||
     */
 | 
			
		||||
    private fun extendIdea(project: Project) {
 | 
			
		||||
        val ideaModel = project.extensions.findByName("idea") as IdeaModel? ?: return
 | 
			
		||||
        val ideaProject = ideaModel.project ?: return
 | 
			
		||||
 | 
			
		||||
        ideaProject.settings.runConfigurations {
 | 
			
		||||
            registerFactory(JUnitExt::class.java) { name -> project.objects.newInstance(JUnitExt::class.java, name) }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,92 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package cc.tweaked.gradle
 | 
			
		||||
 | 
			
		||||
import org.gradle.api.DefaultTask
 | 
			
		||||
import org.gradle.api.GradleException
 | 
			
		||||
import org.gradle.api.artifacts.Configuration
 | 
			
		||||
import org.gradle.api.artifacts.MinimalExternalModuleDependency
 | 
			
		||||
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
 | 
			
		||||
import org.gradle.api.artifacts.component.ModuleComponentSelector
 | 
			
		||||
import org.gradle.api.artifacts.component.ProjectComponentIdentifier
 | 
			
		||||
import org.gradle.api.artifacts.result.DependencyResult
 | 
			
		||||
import org.gradle.api.artifacts.result.ResolvedDependencyResult
 | 
			
		||||
import org.gradle.api.provider.ListProperty
 | 
			
		||||
import org.gradle.api.provider.MapProperty
 | 
			
		||||
import org.gradle.api.provider.Provider
 | 
			
		||||
import org.gradle.api.tasks.Input
 | 
			
		||||
import org.gradle.api.tasks.TaskAction
 | 
			
		||||
import org.gradle.language.base.plugins.LifecycleBasePlugin
 | 
			
		||||
 | 
			
		||||
abstract class DependencyCheck : DefaultTask() {
 | 
			
		||||
    @get:Input
 | 
			
		||||
    abstract val configuration: ListProperty<Configuration>
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A mapping of module coordinates (`group:module`) to versions, overriding the requested version.
 | 
			
		||||
     */
 | 
			
		||||
    @get:Input
 | 
			
		||||
    abstract val overrides: MapProperty<String, String>
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        description = "Check :core's dependencies are consistent with Minecraft's."
 | 
			
		||||
        group = LifecycleBasePlugin.VERIFICATION_GROUP
 | 
			
		||||
 | 
			
		||||
        configuration.finalizeValueOnRead()
 | 
			
		||||
        overrides.finalizeValueOnRead()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Override a module with a different version.
 | 
			
		||||
     */
 | 
			
		||||
    fun override(module: Provider<MinimalExternalModuleDependency>, version: String) {
 | 
			
		||||
        overrides.putAll(project.provider { mutableMapOf(module.get().module.toString() to version) })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @TaskAction
 | 
			
		||||
    fun run() {
 | 
			
		||||
        var ok = true
 | 
			
		||||
        for (configuration in configuration.get()) {
 | 
			
		||||
            configuration.incoming.resolutionResult.allDependencies {
 | 
			
		||||
                if (!check(this@allDependencies)) ok = false
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!ok) {
 | 
			
		||||
            throw GradleException("Mismatched versions in Minecraft dependencies. gradle/libs.versions.toml may need updating.")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun check(dependency: DependencyResult): Boolean {
 | 
			
		||||
        if (dependency !is ResolvedDependencyResult) {
 | 
			
		||||
            logger.warn("Found unexpected dependency result {}", dependency)
 | 
			
		||||
            return false
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Skip dependencies on non-modules.
 | 
			
		||||
        val requested = dependency.requested
 | 
			
		||||
        if (requested !is ModuleComponentSelector) return true
 | 
			
		||||
 | 
			
		||||
        // If this dependency is specified within some project (so is non-transitive), or is pulled in via Minecraft,
 | 
			
		||||
        // then check for consistency.
 | 
			
		||||
        // It would be nice to be smarter about transitive dependencies, but avoiding false positives is hard.
 | 
			
		||||
        val from = dependency.from.id
 | 
			
		||||
        if (
 | 
			
		||||
            from is ProjectComponentIdentifier ||
 | 
			
		||||
            from is ModuleComponentIdentifier && (from.group == "net.minecraft" || from.group == "io.netty")
 | 
			
		||||
        ) {
 | 
			
		||||
            // If the version is different between the requested and selected version, report an error.
 | 
			
		||||
            val selected = dependency.selected.moduleVersion!!.version
 | 
			
		||||
            val requestedVersion = overrides.get()["${requested.group}:${requested.module}"] ?: requested.version
 | 
			
		||||
            if (requestedVersion != selected) {
 | 
			
		||||
                logger.error("Requested dependency {} (via {}) but got version {}", requested, from, selected)
 | 
			
		||||
                return false
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return true
 | 
			
		||||
        }
 | 
			
		||||
        return true
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -4,6 +4,7 @@
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
@@ -11,5 +12,5 @@ import java.io.File
 | 
			
		||||
 | 
			
		||||
abstract class ExecToDir : AbstractExecTask<ExecToDir>(ExecToDir::class.java) {
 | 
			
		||||
    @get:OutputDirectory
 | 
			
		||||
    abstract val output: Property<File>
 | 
			
		||||
    abstract val output: DirectoryProperty
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@
 | 
			
		||||
package cc.tweaked.gradle
 | 
			
		||||
 | 
			
		||||
import org.gradle.api.artifacts.dsl.DependencyHandler
 | 
			
		||||
import org.gradle.api.file.FileSystemLocation
 | 
			
		||||
import org.gradle.api.provider.Property
 | 
			
		||||
import org.gradle.api.provider.Provider
 | 
			
		||||
import org.gradle.api.tasks.JavaExec
 | 
			
		||||
@@ -124,3 +125,33 @@ class CloseScope : AutoCloseable {
 | 
			
		||||
 | 
			
		||||
/** Proxy method to avoid overload ambiguity. */
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get the version immediately after the provided version.
 | 
			
		||||
 *
 | 
			
		||||
 * For example, given "1.2.3", this will return "1.2.4".
 | 
			
		||||
 */
 | 
			
		||||
fun getNextVersion(version: String): String {
 | 
			
		||||
    // Split a version like x.y.z-SNAPSHOT into x.y.z and -SNAPSHOT
 | 
			
		||||
    val dashIndex = version.indexOf('-')
 | 
			
		||||
    val mainVersion = if (dashIndex < 0) version else version.substring(0, dashIndex)
 | 
			
		||||
 | 
			
		||||
    // Find the last component in x.y.z and increment it.
 | 
			
		||||
    val lastIndex = mainVersion.lastIndexOf('.')
 | 
			
		||||
    if (lastIndex < 0) throw IllegalArgumentException("Cannot parse version format \"$version\"")
 | 
			
		||||
    val lastVersion = try {
 | 
			
		||||
        version.substring(lastIndex + 1).toInt()
 | 
			
		||||
    } catch (e: NumberFormatException) {
 | 
			
		||||
        throw IllegalArgumentException("Cannot parse version format \"$version\"", e)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Then append all components together.
 | 
			
		||||
    val out = StringBuilder()
 | 
			
		||||
    out.append(version, 0, lastIndex + 1)
 | 
			
		||||
    out.append(lastVersion + 1)
 | 
			
		||||
    if (dashIndex >= 0) out.append(version, dashIndex, version.length)
 | 
			
		||||
    return out.toString()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										23
									
								
								buildSrc/src/main/kotlin/cc/tweaked/gradle/IdeaExt.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								buildSrc/src/main/kotlin/cc/tweaked/gradle/IdeaExt.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package cc.tweaked.gradle
 | 
			
		||||
 | 
			
		||||
import org.jetbrains.gradle.ext.JUnit
 | 
			
		||||
import javax.inject.Inject
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A version of [JUnit] with a functional [className].
 | 
			
		||||
 *
 | 
			
		||||
 * See [#92](https://github.com/JetBrains/gradle-idea-ext-plugin/issues/92).
 | 
			
		||||
 */
 | 
			
		||||
open class JUnitExt @Inject constructor(nameParam: String) : JUnit(nameParam) {
 | 
			
		||||
    override fun toMap(): MutableMap<String, *> {
 | 
			
		||||
        val map = HashMap(super.toMap())
 | 
			
		||||
        // Should be "class" instead of "className".
 | 
			
		||||
        // See https://github.com/JetBrains/intellij-community/blob/9ba394021dc73a3926f13d6d6cdf434f9ee7046d/plugins/junit/src/com/intellij/execution/junit/JUnitRunConfigurationImporter.kt#L39
 | 
			
		||||
        map["class"] = className
 | 
			
		||||
        return map
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -6,6 +6,8 @@ package cc.tweaked.gradle
 | 
			
		||||
 | 
			
		||||
import org.gradle.api.artifacts.Dependency
 | 
			
		||||
import org.gradle.api.artifacts.MinimalExternalModuleDependency
 | 
			
		||||
import org.gradle.api.artifacts.ProjectDependency
 | 
			
		||||
import org.gradle.api.plugins.BasePluginExtension
 | 
			
		||||
import org.gradle.api.publish.maven.MavenPublication
 | 
			
		||||
import org.gradle.api.specs.Spec
 | 
			
		||||
 | 
			
		||||
@@ -26,8 +28,13 @@ class MavenDependencySpec {
 | 
			
		||||
 | 
			
		||||
    fun exclude(dep: Dependency) {
 | 
			
		||||
        exclude {
 | 
			
		||||
            // We have to cheat a little for project dependencies, as the project name doesn't match the artifact group.
 | 
			
		||||
            val name = when (dep) {
 | 
			
		||||
                is ProjectDependency -> dep.dependencyProject.extensions.getByType(BasePluginExtension::class.java).archivesName.get()
 | 
			
		||||
                else -> dep.name
 | 
			
		||||
            }
 | 
			
		||||
            (dep.group.isNullOrEmpty() || dep.group == it.groupId) &&
 | 
			
		||||
                (dep.name.isNullOrEmpty() || dep.name == it.artifactId) &&
 | 
			
		||||
                (name.isNullOrEmpty() || name == it.artifactId) &&
 | 
			
		||||
                (dep.version.isNullOrEmpty() || dep.version == it.version)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -4,23 +4,17 @@
 | 
			
		||||
 | 
			
		||||
package cc.tweaked.gradle
 | 
			
		||||
 | 
			
		||||
import cc.tweaked.vanillaextract.configurations.Capabilities
 | 
			
		||||
import cc.tweaked.vanillaextract.configurations.MinecraftSetup
 | 
			
		||||
import org.gradle.api.Project
 | 
			
		||||
import org.gradle.api.artifacts.Configuration
 | 
			
		||||
import org.gradle.api.artifacts.ModuleDependency
 | 
			
		||||
import org.gradle.api.artifacts.dsl.DependencyHandler
 | 
			
		||||
import org.gradle.api.attributes.Bundling
 | 
			
		||||
import org.gradle.api.attributes.Category
 | 
			
		||||
import org.gradle.api.attributes.LibraryElements
 | 
			
		||||
import org.gradle.api.attributes.Usage
 | 
			
		||||
import org.gradle.api.attributes.java.TargetJvmVersion
 | 
			
		||||
import org.gradle.api.capabilities.Capability
 | 
			
		||||
import org.gradle.api.plugins.BasePlugin
 | 
			
		||||
import org.gradle.api.plugins.JavaPluginExtension
 | 
			
		||||
import org.gradle.api.tasks.SourceSet
 | 
			
		||||
import org.gradle.api.tasks.bundling.Jar
 | 
			
		||||
import org.gradle.api.tasks.javadoc.Javadoc
 | 
			
		||||
import org.gradle.kotlin.dsl.get
 | 
			
		||||
import org.gradle.kotlin.dsl.named
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This sets up a separate client-only source set, and extends that and the main/common source set with additional
 | 
			
		||||
@@ -59,31 +53,13 @@ class MinecraftConfigurations private constructor(private val project: Project)
 | 
			
		||||
        }
 | 
			
		||||
        configurations.named(client.implementationConfigurationName) { extendsFrom(clientApi) }
 | 
			
		||||
 | 
			
		||||
        /*
 | 
			
		||||
          Now add outgoing variants for the main and common source sets that we can consume downstream. This is possibly
 | 
			
		||||
          the worst way to do things, but unfortunately the alternatives don't actually work very well:
 | 
			
		||||
 | 
			
		||||
           - Just using source set outputs: This means dependencies don't propagate, which means when :fabric depends
 | 
			
		||||
             on :fabric-api, we don't inherit the fake :common-api in IDEA.
 | 
			
		||||
 | 
			
		||||
           - Having separate common/main jars: Nice in principle, but unfortunately Forge needs a separate deobf jar
 | 
			
		||||
             task (as the original jar is obfuscated), and IDEA is not able to map its output back to a source set.
 | 
			
		||||
 | 
			
		||||
          This works for now, but is incredibly brittle. It's part of the reason we can't use testFixtures inside our
 | 
			
		||||
          MC projects, as that adds a project(self) -> test dependency, which would pull in the jar instead.
 | 
			
		||||
 | 
			
		||||
          Note we register a fake client jar here. It's not actually needed, but is there to make sure IDEA has
 | 
			
		||||
          a way to tell that client classes are needed at runtime.
 | 
			
		||||
 | 
			
		||||
          I'm so sorry, deeply aware how cursed this is.
 | 
			
		||||
        */
 | 
			
		||||
        setupOutgoing(main, "CommonOnly")
 | 
			
		||||
        project.tasks.register(client.jarTaskName, Jar::class.java) {
 | 
			
		||||
            description = "An empty jar standing in for the client classes."
 | 
			
		||||
            group = BasePlugin.BUILD_GROUP
 | 
			
		||||
            archiveClassifier.set("client")
 | 
			
		||||
        }
 | 
			
		||||
        setupOutgoing(client)
 | 
			
		||||
 | 
			
		||||
        MinecraftSetup(project).setupOutgoingConfigurations()
 | 
			
		||||
 | 
			
		||||
        // Reset the client classpath (Loom configures it slightly differently to this) and add a main -> client
 | 
			
		||||
        // dependency. Here we /can/ use source set outputs as we add transitive deps by patching the classpath. Nasty,
 | 
			
		||||
@@ -106,88 +82,39 @@ class MinecraftConfigurations private constructor(private val project: Project)
 | 
			
		||||
        project.tasks.named("jar", Jar::class.java) { from(client.output) }
 | 
			
		||||
        project.tasks.named("sourcesJar", Jar::class.java) { from(client.allSource) }
 | 
			
		||||
 | 
			
		||||
        setupBasic()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun setupBasic() {
 | 
			
		||||
        val client = sourceSets["client"]
 | 
			
		||||
 | 
			
		||||
        project.extensions.configure(CCTweakedExtension::class.java) {
 | 
			
		||||
            sourceDirectories.add(SourceSetReference.internal(client))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun setupOutgoing(sourceSet: SourceSet, suffix: String = "") {
 | 
			
		||||
        setupOutgoing("${sourceSet.apiElementsConfigurationName}$suffix", sourceSet, objects.named(Usage.JAVA_API)) {
 | 
			
		||||
            description = "API elements for ${sourceSet.name}"
 | 
			
		||||
            extendsFrom(configurations[sourceSet.apiConfigurationName])
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        setupOutgoing("${sourceSet.runtimeElementsConfigurationName}$suffix", sourceSet, objects.named(Usage.JAVA_RUNTIME)) {
 | 
			
		||||
            description = "Runtime elements for ${sourceSet.name}"
 | 
			
		||||
            extendsFrom(configurations[sourceSet.implementationConfigurationName], configurations[sourceSet.runtimeOnlyConfigurationName])
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set up an outgoing configuration for a specific source set. We set an additional "main" or "client" capability
 | 
			
		||||
     * (depending on the source set name) which allows downstream projects to consume them separately (see
 | 
			
		||||
     * [DependencyHandler.commonClasses] and [DependencyHandler.clientClasses]).
 | 
			
		||||
     */
 | 
			
		||||
    private fun setupOutgoing(name: String, sourceSet: SourceSet, usage: Usage, configure: Configuration.() -> Unit) {
 | 
			
		||||
        configurations.register(name) {
 | 
			
		||||
            isVisible = false
 | 
			
		||||
            isCanBeConsumed = true
 | 
			
		||||
            isCanBeResolved = false
 | 
			
		||||
 | 
			
		||||
            configure(this)
 | 
			
		||||
 | 
			
		||||
            attributes {
 | 
			
		||||
                attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.LIBRARY))
 | 
			
		||||
                attribute(Usage.USAGE_ATTRIBUTE, usage)
 | 
			
		||||
                attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EXTERNAL))
 | 
			
		||||
                attributeProvider(
 | 
			
		||||
                    TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE,
 | 
			
		||||
                    java.toolchain.languageVersion.map { it.asInt() },
 | 
			
		||||
                )
 | 
			
		||||
                attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.JAR))
 | 
			
		||||
        // Register a task to check there are no conflicts with the core project.
 | 
			
		||||
        val checkDependencyConsistency =
 | 
			
		||||
            project.tasks.register("checkDependencyConsistency", DependencyCheck::class.java) {
 | 
			
		||||
                // We need to check both the main and client classpath *configurations*, as the actual configuration
 | 
			
		||||
                configuration.add(configurations.named(main.runtimeClasspathConfigurationName))
 | 
			
		||||
                configuration.add(configurations.named(client.runtimeClasspathConfigurationName))
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            outgoing {
 | 
			
		||||
                capability(BasicOutgoingCapability(project, sourceSet.name))
 | 
			
		||||
 | 
			
		||||
                // We have two outgoing variants here: the original jar and the classes.
 | 
			
		||||
                artifact(project.tasks.named(sourceSet.jarTaskName))
 | 
			
		||||
 | 
			
		||||
                variants.create("classes") {
 | 
			
		||||
                    attributes.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.CLASSES))
 | 
			
		||||
                    sourceSet.output.classesDirs.forEach { artifact(it) { builtBy(sourceSet.output) } }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        project.tasks.named("check") { dependsOn(checkDependencyConsistency) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        fun setupBasic(project: Project) {
 | 
			
		||||
            MinecraftConfigurations(project).setupBasic()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fun setup(project: Project) {
 | 
			
		||||
            MinecraftConfigurations(project).setup()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private class BasicIncomingCapability(private val module: ModuleDependency, private val name: String) : Capability {
 | 
			
		||||
    override fun getGroup(): String = module.group!!
 | 
			
		||||
    override fun getName(): String = "${module.name}-$name"
 | 
			
		||||
    override fun getVersion(): String? = null
 | 
			
		||||
}
 | 
			
		||||
fun DependencyHandler.clientClasses(notation: Any): ModuleDependency =
 | 
			
		||||
    Capabilities.clientClasses(create(notation) as ModuleDependency)
 | 
			
		||||
 | 
			
		||||
private class BasicOutgoingCapability(private val project: Project, private val name: String) : Capability {
 | 
			
		||||
    override fun getGroup(): String = project.group.toString()
 | 
			
		||||
    override fun getName(): String = "${project.name}-$name"
 | 
			
		||||
    override fun getVersion(): String = project.version.toString()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun DependencyHandler.clientClasses(notation: Any): ModuleDependency {
 | 
			
		||||
    val dep = create(notation) as ModuleDependency
 | 
			
		||||
    dep.capabilities { requireCapability(BasicIncomingCapability(dep, "client")) }
 | 
			
		||||
    return dep
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun DependencyHandler.commonClasses(notation: Any): ModuleDependency {
 | 
			
		||||
    val dep = create(notation) as ModuleDependency
 | 
			
		||||
    dep.capabilities { requireCapability(BasicIncomingCapability(dep, "main")) }
 | 
			
		||||
    return dep
 | 
			
		||||
}
 | 
			
		||||
fun DependencyHandler.commonClasses(notation: Any): ModuleDependency =
 | 
			
		||||
    Capabilities.commonClasses(create(notation) as ModuleDependency)
 | 
			
		||||
 
 | 
			
		||||
@@ -58,7 +58,7 @@ abstract class ClientJavaExec : JavaExec() {
 | 
			
		||||
        if (!clientDebug) systemProperty("cctest.client", "")
 | 
			
		||||
        if (renderdoc) environment("LD_PRELOAD", "/usr/lib/librenderdoc.so")
 | 
			
		||||
        systemProperty("cctest.gametest-report", testResults.get().asFile.absoluteFile)
 | 
			
		||||
        workingDir(project.buildDir.resolve("gametest").resolve(name))
 | 
			
		||||
        workingDir(project.layout.buildDirectory.dir("gametest/$name"))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
 
 | 
			
		||||
@@ -112,7 +112,9 @@ SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
        <module name="LambdaParameterName" />
 | 
			
		||||
        <module name="LocalFinalVariableName" />
 | 
			
		||||
        <module name="LocalVariableName" />
 | 
			
		||||
        <module name="MemberName" />
 | 
			
		||||
        <module name="MemberName">
 | 
			
		||||
            <property name="format" value="^\$?[a-z][a-zA-Z0-9]*$" />
 | 
			
		||||
        </module>
 | 
			
		||||
        <module name="MethodName">
 | 
			
		||||
            <property name="format" value="^(computercraft\$)?[a-z][a-zA-Z0-9]*$" />
 | 
			
		||||
        </module>
 | 
			
		||||
@@ -122,7 +124,7 @@ SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
        </module>
 | 
			
		||||
        <module name="ParameterName" />
 | 
			
		||||
        <module name="StaticVariableName">
 | 
			
		||||
            <property name="format" value="^[a-z][a-zA-Z0-9]*|CAPABILITY(_[A-Z_]+)?$" />
 | 
			
		||||
            <property name="format" value="^[a-z][a-zA-Z0-9]*$" />
 | 
			
		||||
        </module>
 | 
			
		||||
        <module name="TypeName" />
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -16,4 +16,10 @@ SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
    <!-- The commands API is documented in Lua. -->
 | 
			
		||||
    <suppress checks="SummaryJavadocCheck" files=".*[\\/]CommandAPI.java" />
 | 
			
		||||
 | 
			
		||||
    <!-- Allow putting files in other packages if they look like our TeaVM stubs. -->
 | 
			
		||||
    <suppress checks="PackageName" files=".*[\\/]T[A-Za-z]+.java" />
 | 
			
		||||
 | 
			
		||||
    <!-- Allow underscores in our test classes. -->
 | 
			
		||||
    <suppress checks="MethodName" files=".*Contract.java" />
 | 
			
		||||
</suppressions>
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ see: key To listen to any key press.
 | 
			
		||||
<!--
 | 
			
		||||
SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
 | 
			
		||||
SPDX-License-Identifier: LicenseRef-CCPL
 | 
			
		||||
SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
The [`char`] event is fired when a character is typed on the keyboard.
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@ SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
The [`file_transfer`] event is queued when a user drags-and-drops a file on an open computer.
 | 
			
		||||
 | 
			
		||||
This event contains a single argument of type [`TransferredFiles`], which can be used to [get the files to be
 | 
			
		||||
transferred][`TransferredFiles.getFiles`]. Each file returned is a [binary file handle][`fs.BinaryReadHandle`] with an
 | 
			
		||||
transferred][`TransferredFiles.getFiles`]. Each file returned is a [binary file handle][`fs.ReadHandle`] with an
 | 
			
		||||
additional [getName][`TransferredFile.getName`] method.
 | 
			
		||||
 | 
			
		||||
## Return values
 | 
			
		||||
@@ -29,7 +29,7 @@ for _, file in ipairs(files.getFiles()) do
 | 
			
		||||
  local size = file.seek("end")
 | 
			
		||||
  file.seek("set", 0)
 | 
			
		||||
 | 
			
		||||
  print(file.getName() .. " " .. file.getSize())
 | 
			
		||||
  print(file.getName() .. " " .. size)
 | 
			
		||||
end
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ module: [kind=event] key
 | 
			
		||||
<!--
 | 
			
		||||
SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
 | 
			
		||||
SPDX-License-Identifier: LicenseRef-CCPL
 | 
			
		||||
SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
This event is fired when any key is pressed while the terminal is focused.
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ see: keys For a lookup table of the given keys.
 | 
			
		||||
<!--
 | 
			
		||||
SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
 | 
			
		||||
SPDX-License-Identifier: LicenseRef-CCPL
 | 
			
		||||
SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
Fired whenever a key is released (or the terminal is closed while a key was being pressed).
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ module: [kind=event] mouse_click
 | 
			
		||||
<!--
 | 
			
		||||
SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
 | 
			
		||||
SPDX-License-Identifier: LicenseRef-CCPL
 | 
			
		||||
SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
This event is fired when the terminal is clicked with a mouse. This event is only fired on advanced computers (including
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ see: mouse_click For when a mouse button is initially pressed.
 | 
			
		||||
<!--
 | 
			
		||||
SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
 | 
			
		||||
SPDX-License-Identifier: LicenseRef-CCPL
 | 
			
		||||
SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
This event is fired every time the mouse is moved while a mouse button is being held.
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ module: [kind=event] mouse_scroll
 | 
			
		||||
<!--
 | 
			
		||||
SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
 | 
			
		||||
SPDX-License-Identifier: LicenseRef-CCPL
 | 
			
		||||
SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
This event is fired when a mouse wheel is scrolled in the terminal.
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ module: [kind=event] mouse_up
 | 
			
		||||
<!--
 | 
			
		||||
SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
 | 
			
		||||
SPDX-License-Identifier: LicenseRef-CCPL
 | 
			
		||||
SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
This event is fired when a mouse button is released or a held mouse leaves the computer's terminal.
 | 
			
		||||
 
 | 
			
		||||
@@ -134,7 +134,7 @@ accepts blocks of DFPWM data and converts it to a list of 8-bit amplitudes, whic
 | 
			
		||||
As mentioned above, [`speaker.playAudio`] accepts at most 128×1024 samples in one go. DFPMW uses a single bit for each
 | 
			
		||||
sample, which means we want to process our audio in chunks of 16×1024 bytes (16KiB). In order to do this, we use
 | 
			
		||||
[`io.lines`], which provides a nice way to loop over chunks of a file. You can of course just use [`fs.open`] and
 | 
			
		||||
[`fs.BinaryReadHandle.read`] if you prefer.
 | 
			
		||||
[`fs.ReadHandle.read`] if you prefer.
 | 
			
		||||
 | 
			
		||||
## Processing audio
 | 
			
		||||
As mentioned near the beginning of this guide, PCM audio is pretty easy to work with as it's just a list of amplitudes.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										81
									
								
								doc/reference/breaking_changes.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								doc/reference/breaking_changes.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,81 @@
 | 
			
		||||
---
 | 
			
		||||
module: [kind=reference] breaking_changes
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
<!--
 | 
			
		||||
SPDX-FileCopyrightText: 2019 The CC: Tweaked Developers
 | 
			
		||||
 | 
			
		||||
SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
# Incompatibilities between versions
 | 
			
		||||
 | 
			
		||||
CC: Tweaked tries to remain as compatible between versions as possible, meaning most programs written for older version
 | 
			
		||||
of the mod should run fine on later versions.
 | 
			
		||||
 | 
			
		||||
> [External peripherals][!WARNING]
 | 
			
		||||
>
 | 
			
		||||
> While CC: Tweaked is relatively stable across versions, this may not be true for other mods which add their own
 | 
			
		||||
> peripherals. Older programs which interact with external blocks may not work on newer versions of the game.
 | 
			
		||||
 | 
			
		||||
However, some changes to the underlying game, or CC: Tweaked's own internals may break some programs. This page serves
 | 
			
		||||
as documentation for breaking changes and "gotchas" one should look out for between versions.
 | 
			
		||||
 | 
			
		||||
## CC: Tweaked 1.109.0 to 1.109.3 {#cct-1.109}
 | 
			
		||||
 | 
			
		||||
 - Update to Lua 5.2:
 | 
			
		||||
   - Support for Lua 5.0's pseudo-argument `arg` has been removed. You should always use `...` for varargs.
 | 
			
		||||
   - Environments are no longer baked into the runtime, and instead use the `_ENV` local or upvalue. `getfenv`/`setfenv`
 | 
			
		||||
     now only work on Lua functions with an `_ENV` upvalue. `getfenv` will return the global environment when called
 | 
			
		||||
     with other functions, and `setfenv` will have no effect.
 | 
			
		||||
   - `load`/`loadstring` defaults to using the global environment (`_G`) rather than the current coroutine's
 | 
			
		||||
     environment.
 | 
			
		||||
   - Support for dumping functions (`string.dump`) and loading binary chunks has been removed.
 | 
			
		||||
   - `math.random` now uses Lua 5.4's random number generator.
 | 
			
		||||
 | 
			
		||||
 - File handles, HTTP requests and websockets now always use the original bytes rather than encoding/decoding to UTF-8.
 | 
			
		||||
 | 
			
		||||
## Minecraft 1.13 {#mc-1.13}
 | 
			
		||||
 - The "key code" for [`key`] and [`key_up`] events has changed, due to Minecraft updating to LWJGL 3. Make sure you're
 | 
			
		||||
   using the constants provided by the [`keys`] API, rather than hard-coding numerical values.
 | 
			
		||||
 | 
			
		||||
   Related to this change, the numpad enter key now has a different key code to the enter key. You may need to adjust
 | 
			
		||||
   your programs to handle both. (Note, the `keys.numpadEnter` constant was defined in pre-1.13 versions of CC, but the
 | 
			
		||||
   `keys.enter` constant was queued when the key was pressed)
 | 
			
		||||
 | 
			
		||||
 - Minecraft 1.13 removed the concept of item damage and block metadata (see ["The Flattening"][flattening]). As a
 | 
			
		||||
   result `turtle.inspect` no longer provides block metadata, and `turtle.getItemDetail` no longer provides damage.
 | 
			
		||||
 | 
			
		||||
   - Block states (`turtle.inspect().state`) should provide all the same information as block metadata, but in a much
 | 
			
		||||
     more understandable format.
 | 
			
		||||
 | 
			
		||||
   - Item and block names now represent a unique item type. For instance, wool is split into 16 separate items
 | 
			
		||||
     (`minecraft:white_wool`, etc...) rather than a single `minecraft:wool` with each meta/damage value specifying the
 | 
			
		||||
     colour.
 | 
			
		||||
 | 
			
		||||
 - Custom ROMs are now provided using data packs rather than resource packs. This should mostly be a matter of renaming
 | 
			
		||||
   the "assets" folder to "data", and placing it in "datapacks", but there are a couple of other gotchas to look out
 | 
			
		||||
   for:
 | 
			
		||||
 | 
			
		||||
   - Data packs [impose some restrictions on file names][legal_data_pack]. As a result, your programs and directories
 | 
			
		||||
     must all be lower case.
 | 
			
		||||
   - Due to how data packs are read by CC: Tweaked, you may need to use the `/reload` command to see changes to your
 | 
			
		||||
     pack show up on the computer.
 | 
			
		||||
 | 
			
		||||
   See [the example datapack][datapack-example] for how to get started.
 | 
			
		||||
 | 
			
		||||
 - Turtles can now be waterlogged and move "through" water sources rather than breaking them.
 | 
			
		||||
 | 
			
		||||
## CC: Tweaked 1.88.0 {#cc-1.88}
 | 
			
		||||
 - Unlabelled computers and turtles now keep their ID when broken, meaning that unlabelled computers/items do not stack.
 | 
			
		||||
 | 
			
		||||
## ComputerCraft 1.80pr1 {#cc-1.80}
 | 
			
		||||
 - Programs run via `shell.run` are now started in their own isolated environment. This means globals set by programs
 | 
			
		||||
   will not be accessible outside of this program.
 | 
			
		||||
 | 
			
		||||
 - Programs containing `/` are looked up in the current directory and are no longer looked up on the path. For instance,
 | 
			
		||||
   you can no longer type `turtle/excavate` to run `/rom/programs/turtle/excavate.lua`.
 | 
			
		||||
 | 
			
		||||
[flattening]: https://minecraft.wiki/w/Java_Edition_1.13/Flattening
 | 
			
		||||
[legal_data_pack]: https://minecraft.gamepedia.com/Tutorials/Creating_a_data_pack#Legal_characters
 | 
			
		||||
[datapack-example]: https://github.com/cc-tweaked/datapack-example "An example datapack for CC: Tweaked"
 | 
			
		||||
@@ -9,17 +9,19 @@ SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
# Lua 5.2/5.3 features in CC: Tweaked
 | 
			
		||||
CC: Tweaked is based off of the Cobalt Lua runtime, which uses Lua 5.1. However, Cobalt and CC:T implement additional features from Lua 5.2 and 5.3 (as well as some deprecated 5.0 features) that are not available in base 5.1. This page lists all of the compatibility for these newer versions.
 | 
			
		||||
CC: Tweaked is based off of the Cobalt Lua runtime, which uses Lua 5.2. However, Cobalt and CC:T implement additional
 | 
			
		||||
features from Lua 5.2 and 5.3 (as well as some deprecated 5.0 and 5.1 features). This page lists all of the
 | 
			
		||||
compatibility for these newer versions.
 | 
			
		||||
 | 
			
		||||
## Lua 5.2
 | 
			
		||||
| Feature                                                       | Supported? | Notes                                                             |
 | 
			
		||||
|---------------------------------------------------------------|------------|-------------------------------------------------------------------|
 | 
			
		||||
| `goto`/labels                                                 | ❌         |                                                                   |
 | 
			
		||||
| `_ENV`                                                        | 🔶         | The `_ENV` global points to `getfenv()`, but it cannot be set.    |
 | 
			
		||||
| `goto`/labels                                                 | ✔          |                                                                   |
 | 
			
		||||
| `_ENV`                                                        | ✔          |                                                                   |
 | 
			
		||||
| `\z` escape                                                   | ✔          |                                                                   |
 | 
			
		||||
| `\xNN` escape                                                 | ✔          |                                                                   |
 | 
			
		||||
| Hex literal fractional/exponent parts                         | ✔          |                                                                   |
 | 
			
		||||
| Empty statements                                              | ❌         |                                                                   |
 | 
			
		||||
| Empty statements                                              | ✔          |                                                                   |
 | 
			
		||||
| `__len` metamethod                                            | ✔          |                                                                   |
 | 
			
		||||
| `__ipairs` metamethod                                         | ❌         | Deprecated in Lua 5.3. `ipairs` uses `__len`/`__index` instead.   |
 | 
			
		||||
| `__pairs` metamethod                                          | ✔          |                                                                   |
 | 
			
		||||
@@ -27,12 +29,12 @@ CC: Tweaked is based off of the Cobalt Lua runtime, which uses Lua 5.1. However,
 | 
			
		||||
| `collectgarbage` isrunning, generational, incremental options | ❌         | `collectgarbage` does not exist in CC:T.                          |
 | 
			
		||||
| New `load` syntax                                             | ✔          |                                                                   |
 | 
			
		||||
| `loadfile` mode parameter                                     | ✔          | Supports both 5.1 and 5.2+ syntax.                                |
 | 
			
		||||
| Removed `loadstring`                                          | 🔶         | Only if `disable_lua51_features` is enabled in the configuration. |
 | 
			
		||||
| Removed `getfenv`, `setfenv`                                  | 🔶         | Only if `disable_lua51_features` is enabled in the configuration. |
 | 
			
		||||
| Removed `loadstring`                                          | ❌         |                                                                   |
 | 
			
		||||
| Removed `getfenv`, `setfenv`                                  | 🔶         | Only supports closures with an `_ENV` upvalue.                    |
 | 
			
		||||
| `rawlen` function                                             | ✔          |                                                                   |
 | 
			
		||||
| Negative index to `select`                                    | ✔          |                                                                   |
 | 
			
		||||
| Removed `unpack`                                              | 🔶         | Only if `disable_lua51_features` is enabled in the configuration. |
 | 
			
		||||
| Arguments to `xpcall`                                         | ✔         |                                                                   |
 | 
			
		||||
| Removed `unpack`                                              | ❌         |                                                                   |
 | 
			
		||||
| Arguments to `xpcall`                                         | ✔          |                                                                   |
 | 
			
		||||
| Second return value from `coroutine.running`                  | ✔          |                                                                   |
 | 
			
		||||
| Removed `module`                                              | ✔          |                                                                   |
 | 
			
		||||
| `package.loaders` -> `package.searchers`                      | ❌         |                                                                   |
 | 
			
		||||
@@ -40,14 +42,14 @@ CC: Tweaked is based off of the Cobalt Lua runtime, which uses Lua 5.1. However,
 | 
			
		||||
| `package.config`                                              | ✔          |                                                                   |
 | 
			
		||||
| `package.searchpath`                                          | ✔          |                                                                   |
 | 
			
		||||
| Removed `package.seeall`                                      | ✔          |                                                                   |
 | 
			
		||||
| `string.dump` on functions with upvalues (blanks them out)    | ✔          |                                                                   |
 | 
			
		||||
| `string.rep` separator                                        | ✔         |                                                                   |
 | 
			
		||||
| `string.dump` on functions with upvalues (blanks them out)    | ❌         | `string.dump` is not supported                                    |
 | 
			
		||||
| `string.rep` separator                                        | ✔          |                                                                   |
 | 
			
		||||
| `%g` match group                                              | ❌         |                                                                   |
 | 
			
		||||
| Removal of `%z` match group                                   | ❌         |                                                                   |
 | 
			
		||||
| Removed `table.maxn`                                          | 🔶         | Only if `disable_lua51_features` is enabled in the configuration. |
 | 
			
		||||
| Removed `table.maxn`                                          | ❌         |                                                                   |
 | 
			
		||||
| `table.pack`/`table.unpack`                                   | ✔          |                                                                   |
 | 
			
		||||
| `math.log` base argument                                      | ✔          |                                                                   |
 | 
			
		||||
| Removed `math.log10`                                          | 🔶         | Only if `disable_lua51_features` is enabled in the configuration. |
 | 
			
		||||
| Removed `math.log10`                                          | ❌         |                                                                   |
 | 
			
		||||
| `*L` mode to `file:read`                                      | ✔          |                                                                   |
 | 
			
		||||
| `os.execute` exit type + return value                         | ❌         | `os.execute` does not exist in CC:T.                              |
 | 
			
		||||
| `os.exit` close argument                                      | ❌         | `os.exit` does not exist in CC:T.                                 |
 | 
			
		||||
@@ -61,7 +63,7 @@ CC: Tweaked is based off of the Cobalt Lua runtime, which uses Lua 5.1. However,
 | 
			
		||||
| Tail call hooks                                               | ❌         |                                                                   |
 | 
			
		||||
| `=` prefix for chunks                                         | ✔          |                                                                   |
 | 
			
		||||
| Yield across C boundary                                       | ✔          |                                                                   |
 | 
			
		||||
| Removal of ambiguity error                                    | ❌         |                                                                   |
 | 
			
		||||
| Removal of ambiguity error                                    | ✔          |                                                                   |
 | 
			
		||||
| Identifiers may no longer use locale-dependent letters        | ✔          |                                                                   |
 | 
			
		||||
| Ephemeron tables                                              | ❌         |                                                                   |
 | 
			
		||||
| Identical functions may be reused                             | ❌         | Removed in Lua 5.4                                                |
 | 
			
		||||
 
 | 
			
		||||
@@ -95,10 +95,10 @@ function pullEventRaw(filter) end
 | 
			
		||||
-- nearest multiple of 0.05.
 | 
			
		||||
function sleep(time) end
 | 
			
		||||
 | 
			
		||||
--- Get the current CraftOS version (for example, `CraftOS 1.8`).
 | 
			
		||||
--- Get the current CraftOS version (for example, `CraftOS 1.9`).
 | 
			
		||||
--
 | 
			
		||||
-- This is defined by `bios.lua`. For the current version of CC:Tweaked, this
 | 
			
		||||
-- should return `CraftOS 1.8`.
 | 
			
		||||
-- should return `CraftOS 1.9`.
 | 
			
		||||
--
 | 
			
		||||
-- @treturn string The current CraftOS version.
 | 
			
		||||
-- @usage os.version()
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ kotlin.jvm.target.validation.mode=error
 | 
			
		||||
 | 
			
		||||
# Mod properties
 | 
			
		||||
isUnstable=false
 | 
			
		||||
modVersion=1.108.0
 | 
			
		||||
modVersion=1.109.6
 | 
			
		||||
 | 
			
		||||
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
 | 
			
		||||
mcVersion=1.19.4
 | 
			
		||||
mcVersion=1.20.1
 | 
			
		||||
 
 | 
			
		||||
@@ -7,74 +7,82 @@
 | 
			
		||||
# 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.19.4"
 | 
			
		||||
fabric-api = "0.86.1+1.20.1"
 | 
			
		||||
fabric-loader = "0.14.21"
 | 
			
		||||
forge = "45.0.42"
 | 
			
		||||
forgeSpi = "6.0.0"
 | 
			
		||||
forge = "47.1.0"
 | 
			
		||||
forgeSpi = "7.0.1"
 | 
			
		||||
mixin = "0.8.5"
 | 
			
		||||
parchment = "2023.06.26"
 | 
			
		||||
parchmentMc = "1.19.4"
 | 
			
		||||
parchment = "2023.08.20"
 | 
			
		||||
parchmentMc = "1.20.1"
 | 
			
		||||
yarn = "1.20.1+build.10"
 | 
			
		||||
 | 
			
		||||
# Normal dependencies
 | 
			
		||||
asm = "9.3"
 | 
			
		||||
autoService = "1.0.1"
 | 
			
		||||
checkerFramework = "3.32.0"
 | 
			
		||||
cobalt = "0.7.3"
 | 
			
		||||
cobalt-next = "0.7.4" # Not a real version, used to constrain the version we accept.
 | 
			
		||||
# Core dependencies (these versions are tied to the version Minecraft uses)
 | 
			
		||||
fastutil = "8.5.9"
 | 
			
		||||
guava = "31.1-jre"
 | 
			
		||||
jetbrainsAnnotations = "24.0.1"
 | 
			
		||||
netty = "4.1.82.Final"
 | 
			
		||||
slf4j = "2.0.1"
 | 
			
		||||
 | 
			
		||||
# Core dependencies (independent of Minecraft)
 | 
			
		||||
asm = "9.6"
 | 
			
		||||
autoService = "1.1.1"
 | 
			
		||||
checkerFramework = "3.42.0"
 | 
			
		||||
cobalt = "0.9.1"
 | 
			
		||||
commonsCli = "1.6.0"
 | 
			
		||||
jetbrainsAnnotations = "24.1.0"
 | 
			
		||||
jsr305 = "3.0.2"
 | 
			
		||||
jzlib = "1.1.3"
 | 
			
		||||
kotlin = "1.8.10"
 | 
			
		||||
kotlin-coroutines = "1.6.4"
 | 
			
		||||
netty = "4.1.82.Final"
 | 
			
		||||
nightConfig = "3.6.5"
 | 
			
		||||
slf4j = "1.7.36"
 | 
			
		||||
kotlin = "1.9.21"
 | 
			
		||||
kotlin-coroutines = "1.7.3"
 | 
			
		||||
nightConfig = "3.6.7"
 | 
			
		||||
 | 
			
		||||
# Minecraft mods
 | 
			
		||||
emi = "1.0.8+1.19.4"
 | 
			
		||||
fabricPermissions = "0.2.20221016"
 | 
			
		||||
iris = "1.5.2+1.19.4"
 | 
			
		||||
jei = "13.1.0.11"
 | 
			
		||||
modmenu = "6.1.0-rc.1"
 | 
			
		||||
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"
 | 
			
		||||
moreRed = "4.0.0.4"
 | 
			
		||||
oculus = "1.2.5"
 | 
			
		||||
rei = "10.0.578"
 | 
			
		||||
rei = "12.0.626"
 | 
			
		||||
rubidium = "0.6.1"
 | 
			
		||||
sodium = "mc1.19.4-0.4.10"
 | 
			
		||||
sodium = "mc1.20-0.4.10"
 | 
			
		||||
 | 
			
		||||
# Testing
 | 
			
		||||
byteBuddy = "1.14.2"
 | 
			
		||||
hamcrest = "2.2"
 | 
			
		||||
jqwik = "1.7.2"
 | 
			
		||||
junit = "5.9.2"
 | 
			
		||||
jqwik = "1.8.2"
 | 
			
		||||
junit = "5.10.1"
 | 
			
		||||
 | 
			
		||||
# Build tools
 | 
			
		||||
cctJavadoc = "1.8.0"
 | 
			
		||||
checkstyle = "10.3.4"
 | 
			
		||||
cctJavadoc = "1.8.2"
 | 
			
		||||
checkstyle = "10.12.6"
 | 
			
		||||
curseForgeGradle = "1.0.14"
 | 
			
		||||
errorProne-core = "2.18.0"
 | 
			
		||||
errorProne-plugin = "3.0.1"
 | 
			
		||||
fabric-loom = "1.3.7"
 | 
			
		||||
forgeGradle = "6.0.8"
 | 
			
		||||
githubRelease = "2.2.12"
 | 
			
		||||
ideaExt = "1.1.6"
 | 
			
		||||
illuaminate = "0.1.0-40-g975cbc3"
 | 
			
		||||
errorProne-core = "2.23.0"
 | 
			
		||||
errorProne-plugin = "3.1.0"
 | 
			
		||||
fabric-loom = "1.5.7"
 | 
			
		||||
forgeGradle = "6.0.20"
 | 
			
		||||
githubRelease = "2.5.2"
 | 
			
		||||
gradleVersions = "0.50.0"
 | 
			
		||||
ideaExt = "1.1.7"
 | 
			
		||||
illuaminate = "0.1.0-44-g9ee0055"
 | 
			
		||||
librarian = "1.+"
 | 
			
		||||
lwjgl = "3.3.3"
 | 
			
		||||
minotaur = "2.+"
 | 
			
		||||
mixinGradle = "0.7.+"
 | 
			
		||||
mixinGradle = "0.7.38"
 | 
			
		||||
nullAway = "0.9.9"
 | 
			
		||||
quiltflower = "1.10.0"
 | 
			
		||||
spotless = "6.17.0"
 | 
			
		||||
spotless = "6.23.3"
 | 
			
		||||
taskTree = "2.1.1"
 | 
			
		||||
vanillaGradle = "0.2.1-SNAPSHOT"
 | 
			
		||||
teavm = "0.10.0-SQUID.2"
 | 
			
		||||
vanillaExtract = "0.1.1"
 | 
			
		||||
versionCatalogUpdate = "0.8.1"
 | 
			
		||||
 | 
			
		||||
[libraries]
 | 
			
		||||
# Normal dependencies
 | 
			
		||||
asm = { module = "org.ow2.asm:asm", version.ref = "asm" }
 | 
			
		||||
asm-commons = { module = "org.ow2.asm:asm-commons", version.ref = "asm" }
 | 
			
		||||
autoService = { module = "com.google.auto.service:auto-service", version.ref = "autoService" }
 | 
			
		||||
checkerFramework = { module = "org.checkerframework:checker-qual", version.ref = "checkerFramework" }
 | 
			
		||||
cobalt = { module = "org.squiddev:Cobalt", version.ref = "cobalt" }
 | 
			
		||||
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" }
 | 
			
		||||
guava = { module = "com.google.guava:guava", version.ref = "guava" }
 | 
			
		||||
@@ -94,14 +102,16 @@ slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
 | 
			
		||||
# Minecraft mods
 | 
			
		||||
fabric-api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabric-api" }
 | 
			
		||||
fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" }
 | 
			
		||||
fabric-junit = { module = "net.fabricmc:fabric-loader-junit", version.ref = "fabric-loader" }
 | 
			
		||||
fabricPermissions = { module = "me.lucko:fabric-permissions-api", version.ref = "fabricPermissions" }
 | 
			
		||||
emi = { module = "dev.emi:emi-xplat-mojmap", version.ref = "emi" }
 | 
			
		||||
iris = { module = "maven.modrinth:iris", version.ref = "iris" }
 | 
			
		||||
jei-api = { module = "mezz.jei:jei-1.19.4-common-api", version.ref = "jei" }
 | 
			
		||||
jei-fabric = { module = "mezz.jei:jei-1.19.4-fabric", version.ref = "jei" }
 | 
			
		||||
jei-forge = { module = "mezz.jei:jei-1.19.4-forge", version.ref = "jei" }
 | 
			
		||||
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" }
 | 
			
		||||
mixin = { module = "org.spongepowered:mixin", version.ref = "mixin" }
 | 
			
		||||
modmenu = { module = "com.terraformersmc:modmenu", version.ref = "modmenu" }
 | 
			
		||||
moreRed = { module = "commoble.morered:morered-1.20.1", version.ref = "moreRed" }
 | 
			
		||||
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" }
 | 
			
		||||
@@ -110,8 +120,6 @@ rubidium = { module = "maven.modrinth:rubidium", version.ref = "rubidium" }
 | 
			
		||||
sodium = { module = "maven.modrinth:sodium", version.ref = "sodium" }
 | 
			
		||||
 | 
			
		||||
# Testing
 | 
			
		||||
byteBuddyAgent = { module = "net.bytebuddy:byte-buddy-agent", version.ref = "byteBuddy" }
 | 
			
		||||
byteBuddy = { module = "net.bytebuddy:byte-buddy", version.ref = "byteBuddy" }
 | 
			
		||||
hamcrest = { module = "org.hamcrest:hamcrest", version.ref = "hamcrest" }
 | 
			
		||||
jqwik-api = { module = "net.jqwik:jqwik-api", version.ref = "jqwik" }
 | 
			
		||||
jqwik-engine = { module = "net.jqwik:jqwik-engine", version.ref = "jqwik" }
 | 
			
		||||
@@ -120,6 +128,12 @@ junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", vers
 | 
			
		||||
junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit" }
 | 
			
		||||
slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" }
 | 
			
		||||
 | 
			
		||||
# LWJGL
 | 
			
		||||
lwjgl-bom = { module = "org.lwjgl:lwjgl-bom", version.ref = "lwjgl" }
 | 
			
		||||
lwjgl-core = { module = "org.lwjgl:lwjgl" }
 | 
			
		||||
lwjgl-opengl = { module = "org.lwjgl:lwjgl-opengl" }
 | 
			
		||||
lwjgl-glfw = { module = "org.lwjgl:lwjgl-glfw" }
 | 
			
		||||
 | 
			
		||||
# Build tools
 | 
			
		||||
cctJavadoc = { module = "cc.tweaked:cct-javadoc", version.ref = "cctJavadoc" }
 | 
			
		||||
checkstyle = { module = "com.puppycrawl.tools:checkstyle", version.ref = "checkstyle" }
 | 
			
		||||
@@ -131,34 +145,48 @@ errorProne-plugin = { module = "net.ltgt.gradle:gradle-errorprone-plugin", versi
 | 
			
		||||
errorProne-testHelpers = { module = "com.google.errorprone:error_prone_test_helpers", version.ref = "errorProne-core" }
 | 
			
		||||
fabric-loom = { module = "net.fabricmc:fabric-loom", version.ref = "fabric-loom" }
 | 
			
		||||
forgeGradle = { module = "net.minecraftforge.gradle:ForgeGradle", version.ref = "forgeGradle" }
 | 
			
		||||
ideaExt = { module = "gradle.plugin.org.jetbrains.gradle.plugin.idea-ext:gradle-idea-ext", version.ref = "ideaExt" }
 | 
			
		||||
kotlin-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
 | 
			
		||||
librarian = { module = "org.parchmentmc:librarian", version.ref = "librarian" }
 | 
			
		||||
minotaur = { module = "com.modrinth.minotaur:Minotaur", version.ref = "minotaur" }
 | 
			
		||||
nullAway = { module = "com.uber.nullaway:nullaway", version.ref = "nullAway" }
 | 
			
		||||
quiltflower = { module = "io.github.juuxel:loom-quiltflower", version.ref = "quiltflower" }
 | 
			
		||||
spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" }
 | 
			
		||||
vanillaGradle = { module = "org.spongepowered:vanillagradle", version.ref = "vanillaGradle" }
 | 
			
		||||
teavm-classlib = { module = "org.teavm:teavm-classlib", version.ref = "teavm" }
 | 
			
		||||
teavm-jso = { module = "org.teavm:teavm-jso", version.ref = "teavm" }
 | 
			
		||||
teavm-jso-apis = { module = "org.teavm:teavm-jso-apis", version.ref = "teavm" }
 | 
			
		||||
teavm-jso-impl = { module = "org.teavm:teavm-jso-impl", version.ref = "teavm" }
 | 
			
		||||
teavm-metaprogramming-api = { module = "org.teavm:teavm-metaprogramming-api", version.ref = "teavm" }
 | 
			
		||||
teavm-metaprogramming-impl = { module = "org.teavm:teavm-metaprogramming-impl", version.ref = "teavm" }
 | 
			
		||||
teavm-platform = { module = "org.teavm:teavm-platform", version.ref = "teavm" }
 | 
			
		||||
teavm-tooling = { module = "org.teavm:teavm-tooling", version.ref = "teavm" }
 | 
			
		||||
vanillaExtract = { module = "cc.tweaked.vanilla-extract:plugin", version.ref = "vanillaExtract" }
 | 
			
		||||
yarn = { module = "net.fabricmc:yarn", version.ref = "yarn" }
 | 
			
		||||
 | 
			
		||||
[plugins]
 | 
			
		||||
forgeGradle = { id = "net.minecraftforge.gradle", version.ref = "forgeGradle" }
 | 
			
		||||
githubRelease = { id = "com.github.breadmoirai.github-release", version.ref = "githubRelease" }
 | 
			
		||||
ideaExt = { id = "org.jetbrains.gradle.plugin.idea-ext", version.ref = "ideaExt" }
 | 
			
		||||
gradleVersions = { id = "com.github.ben-manes.versions", version.ref = "gradleVersions" }
 | 
			
		||||
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
 | 
			
		||||
librarian = { id = "org.parchmentmc.librarian.forgegradle", version.ref = "librarian" }
 | 
			
		||||
mixinGradle = { id = "org.spongepowered.mixin", version.ref = "mixinGradle" }
 | 
			
		||||
taskTree = { id = "com.dorongold.task-tree", version.ref = "taskTree" }
 | 
			
		||||
versionCatalogUpdate = { id = "nl.littlerobots.version-catalog-update", version.ref = "versionCatalogUpdate" }
 | 
			
		||||
 | 
			
		||||
[bundles]
 | 
			
		||||
annotations = ["jsr305", "checkerFramework", "jetbrainsAnnotations"]
 | 
			
		||||
kotlin = ["kotlin-stdlib", "kotlin-coroutines"]
 | 
			
		||||
 | 
			
		||||
# Minecraft
 | 
			
		||||
externalMods-common = ["jei-api", "nightConfig-core", "nightConfig-toml"]
 | 
			
		||||
externalMods-forge-compile = ["oculus", "jei-api"]
 | 
			
		||||
externalMods-forge-compile = ["moreRed", "oculus", "jei-api"]
 | 
			
		||||
externalMods-forge-runtime = ["jei-forge"]
 | 
			
		||||
externalMods-fabric = ["nightConfig-core", "nightConfig-toml"]
 | 
			
		||||
externalMods-fabric-compile = ["fabricPermissions", "iris", "jei-api", "rei-api", "rei-builtin"]
 | 
			
		||||
externalMods-fabric-runtime = ["jei-fabric", "modmenu"]
 | 
			
		||||
 | 
			
		||||
# Testing
 | 
			
		||||
test = ["junit-jupiter-api", "junit-jupiter-params", "hamcrest", "jqwik-api"]
 | 
			
		||||
testRuntime = ["junit-jupiter-engine", "jqwik-engine"]
 | 
			
		||||
 | 
			
		||||
# Build tools
 | 
			
		||||
teavm-api = ["teavm-jso", "teavm-jso-apis", "teavm-platform", "teavm-classlib", "teavm-metaprogramming-api"]
 | 
			
		||||
teavm-tooling = ["teavm-tooling", "teavm-metaprogramming-impl", "teavm-jso-impl"]
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								gradle/wrapper/gradle-wrapper.jar
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								gradle/wrapper/gradle-wrapper.jar
									
									
									
									
										vendored
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										3
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							@@ -1,6 +1,7 @@
 | 
			
		||||
distributionBase=GRADLE_USER_HOME
 | 
			
		||||
distributionPath=wrapper/dists
 | 
			
		||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
 | 
			
		||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
 | 
			
		||||
networkTimeout=10000
 | 
			
		||||
validateDistributionUrl=true
 | 
			
		||||
zipStoreBase=GRADLE_USER_HOME
 | 
			
		||||
zipStorePath=wrapper/dists
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										22
									
								
								gradlew
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										22
									
								
								gradlew
									
									
									
									
										vendored
									
									
								
							@@ -83,7 +83,8 @@ done
 | 
			
		||||
# This is normally unused
 | 
			
		||||
# shellcheck disable=SC2034
 | 
			
		||||
APP_BASE_NAME=${0##*/}
 | 
			
		||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
 | 
			
		||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
 | 
			
		||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
 | 
			
		||||
 | 
			
		||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
 | 
			
		||||
MAX_FD=maximum
 | 
			
		||||
@@ -130,10 +131,13 @@ location of your Java installation."
 | 
			
		||||
    fi
 | 
			
		||||
else
 | 
			
		||||
    JAVACMD=java
 | 
			
		||||
    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
 | 
			
		||||
    if ! command -v java >/dev/null 2>&1
 | 
			
		||||
    then
 | 
			
		||||
        die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
 | 
			
		||||
 | 
			
		||||
Please set the JAVA_HOME variable in your environment to match the
 | 
			
		||||
location of your Java installation."
 | 
			
		||||
    fi
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# Increase the maximum file descriptors if we can.
 | 
			
		||||
@@ -141,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
 | 
			
		||||
    case $MAX_FD in #(
 | 
			
		||||
      max*)
 | 
			
		||||
        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
 | 
			
		||||
        # shellcheck disable=SC3045
 | 
			
		||||
        # shellcheck disable=SC2039,SC3045
 | 
			
		||||
        MAX_FD=$( ulimit -H -n ) ||
 | 
			
		||||
            warn "Could not query maximum file descriptor limit"
 | 
			
		||||
    esac
 | 
			
		||||
@@ -149,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
 | 
			
		||||
      '' | soft) :;; #(
 | 
			
		||||
      *)
 | 
			
		||||
        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
 | 
			
		||||
        # shellcheck disable=SC3045
 | 
			
		||||
        # shellcheck disable=SC2039,SC3045
 | 
			
		||||
        ulimit -n "$MAX_FD" ||
 | 
			
		||||
            warn "Could not set maximum file descriptor limit to $MAX_FD"
 | 
			
		||||
    esac
 | 
			
		||||
@@ -198,11 +202,11 @@ fi
 | 
			
		||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
 | 
			
		||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
 | 
			
		||||
 | 
			
		||||
# Collect all arguments for the java command;
 | 
			
		||||
#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
 | 
			
		||||
#     shell script including quotes and variable substitutions, so put them in
 | 
			
		||||
#     double quotes to make sure that they get re-expanded; and
 | 
			
		||||
#   * put everything else in single quotes, so that it's not re-expanded.
 | 
			
		||||
# Collect all arguments for the java command:
 | 
			
		||||
#   * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
 | 
			
		||||
#     and any embedded shellness will be escaped.
 | 
			
		||||
#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
 | 
			
		||||
#     treated as '${Hostname}' itself on the command line.
 | 
			
		||||
 | 
			
		||||
set -- \
 | 
			
		||||
        "-Dorg.gradle.appname=$APP_BASE_NAME" \
 | 
			
		||||
 
 | 
			
		||||
@@ -2,15 +2,15 @@
 | 
			
		||||
 | 
			
		||||
; SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
;
 | 
			
		||||
; SPDX-License-Identifier: LicenseRef-CCPL
 | 
			
		||||
; SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
(sources
 | 
			
		||||
  /doc/
 | 
			
		||||
  /projects/forge/build/docs/luaJavadoc/
 | 
			
		||||
  /projects/common/build/docs/luaJavadoc/
 | 
			
		||||
  /projects/core/src/main/resources/data/computercraft/lua/bios.lua
 | 
			
		||||
  /projects/core/src/main/resources/data/computercraft/lua/rom/
 | 
			
		||||
  /projects/core/src/test/resources/test-rom
 | 
			
		||||
  /projects/web/src/mount)
 | 
			
		||||
  /projects/web/src/frontend/mount)
 | 
			
		||||
 | 
			
		||||
(doc
 | 
			
		||||
  ; Also defined in projects/web/build.gradle.kts
 | 
			
		||||
@@ -23,7 +23,7 @@
 | 
			
		||||
    (url https://tweaked.cc/)
 | 
			
		||||
    (source-link https://github.com/cc-tweaked/CC-Tweaked/blob/${commit}/${path}#L${line})
 | 
			
		||||
 | 
			
		||||
    (styles /projects/web/src/styles.css)
 | 
			
		||||
    (styles  /projects/web/build/rollup/index.css)
 | 
			
		||||
    (scripts /projects/web/build/rollup/index.js)
 | 
			
		||||
    (head doc/head.html))
 | 
			
		||||
 | 
			
		||||
@@ -36,7 +36,7 @@
 | 
			
		||||
 | 
			
		||||
  (library-path
 | 
			
		||||
    /doc/stub/
 | 
			
		||||
    /projects/forge/build/docs/luaJavadoc/
 | 
			
		||||
    /projects/common/build/docs/luaJavadoc/
 | 
			
		||||
 | 
			
		||||
    /projects/core/src/main/resources/data/computercraft/lua/rom/apis/
 | 
			
		||||
    /projects/core/src/main/resources/data/computercraft/lua/rom/apis/command/
 | 
			
		||||
@@ -77,7 +77,6 @@
 | 
			
		||||
    (globals
 | 
			
		||||
      :max
 | 
			
		||||
      _CC_DEFAULT_SETTINGS
 | 
			
		||||
      _CC_DISABLE_LUA51_FEATURES
 | 
			
		||||
      _HOST
 | 
			
		||||
      ;; Ideally we'd pick these up from bios.lua, but illuaminate currently
 | 
			
		||||
      ;; isn't smart enough.
 | 
			
		||||
@@ -89,7 +88,7 @@
 | 
			
		||||
  (/doc/stub/
 | 
			
		||||
   /projects/core/src/main/resources/data/computercraft/lua/bios.lua
 | 
			
		||||
   /projects/core/src/main/resources/data/computercraft/lua/rom/apis/
 | 
			
		||||
   /projects/forge/build/docs/luaJavadoc/)
 | 
			
		||||
   /projects/common/build/docs/luaJavadoc/)
 | 
			
		||||
  (linters -var:unused-global)
 | 
			
		||||
  (lint (allow-toplevel-global true)))
 | 
			
		||||
 | 
			
		||||
@@ -106,6 +105,10 @@
 | 
			
		||||
   /projects/core/src/main/resources/data/computercraft/lua/rom/apis/turtle/turtle.lua)
 | 
			
		||||
  (linters -var:deprecated))
 | 
			
		||||
 | 
			
		||||
;; Suppress unused variable warnings in the parser.
 | 
			
		||||
(at /projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/cc/internal/syntax/parser.lua
 | 
			
		||||
  (linters -var:unused))
 | 
			
		||||
 | 
			
		||||
(at /projects/core/src/test/resources/test-rom
 | 
			
		||||
  ; We should still be able to test deprecated members.
 | 
			
		||||
  (linters -var:deprecated)
 | 
			
		||||
@@ -115,4 +118,4 @@
 | 
			
		||||
      :max sleep write
 | 
			
		||||
      cct_test describe expect howlci fail it pending stub before_each)))
 | 
			
		||||
 | 
			
		||||
(at /projects/web/src/mount/expr_template.lua (lint (globals :max __expr__)))
 | 
			
		||||
(at /projects/web/src/frontend/mount/expr_template.lua (lint (globals :max __expr__)))
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4091
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4091
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										26
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								package.json
									
									
									
									
									
								
							@@ -6,24 +6,24 @@
 | 
			
		||||
  "license": "BSD-3-Clause",
 | 
			
		||||
  "type": "module",
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@squid-dev/cc-web-term": "^2.0.0",
 | 
			
		||||
    "preact": "^10.5.5",
 | 
			
		||||
    "setimmediate": "^1.0.5",
 | 
			
		||||
    "tslib": "^2.0.3"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@rollup/plugin-terser": "^0.4.0",
 | 
			
		||||
    "@rollup/plugin-node-resolve": "^15.2.1",
 | 
			
		||||
    "@rollup/plugin-typescript": "^11.0.0",
 | 
			
		||||
    "@rollup/plugin-url": "^8.0.1",
 | 
			
		||||
    "@types/glob": "^8.1.0",
 | 
			
		||||
    "@types/react-dom": "^18.0.5",
 | 
			
		||||
    "glob": "^9.3.0",
 | 
			
		||||
    "react-dom": "^18.1.0",
 | 
			
		||||
    "react": "^18.1.0",
 | 
			
		||||
    "rehype-highlight": "^6.0.0",
 | 
			
		||||
    "rehype-react": "^7.1.1",
 | 
			
		||||
    "rehype": "^12.0.1",
 | 
			
		||||
    "requirejs": "^2.3.6",
 | 
			
		||||
    "rollup": "^3.19.1",
 | 
			
		||||
    "ts-node": "^10.8.0",
 | 
			
		||||
    "typescript": "^4.0.5"
 | 
			
		||||
    "@swc/core": "^1.3.92",
 | 
			
		||||
    "@types/node": "^20.8.3",
 | 
			
		||||
    "lightningcss": "^1.22.0",
 | 
			
		||||
    "preact-render-to-string": "^6.2.1",
 | 
			
		||||
    "rehype": "^13.0.0",
 | 
			
		||||
    "rehype-highlight": "^7.0.0",
 | 
			
		||||
    "rehype-react": "^8.0.0",
 | 
			
		||||
    "rollup": "^4.0.0",
 | 
			
		||||
    "tsx": "^4.7.0",
 | 
			
		||||
    "typescript": "^5.2.2"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -62,6 +62,9 @@ mentioning:
 | 
			
		||||
 - `lints`: This defines an [ErrorProne] plugin which adds a couple of compile-time checks to our code. This is what
 | 
			
		||||
   enforces that no client-specific code is used inside the `main` source set (and a couple of other things!).
 | 
			
		||||
 | 
			
		||||
 - `standalone`: This contains a standalone UI for computers, allowing debugging and development of CraftOS without
 | 
			
		||||
   launching Minecraft.
 | 
			
		||||
 | 
			
		||||
 - `web`: This contains the additional tooling for building [the documentation website][tweaked.cc], such as support for
 | 
			
		||||
   rendering recipes
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -27,8 +27,13 @@ public final class ComputerCraftAPIClient {
 | 
			
		||||
     * @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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,26 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.api.client.turtle;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A functional interface to register a {@link TurtleUpgradeModeller} for a class of turtle upgrades.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * This interface is largely intended to be used from multi-loader code, to allow sharing registration code between
 | 
			
		||||
 * multiple loaders.
 | 
			
		||||
 */
 | 
			
		||||
@FunctionalInterface
 | 
			
		||||
public interface RegisterTurtleUpgradeModeller {
 | 
			
		||||
    /**
 | 
			
		||||
     * Register a {@link TurtleUpgradeModeller}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param serialiser The turtle upgrade serialiser.
 | 
			
		||||
     * @param modeller   The upgrade modeller.
 | 
			
		||||
     * @param <T>        The type of the turtle upgrade.
 | 
			
		||||
     */
 | 
			
		||||
    <T extends ITurtleUpgrade> void register(TurtleUpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller);
 | 
			
		||||
}
 | 
			
		||||
@@ -4,12 +4,10 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.api.client.turtle;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.client.ComputerCraftAPIClient;
 | 
			
		||||
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 dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
 | 
			
		||||
import net.minecraft.client.resources.model.ModelResourceLocation;
 | 
			
		||||
import net.minecraft.client.resources.model.UnbakedModel;
 | 
			
		||||
import net.minecraft.nbt.CompoundTag;
 | 
			
		||||
@@ -21,9 +19,13 @@ import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides models for a {@link ITurtleUpgrade}.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Use {@code dan200.computercraft.api.client.FabricComputerCraftAPIClient#registerTurtleUpgradeModeller} to register a
 | 
			
		||||
 * modeller on Fabric and {@code dan200.computercraft.api.client.turtle.RegisterTurtleModellersEvent} to register one
 | 
			
		||||
 * on Forge
 | 
			
		||||
 *
 | 
			
		||||
 * @param <T> The type of turtle upgrade this modeller applies to.
 | 
			
		||||
 * @see ComputerCraftAPIClient#registerTurtleUpgradeModeller(TurtleUpgradeSerialiser, TurtleUpgradeModeller) To register a modeller.
 | 
			
		||||
 * @see RegisterTurtleUpgradeModeller For multi-loader registration support.
 | 
			
		||||
 */
 | 
			
		||||
public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@ import org.joml.Matrix4f;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
class TurtleUpgradeModellers {
 | 
			
		||||
final class TurtleUpgradeModellers {
 | 
			
		||||
    private static final Transformation leftTransform = getMatrixFor(-0.4065f);
 | 
			
		||||
    private static final Transformation rightTransform = getMatrixFor(0.4065f);
 | 
			
		||||
 | 
			
		||||
@@ -35,7 +35,7 @@ class TurtleUpgradeModellers {
 | 
			
		||||
 | 
			
		||||
    static final TurtleUpgradeModeller<ITurtleUpgrade> UPGRADE_ITEM = new UpgradeItemModeller();
 | 
			
		||||
 | 
			
		||||
    private static class UpgradeItemModeller implements TurtleUpgradeModeller<ITurtleUpgrade> {
 | 
			
		||||
    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);
 | 
			
		||||
 
 | 
			
		||||
@@ -46,6 +46,14 @@ public class ComputerCraftTags {
 | 
			
		||||
        public static final TagKey<Block> WIRED_MODEM = make("wired_modem");
 | 
			
		||||
        public static final TagKey<Block> MONITOR = make("monitor");
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Blocks which should be ignored by a {@code peripheral_hub} peripheral.
 | 
			
		||||
         * <p>
 | 
			
		||||
         * This should include blocks which themselves expose a peripheral hub (such as {@linkplain #WIRED_MODEM wired
 | 
			
		||||
         * modems}).
 | 
			
		||||
         */
 | 
			
		||||
        public static final TagKey<Block> PERIPHERAL_HUB_IGNORE = make("peripheral_hub_ignore");
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Blocks which can be broken by any turtle tool.
 | 
			
		||||
         */
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,6 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.api.pocket;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeBase;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.impl.ComputerCraftAPIService;
 | 
			
		||||
@@ -30,14 +29,6 @@ import java.util.function.Function;
 | 
			
		||||
 * @see PocketUpgradeDataProvider
 | 
			
		||||
 */
 | 
			
		||||
public interface PocketUpgradeSerialiser<T extends IPocketUpgrade> extends UpgradeSerialiser<T> {
 | 
			
		||||
    /**
 | 
			
		||||
     * The ID for the associated registry.
 | 
			
		||||
     *
 | 
			
		||||
     * @deprecated Use {@link #registryId()} instead.
 | 
			
		||||
     */
 | 
			
		||||
    @Deprecated(forRemoval = true)
 | 
			
		||||
    ResourceKey<Registry<PocketUpgradeSerialiser<?>>> REGISTRY_ID = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "pocket_upgrade_serialiser"));
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The ID for the associated registry.
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,6 @@ import net.minecraft.nbt.CompoundTag;
 | 
			
		||||
import net.minecraft.world.Container;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.level.Level;
 | 
			
		||||
import net.minecraft.world.phys.Vec3;
 | 
			
		||||
import org.jetbrains.annotations.ApiStatus;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
@@ -71,29 +70,6 @@ public interface ITurtleAccess {
 | 
			
		||||
     */
 | 
			
		||||
    boolean teleportTo(Level world, BlockPos pos);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns a vector containing the floating point co-ordinates at which the turtle is rendered.
 | 
			
		||||
     * This will shift when the turtle is moving.
 | 
			
		||||
     *
 | 
			
		||||
     * @param f The subframe fraction.
 | 
			
		||||
     * @return A vector containing the floating point co-ordinates at which the turtle resides.
 | 
			
		||||
     * @see #getVisualYaw(float)
 | 
			
		||||
     * @deprecated Will be removed in 1.20.
 | 
			
		||||
     */
 | 
			
		||||
    @Deprecated(forRemoval = true)
 | 
			
		||||
    Vec3 getVisualPosition(float f);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the yaw the turtle is facing when it is rendered.
 | 
			
		||||
     *
 | 
			
		||||
     * @param f The subframe fraction.
 | 
			
		||||
     * @return The yaw the turtle is facing.
 | 
			
		||||
     * @see #getVisualPosition(float)
 | 
			
		||||
     * @deprecated Will be removed in 1.20.
 | 
			
		||||
     */
 | 
			
		||||
    @Deprecated(forRemoval = true)
 | 
			
		||||
    float getVisualYaw(float f);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the world direction the turtle is currently facing.
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,6 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.api.turtle;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeBase;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.impl.ComputerCraftAPIService;
 | 
			
		||||
@@ -49,13 +48,8 @@ import java.util.function.Function;
 | 
			
		||||
 * }
 | 
			
		||||
 * }</pre>
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Finally, we need to register a model for our upgrade. This is done with
 | 
			
		||||
 * {@link dan200.computercraft.api.client.ComputerCraftAPIClient#registerTurtleUpgradeModeller}:
 | 
			
		||||
 *
 | 
			
		||||
 * <pre>{@code
 | 
			
		||||
 * // Register our model inside FMLClientSetupEvent
 | 
			
		||||
 * ComputerCraftAPIClient.registerTurtleUpgradeModeller(MY_UPGRADE.get(), TurtleUpgradeModeller.flatItem())
 | 
			
		||||
 * }</pre>
 | 
			
		||||
 * Finally, we need to register a model for our upgrade. The way to do this varies on mod loader, see
 | 
			
		||||
 * {@link dan200.computercraft.api.client.turtle.TurtleUpgradeModeller} for more information.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * {@link TurtleUpgradeDataProvider} provides a data provider to aid with generating these JSON files.
 | 
			
		||||
 *
 | 
			
		||||
@@ -65,14 +59,6 @@ import java.util.function.Function;
 | 
			
		||||
 * @see dan200.computercraft.api.client.turtle.TurtleUpgradeModeller
 | 
			
		||||
 */
 | 
			
		||||
public interface TurtleUpgradeSerialiser<T extends ITurtleUpgrade> extends UpgradeSerialiser<T> {
 | 
			
		||||
    /**
 | 
			
		||||
     * The ID for the associated registry.
 | 
			
		||||
     *
 | 
			
		||||
     * @deprecated Use {@link #registryId()} instead.
 | 
			
		||||
     */
 | 
			
		||||
    @Deprecated(forRemoval = true)
 | 
			
		||||
    ResourceKey<Registry<TurtleUpgradeSerialiser<?>>> REGISTRY_ID = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_upgrade_serialiser"));
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The ID for the associated registry.
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
@@ -19,8 +19,6 @@ import net.minecraft.data.PackOutput;
 | 
			
		||||
import net.minecraft.resources.ResourceKey;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.item.Item;
 | 
			
		||||
import org.apache.logging.log4j.LogManager;
 | 
			
		||||
import org.apache.logging.log4j.Logger;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
@@ -36,8 +34,6 @@ import java.util.function.Function;
 | 
			
		||||
 * @param <R> The upgrade serialiser to register for.
 | 
			
		||||
 */
 | 
			
		||||
public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends UpgradeSerialiser<? extends T>> implements DataProvider {
 | 
			
		||||
    private static final Logger LOGGER = LogManager.getLogger();
 | 
			
		||||
 | 
			
		||||
    private final PackOutput output;
 | 
			
		||||
    private final String name;
 | 
			
		||||
    private final String folder;
 | 
			
		||||
 
 | 
			
		||||
@@ -2,14 +2,13 @@
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
import cc.tweaked.gradle.annotationProcessorEverywhere
 | 
			
		||||
import cc.tweaked.gradle.clientClasses
 | 
			
		||||
import cc.tweaked.gradle.commonClasses
 | 
			
		||||
import cc.tweaked.gradle.*
 | 
			
		||||
 | 
			
		||||
plugins {
 | 
			
		||||
    id("cc-tweaked.publishing")
 | 
			
		||||
    id("cc-tweaked.vanilla")
 | 
			
		||||
    id("cc-tweaked.gametest")
 | 
			
		||||
    id("cc-tweaked.illuaminate")
 | 
			
		||||
    id("cc-tweaked.publishing")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
minecraft {
 | 
			
		||||
@@ -19,6 +18,18 @@ minecraft {
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
configurations {
 | 
			
		||||
    register("cctJavadoc")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
repositories {
 | 
			
		||||
    maven("https://maven.minecraftforge.net/") {
 | 
			
		||||
        content {
 | 
			
		||||
            includeModule("org.spongepowered", "mixin")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    // Pull in our other projects. See comments in MinecraftConfigurations on this nastiness.
 | 
			
		||||
    implementation(project(":core"))
 | 
			
		||||
@@ -28,7 +39,6 @@ dependencies {
 | 
			
		||||
    compileOnly(libs.bundles.externalMods.common)
 | 
			
		||||
    clientCompileOnly(variantOf(libs.emi) { classifier("api") })
 | 
			
		||||
 | 
			
		||||
    compileOnly(libs.mixin)
 | 
			
		||||
    annotationProcessorEverywhere(libs.autoService)
 | 
			
		||||
    testFixturesAnnotationProcessor(libs.autoService)
 | 
			
		||||
 | 
			
		||||
@@ -36,7 +46,59 @@ dependencies {
 | 
			
		||||
    testImplementation(libs.bundles.test)
 | 
			
		||||
    testRuntimeOnly(libs.bundles.testRuntime)
 | 
			
		||||
 | 
			
		||||
    testModCompileOnly(libs.mixin)
 | 
			
		||||
    testModImplementation(testFixtures(project(":core")))
 | 
			
		||||
    testModImplementation(testFixtures(project(":common")))
 | 
			
		||||
    testModImplementation(libs.bundles.kotlin)
 | 
			
		||||
 | 
			
		||||
    testFixturesImplementation(testFixtures(project(":core")))
 | 
			
		||||
 | 
			
		||||
    "cctJavadoc"(libs.cctJavadoc)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
illuaminate {
 | 
			
		||||
    version.set(libs.versions.illuaminate)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
val luaJavadoc by tasks.registering(Javadoc::class) {
 | 
			
		||||
    description = "Generates documentation for Java-side Lua functions."
 | 
			
		||||
    group = JavaBasePlugin.DOCUMENTATION_GROUP
 | 
			
		||||
 | 
			
		||||
    val sourceSets = listOf(sourceSets.main.get(), project(":core").sourceSets.main.get())
 | 
			
		||||
    for (sourceSet in sourceSets) {
 | 
			
		||||
        source(sourceSet.java)
 | 
			
		||||
        classpath += sourceSet.compileClasspath
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    destinationDir = layout.buildDirectory.dir("docs/luaJavadoc").get().asFile
 | 
			
		||||
 | 
			
		||||
    val options = options as StandardJavadocDocletOptions
 | 
			
		||||
    options.docletpath = configurations["cctJavadoc"].files.toList()
 | 
			
		||||
    options.doclet = "cc.tweaked.javadoc.LuaDoclet"
 | 
			
		||||
    options.addStringOption("project-root", rootProject.file(".").absolutePath)
 | 
			
		||||
    options.noTimestamp(false)
 | 
			
		||||
 | 
			
		||||
    javadocTool.set(
 | 
			
		||||
        javaToolchains.javadocToolFor {
 | 
			
		||||
            languageVersion.set(CCTweakedPlugin.JAVA_VERSION)
 | 
			
		||||
        },
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
val lintLua by tasks.registering(IlluaminateExec::class) {
 | 
			
		||||
    group = JavaBasePlugin.VERIFICATION_GROUP
 | 
			
		||||
    description = "Lint Lua (and Lua docs) with illuaminate"
 | 
			
		||||
 | 
			
		||||
    // Config files
 | 
			
		||||
    inputs.file(rootProject.file("illuaminate.sexp")).withPropertyName("illuaminate.sexp")
 | 
			
		||||
    // Sources
 | 
			
		||||
    inputs.files(rootProject.fileTree("doc")).withPropertyName("docs")
 | 
			
		||||
    inputs.files(project(":core").fileTree("src/main/resources/data/computercraft/lua")).withPropertyName("lua rom")
 | 
			
		||||
    inputs.files(luaJavadoc)
 | 
			
		||||
 | 
			
		||||
    args = listOf("lint")
 | 
			
		||||
    workingDir = rootProject.projectDir
 | 
			
		||||
 | 
			
		||||
    doFirst { if (System.getenv("GITHUB_ACTIONS") != null) println("::add-matcher::.github/matchers/illuaminate.json") }
 | 
			
		||||
    doLast { if (System.getenv("GITHUB_ACTIONS") != null) println("::remove-matcher owner=illuaminate::") }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -17,8 +17,6 @@ import dan200.computercraft.client.render.monitor.MonitorRenderState;
 | 
			
		||||
import dan200.computercraft.client.sound.SpeakerManager;
 | 
			
		||||
import dan200.computercraft.shared.CommonHooks;
 | 
			
		||||
import dan200.computercraft.shared.ModRegistry;
 | 
			
		||||
import dan200.computercraft.shared.command.CommandComputerCraft;
 | 
			
		||||
import dan200.computercraft.shared.computer.core.ServerContext;
 | 
			
		||||
import dan200.computercraft.shared.media.items.PrintoutItem;
 | 
			
		||||
import dan200.computercraft.shared.peripheral.modem.wired.CableBlock;
 | 
			
		||||
import dan200.computercraft.shared.peripheral.modem.wired.CableModemVariant;
 | 
			
		||||
@@ -28,7 +26,6 @@ import dan200.computercraft.shared.pocket.items.PocketComputerItem;
 | 
			
		||||
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
 | 
			
		||||
import dan200.computercraft.shared.util.PauseAwareTimer;
 | 
			
		||||
import dan200.computercraft.shared.util.WorldUtil;
 | 
			
		||||
import net.minecraft.Util;
 | 
			
		||||
import net.minecraft.client.Camera;
 | 
			
		||||
import net.minecraft.client.Minecraft;
 | 
			
		||||
import net.minecraft.client.renderer.MultiBufferSource;
 | 
			
		||||
@@ -43,7 +40,6 @@ import net.minecraft.world.phys.BlockHitResult;
 | 
			
		||||
import net.minecraft.world.phys.HitResult;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -71,10 +67,6 @@ public final class ClientHooks {
 | 
			
		||||
        ClientPocketComputers.reset();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static boolean onChatMessage(String message) {
 | 
			
		||||
        return handleOpenComputerCommand(message);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static boolean drawHighlight(PoseStack transform, MultiBufferSource bufferSource, Camera camera, BlockHitResult hit) {
 | 
			
		||||
        return CableHighlightRenderer.drawHighlight(transform, bufferSource, camera, hit)
 | 
			
		||||
            || MonitorHighlightRenderer.drawHighlight(transform, bufferSource, camera, hit);
 | 
			
		||||
@@ -109,34 +101,6 @@ public final class ClientHooks {
 | 
			
		||||
        SpeakerManager.onPlayStreaming(engine, channel, stream);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handle the {@link CommandComputerCraft#OPEN_COMPUTER} "clientside command". This isn't a true command, as we
 | 
			
		||||
     * don't want it to actually be visible to the user.
 | 
			
		||||
     *
 | 
			
		||||
     * @param message The current chat message.
 | 
			
		||||
     * @return Whether to cancel sending this message.
 | 
			
		||||
     */
 | 
			
		||||
    private static boolean handleOpenComputerCommand(String message) {
 | 
			
		||||
        if (!message.startsWith(CommandComputerCraft.OPEN_COMPUTER)) return false;
 | 
			
		||||
 | 
			
		||||
        var server = Minecraft.getInstance().getSingleplayerServer();
 | 
			
		||||
        if (server == null) return false;
 | 
			
		||||
 | 
			
		||||
        var idStr = message.substring(CommandComputerCraft.OPEN_COMPUTER.length()).trim();
 | 
			
		||||
        int id;
 | 
			
		||||
        try {
 | 
			
		||||
            id = Integer.parseInt(idStr);
 | 
			
		||||
        } catch (NumberFormatException ignore) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var file = new File(ServerContext.get(server).storageDir().toFile(), "computer/" + id);
 | 
			
		||||
        if (!file.isDirectory()) return false;
 | 
			
		||||
 | 
			
		||||
        Util.getPlatform().openFile(file);
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add additional information about the currently targeted block to the debug screen.
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,12 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.client;
 | 
			
		||||
 | 
			
		||||
import com.mojang.brigadier.CommandDispatcher;
 | 
			
		||||
import com.mojang.brigadier.arguments.IntegerArgumentType;
 | 
			
		||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
 | 
			
		||||
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.api.client.ComputerCraftAPIClient;
 | 
			
		||||
import dan200.computercraft.api.client.turtle.RegisterTurtleUpgradeModeller;
 | 
			
		||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
 | 
			
		||||
import dan200.computercraft.client.gui.*;
 | 
			
		||||
import dan200.computercraft.client.pocket.ClientPocketComputers;
 | 
			
		||||
@@ -16,11 +20,14 @@ import dan200.computercraft.client.turtle.TurtleModemModeller;
 | 
			
		||||
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.ServerContext;
 | 
			
		||||
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
 | 
			
		||||
import dan200.computercraft.shared.computer.inventory.ViewComputerMenu;
 | 
			
		||||
import dan200.computercraft.shared.media.items.DiskItem;
 | 
			
		||||
import dan200.computercraft.shared.media.items.TreasureDiskItem;
 | 
			
		||||
import net.minecraft.Util;
 | 
			
		||||
import net.minecraft.client.Minecraft;
 | 
			
		||||
import net.minecraft.client.color.item.ItemColor;
 | 
			
		||||
import net.minecraft.client.gui.screens.MenuScreens;
 | 
			
		||||
@@ -30,6 +37,7 @@ import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
 | 
			
		||||
import net.minecraft.client.renderer.blockentity.BlockEntityRenderers;
 | 
			
		||||
import net.minecraft.client.renderer.item.ClampedItemPropertyFunction;
 | 
			
		||||
import net.minecraft.client.renderer.item.ItemProperties;
 | 
			
		||||
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;
 | 
			
		||||
@@ -39,6 +47,7 @@ import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.level.ItemLike;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.util.function.BiConsumer;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
@@ -60,18 +69,6 @@ public final class ClientRegistry {
 | 
			
		||||
     * Register any client-side objects which don't have to be done on the main thread.
 | 
			
		||||
     */
 | 
			
		||||
    public static void register() {
 | 
			
		||||
        ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.SPEAKER.get(), TurtleUpgradeModeller.sided(
 | 
			
		||||
            new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_left"),
 | 
			
		||||
            new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_right")
 | 
			
		||||
        ));
 | 
			
		||||
        ComputerCraftAPIClient.registerTurtleUpgradeModeller(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")
 | 
			
		||||
        ));
 | 
			
		||||
        ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.WIRELESS_MODEM_NORMAL.get(), new TurtleModemModeller(false));
 | 
			
		||||
        ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.WIRELESS_MODEM_ADVANCED.get(), new TurtleModemModeller(true));
 | 
			
		||||
        ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.TOOL.get(), TurtleUpgradeModeller.flatItem());
 | 
			
		||||
 | 
			
		||||
        BlockEntityRenderers.register(ModRegistry.BlockEntities.MONITOR_NORMAL.get(), MonitorBlockEntityRenderer::new);
 | 
			
		||||
        BlockEntityRenderers.register(ModRegistry.BlockEntities.MONITOR_ADVANCED.get(), MonitorBlockEntityRenderer::new);
 | 
			
		||||
        BlockEntityRenderers.register(ModRegistry.BlockEntities.TURTLE_NORMAL.get(), TurtleBlockEntityRenderer::new);
 | 
			
		||||
@@ -103,6 +100,20 @@ public final class ClientRegistry {
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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.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.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());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SafeVarargs
 | 
			
		||||
    private static void registerItemProperty(String name, ClampedItemPropertyFunction getter, Supplier<? extends Item>... items) {
 | 
			
		||||
        var id = new ResourceLocation(ComputerCraftAPI.MOD_ID, name);
 | 
			
		||||
@@ -179,4 +190,45 @@ public final class ClientRegistry {
 | 
			
		||||
            return function.unclampedCall(stack, level, entity, layer);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Register client-side commands.
 | 
			
		||||
     *
 | 
			
		||||
     * @param dispatcher The dispatcher to register the commands to.
 | 
			
		||||
     * @param sendError  A function to send an error message.
 | 
			
		||||
     * @param <T>        The type of the client-side command context.
 | 
			
		||||
     */
 | 
			
		||||
    public static <T> void registerClientCommands(CommandDispatcher<T> dispatcher, BiConsumer<T, Component> sendError) {
 | 
			
		||||
        dispatcher.register(LiteralArgumentBuilder.<T>literal(CommandComputerCraft.CLIENT_OPEN_FOLDER)
 | 
			
		||||
            .requires(x -> Minecraft.getInstance().getSingleplayerServer() != null)
 | 
			
		||||
            .then(RequiredArgumentBuilder.<T, Integer>argument("computer_id", IntegerArgumentType.integer(0))
 | 
			
		||||
                .executes(c -> handleOpenComputerCommand(c.getSource(), sendError, c.getArgument("computer_id", Integer.class)))
 | 
			
		||||
            ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handle the {@link CommandComputerCraft#CLIENT_OPEN_FOLDER} command.
 | 
			
		||||
     *
 | 
			
		||||
     * @param context   The command context.
 | 
			
		||||
     * @param sendError A function to send an error message.
 | 
			
		||||
     * @param id        The computer's id.
 | 
			
		||||
     * @param <T>       The type of the client-side command context.
 | 
			
		||||
     * @return {@code 1} if a folder was opened, {@code 0} otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    private static <T> int handleOpenComputerCommand(T context, BiConsumer<T, Component> sendError, int id) {
 | 
			
		||||
        var server = Minecraft.getInstance().getSingleplayerServer();
 | 
			
		||||
        if (server == null) {
 | 
			
		||||
            sendError.accept(context, Component.literal("Not on a single-player server"));
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var file = new File(ServerContext.get(server).storageDir().toFile(), "computer/" + id);
 | 
			
		||||
        if (!file.isDirectory()) {
 | 
			
		||||
            sendError.accept(context, Component.literal("Computer's folder does not exist"));
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Util.getPlatform().openFile(file);
 | 
			
		||||
        return 1;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,11 +4,10 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.client.gui;
 | 
			
		||||
 | 
			
		||||
import com.mojang.blaze3d.vertex.PoseStack;
 | 
			
		||||
import dan200.computercraft.client.gui.widgets.ComputerSidebar;
 | 
			
		||||
import dan200.computercraft.client.gui.widgets.DynamicImageButton;
 | 
			
		||||
import dan200.computercraft.client.gui.widgets.TerminalWidget;
 | 
			
		||||
import dan200.computercraft.client.platform.ClientPlatformHelper;
 | 
			
		||||
import dan200.computercraft.client.network.ClientNetworking;
 | 
			
		||||
import dan200.computercraft.core.terminal.Terminal;
 | 
			
		||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
 | 
			
		||||
import dan200.computercraft.shared.computer.core.InputHandler;
 | 
			
		||||
@@ -19,6 +18,8 @@ import dan200.computercraft.shared.config.Config;
 | 
			
		||||
import dan200.computercraft.shared.network.server.UploadFileMessage;
 | 
			
		||||
import net.minecraft.ChatFormatting;
 | 
			
		||||
import net.minecraft.Util;
 | 
			
		||||
import net.minecraft.client.gui.GuiGraphics;
 | 
			
		||||
import net.minecraft.client.gui.components.events.GuiEventListener;
 | 
			
		||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
import net.minecraft.world.entity.player.Inventory;
 | 
			
		||||
@@ -33,7 +34,6 @@ import java.nio.ByteBuffer;
 | 
			
		||||
import java.nio.file.Files;
 | 
			
		||||
import java.nio.file.Path;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
 | 
			
		||||
@@ -124,10 +124,10 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void render(PoseStack stack, int mouseX, int mouseY, float partialTicks) {
 | 
			
		||||
        renderBackground(stack);
 | 
			
		||||
        super.render(stack, mouseX, mouseY, partialTicks);
 | 
			
		||||
        renderTooltip(stack, mouseX, mouseY);
 | 
			
		||||
    public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
 | 
			
		||||
        renderBackground(graphics);
 | 
			
		||||
        super.render(graphics, mouseX, mouseY, partialTicks);
 | 
			
		||||
        renderTooltip(graphics, mouseX, mouseY);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
@@ -145,9 +145,14 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
 | 
			
		||||
            || super.mouseDragged(x, y, button, deltaX, deltaY);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setFocused(@Nullable GuiEventListener listener) {
 | 
			
		||||
        // Don't clear and re-focus if we're already focused.
 | 
			
		||||
        if (listener != getFocused()) super.setFocused(listener);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void renderLabels(PoseStack transform, int mouseX, int mouseY) {
 | 
			
		||||
    protected void renderLabels(GuiGraphics graphics, int mouseX, int mouseY) {
 | 
			
		||||
        // Skip rendering labels.
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -202,7 +207,7 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (toUpload.size() > 0) UploadFileMessage.send(menu, toUpload, ClientPlatformHelper.get()::sendToServer);
 | 
			
		||||
        if (toUpload.size() > 0) UploadFileMessage.send(menu, toUpload, ClientNetworking::sendToServer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void uploadResult(UploadResult result, @Nullable Component message) {
 | 
			
		||||
@@ -219,7 +224,7 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
 | 
			
		||||
 | 
			
		||||
    private void alert(Component title, Component message) {
 | 
			
		||||
        OptionScreen.show(minecraft, title, message,
 | 
			
		||||
            Collections.singletonList(OptionScreen.newButton(OK, b -> minecraft.setScreen(this))),
 | 
			
		||||
            List.of(OptionScreen.newButton(OK, b -> minecraft.setScreen(this))),
 | 
			
		||||
            () -> minecraft.setScreen(this)
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.client.gui;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.client.platform.ClientPlatformHelper;
 | 
			
		||||
import dan200.computercraft.client.network.ClientNetworking;
 | 
			
		||||
import dan200.computercraft.shared.computer.core.InputHandler;
 | 
			
		||||
import dan200.computercraft.shared.computer.menu.ComputerMenu;
 | 
			
		||||
import dan200.computercraft.shared.network.server.ComputerActionServerMessage;
 | 
			
		||||
@@ -29,51 +29,51 @@ public final class ClientInputHandler implements InputHandler {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void turnOn() {
 | 
			
		||||
        ClientPlatformHelper.get().sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.TURN_ON));
 | 
			
		||||
        ClientNetworking.sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.TURN_ON));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void shutdown() {
 | 
			
		||||
        ClientPlatformHelper.get().sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.SHUTDOWN));
 | 
			
		||||
        ClientNetworking.sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.SHUTDOWN));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void reboot() {
 | 
			
		||||
        ClientPlatformHelper.get().sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.REBOOT));
 | 
			
		||||
        ClientNetworking.sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.REBOOT));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void queueEvent(String event, @Nullable Object[] arguments) {
 | 
			
		||||
        ClientPlatformHelper.get().sendToServer(new QueueEventServerMessage(menu, event, arguments));
 | 
			
		||||
        ClientNetworking.sendToServer(new QueueEventServerMessage(menu, event, arguments));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void keyDown(int key, boolean repeat) {
 | 
			
		||||
        ClientPlatformHelper.get().sendToServer(new KeyEventServerMessage(menu, repeat ? KeyEventServerMessage.TYPE_REPEAT : KeyEventServerMessage.TYPE_DOWN, key));
 | 
			
		||||
        ClientNetworking.sendToServer(new KeyEventServerMessage(menu, repeat ? KeyEventServerMessage.TYPE_REPEAT : KeyEventServerMessage.TYPE_DOWN, key));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void keyUp(int key) {
 | 
			
		||||
        ClientPlatformHelper.get().sendToServer(new KeyEventServerMessage(menu, KeyEventServerMessage.TYPE_UP, key));
 | 
			
		||||
        ClientNetworking.sendToServer(new KeyEventServerMessage(menu, KeyEventServerMessage.TYPE_UP, key));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void mouseClick(int button, int x, int y) {
 | 
			
		||||
        ClientPlatformHelper.get().sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_CLICK, button, x, y));
 | 
			
		||||
        ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_CLICK, button, x, y));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void mouseUp(int button, int x, int y) {
 | 
			
		||||
        ClientPlatformHelper.get().sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_UP, button, x, y));
 | 
			
		||||
        ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_UP, button, x, y));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void mouseDrag(int button, int x, int y) {
 | 
			
		||||
        ClientPlatformHelper.get().sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_DRAG, button, x, y));
 | 
			
		||||
        ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_DRAG, button, x, y));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void mouseScroll(int direction, int x, int y) {
 | 
			
		||||
        ClientPlatformHelper.get().sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_SCROLL, direction, x, y));
 | 
			
		||||
        ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_SCROLL, direction, x, y));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,15 +4,13 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.client.gui;
 | 
			
		||||
 | 
			
		||||
import com.mojang.blaze3d.vertex.PoseStack;
 | 
			
		||||
import com.mojang.blaze3d.vertex.Tesselator;
 | 
			
		||||
import dan200.computercraft.client.gui.widgets.ComputerSidebar;
 | 
			
		||||
import dan200.computercraft.client.gui.widgets.TerminalWidget;
 | 
			
		||||
import dan200.computercraft.client.render.ComputerBorderRenderer;
 | 
			
		||||
import dan200.computercraft.client.render.RenderTypes;
 | 
			
		||||
import dan200.computercraft.client.render.SpriteRenderer;
 | 
			
		||||
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
 | 
			
		||||
import net.minecraft.client.renderer.MultiBufferSource;
 | 
			
		||||
import net.minecraft.client.gui.GuiGraphics;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
import net.minecraft.world.entity.player.Inventory;
 | 
			
		||||
 | 
			
		||||
@@ -39,12 +37,10 @@ public final class ComputerScreen<T extends AbstractComputerMenu> extends Abstra
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void renderBg(PoseStack stack, float partialTicks, int mouseX, int mouseY) {
 | 
			
		||||
    public void renderBg(GuiGraphics graphics, float partialTicks, int mouseX, int mouseY) {
 | 
			
		||||
        // Draw a border around the terminal
 | 
			
		||||
        var terminal = getTerminal();
 | 
			
		||||
        var buffers = MultiBufferSource.immediate(Tesselator.getInstance().getBuilder());
 | 
			
		||||
 | 
			
		||||
        var spriteRenderer = SpriteRenderer.createForGui(stack, buffers.getBuffer(RenderTypes.GUI_SPRITES));
 | 
			
		||||
        var spriteRenderer = SpriteRenderer.createForGui(graphics, RenderTypes.GUI_SPRITES);
 | 
			
		||||
        var computerTextures = GuiSprites.getComputerTextures(family);
 | 
			
		||||
 | 
			
		||||
        ComputerBorderRenderer.render(
 | 
			
		||||
@@ -52,6 +48,6 @@ public final class ComputerScreen<T extends AbstractComputerMenu> extends Abstra
 | 
			
		||||
            terminal.getX(), terminal.getY(), terminal.getWidth(), terminal.getHeight(), false
 | 
			
		||||
        );
 | 
			
		||||
        ComputerSidebar.renderBackground(spriteRenderer, computerTextures, leftPos, topPos + sidebarYOffset);
 | 
			
		||||
        buffers.endBatch();
 | 
			
		||||
        graphics.flush(); // Flush to ensure background textures are drawn before foreground.
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,9 +4,8 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.client.gui;
 | 
			
		||||
 | 
			
		||||
import com.mojang.blaze3d.systems.RenderSystem;
 | 
			
		||||
import com.mojang.blaze3d.vertex.PoseStack;
 | 
			
		||||
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveMenu;
 | 
			
		||||
import net.minecraft.client.gui.GuiGraphics;
 | 
			
		||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
@@ -23,16 +22,14 @@ public class DiskDriveScreen extends AbstractContainerScreen<DiskDriveMenu> {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void renderBg(PoseStack transform, float partialTicks, int mouseX, int mouseY) {
 | 
			
		||||
        RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
 | 
			
		||||
        RenderSystem.setShaderTexture(0, BACKGROUND);
 | 
			
		||||
        blit(transform, leftPos, topPos, 0, 0, imageWidth, imageHeight);
 | 
			
		||||
    protected void renderBg(GuiGraphics graphics, float partialTicks, int mouseX, int mouseY) {
 | 
			
		||||
        graphics.blit(BACKGROUND, leftPos, topPos, 0, 0, imageWidth, imageHeight);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void render(PoseStack transform, int mouseX, int mouseY, float partialTicks) {
 | 
			
		||||
        renderBackground(transform);
 | 
			
		||||
        super.render(transform, mouseX, mouseY, partialTicks);
 | 
			
		||||
        renderTooltip(transform, mouseX, mouseY);
 | 
			
		||||
    public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
 | 
			
		||||
        renderBackground(graphics);
 | 
			
		||||
        super.render(graphics, mouseX, mouseY, partialTicks);
 | 
			
		||||
        renderTooltip(graphics, mouseX, mouseY);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,10 +4,8 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.client.gui;
 | 
			
		||||
 | 
			
		||||
import com.mojang.blaze3d.systems.RenderSystem;
 | 
			
		||||
import com.mojang.blaze3d.vertex.PoseStack;
 | 
			
		||||
import net.minecraft.client.Minecraft;
 | 
			
		||||
import net.minecraft.client.gui.GuiComponent;
 | 
			
		||||
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;
 | 
			
		||||
@@ -73,55 +71,52 @@ public class ItemToast implements Toast {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Visibility render(PoseStack transform, ToastComponent component, long time) {
 | 
			
		||||
    public Visibility render(GuiGraphics graphics, ToastComponent component, long time) {
 | 
			
		||||
        if (isNew) {
 | 
			
		||||
 | 
			
		||||
            firstDisplay = time;
 | 
			
		||||
            isNew = false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        RenderSystem.setShaderTexture(0, TEXTURE);
 | 
			
		||||
        RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
 | 
			
		||||
 | 
			
		||||
        if (width == 160 && message.size() <= 1) {
 | 
			
		||||
            GuiComponent.blit(transform, 0, 0, 0, 64, width, height());
 | 
			
		||||
            graphics.blit(TEXTURE, 0, 0, 0, 64, width, height());
 | 
			
		||||
        } else {
 | 
			
		||||
 | 
			
		||||
            var height = height();
 | 
			
		||||
 | 
			
		||||
            var bottom = Math.min(4, height - 28);
 | 
			
		||||
            renderBackgroundRow(transform, component, width, 0, 0, 28);
 | 
			
		||||
            renderBackgroundRow(graphics, width, 0, 0, 28);
 | 
			
		||||
 | 
			
		||||
            for (var i = 28; i < height - bottom; i += 10) {
 | 
			
		||||
                renderBackgroundRow(transform, component, width, 16, i, Math.min(16, height - i - bottom));
 | 
			
		||||
                renderBackgroundRow(graphics, width, 16, i, Math.min(16, height - i - bottom));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            renderBackgroundRow(transform, component, width, 32 - bottom, height - bottom, bottom);
 | 
			
		||||
            renderBackgroundRow(graphics, width, 32 - bottom, height - bottom, bottom);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var textX = MARGIN;
 | 
			
		||||
        if (!stack.isEmpty()) {
 | 
			
		||||
            textX += MARGIN + IMAGE_SIZE;
 | 
			
		||||
            component.getMinecraft().getItemRenderer().renderAndDecorateFakeItem(transform, stack, MARGIN, MARGIN + height() / 2 - IMAGE_SIZE);
 | 
			
		||||
            graphics.renderFakeItem(stack, MARGIN, MARGIN + height() / 2 - IMAGE_SIZE);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        component.getMinecraft().font.draw(transform, title, textX, MARGIN, 0xff500050);
 | 
			
		||||
        graphics.drawString(component.getMinecraft().font, title, textX, MARGIN, 0xff500050, false);
 | 
			
		||||
        for (var i = 0; i < message.size(); ++i) {
 | 
			
		||||
            component.getMinecraft().font.draw(transform, message.get(i), textX, (float) (LINE_SPACING + (i + 1) * LINE_SPACING), 0xff000000);
 | 
			
		||||
            graphics.drawString(component.getMinecraft().font, message.get(i), textX, LINE_SPACING + (i + 1) * LINE_SPACING, 0xff000000, false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return time - firstDisplay < DISPLAY_TIME ? Visibility.SHOW : Visibility.HIDE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void renderBackgroundRow(PoseStack transform, ToastComponent component, int x, int u, int y, int height) {
 | 
			
		||||
    private static void renderBackgroundRow(GuiGraphics graphics, int x, int u, int y, int height) {
 | 
			
		||||
        var leftOffset = 5;
 | 
			
		||||
        var rightOffset = Math.min(60, x - leftOffset);
 | 
			
		||||
 | 
			
		||||
        GuiComponent.blit(transform, 0, y, 0, 32 + u, leftOffset, height);
 | 
			
		||||
        graphics.blit(TEXTURE, 0, y, 0, 32 + u, leftOffset, height);
 | 
			
		||||
        for (var k = leftOffset; k < x - rightOffset; k += 64) {
 | 
			
		||||
            GuiComponent.blit(transform, k, y, 32, 32 + u, Math.min(64, x - k - rightOffset), height);
 | 
			
		||||
            graphics.blit(TEXTURE, k, y, 32, 32 + u, Math.min(64, x - k - rightOffset), height);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        GuiComponent.blit(transform, x - rightOffset, y, 160 - rightOffset, 32 + u, rightOffset, height);
 | 
			
		||||
        graphics.blit(TEXTURE, x - rightOffset, y, 160 - rightOffset, 32 + u, rightOffset, height);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,14 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: LicenseRef-CCPL
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.client.gui;
 | 
			
		||||
 | 
			
		||||
import com.mojang.blaze3d.vertex.PoseStack;
 | 
			
		||||
import dan200.computercraft.client.gui.widgets.TerminalWidget;
 | 
			
		||||
import dan200.computercraft.core.terminal.Terminal;
 | 
			
		||||
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
 | 
			
		||||
import net.minecraft.client.KeyMapping;
 | 
			
		||||
import net.minecraft.client.gui.GuiGraphics;
 | 
			
		||||
import net.minecraft.client.gui.screens.Screen;
 | 
			
		||||
import net.minecraft.client.gui.screens.inventory.MenuAccess;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
@@ -42,7 +42,6 @@ public class NoTermComputerScreen<T extends AbstractComputerMenu> extends Screen
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void init() {
 | 
			
		||||
        passEvents = true; // Pass mouse vents through to the game's mouse handler.
 | 
			
		||||
        // First ensure we're still grabbing the mouse, so the user can look around. Then reset bits of state that
 | 
			
		||||
        // grabbing unsets.
 | 
			
		||||
        minecraft.mouseHandler.grabMouse();
 | 
			
		||||
@@ -91,15 +90,15 @@ public class NoTermComputerScreen<T extends AbstractComputerMenu> extends Screen
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void render(PoseStack transform, int mouseX, int mouseY, float partialTicks) {
 | 
			
		||||
        super.render(transform, mouseX, mouseY, partialTicks);
 | 
			
		||||
    public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
 | 
			
		||||
        super.render(graphics, mouseX, mouseY, partialTicks);
 | 
			
		||||
 | 
			
		||||
        var font = minecraft.font;
 | 
			
		||||
        var lines = font.split(Component.translatable("gui.computercraft.pocket_computer_overlay"), (int) (width * 0.8));
 | 
			
		||||
        var y = 10.0f;
 | 
			
		||||
        var y = 10;
 | 
			
		||||
        for (var line : lines) {
 | 
			
		||||
            font.drawShadow(transform, line, (float) ((width / 2) - (minecraft.font.width(line) / 2)), y, 0xFFFFFF);
 | 
			
		||||
            y += 9.0f;
 | 
			
		||||
            graphics.drawString(font, line, (width / 2) - (minecraft.font.width(line) / 2), y, 0xFFFFFF, true);
 | 
			
		||||
            y += 9;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,9 +4,8 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.client.gui;
 | 
			
		||||
 | 
			
		||||
import com.mojang.blaze3d.systems.RenderSystem;
 | 
			
		||||
import com.mojang.blaze3d.vertex.PoseStack;
 | 
			
		||||
import net.minecraft.client.Minecraft;
 | 
			
		||||
import net.minecraft.client.gui.GuiGraphics;
 | 
			
		||||
import net.minecraft.client.gui.components.AbstractWidget;
 | 
			
		||||
import net.minecraft.client.gui.components.Button;
 | 
			
		||||
import net.minecraft.client.gui.components.MultiLineLabel;
 | 
			
		||||
@@ -86,20 +85,19 @@ public final class OptionScreen extends Screen {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void render(PoseStack transform, int mouseX, int mouseY, float partialTicks) {
 | 
			
		||||
        renderBackground(transform);
 | 
			
		||||
    public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
 | 
			
		||||
        renderBackground(graphics);
 | 
			
		||||
 | 
			
		||||
        // Render the actual texture.
 | 
			
		||||
        RenderSystem.setShaderTexture(0, BACKGROUND);
 | 
			
		||||
        blit(transform, x, y, 0, 0, innerWidth, PADDING);
 | 
			
		||||
        blit(transform,
 | 
			
		||||
        graphics.blit(BACKGROUND, x, y, 0, 0, innerWidth, PADDING);
 | 
			
		||||
        graphics.blit(BACKGROUND,
 | 
			
		||||
            x, y + PADDING, 0, PADDING, innerWidth, innerHeight - PADDING * 2,
 | 
			
		||||
            innerWidth, PADDING
 | 
			
		||||
        );
 | 
			
		||||
        blit(transform, x, y + innerHeight - PADDING, 0, 256 - PADDING, innerWidth, PADDING);
 | 
			
		||||
        graphics.blit(BACKGROUND, x, y + innerHeight - PADDING, 0, 256 - PADDING, innerWidth, PADDING);
 | 
			
		||||
 | 
			
		||||
        assertNonNull(messageRenderer).renderLeftAlignedNoShadow(transform, x + PADDING, y + PADDING, FONT_HEIGHT, 0x404040);
 | 
			
		||||
        super.render(transform, mouseX, mouseY, partialTicks);
 | 
			
		||||
        assertNonNull(messageRenderer).renderLeftAlignedNoShadow(graphics, x + PADDING, y + PADDING, FONT_HEIGHT, 0x404040);
 | 
			
		||||
        super.render(graphics, mouseX, mouseY, partialTicks);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
 
 | 
			
		||||
@@ -4,9 +4,8 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.client.gui;
 | 
			
		||||
 | 
			
		||||
import com.mojang.blaze3d.systems.RenderSystem;
 | 
			
		||||
import com.mojang.blaze3d.vertex.PoseStack;
 | 
			
		||||
import dan200.computercraft.shared.peripheral.printer.PrinterMenu;
 | 
			
		||||
import net.minecraft.client.gui.GuiGraphics;
 | 
			
		||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
@@ -23,18 +22,16 @@ public class PrinterScreen extends AbstractContainerScreen<PrinterMenu> {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void renderBg(PoseStack transform, float partialTicks, int mouseX, int mouseY) {
 | 
			
		||||
        RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
 | 
			
		||||
        RenderSystem.setShaderTexture(0, BACKGROUND);
 | 
			
		||||
        blit(transform, leftPos, topPos, 0, 0, imageWidth, imageHeight);
 | 
			
		||||
    protected void renderBg(GuiGraphics graphics, float partialTicks, int mouseX, int mouseY) {
 | 
			
		||||
        graphics.blit(BACKGROUND, leftPos, topPos, 0, 0, imageWidth, imageHeight);
 | 
			
		||||
 | 
			
		||||
        if (getMenu().isPrinting()) blit(transform, leftPos + 34, topPos + 21, 176, 0, 25, 45);
 | 
			
		||||
        if (getMenu().isPrinting()) graphics.blit(BACKGROUND, leftPos + 34, topPos + 21, 176, 0, 25, 45);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void render(PoseStack stack, int mouseX, int mouseY, float partialTicks) {
 | 
			
		||||
        renderBackground(stack);
 | 
			
		||||
        super.render(stack, mouseX, mouseY, partialTicks);
 | 
			
		||||
        renderTooltip(stack, mouseX, mouseY);
 | 
			
		||||
    public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
 | 
			
		||||
        renderBackground(graphics);
 | 
			
		||||
        super.render(graphics, mouseX, mouseY, partialTicks);
 | 
			
		||||
        renderTooltip(graphics, mouseX, mouseY);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,12 +4,11 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.client.gui;
 | 
			
		||||
 | 
			
		||||
import com.mojang.blaze3d.systems.RenderSystem;
 | 
			
		||||
import com.mojang.blaze3d.vertex.PoseStack;
 | 
			
		||||
import com.mojang.blaze3d.vertex.Tesselator;
 | 
			
		||||
import dan200.computercraft.core.terminal.TextBuffer;
 | 
			
		||||
import dan200.computercraft.shared.common.HeldItemMenu;
 | 
			
		||||
import dan200.computercraft.shared.media.items.PrintoutItem;
 | 
			
		||||
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;
 | 
			
		||||
@@ -83,30 +82,27 @@ public class PrintoutScreen extends AbstractContainerScreen<HeldItemMenu> {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void renderBg(PoseStack transform, float partialTicks, int mouseX, int mouseY) {
 | 
			
		||||
    protected void renderBg(GuiGraphics graphics, float partialTicks, int mouseX, int mouseY) {
 | 
			
		||||
        // Draw the printout
 | 
			
		||||
        RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f);
 | 
			
		||||
        RenderSystem.enableDepthTest();
 | 
			
		||||
 | 
			
		||||
        var renderer = MultiBufferSource.immediate(Tesselator.getInstance().getBuilder());
 | 
			
		||||
        drawBorder(transform, renderer, leftPos, topPos, 0, page, pages, book, FULL_BRIGHT_LIGHTMAP);
 | 
			
		||||
        drawText(transform, renderer, leftPos + X_TEXT_MARGIN, topPos + Y_TEXT_MARGIN, PrintoutItem.LINES_PER_PAGE * page, FULL_BRIGHT_LIGHTMAP, text, colours);
 | 
			
		||||
        drawBorder(graphics.pose(), renderer, leftPos, topPos, 0, page, pages, book, FULL_BRIGHT_LIGHTMAP);
 | 
			
		||||
        drawText(graphics.pose(), renderer, leftPos + X_TEXT_MARGIN, topPos + Y_TEXT_MARGIN, PrintoutItem.LINES_PER_PAGE * page, FULL_BRIGHT_LIGHTMAP, text, colours);
 | 
			
		||||
        renderer.endBatch();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void render(PoseStack stack, int mouseX, int mouseY, float partialTicks) {
 | 
			
		||||
    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.
 | 
			
		||||
        stack.pushPose();
 | 
			
		||||
        stack.translate(0, 0, -1);
 | 
			
		||||
        renderBackground(stack);
 | 
			
		||||
        stack.popPose();
 | 
			
		||||
        graphics.pose().pushPose();
 | 
			
		||||
        graphics.pose().translate(0, 0, -1);
 | 
			
		||||
        renderBackground(graphics);
 | 
			
		||||
        graphics.pose().popPose();
 | 
			
		||||
 | 
			
		||||
        super.render(stack, mouseX, mouseY, partialTicks);
 | 
			
		||||
        super.render(graphics, mouseX, mouseY, partialTicks);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void renderLabels(PoseStack transform, int mouseX, int mouseY) {
 | 
			
		||||
    protected void renderLabels(GuiGraphics graphics, int mouseX, int mouseY) {
 | 
			
		||||
        // Skip rendering labels.
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,9 +4,6 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.client.gui;
 | 
			
		||||
 | 
			
		||||
import com.mojang.blaze3d.systems.RenderSystem;
 | 
			
		||||
import com.mojang.blaze3d.vertex.PoseStack;
 | 
			
		||||
import com.mojang.blaze3d.vertex.Tesselator;
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.client.gui.widgets.ComputerSidebar;
 | 
			
		||||
import dan200.computercraft.client.gui.widgets.TerminalWidget;
 | 
			
		||||
@@ -15,7 +12,7 @@ import dan200.computercraft.client.render.SpriteRenderer;
 | 
			
		||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
 | 
			
		||||
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
 | 
			
		||||
import dan200.computercraft.shared.turtle.inventory.TurtleMenu;
 | 
			
		||||
import net.minecraft.client.renderer.MultiBufferSource;
 | 
			
		||||
import net.minecraft.client.gui.GuiGraphics;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.entity.player.Inventory;
 | 
			
		||||
@@ -47,26 +44,25 @@ public class TurtleScreen extends AbstractComputerScreen<TurtleMenu> {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void renderBg(PoseStack transform, float partialTicks, int mouseX, int mouseY) {
 | 
			
		||||
    protected void renderBg(GuiGraphics graphics, float partialTicks, int mouseX, int mouseY) {
 | 
			
		||||
        var advanced = family == ComputerFamily.ADVANCED;
 | 
			
		||||
        RenderSystem.setShaderTexture(0, advanced ? BACKGROUND_ADVANCED : BACKGROUND_NORMAL);
 | 
			
		||||
        blit(transform, leftPos + AbstractComputerMenu.SIDEBAR_WIDTH, topPos, 0, 0, 0, TEX_WIDTH, TEX_HEIGHT, FULL_TEX_SIZE, FULL_TEX_SIZE);
 | 
			
		||||
        var texture = advanced ? BACKGROUND_ADVANCED : BACKGROUND_NORMAL;
 | 
			
		||||
        graphics.blit(texture, leftPos + AbstractComputerMenu.SIDEBAR_WIDTH, topPos, 0, 0, 0, TEX_WIDTH, TEX_HEIGHT, FULL_TEX_SIZE, FULL_TEX_SIZE);
 | 
			
		||||
 | 
			
		||||
        // Render selected slot
 | 
			
		||||
        var slot = getMenu().getSelectedSlot();
 | 
			
		||||
        if (slot >= 0) {
 | 
			
		||||
            var slotX = slot % 4;
 | 
			
		||||
            var slotY = slot / 4;
 | 
			
		||||
            blit(transform,
 | 
			
		||||
            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
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Render sidebar
 | 
			
		||||
        var buffers = MultiBufferSource.immediate(Tesselator.getInstance().getBuilder());
 | 
			
		||||
        var spriteRenderer = SpriteRenderer.createForGui(transform, buffers.getBuffer(RenderTypes.GUI_SPRITES));
 | 
			
		||||
        var spriteRenderer = SpriteRenderer.createForGui(graphics, RenderTypes.GUI_SPRITES);
 | 
			
		||||
        ComputerSidebar.renderBackground(spriteRenderer, GuiSprites.getComputerTextures(family), leftPos, topPos + sidebarYOffset);
 | 
			
		||||
        buffers.endBatch();
 | 
			
		||||
        graphics.flush(); // Flush to ensure background textures are drawn before foreground.
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,9 +5,9 @@
 | 
			
		||||
package dan200.computercraft.client.gui.widgets;
 | 
			
		||||
 | 
			
		||||
import com.mojang.blaze3d.systems.RenderSystem;
 | 
			
		||||
import com.mojang.blaze3d.vertex.PoseStack;
 | 
			
		||||
import it.unimi.dsi.fastutil.booleans.Boolean2ObjectFunction;
 | 
			
		||||
import net.minecraft.ChatFormatting;
 | 
			
		||||
import net.minecraft.client.gui.GuiGraphics;
 | 
			
		||||
import net.minecraft.client.gui.components.Button;
 | 
			
		||||
import net.minecraft.client.gui.components.Tooltip;
 | 
			
		||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
 | 
			
		||||
@@ -42,21 +42,20 @@ public class DynamicImageButton extends Button {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void renderWidget(PoseStack stack, int mouseX, int mouseY, float partialTicks) {
 | 
			
		||||
    public void renderWidget(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
 | 
			
		||||
        var texture = this.texture.get(isHoveredOrFocused());
 | 
			
		||||
        RenderSystem.setShaderTexture(0, texture.atlasLocation());
 | 
			
		||||
        RenderSystem.disableDepthTest();
 | 
			
		||||
 | 
			
		||||
        blit(stack, getX(), getY(), 0, width, height, texture);
 | 
			
		||||
        RenderSystem.disableDepthTest();
 | 
			
		||||
        graphics.blit(getX(), getY(), 0, width, height, texture);
 | 
			
		||||
        RenderSystem.enableDepthTest();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void render(PoseStack stack, int mouseX, int mouseY, float partialTicks) {
 | 
			
		||||
    public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
 | 
			
		||||
        var message = this.message.get();
 | 
			
		||||
        setMessage(message.message());
 | 
			
		||||
        setTooltip(message.tooltip());
 | 
			
		||||
        super.render(stack, mouseX, mouseY, partialTicks);
 | 
			
		||||
        super.render(graphics, mouseX, mouseY, partialTicks);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public record HintedMessage(Component message, Tooltip tooltip) {
 | 
			
		||||
 
 | 
			
		||||
@@ -4,14 +4,14 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.client.gui.widgets;
 | 
			
		||||
 | 
			
		||||
import com.mojang.blaze3d.vertex.PoseStack;
 | 
			
		||||
import com.mojang.blaze3d.vertex.Tesselator;
 | 
			
		||||
import dan200.computercraft.client.render.RenderTypes;
 | 
			
		||||
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
 | 
			
		||||
import dan200.computercraft.core.terminal.Terminal;
 | 
			
		||||
import dan200.computercraft.core.util.StringUtil;
 | 
			
		||||
import dan200.computercraft.shared.computer.core.InputHandler;
 | 
			
		||||
import net.minecraft.SharedConstants;
 | 
			
		||||
import net.minecraft.client.Minecraft;
 | 
			
		||||
import net.minecraft.client.gui.GuiGraphics;
 | 
			
		||||
import net.minecraft.client.gui.components.AbstractWidget;
 | 
			
		||||
import net.minecraft.client.gui.narration.NarratedElementType;
 | 
			
		||||
import net.minecraft.client.gui.narration.NarrationElementOutput;
 | 
			
		||||
@@ -112,26 +112,8 @@ public class TerminalWidget extends AbstractWidget {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void paste() {
 | 
			
		||||
        var clipboard = Minecraft.getInstance().keyboardHandler.getClipboard();
 | 
			
		||||
 | 
			
		||||
        // Clip to the first occurrence of \r or \n
 | 
			
		||||
        var newLineIndex1 = clipboard.indexOf('\r');
 | 
			
		||||
        var newLineIndex2 = clipboard.indexOf('\n');
 | 
			
		||||
        if (newLineIndex1 >= 0 && newLineIndex2 >= 0) {
 | 
			
		||||
            clipboard = clipboard.substring(0, Math.min(newLineIndex1, newLineIndex2));
 | 
			
		||||
        } else if (newLineIndex1 >= 0) {
 | 
			
		||||
            clipboard = clipboard.substring(0, newLineIndex1);
 | 
			
		||||
        } else if (newLineIndex2 >= 0) {
 | 
			
		||||
            clipboard = clipboard.substring(0, newLineIndex2);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Filter the string
 | 
			
		||||
        clipboard = SharedConstants.filterText(clipboard);
 | 
			
		||||
        if (!clipboard.isEmpty()) {
 | 
			
		||||
            // Clip to 512 characters and queue the event
 | 
			
		||||
            if (clipboard.length() > 512) clipboard = clipboard.substring(0, 512);
 | 
			
		||||
            computer.queueEvent("paste", new Object[]{ clipboard });
 | 
			
		||||
        }
 | 
			
		||||
        var clipboard = StringUtil.normaliseClipboardString(Minecraft.getInstance().keyboardHandler.getClipboard());
 | 
			
		||||
        if (!clipboard.isEmpty()) computer.queueEvent("paste", new Object[]{ clipboard });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
@@ -264,7 +246,7 @@ public class TerminalWidget extends AbstractWidget {
 | 
			
		||||
            keysDown.clear();
 | 
			
		||||
 | 
			
		||||
            // When blurring, we should make the last mouse button go up
 | 
			
		||||
            if (lastMouseButton > 0) {
 | 
			
		||||
            if (lastMouseButton >= 0) {
 | 
			
		||||
                computer.mouseUp(lastMouseButton + 1, lastMouseX + 1, lastMouseY + 1);
 | 
			
		||||
                lastMouseButton = -1;
 | 
			
		||||
            }
 | 
			
		||||
@@ -274,11 +256,11 @@ public class TerminalWidget extends AbstractWidget {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void renderWidget(PoseStack transform, int mouseX, int mouseY, float partialTicks) {
 | 
			
		||||
    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(transform, bufferSource.getBuffer(RenderTypes.TERMINAL));
 | 
			
		||||
        var emitter = FixedWidthFontRenderer.toVertexConsumer(graphics.pose(), bufferSource.getBuffer(RenderTypes.TERMINAL));
 | 
			
		||||
 | 
			
		||||
        FixedWidthFontRenderer.drawTerminal(
 | 
			
		||||
            emitter,
 | 
			
		||||
 
 | 
			
		||||
@@ -47,7 +47,7 @@ public class ShaderMod {
 | 
			
		||||
        Optional<ShaderMod> get();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static class Storage {
 | 
			
		||||
    private static final class Storage {
 | 
			
		||||
        static final ShaderMod INSTANCE = ServiceLoader.load(Provider.class)
 | 
			
		||||
            .stream()
 | 
			
		||||
            .flatMap(x -> x.get().get().stream())
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,28 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.client.network;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.client.platform.ClientPlatformHelper;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessage;
 | 
			
		||||
import dan200.computercraft.shared.network.server.ServerNetworkContext;
 | 
			
		||||
import net.minecraft.client.Minecraft;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Methods for sending packets from clients to the server.
 | 
			
		||||
 */
 | 
			
		||||
public final class ClientNetworking {
 | 
			
		||||
    private ClientNetworking() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Send a network message to the server.
 | 
			
		||||
     *
 | 
			
		||||
     * @param message The message to send.
 | 
			
		||||
     */
 | 
			
		||||
    public static void sendToServer(NetworkMessage<ServerNetworkContext> message) {
 | 
			
		||||
        var connection = Minecraft.getInstance().getConnection();
 | 
			
		||||
        if (connection != null) connection.send(ClientPlatformHelper.get().createPacket(message));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -4,6 +4,7 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.client.platform;
 | 
			
		||||
 | 
			
		||||
import com.google.auto.service.AutoService;
 | 
			
		||||
import dan200.computercraft.client.ClientTableFormatter;
 | 
			
		||||
import dan200.computercraft.client.gui.AbstractComputerScreen;
 | 
			
		||||
import dan200.computercraft.client.gui.OptionScreen;
 | 
			
		||||
@@ -17,30 +18,30 @@ import dan200.computercraft.shared.computer.upload.UploadResult;
 | 
			
		||||
import dan200.computercraft.shared.network.client.ClientNetworkContext;
 | 
			
		||||
import dan200.computercraft.shared.peripheral.monitor.MonitorBlockEntity;
 | 
			
		||||
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
 | 
			
		||||
import io.netty.buffer.ByteBuf;
 | 
			
		||||
import net.minecraft.client.Minecraft;
 | 
			
		||||
import net.minecraft.core.BlockPos;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.sounds.SoundEvent;
 | 
			
		||||
import net.minecraft.world.entity.player.Player;
 | 
			
		||||
import net.minecraft.world.level.Level;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.nio.ByteBuffer;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The base implementation of {@link ClientNetworkContext}.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * This should be extended by mod loader specific modules with the remaining abstract methods.
 | 
			
		||||
 * The client-side implementation of {@link ClientNetworkContext}.
 | 
			
		||||
 */
 | 
			
		||||
public abstract class AbstractClientNetworkContext implements ClientNetworkContext {
 | 
			
		||||
@AutoService(ClientNetworkContext.class)
 | 
			
		||||
public final class ClientNetworkContextImpl implements ClientNetworkContext {
 | 
			
		||||
    @Override
 | 
			
		||||
    public final void handleChatTable(TableBuilder table) {
 | 
			
		||||
    public void handleChatTable(TableBuilder table) {
 | 
			
		||||
        ClientTableFormatter.INSTANCE.display(table);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final void handleComputerTerminal(int containerId, TerminalState terminal) {
 | 
			
		||||
    public void handleComputerTerminal(int containerId, TerminalState terminal) {
 | 
			
		||||
        Player player = Minecraft.getInstance().player;
 | 
			
		||||
        if (player != null && player.containerMenu.containerId == containerId && player.containerMenu instanceof ComputerMenu menu) {
 | 
			
		||||
            menu.updateTerminal(terminal);
 | 
			
		||||
@@ -48,55 +49,57 @@ public abstract class AbstractClientNetworkContext implements ClientNetworkConte
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final void handleMonitorData(BlockPos pos, TerminalState terminal) {
 | 
			
		||||
    public void handleMonitorData(BlockPos pos, TerminalState terminal) {
 | 
			
		||||
        var player = Minecraft.getInstance().player;
 | 
			
		||||
        if (player == null) return;
 | 
			
		||||
 | 
			
		||||
        var te = player.level.getBlockEntity(pos);
 | 
			
		||||
        var te = player.level().getBlockEntity(pos);
 | 
			
		||||
        if (!(te instanceof MonitorBlockEntity monitor)) return;
 | 
			
		||||
 | 
			
		||||
        monitor.read(terminal);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final void handlePocketComputerData(int instanceId, ComputerState state, int lightState, TerminalState terminal) {
 | 
			
		||||
    public void handlePlayRecord(BlockPos pos, @Nullable SoundEvent sound, @Nullable String name) {
 | 
			
		||||
        var mc = Minecraft.getInstance();
 | 
			
		||||
        ClientPlatformHelper.get().playStreamingMusic(pos, sound);
 | 
			
		||||
        if (name != null) mc.gui.setNowPlaying(Component.literal(name));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void handlePocketComputerData(int instanceId, ComputerState state, int lightState, TerminalState terminal) {
 | 
			
		||||
        var computer = ClientPocketComputers.get(instanceId, terminal.colour);
 | 
			
		||||
        computer.setState(state, lightState);
 | 
			
		||||
        if (terminal.hasTerminal()) computer.setTerminal(terminal);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final void handlePocketComputerDeleted(int instanceId) {
 | 
			
		||||
    public void handlePocketComputerDeleted(int instanceId) {
 | 
			
		||||
        ClientPocketComputers.remove(instanceId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final void handleSpeakerAudio(UUID source, SpeakerPosition.Message position, float volume) {
 | 
			
		||||
        SpeakerManager.getSound(source).playAudio(reifyPosition(position), volume);
 | 
			
		||||
    public void handleSpeakerAudio(UUID source, SpeakerPosition.Message position, float volume, ByteBuffer buffer) {
 | 
			
		||||
        SpeakerManager.getSound(source).playAudio(reifyPosition(position), volume, buffer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final void handleSpeakerAudioPush(UUID source, ByteBuf buffer) {
 | 
			
		||||
        SpeakerManager.getSound(source).pushAudio(buffer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final void handleSpeakerMove(UUID source, SpeakerPosition.Message position) {
 | 
			
		||||
    public void handleSpeakerMove(UUID source, SpeakerPosition.Message position) {
 | 
			
		||||
        SpeakerManager.moveSound(source, reifyPosition(position));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final void handleSpeakerPlay(UUID source, SpeakerPosition.Message position, ResourceLocation sound, float volume, float pitch) {
 | 
			
		||||
    public void handleSpeakerPlay(UUID source, SpeakerPosition.Message position, ResourceLocation sound, float volume, float pitch) {
 | 
			
		||||
        SpeakerManager.getSound(source).playSound(reifyPosition(position), sound, volume, pitch);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final void handleSpeakerStop(UUID source) {
 | 
			
		||||
    public void handleSpeakerStop(UUID source) {
 | 
			
		||||
        SpeakerManager.stopSound(source);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final void handleUploadResult(int containerId, UploadResult result, @Nullable Component errorMessage) {
 | 
			
		||||
    public void handleUploadResult(int containerId, UploadResult result, @Nullable Component errorMessage) {
 | 
			
		||||
        var minecraft = Minecraft.getInstance();
 | 
			
		||||
 | 
			
		||||
        var screen = OptionScreen.unwrap(minecraft.screen);
 | 
			
		||||
@@ -9,6 +9,10 @@ import dan200.computercraft.shared.network.NetworkMessage;
 | 
			
		||||
import dan200.computercraft.shared.network.server.ServerNetworkContext;
 | 
			
		||||
import net.minecraft.client.renderer.MultiBufferSource;
 | 
			
		||||
import net.minecraft.client.resources.model.BakedModel;
 | 
			
		||||
import net.minecraft.core.BlockPos;
 | 
			
		||||
import net.minecraft.network.protocol.Packet;
 | 
			
		||||
import net.minecraft.network.protocol.game.ServerGamePacketListener;
 | 
			
		||||
import net.minecraft.sounds.SoundEvent;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
@@ -18,11 +22,12 @@ public interface ClientPlatformHelper extends dan200.computercraft.impl.client.C
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Send a network message to the server.
 | 
			
		||||
     * Convert a serverbound {@link NetworkMessage} to a Minecraft {@link Packet}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param message The message to send.
 | 
			
		||||
     * @param message The messsge to convert.
 | 
			
		||||
     * @return The converted message.
 | 
			
		||||
     */
 | 
			
		||||
    void sendToServer(NetworkMessage<ServerNetworkContext> message);
 | 
			
		||||
    Packet<ServerGamePacketListener> createPacket(NetworkMessage<ServerNetworkContext> message);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Render a {@link BakedModel}, using any loader-specific hooks.
 | 
			
		||||
@@ -35,4 +40,13 @@ public interface ClientPlatformHelper extends dan200.computercraft.impl.client.C
 | 
			
		||||
     * @param tints         Block colour tints to apply to the model.
 | 
			
		||||
     */
 | 
			
		||||
    void renderBakedModel(PoseStack transform, MultiBufferSource buffers, BakedModel model, int lightmapCoord, int overlayLight, @Nullable int[] tints);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Play a record at a particular position.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pos   The position to play this record.
 | 
			
		||||
     * @param sound The record to play, or {@code null} to stop it.
 | 
			
		||||
     * @see net.minecraft.client.renderer.LevelRenderer#playStreamingMusic(SoundEvent, BlockPos)
 | 
			
		||||
     */
 | 
			
		||||
    void playStreamingMusic(BlockPos pos, @Nullable SoundEvent sound);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,14 +4,14 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.client.render;
 | 
			
		||||
 | 
			
		||||
import com.mojang.blaze3d.vertex.PoseStack;
 | 
			
		||||
import com.mojang.blaze3d.vertex.VertexConsumer;
 | 
			
		||||
import net.minecraft.client.gui.GuiComponent;
 | 
			
		||||
import net.minecraft.client.gui.GuiGraphics;
 | 
			
		||||
import net.minecraft.client.renderer.RenderType;
 | 
			
		||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
 | 
			
		||||
import org.joml.Matrix4f;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A {@link GuiComponent}-equivalent which is suitable for both rendering in to a GUI and in-world (as part of an entity
 | 
			
		||||
 * A {@link GuiGraphics}-equivalent which is suitable for both rendering in to a GUI and in-world (as part of an entity
 | 
			
		||||
 * renderer).
 | 
			
		||||
 * <p>
 | 
			
		||||
 * This batches all render calls together, though requires that all {@link TextureAtlasSprite}s are on the same sprite
 | 
			
		||||
@@ -34,8 +34,11 @@ public class SpriteRenderer {
 | 
			
		||||
        this.b = b;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static SpriteRenderer createForGui(PoseStack stack, VertexConsumer builder) {
 | 
			
		||||
        return new SpriteRenderer(stack.last().pose(), builder, 0, RenderTypes.FULL_BRIGHT_LIGHTMAP, 255, 255, 255);
 | 
			
		||||
    public static SpriteRenderer createForGui(GuiGraphics graphics, RenderType renderType) {
 | 
			
		||||
        return new SpriteRenderer(
 | 
			
		||||
            graphics.pose().last().pose(), graphics.bufferSource().getBuffer(renderType),
 | 
			
		||||
            0, RenderTypes.FULL_BRIGHT_LIGHTMAP, 255, 255, 255
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -52,7 +55,7 @@ public class SpriteRenderer {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Render a horizontal 3-sliced texture (i.e. split into left, middle and right). Unlike {@link GuiComponent#blitNineSliced},
 | 
			
		||||
     * Render a horizontal 3-sliced texture (i.e. split into left, middle and right). Unlike {@link GuiGraphics#blitNineSliced},
 | 
			
		||||
     * the middle texture is stretched rather than repeated.
 | 
			
		||||
     *
 | 
			
		||||
     * @param sprite       The texture to draw.
 | 
			
		||||
@@ -77,7 +80,7 @@ public class SpriteRenderer {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Render a vertical 3-sliced texture (i.e. split into top, middle and bottom). Unlike {@link GuiComponent#blitNineSliced},
 | 
			
		||||
     * Render a vertical 3-sliced texture (i.e. split into top, middle and bottom). Unlike {@link GuiGraphics#blitNineSliced},
 | 
			
		||||
     * the middle texture is stretched rather than repeated.
 | 
			
		||||
     *
 | 
			
		||||
     * @param sprite        The texture to draw.
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,6 @@ import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleSide;
 | 
			
		||||
import dan200.computercraft.client.platform.ClientPlatformHelper;
 | 
			
		||||
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
 | 
			
		||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
 | 
			
		||||
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
 | 
			
		||||
import dan200.computercraft.shared.util.Holiday;
 | 
			
		||||
import net.minecraft.client.Minecraft;
 | 
			
		||||
@@ -21,7 +20,6 @@ import net.minecraft.client.renderer.blockentity.BlockEntityRenderDispatcher;
 | 
			
		||||
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
 | 
			
		||||
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
 | 
			
		||||
import net.minecraft.client.resources.model.BakedModel;
 | 
			
		||||
import net.minecraft.client.resources.model.ModelResourceLocation;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.phys.BlockHitResult;
 | 
			
		||||
import net.minecraft.world.phys.HitResult;
 | 
			
		||||
@@ -29,8 +27,6 @@ import net.minecraft.world.phys.HitResult;
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBlockEntity> {
 | 
			
		||||
    private static final ModelResourceLocation NORMAL_TURTLE_MODEL = new ModelResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_normal", "inventory");
 | 
			
		||||
    private static final ModelResourceLocation ADVANCED_TURTLE_MODEL = new ModelResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_advanced", "inventory");
 | 
			
		||||
    private static final ResourceLocation COLOUR_TURTLE_MODEL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_colour");
 | 
			
		||||
    private static final ResourceLocation ELF_OVERLAY_MODEL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_elf_overlay");
 | 
			
		||||
 | 
			
		||||
@@ -42,13 +38,6 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
 | 
			
		||||
        font = context.getFont();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static ResourceLocation getTurtleModel(ComputerFamily family, boolean coloured) {
 | 
			
		||||
        return switch (family) {
 | 
			
		||||
            default -> coloured ? COLOUR_TURTLE_MODEL : NORMAL_TURTLE_MODEL;
 | 
			
		||||
            case ADVANCED -> coloured ? COLOUR_TURTLE_MODEL : ADVANCED_TURTLE_MODEL;
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static @Nullable ResourceLocation getTurtleOverlayModel(@Nullable ResourceLocation overlay, boolean christmas) {
 | 
			
		||||
        if (overlay != null) return overlay;
 | 
			
		||||
        if (christmas) return ELF_OVERLAY_MODEL;
 | 
			
		||||
@@ -78,7 +67,6 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
 | 
			
		||||
            var matrix = transform.last().pose();
 | 
			
		||||
            var opacity = (int) (mc.options.getBackgroundOpacity(0.25f) * 255) << 24;
 | 
			
		||||
            var width = -font.width(label) / 2.0f;
 | 
			
		||||
            // TODO: Check this looks okay
 | 
			
		||||
            font.drawInBatch(label, width, (float) 0, 0x20ffffff, false, matrix, buffers, Font.DisplayMode.SEE_THROUGH, opacity, lightmapCoord);
 | 
			
		||||
            font.drawInBatch(label, width, (float) 0, 0xffffffff, false, matrix, buffers, Font.DisplayMode.NORMAL, 0, lightmapCoord);
 | 
			
		||||
 | 
			
		||||
@@ -96,10 +84,18 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
 | 
			
		||||
 | 
			
		||||
        // Render the turtle
 | 
			
		||||
        var colour = turtle.getColour();
 | 
			
		||||
        var family = turtle.getFamily();
 | 
			
		||||
        var overlay = turtle.getOverlay();
 | 
			
		||||
 | 
			
		||||
        renderModel(transform, buffers, lightmapCoord, overlayLight, getTurtleModel(family, colour != -1), colour == -1 ? null : new int[]{ colour });
 | 
			
		||||
        if (colour == -1) {
 | 
			
		||||
            // Render the turtle using its item model.
 | 
			
		||||
            var modelManager = Minecraft.getInstance().getItemRenderer().getItemModelShaper();
 | 
			
		||||
            var model = modelManager.getItemModel(turtle.getBlockState().getBlock().asItem());
 | 
			
		||||
            if (model == null) model = modelManager.getModelManager().getMissingModel();
 | 
			
		||||
            renderModel(transform, buffers, lightmapCoord, overlayLight, model, null);
 | 
			
		||||
        } else {
 | 
			
		||||
            // Otherwise render it using the colour item.
 | 
			
		||||
            renderModel(transform, buffers, lightmapCoord, overlayLight, COLOUR_TURTLE_MODEL, new int[]{ colour });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Render the overlay
 | 
			
		||||
        var overlayModel = getTurtleOverlayModel(overlay, Holiday.getCurrent() == Holiday.CHRISTMAS);
 | 
			
		||||
 
 | 
			
		||||
@@ -7,10 +7,7 @@ package dan200.computercraft.client.render.monitor;
 | 
			
		||||
import com.mojang.blaze3d.platform.GlStateManager;
 | 
			
		||||
import com.mojang.blaze3d.platform.MemoryTracker;
 | 
			
		||||
import com.mojang.blaze3d.systems.RenderSystem;
 | 
			
		||||
import com.mojang.blaze3d.vertex.PoseStack;
 | 
			
		||||
import com.mojang.blaze3d.vertex.Tesselator;
 | 
			
		||||
import com.mojang.blaze3d.vertex.VertexBuffer;
 | 
			
		||||
import com.mojang.blaze3d.vertex.VertexConsumer;
 | 
			
		||||
import com.mojang.blaze3d.vertex.*;
 | 
			
		||||
import com.mojang.math.Axis;
 | 
			
		||||
import dan200.computercraft.client.FrameInfo;
 | 
			
		||||
import dan200.computercraft.client.integration.ShaderMod;
 | 
			
		||||
@@ -170,7 +167,7 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl
 | 
			
		||||
                tboVertex(buffer, matrix, -xMargin, pixelHeight + yMargin);
 | 
			
		||||
                tboVertex(buffer, matrix, pixelWidth + xMargin, -yMargin);
 | 
			
		||||
                tboVertex(buffer, matrix, pixelWidth + xMargin, pixelHeight + yMargin);
 | 
			
		||||
                RenderTypes.MONITOR_TBO.end(buffer, 0, 0, 0);
 | 
			
		||||
                RenderTypes.MONITOR_TBO.end(buffer, VertexSorting.DISTANCE_TO_ORIGIN);
 | 
			
		||||
            }
 | 
			
		||||
            case VBO -> {
 | 
			
		||||
                var backgroundBuffer = assertNonNull(renderState.backgroundBuffer);
 | 
			
		||||
 
 | 
			
		||||
@@ -14,10 +14,10 @@ import dan200.computercraft.core.terminal.TextBuffer;
 | 
			
		||||
import dan200.computercraft.core.util.Colour;
 | 
			
		||||
import net.minecraft.client.renderer.ShaderInstance;
 | 
			
		||||
import net.minecraft.server.packs.resources.ResourceProvider;
 | 
			
		||||
import org.apache.logging.log4j.LogManager;
 | 
			
		||||
import org.apache.logging.log4j.Logger;
 | 
			
		||||
import org.lwjgl.opengl.GL13;
 | 
			
		||||
import org.lwjgl.opengl.GL31;
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
@@ -36,12 +36,12 @@ import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.get
 | 
			
		||||
 * @see RenderTypes#getMonitorTextureBufferShader()
 | 
			
		||||
 */
 | 
			
		||||
public class MonitorTextureBufferShader extends ShaderInstance {
 | 
			
		||||
    private static final Logger LOG = LoggerFactory.getLogger(MonitorTextureBufferShader.class);
 | 
			
		||||
 | 
			
		||||
    public static final int UNIFORM_SIZE = 4 * 4 * 16 + 4 + 4 + 2 * 4 + 4;
 | 
			
		||||
 | 
			
		||||
    static final int TEXTURE_INDEX = GL13.GL_TEXTURE3;
 | 
			
		||||
 | 
			
		||||
    private static final Logger LOGGER = LogManager.getLogger();
 | 
			
		||||
 | 
			
		||||
    private final int monitorData;
 | 
			
		||||
    private int uniformBuffer = 0;
 | 
			
		||||
 | 
			
		||||
@@ -75,7 +75,7 @@ public class MonitorTextureBufferShader extends ShaderInstance {
 | 
			
		||||
    private Uniform getUniformChecked(String name) {
 | 
			
		||||
        var uniform = getUniform(name);
 | 
			
		||||
        if (uniform == null) {
 | 
			
		||||
            LOGGER.warn("Monitor shader {} should have uniform {}, but it was not present.", getName(), name);
 | 
			
		||||
            LOG.warn("Monitor shader {} should have uniform {}, but it was not present.", getName(), name);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return uniform;
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,7 @@ public class DirectVertexBuffer extends VertexBuffer {
 | 
			
		||||
    private int actualIndexCount;
 | 
			
		||||
 | 
			
		||||
    public DirectVertexBuffer() {
 | 
			
		||||
        super(Usage.STATIC);
 | 
			
		||||
        if (DirectBuffers.HAS_DSA) {
 | 
			
		||||
            RenderSystem.glDeleteBuffers(vertexBufferId);
 | 
			
		||||
            if (DirectBuffers.ON_LINUX) BufferUploader.reset(); // See comment on DirectBuffers.deleteBuffer.
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ package dan200.computercraft.client.sound;
 | 
			
		||||
 | 
			
		||||
import com.mojang.blaze3d.audio.Channel;
 | 
			
		||||
import dan200.computercraft.shared.peripheral.speaker.SpeakerPeripheral;
 | 
			
		||||
import io.netty.buffer.ByteBuf;
 | 
			
		||||
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
 | 
			
		||||
import net.minecraft.client.sounds.AudioStream;
 | 
			
		||||
import net.minecraft.client.sounds.SoundEngine;
 | 
			
		||||
import org.lwjgl.BufferUtils;
 | 
			
		||||
@@ -36,7 +36,7 @@ class DfpwmStream implements AudioStream {
 | 
			
		||||
    /**
 | 
			
		||||
     * The {@link Channel} which this sound is playing on.
 | 
			
		||||
     *
 | 
			
		||||
     * @see SpeakerInstance#pushAudio(ByteBuf)
 | 
			
		||||
     * @see SpeakerInstance#playAudio(SpeakerPosition, float, ByteBuffer)
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    Channel channel;
 | 
			
		||||
@@ -44,7 +44,7 @@ class DfpwmStream implements AudioStream {
 | 
			
		||||
    /**
 | 
			
		||||
     * The underlying {@link SoundEngine} executor.
 | 
			
		||||
     *
 | 
			
		||||
     * @see SpeakerInstance#pushAudio(ByteBuf)
 | 
			
		||||
     * @see SpeakerInstance#playAudio(SpeakerPosition, float, ByteBuffer)
 | 
			
		||||
     * @see SoundEngine#executor
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
@@ -58,12 +58,12 @@ class DfpwmStream implements AudioStream {
 | 
			
		||||
    DfpwmStream() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void push(ByteBuf input) {
 | 
			
		||||
        var readable = input.readableBytes();
 | 
			
		||||
    void push(ByteBuffer input) {
 | 
			
		||||
        var readable = input.remaining();
 | 
			
		||||
        var output = ByteBuffer.allocate(readable * 8).order(ByteOrder.nativeOrder());
 | 
			
		||||
 | 
			
		||||
        for (var i = 0; i < readable; i++) {
 | 
			
		||||
            var inputByte = input.readByte();
 | 
			
		||||
            var inputByte = input.get();
 | 
			
		||||
            for (var j = 0; j < 8; j++) {
 | 
			
		||||
                var currentBit = (inputByte & 1) != 0;
 | 
			
		||||
                var target = currentBit ? 127 : -128;
 | 
			
		||||
 
 | 
			
		||||
@@ -7,11 +7,11 @@ package dan200.computercraft.client.sound;
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.core.util.Nullability;
 | 
			
		||||
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
 | 
			
		||||
import io.netty.buffer.ByteBuf;
 | 
			
		||||
import net.minecraft.client.Minecraft;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.nio.ByteBuffer;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An instance of a speaker, which is either playing a {@link DfpwmStream} stream or a normal sound.
 | 
			
		||||
@@ -25,7 +25,7 @@ public class SpeakerInstance {
 | 
			
		||||
    SpeakerInstance() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public synchronized void pushAudio(ByteBuf buffer) {
 | 
			
		||||
    private void pushAudio(ByteBuffer buffer) {
 | 
			
		||||
        var sound = this.sound;
 | 
			
		||||
 | 
			
		||||
        var stream = currentStream;
 | 
			
		||||
@@ -43,7 +43,9 @@ public class SpeakerInstance {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void playAudio(SpeakerPosition position, float volume) {
 | 
			
		||||
    public void playAudio(SpeakerPosition position, float volume, ByteBuffer buffer) {
 | 
			
		||||
        pushAudio(buffer);
 | 
			
		||||
 | 
			
		||||
        var soundManager = Minecraft.getInstance().getSoundManager();
 | 
			
		||||
 | 
			
		||||
        if (sound != null && sound.stream != currentStream) {
 | 
			
		||||
 
 | 
			
		||||
@@ -11,11 +11,14 @@ import dan200.computercraft.api.turtle.ITurtleAccess;
 | 
			
		||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleSide;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.impl.PlatformHelper;
 | 
			
		||||
import dan200.computercraft.impl.TurtleUpgrades;
 | 
			
		||||
import dan200.computercraft.impl.UpgradeManager;
 | 
			
		||||
import net.minecraft.client.Minecraft;
 | 
			
		||||
import net.minecraft.nbt.CompoundTag;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.WeakHashMap;
 | 
			
		||||
@@ -24,14 +27,15 @@ import java.util.stream.Stream;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A registry of {@link TurtleUpgradeModeller}s.
 | 
			
		||||
 *
 | 
			
		||||
 * @see dan200.computercraft.api.client.ComputerCraftAPIClient#registerTurtleUpgradeModeller(TurtleUpgradeSerialiser, TurtleUpgradeModeller)
 | 
			
		||||
 */
 | 
			
		||||
public final class TurtleUpgradeModellers {
 | 
			
		||||
    private static final Logger LOG = LoggerFactory.getLogger(TurtleUpgradeModellers.class);
 | 
			
		||||
 | 
			
		||||
    private static final TurtleUpgradeModeller<ITurtleUpgrade> NULL_TURTLE_MODELLER = (upgrade, turtle, side) ->
 | 
			
		||||
        new TransformedModel(Minecraft.getInstance().getModelManager().getMissingModel(), Transformation.identity());
 | 
			
		||||
 | 
			
		||||
    private static final Map<TurtleUpgradeSerialiser<?>, TurtleUpgradeModeller<?>> turtleModels = new ConcurrentHashMap<>();
 | 
			
		||||
    private static volatile boolean fetchedModels;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * In order to avoid a double lookup of {@link ITurtleUpgrade} to {@link UpgradeManager.UpgradeWrapper} to
 | 
			
		||||
@@ -45,12 +49,18 @@ public final class TurtleUpgradeModellers {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static <T extends ITurtleUpgrade> void register(TurtleUpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller) {
 | 
			
		||||
        synchronized (turtleModels) {
 | 
			
		||||
            if (turtleModels.containsKey(serialiser)) {
 | 
			
		||||
                throw new IllegalStateException("Modeller already registered for serialiser");
 | 
			
		||||
            }
 | 
			
		||||
        if (fetchedModels) {
 | 
			
		||||
            // TODO(1.20.4): Replace with an error.
 | 
			
		||||
            LOG.warn(
 | 
			
		||||
                "Turtle upgrade serialiser {} was registered too late, its models may not be loaded correctly. If you are " +
 | 
			
		||||
                    "the mod author, you may be using a deprecated API - see https://github.com/cc-tweaked/CC-Tweaked/pull/1684 " +
 | 
			
		||||
                    "for further information.",
 | 
			
		||||
                PlatformHelper.get().getRegistryKey(TurtleUpgradeSerialiser.registryId(), serialiser)
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            turtleModels.put(serialiser, modeller);
 | 
			
		||||
        if (turtleModels.putIfAbsent(serialiser, modeller) != null) {
 | 
			
		||||
            throw new IllegalStateException("Modeller already registered for serialiser");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -75,6 +85,7 @@ public final class TurtleUpgradeModellers {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static Stream<ResourceLocation> getDependencies() {
 | 
			
		||||
        fetchedModels = true;
 | 
			
		||||
        return turtleModels.values().stream().flatMap(x -> x.getDependencies().stream());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
    "required": true,
 | 
			
		||||
    "package": "dan200.computercraft.mixin.client",
 | 
			
		||||
    "minVersion": "0.8",
 | 
			
		||||
    "compatibilityLevel": "JAVA_17",
 | 
			
		||||
    "injectors": {
 | 
			
		||||
        "defaultRequire": 1
 | 
			
		||||
    },
 | 
			
		||||
    "client": [
 | 
			
		||||
        "ClientPacketListenerMixin"
 | 
			
		||||
    ],
 | 
			
		||||
    "refmap": "client-computercraft.refmap.json"
 | 
			
		||||
}
 | 
			
		||||
@@ -139,8 +139,6 @@ public final class LanguageProvider implements DataProvider {
 | 
			
		||||
        add("commands.computercraft.tp.synopsis", "Teleport to a specific computer.");
 | 
			
		||||
        add("commands.computercraft.tp.desc", "Teleport to the location of a computer. You can either specify the computer's instance id (e.g. 123) or computer id (e.g #123).");
 | 
			
		||||
        add("commands.computercraft.tp.action", "Teleport to this computer");
 | 
			
		||||
        add("commands.computercraft.tp.not_player", "Cannot open terminal for non-player");
 | 
			
		||||
        add("commands.computercraft.tp.not_there", "Cannot locate computer in the world");
 | 
			
		||||
        add("commands.computercraft.view.synopsis", "View the terminal of a computer.");
 | 
			
		||||
        add("commands.computercraft.view.desc", "Open the terminal of a computer, allowing remote control of a computer. This does not provide access to turtle's inventories. You can either specify the computer's instance id (e.g. 123) or computer id (e.g #123).");
 | 
			
		||||
        add("commands.computercraft.view.action", "View this computer");
 | 
			
		||||
@@ -176,6 +174,7 @@ public final class LanguageProvider implements DataProvider {
 | 
			
		||||
        // Metrics
 | 
			
		||||
        add(Metrics.COMPUTER_TASKS, "Tasks");
 | 
			
		||||
        add(Metrics.SERVER_TASKS, "Server tasks");
 | 
			
		||||
        add(Metrics.JAVA_ALLOCATION, "Java Allocations");
 | 
			
		||||
        add(Metrics.PERIPHERAL_OPS, "Peripheral calls");
 | 
			
		||||
        add(Metrics.FS_OPS, "Filesystem operations");
 | 
			
		||||
        add(Metrics.HTTP_REQUESTS, "HTTP requests");
 | 
			
		||||
@@ -215,7 +214,6 @@ public final class LanguageProvider implements DataProvider {
 | 
			
		||||
        addConfigEntry(ConfigSpec.floppySpaceLimit, "Floppy Disk space limit (bytes)");
 | 
			
		||||
        addConfigEntry(ConfigSpec.uploadMaxSize, "File upload size limit (bytes)");
 | 
			
		||||
        addConfigEntry(ConfigSpec.maximumFilesOpen, "Maximum files open per computer");
 | 
			
		||||
        addConfigEntry(ConfigSpec.disableLua51Features, "Disable Lua 5.1 features");
 | 
			
		||||
        addConfigEntry(ConfigSpec.defaultComputerSettings, "Default Computer settings");
 | 
			
		||||
        addConfigEntry(ConfigSpec.logComputerErrors, "Log computer errors");
 | 
			
		||||
        addConfigEntry(ConfigSpec.commandRequireCreative, "Command computers require creative");
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ import net.minecraft.world.level.storage.loot.entries.LootItem;
 | 
			
		||||
import net.minecraft.world.level.storage.loot.entries.LootPoolEntryContainer;
 | 
			
		||||
import net.minecraft.world.level.storage.loot.functions.CopyNameFunction;
 | 
			
		||||
import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets;
 | 
			
		||||
import net.minecraft.world.level.storage.loot.predicates.AlternativeLootItemCondition;
 | 
			
		||||
import net.minecraft.world.level.storage.loot.predicates.AnyOfCondition;
 | 
			
		||||
import net.minecraft.world.level.storage.loot.predicates.ExplosionCondition;
 | 
			
		||||
import net.minecraft.world.level.storage.loot.predicates.LootItemBlockStatePropertyCondition;
 | 
			
		||||
import net.minecraft.world.level.storage.loot.predicates.LootItemCondition;
 | 
			
		||||
@@ -79,7 +79,7 @@ class LootTableProvider {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void registerGeneric(BiConsumer<ResourceLocation, LootTable.Builder> add) {
 | 
			
		||||
        add.accept(CommonHooks.LOOT_TREASURE_DISK, LootTable.lootTable());
 | 
			
		||||
        add.accept(CommonHooks.TREASURE_DISK_LOOT, LootTable.lootTable());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void selfDrop(BiConsumer<ResourceLocation, LootTable.Builder> add, Supplier<? extends Block> wrapper) {
 | 
			
		||||
@@ -98,7 +98,7 @@ class LootTableProvider {
 | 
			
		||||
        blockDrop(
 | 
			
		||||
            add, block,
 | 
			
		||||
            DynamicLoot.dynamicEntry(new ResourceLocation(ComputerCraftAPI.MOD_ID, "computer")),
 | 
			
		||||
            AlternativeLootItemCondition.alternative(
 | 
			
		||||
            AnyOfCondition.anyOf(
 | 
			
		||||
                BlockNamedEntityLootCondition.BUILDER,
 | 
			
		||||
                HasComputerIdLootCondition.BUILDER,
 | 
			
		||||
                PlayerCreativeLootCondition.BUILDER.invert()
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,48 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.data;
 | 
			
		||||
 | 
			
		||||
import com.google.common.hash.HashCode;
 | 
			
		||||
import com.google.common.hash.HashFunction;
 | 
			
		||||
import com.google.common.hash.Hashing;
 | 
			
		||||
import net.minecraft.data.CachedOutput;
 | 
			
		||||
import net.minecraft.data.DataProvider;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.nio.file.Path;
 | 
			
		||||
import java.util.concurrent.CompletableFuture;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Wraps an existing {@link DataProvider}, passing generated JSON through {@link PrettyJsonWriter}.
 | 
			
		||||
 *
 | 
			
		||||
 * @param provider The provider to wrap.
 | 
			
		||||
 * @param <T>      The type of the provider to wrap.
 | 
			
		||||
 */
 | 
			
		||||
public record PrettyDataProvider<T extends DataProvider>(T provider) implements DataProvider {
 | 
			
		||||
    @Override
 | 
			
		||||
    public CompletableFuture<?> run(CachedOutput cachedOutput) {
 | 
			
		||||
        return provider.run(new Output(cachedOutput));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String getName() {
 | 
			
		||||
        return provider.getName();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private record Output(CachedOutput output) implements CachedOutput {
 | 
			
		||||
        @SuppressWarnings("deprecation")
 | 
			
		||||
        private static final HashFunction HASH_FUNCTION = Hashing.sha1();
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void writeIfNeeded(Path path, byte[] bytes, HashCode hashCode) throws IOException {
 | 
			
		||||
            if (path.getFileName().toString().endsWith(".json")) {
 | 
			
		||||
                bytes = PrettyJsonWriter.reformat(bytes);
 | 
			
		||||
                hashCode = HASH_FUNCTION.hashBytes(bytes);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            output.writeIfNeeded(path, bytes, hashCode);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -22,11 +22,10 @@ import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Alternative version of {@link JsonWriter} which attempts to lay out the JSON in a more compact format.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Yes, this is at least a little deranged.
 | 
			
		||||
 *
 | 
			
		||||
 * @see PrettyDataProvider
 | 
			
		||||
 */
 | 
			
		||||
public class PrettyJsonWriter extends JsonWriter {
 | 
			
		||||
    public static final boolean ENABLED = System.getProperty("cct.pretty-json") != null;
 | 
			
		||||
    private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
 | 
			
		||||
 | 
			
		||||
    private static final int MAX_WIDTH = 120;
 | 
			
		||||
@@ -44,17 +43,6 @@ public class PrettyJsonWriter extends JsonWriter {
 | 
			
		||||
        this.out = out;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a JSON writer. This will either be a pretty or normal version, depending on whether the global flag is
 | 
			
		||||
     * set.
 | 
			
		||||
     *
 | 
			
		||||
     * @param out The writer to emit to.
 | 
			
		||||
     * @return The constructed JSON writer.
 | 
			
		||||
     */
 | 
			
		||||
    public static JsonWriter createWriter(Writer out) {
 | 
			
		||||
        return ENABLED ? new PrettyJsonWriter(out) : new JsonWriter(out);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Reformat a JSON string with our pretty printer.
 | 
			
		||||
     *
 | 
			
		||||
@@ -62,8 +50,6 @@ public class PrettyJsonWriter extends JsonWriter {
 | 
			
		||||
     * @return The reformatted string.
 | 
			
		||||
     */
 | 
			
		||||
    public static byte[] reformat(byte[] contents) {
 | 
			
		||||
        if (!ENABLED) return contents;
 | 
			
		||||
 | 
			
		||||
        JsonElement object;
 | 
			
		||||
        try (var reader = new InputStreamReader(new ByteArrayInputStream(contents), StandardCharsets.UTF_8)) {
 | 
			
		||||
            object = GSON.fromJson(reader, JsonElement.class);
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@
 | 
			
		||||
package dan200.computercraft.data;
 | 
			
		||||
 | 
			
		||||
import com.google.gson.JsonObject;
 | 
			
		||||
import com.mojang.authlib.GameProfile;
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.api.pocket.PocketUpgradeDataProvider;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleUpgradeDataProvider;
 | 
			
		||||
@@ -12,7 +13,6 @@ import dan200.computercraft.api.upgrades.UpgradeData;
 | 
			
		||||
import dan200.computercraft.core.util.Colour;
 | 
			
		||||
import dan200.computercraft.shared.ModRegistry;
 | 
			
		||||
import dan200.computercraft.shared.common.IColouredItem;
 | 
			
		||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
 | 
			
		||||
import dan200.computercraft.shared.platform.PlatformHelper;
 | 
			
		||||
import dan200.computercraft.shared.platform.RecipeIngredients;
 | 
			
		||||
import dan200.computercraft.shared.platform.RegistryWrappers;
 | 
			
		||||
@@ -25,22 +25,19 @@ import net.minecraft.core.registries.Registries;
 | 
			
		||||
import net.minecraft.data.PackOutput;
 | 
			
		||||
import net.minecraft.data.recipes.*;
 | 
			
		||||
import net.minecraft.nbt.CompoundTag;
 | 
			
		||||
import net.minecraft.nbt.NbtUtils;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.tags.TagKey;
 | 
			
		||||
import net.minecraft.util.GsonHelper;
 | 
			
		||||
import net.minecraft.world.item.DyeColor;
 | 
			
		||||
import net.minecraft.world.item.DyeItem;
 | 
			
		||||
import net.minecraft.world.item.Item;
 | 
			
		||||
import net.minecraft.world.item.Items;
 | 
			
		||||
import net.minecraft.world.item.*;
 | 
			
		||||
import net.minecraft.world.item.crafting.Ingredient;
 | 
			
		||||
import net.minecraft.world.item.crafting.RecipeSerializer;
 | 
			
		||||
import net.minecraft.world.item.crafting.ShapedRecipe;
 | 
			
		||||
import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer;
 | 
			
		||||
import net.minecraft.world.level.ItemLike;
 | 
			
		||||
import net.minecraft.world.level.block.Blocks;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Locale;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
 | 
			
		||||
import static dan200.computercraft.api.ComputerCraftTags.Items.COMPUTER;
 | 
			
		||||
@@ -107,14 +104,13 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
 | 
			
		||||
    private void turtleUpgrades(Consumer<FinishedRecipe> add) {
 | 
			
		||||
        for (var turtleItem : turtleItems()) {
 | 
			
		||||
            var base = turtleItem.create(-1, null, -1, null, null, 0, null);
 | 
			
		||||
 | 
			
		||||
            var nameId = turtleItem.getFamily().name().toLowerCase(Locale.ROOT);
 | 
			
		||||
            var name = RegistryWrappers.ITEMS.getKey(turtleItem);
 | 
			
		||||
 | 
			
		||||
            for (var upgrade : turtleUpgrades.getGeneratedUpgrades()) {
 | 
			
		||||
                var result = turtleItem.create(-1, null, -1, null, UpgradeData.ofDefault(upgrade), -1, null);
 | 
			
		||||
                ShapedRecipeBuilder
 | 
			
		||||
                    .shaped(RecipeCategory.REDSTONE, result.getItem())
 | 
			
		||||
                    .group(String.format("%s:turtle_%s", ComputerCraftAPI.MOD_ID, nameId))
 | 
			
		||||
                    .group(name.toString())
 | 
			
		||||
                    .pattern("#T")
 | 
			
		||||
                    .define('T', base.getItem())
 | 
			
		||||
                    .define('#', upgrade.getCraftingItem().getItem())
 | 
			
		||||
@@ -122,9 +118,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
 | 
			
		||||
                        inventoryChange(base.getItem(), upgrade.getCraftingItem().getItem()))
 | 
			
		||||
                    .save(
 | 
			
		||||
                        RecipeWrapper.wrap(ModRegistry.RecipeSerializers.IMPOSTOR_SHAPED.get(), add).withResultTag(result.getTag()),
 | 
			
		||||
                        new ResourceLocation(ComputerCraftAPI.MOD_ID, String.format("turtle_%s/%s/%s",
 | 
			
		||||
                            nameId, upgrade.getUpgradeID().getNamespace(), upgrade.getUpgradeID().getPath()
 | 
			
		||||
                        ))
 | 
			
		||||
                        name.withSuffix(String.format("/%s/%s", upgrade.getUpgradeID().getNamespace(), upgrade.getUpgradeID().getPath()))
 | 
			
		||||
                    );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -142,15 +136,13 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
 | 
			
		||||
    private void pocketUpgrades(Consumer<FinishedRecipe> add) {
 | 
			
		||||
        for (var pocket : pocketComputerItems()) {
 | 
			
		||||
            var base = pocket.create(-1, null, -1, null);
 | 
			
		||||
            if (base.isEmpty()) continue;
 | 
			
		||||
 | 
			
		||||
            var nameId = pocket.getFamily().name().toLowerCase(Locale.ROOT);
 | 
			
		||||
            var name = RegistryWrappers.ITEMS.getKey(pocket).withPath(x -> x.replace("pocket_computer_", "pocket_"));
 | 
			
		||||
 | 
			
		||||
            for (var upgrade : pocketUpgrades.getGeneratedUpgrades()) {
 | 
			
		||||
                var result = pocket.create(-1, null, -1, UpgradeData.ofDefault(upgrade));
 | 
			
		||||
                ShapedRecipeBuilder
 | 
			
		||||
                    .shaped(RecipeCategory.REDSTONE, result.getItem())
 | 
			
		||||
                    .group(String.format("%s:pocket_%s", ComputerCraftAPI.MOD_ID, nameId))
 | 
			
		||||
                    .group(name.toString())
 | 
			
		||||
                    .pattern("#")
 | 
			
		||||
                    .pattern("P")
 | 
			
		||||
                    .define('P', base.getItem())
 | 
			
		||||
@@ -159,9 +151,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
 | 
			
		||||
                        inventoryChange(base.getItem(), upgrade.getCraftingItem().getItem()))
 | 
			
		||||
                    .save(
 | 
			
		||||
                        RecipeWrapper.wrap(ModRegistry.RecipeSerializers.IMPOSTOR_SHAPED.get(), add).withResultTag(result.getTag()),
 | 
			
		||||
                        new ResourceLocation(ComputerCraftAPI.MOD_ID, String.format("pocket_%s/%s/%s",
 | 
			
		||||
                            nameId, upgrade.getUpgradeID().getNamespace(), upgrade.getUpgradeID().getPath()
 | 
			
		||||
                        ))
 | 
			
		||||
                        name.withSuffix(String.format("/%s/%s", upgrade.getUpgradeID().getNamespace(), upgrade.getUpgradeID().getPath()))
 | 
			
		||||
                    );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -191,12 +181,10 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
 | 
			
		||||
    private void turtleOverlay(Consumer<FinishedRecipe> add, String overlay, Consumer<ShapelessRecipeBuilder> build) {
 | 
			
		||||
        for (var turtleItem : turtleItems()) {
 | 
			
		||||
            var base = turtleItem.create(-1, null, -1, null, null, 0, null);
 | 
			
		||||
 | 
			
		||||
            var nameId = turtleItem.getFamily().name().toLowerCase(Locale.ROOT);
 | 
			
		||||
            var group = "%s:turtle_%s_overlay".formatted(ComputerCraftAPI.MOD_ID, nameId);
 | 
			
		||||
            var name = RegistryWrappers.ITEMS.getKey(turtleItem);
 | 
			
		||||
 | 
			
		||||
            var builder = ShapelessRecipeBuilder.shapeless(RecipeCategory.REDSTONE, base.getItem())
 | 
			
		||||
                .group(group)
 | 
			
		||||
                .group(name.withSuffix("_overlay").toString())
 | 
			
		||||
                .unlockedBy("has_turtle", inventoryChange(base.getItem()));
 | 
			
		||||
            build.accept(builder);
 | 
			
		||||
            builder
 | 
			
		||||
@@ -205,7 +193,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
 | 
			
		||||
                    RecipeWrapper
 | 
			
		||||
                        .wrap(ModRegistry.RecipeSerializers.TURTLE_OVERLAY.get(), add)
 | 
			
		||||
                        .withExtraData(x -> x.addProperty("overlay", new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/" + overlay).toString())),
 | 
			
		||||
                    new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_%s_overlays/%s".formatted(nameId, overlay))
 | 
			
		||||
                    name.withSuffix("_overlays/" + overlay)
 | 
			
		||||
                );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -254,7 +242,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
 | 
			
		||||
            .define('C', ModRegistry.Items.COMPUTER_NORMAL.get())
 | 
			
		||||
            .unlockedBy("has_components", inventoryChange(itemPredicate(ModRegistry.Items.COMPUTER_NORMAL.get()), itemPredicate(ingredients.goldIngot())))
 | 
			
		||||
            .save(
 | 
			
		||||
                RecipeWrapper.wrap(ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get(), add).withExtraData(family(ComputerFamily.ADVANCED)),
 | 
			
		||||
                RecipeWrapper.wrap(ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get(), add),
 | 
			
		||||
                new ResourceLocation(ComputerCraftAPI.MOD_ID, "computer_advanced_upgrade")
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
@@ -278,7 +266,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
 | 
			
		||||
            .define('C', ModRegistry.Items.COMPUTER_NORMAL.get())
 | 
			
		||||
            .define('I', ingredients.woodenChest())
 | 
			
		||||
            .unlockedBy("has_computer", inventoryChange(ModRegistry.Items.COMPUTER_NORMAL.get()))
 | 
			
		||||
            .save(RecipeWrapper.wrap(ModRegistry.RecipeSerializers.TURTLE.get(), add).withExtraData(family(ComputerFamily.NORMAL)));
 | 
			
		||||
            .save(RecipeWrapper.wrap(ModRegistry.RecipeSerializers.TURTLE.get(), add));
 | 
			
		||||
 | 
			
		||||
        ShapedRecipeBuilder
 | 
			
		||||
            .shaped(RecipeCategory.REDSTONE, ModRegistry.Blocks.TURTLE_ADVANCED.get())
 | 
			
		||||
@@ -289,7 +277,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
 | 
			
		||||
            .define('C', ModRegistry.Items.COMPUTER_ADVANCED.get())
 | 
			
		||||
            .define('I', ingredients.woodenChest())
 | 
			
		||||
            .unlockedBy("has_computer", inventoryChange(ModRegistry.Items.COMPUTER_NORMAL.get()))
 | 
			
		||||
            .save(RecipeWrapper.wrap(ModRegistry.RecipeSerializers.TURTLE.get(), add).withExtraData(family(ComputerFamily.ADVANCED)));
 | 
			
		||||
            .save(RecipeWrapper.wrap(ModRegistry.RecipeSerializers.TURTLE.get(), add));
 | 
			
		||||
 | 
			
		||||
        ShapedRecipeBuilder
 | 
			
		||||
            .shaped(RecipeCategory.REDSTONE, ModRegistry.Blocks.TURTLE_ADVANCED.get())
 | 
			
		||||
@@ -301,7 +289,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
 | 
			
		||||
            .define('B', ingredients.goldBlock())
 | 
			
		||||
            .unlockedBy("has_components", inventoryChange(itemPredicate(ModRegistry.Items.TURTLE_NORMAL.get()), itemPredicate(ingredients.goldIngot())))
 | 
			
		||||
            .save(
 | 
			
		||||
                RecipeWrapper.wrap(ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get(), add).withExtraData(family(ComputerFamily.ADVANCED)),
 | 
			
		||||
                RecipeWrapper.wrap(ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get(), add),
 | 
			
		||||
                new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_advanced_upgrade")
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
@@ -368,7 +356,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
 | 
			
		||||
            .define('C', ModRegistry.Items.POCKET_COMPUTER_NORMAL.get())
 | 
			
		||||
            .unlockedBy("has_components", inventoryChange(itemPredicate(ModRegistry.Items.POCKET_COMPUTER_NORMAL.get()), itemPredicate(ingredients.goldIngot())))
 | 
			
		||||
            .save(
 | 
			
		||||
                RecipeWrapper.wrap(ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get(), add).withExtraData(family(ComputerFamily.ADVANCED)),
 | 
			
		||||
                RecipeWrapper.wrap(ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get(), add),
 | 
			
		||||
                new ResourceLocation(ComputerCraftAPI.MOD_ID, "pocket_computer_advanced_upgrade")
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
@@ -443,7 +431,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
 | 
			
		||||
            .requires(ModRegistry.Items.MONITOR_NORMAL.get())
 | 
			
		||||
            .unlockedBy("has_monitor", inventoryChange(ModRegistry.Items.MONITOR_NORMAL.get()))
 | 
			
		||||
            .save(
 | 
			
		||||
                RecipeWrapper.wrap(RecipeSerializer.SHAPELESS_RECIPE, add)
 | 
			
		||||
                RecipeWrapper.wrap(ModRegistry.RecipeSerializers.SHAPELESS.get(), add)
 | 
			
		||||
                    .withResultTag(playerHead("Cloudhunter", "6d074736-b1e9-4378-a99b-bd8777821c9c")),
 | 
			
		||||
                new ResourceLocation(ComputerCraftAPI.MOD_ID, "skull_cloudy")
 | 
			
		||||
            );
 | 
			
		||||
@@ -454,7 +442,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
 | 
			
		||||
            .requires(ModRegistry.Items.COMPUTER_ADVANCED.get())
 | 
			
		||||
            .unlockedBy("has_computer", inventoryChange(ModRegistry.Items.COMPUTER_ADVANCED.get()))
 | 
			
		||||
            .save(
 | 
			
		||||
                RecipeWrapper.wrap(RecipeSerializer.SHAPELESS_RECIPE, add)
 | 
			
		||||
                RecipeWrapper.wrap(ModRegistry.RecipeSerializers.SHAPELESS.get(), add)
 | 
			
		||||
                    .withResultTag(playerHead("dan200", "f3c8d69b-0776-4512-8434-d1b2165909eb")),
 | 
			
		||||
                new ResourceLocation(ComputerCraftAPI.MOD_ID, "skull_dan200")
 | 
			
		||||
            );
 | 
			
		||||
@@ -513,19 +501,13 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static CompoundTag playerHead(String name, String uuid) {
 | 
			
		||||
        var owner = new CompoundTag();
 | 
			
		||||
        owner.putString("Name", name);
 | 
			
		||||
        owner.putString("Id", uuid);
 | 
			
		||||
        var owner = NbtUtils.writeGameProfile(new CompoundTag(), new GameProfile(UUID.fromString(uuid), name));
 | 
			
		||||
 | 
			
		||||
        var tag = new CompoundTag();
 | 
			
		||||
        tag.put("SkullOwner", owner);
 | 
			
		||||
        tag.put(PlayerHeadItem.TAG_SKULL_OWNER, owner);
 | 
			
		||||
        return tag;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static Consumer<JsonObject> family(ComputerFamily family) {
 | 
			
		||||
        return json -> json.addProperty("family", family.toString());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void addSpecial(Consumer<FinishedRecipe> add, SimpleCraftingRecipeSerializer<?> special) {
 | 
			
		||||
        SpecialRecipeBuilder.special(special).save(add, RegistryWrappers.RECIPE_SERIALIZERS.getKey(special).toString());
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -35,6 +35,8 @@ class TagProvider {
 | 
			
		||||
        tags.tag(ComputerCraftTags.Blocks.WIRED_MODEM).add(ModRegistry.Blocks.CABLE.get(), ModRegistry.Blocks.WIRED_MODEM_FULL.get());
 | 
			
		||||
        tags.tag(ComputerCraftTags.Blocks.MONITOR).add(ModRegistry.Blocks.MONITOR_NORMAL.get(), ModRegistry.Blocks.MONITOR_ADVANCED.get());
 | 
			
		||||
 | 
			
		||||
        tags.tag(ComputerCraftTags.Blocks.PERIPHERAL_HUB_IGNORE).addTag(ComputerCraftTags.Blocks.WIRED_MODEM);
 | 
			
		||||
 | 
			
		||||
        tags.tag(ComputerCraftTags.Blocks.TURTLE_ALWAYS_BREAKABLE).addTag(BlockTags.LEAVES).add(
 | 
			
		||||
            Blocks.BAMBOO, Blocks.BAMBOO_SAPLING // Bamboo isn't instabreak for some odd reason.
 | 
			
		||||
        );
 | 
			
		||||
@@ -56,7 +58,10 @@ class TagProvider {
 | 
			
		||||
 | 
			
		||||
        tags.tag(ComputerCraftTags.Blocks.TURTLE_SWORD_BREAKABLE).addTag(BlockTags.WOOL).add(Blocks.COBWEB);
 | 
			
		||||
 | 
			
		||||
        tags.tag(ComputerCraftTags.Blocks.TURTLE_CAN_USE).addTag(BlockTags.CAULDRONS).addTag(BlockTags.BEEHIVES);
 | 
			
		||||
        tags.tag(ComputerCraftTags.Blocks.TURTLE_CAN_USE)
 | 
			
		||||
            .addTag(BlockTags.BEEHIVES)
 | 
			
		||||
            .addTag(BlockTags.CAULDRONS)
 | 
			
		||||
            .add(Blocks.COMPOSTER);
 | 
			
		||||
 | 
			
		||||
        // Make all blocks aside from command computer mineable.
 | 
			
		||||
        tags.tag(BlockTags.MINEABLE_WITH_PICKAXE).add(
 | 
			
		||||
@@ -74,6 +79,8 @@ class TagProvider {
 | 
			
		||||
            ModRegistry.Blocks.WIRED_MODEM_FULL.get(),
 | 
			
		||||
            ModRegistry.Blocks.CABLE.get()
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        tags.tag(BlockTags.WITHER_IMMUNE).add(ModRegistry.Blocks.COMPUTER_COMMAND.get());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void itemTags(ItemTagConsumer tags) {
 | 
			
		||||
@@ -88,6 +95,8 @@ class TagProvider {
 | 
			
		||||
            ModRegistry.Items.MONITOR_ADVANCED.get()
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        tags.tag(ItemTags.BOOKSHELF_BOOKS).add(ModRegistry.Items.PRINTED_BOOK.get());
 | 
			
		||||
 | 
			
		||||
        tags.tag(ComputerCraftTags.Items.TURTLE_CAN_PLACE)
 | 
			
		||||
            .add(Items.GLASS_BOTTLE)
 | 
			
		||||
            .addTag(ItemTags.BOATS);
 | 
			
		||||
 
 | 
			
		||||
@@ -18,8 +18,8 @@ import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener;
 | 
			
		||||
import net.minecraft.util.GsonHelper;
 | 
			
		||||
import net.minecraft.util.profiling.ProfilerFiller;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import org.apache.logging.log4j.LogManager;
 | 
			
		||||
import org.apache.logging.log4j.Logger;
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
@@ -37,7 +37,7 @@ import java.util.stream.Collectors;
 | 
			
		||||
 * @see PocketUpgrades
 | 
			
		||||
 */
 | 
			
		||||
public class UpgradeManager<R extends UpgradeSerialiser<? extends T>, T extends UpgradeBase> extends SimpleJsonResourceReloadListener {
 | 
			
		||||
    private static final Logger LOGGER = LogManager.getLogger();
 | 
			
		||||
    private static final Logger LOG = LoggerFactory.getLogger(UpgradeManager.class);
 | 
			
		||||
    private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
 | 
			
		||||
 | 
			
		||||
    public record UpgradeWrapper<R extends UpgradeSerialiser<? extends T>, T extends UpgradeBase>(
 | 
			
		||||
@@ -48,8 +48,8 @@ public class UpgradeManager<R extends UpgradeSerialiser<? extends T>, T extends
 | 
			
		||||
    private final String kind;
 | 
			
		||||
    private final ResourceKey<Registry<R>> registry;
 | 
			
		||||
 | 
			
		||||
    private Map<String, UpgradeWrapper<R, T>> current = Collections.emptyMap();
 | 
			
		||||
    private Map<T, UpgradeWrapper<R, T>> currentWrappers = Collections.emptyMap();
 | 
			
		||||
    private Map<String, UpgradeWrapper<R, T>> current = Map.of();
 | 
			
		||||
    private Map<T, UpgradeWrapper<R, T>> currentWrappers = Map.of();
 | 
			
		||||
 | 
			
		||||
    public UpgradeManager(String kind, String path, ResourceKey<Registry<R>> registry) {
 | 
			
		||||
        super(GSON, path);
 | 
			
		||||
@@ -103,13 +103,13 @@ public class UpgradeManager<R extends UpgradeSerialiser<? extends T>, T extends
 | 
			
		||||
            try {
 | 
			
		||||
                loadUpgrade(newUpgrades, element.getKey(), element.getValue());
 | 
			
		||||
            } catch (IllegalArgumentException | JsonParseException e) {
 | 
			
		||||
                LOGGER.error("Error loading {} {} from JSON file", kind, element.getKey(), e);
 | 
			
		||||
                LOG.error("Error loading {} {} from JSON file", kind, element.getKey(), e);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        current = Collections.unmodifiableMap(newUpgrades);
 | 
			
		||||
        currentWrappers = newUpgrades.values().stream().collect(Collectors.toUnmodifiableMap(UpgradeWrapper::upgrade, x -> x));
 | 
			
		||||
        LOGGER.info("Loaded {} {}s", current.size(), kind);
 | 
			
		||||
        LOG.info("Loaded {} {}s", current.size(), kind);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void loadUpgrade(Map<String, UpgradeWrapper<R, T>> current, ResourceLocation id, JsonElement json) {
 | 
			
		||||
@@ -123,7 +123,7 @@ public class UpgradeManager<R extends UpgradeSerialiser<? extends T>, T extends
 | 
			
		||||
        // TODO: Can we track which mod this resource came from and use that instead? It's theoretically possible,
 | 
			
		||||
        //  but maybe not ideal for datapacks.
 | 
			
		||||
        var modId = id.getNamespace();
 | 
			
		||||
        if (modId.equals("minecraft") || modId.equals("")) modId = ComputerCraftAPI.MOD_ID;
 | 
			
		||||
        if (modId.equals("minecraft") || modId.isEmpty()) modId = ComputerCraftAPI.MOD_ID;
 | 
			
		||||
 | 
			
		||||
        var upgrade = serialiser.fromJson(id, root);
 | 
			
		||||
        if (!upgrade.getUpgradeID().equals(id)) {
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@ import java.util.HashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
final class WiredNetworkChangeImpl implements WiredNetworkChange {
 | 
			
		||||
    private static final WiredNetworkChangeImpl EMPTY = new WiredNetworkChangeImpl(Collections.emptyMap(), Collections.emptyMap());
 | 
			
		||||
    private static final WiredNetworkChangeImpl EMPTY = new WiredNetworkChangeImpl(Map.of(), Map.of());
 | 
			
		||||
 | 
			
		||||
    private final Map<String, IPeripheral> removed;
 | 
			
		||||
    private final Map<String, IPeripheral> added;
 | 
			
		||||
@@ -27,11 +27,11 @@ final class WiredNetworkChangeImpl implements WiredNetworkChange {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static WiredNetworkChangeImpl added(Map<String, IPeripheral> added) {
 | 
			
		||||
        return added.isEmpty() ? EMPTY : new WiredNetworkChangeImpl(Collections.emptyMap(), Collections.unmodifiableMap(added));
 | 
			
		||||
        return added.isEmpty() ? EMPTY : new WiredNetworkChangeImpl(Map.of(), Collections.unmodifiableMap(added));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static WiredNetworkChangeImpl removed(Map<String, IPeripheral> removed) {
 | 
			
		||||
        return removed.isEmpty() ? EMPTY : new WiredNetworkChangeImpl(Collections.unmodifiableMap(removed), Collections.emptyMap());
 | 
			
		||||
        return removed.isEmpty() ? EMPTY : new WiredNetworkChangeImpl(Collections.unmodifiableMap(removed), Map.of());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static WiredNetworkChangeImpl changeOf(Map<String, IPeripheral> oldPeripherals, Map<String, IPeripheral> newPeripherals) {
 | 
			
		||||
@@ -39,9 +39,9 @@ final class WiredNetworkChangeImpl implements WiredNetworkChange {
 | 
			
		||||
        if (oldPeripherals.isEmpty() && newPeripherals.isEmpty()) {
 | 
			
		||||
            return EMPTY;
 | 
			
		||||
        } else if (oldPeripherals.isEmpty()) {
 | 
			
		||||
            return new WiredNetworkChangeImpl(Collections.emptyMap(), newPeripherals);
 | 
			
		||||
            return new WiredNetworkChangeImpl(Map.of(), newPeripherals);
 | 
			
		||||
        } else if (newPeripherals.isEmpty()) {
 | 
			
		||||
            return new WiredNetworkChangeImpl(oldPeripherals, Collections.emptyMap());
 | 
			
		||||
            return new WiredNetworkChangeImpl(oldPeripherals, Map.of());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Map<String, IPeripheral> added = new HashMap<>(newPeripherals);
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,6 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.impl.network.wired;
 | 
			
		||||
 | 
			
		||||
import com.google.common.collect.ImmutableMap;
 | 
			
		||||
import dan200.computercraft.api.network.Packet;
 | 
			
		||||
import dan200.computercraft.api.network.wired.WiredNetwork;
 | 
			
		||||
import dan200.computercraft.api.network.wired.WiredNode;
 | 
			
		||||
@@ -24,7 +23,7 @@ final class WiredNetworkImpl implements WiredNetwork {
 | 
			
		||||
        nodes.add(node);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private WiredNetworkImpl(HashSet<WiredNodeImpl> nodes) {
 | 
			
		||||
    private WiredNetworkImpl(Set<WiredNodeImpl> nodes) {
 | 
			
		||||
        this.nodes = nodes;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -57,10 +56,10 @@ final class WiredNetworkImpl implements WiredNetwork {
 | 
			
		||||
                    // Move all nodes across into this network, destroying the original nodes.
 | 
			
		||||
                    nodes.addAll(otherNodes);
 | 
			
		||||
                    for (var node : otherNodes) node.network = this;
 | 
			
		||||
                    other.nodes = Collections.emptySet();
 | 
			
		||||
                    other.nodes = Set.of();
 | 
			
		||||
 | 
			
		||||
                    // Move all peripherals across,
 | 
			
		||||
                    other.peripherals = Collections.emptyMap();
 | 
			
		||||
                    other.peripherals = Map.of();
 | 
			
		||||
                    peripherals.putAll(otherPeripherals);
 | 
			
		||||
 | 
			
		||||
                    if (!thisPeripherals.isEmpty()) {
 | 
			
		||||
@@ -217,7 +216,7 @@ final class WiredNetworkImpl implements WiredNetwork {
 | 
			
		||||
            try {
 | 
			
		||||
                // We special case the original node: detaching all peripherals when needed.
 | 
			
		||||
                wired.network = wiredNetwork;
 | 
			
		||||
                wired.peripherals = Collections.emptyMap();
 | 
			
		||||
                wired.peripherals = Map.of();
 | 
			
		||||
 | 
			
		||||
                // Ensure every network is finalised
 | 
			
		||||
                for (var network : maximals) {
 | 
			
		||||
@@ -260,7 +259,7 @@ final class WiredNetworkImpl implements WiredNetwork {
 | 
			
		||||
            var change = WiredNetworkChangeImpl.changeOf(oldPeripherals, newPeripherals);
 | 
			
		||||
            if (change.isEmpty()) return;
 | 
			
		||||
 | 
			
		||||
            wired.peripherals = ImmutableMap.copyOf(newPeripherals);
 | 
			
		||||
            wired.peripherals = Map.copyOf(newPeripherals);
 | 
			
		||||
 | 
			
		||||
            // Detach the old peripherals then remove them.
 | 
			
		||||
            peripherals.keySet().removeAll(change.peripheralsRemoved().keySet());
 | 
			
		||||
@@ -333,7 +332,7 @@ final class WiredNetworkImpl implements WiredNetwork {
 | 
			
		||||
            // Detach the old peripherals then remove them from the old network
 | 
			
		||||
            wired.network = wiredNetwork;
 | 
			
		||||
            wired.neighbours.clear();
 | 
			
		||||
            wired.peripherals = Collections.emptyMap();
 | 
			
		||||
            wired.peripherals = Map.of();
 | 
			
		||||
 | 
			
		||||
            // Broadcast the change
 | 
			
		||||
            if (!peripherals.isEmpty()) WiredNetworkChangeImpl.removed(peripherals).broadcast(wired);
 | 
			
		||||
@@ -375,7 +374,7 @@ final class WiredNetworkImpl implements WiredNetwork {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static HashSet<WiredNodeImpl> reachableNodes(WiredNodeImpl start) {
 | 
			
		||||
    private static Set<WiredNodeImpl> reachableNodes(WiredNodeImpl start) {
 | 
			
		||||
        Queue<WiredNodeImpl> enqueued = new ArrayDeque<>();
 | 
			
		||||
        var reachable = new HashSet<WiredNodeImpl>();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -13,13 +13,16 @@ import dan200.computercraft.api.network.wired.WiredSender;
 | 
			
		||||
import dan200.computercraft.api.peripheral.IPeripheral;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
import java.util.HashSet;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
 | 
			
		||||
public final class WiredNodeImpl implements WiredNode {
 | 
			
		||||
    private @Nullable Set<PacketReceiver> receivers;
 | 
			
		||||
 | 
			
		||||
    final WiredElement element;
 | 
			
		||||
    Map<String, IPeripheral> peripherals = Collections.emptyMap();
 | 
			
		||||
    Map<String, IPeripheral> peripherals = Map.of();
 | 
			
		||||
 | 
			
		||||
    final HashSet<WiredNodeImpl> neighbours = new HashSet<>();
 | 
			
		||||
    volatile WiredNetworkImpl network;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +0,0 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.mixin;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.data.PrettyJsonWriter;
 | 
			
		||||
import org.spongepowered.asm.mixin.Mixin;
 | 
			
		||||
import org.spongepowered.asm.mixin.injection.At;
 | 
			
		||||
import org.spongepowered.asm.mixin.injection.ModifyArg;
 | 
			
		||||
 | 
			
		||||
@Mixin(targets = "net/minecraft/data/HashCache$CacheUpdater")
 | 
			
		||||
public class CacheUpdaterMixin {
 | 
			
		||||
    @SuppressWarnings("UnusedMethod")
 | 
			
		||||
    @ModifyArg(
 | 
			
		||||
        method = "writeIfNeeded",
 | 
			
		||||
        at = @At(value = "INVOKE", target = "Ljava/nio/file/Files;write(Ljava/nio/file/Path;[B[Ljava/nio/file/OpenOption;)Ljava/nio/file/Path;"),
 | 
			
		||||
        require = 0
 | 
			
		||||
    )
 | 
			
		||||
    private byte[] reformatJson(byte[] contents) {
 | 
			
		||||
        // It would be cleaner to do this inside DataProvider.saveStable, but Forge's version of Mixin doesn't allow us
 | 
			
		||||
        // to inject into interfaces.
 | 
			
		||||
        return PrettyJsonWriter.reformat(contents);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -14,12 +14,15 @@ import dan200.computercraft.shared.computer.metrics.ComputerMBean;
 | 
			
		||||
import dan200.computercraft.shared.peripheral.monitor.MonitorWatcher;
 | 
			
		||||
import dan200.computercraft.shared.util.DropConsumer;
 | 
			
		||||
import dan200.computercraft.shared.util.TickScheduler;
 | 
			
		||||
import net.minecraft.resources.ResourceKey;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.server.MinecraftServer;
 | 
			
		||||
import net.minecraft.server.dedicated.DedicatedServer;
 | 
			
		||||
import net.minecraft.server.level.ServerPlayer;
 | 
			
		||||
import net.minecraft.server.packs.resources.PreparableReloadListener;
 | 
			
		||||
import net.minecraft.world.entity.Entity;
 | 
			
		||||
import net.minecraft.world.item.CreativeModeTab;
 | 
			
		||||
import net.minecraft.world.item.CreativeModeTabs;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.level.chunk.LevelChunk;
 | 
			
		||||
import net.minecraft.world.level.storage.loot.BuiltInLootTables;
 | 
			
		||||
@@ -28,8 +31,6 @@ import net.minecraft.world.level.storage.loot.entries.LootTableReference;
 | 
			
		||||
import net.minecraft.world.level.storage.loot.providers.number.ConstantValue;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.HashSet;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
import java.util.function.BiConsumer;
 | 
			
		||||
 | 
			
		||||
@@ -75,9 +76,9 @@ public final class CommonHooks {
 | 
			
		||||
        MonitorWatcher.onWatch(chunk, player);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static final ResourceLocation LOOT_TREASURE_DISK = new ResourceLocation(ComputerCraftAPI.MOD_ID, "treasure_disk");
 | 
			
		||||
    public static final ResourceLocation TREASURE_DISK_LOOT = new ResourceLocation(ComputerCraftAPI.MOD_ID, "treasure_disk");
 | 
			
		||||
 | 
			
		||||
    private static final Set<ResourceLocation> TABLES = new HashSet<>(Arrays.asList(
 | 
			
		||||
    private static final Set<ResourceLocation> TREASURE_DISK_LOOT_TABLES = Set.of(
 | 
			
		||||
        BuiltInLootTables.SIMPLE_DUNGEON,
 | 
			
		||||
        BuiltInLootTables.ABANDONED_MINESHAFT,
 | 
			
		||||
        BuiltInLootTables.STRONGHOLD_CORRIDOR,
 | 
			
		||||
@@ -88,14 +89,15 @@ public final class CommonHooks {
 | 
			
		||||
        BuiltInLootTables.IGLOO_CHEST,
 | 
			
		||||
        BuiltInLootTables.WOODLAND_MANSION,
 | 
			
		||||
        BuiltInLootTables.VILLAGE_CARTOGRAPHER
 | 
			
		||||
    ));
 | 
			
		||||
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    public static @Nullable LootPool.Builder getExtraLootPool(ResourceLocation lootTable) {
 | 
			
		||||
        if (!lootTable.getNamespace().equals("minecraft") || !TABLES.contains(lootTable)) return null;
 | 
			
		||||
        if (!lootTable.getNamespace().equals("minecraft") || !TREASURE_DISK_LOOT_TABLES.contains(lootTable)) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return LootPool.lootPool()
 | 
			
		||||
            .add(LootTableReference.lootTableReference(LOOT_TREASURE_DISK))
 | 
			
		||||
            .add(LootTableReference.lootTableReference(TREASURE_DISK_LOOT))
 | 
			
		||||
            .setRolls(ConstantValue.exactly(1));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -112,4 +114,17 @@ public final class CommonHooks {
 | 
			
		||||
    public static boolean onLivingDrop(Entity entity, ItemStack stack) {
 | 
			
		||||
        return DropConsumer.onLivingDrop(entity, stack);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add items to an existing creative tab.
 | 
			
		||||
     *
 | 
			
		||||
     * @param key     The {@link ResourceKey} for this creative tab.
 | 
			
		||||
     * @param context Additional parameters used for building the contents.
 | 
			
		||||
     * @param out     The creative tab output to append items to.
 | 
			
		||||
     */
 | 
			
		||||
    public static void onBuildCreativeTab(ResourceKey<CreativeModeTab> key, CreativeModeTab.ItemDisplayParameters context, CreativeModeTab.Output out) {
 | 
			
		||||
        if (key == CreativeModeTabs.OP_BLOCKS && context.hasPermissions()) {
 | 
			
		||||
            out.accept(ModRegistry.Items.COMPUTER_COMMAND.get());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -24,14 +24,17 @@ import dan200.computercraft.shared.common.ClearColourRecipe;
 | 
			
		||||
import dan200.computercraft.shared.common.ColourableRecipe;
 | 
			
		||||
import dan200.computercraft.shared.common.DefaultBundledRedstoneProvider;
 | 
			
		||||
import dan200.computercraft.shared.common.HeldItemMenu;
 | 
			
		||||
import dan200.computercraft.shared.computer.blocks.CommandComputerBlock;
 | 
			
		||||
import dan200.computercraft.shared.computer.blocks.CommandComputerBlockEntity;
 | 
			
		||||
import dan200.computercraft.shared.computer.blocks.ComputerBlock;
 | 
			
		||||
import dan200.computercraft.shared.computer.blocks.ComputerBlockEntity;
 | 
			
		||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
 | 
			
		||||
import dan200.computercraft.shared.computer.inventory.ComputerMenuWithoutInventory;
 | 
			
		||||
import dan200.computercraft.shared.computer.inventory.ViewComputerMenu;
 | 
			
		||||
import dan200.computercraft.shared.computer.items.CommandComputerItem;
 | 
			
		||||
import dan200.computercraft.shared.computer.items.ComputerItem;
 | 
			
		||||
import dan200.computercraft.shared.computer.recipe.ComputerUpgradeRecipe;
 | 
			
		||||
import dan200.computercraft.shared.config.Config;
 | 
			
		||||
import dan200.computercraft.shared.data.BlockNamedEntityLootCondition;
 | 
			
		||||
import dan200.computercraft.shared.data.ConstantLootConditionSerializer;
 | 
			
		||||
import dan200.computercraft.shared.data.HasComputerIdLootCondition;
 | 
			
		||||
@@ -68,6 +71,10 @@ import dan200.computercraft.shared.pocket.items.PocketComputerItem;
 | 
			
		||||
import dan200.computercraft.shared.pocket.peripherals.PocketModem;
 | 
			
		||||
import dan200.computercraft.shared.pocket.peripherals.PocketSpeaker;
 | 
			
		||||
import dan200.computercraft.shared.pocket.recipes.PocketComputerUpgradeRecipe;
 | 
			
		||||
import dan200.computercraft.shared.recipe.CustomShapedRecipe;
 | 
			
		||||
import dan200.computercraft.shared.recipe.CustomShapelessRecipe;
 | 
			
		||||
import dan200.computercraft.shared.recipe.ImpostorShapedRecipe;
 | 
			
		||||
import dan200.computercraft.shared.recipe.ImpostorShapelessRecipe;
 | 
			
		||||
import dan200.computercraft.shared.turtle.FurnaceRefuelHandler;
 | 
			
		||||
import dan200.computercraft.shared.turtle.blocks.TurtleBlock;
 | 
			
		||||
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
 | 
			
		||||
@@ -77,8 +84,6 @@ import dan200.computercraft.shared.turtle.recipes.TurtleOverlayRecipe;
 | 
			
		||||
import dan200.computercraft.shared.turtle.recipes.TurtleRecipe;
 | 
			
		||||
import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe;
 | 
			
		||||
import dan200.computercraft.shared.turtle.upgrades.*;
 | 
			
		||||
import dan200.computercraft.shared.util.ImpostorRecipe;
 | 
			
		||||
import dan200.computercraft.shared.util.ImpostorShapelessRecipe;
 | 
			
		||||
import net.minecraft.commands.CommandSourceStack;
 | 
			
		||||
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
 | 
			
		||||
import net.minecraft.commands.synchronization.SingletonArgumentInfo;
 | 
			
		||||
@@ -97,7 +102,7 @@ import net.minecraft.world.level.block.entity.BlockEntity;
 | 
			
		||||
import net.minecraft.world.level.block.entity.BlockEntityType;
 | 
			
		||||
import net.minecraft.world.level.block.state.BlockBehaviour;
 | 
			
		||||
import net.minecraft.world.level.block.state.BlockState;
 | 
			
		||||
import net.minecraft.world.level.material.Material;
 | 
			
		||||
import net.minecraft.world.level.material.MapColor;
 | 
			
		||||
import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType;
 | 
			
		||||
 | 
			
		||||
import java.util.function.BiFunction;
 | 
			
		||||
@@ -117,7 +122,7 @@ public final class ModRegistry {
 | 
			
		||||
        static final RegistrationHelper<Block> REGISTRY = PlatformHelper.get().createRegistrationHelper(Registries.BLOCK);
 | 
			
		||||
 | 
			
		||||
        private static BlockBehaviour.Properties properties() {
 | 
			
		||||
            return BlockBehaviour.Properties.of(Material.STONE).strength(2);
 | 
			
		||||
            return BlockBehaviour.Properties.of().strength(2);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static BlockBehaviour.Properties computerProperties() {
 | 
			
		||||
@@ -127,45 +132,43 @@ public final class ModRegistry {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static BlockBehaviour.Properties turtleProperties() {
 | 
			
		||||
            return BlockBehaviour.Properties.of(Material.STONE).strength(2.5f);
 | 
			
		||||
            return BlockBehaviour.Properties.of().strength(2.5f);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static BlockBehaviour.Properties modemProperties() {
 | 
			
		||||
            return BlockBehaviour.Properties.of(Material.STONE).strength(1.5f);
 | 
			
		||||
            return BlockBehaviour.Properties.of().strength(1.5f);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static final RegistryEntry<ComputerBlock<ComputerBlockEntity>> COMPUTER_NORMAL = REGISTRY.register("computer_normal",
 | 
			
		||||
            () -> new ComputerBlock<>(computerProperties(), ComputerFamily.NORMAL, BlockEntities.COMPUTER_NORMAL));
 | 
			
		||||
            () -> new ComputerBlock<>(computerProperties().mapColor(MapColor.STONE), BlockEntities.COMPUTER_NORMAL));
 | 
			
		||||
        public static final RegistryEntry<ComputerBlock<ComputerBlockEntity>> COMPUTER_ADVANCED = REGISTRY.register("computer_advanced",
 | 
			
		||||
            () -> new ComputerBlock<>(computerProperties(), ComputerFamily.ADVANCED, BlockEntities.COMPUTER_ADVANCED));
 | 
			
		||||
            () -> new ComputerBlock<>(computerProperties().mapColor(MapColor.GOLD), BlockEntities.COMPUTER_ADVANCED));
 | 
			
		||||
 | 
			
		||||
        public static final RegistryEntry<ComputerBlock<CommandComputerBlockEntity>> COMPUTER_COMMAND = REGISTRY.register("computer_command", () -> new ComputerBlock<>(
 | 
			
		||||
            computerProperties().strength(-1, 6000000.0F),
 | 
			
		||||
            ComputerFamily.COMMAND, BlockEntities.COMPUTER_COMMAND
 | 
			
		||||
        ));
 | 
			
		||||
        public static final RegistryEntry<ComputerBlock<CommandComputerBlockEntity>> COMPUTER_COMMAND = REGISTRY.register("computer_command",
 | 
			
		||||
            () -> new CommandComputerBlock<>(computerProperties().strength(-1, 6000000.0F), BlockEntities.COMPUTER_COMMAND));
 | 
			
		||||
 | 
			
		||||
        public static final RegistryEntry<TurtleBlock> TURTLE_NORMAL = REGISTRY.register("turtle_normal",
 | 
			
		||||
            () -> new TurtleBlock(turtleProperties(), ComputerFamily.NORMAL, BlockEntities.TURTLE_NORMAL));
 | 
			
		||||
            () -> new TurtleBlock(turtleProperties().mapColor(MapColor.STONE), BlockEntities.TURTLE_NORMAL));
 | 
			
		||||
        public static final RegistryEntry<TurtleBlock> TURTLE_ADVANCED = REGISTRY.register("turtle_advanced",
 | 
			
		||||
            () -> new TurtleBlock(turtleProperties(), ComputerFamily.ADVANCED, BlockEntities.TURTLE_ADVANCED));
 | 
			
		||||
            () -> new TurtleBlock(turtleProperties().mapColor(MapColor.GOLD).explosionResistance(TurtleBlock.IMMUNE_EXPLOSION_RESISTANCE), BlockEntities.TURTLE_ADVANCED));
 | 
			
		||||
 | 
			
		||||
        public static final RegistryEntry<SpeakerBlock> SPEAKER = REGISTRY.register("speaker", () -> new SpeakerBlock(properties()));
 | 
			
		||||
        public static final RegistryEntry<DiskDriveBlock> DISK_DRIVE = REGISTRY.register("disk_drive", () -> new DiskDriveBlock(properties()));
 | 
			
		||||
        public static final RegistryEntry<PrinterBlock> PRINTER = REGISTRY.register("printer", () -> new PrinterBlock(properties()));
 | 
			
		||||
        public static final RegistryEntry<SpeakerBlock> SPEAKER = REGISTRY.register("speaker", () -> new SpeakerBlock(properties().mapColor(MapColor.STONE)));
 | 
			
		||||
        public static final RegistryEntry<DiskDriveBlock> DISK_DRIVE = REGISTRY.register("disk_drive", () -> new DiskDriveBlock(properties().mapColor(MapColor.STONE)));
 | 
			
		||||
        public static final RegistryEntry<PrinterBlock> PRINTER = REGISTRY.register("printer", () -> new PrinterBlock(properties().mapColor(MapColor.STONE)));
 | 
			
		||||
 | 
			
		||||
        public static final RegistryEntry<MonitorBlock> MONITOR_NORMAL = REGISTRY.register("monitor_normal",
 | 
			
		||||
            () -> new MonitorBlock(properties(), BlockEntities.MONITOR_NORMAL));
 | 
			
		||||
            () -> new MonitorBlock(properties().mapColor(MapColor.STONE), BlockEntities.MONITOR_NORMAL));
 | 
			
		||||
        public static final RegistryEntry<MonitorBlock> MONITOR_ADVANCED = REGISTRY.register("monitor_advanced",
 | 
			
		||||
            () -> new MonitorBlock(properties(), BlockEntities.MONITOR_ADVANCED));
 | 
			
		||||
            () -> new MonitorBlock(properties().mapColor(MapColor.GOLD), BlockEntities.MONITOR_ADVANCED));
 | 
			
		||||
 | 
			
		||||
        public static final RegistryEntry<WirelessModemBlock> WIRELESS_MODEM_NORMAL = REGISTRY.register("wireless_modem_normal",
 | 
			
		||||
            () -> new WirelessModemBlock(properties(), BlockEntities.WIRELESS_MODEM_NORMAL));
 | 
			
		||||
            () -> new WirelessModemBlock(properties().mapColor(MapColor.STONE), BlockEntities.WIRELESS_MODEM_NORMAL));
 | 
			
		||||
        public static final RegistryEntry<WirelessModemBlock> WIRELESS_MODEM_ADVANCED = REGISTRY.register("wireless_modem_advanced",
 | 
			
		||||
            () -> new WirelessModemBlock(properties(), BlockEntities.WIRELESS_MODEM_ADVANCED));
 | 
			
		||||
            () -> new WirelessModemBlock(properties().mapColor(MapColor.GOLD), BlockEntities.WIRELESS_MODEM_ADVANCED));
 | 
			
		||||
 | 
			
		||||
        public static final RegistryEntry<WiredModemFullBlock> WIRED_MODEM_FULL = REGISTRY.register("wired_modem_full",
 | 
			
		||||
            () -> new WiredModemFullBlock(modemProperties()));
 | 
			
		||||
        public static final RegistryEntry<CableBlock> CABLE = REGISTRY.register("cable", () -> new CableBlock(modemProperties()));
 | 
			
		||||
            () -> new WiredModemFullBlock(modemProperties().mapColor(MapColor.STONE)));
 | 
			
		||||
        public static final RegistryEntry<CableBlock> CABLE = REGISTRY.register("cable", () -> new CableBlock(modemProperties().mapColor(MapColor.STONE)));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static class BlockEntities {
 | 
			
		||||
@@ -188,9 +191,9 @@ public final class ModRegistry {
 | 
			
		||||
            ofBlock(Blocks.COMPUTER_COMMAND, (p, s) -> new CommandComputerBlockEntity(BlockEntities.COMPUTER_COMMAND.get(), p, s));
 | 
			
		||||
 | 
			
		||||
        public static final RegistryEntry<BlockEntityType<TurtleBlockEntity>> TURTLE_NORMAL =
 | 
			
		||||
            ofBlock(Blocks.TURTLE_NORMAL, (p, s) -> new TurtleBlockEntity(BlockEntities.TURTLE_NORMAL.get(), p, s, ComputerFamily.NORMAL));
 | 
			
		||||
            ofBlock(Blocks.TURTLE_NORMAL, (p, s) -> new TurtleBlockEntity(BlockEntities.TURTLE_NORMAL.get(), p, s, () -> Config.turtleFuelLimit, ComputerFamily.NORMAL));
 | 
			
		||||
        public static final RegistryEntry<BlockEntityType<TurtleBlockEntity>> TURTLE_ADVANCED =
 | 
			
		||||
            ofBlock(Blocks.TURTLE_ADVANCED, (p, s) -> new TurtleBlockEntity(BlockEntities.TURTLE_ADVANCED.get(), p, s, ComputerFamily.ADVANCED));
 | 
			
		||||
            ofBlock(Blocks.TURTLE_ADVANCED, (p, s) -> new TurtleBlockEntity(BlockEntities.TURTLE_ADVANCED.get(), p, s, () -> Config.advancedTurtleFuelLimit, ComputerFamily.ADVANCED));
 | 
			
		||||
 | 
			
		||||
        public static final RegistryEntry<BlockEntityType<SpeakerBlockEntity>> SPEAKER =
 | 
			
		||||
            ofBlock(Blocks.SPEAKER, (p, s) -> new SpeakerBlockEntity(BlockEntities.SPEAKER.get(), p, s));
 | 
			
		||||
@@ -222,7 +225,7 @@ public final class ModRegistry {
 | 
			
		||||
 | 
			
		||||
        public static final RegistryEntry<ComputerItem> COMPUTER_NORMAL = ofBlock(Blocks.COMPUTER_NORMAL, ComputerItem::new);
 | 
			
		||||
        public static final RegistryEntry<ComputerItem> COMPUTER_ADVANCED = ofBlock(Blocks.COMPUTER_ADVANCED, ComputerItem::new);
 | 
			
		||||
        public static final RegistryEntry<ComputerItem> COMPUTER_COMMAND = ofBlock(Blocks.COMPUTER_COMMAND, ComputerItem::new);
 | 
			
		||||
        public static final RegistryEntry<ComputerItem> COMPUTER_COMMAND = ofBlock(Blocks.COMPUTER_COMMAND, CommandComputerItem::new);
 | 
			
		||||
 | 
			
		||||
        public static final RegistryEntry<PocketComputerItem> POCKET_COMPUTER_NORMAL = REGISTRY.register("pocket_computer_normal",
 | 
			
		||||
            () -> new PocketComputerItem(properties().stacksTo(1), ComputerFamily.NORMAL));
 | 
			
		||||
@@ -307,7 +310,10 @@ public final class ModRegistry {
 | 
			
		||||
            () -> new MenuType<>(PrinterMenu::new, FeatureFlags.VANILLA_SET));
 | 
			
		||||
 | 
			
		||||
        public static final RegistryEntry<MenuType<HeldItemMenu>> PRINTOUT = REGISTRY.register("printout",
 | 
			
		||||
            () -> ContainerData.toType(HeldItemContainerData::new, HeldItemMenu::createPrintout));
 | 
			
		||||
            () -> ContainerData.toType(
 | 
			
		||||
                HeldItemContainerData::new,
 | 
			
		||||
                (id, inventory, data) -> new HeldItemMenu(Menus.PRINTOUT.get(), id, inventory.player, data.getHand())
 | 
			
		||||
            ));
 | 
			
		||||
 | 
			
		||||
        public static final RegistryEntry<MenuType<ViewComputerMenu>> VIEW_COMPUTER = REGISTRY.register("view_computer",
 | 
			
		||||
            () -> ContainerData.toType(ComputerContainerData::new, ViewComputerMenu::new));
 | 
			
		||||
@@ -357,17 +363,21 @@ public final class ModRegistry {
 | 
			
		||||
            return REGISTRY.register(name, () -> new SimpleCraftingRecipeSerializer<>(factory));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static final RegistryEntry<RecipeSerializer<CustomShapedRecipe>> SHAPED = REGISTRY.register("shaped", () -> CustomShapedRecipe.serialiser(CustomShapedRecipe::new));
 | 
			
		||||
        public static final RegistryEntry<RecipeSerializer<CustomShapelessRecipe>> SHAPELESS = REGISTRY.register("shapeless", () -> CustomShapelessRecipe.serialiser(CustomShapelessRecipe::new));
 | 
			
		||||
 | 
			
		||||
        public static final RegistryEntry<RecipeSerializer<ImpostorShapedRecipe>> IMPOSTOR_SHAPED = REGISTRY.register("impostor_shaped", () -> CustomShapedRecipe.serialiser(ImpostorShapedRecipe::new));
 | 
			
		||||
        public static final RegistryEntry<RecipeSerializer<ImpostorShapelessRecipe>> IMPOSTOR_SHAPELESS = REGISTRY.register("impostor_shapeless", () -> CustomShapelessRecipe.serialiser(ImpostorShapelessRecipe::new));
 | 
			
		||||
 | 
			
		||||
        public static final RegistryEntry<SimpleCraftingRecipeSerializer<ColourableRecipe>> DYEABLE_ITEM = simple("colour", ColourableRecipe::new);
 | 
			
		||||
        public static final RegistryEntry<SimpleCraftingRecipeSerializer<ClearColourRecipe>> DYEABLE_ITEM_CLEAR = simple("clear_colour", ClearColourRecipe::new);
 | 
			
		||||
        public static final RegistryEntry<TurtleRecipe.Serializer> TURTLE = REGISTRY.register("turtle", TurtleRecipe.Serializer::new);
 | 
			
		||||
        public static final RegistryEntry<RecipeSerializer<TurtleRecipe>> TURTLE = REGISTRY.register("turtle", () -> TurtleRecipe.validatingSerialiser(TurtleRecipe::of));
 | 
			
		||||
        public static final RegistryEntry<SimpleCraftingRecipeSerializer<TurtleUpgradeRecipe>> TURTLE_UPGRADE = simple("turtle_upgrade", TurtleUpgradeRecipe::new);
 | 
			
		||||
        public static final RegistryEntry<TurtleOverlayRecipe.Serializer> TURTLE_OVERLAY = REGISTRY.register("turtle_overlay", TurtleOverlayRecipe.Serializer::new);
 | 
			
		||||
        public static final RegistryEntry<RecipeSerializer<TurtleOverlayRecipe>> TURTLE_OVERLAY = REGISTRY.register("turtle_overlay", TurtleOverlayRecipe.Serialiser::new);
 | 
			
		||||
        public static final RegistryEntry<SimpleCraftingRecipeSerializer<PocketComputerUpgradeRecipe>> POCKET_COMPUTER_UPGRADE = simple("pocket_computer_upgrade", PocketComputerUpgradeRecipe::new);
 | 
			
		||||
        public static final RegistryEntry<SimpleCraftingRecipeSerializer<PrintoutRecipe>> PRINTOUT = simple("printout", PrintoutRecipe::new);
 | 
			
		||||
        public static final RegistryEntry<SimpleCraftingRecipeSerializer<DiskRecipe>> DISK = simple("disk", DiskRecipe::new);
 | 
			
		||||
        public static final RegistryEntry<ComputerUpgradeRecipe.Serializer> COMPUTER_UPGRADE = REGISTRY.register("computer_upgrade", ComputerUpgradeRecipe.Serializer::new);
 | 
			
		||||
        public static final RegistryEntry<ImpostorRecipe.Serializer> IMPOSTOR_SHAPED = REGISTRY.register("impostor_shaped", ImpostorRecipe.Serializer::new);
 | 
			
		||||
        public static final RegistryEntry<ImpostorShapelessRecipe.Serializer> IMPOSTOR_SHAPELESS = REGISTRY.register("impostor_shapeless", ImpostorShapelessRecipe.Serializer::new);
 | 
			
		||||
        public static final RegistryEntry<RecipeSerializer<ComputerUpgradeRecipe>> COMPUTER_UPGRADE = REGISTRY.register("computer_upgrade", () -> CustomShapedRecipe.validatingSerialiser(ComputerUpgradeRecipe::of));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static class Permissions {
 | 
			
		||||
@@ -382,51 +392,11 @@ public final class ModRegistry {
 | 
			
		||||
        public static final Predicate<CommandSourceStack> PERMISSION_VIEW = REGISTRY.registerCommand("view", UserLevel.OP);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Register any objects which don't have to be done on the main thread.
 | 
			
		||||
     */
 | 
			
		||||
    public static void register() {
 | 
			
		||||
        Blocks.REGISTRY.register();
 | 
			
		||||
        BlockEntities.REGISTRY.register();
 | 
			
		||||
        Items.REGISTRY.register();
 | 
			
		||||
        TurtleSerialisers.REGISTRY.register();
 | 
			
		||||
        PocketUpgradeSerialisers.REGISTRY.register();
 | 
			
		||||
        Menus.REGISTRY.register();
 | 
			
		||||
        ArgumentTypes.REGISTRY.register();
 | 
			
		||||
        LootItemConditionTypes.REGISTRY.register();
 | 
			
		||||
        RecipeSerializers.REGISTRY.register();
 | 
			
		||||
        Permissions.REGISTRY.register();
 | 
			
		||||
    static class CreativeTabs {
 | 
			
		||||
        static final RegistrationHelper<CreativeModeTab> REGISTRY = PlatformHelper.get().createRegistrationHelper(Registries.CREATIVE_MODE_TAB);
 | 
			
		||||
 | 
			
		||||
        // Register bundled power providers
 | 
			
		||||
        ComputerCraftAPI.registerBundledRedstoneProvider(new DefaultBundledRedstoneProvider());
 | 
			
		||||
        ComputerCraftAPI.registerRefuelHandler(new FurnaceRefuelHandler());
 | 
			
		||||
        ComputerCraftAPI.registerMediaProvider(stack -> {
 | 
			
		||||
            var item = stack.getItem();
 | 
			
		||||
            if (item instanceof IMedia media) return media;
 | 
			
		||||
            if (item instanceof RecordItem) return RecordMedia.INSTANCE;
 | 
			
		||||
            return null;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        VanillaDetailRegistries.ITEM_STACK.addProvider(ItemDetails::fill);
 | 
			
		||||
        VanillaDetailRegistries.BLOCK_IN_WORLD.addProvider(BlockDetails::fill);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Register any objects which must be done on the main thread.
 | 
			
		||||
     */
 | 
			
		||||
    public static void registerMainThread() {
 | 
			
		||||
        CauldronInteraction.WATER.put(ModRegistry.Items.TURTLE_NORMAL.get(), TurtleItem.CAULDRON_INTERACTION);
 | 
			
		||||
        CauldronInteraction.WATER.put(ModRegistry.Items.TURTLE_ADVANCED.get(), TurtleItem.CAULDRON_INTERACTION);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Configure a {@link CreativeModeTab.Builder} to contain all of ComputerCraft's items.
 | 
			
		||||
     *
 | 
			
		||||
     * @param builder The builder to configure.
 | 
			
		||||
     * @return The same building, for calling {@link CreativeModeTab.Builder#build()} on.
 | 
			
		||||
     */
 | 
			
		||||
    public static CreativeModeTab.Builder registerCreativeTab(CreativeModeTab.Builder builder) {
 | 
			
		||||
        return builder
 | 
			
		||||
        @SuppressWarnings("unused")
 | 
			
		||||
        private static final RegistryEntry<CreativeModeTab> TAB = REGISTRY.register("tab", () -> PlatformHelper.get().newCreativeModeTab()
 | 
			
		||||
            .icon(() -> new ItemStack(Items.COMPUTER_NORMAL.get()))
 | 
			
		||||
            .title(Component.translatable("itemGroup.computercraft"))
 | 
			
		||||
            .displayItems((context, out) -> {
 | 
			
		||||
@@ -458,7 +428,46 @@ public final class ModRegistry {
 | 
			
		||||
                for (var colour = 0; colour < 16; colour++) {
 | 
			
		||||
                    out.accept(DiskItem.createFromIDAndColour(-1, null, Colour.VALUES[colour].getHex()));
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            })
 | 
			
		||||
            .build());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Register any objects which don't have to be done on the main thread.
 | 
			
		||||
     */
 | 
			
		||||
    public static void register() {
 | 
			
		||||
        Blocks.REGISTRY.register();
 | 
			
		||||
        BlockEntities.REGISTRY.register();
 | 
			
		||||
        Items.REGISTRY.register();
 | 
			
		||||
        TurtleSerialisers.REGISTRY.register();
 | 
			
		||||
        PocketUpgradeSerialisers.REGISTRY.register();
 | 
			
		||||
        Menus.REGISTRY.register();
 | 
			
		||||
        ArgumentTypes.REGISTRY.register();
 | 
			
		||||
        LootItemConditionTypes.REGISTRY.register();
 | 
			
		||||
        RecipeSerializers.REGISTRY.register();
 | 
			
		||||
        Permissions.REGISTRY.register();
 | 
			
		||||
        CreativeTabs.REGISTRY.register();
 | 
			
		||||
 | 
			
		||||
        // Register bundled power providers
 | 
			
		||||
        ComputerCraftAPI.registerBundledRedstoneProvider(new DefaultBundledRedstoneProvider());
 | 
			
		||||
        ComputerCraftAPI.registerRefuelHandler(new FurnaceRefuelHandler());
 | 
			
		||||
        ComputerCraftAPI.registerMediaProvider(stack -> {
 | 
			
		||||
            var item = stack.getItem();
 | 
			
		||||
            if (item instanceof IMedia media) return media;
 | 
			
		||||
            if (item instanceof RecordItem) return RecordMedia.INSTANCE;
 | 
			
		||||
            return null;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        VanillaDetailRegistries.ITEM_STACK.addProvider(ItemDetails::fill);
 | 
			
		||||
        VanillaDetailRegistries.BLOCK_IN_WORLD.addProvider(BlockDetails::fill);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Register any objects which must be done on the main thread.
 | 
			
		||||
     */
 | 
			
		||||
    public static void registerMainThread() {
 | 
			
		||||
        CauldronInteraction.WATER.put(Items.TURTLE_NORMAL.get(), TurtleItem.CAULDRON_INTERACTION);
 | 
			
		||||
        CauldronInteraction.WATER.put(Items.TURTLE_ADVANCED.get(), TurtleItem.CAULDRON_INTERACTION);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void addTurtle(CreativeModeTab.Output out, TurtleItem turtle) {
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,6 @@ import dan200.computercraft.shared.network.container.ComputerContainerData;
 | 
			
		||||
import net.minecraft.commands.CommandSourceStack;
 | 
			
		||||
import net.minecraft.core.BlockPos;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
import net.minecraft.server.level.ServerPlayer;
 | 
			
		||||
import net.minecraft.world.MenuProvider;
 | 
			
		||||
import net.minecraft.world.entity.RelativeMovement;
 | 
			
		||||
import net.minecraft.world.entity.player.Inventory;
 | 
			
		||||
@@ -34,13 +33,15 @@ import net.minecraft.world.entity.player.Player;
 | 
			
		||||
import net.minecraft.world.inventory.AbstractContainerMenu;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.level.Level;
 | 
			
		||||
import net.minecraft.world.phys.Vec3;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
 | 
			
		||||
import static dan200.computercraft.shared.command.CommandUtils.isPlayer;
 | 
			
		||||
import static dan200.computercraft.shared.command.Exceptions.*;
 | 
			
		||||
import static dan200.computercraft.shared.command.Exceptions.NOT_TRACKING_EXCEPTION;
 | 
			
		||||
import static dan200.computercraft.shared.command.Exceptions.NO_TIMINGS_EXCEPTION;
 | 
			
		||||
import static dan200.computercraft.shared.command.arguments.ComputerArgumentType.getComputerArgument;
 | 
			
		||||
import static dan200.computercraft.shared.command.arguments.ComputerArgumentType.oneComputer;
 | 
			
		||||
import static dan200.computercraft.shared.command.arguments.ComputersArgumentType.*;
 | 
			
		||||
@@ -53,7 +54,12 @@ import static net.minecraft.commands.Commands.literal;
 | 
			
		||||
 | 
			
		||||
public final class CommandComputerCraft {
 | 
			
		||||
    public static final UUID SYSTEM_UUID = new UUID(0, 0);
 | 
			
		||||
    public static final String OPEN_COMPUTER = "computercraft open-computer ";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The client-side command to open the folder. Ideally this would live under the main {@code computercraft}
 | 
			
		||||
     * namespace, but unfortunately that overrides commands, rather than merging them.
 | 
			
		||||
     */
 | 
			
		||||
    public static final String CLIENT_OPEN_FOLDER = "computercraft-computer-folder";
 | 
			
		||||
 | 
			
		||||
    private CommandComputerCraft() {
 | 
			
		||||
    }
 | 
			
		||||
@@ -62,114 +68,25 @@ public final class CommandComputerCraft {
 | 
			
		||||
        dispatcher.register(choice("computercraft")
 | 
			
		||||
            .then(literal("dump")
 | 
			
		||||
                .requires(ModRegistry.Permissions.PERMISSION_DUMP)
 | 
			
		||||
                .executes(context -> {
 | 
			
		||||
                    var table = new TableBuilder("DumpAll", "Computer", "On", "Position");
 | 
			
		||||
 | 
			
		||||
                    var source = context.getSource();
 | 
			
		||||
                    List<ServerComputer> computers = new ArrayList<>(ServerContext.get(source.getServer()).registry().getComputers());
 | 
			
		||||
 | 
			
		||||
                    Level world = source.getLevel();
 | 
			
		||||
                    var pos = BlockPos.containing(source.getPosition());
 | 
			
		||||
 | 
			
		||||
                    computers.sort((a, b) -> {
 | 
			
		||||
                        if (a.getLevel() == b.getLevel() && a.getLevel() == world) {
 | 
			
		||||
                            return Double.compare(a.getPosition().distSqr(pos), b.getPosition().distSqr(pos));
 | 
			
		||||
                        } else if (a.getLevel() == world) {
 | 
			
		||||
                            return -1;
 | 
			
		||||
                        } else if (b.getLevel() == world) {
 | 
			
		||||
                            return 1;
 | 
			
		||||
                        } else {
 | 
			
		||||
                            return Integer.compare(a.getInstanceID(), b.getInstanceID());
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    for (var computer : computers) {
 | 
			
		||||
                        table.row(
 | 
			
		||||
                            linkComputer(source, computer, computer.getID()),
 | 
			
		||||
                            bool(computer.isOn()),
 | 
			
		||||
                            linkPosition(source, computer)
 | 
			
		||||
                        );
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    table.display(context.getSource());
 | 
			
		||||
                    return computers.size();
 | 
			
		||||
                })
 | 
			
		||||
                .executes(c -> dump(c.getSource()))
 | 
			
		||||
                .then(args()
 | 
			
		||||
                    .arg("computer", oneComputer())
 | 
			
		||||
                    .executes(context -> {
 | 
			
		||||
                        var computer = getComputerArgument(context, "computer");
 | 
			
		||||
 | 
			
		||||
                        var table = new TableBuilder("Dump");
 | 
			
		||||
                        table.row(header("Instance"), text(Integer.toString(computer.getInstanceID())));
 | 
			
		||||
                        table.row(header("Id"), text(Integer.toString(computer.getID())));
 | 
			
		||||
                        table.row(header("Label"), text(computer.getLabel()));
 | 
			
		||||
                        table.row(header("On"), bool(computer.isOn()));
 | 
			
		||||
                        table.row(header("Position"), linkPosition(context.getSource(), computer));
 | 
			
		||||
                        table.row(header("Family"), text(computer.getFamily().toString()));
 | 
			
		||||
 | 
			
		||||
                        for (var side : ComputerSide.values()) {
 | 
			
		||||
                            var peripheral = computer.getPeripheral(side);
 | 
			
		||||
                            if (peripheral != null) {
 | 
			
		||||
                                table.row(header("Peripheral " + side.getName()), text(peripheral.getType()));
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        table.display(context.getSource());
 | 
			
		||||
                        return 1;
 | 
			
		||||
                    })))
 | 
			
		||||
                    .executes(c -> dumpComputer(c.getSource(), getComputerArgument(c, "computer")))))
 | 
			
		||||
 | 
			
		||||
            .then(command("shutdown")
 | 
			
		||||
                .requires(ModRegistry.Permissions.PERMISSION_SHUTDOWN)
 | 
			
		||||
                .argManyValue("computers", manyComputers(), s -> ServerContext.get(s.getServer()).registry().getComputers())
 | 
			
		||||
                .executes((context, computerSelectors) -> {
 | 
			
		||||
                    var shutdown = 0;
 | 
			
		||||
                    var computers = unwrap(context.getSource(), computerSelectors);
 | 
			
		||||
                    for (var computer : computers) {
 | 
			
		||||
                        if (computer.isOn()) shutdown++;
 | 
			
		||||
                        computer.shutdown();
 | 
			
		||||
                    }
 | 
			
		||||
                    context.getSource().sendSuccess(Component.translatable("commands.computercraft.shutdown.done", shutdown, computers.size()), false);
 | 
			
		||||
                    return shutdown;
 | 
			
		||||
                }))
 | 
			
		||||
                .executes((c, a) -> shutdown(c.getSource(), unwrap(c.getSource(), a))))
 | 
			
		||||
 | 
			
		||||
            .then(command("turn-on")
 | 
			
		||||
                .requires(ModRegistry.Permissions.PERMISSION_TURN_ON)
 | 
			
		||||
                .argManyValue("computers", manyComputers(), s -> ServerContext.get(s.getServer()).registry().getComputers())
 | 
			
		||||
                .executes((context, computerSelectors) -> {
 | 
			
		||||
                    var on = 0;
 | 
			
		||||
                    var computers = unwrap(context.getSource(), computerSelectors);
 | 
			
		||||
                    for (var computer : computers) {
 | 
			
		||||
                        if (!computer.isOn()) on++;
 | 
			
		||||
                        computer.turnOn();
 | 
			
		||||
                    }
 | 
			
		||||
                    context.getSource().sendSuccess(Component.translatable("commands.computercraft.turn_on.done", on, computers.size()), false);
 | 
			
		||||
                    return on;
 | 
			
		||||
                }))
 | 
			
		||||
                .executes((c, a) -> turnOn(c.getSource(), unwrap(c.getSource(), a))))
 | 
			
		||||
 | 
			
		||||
            .then(command("tp")
 | 
			
		||||
                .requires(ModRegistry.Permissions.PERMISSION_TP)
 | 
			
		||||
                .arg("computer", oneComputer())
 | 
			
		||||
                .executes(context -> {
 | 
			
		||||
                    var computer = getComputerArgument(context, "computer");
 | 
			
		||||
                    var world = computer.getLevel();
 | 
			
		||||
                    var pos = computer.getPosition();
 | 
			
		||||
 | 
			
		||||
                    var entity = context.getSource().getEntityOrException();
 | 
			
		||||
                    if (!(entity instanceof ServerPlayer player)) throw TP_NOT_PLAYER.create();
 | 
			
		||||
 | 
			
		||||
                    if (player.getCommandSenderWorld() == world) {
 | 
			
		||||
                        player.connection.teleport(
 | 
			
		||||
                            pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, 0, 0,
 | 
			
		||||
                            EnumSet.noneOf(RelativeMovement.class)
 | 
			
		||||
                        );
 | 
			
		||||
                    } else {
 | 
			
		||||
                        player.teleportTo(world,
 | 
			
		||||
                            pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, 0, 0
 | 
			
		||||
                        );
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    return 1;
 | 
			
		||||
                }))
 | 
			
		||||
                .executes(c -> teleport(c.getSource(), getComputerArgument(c, "computer"))))
 | 
			
		||||
 | 
			
		||||
            .then(command("queue")
 | 
			
		||||
                .requires(ModRegistry.Permissions.PERMISSION_QUEUE)
 | 
			
		||||
@@ -177,80 +94,244 @@ public final class CommandComputerCraft {
 | 
			
		||||
                    RequiredArgumentBuilder.<CommandSourceStack, ComputersArgumentType.ComputersSupplier>argument("computer", manyComputers())
 | 
			
		||||
                        .suggests((context, builder) -> Suggestions.empty())
 | 
			
		||||
                )
 | 
			
		||||
                .argManyValue("args", StringArgumentType.string(), Collections.emptyList())
 | 
			
		||||
                .executes((ctx, args) -> {
 | 
			
		||||
                    var computers = getComputersArgument(ctx, "computer");
 | 
			
		||||
                    var rest = args.toArray();
 | 
			
		||||
 | 
			
		||||
                    var queued = 0;
 | 
			
		||||
                    for (var computer : computers) {
 | 
			
		||||
                        if (computer.getFamily() == ComputerFamily.COMMAND && computer.isOn()) {
 | 
			
		||||
                            computer.queueEvent("computer_command", rest);
 | 
			
		||||
                            queued++;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    return queued;
 | 
			
		||||
                }))
 | 
			
		||||
                .argManyValue("args", StringArgumentType.string(), List.of())
 | 
			
		||||
                .executes((c, a) -> queue(getComputersArgument(c, "computer"), a)))
 | 
			
		||||
 | 
			
		||||
            .then(command("view")
 | 
			
		||||
                .requires(ModRegistry.Permissions.PERMISSION_VIEW)
 | 
			
		||||
                .arg("computer", oneComputer())
 | 
			
		||||
                .executes(context -> {
 | 
			
		||||
                    var player = context.getSource().getPlayerOrException();
 | 
			
		||||
                    var computer = getComputerArgument(context, "computer");
 | 
			
		||||
                    new ComputerContainerData(computer, ItemStack.EMPTY).open(player, new MenuProvider() {
 | 
			
		||||
                        @Override
 | 
			
		||||
                        public Component getDisplayName() {
 | 
			
		||||
                            return Component.translatable("gui.computercraft.view_computer");
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        @Override
 | 
			
		||||
                        public AbstractContainerMenu createMenu(int id, Inventory player, Player entity) {
 | 
			
		||||
                            return new ViewComputerMenu(id, player, computer);
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                    return 1;
 | 
			
		||||
                }))
 | 
			
		||||
                .executes(c -> view(c.getSource(), getComputerArgument(c, "computer"))))
 | 
			
		||||
 | 
			
		||||
            .then(choice("track")
 | 
			
		||||
                .requires(ModRegistry.Permissions.PERMISSION_TRACK)
 | 
			
		||||
                .then(command("start")
 | 
			
		||||
                    .executes(context -> {
 | 
			
		||||
                        getMetricsInstance(context.getSource()).start();
 | 
			
		||||
 | 
			
		||||
                        var stopCommand = "/computercraft track stop";
 | 
			
		||||
                        context.getSource().sendSuccess(Component.translatable(
 | 
			
		||||
                            "commands.computercraft.track.start.stop",
 | 
			
		||||
                            link(text(stopCommand), stopCommand, Component.translatable("commands.computercraft.track.stop.action"))
 | 
			
		||||
                        ), false);
 | 
			
		||||
                        return 1;
 | 
			
		||||
                    }))
 | 
			
		||||
 | 
			
		||||
                .then(command("stop")
 | 
			
		||||
                    .executes(context -> {
 | 
			
		||||
                        var timings = getMetricsInstance(context.getSource());
 | 
			
		||||
                        if (!timings.stop()) throw NOT_TRACKING_EXCEPTION.create();
 | 
			
		||||
                        displayTimings(context.getSource(), timings.getSnapshot(), new AggregatedMetric(Metrics.COMPUTER_TASKS, Aggregate.AVG), DEFAULT_FIELDS);
 | 
			
		||||
                        return 1;
 | 
			
		||||
                    }))
 | 
			
		||||
 | 
			
		||||
                .then(command("start").executes(c -> trackStart(c.getSource())))
 | 
			
		||||
                .then(command("stop").executes(c -> trackStop(c.getSource())))
 | 
			
		||||
                .then(command("dump")
 | 
			
		||||
                    .argManyValue("fields", metric(), DEFAULT_FIELDS)
 | 
			
		||||
                    .executes((context, fields) -> {
 | 
			
		||||
                        AggregatedMetric sort;
 | 
			
		||||
                        if (fields.size() == 1 && DEFAULT_FIELDS.contains(fields.get(0))) {
 | 
			
		||||
                            sort = fields.get(0);
 | 
			
		||||
                            fields = DEFAULT_FIELDS;
 | 
			
		||||
                        } else {
 | 
			
		||||
                            sort = fields.get(0);
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        return displayTimings(context.getSource(), sort, fields);
 | 
			
		||||
                    })))
 | 
			
		||||
                    .executes((c, f) -> trackDump(c.getSource(), f))))
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Display loaded computers to a table.
 | 
			
		||||
     *
 | 
			
		||||
     * @param source The thing that executed this command.
 | 
			
		||||
     * @return The number of loaded computers.
 | 
			
		||||
     */
 | 
			
		||||
    private static int dump(CommandSourceStack source) {
 | 
			
		||||
        var table = new TableBuilder("DumpAll", "Computer", "On", "Position");
 | 
			
		||||
 | 
			
		||||
        List<ServerComputer> computers = new ArrayList<>(ServerContext.get(source.getServer()).registry().getComputers());
 | 
			
		||||
 | 
			
		||||
        Level world = source.getLevel();
 | 
			
		||||
        var pos = BlockPos.containing(source.getPosition());
 | 
			
		||||
 | 
			
		||||
        // Sort by nearby computers.
 | 
			
		||||
        computers.sort((a, b) -> {
 | 
			
		||||
            if (a.getLevel() == b.getLevel() && a.getLevel() == world) {
 | 
			
		||||
                return Double.compare(a.getPosition().distSqr(pos), b.getPosition().distSqr(pos));
 | 
			
		||||
            } else if (a.getLevel() == world) {
 | 
			
		||||
                return -1;
 | 
			
		||||
            } else if (b.getLevel() == world) {
 | 
			
		||||
                return 1;
 | 
			
		||||
            } else {
 | 
			
		||||
                return Integer.compare(a.getInstanceID(), b.getInstanceID());
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        for (var computer : computers) {
 | 
			
		||||
            table.row(
 | 
			
		||||
                linkComputer(source, computer, computer.getID()),
 | 
			
		||||
                bool(computer.isOn()),
 | 
			
		||||
                linkPosition(source, computer)
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        table.display(source);
 | 
			
		||||
        return computers.size();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Display additional information about a single computer.
 | 
			
		||||
     *
 | 
			
		||||
     * @param source   The thing that executed this command.
 | 
			
		||||
     * @param computer The computer we're dumping.
 | 
			
		||||
     * @return The constant {@code 1}.
 | 
			
		||||
     */
 | 
			
		||||
    private static int dumpComputer(CommandSourceStack source, ServerComputer computer) {
 | 
			
		||||
        var table = new TableBuilder("Dump");
 | 
			
		||||
        table.row(header("Instance"), text(Integer.toString(computer.getInstanceID())));
 | 
			
		||||
        table.row(header("Id"), text(Integer.toString(computer.getID())));
 | 
			
		||||
        table.row(header("Label"), text(computer.getLabel()));
 | 
			
		||||
        table.row(header("On"), bool(computer.isOn()));
 | 
			
		||||
        table.row(header("Position"), linkPosition(source, computer));
 | 
			
		||||
        table.row(header("Family"), text(computer.getFamily().toString()));
 | 
			
		||||
 | 
			
		||||
        for (var side : ComputerSide.values()) {
 | 
			
		||||
            var peripheral = computer.getPeripheral(side);
 | 
			
		||||
            if (peripheral != null) {
 | 
			
		||||
                table.row(header("Peripheral " + side.getName()), text(peripheral.getType()));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        table.display(source);
 | 
			
		||||
        return 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Shutdown a list of computers.
 | 
			
		||||
     *
 | 
			
		||||
     * @param source    The thing that executed this command.
 | 
			
		||||
     * @param computers The computers to shutdown.
 | 
			
		||||
     * @return The constant {@code 1}.
 | 
			
		||||
     */
 | 
			
		||||
    private static int shutdown(CommandSourceStack source, Collection<ServerComputer> computers) {
 | 
			
		||||
        var shutdown = 0;
 | 
			
		||||
        for (var computer : computers) {
 | 
			
		||||
            if (computer.isOn()) shutdown++;
 | 
			
		||||
            computer.shutdown();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var didShutdown = shutdown;
 | 
			
		||||
        source.sendSuccess(() -> Component.translatable("commands.computercraft.shutdown.done", didShutdown, computers.size()), false);
 | 
			
		||||
        return shutdown;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Turn on a list of computers.
 | 
			
		||||
     *
 | 
			
		||||
     * @param source    The thing that executed this command.
 | 
			
		||||
     * @param computers The computers to turn on.
 | 
			
		||||
     * @return The constant {@code 1}.
 | 
			
		||||
     */
 | 
			
		||||
    private static int turnOn(CommandSourceStack source, Collection<ServerComputer> computers) {
 | 
			
		||||
        var on = 0;
 | 
			
		||||
        for (var computer : computers) {
 | 
			
		||||
            if (!computer.isOn()) on++;
 | 
			
		||||
            computer.turnOn();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var didOn = on;
 | 
			
		||||
        source.sendSuccess(() -> Component.translatable("commands.computercraft.turn_on.done", didOn, computers.size()), false);
 | 
			
		||||
        return on;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Teleport to a computer.
 | 
			
		||||
     *
 | 
			
		||||
     * @param source   The thing that executed this command. This must be an entity, other types will throw an exception.
 | 
			
		||||
     * @param computer The computer to teleport to.
 | 
			
		||||
     * @return The constant {@code 1}.
 | 
			
		||||
     */
 | 
			
		||||
    private static int teleport(CommandSourceStack source, ServerComputer computer) throws CommandSyntaxException {
 | 
			
		||||
        var world = computer.getLevel();
 | 
			
		||||
        var pos = Vec3.atBottomCenterOf(computer.getPosition());
 | 
			
		||||
        source.getEntityOrException().teleportTo(world, pos.x(), pos.y(), pos.z(), EnumSet.noneOf(RelativeMovement.class), 0, 0);
 | 
			
		||||
 | 
			
		||||
        return 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Queue a {@code computer_command} event on a command computer.
 | 
			
		||||
     *
 | 
			
		||||
     * @param computers The list of computers to queue on.
 | 
			
		||||
     * @param args      The arguments for this event.
 | 
			
		||||
     * @return The number of computers this event was queued on.
 | 
			
		||||
     */
 | 
			
		||||
    private static int queue(Collection<ServerComputer> computers, List<String> args) {
 | 
			
		||||
        var rest = args.toArray();
 | 
			
		||||
 | 
			
		||||
        var queued = 0;
 | 
			
		||||
        for (var computer : computers) {
 | 
			
		||||
            if (computer.getFamily() == ComputerFamily.COMMAND && computer.isOn()) {
 | 
			
		||||
                computer.queueEvent("computer_command", rest);
 | 
			
		||||
                queued++;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return queued;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Open a terminal for a computer.
 | 
			
		||||
     *
 | 
			
		||||
     * @param source   The thing that executed this command.
 | 
			
		||||
     * @param computer The computer to view.
 | 
			
		||||
     * @return The constant {@code 1}.
 | 
			
		||||
     */
 | 
			
		||||
    private static int view(CommandSourceStack source, ServerComputer computer) throws CommandSyntaxException {
 | 
			
		||||
        var player = source.getPlayerOrException();
 | 
			
		||||
        new ComputerContainerData(computer, ItemStack.EMPTY).open(player, new MenuProvider() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public Component getDisplayName() {
 | 
			
		||||
                return Component.translatable("gui.computercraft.view_computer");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public AbstractContainerMenu createMenu(int id, Inventory player, Player entity) {
 | 
			
		||||
                return new ViewComputerMenu(id, player, computer);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        return 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Start tracking metrics for the current player.
 | 
			
		||||
     *
 | 
			
		||||
     * @param source The thing that executed this command.
 | 
			
		||||
     * @return The constant {@code 1}.
 | 
			
		||||
     */
 | 
			
		||||
    private static int trackStart(CommandSourceStack source) {
 | 
			
		||||
        getMetricsInstance(source).start();
 | 
			
		||||
 | 
			
		||||
        var stopCommand = "/computercraft track stop";
 | 
			
		||||
        source.sendSuccess(() -> Component.translatable(
 | 
			
		||||
            "commands.computercraft.track.start.stop",
 | 
			
		||||
            link(text(stopCommand), stopCommand, Component.translatable("commands.computercraft.track.stop.action"))
 | 
			
		||||
        ), false);
 | 
			
		||||
        return 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Stop tracking metrics for the current player, displaying a table with the results.
 | 
			
		||||
     *
 | 
			
		||||
     * @param source The thing that executed this command.
 | 
			
		||||
     * @return The constant {@code 1}.
 | 
			
		||||
     */
 | 
			
		||||
    private static int trackStop(CommandSourceStack source) throws CommandSyntaxException {
 | 
			
		||||
        var metrics = getMetricsInstance(source);
 | 
			
		||||
        if (!metrics.stop()) throw NOT_TRACKING_EXCEPTION.create();
 | 
			
		||||
        displayTimings(source, metrics.getSnapshot(), new AggregatedMetric(Metrics.COMPUTER_TASKS, Aggregate.AVG), DEFAULT_FIELDS);
 | 
			
		||||
        return 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static final List<AggregatedMetric> DEFAULT_FIELDS = List.of(
 | 
			
		||||
        new AggregatedMetric(Metrics.COMPUTER_TASKS, Aggregate.COUNT),
 | 
			
		||||
        new AggregatedMetric(Metrics.COMPUTER_TASKS, Aggregate.NONE),
 | 
			
		||||
        new AggregatedMetric(Metrics.COMPUTER_TASKS, Aggregate.AVG)
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Display the latest metrics for the current player.
 | 
			
		||||
     *
 | 
			
		||||
     * @param source The thing that executed this command.
 | 
			
		||||
     * @param fields The fields to display in this table, defaulting to {@link #DEFAULT_FIELDS}.
 | 
			
		||||
     * @return The constant {@code 1}.
 | 
			
		||||
     */
 | 
			
		||||
    private static int trackDump(CommandSourceStack source, List<AggregatedMetric> fields) throws CommandSyntaxException {
 | 
			
		||||
        AggregatedMetric sort;
 | 
			
		||||
        if (fields.size() == 1 && DEFAULT_FIELDS.contains(fields.get(0))) {
 | 
			
		||||
            sort = fields.get(0);
 | 
			
		||||
            fields = DEFAULT_FIELDS;
 | 
			
		||||
        } else {
 | 
			
		||||
            sort = fields.get(0);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return displayTimings(source, getMetricsInstance(source).getTimings(), sort, fields);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Additional helper functions.
 | 
			
		||||
 | 
			
		||||
    private static Component linkComputer(CommandSourceStack source, @Nullable ServerComputer serverComputer, int computerId) {
 | 
			
		||||
        var out = Component.literal("");
 | 
			
		||||
 | 
			
		||||
@@ -313,7 +394,7 @@ public final class CommandComputerCraft {
 | 
			
		||||
 | 
			
		||||
        return link(
 | 
			
		||||
            text("\u270E"),
 | 
			
		||||
            "/" + OPEN_COMPUTER + id,
 | 
			
		||||
            "/" + CLIENT_OPEN_FOLDER + " " + id,
 | 
			
		||||
            Component.translatable("commands.computercraft.dump.open_path")
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
@@ -323,16 +404,6 @@ public final class CommandComputerCraft {
 | 
			
		||||
        return ServerContext.get(source.getServer()).metrics().getMetricsInstance(entity instanceof Player ? entity.getUUID() : SYSTEM_UUID);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static final List<AggregatedMetric> DEFAULT_FIELDS = Arrays.asList(
 | 
			
		||||
        new AggregatedMetric(Metrics.COMPUTER_TASKS, Aggregate.COUNT),
 | 
			
		||||
        new AggregatedMetric(Metrics.COMPUTER_TASKS, Aggregate.NONE),
 | 
			
		||||
        new AggregatedMetric(Metrics.COMPUTER_TASKS, Aggregate.AVG)
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    private static int displayTimings(CommandSourceStack source, AggregatedMetric sortField, List<AggregatedMetric> fields) throws CommandSyntaxException {
 | 
			
		||||
        return displayTimings(source, getMetricsInstance(source).getTimings(), sortField, fields);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static int displayTimings(CommandSourceStack source, List<ComputerMetrics> timings, AggregatedMetric sortField, List<AggregatedMetric> fields) throws CommandSyntaxException {
 | 
			
		||||
        if (timings.isEmpty()) throw NO_TIMINGS_EXCEPTION.create();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -18,9 +18,6 @@ public final class Exceptions {
 | 
			
		||||
    static final SimpleCommandExceptionType NOT_TRACKING_EXCEPTION = translated("commands.computercraft.track.stop.not_enabled");
 | 
			
		||||
    static final SimpleCommandExceptionType NO_TIMINGS_EXCEPTION = translated("commands.computercraft.track.dump.no_timings");
 | 
			
		||||
 | 
			
		||||
    static final SimpleCommandExceptionType TP_NOT_THERE = translated("commands.computercraft.tp.not_there");
 | 
			
		||||
    static final SimpleCommandExceptionType TP_NOT_PLAYER = translated("commands.computercraft.tp.not_player");
 | 
			
		||||
 | 
			
		||||
    public static final SimpleCommandExceptionType ARGUMENT_EXPECTED = translated("argument.computercraft.argument_expected");
 | 
			
		||||
 | 
			
		||||
    private static SimpleCommandExceptionType translated(String key) {
 | 
			
		||||
 
 | 
			
		||||
@@ -32,7 +32,7 @@ public final class ComputersArgumentType implements ArgumentType<ComputersArgume
 | 
			
		||||
    private static final ComputersArgumentType MANY = new ComputersArgumentType(false);
 | 
			
		||||
    private static final ComputersArgumentType SOME = new ComputersArgumentType(true);
 | 
			
		||||
 | 
			
		||||
    private static final List<String> EXAMPLES = Arrays.asList(
 | 
			
		||||
    private static final List<String> EXAMPLES = List.of(
 | 
			
		||||
        "0", "#0", "@Label", "~Advanced"
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
@@ -75,7 +75,7 @@ public final class ComputersArgumentType implements ArgumentType<ComputersArgume
 | 
			
		||||
            var instance = reader.readInt();
 | 
			
		||||
            computers = s -> {
 | 
			
		||||
                var computer = ServerContext.get(s.getServer()).registry().get(instance);
 | 
			
		||||
                return computer == null ? Collections.emptyList() : Collections.singletonList(computer);
 | 
			
		||||
                return computer == null ? List.of() : List.of(computer);
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,6 @@ import net.minecraft.commands.CommandSourceStack;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.function.Predicate;
 | 
			
		||||
import java.util.function.Supplier;
 | 
			
		||||
@@ -63,7 +62,7 @@ public class CommandBuilder<S> implements CommandNodeBuilder<S, Command<S>> {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public <T> CommandNodeBuilder<S, ArgCommand<S, List<T>>> argManyValue(String name, ArgumentType<T> type, T defaultValue) {
 | 
			
		||||
        return argManyValue(name, type, Collections.singletonList(defaultValue));
 | 
			
		||||
        return argManyValue(name, type, List.of(defaultValue));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public <T> CommandNodeBuilder<S, ArgCommand<S, List<T>>> argMany(String name, ArgumentType<T> type, Supplier<List<T>> empty) {
 | 
			
		||||
 
 | 
			
		||||
@@ -147,14 +147,14 @@ public final class HelpingArgumentBuilder extends LiteralArgumentBuilder<Command
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public int run(CommandContext<CommandSourceStack> context) {
 | 
			
		||||
            context.getSource().sendSuccess(getHelp(context, assertNonNull(node), id, command), false);
 | 
			
		||||
            context.getSource().sendSuccess(() -> getHelp(context, assertNonNull(node), id, command), false);
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static Command<CommandSourceStack> helpForChild(CommandNode<CommandSourceStack> node, String id, String command) {
 | 
			
		||||
        return context -> {
 | 
			
		||||
            context.getSource().sendSuccess(getHelp(context, node, id + "." + node.getName().replace('-', '_'), command + " " + node.getName()), false);
 | 
			
		||||
            context.getSource().sendSuccess(() -> getHelp(context, node, id + "." + node.getName().replace('-', '_'), command + " " + node.getName()), false);
 | 
			
		||||
            return 0;
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -37,6 +37,6 @@ public class ServerTableFormatter implements TableFormatter {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void writeLine(String label, Component component) {
 | 
			
		||||
        source.sendSuccess(component, false);
 | 
			
		||||
        source.sendSuccess(() -> component, false);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@ package dan200.computercraft.shared.command.text;
 | 
			
		||||
import dan200.computercraft.core.util.Nullability;
 | 
			
		||||
import dan200.computercraft.shared.command.CommandUtils;
 | 
			
		||||
import dan200.computercraft.shared.network.client.ChatTableClientMessage;
 | 
			
		||||
import dan200.computercraft.shared.platform.PlatformHelper;
 | 
			
		||||
import dan200.computercraft.shared.network.server.ServerNetworking;
 | 
			
		||||
import net.minecraft.commands.CommandSourceStack;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
import net.minecraft.server.level.ServerPlayer;
 | 
			
		||||
@@ -105,7 +105,7 @@ public class TableBuilder {
 | 
			
		||||
        if (CommandUtils.isPlayer(source)) {
 | 
			
		||||
            trim(18);
 | 
			
		||||
            var player = (ServerPlayer) Nullability.assertNonNull(source.getEntity());
 | 
			
		||||
            PlatformHelper.get().sendToPlayer(new ChatTableClientMessage(this), player);
 | 
			
		||||
            ServerNetworking.sendToPlayer(new ChatTableClientMessage(this), player);
 | 
			
		||||
        } else {
 | 
			
		||||
            trim(100);
 | 
			
		||||
            new ServerTableFormatter(source).display(this);
 | 
			
		||||
 
 | 
			
		||||
@@ -18,11 +18,6 @@ public class DefaultBundledRedstoneProvider implements BundledRedstoneProvider {
 | 
			
		||||
 | 
			
		||||
    public static int getDefaultBundledRedstoneOutput(Level world, BlockPos pos, Direction side) {
 | 
			
		||||
        var block = world.getBlockState(pos).getBlock();
 | 
			
		||||
        if (block instanceof IBundledRedstoneBlock generic) {
 | 
			
		||||
            if (generic.getBundledRedstoneConnectivity(world, pos, side)) {
 | 
			
		||||
                return generic.getBundledRedstoneOutput(world, pos, side);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return -1;
 | 
			
		||||
        return block instanceof IBundledRedstoneBlock bundledBlock ? bundledBlock.getBundledRedstoneOutput(world, pos, side) : -1;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user