mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-11-04 07:32:59 +00:00 
			
		
		
		
	Compare commits
	
		
			12 Commits
		
	
	
		
			v1.20.1-1.
			...
			v1.20.1-1.
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					8be6b1b772 | ||
| 
						 | 
					104d5e70de | ||
| 
						 | 
					e3bda2f763 | ||
| 
						 | 
					234f69e8e5 | ||
| 
						 | 
					ed3a17f9b9 | ||
| 
						 | 
					0349c2b1f9 | ||
| 
						 | 
					03f9e6bd6d | ||
| 
						 | 
					9d8c933a14 | ||
| 
						 | 
					78bb3da58c | ||
| 
						 | 
					39a5e40c92 | ||
| 
						 | 
					763ba51919 | ||
| 
						 | 
					cf6ec8c28f | 
@@ -13,6 +13,8 @@ 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")
 | 
			
		||||
}
 | 
			
		||||
@@ -102,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
 | 
			
		||||
@@ -75,3 +77,9 @@ gradlePlugin {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
versionCatalogUpdate {
 | 
			
		||||
    sortByKey.set(false)
 | 
			
		||||
    keep { keepUnusedLibraries.set(true) }
 | 
			
		||||
    catalogFile.set(file("../gradle/libs.versions.toml"))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -76,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())
 | 
			
		||||
}
 | 
			
		||||
@@ -201,6 +207,7 @@ 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,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() }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -246,6 +254,20 @@ abstract class CCTweakedExtension(
 | 
			
		||||
        ).resolve().single()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Exclude a dependency from being publisehd 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(
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,72 @@
 | 
			
		||||
// 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.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.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>
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        description = "Check :core's dependencies are consistent with Minecraft's."
 | 
			
		||||
        group = LifecycleBasePlugin.VERIFICATION_GROUP
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @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
 | 
			
		||||
            if (requested.version != selected) {
 | 
			
		||||
                logger.error("Requested dependency {} (via {}) but got version {}", requested, from, selected)
 | 
			
		||||
                return false
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return true
 | 
			
		||||
        }
 | 
			
		||||
        return true
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -6,7 +6,6 @@ package cc.tweaked.gradle
 | 
			
		||||
 | 
			
		||||
import org.gradle.api.artifacts.dsl.DependencyHandler
 | 
			
		||||
import org.gradle.api.file.FileSystemLocation
 | 
			
		||||
import org.gradle.api.file.FileSystemLocationProperty
 | 
			
		||||
import org.gradle.api.provider.Property
 | 
			
		||||
import org.gradle.api.provider.Provider
 | 
			
		||||
import org.gradle.api.tasks.JavaExec
 | 
			
		||||
@@ -129,3 +128,30 @@ 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()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -109,6 +109,15 @@ class MinecraftConfigurations private constructor(private val project: Project)
 | 
			
		||||
        project.extensions.configure(CCTweakedExtension::class.java) {
 | 
			
		||||
            sourceDirectories.add(SourceSetReference.internal(client))
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 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))
 | 
			
		||||
            }
 | 
			
		||||
        project.tasks.named("check") { dependsOn(checkDependencyConsistency) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun setupOutgoing(sourceSet: SourceSet, suffix: String = "") {
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ of the mod should run fine on later versions.
 | 
			
		||||
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 {#cct-1.109}
 | 
			
		||||
## CC: Tweaked 1.109.0 to 1.109.2 {#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.
 | 
			
		||||
@@ -31,6 +31,7 @@ as documentation for breaking changes and "gotchas" one should look out for betw
 | 
			
		||||
   - `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.
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,8 +9,8 @@ kotlin.stdlib.default.dependency=false
 | 
			
		||||
kotlin.jvm.target.validation.mode=error
 | 
			
		||||
 | 
			
		||||
# Mod properties
 | 
			
		||||
isUnstable=true
 | 
			
		||||
modVersion=1.109.1
 | 
			
		||||
isUnstable=false
 | 
			
		||||
modVersion=1.109.3
 | 
			
		||||
 | 
			
		||||
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
 | 
			
		||||
mcVersion=1.20.1
 | 
			
		||||
 
 | 
			
		||||
@@ -10,28 +10,29 @@
 | 
			
		||||
fabric-api = "0.86.1+1.20.1"
 | 
			
		||||
fabric-loader = "0.14.21"
 | 
			
		||||
forge = "47.1.0"
 | 
			
		||||
forgeSpi = "6.0.0"
 | 
			
		||||
forgeSpi = "7.0.1"
 | 
			
		||||
mixin = "0.8.5"
 | 
			
		||||
parchment = "2023.08.20"
 | 
			
		||||
parchmentMc = "1.20.1"
 | 
			
		||||
 | 
			
		||||
# Normal dependencies
 | 
			
		||||
asm = "9.5"
 | 
			
		||||
autoService = "1.1.1"
 | 
			
		||||
checkerFramework = "3.32.0"
 | 
			
		||||
cobalt = "0.8.0"
 | 
			
		||||
cobalt-next = "0.8.1" # Not a real version, used to constrain the version we accept.
 | 
			
		||||
commonsCli = "1.3.1"
 | 
			
		||||
# 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.8.2"
 | 
			
		||||
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"
 | 
			
		||||
kotlin = "1.9.21"
 | 
			
		||||
kotlin-coroutines = "1.7.3"
 | 
			
		||||
nightConfig = "3.6.7"
 | 
			
		||||
slf4j = "2.0.1"
 | 
			
		||||
 | 
			
		||||
# Minecraft mods
 | 
			
		||||
emi = "1.0.8+1.20.1"
 | 
			
		||||
@@ -47,29 +48,31 @@ sodium = "mc1.20-0.4.10"
 | 
			
		||||
 | 
			
		||||
# Testing
 | 
			
		||||
hamcrest = "2.2"
 | 
			
		||||
jqwik = "1.7.4"
 | 
			
		||||
junit = "5.10.0"
 | 
			
		||||
jqwik = "1.8.2"
 | 
			
		||||
junit = "5.10.1"
 | 
			
		||||
 | 
			
		||||
# Build tools
 | 
			
		||||
cctJavadoc = "1.8.2"
 | 
			
		||||
checkstyle = "10.12.3"
 | 
			
		||||
checkstyle = "10.12.6"
 | 
			
		||||
curseForgeGradle = "1.0.14"
 | 
			
		||||
errorProne-core = "2.21.1"
 | 
			
		||||
errorProne-core = "2.23.0"
 | 
			
		||||
errorProne-plugin = "3.1.0"
 | 
			
		||||
fabric-loom = "1.3.7"
 | 
			
		||||
fabric-loom = "1.3.9"
 | 
			
		||||
forgeGradle = "6.0.8"
 | 
			
		||||
githubRelease = "2.4.1"
 | 
			
		||||
githubRelease = "2.5.2"
 | 
			
		||||
gradleVersions = "0.50.0"
 | 
			
		||||
ideaExt = "1.1.7"
 | 
			
		||||
illuaminate = "0.1.0-44-g9ee0055"
 | 
			
		||||
librarian = "1.+"
 | 
			
		||||
lwjgl = "3.3.1"
 | 
			
		||||
lwjgl = "3.3.3"
 | 
			
		||||
minotaur = "2.+"
 | 
			
		||||
mixinGradle = "0.7.+"
 | 
			
		||||
mixinGradle = "0.7.38"
 | 
			
		||||
nullAway = "0.9.9"
 | 
			
		||||
spotless = "6.21.0"
 | 
			
		||||
spotless = "6.23.3"
 | 
			
		||||
taskTree = "2.1.1"
 | 
			
		||||
teavm = "0.10.0-SQUID.2"
 | 
			
		||||
vanillaGradle = "0.2.1-SNAPSHOT"
 | 
			
		||||
versionCatalogUpdate = "0.8.1"
 | 
			
		||||
vineflower = "1.11.0"
 | 
			
		||||
 | 
			
		||||
[libraries]
 | 
			
		||||
@@ -162,10 +165,12 @@ vineflower = { module = "io.github.juuxel:loom-vineflower", version.ref = "vinef
 | 
			
		||||
[plugins]
 | 
			
		||||
forgeGradle = { id = "net.minecraftforge.gradle", version.ref = "forgeGradle" }
 | 
			
		||||
githubRelease = { id = "com.github.breadmoirai.github-release", version.ref = "githubRelease" }
 | 
			
		||||
gradleVersions = { id = "com.github.ben-manes.versions", version.ref = "gradleVersions" }
 | 
			
		||||
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
 | 
			
		||||
librarian = { id = "org.parchmentmc.librarian.forgegradle", version.ref = "librarian" }
 | 
			
		||||
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"]
 | 
			
		||||
@@ -184,5 +189,5 @@ 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" ]
 | 
			
		||||
teavm-api = ["teavm-jso", "teavm-jso-apis", "teavm-platform", "teavm-classlib", "teavm-metaprogramming-api"]
 | 
			
		||||
teavm-tooling = ["teavm-tooling", "teavm-metaprogramming-impl", "teavm-jso-impl"]
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 
 | 
			
		||||
@@ -13,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;
 | 
			
		||||
@@ -38,7 +37,6 @@ 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;
 | 
			
		||||
 | 
			
		||||
@@ -106,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())
 | 
			
		||||
@@ -121,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()))
 | 
			
		||||
                    );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -141,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())
 | 
			
		||||
@@ -158,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()))
 | 
			
		||||
                    );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -190,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
 | 
			
		||||
@@ -204,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)
 | 
			
		||||
                );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -253,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")
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
@@ -277,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())
 | 
			
		||||
@@ -288,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())
 | 
			
		||||
@@ -300,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")
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
@@ -367,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")
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
@@ -519,10 +508,6 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
 | 
			
		||||
        return tag;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static Consumer<JsonObject> family(ComputerFamily family) {
 | 
			
		||||
        return json -> json.addProperty("family", family.getSerializedName());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void addSpecial(Consumer<FinishedRecipe> add, SimpleCraftingRecipeSerializer<?> special) {
 | 
			
		||||
        SpecialRecipeBuilder.special(special).save(add, RegistryWrappers.RECIPE_SERIALIZERS.getKey(special).toString());
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -79,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) {
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
@@ -111,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());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -34,6 +34,7 @@ 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;
 | 
			
		||||
@@ -139,19 +140,17 @@ public final class ModRegistry {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static final RegistryEntry<ComputerBlock<ComputerBlockEntity>> COMPUTER_NORMAL = REGISTRY.register("computer_normal",
 | 
			
		||||
            () -> new ComputerBlock<>(computerProperties().mapColor(MapColor.STONE), 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().mapColor(MapColor.GOLD), 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 CommandComputerBlock<>(
 | 
			
		||||
            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().mapColor(MapColor.STONE), 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().mapColor(MapColor.GOLD), 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().mapColor(MapColor.STONE)));
 | 
			
		||||
        public static final RegistryEntry<DiskDriveBlock> DISK_DRIVE = REGISTRY.register("disk_drive", () -> new DiskDriveBlock(properties().mapColor(MapColor.STONE)));
 | 
			
		||||
@@ -192,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));
 | 
			
		||||
@@ -311,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));
 | 
			
		||||
@@ -371,11 +373,11 @@ public final class ModRegistry {
 | 
			
		||||
        public static final RegistryEntry<SimpleCraftingRecipeSerializer<ClearColourRecipe>> DYEABLE_ITEM_CLEAR = simple("clear_colour", ClearColourRecipe::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<RecipeSerializer<TurtleOverlayRecipe>> 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<RecipeSerializer<ComputerUpgradeRecipe>> COMPUTER_UPGRADE = REGISTRY.register("computer_upgrade", ComputerUpgradeRecipe.Serializer::new);
 | 
			
		||||
        public static final RegistryEntry<RecipeSerializer<ComputerUpgradeRecipe>> COMPUTER_UPGRADE = REGISTRY.register("computer_upgrade", () -> CustomShapedRecipe.validatingSerialiser(ComputerUpgradeRecipe::of));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static class Permissions {
 | 
			
		||||
@@ -464,8 +466,8 @@ public final class ModRegistry {
 | 
			
		||||
     * 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);
 | 
			
		||||
        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) {
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,6 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.shared.common;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.shared.ModRegistry;
 | 
			
		||||
import dan200.computercraft.shared.network.container.HeldItemContainerData;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
import net.minecraft.world.InteractionHand;
 | 
			
		||||
import net.minecraft.world.MenuProvider;
 | 
			
		||||
@@ -28,10 +26,6 @@ public class HeldItemMenu extends AbstractContainerMenu {
 | 
			
		||||
        stack = player.getItemInHand(hand).copy();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static HeldItemMenu createPrintout(int id, Inventory inventory, HeldItemContainerData data) {
 | 
			
		||||
        return new HeldItemMenu(ModRegistry.Menus.PRINTOUT.get(), id, inventory.player, data.getHand());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ItemStack getStack() {
 | 
			
		||||
        return stack;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,6 @@ package dan200.computercraft.shared.computer.blocks;
 | 
			
		||||
import dan200.computercraft.annotations.ForgeOverride;
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.shared.common.IBundledRedstoneBlock;
 | 
			
		||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
 | 
			
		||||
import dan200.computercraft.shared.computer.items.IComputerItem;
 | 
			
		||||
import dan200.computercraft.shared.platform.RegistryEntry;
 | 
			
		||||
import dan200.computercraft.shared.util.BlockEntityHelpers;
 | 
			
		||||
@@ -42,13 +41,11 @@ import javax.annotation.Nullable;
 | 
			
		||||
public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntity> extends HorizontalDirectionalBlock implements IBundledRedstoneBlock, EntityBlock {
 | 
			
		||||
    private static final ResourceLocation DROP = new ResourceLocation(ComputerCraftAPI.MOD_ID, "computer");
 | 
			
		||||
 | 
			
		||||
    private final ComputerFamily family;
 | 
			
		||||
    protected final RegistryEntry<BlockEntityType<T>> type;
 | 
			
		||||
    private final BlockEntityTicker<T> serverTicker = (level, pos, state, computer) -> computer.serverTick();
 | 
			
		||||
 | 
			
		||||
    protected AbstractComputerBlock(Properties settings, ComputerFamily family, RegistryEntry<BlockEntityType<T>> type) {
 | 
			
		||||
    protected AbstractComputerBlock(Properties settings, RegistryEntry<BlockEntityType<T>> type) {
 | 
			
		||||
        super(settings);
 | 
			
		||||
        this.family = family;
 | 
			
		||||
        this.type = type;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -82,10 +79,6 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit
 | 
			
		||||
 | 
			
		||||
    protected abstract ItemStack getItem(AbstractComputerBlockEntity tile);
 | 
			
		||||
 | 
			
		||||
    public ComputerFamily getFamily() {
 | 
			
		||||
        return family;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    @Deprecated
 | 
			
		||||
    public int getSignal(BlockState state, BlockGetter world, BlockPos pos, Direction incomingSide) {
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,6 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.shared.computer.blocks;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
 | 
			
		||||
import dan200.computercraft.shared.platform.RegistryEntry;
 | 
			
		||||
import net.minecraft.world.level.block.GameMasterBlock;
 | 
			
		||||
import net.minecraft.world.level.block.entity.BlockEntityType;
 | 
			
		||||
@@ -17,7 +16,7 @@ import net.minecraft.world.level.block.entity.BlockEntityType;
 | 
			
		||||
 * @see dan200.computercraft.shared.computer.items.CommandComputerItem
 | 
			
		||||
 */
 | 
			
		||||
public class CommandComputerBlock<T extends CommandComputerBlockEntity> extends ComputerBlock<T> implements GameMasterBlock {
 | 
			
		||||
    public CommandComputerBlock(Properties settings, ComputerFamily family, RegistryEntry<BlockEntityType<T>> type) {
 | 
			
		||||
        super(settings, family, type);
 | 
			
		||||
    public CommandComputerBlock(Properties settings, RegistryEntry<BlockEntityType<T>> type) {
 | 
			
		||||
        super(settings, type);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,9 +4,8 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.shared.computer.blocks;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
 | 
			
		||||
import dan200.computercraft.shared.computer.core.ComputerState;
 | 
			
		||||
import dan200.computercraft.shared.computer.items.ComputerItemFactory;
 | 
			
		||||
import dan200.computercraft.shared.computer.items.ComputerItem;
 | 
			
		||||
import dan200.computercraft.shared.platform.RegistryEntry;
 | 
			
		||||
import net.minecraft.core.Direction;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
@@ -25,8 +24,8 @@ public class ComputerBlock<T extends ComputerBlockEntity> extends AbstractComput
 | 
			
		||||
    public static final EnumProperty<ComputerState> STATE = EnumProperty.create("state", ComputerState.class);
 | 
			
		||||
    public static final DirectionProperty FACING = BlockStateProperties.HORIZONTAL_FACING;
 | 
			
		||||
 | 
			
		||||
    public ComputerBlock(Properties settings, ComputerFamily family, RegistryEntry<BlockEntityType<T>> type) {
 | 
			
		||||
        super(settings, family, type);
 | 
			
		||||
    public ComputerBlock(Properties settings, RegistryEntry<BlockEntityType<T>> type) {
 | 
			
		||||
        super(settings, type);
 | 
			
		||||
        registerDefaultState(defaultBlockState()
 | 
			
		||||
            .setValue(FACING, Direction.NORTH)
 | 
			
		||||
            .setValue(STATE, ComputerState.OFF)
 | 
			
		||||
@@ -46,6 +45,9 @@ public class ComputerBlock<T extends ComputerBlockEntity> extends AbstractComput
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected ItemStack getItem(AbstractComputerBlockEntity tile) {
 | 
			
		||||
        return tile instanceof ComputerBlockEntity ? ComputerItemFactory.create((ComputerBlockEntity) tile) : ItemStack.EMPTY;
 | 
			
		||||
        if (!(tile instanceof ComputerBlockEntity computer)) return ItemStack.EMPTY;
 | 
			
		||||
        if (!(asItem() instanceof ComputerItem item)) return ItemStack.EMPTY;
 | 
			
		||||
 | 
			
		||||
        return item.create(computer.getComputerID(), computer.getLabel());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -32,11 +32,9 @@ public class ComputerBlockEntity extends AbstractComputerBlockEntity {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected ServerComputer createComputer(int id) {
 | 
			
		||||
        var family = getFamily();
 | 
			
		||||
        return new ServerComputer(
 | 
			
		||||
            (ServerLevel) getLevel(), getBlockPos(), id, label,
 | 
			
		||||
            family, Config.computerTermWidth,
 | 
			
		||||
            Config.computerTermHeight
 | 
			
		||||
            getFamily(), Config.computerTermWidth, Config.computerTermHeight
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,33 +4,8 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.shared.computer.core;
 | 
			
		||||
 | 
			
		||||
import com.google.gson.JsonObject;
 | 
			
		||||
import com.google.gson.JsonSyntaxException;
 | 
			
		||||
import net.minecraft.util.GsonHelper;
 | 
			
		||||
import net.minecraft.util.StringRepresentable;
 | 
			
		||||
 | 
			
		||||
public enum ComputerFamily implements StringRepresentable {
 | 
			
		||||
    NORMAL("normal"),
 | 
			
		||||
    ADVANCED("advanced"),
 | 
			
		||||
    COMMAND("command");
 | 
			
		||||
 | 
			
		||||
    private final String name;
 | 
			
		||||
 | 
			
		||||
    ComputerFamily(String name) {
 | 
			
		||||
        this.name = name;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static ComputerFamily getFamily(JsonObject json, String name) {
 | 
			
		||||
        var familyName = GsonHelper.getAsString(json, name);
 | 
			
		||||
        for (var family : values()) {
 | 
			
		||||
            if (family.getSerializedName().equalsIgnoreCase(familyName)) return family;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        throw new JsonSyntaxException("Unknown computer family '" + familyName + "' for field " + name);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String getSerializedName() {
 | 
			
		||||
        return name;
 | 
			
		||||
    }
 | 
			
		||||
public enum ComputerFamily {
 | 
			
		||||
    NORMAL,
 | 
			
		||||
    ADVANCED,
 | 
			
		||||
    COMMAND,
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,6 @@ import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.api.filesystem.Mount;
 | 
			
		||||
import dan200.computercraft.api.media.IMedia;
 | 
			
		||||
import dan200.computercraft.shared.computer.blocks.AbstractComputerBlock;
 | 
			
		||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
 | 
			
		||||
import dan200.computercraft.shared.config.Config;
 | 
			
		||||
import net.minecraft.ChatFormatting;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
@@ -22,11 +21,8 @@ import javax.annotation.Nullable;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
public abstract class AbstractComputerItem extends BlockItem implements IComputerItem, IMedia {
 | 
			
		||||
    private final ComputerFamily family;
 | 
			
		||||
 | 
			
		||||
    public AbstractComputerItem(AbstractComputerBlock<?> block, Properties settings) {
 | 
			
		||||
        super(block, settings);
 | 
			
		||||
        family = block.getFamily();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
@@ -45,13 +41,6 @@ public abstract class AbstractComputerItem extends BlockItem implements ICompute
 | 
			
		||||
        return IComputerItem.super.getLabel(stack);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final ComputerFamily getFamily() {
 | 
			
		||||
        return family;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // IMedia implementation
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean setLabel(ItemStack stack, @Nullable String label) {
 | 
			
		||||
        if (label != null) {
 | 
			
		||||
 
 | 
			
		||||
@@ -5,8 +5,8 @@
 | 
			
		||||
package dan200.computercraft.shared.computer.items;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.shared.computer.blocks.ComputerBlock;
 | 
			
		||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
import net.minecraft.world.item.Item;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
@@ -24,9 +24,9 @@ public class ComputerItem extends AbstractComputerItem {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public ItemStack withFamily(ItemStack stack, ComputerFamily family) {
 | 
			
		||||
        var result = ComputerItemFactory.create(getComputerID(stack), null, family);
 | 
			
		||||
        if (stack.hasCustomHoverName()) result.setHoverName(stack.getHoverName());
 | 
			
		||||
        return result;
 | 
			
		||||
    public ItemStack changeItem(ItemStack stack, Item newItem) {
 | 
			
		||||
        return newItem instanceof ComputerItem computer
 | 
			
		||||
            ? computer.create(getComputerID(stack), getLabel(stack))
 | 
			
		||||
            : ItemStack.EMPTY;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,29 +0,0 @@
 | 
			
		||||
// Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: LicenseRef-CCPL
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.shared.computer.items;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.shared.ModRegistry;
 | 
			
		||||
import dan200.computercraft.shared.computer.blocks.ComputerBlockEntity;
 | 
			
		||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
public final class ComputerItemFactory {
 | 
			
		||||
    private ComputerItemFactory() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static ItemStack create(ComputerBlockEntity tile) {
 | 
			
		||||
        return create(tile.getComputerID(), tile.getLabel(), tile.getFamily());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static ItemStack create(int id, @Nullable String label, ComputerFamily family) {
 | 
			
		||||
        return switch (family) {
 | 
			
		||||
            case NORMAL -> ModRegistry.Items.COMPUTER_NORMAL.get().create(id, label);
 | 
			
		||||
            case ADVANCED -> ModRegistry.Items.COMPUTER_ADVANCED.get().create(id, label);
 | 
			
		||||
            case COMMAND -> ModRegistry.Items.COMPUTER_COMMAND.get().create(id, label);
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.shared.computer.items;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
 | 
			
		||||
import net.minecraft.world.item.Item;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
@@ -21,7 +21,15 @@ public interface IComputerItem {
 | 
			
		||||
        return stack.hasCustomHoverName() ? stack.getHoverName().getString() : null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ComputerFamily getFamily();
 | 
			
		||||
 | 
			
		||||
    ItemStack withFamily(ItemStack stack, ComputerFamily family);
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a new stack, changing the underlying item.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This should copy the computer's data to a different item of the same type (for instance, converting a normal
 | 
			
		||||
     * computer to an advanced one).
 | 
			
		||||
     *
 | 
			
		||||
     * @param stack   The current computer stack.
 | 
			
		||||
     * @param newItem The new item.
 | 
			
		||||
     * @return The new stack, possibly {@linkplain ItemStack#EMPTY empty} if {@code newItem} is of the same type.
 | 
			
		||||
     */
 | 
			
		||||
    ItemStack changeItem(ItemStack stack, Item newItem);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,57 +4,44 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.shared.computer.recipe;
 | 
			
		||||
 | 
			
		||||
import com.google.gson.JsonObject;
 | 
			
		||||
import com.mojang.serialization.DataResult;
 | 
			
		||||
import dan200.computercraft.shared.ModRegistry;
 | 
			
		||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
 | 
			
		||||
import dan200.computercraft.shared.computer.items.IComputerItem;
 | 
			
		||||
import dan200.computercraft.shared.recipe.ShapedRecipeSpec;
 | 
			
		||||
import net.minecraft.network.FriendlyByteBuf;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.item.Item;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.item.crafting.RecipeSerializer;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A recipe which "upgrades" a {@linkplain IComputerItem computer}, converting it from one {@linkplain ComputerFamily
 | 
			
		||||
 * family} to another.
 | 
			
		||||
 * A recipe which "upgrades" a {@linkplain IComputerItem computer}, converting to it a new item (for instance a normal
 | 
			
		||||
 * turtle to an advanced one).
 | 
			
		||||
 *
 | 
			
		||||
 * @see IComputerItem#changeItem(ItemStack, Item)
 | 
			
		||||
 */
 | 
			
		||||
public final class ComputerUpgradeRecipe extends ComputerConvertRecipe {
 | 
			
		||||
    private final ComputerFamily family;
 | 
			
		||||
    private final Item result;
 | 
			
		||||
 | 
			
		||||
    private ComputerUpgradeRecipe(ResourceLocation identifier, ShapedRecipeSpec recipe, ComputerFamily family) {
 | 
			
		||||
    private ComputerUpgradeRecipe(ResourceLocation identifier, ShapedRecipeSpec recipe) {
 | 
			
		||||
        super(identifier, recipe);
 | 
			
		||||
        this.family = family;
 | 
			
		||||
        this.result = recipe.result().getItem();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static DataResult<ComputerUpgradeRecipe> of(ResourceLocation id, ShapedRecipeSpec recipe) {
 | 
			
		||||
        if (!(recipe.result().getItem() instanceof IComputerItem)) {
 | 
			
		||||
            return DataResult.error(() -> recipe.result().getItem() + " is not a computer item");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return DataResult.success(new ComputerUpgradeRecipe(id, recipe));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected ItemStack convert(IComputerItem item, ItemStack stack) {
 | 
			
		||||
        return item.withFamily(stack, family);
 | 
			
		||||
        return item.changeItem(stack, result);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public RecipeSerializer<ComputerUpgradeRecipe> getSerializer() {
 | 
			
		||||
        return ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static class Serializer implements RecipeSerializer<ComputerUpgradeRecipe> {
 | 
			
		||||
        @Override
 | 
			
		||||
        public ComputerUpgradeRecipe fromJson(ResourceLocation identifier, JsonObject json) {
 | 
			
		||||
            var recipe = ShapedRecipeSpec.fromJson(json);
 | 
			
		||||
            var family = ComputerFamily.getFamily(json, "family");
 | 
			
		||||
            return new ComputerUpgradeRecipe(identifier, recipe, family);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public ComputerUpgradeRecipe fromNetwork(ResourceLocation identifier, FriendlyByteBuf buf) {
 | 
			
		||||
            var recipe = ShapedRecipeSpec.fromNetwork(buf);
 | 
			
		||||
            var family = buf.readEnum(ComputerFamily.class);
 | 
			
		||||
            return new ComputerUpgradeRecipe(identifier, recipe, family);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public void toNetwork(FriendlyByteBuf buf, ComputerUpgradeRecipe recipe) {
 | 
			
		||||
            recipe.toSpec().toNetwork(buf);
 | 
			
		||||
            buf.writeEnum(recipe.family);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,18 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.shared.network;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A type of message to send over the network.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Much like recipe or argument serialisers, each type of {@link NetworkMessage} should have a unique type associated
 | 
			
		||||
 * with it. This holds platform-specific information about how the packet should be sent over the network.
 | 
			
		||||
 *
 | 
			
		||||
 * @param <T> The type of message to send
 | 
			
		||||
 * @see NetworkMessages
 | 
			
		||||
 * @see NetworkMessage#type()
 | 
			
		||||
 */
 | 
			
		||||
public interface MessageType<T extends NetworkMessage<?>> {
 | 
			
		||||
}
 | 
			
		||||
@@ -17,6 +17,13 @@ import net.minecraft.network.FriendlyByteBuf;
 | 
			
		||||
 * @see ServerNetworkContext
 | 
			
		||||
 */
 | 
			
		||||
public interface NetworkMessage<T> {
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the type of this message.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The type of this message.
 | 
			
		||||
     */
 | 
			
		||||
    MessageType<?> type();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Write this packet to a buffer.
 | 
			
		||||
     * <p>
 | 
			
		||||
@@ -24,7 +31,7 @@ public interface NetworkMessage<T> {
 | 
			
		||||
     *
 | 
			
		||||
     * @param buf The buffer to write data to.
 | 
			
		||||
     */
 | 
			
		||||
    void toBytes(FriendlyByteBuf buf);
 | 
			
		||||
    void write(FriendlyByteBuf buf);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handle this {@link NetworkMessage}.
 | 
			
		||||
 
 | 
			
		||||
@@ -4,48 +4,84 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.shared.network;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.shared.network.client.*;
 | 
			
		||||
import dan200.computercraft.shared.network.server.*;
 | 
			
		||||
import dan200.computercraft.shared.platform.PlatformHelper;
 | 
			
		||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
 | 
			
		||||
import it.unimi.dsi.fastutil.ints.IntSet;
 | 
			
		||||
import net.minecraft.network.FriendlyByteBuf;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Registry for all packets provided by CC: Tweaked.
 | 
			
		||||
 * List of all {@link MessageType}s provided by CC: Tweaked.
 | 
			
		||||
 *
 | 
			
		||||
 * @see PlatformHelper The platform helper is used to send packets.
 | 
			
		||||
 */
 | 
			
		||||
public final class NetworkMessages {
 | 
			
		||||
    private static final IntSet seenIds = new IntOpenHashSet();
 | 
			
		||||
    private static final Set<String> seenChannel = new HashSet<>();
 | 
			
		||||
    private static final List<MessageType<? extends NetworkMessage<ServerNetworkContext>>> serverMessages = new ArrayList<>();
 | 
			
		||||
    private static final List<MessageType<? extends NetworkMessage<ClientNetworkContext>>> clientMessages = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
    public static final MessageType<ComputerActionServerMessage> COMPUTER_ACTION = registerServerbound(0, "computer_action", ComputerActionServerMessage.class, ComputerActionServerMessage::new);
 | 
			
		||||
    public static final MessageType<QueueEventServerMessage> QUEUE_EVENT = registerServerbound(1, "queue_event", QueueEventServerMessage.class, QueueEventServerMessage::new);
 | 
			
		||||
    public static final MessageType<KeyEventServerMessage> KEY_EVENT = registerServerbound(2, "key_event", KeyEventServerMessage.class, KeyEventServerMessage::new);
 | 
			
		||||
    public static final MessageType<MouseEventServerMessage> MOUSE_EVENT = registerServerbound(3, "mouse_event", MouseEventServerMessage.class, MouseEventServerMessage::new);
 | 
			
		||||
    public static final MessageType<UploadFileMessage> UPLOAD_FILE = registerServerbound(4, "upload_file", UploadFileMessage.class, UploadFileMessage::new);
 | 
			
		||||
 | 
			
		||||
    public static final MessageType<ChatTableClientMessage> CHAT_TABLE = registerClientbound(10, "chat_table", ChatTableClientMessage.class, ChatTableClientMessage::new);
 | 
			
		||||
    public static final MessageType<PocketComputerDataMessage> POCKET_COMPUTER_DATA = registerClientbound(11, "pocket_computer_data", PocketComputerDataMessage.class, PocketComputerDataMessage::new);
 | 
			
		||||
    public static final MessageType<PocketComputerDeletedClientMessage> POCKET_COMPUTER_DELETED = registerClientbound(12, "pocket_computer_deleted", PocketComputerDeletedClientMessage.class, PocketComputerDeletedClientMessage::new);
 | 
			
		||||
    public static final MessageType<ComputerTerminalClientMessage> COMPUTER_TERMINAL = registerClientbound(13, "computer_terminal", ComputerTerminalClientMessage.class, ComputerTerminalClientMessage::new);
 | 
			
		||||
    public static final MessageType<PlayRecordClientMessage> PLAY_RECORD = registerClientbound(14, "play_record", PlayRecordClientMessage.class, PlayRecordClientMessage::new);
 | 
			
		||||
    public static final MessageType<MonitorClientMessage> MONITOR_CLIENT = registerClientbound(15, "monitor_client", MonitorClientMessage.class, MonitorClientMessage::new);
 | 
			
		||||
    public static final MessageType<SpeakerAudioClientMessage> SPEAKER_AUDIO = registerClientbound(16, "speaker_audio", SpeakerAudioClientMessage.class, SpeakerAudioClientMessage::new);
 | 
			
		||||
    public static final MessageType<SpeakerMoveClientMessage> SPEAKER_MOVE = registerClientbound(17, "speaker_move", SpeakerMoveClientMessage.class, SpeakerMoveClientMessage::new);
 | 
			
		||||
    public static final MessageType<SpeakerPlayClientMessage> SPEAKER_PLAY = registerClientbound(18, "speaker_play", SpeakerPlayClientMessage.class, SpeakerPlayClientMessage::new);
 | 
			
		||||
    public static final MessageType<SpeakerStopClientMessage> SPEAKER_STOP = registerClientbound(19, "speaker_stop", SpeakerStopClientMessage.class, SpeakerStopClientMessage::new);
 | 
			
		||||
    public static final MessageType<UploadResultMessage> UPLOAD_RESULT = registerClientbound(20, "upload_result", UploadResultMessage.class, UploadResultMessage::new);
 | 
			
		||||
    public static final MessageType<UpgradesLoadedMessage> UPGRADES_LOADED = registerClientbound(21, "upgrades_loaded", UpgradesLoadedMessage.class, UpgradesLoadedMessage::new);
 | 
			
		||||
 | 
			
		||||
    private NetworkMessages() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public interface PacketRegistry {
 | 
			
		||||
        <T extends NetworkMessage<ClientNetworkContext>> void registerClientbound(int id, Class<T> type, Function<FriendlyByteBuf, T> decoder);
 | 
			
		||||
 | 
			
		||||
        <T extends NetworkMessage<ServerNetworkContext>> void registerServerbound(int id, Class<T> type, Function<FriendlyByteBuf, T> decoder);
 | 
			
		||||
    private static <C, T extends NetworkMessage<C>> MessageType<T> register(
 | 
			
		||||
        List<MessageType<? extends NetworkMessage<C>>> messages,
 | 
			
		||||
        int id, String channel, Class<T> klass, FriendlyByteBuf.Reader<T> reader
 | 
			
		||||
    ) {
 | 
			
		||||
        if (!seenIds.add(id)) throw new IllegalArgumentException("Duplicate id " + id);
 | 
			
		||||
        if (!seenChannel.add(channel)) throw new IllegalArgumentException("Duplicate channel " + channel);
 | 
			
		||||
        var type = PlatformHelper.get().createMessageType(id, new ResourceLocation(ComputerCraftAPI.MOD_ID, channel), klass, reader);
 | 
			
		||||
        messages.add(type);
 | 
			
		||||
        return type;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void register(PacketRegistry registry) {
 | 
			
		||||
        // Server messages
 | 
			
		||||
        registry.registerServerbound(0, ComputerActionServerMessage.class, ComputerActionServerMessage::new);
 | 
			
		||||
        registry.registerServerbound(1, QueueEventServerMessage.class, QueueEventServerMessage::new);
 | 
			
		||||
        registry.registerServerbound(2, KeyEventServerMessage.class, KeyEventServerMessage::new);
 | 
			
		||||
        registry.registerServerbound(3, MouseEventServerMessage.class, MouseEventServerMessage::new);
 | 
			
		||||
        registry.registerServerbound(4, UploadFileMessage.class, UploadFileMessage::new);
 | 
			
		||||
    private static <T extends NetworkMessage<ServerNetworkContext>> MessageType<T> registerServerbound(int id, String channel, Class<T> klass, FriendlyByteBuf.Reader<T> reader) {
 | 
			
		||||
        return register(serverMessages, id, channel, klass, reader);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        // Client messages
 | 
			
		||||
        registry.registerClientbound(10, ChatTableClientMessage.class, ChatTableClientMessage::new);
 | 
			
		||||
        registry.registerClientbound(11, PocketComputerDataMessage.class, PocketComputerDataMessage::new);
 | 
			
		||||
        registry.registerClientbound(12, PocketComputerDeletedClientMessage.class, PocketComputerDeletedClientMessage::new);
 | 
			
		||||
        registry.registerClientbound(13, ComputerTerminalClientMessage.class, ComputerTerminalClientMessage::new);
 | 
			
		||||
        registry.registerClientbound(14, PlayRecordClientMessage.class, PlayRecordClientMessage::new);
 | 
			
		||||
        registry.registerClientbound(15, MonitorClientMessage.class, MonitorClientMessage::new);
 | 
			
		||||
        registry.registerClientbound(16, SpeakerAudioClientMessage.class, SpeakerAudioClientMessage::new);
 | 
			
		||||
        registry.registerClientbound(17, SpeakerMoveClientMessage.class, SpeakerMoveClientMessage::new);
 | 
			
		||||
        registry.registerClientbound(18, SpeakerPlayClientMessage.class, SpeakerPlayClientMessage::new);
 | 
			
		||||
        registry.registerClientbound(19, SpeakerStopClientMessage.class, SpeakerStopClientMessage::new);
 | 
			
		||||
        registry.registerClientbound(20, UploadResultMessage.class, UploadResultMessage::new);
 | 
			
		||||
        registry.registerClientbound(21, UpgradesLoadedMessage.class, UpgradesLoadedMessage::new);
 | 
			
		||||
    private static <T extends NetworkMessage<ClientNetworkContext>> MessageType<T> registerClientbound(int id, String channel, Class<T> klass, FriendlyByteBuf.Reader<T> reader) {
 | 
			
		||||
        return register(clientMessages, id, channel, klass, reader);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get all serverbound message types.
 | 
			
		||||
     *
 | 
			
		||||
     * @return An unmodifiable sequence of all serverbound message types.
 | 
			
		||||
     */
 | 
			
		||||
    public static Collection<MessageType<? extends NetworkMessage<ServerNetworkContext>>> getServerbound() {
 | 
			
		||||
        return Collections.unmodifiableCollection(serverMessages);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get all clientbound message types.
 | 
			
		||||
     *
 | 
			
		||||
     * @return An unmodifiable sequence of all clientbound message types.
 | 
			
		||||
     */
 | 
			
		||||
    public static Collection<MessageType<? extends NetworkMessage<ClientNetworkContext>>> getClientbound() {
 | 
			
		||||
        return Collections.unmodifiableCollection(clientMessages);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,9 @@
 | 
			
		||||
package dan200.computercraft.shared.network.client;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.shared.command.text.TableBuilder;
 | 
			
		||||
import dan200.computercraft.shared.network.MessageType;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessage;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessages;
 | 
			
		||||
import net.minecraft.network.FriendlyByteBuf;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
 | 
			
		||||
@@ -43,7 +45,7 @@ public class ChatTableClientMessage implements NetworkMessage<ClientNetworkConte
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void toBytes(FriendlyByteBuf buf) {
 | 
			
		||||
    public void write(FriendlyByteBuf buf) {
 | 
			
		||||
        buf.writeUtf(table.getId(), MAX_LEN);
 | 
			
		||||
        buf.writeVarInt(table.getColumns());
 | 
			
		||||
        buf.writeBoolean(table.getHeaders() != null);
 | 
			
		||||
@@ -63,4 +65,9 @@ public class ChatTableClientMessage implements NetworkMessage<ClientNetworkConte
 | 
			
		||||
    public void handle(ClientNetworkContext context) {
 | 
			
		||||
        context.handleChatTable(table);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public MessageType<ChatTableClientMessage> type() {
 | 
			
		||||
        return NetworkMessages.CHAT_TABLE;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,9 @@
 | 
			
		||||
package dan200.computercraft.shared.network.client;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.shared.computer.terminal.TerminalState;
 | 
			
		||||
import dan200.computercraft.shared.network.MessageType;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessage;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessages;
 | 
			
		||||
import net.minecraft.network.FriendlyByteBuf;
 | 
			
		||||
import net.minecraft.world.inventory.AbstractContainerMenu;
 | 
			
		||||
 | 
			
		||||
@@ -25,7 +27,7 @@ public class ComputerTerminalClientMessage implements NetworkMessage<ClientNetwo
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void toBytes(FriendlyByteBuf buf) {
 | 
			
		||||
    public void write(FriendlyByteBuf buf) {
 | 
			
		||||
        buf.writeVarInt(containerId);
 | 
			
		||||
        terminal.write(buf);
 | 
			
		||||
    }
 | 
			
		||||
@@ -34,4 +36,9 @@ public class ComputerTerminalClientMessage implements NetworkMessage<ClientNetwo
 | 
			
		||||
    public void handle(ClientNetworkContext context) {
 | 
			
		||||
        context.handleComputerTerminal(containerId, terminal);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public MessageType<ComputerTerminalClientMessage> type() {
 | 
			
		||||
        return NetworkMessages.COMPUTER_TERMINAL;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,9 @@
 | 
			
		||||
package dan200.computercraft.shared.network.client;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.shared.computer.terminal.TerminalState;
 | 
			
		||||
import dan200.computercraft.shared.network.MessageType;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessage;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessages;
 | 
			
		||||
import net.minecraft.core.BlockPos;
 | 
			
		||||
import net.minecraft.network.FriendlyByteBuf;
 | 
			
		||||
 | 
			
		||||
@@ -25,7 +27,7 @@ public class MonitorClientMessage implements NetworkMessage<ClientNetworkContext
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void toBytes(FriendlyByteBuf buf) {
 | 
			
		||||
    public void write(FriendlyByteBuf buf) {
 | 
			
		||||
        buf.writeBlockPos(pos);
 | 
			
		||||
        state.write(buf);
 | 
			
		||||
    }
 | 
			
		||||
@@ -34,4 +36,9 @@ public class MonitorClientMessage implements NetworkMessage<ClientNetworkContext
 | 
			
		||||
    public void handle(ClientNetworkContext context) {
 | 
			
		||||
        context.handleMonitorData(pos, state);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public MessageType<MonitorClientMessage> type() {
 | 
			
		||||
        return NetworkMessages.MONITOR_CLIENT;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,9 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.shared.network.client;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.shared.network.MessageType;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessage;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessages;
 | 
			
		||||
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveBlockEntity;
 | 
			
		||||
import net.minecraft.core.BlockPos;
 | 
			
		||||
import net.minecraft.network.FriendlyByteBuf;
 | 
			
		||||
@@ -43,7 +45,7 @@ public class PlayRecordClientMessage implements NetworkMessage<ClientNetworkCont
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void toBytes(FriendlyByteBuf buf) {
 | 
			
		||||
    public void write(FriendlyByteBuf buf) {
 | 
			
		||||
        buf.writeBlockPos(pos);
 | 
			
		||||
        buf.writeNullable(soundEvent, (b, e) -> e.writeToNetwork(b));
 | 
			
		||||
        buf.writeNullable(name, FriendlyByteBuf::writeUtf);
 | 
			
		||||
@@ -53,4 +55,9 @@ public class PlayRecordClientMessage implements NetworkMessage<ClientNetworkCont
 | 
			
		||||
    public void handle(ClientNetworkContext context) {
 | 
			
		||||
        context.handlePlayRecord(pos, soundEvent, name);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public MessageType<PlayRecordClientMessage> type() {
 | 
			
		||||
        return NetworkMessages.PLAY_RECORD;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,9 @@ package dan200.computercraft.shared.network.client;
 | 
			
		||||
import dan200.computercraft.shared.computer.core.ComputerState;
 | 
			
		||||
import dan200.computercraft.shared.computer.terminal.NetworkedTerminal;
 | 
			
		||||
import dan200.computercraft.shared.computer.terminal.TerminalState;
 | 
			
		||||
import dan200.computercraft.shared.network.MessageType;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessage;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessages;
 | 
			
		||||
import dan200.computercraft.shared.pocket.core.PocketServerComputer;
 | 
			
		||||
import net.minecraft.network.FriendlyByteBuf;
 | 
			
		||||
 | 
			
		||||
@@ -35,7 +37,7 @@ public class PocketComputerDataMessage implements NetworkMessage<ClientNetworkCo
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void toBytes(FriendlyByteBuf buf) {
 | 
			
		||||
    public void write(FriendlyByteBuf buf) {
 | 
			
		||||
        buf.writeVarInt(instanceId);
 | 
			
		||||
        buf.writeEnum(state);
 | 
			
		||||
        buf.writeVarInt(lightState);
 | 
			
		||||
@@ -46,4 +48,9 @@ public class PocketComputerDataMessage implements NetworkMessage<ClientNetworkCo
 | 
			
		||||
    public void handle(ClientNetworkContext context) {
 | 
			
		||||
        context.handlePocketComputerData(instanceId, state, lightState, terminal);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public MessageType<PocketComputerDataMessage> type() {
 | 
			
		||||
        return NetworkMessages.POCKET_COMPUTER_DATA;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,9 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.shared.network.client;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.shared.network.MessageType;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessage;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessages;
 | 
			
		||||
import net.minecraft.network.FriendlyByteBuf;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -20,7 +22,7 @@ public class PocketComputerDeletedClientMessage implements NetworkMessage<Client
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void toBytes(FriendlyByteBuf buf) {
 | 
			
		||||
    public void write(FriendlyByteBuf buf) {
 | 
			
		||||
        buf.writeVarInt(instanceId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -28,4 +30,9 @@ public class PocketComputerDeletedClientMessage implements NetworkMessage<Client
 | 
			
		||||
    public void handle(ClientNetworkContext context) {
 | 
			
		||||
        context.handlePocketComputerDeleted(instanceId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public MessageType<PocketComputerDeletedClientMessage> type() {
 | 
			
		||||
        return NetworkMessages.POCKET_COMPUTER_DELETED;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,9 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.shared.network.client;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.shared.network.MessageType;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessage;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessages;
 | 
			
		||||
import dan200.computercraft.shared.peripheral.speaker.SpeakerBlockEntity;
 | 
			
		||||
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
 | 
			
		||||
import net.minecraft.network.FriendlyByteBuf;
 | 
			
		||||
@@ -47,7 +49,7 @@ public class SpeakerAudioClientMessage implements NetworkMessage<ClientNetworkCo
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void toBytes(FriendlyByteBuf buf) {
 | 
			
		||||
    public void write(FriendlyByteBuf buf) {
 | 
			
		||||
        buf.writeUUID(source);
 | 
			
		||||
        pos.write(buf);
 | 
			
		||||
        buf.writeFloat(volume);
 | 
			
		||||
@@ -58,4 +60,9 @@ public class SpeakerAudioClientMessage implements NetworkMessage<ClientNetworkCo
 | 
			
		||||
    public void handle(ClientNetworkContext context) {
 | 
			
		||||
        context.handleSpeakerAudio(source, pos, volume);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public MessageType<SpeakerAudioClientMessage> type() {
 | 
			
		||||
        return NetworkMessages.SPEAKER_AUDIO;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,9 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.shared.network.client;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.shared.network.MessageType;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessage;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessages;
 | 
			
		||||
import dan200.computercraft.shared.peripheral.speaker.SpeakerBlockEntity;
 | 
			
		||||
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
 | 
			
		||||
import net.minecraft.network.FriendlyByteBuf;
 | 
			
		||||
@@ -33,7 +35,7 @@ public class SpeakerMoveClientMessage implements NetworkMessage<ClientNetworkCon
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void toBytes(FriendlyByteBuf buf) {
 | 
			
		||||
    public void write(FriendlyByteBuf buf) {
 | 
			
		||||
        buf.writeUUID(source);
 | 
			
		||||
        pos.write(buf);
 | 
			
		||||
    }
 | 
			
		||||
@@ -42,4 +44,9 @@ public class SpeakerMoveClientMessage implements NetworkMessage<ClientNetworkCon
 | 
			
		||||
    public void handle(ClientNetworkContext context) {
 | 
			
		||||
        context.handleSpeakerMove(source, pos);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public MessageType<SpeakerMoveClientMessage> type() {
 | 
			
		||||
        return NetworkMessages.SPEAKER_MOVE;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,9 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.shared.network.client;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.shared.network.MessageType;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessage;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessages;
 | 
			
		||||
import dan200.computercraft.shared.peripheral.speaker.SpeakerBlockEntity;
 | 
			
		||||
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
 | 
			
		||||
import net.minecraft.network.FriendlyByteBuf;
 | 
			
		||||
@@ -43,7 +45,7 @@ public class SpeakerPlayClientMessage implements NetworkMessage<ClientNetworkCon
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void toBytes(FriendlyByteBuf buf) {
 | 
			
		||||
    public void write(FriendlyByteBuf buf) {
 | 
			
		||||
        buf.writeUUID(source);
 | 
			
		||||
        pos.write(buf);
 | 
			
		||||
        buf.writeResourceLocation(sound);
 | 
			
		||||
@@ -55,4 +57,9 @@ public class SpeakerPlayClientMessage implements NetworkMessage<ClientNetworkCon
 | 
			
		||||
    public void handle(ClientNetworkContext context) {
 | 
			
		||||
        context.handleSpeakerPlay(source, pos, sound, volume, pitch);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public MessageType<SpeakerPlayClientMessage> type() {
 | 
			
		||||
        return NetworkMessages.SPEAKER_PLAY;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,9 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.shared.network.client;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.shared.network.MessageType;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessage;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessages;
 | 
			
		||||
import dan200.computercraft.shared.peripheral.speaker.SpeakerBlockEntity;
 | 
			
		||||
import net.minecraft.network.FriendlyByteBuf;
 | 
			
		||||
 | 
			
		||||
@@ -29,7 +31,7 @@ public class SpeakerStopClientMessage implements NetworkMessage<ClientNetworkCon
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void toBytes(FriendlyByteBuf buf) {
 | 
			
		||||
    public void write(FriendlyByteBuf buf) {
 | 
			
		||||
        buf.writeUUID(source);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -37,4 +39,9 @@ public class SpeakerStopClientMessage implements NetworkMessage<ClientNetworkCon
 | 
			
		||||
    public void handle(ClientNetworkContext context) {
 | 
			
		||||
        context.handleSpeakerStop(source);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public MessageType<SpeakerStopClientMessage> type() {
 | 
			
		||||
        return NetworkMessages.SPEAKER_STOP;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,9 @@ import dan200.computercraft.api.upgrades.UpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.impl.PocketUpgrades;
 | 
			
		||||
import dan200.computercraft.impl.TurtleUpgrades;
 | 
			
		||||
import dan200.computercraft.impl.UpgradeManager;
 | 
			
		||||
import dan200.computercraft.shared.network.MessageType;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessage;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessages;
 | 
			
		||||
import dan200.computercraft.shared.platform.PlatformHelper;
 | 
			
		||||
import net.minecraft.core.Registry;
 | 
			
		||||
import net.minecraft.network.FriendlyByteBuf;
 | 
			
		||||
@@ -27,7 +29,7 @@ import java.util.Objects;
 | 
			
		||||
/**
 | 
			
		||||
 * Syncs turtle and pocket upgrades to the client.
 | 
			
		||||
 */
 | 
			
		||||
public class UpgradesLoadedMessage implements NetworkMessage<ClientNetworkContext> {
 | 
			
		||||
public final class UpgradesLoadedMessage implements NetworkMessage<ClientNetworkContext> {
 | 
			
		||||
    private final Map<String, UpgradeManager.UpgradeWrapper<TurtleUpgradeSerialiser<?>, ITurtleUpgrade>> turtleUpgrades;
 | 
			
		||||
    private final Map<String, UpgradeManager.UpgradeWrapper<PocketUpgradeSerialiser<?>, IPocketUpgrade>> pocketUpgrades;
 | 
			
		||||
 | 
			
		||||
@@ -65,7 +67,7 @@ public class UpgradesLoadedMessage implements NetworkMessage<ClientNetworkContex
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void toBytes(FriendlyByteBuf buf) {
 | 
			
		||||
    public void write(FriendlyByteBuf buf) {
 | 
			
		||||
        toBytes(buf, TurtleUpgradeSerialiser.registryId(), turtleUpgrades);
 | 
			
		||||
        toBytes(buf, PocketUpgradeSerialiser.registryId(), pocketUpgrades);
 | 
			
		||||
    }
 | 
			
		||||
@@ -95,4 +97,9 @@ public class UpgradesLoadedMessage implements NetworkMessage<ClientNetworkContex
 | 
			
		||||
        TurtleUpgrades.instance().loadFromNetwork(turtleUpgrades);
 | 
			
		||||
        PocketUpgrades.instance().loadFromNetwork(pocketUpgrades);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public MessageType<UpgradesLoadedMessage> type() {
 | 
			
		||||
        return NetworkMessages.UPGRADES_LOADED;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,9 @@ package dan200.computercraft.shared.network.client;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.core.util.Nullability;
 | 
			
		||||
import dan200.computercraft.shared.computer.upload.UploadResult;
 | 
			
		||||
import dan200.computercraft.shared.network.MessageType;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessage;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessages;
 | 
			
		||||
import net.minecraft.network.FriendlyByteBuf;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
import net.minecraft.world.inventory.AbstractContainerMenu;
 | 
			
		||||
@@ -43,7 +45,7 @@ public class UploadResultMessage implements NetworkMessage<ClientNetworkContext>
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void toBytes(FriendlyByteBuf buf) {
 | 
			
		||||
    public void write(FriendlyByteBuf buf) {
 | 
			
		||||
        buf.writeVarInt(containerId);
 | 
			
		||||
        buf.writeEnum(result);
 | 
			
		||||
        if (result == UploadResult.ERROR) buf.writeComponent(Nullability.assertNonNull(errorMessage));
 | 
			
		||||
@@ -53,4 +55,9 @@ public class UploadResultMessage implements NetworkMessage<ClientNetworkContext>
 | 
			
		||||
    public void handle(ClientNetworkContext context) {
 | 
			
		||||
        context.handleUploadResult(containerId, result, errorMessage);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public MessageType<UploadResultMessage> type() {
 | 
			
		||||
        return NetworkMessages.UPLOAD_RESULT;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,8 @@
 | 
			
		||||
package dan200.computercraft.shared.network.server;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.shared.computer.menu.ComputerMenu;
 | 
			
		||||
import dan200.computercraft.shared.network.MessageType;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessages;
 | 
			
		||||
import net.minecraft.network.FriendlyByteBuf;
 | 
			
		||||
import net.minecraft.world.inventory.AbstractContainerMenu;
 | 
			
		||||
 | 
			
		||||
@@ -23,8 +25,8 @@ public class ComputerActionServerMessage extends ComputerServerMessage {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void toBytes(FriendlyByteBuf buf) {
 | 
			
		||||
        super.toBytes(buf);
 | 
			
		||||
    public void write(FriendlyByteBuf buf) {
 | 
			
		||||
        super.write(buf);
 | 
			
		||||
        buf.writeEnum(action);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -37,6 +39,11 @@ public class ComputerActionServerMessage extends ComputerServerMessage {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public MessageType<ComputerActionServerMessage> type() {
 | 
			
		||||
        return NetworkMessages.COMPUTER_ACTION;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public enum Action {
 | 
			
		||||
        TURN_ON,
 | 
			
		||||
        SHUTDOWN,
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@ public abstract class ComputerServerMessage implements NetworkMessage<ServerNetw
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    @OverridingMethodsMustInvokeSuper
 | 
			
		||||
    public void toBytes(FriendlyByteBuf buf) {
 | 
			
		||||
    public void write(FriendlyByteBuf buf) {
 | 
			
		||||
        buf.writeVarInt(containerId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,8 @@
 | 
			
		||||
package dan200.computercraft.shared.network.server;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.shared.computer.menu.ComputerMenu;
 | 
			
		||||
import dan200.computercraft.shared.network.MessageType;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessages;
 | 
			
		||||
import net.minecraft.network.FriendlyByteBuf;
 | 
			
		||||
import net.minecraft.world.inventory.AbstractContainerMenu;
 | 
			
		||||
 | 
			
		||||
@@ -30,8 +32,8 @@ public class KeyEventServerMessage extends ComputerServerMessage {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void toBytes(FriendlyByteBuf buf) {
 | 
			
		||||
        super.toBytes(buf);
 | 
			
		||||
    public void write(FriendlyByteBuf buf) {
 | 
			
		||||
        super.write(buf);
 | 
			
		||||
        buf.writeByte(type);
 | 
			
		||||
        buf.writeVarInt(key);
 | 
			
		||||
    }
 | 
			
		||||
@@ -45,4 +47,9 @@ public class KeyEventServerMessage extends ComputerServerMessage {
 | 
			
		||||
            input.keyDown(key, type == TYPE_REPEAT);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public MessageType<KeyEventServerMessage> type() {
 | 
			
		||||
        return NetworkMessages.KEY_EVENT;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,8 @@
 | 
			
		||||
package dan200.computercraft.shared.network.server;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.shared.computer.menu.ComputerMenu;
 | 
			
		||||
import dan200.computercraft.shared.network.MessageType;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessages;
 | 
			
		||||
import net.minecraft.network.FriendlyByteBuf;
 | 
			
		||||
import net.minecraft.world.inventory.AbstractContainerMenu;
 | 
			
		||||
 | 
			
		||||
@@ -37,8 +39,8 @@ public class MouseEventServerMessage extends ComputerServerMessage {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void toBytes(FriendlyByteBuf buf) {
 | 
			
		||||
        super.toBytes(buf);
 | 
			
		||||
    public void write(FriendlyByteBuf buf) {
 | 
			
		||||
        super.write(buf);
 | 
			
		||||
        buf.writeByte(type);
 | 
			
		||||
        buf.writeVarInt(arg);
 | 
			
		||||
        buf.writeVarInt(x);
 | 
			
		||||
@@ -55,4 +57,9 @@ public class MouseEventServerMessage extends ComputerServerMessage {
 | 
			
		||||
            case TYPE_SCROLL -> input.mouseScroll(arg, x, y);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public MessageType<MouseEventServerMessage> type() {
 | 
			
		||||
        return NetworkMessages.MOUSE_EVENT;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,8 @@ package dan200.computercraft.shared.network.server;
 | 
			
		||||
import dan200.computercraft.shared.computer.core.ServerComputer;
 | 
			
		||||
import dan200.computercraft.shared.computer.menu.ComputerMenu;
 | 
			
		||||
import dan200.computercraft.shared.computer.menu.ServerInputHandler;
 | 
			
		||||
import dan200.computercraft.shared.network.MessageType;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessages;
 | 
			
		||||
import dan200.computercraft.shared.util.NBTUtil;
 | 
			
		||||
import net.minecraft.network.FriendlyByteBuf;
 | 
			
		||||
import net.minecraft.world.inventory.AbstractContainerMenu;
 | 
			
		||||
@@ -37,8 +39,8 @@ public class QueueEventServerMessage extends ComputerServerMessage {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void toBytes(FriendlyByteBuf buf) {
 | 
			
		||||
        super.toBytes(buf);
 | 
			
		||||
    public void write(FriendlyByteBuf buf) {
 | 
			
		||||
        super.write(buf);
 | 
			
		||||
        buf.writeUtf(event);
 | 
			
		||||
        buf.writeNbt(args == null ? null : NBTUtil.encodeObjects(args));
 | 
			
		||||
    }
 | 
			
		||||
@@ -47,4 +49,9 @@ public class QueueEventServerMessage extends ComputerServerMessage {
 | 
			
		||||
    protected void handle(ServerNetworkContext context, ComputerMenu container) {
 | 
			
		||||
        container.getInput().queueEvent(event, args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public MessageType<QueueEventServerMessage> type() {
 | 
			
		||||
        return NetworkMessages.QUEUE_EVENT;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,8 @@ import dan200.computercraft.shared.computer.menu.ComputerMenu;
 | 
			
		||||
import dan200.computercraft.shared.computer.upload.FileSlice;
 | 
			
		||||
import dan200.computercraft.shared.computer.upload.FileUpload;
 | 
			
		||||
import dan200.computercraft.shared.config.Config;
 | 
			
		||||
import dan200.computercraft.shared.network.MessageType;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessages;
 | 
			
		||||
import io.netty.handler.codec.DecoderException;
 | 
			
		||||
import net.minecraft.network.FriendlyByteBuf;
 | 
			
		||||
import net.minecraft.world.inventory.AbstractContainerMenu;
 | 
			
		||||
@@ -91,8 +93,8 @@ public class UploadFileMessage extends ComputerServerMessage {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void toBytes(FriendlyByteBuf buf) {
 | 
			
		||||
        super.toBytes(buf);
 | 
			
		||||
    public void write(FriendlyByteBuf buf) {
 | 
			
		||||
        super.write(buf);
 | 
			
		||||
        buf.writeUUID(uuid);
 | 
			
		||||
        buf.writeByte(flag);
 | 
			
		||||
 | 
			
		||||
@@ -166,4 +168,9 @@ public class UploadFileMessage extends ComputerServerMessage {
 | 
			
		||||
        input.continueUpload(uuid, slices);
 | 
			
		||||
        if ((flag & FLAG_LAST) != 0) input.finishUpload(player, uuid);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public MessageType<UploadFileMessage> type() {
 | 
			
		||||
        return NetworkMessages.UPLOAD_FILE;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ import com.mojang.brigadier.arguments.ArgumentType;
 | 
			
		||||
import dan200.computercraft.api.network.wired.WiredElement;
 | 
			
		||||
import dan200.computercraft.api.peripheral.IPeripheral;
 | 
			
		||||
import dan200.computercraft.shared.config.ConfigFile;
 | 
			
		||||
import dan200.computercraft.shared.network.MessageType;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessage;
 | 
			
		||||
import dan200.computercraft.shared.network.client.ClientNetworkContext;
 | 
			
		||||
import dan200.computercraft.shared.network.container.ContainerData;
 | 
			
		||||
@@ -164,6 +165,18 @@ public interface PlatformHelper extends dan200.computercraft.impl.PlatformHelper
 | 
			
		||||
     */
 | 
			
		||||
    void openMenu(Player player, MenuProvider owner, ContainerData menu);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a new {@link MessageType}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param id      The descriminator for this message type.
 | 
			
		||||
     * @param channel The channel name for this message type.
 | 
			
		||||
     * @param klass   The type of this message.
 | 
			
		||||
     * @param reader  The function which reads the packet from a buffer. Should be the inverse to {@link NetworkMessage#write(FriendlyByteBuf)}.
 | 
			
		||||
     * @param <T>     The type of this message.
 | 
			
		||||
     * @return The new {@link MessageType} instance.
 | 
			
		||||
     */
 | 
			
		||||
    <T extends NetworkMessage<?>> MessageType<T> createMessageType(int id, ResourceLocation channel, Class<T> klass, FriendlyByteBuf.Reader<T> reader);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Send a message to a specific player.
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,6 @@ import dan200.computercraft.api.pocket.IPocketUpgrade;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeData;
 | 
			
		||||
import dan200.computercraft.core.computer.ComputerSide;
 | 
			
		||||
import dan200.computercraft.impl.PocketUpgrades;
 | 
			
		||||
import dan200.computercraft.shared.ModRegistry;
 | 
			
		||||
import dan200.computercraft.shared.common.IColouredItem;
 | 
			
		||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
 | 
			
		||||
import dan200.computercraft.shared.computer.core.ServerContext;
 | 
			
		||||
@@ -60,14 +59,6 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I
 | 
			
		||||
        this.family = family;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static ItemStack create(int id, @Nullable String label, int colour, ComputerFamily family, @Nullable UpgradeData<IPocketUpgrade> upgrade) {
 | 
			
		||||
        return switch (family) {
 | 
			
		||||
            case NORMAL -> ModRegistry.Items.POCKET_COMPUTER_NORMAL.get().create(id, label, colour, upgrade);
 | 
			
		||||
            case ADVANCED -> ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get().create(id, label, colour, upgrade);
 | 
			
		||||
            default -> ItemStack.EMPTY;
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ItemStack create(int id, @Nullable String label, int colour, @Nullable UpgradeData<IPocketUpgrade> upgrade) {
 | 
			
		||||
        var result = new ItemStack(this);
 | 
			
		||||
        if (id >= 0) result.getOrCreateTag().putInt(NBT_ID, id);
 | 
			
		||||
@@ -243,17 +234,16 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I
 | 
			
		||||
        return IComputerItem.super.getLabel(stack);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public ComputerFamily getFamily() {
 | 
			
		||||
        return family;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public ItemStack withFamily(ItemStack stack, ComputerFamily family) {
 | 
			
		||||
        return create(
 | 
			
		||||
    public ItemStack changeItem(ItemStack stack, Item newItem) {
 | 
			
		||||
        return newItem instanceof PocketComputerItem pocket ? pocket.create(
 | 
			
		||||
            getComputerID(stack), getLabel(stack), getColour(stack),
 | 
			
		||||
            family, getUpgradeWithData(stack)
 | 
			
		||||
        );
 | 
			
		||||
            getUpgradeWithData(stack)
 | 
			
		||||
        ) : ItemStack.EMPTY;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // IMedia
 | 
			
		||||
 
 | 
			
		||||
@@ -81,7 +81,6 @@ public final class PocketComputerUpgradeRecipe extends CustomRecipe {
 | 
			
		||||
        if (upgrade == null) return ItemStack.EMPTY;
 | 
			
		||||
 | 
			
		||||
        // Construct the new stack
 | 
			
		||||
        var family = itemComputer.getFamily();
 | 
			
		||||
        var computerID = itemComputer.getComputerID(computer);
 | 
			
		||||
        var label = itemComputer.getLabel(computer);
 | 
			
		||||
        var colour = itemComputer.getColour(computer);
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,6 @@ import dan200.computercraft.api.turtle.TurtleSide;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeData;
 | 
			
		||||
import dan200.computercraft.shared.computer.blocks.AbstractComputerBlock;
 | 
			
		||||
import dan200.computercraft.shared.computer.blocks.AbstractComputerBlockEntity;
 | 
			
		||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
 | 
			
		||||
import dan200.computercraft.shared.platform.RegistryEntry;
 | 
			
		||||
import dan200.computercraft.shared.turtle.core.TurtleBrain;
 | 
			
		||||
import dan200.computercraft.shared.turtle.items.TurtleItem;
 | 
			
		||||
@@ -52,6 +51,16 @@ import static dan200.computercraft.shared.util.WaterloggableHelpers.getFluidStat
 | 
			
		||||
public class TurtleBlock extends AbstractComputerBlock<TurtleBlockEntity> implements SimpleWaterloggedBlock {
 | 
			
		||||
    public static final DirectionProperty FACING = BlockStateProperties.HORIZONTAL_FACING;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The explosion resistance to use when a turtle is "immune" to explosions.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This is used as the default explosion resistance for advanced turtles, and the resistance for entity-based
 | 
			
		||||
     * explosions (e.g. creepers).
 | 
			
		||||
     *
 | 
			
		||||
     * @see #getExplosionResistance(BlockState, BlockGetter, BlockPos, Explosion)
 | 
			
		||||
     */
 | 
			
		||||
    public static final float IMMUNE_EXPLOSION_RESISTANCE = 2000f;
 | 
			
		||||
 | 
			
		||||
    private static final VoxelShape DEFAULT_SHAPE = Shapes.box(
 | 
			
		||||
        0.125, 0.125, 0.125,
 | 
			
		||||
        0.875, 0.875, 0.875
 | 
			
		||||
@@ -59,8 +68,8 @@ public class TurtleBlock extends AbstractComputerBlock<TurtleBlockEntity> implem
 | 
			
		||||
 | 
			
		||||
    private final BlockEntityTicker<TurtleBlockEntity> clientTicker = (level, pos, state, computer) -> computer.clientTick();
 | 
			
		||||
 | 
			
		||||
    public TurtleBlock(Properties settings, ComputerFamily family, RegistryEntry<BlockEntityType<TurtleBlockEntity>> type) {
 | 
			
		||||
        super(settings, family, type);
 | 
			
		||||
    public TurtleBlock(Properties settings, RegistryEntry<BlockEntityType<TurtleBlockEntity>> type) {
 | 
			
		||||
        super(settings, type);
 | 
			
		||||
        registerDefaultState(getStateDefinition().any()
 | 
			
		||||
            .setValue(FACING, Direction.NORTH)
 | 
			
		||||
            .setValue(WATERLOGGED, false)
 | 
			
		||||
@@ -149,20 +158,21 @@ public class TurtleBlock extends AbstractComputerBlock<TurtleBlockEntity> implem
 | 
			
		||||
    @ForgeOverride
 | 
			
		||||
    public float getExplosionResistance(BlockState state, BlockGetter world, BlockPos pos, Explosion explosion) {
 | 
			
		||||
        var exploder = explosion.getDirectSourceEntity();
 | 
			
		||||
        if (getFamily() == ComputerFamily.ADVANCED || exploder instanceof LivingEntity || exploder instanceof AbstractHurtingProjectile) {
 | 
			
		||||
            return 2000;
 | 
			
		||||
        if (exploder instanceof LivingEntity || exploder instanceof AbstractHurtingProjectile) {
 | 
			
		||||
            return IMMUNE_EXPLOSION_RESISTANCE;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return explosionResistance;
 | 
			
		||||
        return getExplosionResistance();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected ItemStack getItem(AbstractComputerBlockEntity tile) {
 | 
			
		||||
        if (!(tile instanceof TurtleBlockEntity turtle)) return ItemStack.EMPTY;
 | 
			
		||||
        if (!(asItem() instanceof TurtleItem item)) return ItemStack.EMPTY;
 | 
			
		||||
 | 
			
		||||
        var access = turtle.getAccess();
 | 
			
		||||
        return TurtleItem.create(
 | 
			
		||||
            turtle.getComputerID(), turtle.getLabel(), access.getColour(), turtle.getFamily(),
 | 
			
		||||
        return item.create(
 | 
			
		||||
            turtle.getComputerID(), turtle.getLabel(), access.getColour(),
 | 
			
		||||
            withPersistedData(access.getUpgradeWithData(TurtleSide.LEFT)),
 | 
			
		||||
            withPersistedData(access.getUpgradeWithData(TurtleSide.RIGHT)),
 | 
			
		||||
            access.getFuelLevel(), turtle.getOverlay()
 | 
			
		||||
 
 | 
			
		||||
@@ -38,6 +38,7 @@ import net.minecraft.world.phys.Vec3;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.function.IntSupplier;
 | 
			
		||||
 | 
			
		||||
public class TurtleBlockEntity extends AbstractComputerBlockEntity implements BasicContainer {
 | 
			
		||||
    public static final int INVENTORY_SIZE = 16;
 | 
			
		||||
@@ -53,13 +54,17 @@ public class TurtleBlockEntity extends AbstractComputerBlockEntity implements Ba
 | 
			
		||||
    private final NonNullList<ItemStack> inventory = NonNullList.withSize(INVENTORY_SIZE, ItemStack.EMPTY);
 | 
			
		||||
    private final NonNullList<ItemStack> inventorySnapshot = NonNullList.withSize(INVENTORY_SIZE, ItemStack.EMPTY);
 | 
			
		||||
    private boolean inventoryChanged = false;
 | 
			
		||||
 | 
			
		||||
    private final IntSupplier fuelLimit;
 | 
			
		||||
 | 
			
		||||
    private TurtleBrain brain = new TurtleBrain(this);
 | 
			
		||||
    private MoveState moveState = MoveState.NOT_MOVED;
 | 
			
		||||
    private @Nullable IPeripheral peripheral;
 | 
			
		||||
    private @Nullable Runnable onMoved;
 | 
			
		||||
 | 
			
		||||
    public TurtleBlockEntity(BlockEntityType<? extends TurtleBlockEntity> type, BlockPos pos, BlockState state, ComputerFamily family) {
 | 
			
		||||
    public TurtleBlockEntity(BlockEntityType<? extends TurtleBlockEntity> type, BlockPos pos, BlockState state, IntSupplier fuelLimit, ComputerFamily family) {
 | 
			
		||||
        super(type, pos, state, family);
 | 
			
		||||
        this.fuelLimit = fuelLimit;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    boolean hasMoved() {
 | 
			
		||||
@@ -172,8 +177,6 @@ public class TurtleBlockEntity extends AbstractComputerBlockEntity implements Ba
 | 
			
		||||
        return hasPeripheralUpgradeOnSide(localSide);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // IDirectionalTile
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Direction getDirection() {
 | 
			
		||||
        return getBlockState().getValue(TurtleBlock.FACING);
 | 
			
		||||
@@ -272,6 +275,10 @@ public class TurtleBlockEntity extends AbstractComputerBlockEntity implements Ba
 | 
			
		||||
 | 
			
		||||
    // Privates
 | 
			
		||||
 | 
			
		||||
    public int getFuelLimit() {
 | 
			
		||||
        return fuelLimit.getAsInt();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private boolean hasPeripheralUpgradeOnSide(ComputerSide side) {
 | 
			
		||||
        ITurtleUpgrade upgrade;
 | 
			
		||||
        switch (side) {
 | 
			
		||||
 
 | 
			
		||||
@@ -395,11 +395,7 @@ public class TurtleBrain implements TurtleAccessInternal {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int getFuelLimit() {
 | 
			
		||||
        if (owner.getFamily() == ComputerFamily.ADVANCED) {
 | 
			
		||||
            return Config.advancedTurtleFuelLimit;
 | 
			
		||||
        } else {
 | 
			
		||||
            return Config.turtleFuelLimit;
 | 
			
		||||
        }
 | 
			
		||||
        return owner.getFuelLimit();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
 
 | 
			
		||||
@@ -10,9 +10,7 @@ import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleSide;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeData;
 | 
			
		||||
import dan200.computercraft.impl.TurtleUpgrades;
 | 
			
		||||
import dan200.computercraft.shared.ModRegistry;
 | 
			
		||||
import dan200.computercraft.shared.common.IColouredItem;
 | 
			
		||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
 | 
			
		||||
import dan200.computercraft.shared.computer.items.AbstractComputerItem;
 | 
			
		||||
import dan200.computercraft.shared.turtle.blocks.TurtleBlock;
 | 
			
		||||
import dan200.computercraft.shared.util.NBTUtil;
 | 
			
		||||
@@ -20,6 +18,7 @@ import net.minecraft.core.cauldron.CauldronInteraction;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.InteractionResult;
 | 
			
		||||
import net.minecraft.world.item.Item;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.level.block.LayeredCauldronBlock;
 | 
			
		||||
 | 
			
		||||
@@ -32,20 +31,6 @@ public class TurtleItem extends AbstractComputerItem implements IColouredItem {
 | 
			
		||||
        super(block, settings);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static ItemStack create(
 | 
			
		||||
        int id, @Nullable String label, int colour, ComputerFamily family,
 | 
			
		||||
        @Nullable UpgradeData<ITurtleUpgrade> leftUpgrade, @Nullable UpgradeData<ITurtleUpgrade> rightUpgrade,
 | 
			
		||||
        int fuelLevel, @Nullable ResourceLocation overlay
 | 
			
		||||
    ) {
 | 
			
		||||
        return switch (family) {
 | 
			
		||||
            case NORMAL ->
 | 
			
		||||
                ModRegistry.Items.TURTLE_NORMAL.get().create(id, label, colour, leftUpgrade, rightUpgrade, fuelLevel, overlay);
 | 
			
		||||
            case ADVANCED ->
 | 
			
		||||
                ModRegistry.Items.TURTLE_ADVANCED.get().create(id, label, colour, leftUpgrade, rightUpgrade, fuelLevel, overlay);
 | 
			
		||||
            default -> ItemStack.EMPTY;
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ItemStack create(
 | 
			
		||||
        int id, @Nullable String label, int colour,
 | 
			
		||||
        @Nullable UpgradeData<ITurtleUpgrade> leftUpgrade, @Nullable UpgradeData<ITurtleUpgrade> rightUpgrade,
 | 
			
		||||
@@ -119,13 +104,13 @@ public class TurtleItem extends AbstractComputerItem implements IColouredItem {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public ItemStack withFamily(ItemStack stack, ComputerFamily family) {
 | 
			
		||||
        return create(
 | 
			
		||||
    public ItemStack changeItem(ItemStack stack, Item newItem) {
 | 
			
		||||
        return newItem instanceof TurtleItem turtle ? turtle.create(
 | 
			
		||||
            getComputerID(stack), getLabel(stack),
 | 
			
		||||
            getColour(stack), family,
 | 
			
		||||
            getColour(stack),
 | 
			
		||||
            getUpgradeWithData(stack, TurtleSide.LEFT), getUpgradeWithData(stack, TurtleSide.RIGHT),
 | 
			
		||||
            getFuelLevel(stack), getOverlay(stack)
 | 
			
		||||
        );
 | 
			
		||||
        ) : ItemStack.EMPTY;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public @Nullable ITurtleUpgrade getUpgrade(ItemStack stack, TurtleSide side) {
 | 
			
		||||
 
 | 
			
		||||
@@ -58,7 +58,7 @@ public class TurtleOverlayRecipe extends CustomShapelessRecipe {
 | 
			
		||||
        return ModRegistry.RecipeSerializers.TURTLE_OVERLAY.get();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static class Serializer implements RecipeSerializer<TurtleOverlayRecipe> {
 | 
			
		||||
    public static class Serialiser implements RecipeSerializer<TurtleOverlayRecipe> {
 | 
			
		||||
        @Override
 | 
			
		||||
        public TurtleOverlayRecipe fromJson(ResourceLocation id, JsonObject json) {
 | 
			
		||||
            var recipe = ShapelessRecipeSpec.fromJson(json);
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ accessWidener v1 named
 | 
			
		||||
accessible method net/minecraft/client/renderer/item/ItemProperties register (Lnet/minecraft/world/item/Item;Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/client/renderer/item/ClampedItemPropertyFunction;)V
 | 
			
		||||
accessible method net/minecraft/client/renderer/blockentity/BlockEntityRenderers register (Lnet/minecraft/world/level/block/entity/BlockEntityType;Lnet/minecraft/client/renderer/blockentity/BlockEntityRendererProvider;)V
 | 
			
		||||
accessible class net/minecraft/world/item/CreativeModeTab$Output
 | 
			
		||||
accessible field net/minecraft/world/item/CreativeModeTabs OP_BLOCKS Lnet/minecraft/resources/ResourceKey;
 | 
			
		||||
 | 
			
		||||
# Containers
 | 
			
		||||
accessible class net/minecraft/world/inventory/MenuType$MenuSupplier
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@ import dan200.computercraft.api.peripheral.IPeripheral;
 | 
			
		||||
import dan200.computercraft.impl.AbstractComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.impl.ComputerCraftAPIService;
 | 
			
		||||
import dan200.computercraft.shared.config.ConfigFile;
 | 
			
		||||
import dan200.computercraft.shared.network.MessageType;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessage;
 | 
			
		||||
import dan200.computercraft.shared.network.client.ClientNetworkContext;
 | 
			
		||||
import dan200.computercraft.shared.network.container.ContainerData;
 | 
			
		||||
@@ -168,6 +169,13 @@ public class TestPlatformHelper extends AbstractComputerCraftAPI implements Plat
 | 
			
		||||
        throw new UnsupportedOperationException("Cannot open menu inside tests");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public <T extends NetworkMessage<?>> MessageType<T> createMessageType(int id, ResourceLocation channel, Class<T> klass, FriendlyByteBuf.Reader<T> reader) {
 | 
			
		||||
        record TypeImpl<T extends NetworkMessage<?>>(Function<FriendlyByteBuf, T> reader) implements MessageType<T> {
 | 
			
		||||
        }
 | 
			
		||||
        return new TypeImpl<>(reader);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public ComponentAccess<IPeripheral> createPeripheralAccess(Consumer<Direction> invalidate) {
 | 
			
		||||
        throw new UnsupportedOperationException("Cannot interact with the world inside tests");
 | 
			
		||||
 
 | 
			
		||||
@@ -29,7 +29,7 @@ class PlayRecordClientMessageTest {
 | 
			
		||||
    @Property
 | 
			
		||||
    public void testRoundTrip(@ForAll("message") PlayRecordClientMessage message) {
 | 
			
		||||
        var buffer = new FriendlyByteBuf(Unpooled.directBuffer());
 | 
			
		||||
        message.toBytes(buffer);
 | 
			
		||||
        message.write(buffer);
 | 
			
		||||
 | 
			
		||||
        var converted = new PlayRecordClientMessage(buffer);
 | 
			
		||||
        assertEquals(buffer.readableBytes(), 0, "Whole packet was read");
 | 
			
		||||
 
 | 
			
		||||
@@ -63,7 +63,7 @@ public class UploadFileMessageTest {
 | 
			
		||||
    private static List<UploadFileMessage> roundtripPackets(List<UploadFileMessage> packets) {
 | 
			
		||||
        return packets.stream().map(packet -> {
 | 
			
		||||
            var buffer = new FriendlyByteBuf(Unpooled.directBuffer());
 | 
			
		||||
            packet.toBytes(buffer);
 | 
			
		||||
            packet.write(buffer);
 | 
			
		||||
            // We include things like file size in the packet, but not in the count, so grant a slightly larger threshold.
 | 
			
		||||
            assertThat("Packet is too large", buffer.writerIndex(), lessThanOrEqualTo(MAX_PACKET_SIZE + 128));
 | 
			
		||||
            if ((packet.flag & FLAG_LAST) == 0) {
 | 
			
		||||
 
 | 
			
		||||
@@ -5,14 +5,13 @@
 | 
			
		||||
package dan200.computercraft.core.apis.http.websocket;
 | 
			
		||||
 | 
			
		||||
import com.google.common.base.Strings;
 | 
			
		||||
import dan200.computercraft.api.lua.LuaException;
 | 
			
		||||
import dan200.computercraft.core.Logging;
 | 
			
		||||
import dan200.computercraft.core.apis.IAPIEnvironment;
 | 
			
		||||
import dan200.computercraft.core.apis.http.HTTPRequestException;
 | 
			
		||||
import dan200.computercraft.core.apis.http.NetworkUtils;
 | 
			
		||||
import dan200.computercraft.core.apis.http.Resource;
 | 
			
		||||
import dan200.computercraft.core.apis.http.ResourceGroup;
 | 
			
		||||
import dan200.computercraft.core.apis.http.*;
 | 
			
		||||
import dan200.computercraft.core.apis.http.options.Options;
 | 
			
		||||
import dan200.computercraft.core.metrics.Metrics;
 | 
			
		||||
import dan200.computercraft.core.util.AtomicHelpers;
 | 
			
		||||
import io.netty.bootstrap.Bootstrap;
 | 
			
		||||
import io.netty.buffer.Unpooled;
 | 
			
		||||
import io.netty.channel.Channel;
 | 
			
		||||
@@ -24,10 +23,8 @@ import io.netty.handler.codec.http.HttpClientCodec;
 | 
			
		||||
import io.netty.handler.codec.http.HttpHeaderNames;
 | 
			
		||||
import io.netty.handler.codec.http.HttpHeaders;
 | 
			
		||||
import io.netty.handler.codec.http.HttpObjectAggregator;
 | 
			
		||||
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
 | 
			
		||||
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
 | 
			
		||||
import io.netty.handler.codec.http.websocketx.WebSocketClientProtocolHandler;
 | 
			
		||||
import io.netty.handler.codec.http.websocketx.WebSocketVersion;
 | 
			
		||||
import io.netty.handler.codec.http.websocketx.*;
 | 
			
		||||
import io.netty.util.concurrent.GenericFutureListener;
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
 | 
			
		||||
@@ -35,6 +32,7 @@ import javax.annotation.Nullable;
 | 
			
		||||
import java.net.URI;
 | 
			
		||||
import java.nio.ByteBuffer;
 | 
			
		||||
import java.util.concurrent.Future;
 | 
			
		||||
import java.util.concurrent.atomic.AtomicInteger;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides functionality to verify and connect to a remote websocket.
 | 
			
		||||
@@ -57,6 +55,9 @@ public class Websocket extends Resource<Websocket> implements WebsocketClient {
 | 
			
		||||
    private final HttpHeaders headers;
 | 
			
		||||
    private final int timeout;
 | 
			
		||||
 | 
			
		||||
    private final AtomicInteger inFlight = new AtomicInteger(0);
 | 
			
		||||
    private final GenericFutureListener<? extends io.netty.util.concurrent.Future<? super Void>> onSend = f -> inFlight.decrementAndGet();
 | 
			
		||||
 | 
			
		||||
    public Websocket(ResourceGroup<Websocket> limiter, IAPIEnvironment environment, URI uri, String address, HttpHeaders headers, int timeout) {
 | 
			
		||||
        super(limiter);
 | 
			
		||||
        this.environment = environment;
 | 
			
		||||
@@ -170,18 +171,27 @@ public class Websocket extends Resource<Websocket> implements WebsocketClient {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void sendText(String message) {
 | 
			
		||||
        environment.observe(Metrics.WEBSOCKET_OUTGOING, message.length());
 | 
			
		||||
 | 
			
		||||
        var channel = channel();
 | 
			
		||||
        if (channel != null) channel.writeAndFlush(new TextWebSocketFrame(message));
 | 
			
		||||
    public void sendText(String message) throws LuaException {
 | 
			
		||||
        sendMessage(new TextWebSocketFrame(message), message.length());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void sendBinary(ByteBuffer message) {
 | 
			
		||||
        environment.observe(Metrics.WEBSOCKET_OUTGOING, message.remaining());
 | 
			
		||||
    public void sendBinary(ByteBuffer message) throws LuaException {
 | 
			
		||||
        long size = message.remaining();
 | 
			
		||||
        sendMessage(new BinaryWebSocketFrame(Unpooled.wrappedBuffer(message)), size);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void sendMessage(WebSocketFrame frame, long size) throws LuaException {
 | 
			
		||||
        var channel = channel();
 | 
			
		||||
        if (channel != null) channel.writeAndFlush(new BinaryWebSocketFrame(Unpooled.wrappedBuffer(message)));
 | 
			
		||||
        if (channel == null) return;
 | 
			
		||||
 | 
			
		||||
        // Grow the number of in-flight requests, aborting if we've hit the limit. This is then decremented when the
 | 
			
		||||
        // promise finishes.
 | 
			
		||||
        if (!AtomicHelpers.incrementToLimit(inFlight, ResourceQueue.DEFAULT_LIMIT)) {
 | 
			
		||||
            throw new LuaException("Too many ongoing websocket messages");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        environment.observe(Metrics.WEBSOCKET_OUTGOING, size);
 | 
			
		||||
        channel.writeAndFlush(frame).addListener(onSend);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.core.apis.http.websocket;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.lua.LuaException;
 | 
			
		||||
import dan200.computercraft.core.apis.http.HTTPRequestException;
 | 
			
		||||
 | 
			
		||||
import java.io.Closeable;
 | 
			
		||||
@@ -39,15 +40,17 @@ public interface WebsocketClient extends Closeable {
 | 
			
		||||
     * Send a text websocket frame.
 | 
			
		||||
     *
 | 
			
		||||
     * @param message The message to send.
 | 
			
		||||
     * @throws LuaException If the message could not be sent.
 | 
			
		||||
     */
 | 
			
		||||
    void sendText(String message);
 | 
			
		||||
    void sendText(String message) throws LuaException;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Send a binary websocket frame.
 | 
			
		||||
     *
 | 
			
		||||
     * @param message The message to send.
 | 
			
		||||
     * @throws LuaException If the message could not be sent.
 | 
			
		||||
     */
 | 
			
		||||
    void sendBinary(ByteBuffer message);
 | 
			
		||||
    void sendBinary(ByteBuffer message) throws LuaException;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Parse an address, ensuring it is a valid websocket URI.
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,29 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.core.util;
 | 
			
		||||
 | 
			
		||||
import java.util.concurrent.atomic.AtomicInteger;
 | 
			
		||||
 | 
			
		||||
public final class AtomicHelpers {
 | 
			
		||||
    private AtomicHelpers() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A version of {@link AtomicInteger#getAndIncrement()}, which increments until a limit is reached.
 | 
			
		||||
     *
 | 
			
		||||
     * @param atomic The atomic to increment.
 | 
			
		||||
     * @param limit  The maximum value of {@code value}.
 | 
			
		||||
     * @return Whether the value was sucessfully incremented.
 | 
			
		||||
     */
 | 
			
		||||
    public static boolean incrementToLimit(AtomicInteger atomic, int limit) {
 | 
			
		||||
        int value;
 | 
			
		||||
        do {
 | 
			
		||||
            value = atomic.get();
 | 
			
		||||
            if (value >= limit) return false;
 | 
			
		||||
        } while (!atomic.compareAndSet(value, value + 1));
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,3 +1,20 @@
 | 
			
		||||
# New features in CC: Tweaked 1.109.3
 | 
			
		||||
 | 
			
		||||
* Command computers now display in the operator items creative tab.
 | 
			
		||||
 | 
			
		||||
Several bug fixes:
 | 
			
		||||
* Error if too many websocket messages are queued to be sent at once.
 | 
			
		||||
* Fix trailing-comma on method calls (e.g. `x:f(a, )` not using our custom error message.
 | 
			
		||||
* Fix internal compiler error when using `goto` as the first statement in an `if` block.
 | 
			
		||||
* Fix incorrect incorrect resizing of a tables' hash part when adding and removing keys.
 | 
			
		||||
 | 
			
		||||
# New features in CC: Tweaked 1.109.2
 | 
			
		||||
 | 
			
		||||
* `math.random` now uses Lua 5.4's random number generator.
 | 
			
		||||
 | 
			
		||||
Several bug fixes:
 | 
			
		||||
* Fix errors involving `goto` statements having the wrong line number.
 | 
			
		||||
 | 
			
		||||
# New features in CC: Tweaked 1.109.1
 | 
			
		||||
 | 
			
		||||
Several bug fixes:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,11 @@
 | 
			
		||||
New features in CC: Tweaked 1.109.1
 | 
			
		||||
New features in CC: Tweaked 1.109.3
 | 
			
		||||
 | 
			
		||||
* Command computers now display in the operator items creative tab.
 | 
			
		||||
 | 
			
		||||
Several bug fixes:
 | 
			
		||||
* Fix `mouse_drag` event not firing for right and middle mouse buttons.
 | 
			
		||||
* Fix crash when syntax errors involve `goto` or `::`.
 | 
			
		||||
* Fix deadlock occuring when adding/removing observers.
 | 
			
		||||
* Allow placing seeds into compostor barrels with `turtle.place()`.
 | 
			
		||||
* Error if too many websocket messages are queued to be sent at once.
 | 
			
		||||
* Fix trailing-comma on method calls (e.g. `x:f(a, )` not using our custom error message.
 | 
			
		||||
* Fix internal compiler error when using `goto` as the first statement in an `if` block.
 | 
			
		||||
* Fix incorrect incorrect resizing of a tables' hash part when adding and removing keys.
 | 
			
		||||
 | 
			
		||||
Type "help changelog" to see the full version history.
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -89,6 +89,30 @@ class TestHttpApi {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun `Errors if too many websocket messages are sent`() {
 | 
			
		||||
        runServer {
 | 
			
		||||
            LuaTaskRunner.runTest {
 | 
			
		||||
                val httpApi = addApi(HTTPAPI(environment))
 | 
			
		||||
                assertThat("http.websocket succeeded", httpApi.websocket(ObjectArguments(WS_URL)), array(equalTo(true)))
 | 
			
		||||
 | 
			
		||||
                val connectEvent = pullEvent()
 | 
			
		||||
                assertThat(connectEvent, array(equalTo("websocket_success"), equalTo(WS_URL), isA(WebsocketHandle::class.java)))
 | 
			
		||||
 | 
			
		||||
                val websocket = connectEvent[2] as WebsocketHandle
 | 
			
		||||
                val error = assertThrows<LuaException> {
 | 
			
		||||
                    for (i in 0 until 10_000) {
 | 
			
		||||
                        websocket.send(Coerced(LuaValues.encode("Hello")), Optional.of(false))
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                websocket.close()
 | 
			
		||||
 | 
			
		||||
                assertThat(error.message, equalTo("Too many ongoing websocket messages"))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun `Queues an event when the socket is externally closed`() {
 | 
			
		||||
        runServer { stop ->
 | 
			
		||||
 
 | 
			
		||||
@@ -478,6 +478,7 @@ Unexpected ) in function call.
 | 
			
		||||
 1 | f(2, )
 | 
			
		||||
   |    ^ Tip: Try removing this ,.
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```lua
 | 
			
		||||
f(2, 3, )
 | 
			
		||||
```
 | 
			
		||||
@@ -491,3 +492,17 @@ Unexpected ) in function call.
 | 
			
		||||
 1 | f(2, 3, )
 | 
			
		||||
   |       ^ Tip: Try removing this ,.
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```lua
 | 
			
		||||
x:f(2, 3, )
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```txt
 | 
			
		||||
Unexpected ) in function call.
 | 
			
		||||
   |
 | 
			
		||||
 1 | x:f(2, 3, )
 | 
			
		||||
   |           ^
 | 
			
		||||
   |
 | 
			
		||||
 1 | x:f(2, 3, )
 | 
			
		||||
   |         ^ Tip: Try removing this ,.
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
@@ -48,7 +48,7 @@ addRemappedConfiguration("testWithIris")
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    clientCompileOnly(variantOf(libs.emi) { classifier("api") })
 | 
			
		||||
    modImplementation(libs.bundles.externalMods.fabric)
 | 
			
		||||
    modImplementation(libs.bundles.externalMods.fabric) { cct.exclude(this) }
 | 
			
		||||
    modCompileOnly(libs.bundles.externalMods.fabric.compile) {
 | 
			
		||||
        exclude("net.fabricmc", "fabric-loader")
 | 
			
		||||
        exclude("net.fabricmc.fabric-api")
 | 
			
		||||
@@ -72,9 +72,9 @@ dependencies {
 | 
			
		||||
    include(libs.nightConfig.toml)
 | 
			
		||||
 | 
			
		||||
    // Pull in our other projects. See comments in MinecraftConfigurations on this nastiness.
 | 
			
		||||
    api(commonClasses(project(":fabric-api")))
 | 
			
		||||
    clientApi(clientClasses(project(":fabric-api")))
 | 
			
		||||
    implementation(project(":core"))
 | 
			
		||||
    api(commonClasses(project(":fabric-api"))) { cct.exclude(this) }
 | 
			
		||||
    clientApi(clientClasses(project(":fabric-api"))) { cct.exclude(this) }
 | 
			
		||||
    implementation(project(":core")) { cct.exclude(this) }
 | 
			
		||||
    // These are transitive deps of :core, so we don't need these deps. However, we want them to appear as runtime deps
 | 
			
		||||
    // in our POM, and this is the easiest way.
 | 
			
		||||
    runtimeOnly(libs.cobalt)
 | 
			
		||||
@@ -168,7 +168,11 @@ loom {
 | 
			
		||||
            configureForGameTest(this)
 | 
			
		||||
 | 
			
		||||
            property("fabric-api.gametest")
 | 
			
		||||
            property("fabric-api.gametest.report-file", layout.buildDirectory.dir("test-results/runGametest.xml").getAbsolutePath())
 | 
			
		||||
            property(
 | 
			
		||||
                "fabric-api.gametest.report-file",
 | 
			
		||||
                layout.buildDirectory.dir("test-results/runGametest.xml")
 | 
			
		||||
                    .getAbsolutePath(),
 | 
			
		||||
            )
 | 
			
		||||
            runDir("run/gametest")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -258,9 +262,7 @@ publishing {
 | 
			
		||||
    publications {
 | 
			
		||||
        named("maven", MavenPublication::class) {
 | 
			
		||||
            mavenDependencies {
 | 
			
		||||
                exclude(dependencies.create("cc.tweaked:"))
 | 
			
		||||
                exclude(libs.jei.fabric.get())
 | 
			
		||||
                exclude(libs.modmenu.get())
 | 
			
		||||
                cct.configureExcludes(this)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -8,10 +8,11 @@ import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.client.model.CustomModelLoader;
 | 
			
		||||
import dan200.computercraft.shared.ModRegistry;
 | 
			
		||||
import dan200.computercraft.shared.config.ConfigSpec;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessages;
 | 
			
		||||
import dan200.computercraft.shared.network.client.ClientNetworkContext;
 | 
			
		||||
import dan200.computercraft.shared.peripheral.modem.wired.CableBlock;
 | 
			
		||||
import dan200.computercraft.shared.platform.FabricConfigFile;
 | 
			
		||||
import dan200.computercraft.shared.platform.NetworkHandler;
 | 
			
		||||
import dan200.computercraft.shared.platform.FabricMessageType;
 | 
			
		||||
import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap;
 | 
			
		||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
 | 
			
		||||
import net.fabricmc.fabric.api.client.model.loading.v1.PreparableModelLoadingPlugin;
 | 
			
		||||
@@ -30,10 +31,11 @@ import static dan200.computercraft.core.util.Nullability.assertNonNull;
 | 
			
		||||
 | 
			
		||||
public class ComputerCraftClient {
 | 
			
		||||
    public static void init() {
 | 
			
		||||
        ClientPlayNetworking.registerGlobalReceiver(NetworkHandler.ID, (client, handler, buf, responseSender) -> {
 | 
			
		||||
            var packet = NetworkHandler.decodeClient(buf);
 | 
			
		||||
            if (packet != null) client.execute(() -> packet.handle(ClientNetworkContext.get()));
 | 
			
		||||
        });
 | 
			
		||||
        for (var type : NetworkMessages.getClientbound()) {
 | 
			
		||||
            ClientPlayNetworking.registerGlobalReceiver(
 | 
			
		||||
                FabricMessageType.toFabricType(type), (packet, player, responseSender) -> packet.payload().handle(ClientNetworkContext.get())
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ClientRegistry.register();
 | 
			
		||||
        ClientRegistry.registerItemColours(ColorProviderRegistry.ITEM::register);
 | 
			
		||||
 
 | 
			
		||||
@@ -10,9 +10,9 @@ import dan200.computercraft.client.model.FoiledModel;
 | 
			
		||||
import dan200.computercraft.client.render.ModelRenderer;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessage;
 | 
			
		||||
import dan200.computercraft.shared.network.server.ServerNetworkContext;
 | 
			
		||||
import dan200.computercraft.shared.platform.NetworkHandler;
 | 
			
		||||
import dan200.computercraft.shared.platform.FabricMessageType;
 | 
			
		||||
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
 | 
			
		||||
import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper;
 | 
			
		||||
import net.minecraft.client.Minecraft;
 | 
			
		||||
import net.minecraft.client.renderer.MultiBufferSource;
 | 
			
		||||
import net.minecraft.client.renderer.Sheets;
 | 
			
		||||
import net.minecraft.client.renderer.entity.ItemRenderer;
 | 
			
		||||
@@ -29,7 +29,7 @@ public class ClientPlatformHelperImpl implements ClientPlatformHelper {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void sendToServer(NetworkMessage<ServerNetworkContext> message) {
 | 
			
		||||
        Minecraft.getInstance().player.connection.send(NetworkHandler.encodeServer(message));
 | 
			
		||||
        ClientPlayNetworking.send(FabricMessageType.toFabricPacket(message));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "type": "computercraft:computer_upgrade",
 | 
			
		||||
  "category": "redstone",
 | 
			
		||||
  "family": "advanced",
 | 
			
		||||
  "key": {"#": {"tag": "c:gold_ingots"}, "C": {"item": "computercraft:computer_normal"}},
 | 
			
		||||
  "pattern": ["###", "#C#", "# #"],
 | 
			
		||||
  "result": {"item": "computercraft:computer_advanced"},
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "type": "computercraft:computer_upgrade",
 | 
			
		||||
  "category": "redstone",
 | 
			
		||||
  "family": "advanced",
 | 
			
		||||
  "key": {"#": {"tag": "c:gold_ingots"}, "C": {"item": "computercraft:pocket_computer_normal"}},
 | 
			
		||||
  "pattern": ["###", "#C#", "# #"],
 | 
			
		||||
  "result": {"item": "computercraft:pocket_computer_advanced"},
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "type": "computercraft:turtle",
 | 
			
		||||
  "category": "redstone",
 | 
			
		||||
  "family": "advanced",
 | 
			
		||||
  "key": {
 | 
			
		||||
    "#": {"tag": "c:gold_ingots"},
 | 
			
		||||
    "C": {"item": "computercraft:computer_advanced"},
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "type": "computercraft:computer_upgrade",
 | 
			
		||||
  "category": "redstone",
 | 
			
		||||
  "family": "advanced",
 | 
			
		||||
  "key": {
 | 
			
		||||
    "#": {"tag": "c:gold_ingots"},
 | 
			
		||||
    "B": {"item": "minecraft:gold_block"},
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "type": "computercraft:turtle",
 | 
			
		||||
  "category": "redstone",
 | 
			
		||||
  "family": "normal",
 | 
			
		||||
  "key": {
 | 
			
		||||
    "#": {"tag": "c:iron_ingots"},
 | 
			
		||||
    "C": {"item": "computercraft:computer_normal"},
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								projects/fabric/src/generated/resources/data/minecraft/tags/blocks/wither_immune.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								projects/fabric/src/generated/resources/data/minecraft/tags/blocks/wither_immune.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
{"replace": false, "values": ["computercraft:computer_command"]}
 | 
			
		||||
@@ -13,6 +13,7 @@ import dan200.computercraft.shared.command.CommandComputerCraft;
 | 
			
		||||
import dan200.computercraft.shared.config.Config;
 | 
			
		||||
import dan200.computercraft.shared.config.ConfigSpec;
 | 
			
		||||
import dan200.computercraft.shared.details.FluidDetails;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessages;
 | 
			
		||||
import dan200.computercraft.shared.network.client.UpgradesLoadedMessage;
 | 
			
		||||
import dan200.computercraft.shared.peripheral.commandblock.CommandBlockPeripheral;
 | 
			
		||||
import dan200.computercraft.shared.peripheral.generic.methods.InventoryMethods;
 | 
			
		||||
@@ -20,16 +21,19 @@ import dan200.computercraft.shared.peripheral.modem.wired.CableBlockEntity;
 | 
			
		||||
import dan200.computercraft.shared.peripheral.modem.wired.WiredModemFullBlockEntity;
 | 
			
		||||
import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemBlockEntity;
 | 
			
		||||
import dan200.computercraft.shared.platform.FabricConfigFile;
 | 
			
		||||
import dan200.computercraft.shared.platform.NetworkHandler;
 | 
			
		||||
import dan200.computercraft.shared.platform.FabricMessageType;
 | 
			
		||||
import dan200.computercraft.shared.platform.PlatformHelper;
 | 
			
		||||
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
 | 
			
		||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
 | 
			
		||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
 | 
			
		||||
import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents;
 | 
			
		||||
import net.fabricmc.fabric.api.event.player.UseBlockCallback;
 | 
			
		||||
import net.fabricmc.fabric.api.itemgroup.v1.ItemGroupEvents;
 | 
			
		||||
import net.fabricmc.fabric.api.loot.v2.LootTableEvents;
 | 
			
		||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
 | 
			
		||||
import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener;
 | 
			
		||||
import net.fabricmc.fabric.api.resource.ResourceManagerHelper;
 | 
			
		||||
import net.minecraft.core.registries.BuiltInRegistries;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.server.packs.PackType;
 | 
			
		||||
import net.minecraft.server.packs.resources.PreparableReloadListener;
 | 
			
		||||
@@ -45,7 +49,12 @@ public class ComputerCraft {
 | 
			
		||||
    private static final LevelResource SERVERCONFIG = new LevelResource("serverconfig");
 | 
			
		||||
 | 
			
		||||
    public static void init() {
 | 
			
		||||
        NetworkHandler.init();
 | 
			
		||||
        for (var type : NetworkMessages.getServerbound()) {
 | 
			
		||||
            ServerPlayNetworking.registerGlobalReceiver(
 | 
			
		||||
                FabricMessageType.toFabricType(type), (packet, player, sender) -> packet.payload().handle(() -> player)
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ModRegistry.register();
 | 
			
		||||
        ModRegistry.registerMainThread();
 | 
			
		||||
 | 
			
		||||
@@ -96,6 +105,11 @@ public class ComputerCraft {
 | 
			
		||||
            if (pool != null) tableBuilder.withPool(pool);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        ItemGroupEvents.MODIFY_ENTRIES_ALL.register((tab, entries) -> CommonHooks.onBuildCreativeTab(
 | 
			
		||||
            BuiltInRegistries.CREATIVE_MODE_TAB.getResourceKey(tab).orElseThrow(),
 | 
			
		||||
            entries.getContext(), entries
 | 
			
		||||
        ));
 | 
			
		||||
 | 
			
		||||
        CommonHooks.onDatapackReload((name, listener) -> ResourceManagerHelper.get(PackType.SERVER_DATA).registerReloadListener(new ReloadListener(name, listener)));
 | 
			
		||||
 | 
			
		||||
        FabricDetailRegistries.FLUID_VARIANT.addProvider(FluidDetails::fill);
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,51 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.shared.platform;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.shared.network.MessageType;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessage;
 | 
			
		||||
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
 | 
			
		||||
import net.fabricmc.fabric.api.networking.v1.PacketType;
 | 
			
		||||
import net.minecraft.network.FriendlyByteBuf;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An implementation of {@link MessageType} for Fabric.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * This provides conversions between the {@link FabricPacket}/{@link PacketType} and {@link NetworkMessage}/{@link MessageType}
 | 
			
		||||
 * interfaces, allowing us to interop between the two.
 | 
			
		||||
 *
 | 
			
		||||
 * @param type The underlying {@link PacketType}
 | 
			
		||||
 * @param <T>  The type of the message.
 | 
			
		||||
 */
 | 
			
		||||
public record FabricMessageType<T extends NetworkMessage<?>>(
 | 
			
		||||
    PacketType<PacketWrapper<T>> type
 | 
			
		||||
) implements MessageType<T> {
 | 
			
		||||
    public FabricMessageType(ResourceLocation id, Function<FriendlyByteBuf, T> reader) {
 | 
			
		||||
        this(PacketType.create(id, b -> new PacketWrapper<>(reader.apply(b))));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static <T extends NetworkMessage<?>> PacketType<PacketWrapper<T>> toFabricType(MessageType<T> type) {
 | 
			
		||||
        return ((FabricMessageType<T>) type).type();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static FabricPacket toFabricPacket(NetworkMessage<?> message) {
 | 
			
		||||
        return new PacketWrapper<>(message);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public record PacketWrapper<T extends NetworkMessage<?>>(T payload) implements FabricPacket {
 | 
			
		||||
        @Override
 | 
			
		||||
        public void write(FriendlyByteBuf buf) {
 | 
			
		||||
            payload().write(buf);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public PacketType<?> getType() {
 | 
			
		||||
            return FabricMessageType.toFabricType(payload().type());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,94 +0,0 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.shared.platform;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessage;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessages;
 | 
			
		||||
import dan200.computercraft.shared.network.client.ClientNetworkContext;
 | 
			
		||||
import dan200.computercraft.shared.network.server.ServerNetworkContext;
 | 
			
		||||
import io.netty.buffer.Unpooled;
 | 
			
		||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
 | 
			
		||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
 | 
			
		||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
 | 
			
		||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
 | 
			
		||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
 | 
			
		||||
import net.minecraft.network.FriendlyByteBuf;
 | 
			
		||||
import net.minecraft.network.protocol.game.ClientboundCustomPayloadPacket;
 | 
			
		||||
import net.minecraft.network.protocol.game.ServerboundCustomPayloadPacket;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
 | 
			
		||||
public final class NetworkHandler {
 | 
			
		||||
    private static final Logger LOGGER = LoggerFactory.getLogger(NetworkHandler.class);
 | 
			
		||||
 | 
			
		||||
    public static final ResourceLocation ID = new ResourceLocation(ComputerCraftAPI.MOD_ID, "main");
 | 
			
		||||
 | 
			
		||||
    private static final Int2ObjectMap<Function<FriendlyByteBuf, ? extends NetworkMessage<ClientNetworkContext>>> clientPackets = new Int2ObjectOpenHashMap<>();
 | 
			
		||||
    private static final Int2ObjectMap<Function<FriendlyByteBuf, ? extends NetworkMessage<ServerNetworkContext>>> serverPackets = new Int2ObjectOpenHashMap<>();
 | 
			
		||||
    private static final Object2IntMap<Class<? extends NetworkMessage<?>>> packetIds = new Object2IntOpenHashMap<>();
 | 
			
		||||
 | 
			
		||||
    public static void init() {
 | 
			
		||||
        ServerPlayNetworking.registerGlobalReceiver(ID, (server, player, handler, buf, responseSender) -> {
 | 
			
		||||
            var packet = decodeServer(buf);
 | 
			
		||||
            if (packet != null) server.execute(() -> packet.handle(handler::getPlayer));
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        NetworkMessages.register(new NetworkMessages.PacketRegistry() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public <T extends NetworkMessage<ClientNetworkContext>> void registerClientbound(int id, Class<T> type, Function<FriendlyByteBuf, T> decoder) {
 | 
			
		||||
                clientPackets.put(id, decoder);
 | 
			
		||||
                packetIds.put(type, id);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public <T extends NetworkMessage<ServerNetworkContext>> void registerServerbound(int id, Class<T> type, Function<FriendlyByteBuf, T> decoder) {
 | 
			
		||||
                serverPackets.put(id, decoder);
 | 
			
		||||
                packetIds.put(type, id);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static FriendlyByteBuf encode(NetworkMessage<?> message) {
 | 
			
		||||
        var buf = new FriendlyByteBuf(Unpooled.buffer());
 | 
			
		||||
        buf.writeByte(packetIds.getInt(message.getClass()));
 | 
			
		||||
        message.toBytes(buf);
 | 
			
		||||
        return buf;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static ClientboundCustomPayloadPacket encodeClient(NetworkMessage<ClientNetworkContext> message) {
 | 
			
		||||
        return new ClientboundCustomPayloadPacket(ID, encode(message));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static ServerboundCustomPayloadPacket encodeServer(NetworkMessage<ServerNetworkContext> message) {
 | 
			
		||||
        return new ServerboundCustomPayloadPacket(ID, encode(message));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nullable
 | 
			
		||||
    private static <T> NetworkMessage<T> decode(Int2ObjectMap<Function<FriendlyByteBuf, ? extends NetworkMessage<T>>> packets, FriendlyByteBuf buffer) {
 | 
			
		||||
        int type = buffer.readByte();
 | 
			
		||||
        var reader = packets.get(type);
 | 
			
		||||
        if (reader == null) {
 | 
			
		||||
            LOGGER.debug("Unknown packet {}", type);
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return reader.apply(buffer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public static NetworkMessage<ServerNetworkContext> decodeServer(FriendlyByteBuf buffer) {
 | 
			
		||||
        return decode(serverPackets, buffer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public static NetworkMessage<ClientNetworkContext> decodeClient(FriendlyByteBuf buffer) {
 | 
			
		||||
        return decode(clientPackets, buffer);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -17,6 +17,7 @@ import dan200.computercraft.api.peripheral.PeripheralLookup;
 | 
			
		||||
import dan200.computercraft.impl.Peripherals;
 | 
			
		||||
import dan200.computercraft.mixin.ArgumentTypeInfosAccessor;
 | 
			
		||||
import dan200.computercraft.shared.config.ConfigFile;
 | 
			
		||||
import dan200.computercraft.shared.network.MessageType;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessage;
 | 
			
		||||
import dan200.computercraft.shared.network.client.ClientNetworkContext;
 | 
			
		||||
import dan200.computercraft.shared.network.container.ContainerData;
 | 
			
		||||
@@ -27,6 +28,8 @@ import net.fabricmc.fabric.api.event.player.UseEntityCallback;
 | 
			
		||||
import net.fabricmc.fabric.api.itemgroup.v1.FabricItemGroup;
 | 
			
		||||
import net.fabricmc.fabric.api.lookup.v1.block.BlockApiCache;
 | 
			
		||||
import net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup;
 | 
			
		||||
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
 | 
			
		||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
 | 
			
		||||
import net.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityTypeBuilder;
 | 
			
		||||
import net.fabricmc.fabric.api.registry.FuelRegistry;
 | 
			
		||||
import net.fabricmc.fabric.api.resource.conditions.v1.DefaultResourceConditions;
 | 
			
		||||
@@ -44,6 +47,8 @@ import net.minecraft.core.Registry;
 | 
			
		||||
import net.minecraft.core.registries.BuiltInRegistries;
 | 
			
		||||
import net.minecraft.network.FriendlyByteBuf;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
import net.minecraft.network.protocol.Packet;
 | 
			
		||||
import net.minecraft.network.protocol.game.ClientGamePacketListener;
 | 
			
		||||
import net.minecraft.resources.ResourceKey;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.server.MinecraftServer;
 | 
			
		||||
@@ -170,31 +175,42 @@ public class PlatformHelperImpl implements PlatformHelper {
 | 
			
		||||
        player.openMenu(new WrappedMenuProvider(owner, menu));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public <T extends NetworkMessage<?>> MessageType<T> createMessageType(int id, ResourceLocation channel, Class<T> klass, FriendlyByteBuf.Reader<T> reader) {
 | 
			
		||||
        return new FabricMessageType<>(channel, reader);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Packet<ClientGamePacketListener> encodeClientbound(NetworkMessage<ClientNetworkContext> message) {
 | 
			
		||||
        var buf = PacketByteBufs.create();
 | 
			
		||||
        message.write(buf);
 | 
			
		||||
        return ServerPlayNetworking.createS2CPacket(FabricMessageType.toFabricType(message.type()).getId(), buf);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void sendToPlayer(NetworkMessage<ClientNetworkContext> message, ServerPlayer player) {
 | 
			
		||||
        player.connection.send(NetworkHandler.encodeClient(message));
 | 
			
		||||
        player.connection.send(encodeClientbound(message));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void sendToPlayers(NetworkMessage<ClientNetworkContext> message, Collection<ServerPlayer> players) {
 | 
			
		||||
        if (players.isEmpty()) return;
 | 
			
		||||
        var packet = NetworkHandler.encodeClient(message);
 | 
			
		||||
        var packet = encodeClientbound(message);
 | 
			
		||||
        for (var player : players) player.connection.send(packet);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void sendToAllPlayers(NetworkMessage<ClientNetworkContext> message, MinecraftServer server) {
 | 
			
		||||
        server.getPlayerList().broadcastAll(NetworkHandler.encodeClient(message));
 | 
			
		||||
        server.getPlayerList().broadcastAll(encodeClientbound(message));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void sendToAllAround(NetworkMessage<ClientNetworkContext> message, ServerLevel level, Vec3 pos, float distance) {
 | 
			
		||||
        level.getServer().getPlayerList().broadcast(null, pos.x, pos.y, pos.z, distance, level.dimension(), NetworkHandler.encodeClient(message));
 | 
			
		||||
        level.getServer().getPlayerList().broadcast(null, pos.x, pos.y, pos.z, distance, level.dimension(), encodeClientbound(message));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void sendToAllTracking(NetworkMessage<ClientNetworkContext> message, LevelChunk chunk) {
 | 
			
		||||
        var packet = NetworkHandler.encodeClient(message);
 | 
			
		||||
        var packet = encodeClientbound(message);
 | 
			
		||||
        for (var player : ((ServerChunkCache) chunk.getLevel().getChunkSource()).chunkMap.getPlayers(chunk.getPos(), false)) {
 | 
			
		||||
            player.connection.send(packet);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -131,12 +131,13 @@ dependencies {
 | 
			
		||||
    libs.bundles.externalMods.forge.runtime.get().map { runtimeOnly(fg.deobf(it)) }
 | 
			
		||||
 | 
			
		||||
    // Depend on our other projects.
 | 
			
		||||
    api(commonClasses(project(":forge-api")))
 | 
			
		||||
    api(clientClasses(project(":forge-api")))
 | 
			
		||||
    implementation(project(":core"))
 | 
			
		||||
    api(commonClasses(project(":forge-api"))) { cct.exclude(this) }
 | 
			
		||||
    clientApi(clientClasses(project(":forge-api"))) { cct.exclude(this) }
 | 
			
		||||
    implementation(project(":core")) { cct.exclude(this) }
 | 
			
		||||
 | 
			
		||||
    minecraftEmbed(libs.cobalt) {
 | 
			
		||||
        jarJar.ranged(this, "[${libs.versions.cobalt.asProvider().get()},${libs.versions.cobalt.next.get()})")
 | 
			
		||||
        val version = libs.versions.cobalt.get()
 | 
			
		||||
        jarJar.ranged(this, "[$version,${getNextVersion(version)})")
 | 
			
		||||
    }
 | 
			
		||||
    minecraftEmbed(libs.jzlib) {
 | 
			
		||||
        jarJar.ranged(this, "[${libs.versions.jzlib.get()},)")
 | 
			
		||||
@@ -254,7 +255,7 @@ publishing {
 | 
			
		||||
            artifact(tasks.jarJar)
 | 
			
		||||
 | 
			
		||||
            mavenDependencies {
 | 
			
		||||
                exclude(dependencies.create("cc.tweaked:"))
 | 
			
		||||
                cct.configureExcludes(this)
 | 
			
		||||
                exclude(libs.jei.forge.get())
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "type": "computercraft:computer_upgrade",
 | 
			
		||||
  "category": "redstone",
 | 
			
		||||
  "family": "advanced",
 | 
			
		||||
  "key": {"#": {"tag": "forge:ingots/gold"}, "C": {"item": "computercraft:computer_normal"}},
 | 
			
		||||
  "pattern": ["###", "#C#", "# #"],
 | 
			
		||||
  "result": {"item": "computercraft:computer_advanced"},
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "type": "computercraft:computer_upgrade",
 | 
			
		||||
  "category": "redstone",
 | 
			
		||||
  "family": "advanced",
 | 
			
		||||
  "key": {"#": {"tag": "forge:ingots/gold"}, "C": {"item": "computercraft:pocket_computer_normal"}},
 | 
			
		||||
  "pattern": ["###", "#C#", "# #"],
 | 
			
		||||
  "result": {"item": "computercraft:pocket_computer_advanced"},
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "type": "computercraft:turtle",
 | 
			
		||||
  "category": "redstone",
 | 
			
		||||
  "family": "advanced",
 | 
			
		||||
  "key": {
 | 
			
		||||
    "#": {"tag": "forge:ingots/gold"},
 | 
			
		||||
    "C": {"item": "computercraft:computer_advanced"},
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "type": "computercraft:computer_upgrade",
 | 
			
		||||
  "category": "redstone",
 | 
			
		||||
  "family": "advanced",
 | 
			
		||||
  "key": {
 | 
			
		||||
    "#": {"tag": "forge:ingots/gold"},
 | 
			
		||||
    "B": {"tag": "forge:storage_blocks/gold"},
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "type": "computercraft:turtle",
 | 
			
		||||
  "category": "redstone",
 | 
			
		||||
  "family": "normal",
 | 
			
		||||
  "key": {
 | 
			
		||||
    "#": {"tag": "forge:ingots/iron"},
 | 
			
		||||
    "C": {"item": "computercraft:computer_normal"},
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								projects/forge/src/generated/resources/data/minecraft/tags/blocks/wither_immune.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								projects/forge/src/generated/resources/data/minecraft/tags/blocks/wither_immune.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
{"values": ["computercraft:computer_command"]}
 | 
			
		||||
@@ -12,6 +12,7 @@ import dan200.computercraft.api.network.wired.WiredElement;
 | 
			
		||||
import dan200.computercraft.api.peripheral.IPeripheral;
 | 
			
		||||
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.shared.CommonHooks;
 | 
			
		||||
import dan200.computercraft.shared.ModRegistry;
 | 
			
		||||
import dan200.computercraft.shared.config.ConfigSpec;
 | 
			
		||||
import dan200.computercraft.shared.details.FluidData;
 | 
			
		||||
@@ -23,6 +24,7 @@ import dan200.computercraft.shared.platform.ForgeConfigFile;
 | 
			
		||||
import dan200.computercraft.shared.platform.NetworkHandler;
 | 
			
		||||
import net.minecraftforge.common.capabilities.ForgeCapabilities;
 | 
			
		||||
import net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent;
 | 
			
		||||
import net.minecraftforge.event.BuildCreativeModeTabContentsEvent;
 | 
			
		||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
 | 
			
		||||
import net.minecraftforge.fml.ModList;
 | 
			
		||||
import net.minecraftforge.fml.ModLoadingContext;
 | 
			
		||||
@@ -100,4 +102,9 @@ public final class ComputerCraft {
 | 
			
		||||
            ConfigSpec.syncClient(path);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SubscribeEvent
 | 
			
		||||
    public static void onCreativeTab(BuildCreativeModeTabContentsEvent event) {
 | 
			
		||||
        CommonHooks.onBuildCreativeTab(event.getTabKey(), event.getParameters(), event);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,12 +5,11 @@
 | 
			
		||||
package dan200.computercraft.shared.platform;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.shared.network.MessageType;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessage;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessages;
 | 
			
		||||
import dan200.computercraft.shared.network.client.ClientNetworkContext;
 | 
			
		||||
import dan200.computercraft.shared.network.server.ServerNetworkContext;
 | 
			
		||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
 | 
			
		||||
import it.unimi.dsi.fastutil.ints.IntSet;
 | 
			
		||||
import net.minecraft.network.FriendlyByteBuf;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.server.level.ServerPlayer;
 | 
			
		||||
@@ -47,20 +46,15 @@ public final class NetworkHandler {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void setup() {
 | 
			
		||||
        IntSet usedIds = new IntOpenHashSet();
 | 
			
		||||
        NetworkMessages.register(new NetworkMessages.PacketRegistry() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public <T extends NetworkMessage<ClientNetworkContext>> void registerClientbound(int id, Class<T> type, Function<FriendlyByteBuf, T> decoder) {
 | 
			
		||||
                if (!usedIds.add(id)) throw new IllegalArgumentException("Already have a packet with id " + id);
 | 
			
		||||
                registerMainThread(id, NetworkDirection.PLAY_TO_CLIENT, type, decoder, x -> ClientNetworkContext.get());
 | 
			
		||||
            }
 | 
			
		||||
        for (var type : NetworkMessages.getServerbound()) {
 | 
			
		||||
            var forgeType = (MessageTypeImpl<? extends NetworkMessage<ServerNetworkContext>>) type;
 | 
			
		||||
            registerMainThread(forgeType, NetworkDirection.PLAY_TO_SERVER, c -> () -> assertNonNull(c.getSender()));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public <T extends NetworkMessage<ServerNetworkContext>> void registerServerbound(int id, Class<T> type, Function<FriendlyByteBuf, T> decoder) {
 | 
			
		||||
                if (!usedIds.add(id)) throw new IllegalArgumentException("Already have a packet with id " + id);
 | 
			
		||||
                registerMainThread(id, NetworkDirection.PLAY_TO_SERVER, type, decoder, c -> () -> assertNonNull(c.getSender()));
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        for (var type : NetworkMessages.getClientbound()) {
 | 
			
		||||
            var forgeType = (MessageTypeImpl<? extends NetworkMessage<ClientNetworkContext>>) type;
 | 
			
		||||
            registerMainThread(forgeType, NetworkDirection.PLAY_TO_CLIENT, x -> ClientNetworkContext.get());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static void sendToPlayer(NetworkMessage<ClientNetworkContext> packet, ServerPlayer player) {
 | 
			
		||||
@@ -96,19 +90,16 @@ public final class NetworkHandler {
 | 
			
		||||
     *
 | 
			
		||||
     * @param <T>       The type of the packet to send.
 | 
			
		||||
     * @param <H>       The context this packet is evaluated under.
 | 
			
		||||
     * @param type      The class of the type of packet to send.
 | 
			
		||||
     * @param id        The identifier for this packet type.
 | 
			
		||||
     * @param type      The message type to register.
 | 
			
		||||
     * @param direction A network direction which will be asserted before any processing of this message occurs
 | 
			
		||||
     * @param decoder   The factory for this type of packet.
 | 
			
		||||
     * @param handler   Gets or constructs the handler for this packet.
 | 
			
		||||
     */
 | 
			
		||||
    static <H, T extends NetworkMessage<H>> void registerMainThread(
 | 
			
		||||
        int id, NetworkDirection direction, Class<T> type, Function<FriendlyByteBuf, T> decoder,
 | 
			
		||||
        Function<NetworkEvent.Context, H> handler
 | 
			
		||||
        MessageTypeImpl<T> type, NetworkDirection direction, Function<NetworkEvent.Context, H> handler
 | 
			
		||||
    ) {
 | 
			
		||||
        network.messageBuilder(type, id, direction)
 | 
			
		||||
            .encoder(NetworkMessage::toBytes)
 | 
			
		||||
            .decoder(decoder)
 | 
			
		||||
        network.messageBuilder(type.klass(), type.id(), direction)
 | 
			
		||||
            .encoder(NetworkMessage::write)
 | 
			
		||||
            .decoder(type.reader())
 | 
			
		||||
            .consumerMainThread((packet, contextSup) -> {
 | 
			
		||||
                try {
 | 
			
		||||
                    packet.handle(handler.apply(contextSup.get()));
 | 
			
		||||
@@ -119,4 +110,9 @@ public final class NetworkHandler {
 | 
			
		||||
            })
 | 
			
		||||
            .add();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public record MessageTypeImpl<T extends NetworkMessage<?>>(
 | 
			
		||||
        int id, Class<T> klass, Function<FriendlyByteBuf, T> reader
 | 
			
		||||
    ) implements MessageType<T> {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,7 @@ import dan200.computercraft.api.peripheral.IPeripheral;
 | 
			
		||||
import dan200.computercraft.impl.Peripherals;
 | 
			
		||||
import dan200.computercraft.shared.Capabilities;
 | 
			
		||||
import dan200.computercraft.shared.config.ConfigFile;
 | 
			
		||||
import dan200.computercraft.shared.network.MessageType;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessage;
 | 
			
		||||
import dan200.computercraft.shared.network.client.ClientNetworkContext;
 | 
			
		||||
import dan200.computercraft.shared.network.container.ContainerData;
 | 
			
		||||
@@ -157,6 +158,11 @@ public class PlatformHelperImpl implements PlatformHelper {
 | 
			
		||||
        NetworkHooks.openScreen((ServerPlayer) player, owner, menu::toBytes);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public <T extends NetworkMessage<?>> MessageType<T> createMessageType(int id, ResourceLocation channel, Class<T> klass, FriendlyByteBuf.Reader<T> reader) {
 | 
			
		||||
        return new NetworkHandler.MessageTypeImpl<>(id, klass, reader);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void sendToPlayer(NetworkMessage<ClientNetworkContext> message, ServerPlayer player) {
 | 
			
		||||
        NetworkHandler.sendToPlayer(message, player);
 | 
			
		||||
 
 | 
			
		||||
@@ -6,11 +6,15 @@ package cc.tweaked.standalone;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.core.ComputerContext;
 | 
			
		||||
import dan200.computercraft.core.CoreConfig;
 | 
			
		||||
import dan200.computercraft.core.apis.http.options.Action;
 | 
			
		||||
import dan200.computercraft.core.apis.http.options.AddressRule;
 | 
			
		||||
import dan200.computercraft.core.computer.Computer;
 | 
			
		||||
import dan200.computercraft.core.terminal.Terminal;
 | 
			
		||||
import dan200.computercraft.core.terminal.TextBuffer;
 | 
			
		||||
import dan200.computercraft.core.util.Colour;
 | 
			
		||||
import org.apache.commons.cli.*;
 | 
			
		||||
import org.jetbrains.annotations.Contract;
 | 
			
		||||
import org.lwjgl.glfw.GLFW;
 | 
			
		||||
import org.lwjgl.glfw.GLFWErrorCallback;
 | 
			
		||||
import org.lwjgl.opengl.GL;
 | 
			
		||||
@@ -20,13 +24,16 @@ import org.lwjgl.system.MemoryStack;
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.io.PrintWriter;
 | 
			
		||||
import java.nio.ByteBuffer;
 | 
			
		||||
import java.nio.charset.StandardCharsets;
 | 
			
		||||
import java.nio.file.InvalidPathException;
 | 
			
		||||
import java.nio.file.Path;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
import java.util.OptionalInt;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
import java.util.concurrent.atomic.AtomicBoolean;
 | 
			
		||||
import java.util.regex.Pattern;
 | 
			
		||||
@@ -53,7 +60,7 @@ public class Main {
 | 
			
		||||
        public static final Pattern PATTERN = Pattern.compile("^(\\d+)x(\\d+)$");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static <T> T getParsedOptionValue(CommandLine cli, String opt, Class<T> klass) throws ParseException {
 | 
			
		||||
    private static <T> T getParsedOptionValue(CommandLine cli, Option opt, Class<T> klass) throws ParseException {
 | 
			
		||||
        var res = cli.getOptionValue(opt);
 | 
			
		||||
        if (klass == Path.class) {
 | 
			
		||||
            try {
 | 
			
		||||
@@ -71,30 +78,47 @@ public class Main {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Contract("_, _, _, !null -> !null")
 | 
			
		||||
    private static <T> @Nullable T getParsedOptionValue(CommandLine cli, Option opt, Class<T> klass, @Nullable T defaultValue) throws ParseException {
 | 
			
		||||
        return cli.hasOption(opt) ? getParsedOptionValue(cli, opt, klass) : defaultValue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void main(String[] args) throws InterruptedException {
 | 
			
		||||
        var options = new Options();
 | 
			
		||||
        options.addOption(Option.builder("r").argName("PATH").longOpt("resources").hasArg()
 | 
			
		||||
        Option resourceOpt, computerOpt, termSizeOpt, allowLocalDomainsOpt, helpOpt;
 | 
			
		||||
        options.addOption(resourceOpt = Option.builder("r").argName("PATH").longOpt("resources").hasArg()
 | 
			
		||||
            .desc("The path to the resources directory")
 | 
			
		||||
            .build());
 | 
			
		||||
        options.addOption(Option.builder("c").argName("PATH").longOpt("computer").hasArg()
 | 
			
		||||
        options.addOption(computerOpt = Option.builder("c").argName("PATH").longOpt("computer").hasArg()
 | 
			
		||||
            .desc("The root directory of the computer. Defaults to a temporary directory.")
 | 
			
		||||
            .build());
 | 
			
		||||
        options.addOption(Option.builder("t").argName("WIDTHxHEIGHT").longOpt("term-size").hasArg()
 | 
			
		||||
            .desc("The size of the terminal, defaults to 51x19")
 | 
			
		||||
        options.addOption(termSizeOpt = Option.builder("t").argName("WIDTHxHEIGHT").longOpt("term-size").hasArg()
 | 
			
		||||
            .desc("The size of the terminal, defaults to 51x19.")
 | 
			
		||||
            .build());
 | 
			
		||||
        options.addOption(allowLocalDomainsOpt = Option.builder("L").longOpt("allow-local-domains")
 | 
			
		||||
            .desc("Allow accessing local domains with the HTTP API.")
 | 
			
		||||
            .build());
 | 
			
		||||
 | 
			
		||||
        options.addOption(new Option("h", "help", false, "Print help message"));
 | 
			
		||||
        options.addOption(helpOpt = Option.builder("h").longOpt("help")
 | 
			
		||||
            .desc("Print help message")
 | 
			
		||||
            .build());
 | 
			
		||||
 | 
			
		||||
        Path resourcesDirectory;
 | 
			
		||||
        Path computerDirectory;
 | 
			
		||||
        TermSize termSize;
 | 
			
		||||
        boolean allowLocalDomains;
 | 
			
		||||
        try {
 | 
			
		||||
            var cli = new DefaultParser().parse(options, args);
 | 
			
		||||
            if (!cli.hasOption("r")) throw new ParseException("--resources directory is required");
 | 
			
		||||
            if (cli.hasOption(helpOpt)) {
 | 
			
		||||
                new HelpFormatter().printHelp("standalone.jar", options, true);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            if (!cli.hasOption(resourceOpt)) throw new ParseException("--resources directory is required");
 | 
			
		||||
 | 
			
		||||
            resourcesDirectory = getParsedOptionValue(cli, "r", Path.class);
 | 
			
		||||
            computerDirectory = cli.hasOption("c") ? getParsedOptionValue(cli, "c", Path.class) : null;
 | 
			
		||||
            termSize = cli.hasOption("t") ? getParsedOptionValue(cli, "t", TermSize.class) : TermSize.DEFAULT;
 | 
			
		||||
            resourcesDirectory = getParsedOptionValue(cli, resourceOpt, Path.class);
 | 
			
		||||
            computerDirectory = getParsedOptionValue(cli, computerOpt, Path.class, null);
 | 
			
		||||
            termSize = getParsedOptionValue(cli, termSizeOpt, TermSize.class, TermSize.DEFAULT);
 | 
			
		||||
            allowLocalDomains = cli.hasOption(allowLocalDomainsOpt);
 | 
			
		||||
        } catch (ParseException e) {
 | 
			
		||||
            System.err.println(e.getLocalizedMessage());
 | 
			
		||||
 | 
			
		||||
@@ -106,6 +130,10 @@ public class Main {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (allowLocalDomains) {
 | 
			
		||||
            CoreConfig.httpRules = List.of(AddressRule.parse("*", OptionalInt.empty(), Action.ALLOW.toPartial()));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var context = ComputerContext.builder(new StandaloneGlobalEnvironment(resourcesDirectory)).build();
 | 
			
		||||
        try (var gl = new GLObjects()) {
 | 
			
		||||
            var isDirty = new AtomicBoolean(true);
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user