1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-10-17 15:07:38 +00:00

Compare commits

..

4 Commits

Author SHA1 Message Date
Jonathan Coates
240528cce5 Update NF and NG
Trying to rebuild after this OOM killed all my windows. I hate NeoGradle
so much.
2024-02-18 19:02:23 +00:00
Jonathan Coates
83f1f86888 Merge branch 'mc-1.20.x' into mc-1.20.y 2024-02-18 18:45:20 +00:00
Jonathan Coates
9c202bd1c2 Fix incorrect cast for JEI 2024-02-05 18:52:20 +00:00
Jonathan Coates
fc834cd97f Update to 1.20.4 2024-01-31 20:55:14 +00:00
917 changed files with 7376 additions and 8258 deletions

View File

@@ -9,16 +9,16 @@ jobs:
steps:
- name: 📥 Clone repository
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: 📥 Set up Java
uses: actions/setup-java@v4
uses: actions/setup-java@v3
with:
java-version: 17
distribution: 'temurin'
- name: 📥 Setup Gradle
uses: gradle/actions/setup-gradle@v3
uses: gradle/gradle-build-action@v2
with:
cache-read-only: ${{ !startsWith(github.ref, 'refs/heads/mc-') }}
@@ -58,13 +58,13 @@ jobs:
find projects/forge/build/libs projects/fabric/build/libs -type f -regex '.*[0-9.]+\(-SNAPSHOT\)?\.jar$' -exec bash -c 'cp {} "jars/$(basename {} .jar)-$(git rev-parse HEAD).jar"' \;
- name: 📤 Upload Jar
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: CC-Tweaked
path: ./jars
- name: 📤 Upload coverage
uses: codecov/codecov-action@v4
uses: codecov/codecov-action@v3
build-core:
strategy:
@@ -81,28 +81,24 @@ jobs:
runs-on: ${{ matrix.uses }}
steps:
- name: 📥 Clone repository
uses: actions/checkout@v4
- name: Clone repository
uses: actions/checkout@v3
- name: 📥 Set up Java
uses: actions/setup-java@v4
- name: Set up Java
uses: actions/setup-java@v3
with:
java-version: 17
distribution: 'temurin'
- name: 📥 Setup Gradle
uses: gradle/actions/setup-gradle@v3
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
with:
cache-read-only: ${{ !startsWith(github.ref, 'refs/heads/mc-') }}
- name: ⚒️ Build
run: |
./gradlew --configure-on-demand :core:assemble
- name: 🧪 Run tests
- name: Run tests
run: |
./gradlew --configure-on-demand :core:test
- name: 🧪 Parse test reports
- name: Parse test reports
run: python3 ./tools/parse-reports.py
if: ${{ failure() }}

View File

@@ -3,7 +3,8 @@ name: Build documentation
on:
push:
branches:
- mc-*
- mc-1.19.x
- mc-1.20.x
jobs:
make_doc:
@@ -12,16 +13,16 @@ jobs:
steps:
- name: Clone repository
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Set up Java
uses: actions/setup-java@v4
uses: actions/setup-java@v1
with:
java-version: 17
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
uses: gradle/gradle-build-action@v2
with:
cache-read-only: ${{ !startsWith(github.ref, 'refs/heads/mc-') }}

1
.gitignore vendored
View File

@@ -9,7 +9,6 @@
/projects/*/logs
/projects/fabric/fabricloader.log
/projects/*/build
/projects/*/src/test/generated_tests/
/buildSrc/build
/out
/buildSrc/out

26
.gitpod.yml Normal file
View File

@@ -0,0 +1,26 @@
# SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
#
# SPDX-License-Identifier: MPL-2.0
image:
file: config/gitpod/Dockerfile
ports:
- port: 25565
onOpen: notify
vscode:
extensions:
- eamodio.gitlens
- github.vscode-pull-request-github
- ms-azuretools.vscode-docker
- redhat.java
- richardwillis.vscode-gradle
- vscjava.vscode-java-debug
- vscode.github
tasks:
- name: Setup pre-commit hool
init: pre-commit install --allow-missing-config
- name: Install npm packages
init: npm ci

View File

@@ -8,7 +8,8 @@ Files:
projects/common/src/main/resources/assets/computercraft/sounds/empty.ogg
projects/common/src/testMod/resources/data/cctest/computercraft/turtle_upgrades/*
projects/common/src/testMod/resources/data/cctest/structures/*
projects/*/src/generated/*
projects/fabric/src/generated/*
projects/forge/src/generated/*
projects/web/src/htmlTransform/export/index.json
projects/web/src/htmlTransform/export/items/minecraft/*
Comment: Generated/data files are CC0.

View File

@@ -29,9 +29,9 @@ automatically with GitHub, so please don't submit PRs adding/changing translatio
In order to develop CC: Tweaked, you'll need to download the source code and then run it.
- Make sure you've got the following software installed:
- Java Development Kit (JDK). This can be downloaded from [Adoptium].
- Java Development Kit (JDK) installed. This can be downloaded from [Adoptium].
- [Git](https://git-scm.com/).
- [NodeJS][node].
- If you want to work on documentation, [NodeJS][node].
- Download CC: Tweaked's source code:
```

View File

@@ -51,9 +51,8 @@ dependencies {
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-common-api:$cctVersion")
// Forge Gradle
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-core-api:$cctVersion")
compileOnly(fg.deobf("cc.tweaked:cc-tweaked-$mcVersion-forge-api:$cctVersion"))
runtimeOnly(fg.deobf("cc.tweaked:cc-tweaked-$mcVersion-forge:$cctVersion"))
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-forge-api:$cctVersion")
runtimeOnly("cc.tweaked:cc-tweaked-$mcVersion-forge:$cctVersion")
// Fabric Loom
modCompileOnly("cc.tweaked:cc-tweaked-$mcVersion-fabric-api:$cctVersion")

View File

@@ -5,8 +5,9 @@
import cc.tweaked.gradle.JUnitExt
import net.fabricmc.loom.api.LoomGradleExtensionAPI
import net.fabricmc.loom.util.gradle.SourceSetHelper
import org.jetbrains.gradle.ext.*
import org.jetbrains.gradle.ext.Application
import org.jetbrains.gradle.ext.compiler
import org.jetbrains.gradle.ext.runConfigurations
import org.jetbrains.gradle.ext.settings
plugins {
publishing
@@ -85,19 +86,6 @@ idea.project.settings.runConfigurations {
moduleName = "${idea.project.name}.forge.test"
packageName = ""
}
register<Application>("Standalone") {
moduleName = "${idea.project.name}.standalone.main"
mainClass = "cc.tweaked.standalone.Main"
programParameters = "--resources=projects/core/src/main/resources --term=80x30 --allow-local-domains"
}
}
// Build with the IntelliJ, rather than through Gradle. This may require setting the "Compiler Output" option in
// "Project Structure".
idea.project.settings.delegateActions {
delegateBuildRunToGradle = false
testRunner = ActionDelegationConfig.TestRunner.PLATFORM
}
idea.project.settings.compiler.javac {

View File

@@ -14,18 +14,14 @@ repositories {
mavenCentral()
gradlePluginPortal()
maven("https://maven.minecraftforge.net") {
name = "Forge"
maven("https://maven.neoforged.net/releases") {
name = "NeoForge"
content {
includeGroup("net.minecraftforge")
includeGroup("net.minecraftforge.gradle")
}
}
maven("https://maven.parchmentmc.org") {
name = "Librarian"
content {
includeGroupByRegex("^org\\.parchmentmc.*")
includeGroup("net.neoforged")
includeGroup("net.neoforged.gradle")
includeModule("codechicken", "DiffPatch")
includeModule("net.covers1624", "Quack")
}
}
@@ -51,10 +47,9 @@ dependencies {
implementation(libs.curseForgeGradle)
implementation(libs.fabric.loom)
implementation(libs.forgeGradle)
implementation(libs.ideaExt)
implementation(libs.librarian)
implementation(libs.minotaur)
implementation(libs.neoGradle.userdev)
implementation(libs.vanillaExtract)
}

View File

@@ -10,10 +10,8 @@ import cc.tweaked.gradle.IdeaRunConfigurations
import cc.tweaked.gradle.MinecraftConfigurations
plugins {
id("net.minecraftforge.gradle")
// We must apply java-convention after Forge, as we need the fg extension to be present.
id("cc-tweaked.java-convention")
id("org.parchmentmc.librarian.forgegradle")
id("net.neoforged.gradle.userdev")
}
plugins.apply(CCTweakedPlugin::class.java)
@@ -21,10 +19,20 @@ plugins.apply(CCTweakedPlugin::class.java)
val mcVersion: String by extra
minecraft {
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
mappings("parchment", "${libs.findVersion("parchmentMc").get()}-${libs.findVersion("parchment").get()}-$mcVersion")
modIdentifier("computercraft")
}
accessTransformer(project(":forge").file("src/main/resources/META-INF/accesstransformer.cfg"))
subsystems {
parchment {
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
minecraftVersion = libs.findVersion("parchmentMc").get().toString()
mappingsVersion = libs.findVersion("parchment").get().toString()
}
}
dependencies {
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
implementation("net.neoforged:neoforge:${libs.findVersion("neoForge").get()}")
}
MinecraftConfigurations.setup(project)
@@ -32,13 +40,3 @@ MinecraftConfigurations.setup(project)
extensions.configure(CCTweakedExtension::class.java) {
linters(minecraft = true, loader = "forge")
}
dependencies {
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
"minecraft"("net.minecraftforge:forge:$mcVersion-${libs.findVersion("forge").get()}")
}
tasks.configureEach {
// genIntellijRuns isn't registered until much later, so we need this silly hijinks.
if (name == "genIntellijRuns") doLast { IdeaRunConfigurations(project).patch() }
}

View File

@@ -44,13 +44,6 @@ repositories {
exclusiveContent {
forRepositories(mainMaven)
// Include the ForgeGradle repository if present. This requires that ForgeGradle is already present, which we
// enforce in our Forge overlay.
val fg =
project.extensions.findByType(net.minecraftforge.gradle.userdev.DependencyManagementExtension::class.java)
if (fg != null) forRepositories(fg.repository)
filter {
includeGroup("cc.tweaked")
// Things we mirror
@@ -94,8 +87,9 @@ sourceSets.all {
check("InlineMeSuggester", CheckSeverity.OFF) // Minecraft uses @Deprecated liberally
// Too many false positives right now. Maybe we need an indirection for it later on.
check("ReferenceEquality", CheckSeverity.OFF)
check("EnumOrdinal", CheckSeverity.OFF) // For now. We could replace most of these with EnumMap.
check("UnusedVariable", CheckSeverity.OFF) // Too many false positives with records.
check("OperatorPrecedence", CheckSeverity.OFF) // For now.
check("AlreadyChecked", CheckSeverity.OFF) // Seems to be broken?
check("NonOverridingEquals", CheckSeverity.OFF) // Peripheral.equals makes this hard to avoid
check("FutureReturnValueIgnored", CheckSeverity.OFF) // Too many false positives with Netty
@@ -108,8 +102,6 @@ sourceSets.all {
option("NullAway:CastToNonNullMethod", "dan200.computercraft.core.util.Nullability.assertNonNull")
option("NullAway:CheckOptionalEmptiness")
option("NullAway:AcknowledgeRestrictiveAnnotations")
excludedPaths = ".*/jmh_generated/.*"
}
}
}

View File

@@ -32,7 +32,7 @@ val publishCurseForge by tasks.registering(TaskPublishCurseForge::class) {
apiToken = findProperty("curseForgeApiKey") ?: ""
enabled = apiToken != ""
val mainFile = upload("282001", modPublishing.output)
val mainFile = upload("282001", modPublishing.output.get().archiveFile)
mainFile.changelog =
"Release notes can be found on the [GitHub repository](https://github.com/cc-tweaked/CC-Tweaked/releases/tag/v$mcVersion-$modVersion)."
mainFile.changelogType = "markdown"

View File

@@ -35,6 +35,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import java.io.File
import java.io.IOException
import java.net.URI
import java.net.URL
import java.util.regex.Pattern
abstract class CCTweakedExtension(
@@ -225,12 +226,12 @@ abstract class CCTweakedExtension(
* where possible.
*/
fun downloadFile(label: String, url: String): File {
val uri = URI(url)
val path = File(uri.path)
val url = URL(url)
val path = File(url.path)
project.repositories.ivy {
name = label
setUrl(URI(uri.scheme, uri.userInfo, uri.host, uri.port, path.parent, null, null))
setUrl(URI(url.protocol, url.userInfo, url.host, url.port, path.parent, null, null))
patternLayout {
artifact("[artifact].[ext]")
}

View File

@@ -4,23 +4,59 @@
package cc.tweaked.gradle
import net.minecraftforge.gradle.common.util.RunConfig
import net.minecraftforge.gradle.common.util.runs.setRunConfigInternal
import net.neoforged.gradle.common.runs.run.RunImpl
import net.neoforged.gradle.common.runs.tasks.RunExec
import net.neoforged.gradle.dsl.common.extensions.RunnableSourceSet
import net.neoforged.gradle.dsl.common.runs.run.Run
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.tasks.JavaExec
import org.gradle.api.tasks.SourceSet
import org.gradle.jvm.toolchain.JavaToolchainService
import org.gradle.kotlin.dsl.assign
import org.gradle.kotlin.dsl.create
import org.gradle.kotlin.dsl.findByType
import java.nio.file.Files
/**
* Set [JavaExec] task to run a given [RunConfig].
*
* See also [RunExec].
*/
fun JavaExec.setRunConfig(config: RunConfig) {
dependsOn("prepareRuns")
setRunConfigInternal(project, this, config)
doFirst("Create working directory") { Files.createDirectories(workingDir.toPath()) }
fun JavaExec.setRunConfig(config: Run) {
mainClass.set(config.mainClass)
workingDir = config.workingDirectory.get().asFile
argumentProviders.add { config.programArguments.get() }
jvmArgumentProviders.add { config.jvmArguments.get() }
environment(config.environmentVariables.get())
systemProperties(config.systemProperties.get())
config.modSources.get().forEach { classpath(it.runtimeClasspath) }
classpath(config.classpath)
classpath(config.dependencies.get().configuration)
(config as RunImpl).taskDependencies.forEach { dependsOn(it) }
javaLauncher.set(
project.extensions.getByType(JavaToolchainService::class.java)
.launcherFor(project.extensions.getByType(JavaPluginExtension::class.java).toolchain),
)
doFirst("Create working directory") { Files.createDirectories(workingDir.toPath()) }
}
/**
* Add a new [Run.modSource] with a specific mod id.
*/
fun Run.modSourceAs(sourceSet: SourceSet, mod: String) {
// NeoGradle requires a RunnableSourceSet to be present, so we inject it into other project's source sets.
val runnable = sourceSet.extensions.findByType<RunnableSourceSet>() ?: run {
val extension = sourceSet.extensions.create<RunnableSourceSet>(RunnableSourceSet.NAME, project)
extension.modIdentifier = mod
extension.modIdentifier.finalizeValueOnRead()
extension
}
if (runnable.modIdentifier.get() != mod) throw IllegalArgumentException("Multiple mod identifiers")
modSource(sourceSet)
}

View File

@@ -1,120 +0,0 @@
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package cc.tweaked.gradle
import cc.tweaked.vanillaextract.core.util.MoreFiles
import org.gradle.api.Action
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.file.*
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.*
import javax.inject.Inject
/**
* Merge common files across multiple directories into one destination directory.
*
* This is intended for merging the generated resources from the Forge and Fabric projects. Files common between the two
* are written to the global [output] directory, while distinct files are written to the per-source
* [MergeTrees.Source.output] directory.
*/
abstract class MergeTrees : DefaultTask() {
/**
* A source directory to read from.
*/
interface Source {
/**
* The folder contianing all input files.
*/
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
val input: ConfigurableFileTree
fun input(configure: Action<ConfigurableFileTree>) {
configure.execute(input)
}
/**
* The folder to write files unique to this folder to.
*/
@get:OutputDirectory
val output: DirectoryProperty
}
/**
* The list of sources.
*/
@get:Nested
abstract val sources: ListProperty<Source>
/**
* Add and configure a new source.
*/
fun source(configure: Action<Source>) {
val instance = objectFactory.newInstance(Source::class.java)
configure.execute(instance)
instance.output.disallowChanges()
sources.add(instance)
}
/**
* The directory to write common files to.
*/
@get:OutputDirectory
abstract val output: DirectoryProperty
@get:Inject
protected abstract val objectFactory: ObjectFactory
@get:Inject
protected abstract val fsOperations: FileSystemOperations
@TaskAction
fun run() {
val sources = this.sources.get()
if (sources.isEmpty()) throw GradleException("Cannot have an empty list of sources")
val files = mutableMapOf<String, SharedFile>()
for (source in sources) {
source.input.visit(
object : FileVisitor {
override fun visitDir(dirDetails: FileVisitDetails) = Unit
override fun visitFile(fileDetails: FileVisitDetails) {
val path = fileDetails.file.toRelativeString(source.input.dir)
val hash = MoreFiles.computeSha1(fileDetails.file.toPath())
val existing = files[path]
if (existing == null) {
files[path] = SharedFile(hash, 1)
} else if (existing.hash == hash) {
existing.found++
}
}
},
)
}
val sharedFiles = files.entries.asSequence().filter { (_, v) -> v.found == sources.size }.map { (k, _) -> k }.toList()
// Copy shared files to the common directory
fsOperations.sync {
from(sources[0].input)
into(output)
include(sharedFiles)
}
// And all other files to their per-source directory
for (source in sources) {
fsOperations.sync {
from(source.input)
into(source.output)
exclude(sharedFiles)
}
}
}
class SharedFile(val hash: String, var found: Int)
}

View File

@@ -4,7 +4,6 @@
package cc.tweaked.gradle
import net.minecraftforge.gradle.common.util.RunConfig
import org.gradle.api.GradleException
import org.gradle.api.file.FileSystemOperations
import org.gradle.api.invocation.Gradle
@@ -65,14 +64,6 @@ abstract class ClientJavaExec : JavaExec() {
setTestProperties()
}
/**
* Set this task to run a given [RunConfig].
*/
fun setRunConfig(config: RunConfig) {
(this as JavaExec).setRunConfig(config)
setTestProperties() // setRunConfig may clobber some properties, ensure everything is set.
}
/**
* Copy configuration from a task with the given name.
*/

View File

@@ -46,7 +46,7 @@ abstract class NpmInstall : DefaultTask() {
@TaskAction
fun install() {
project.exec {
commandLine(ProcessHelpers.getExecutable("npm"), "ci")
commandLine("npm", "ci")
workingDir = projectRoot.get().asFile
}
}
@@ -59,6 +59,6 @@ abstract class NpmInstall : DefaultTask() {
abstract class NpxExecToDir : ExecToDir() {
init {
dependsOn(NpmInstall.TASK_NAME)
executable = ProcessHelpers.getExecutable("npx")
executable = "npx"
}
}

View File

@@ -9,7 +9,6 @@ import org.gradle.api.GradleException
import java.io.BufferedReader
import java.io.File
import java.io.InputStreamReader
import java.nio.charset.StandardCharsets
internal object ProcessHelpers {
fun startProcess(vararg command: String): Process {
@@ -35,7 +34,7 @@ internal object ProcessHelpers {
val process = startProcess(*command)
process.outputStream.close()
val out = BufferedReader(InputStreamReader(process.inputStream, StandardCharsets.UTF_8)).use { reader ->
val out = BufferedReader(InputStreamReader(process.inputStream)).use { reader ->
reader.lines().filter { it.isNotEmpty() }.toList()
}
ProcessGroovyMethods.closeStreams(process)
@@ -47,28 +46,6 @@ internal object ProcessHelpers {
val path = System.getenv("PATH") ?: return false
return path.splitToSequence(File.pathSeparator).any { File(it, name).exists() }
}
/**
* Search for an executable on the `PATH` if required.
*
* [Process]/[ProcessBuilder] does not handle all executable file extensions on Windows (such as `.com). When on
* Windows, this function searches `PATH` and `PATHEXT` for an executable matching [name].
*/
fun getExecutable(name: String): String {
if (!System.getProperty("os.name").lowercase().contains("windows")) return name
val path = (System.getenv("PATH") ?: return name).split(File.pathSeparator)
val pathExt = (System.getenv("PATHEXT") ?: return name).split(File.pathSeparator)
for (pathEntry in path) {
for (ext in pathExt) {
val resolved = File(pathEntry, name + ext)
if (resolved.exists()) return resolved.getAbsolutePath()
}
}
return name
}
}
internal fun Process.waitForOrThrow(message: String) {

View File

@@ -1,51 +0,0 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package net.minecraftforge.gradle.common.util.runs
import net.minecraftforge.gradle.common.util.RunConfig
import org.gradle.api.Project
import org.gradle.process.CommandLineArgumentProvider
import org.gradle.process.JavaExecSpec
import java.io.File
import java.util.function.Supplier
import java.util.stream.Collectors
import java.util.stream.Stream
/**
* Set up a [JavaExecSpec] to execute a [RunConfig].
*
* [MinecraftRunTask] sets up all its properties when the task is executed, rather than when configured. As such, it's
* not possible to use [cc.tweaked.gradle.copyToFull] like we do for Fabric. Instead, we set up the task manually.
*
* Unfortunately most of the functionality we need is package-private, and so we have to put our code into the package.
*/
internal fun setRunConfigInternal(project: Project, spec: JavaExecSpec, config: RunConfig) {
spec.workingDir = File(config.workingDirectory)
spec.mainClass.set(config.main)
for (source in config.allSources) spec.classpath(source.runtimeClasspath)
val originalTask = project.tasks.named(config.taskName, MinecraftRunTask::class.java)
// Add argument and JVM argument via providers, to be as lazy as possible with fetching artifacts.
val lazyTokens = RunConfigGenerator.configureTokensLazy(
project, config, RunConfigGenerator.mapModClassesToGradle(project, config),
originalTask.get().minecraftArtifacts,
originalTask.get().runtimeClasspathArtifacts,
)
spec.argumentProviders.add(
CommandLineArgumentProvider {
RunConfigGenerator.getArgsStream(config, lazyTokens, false).toList()
},
)
spec.jvmArgumentProviders.add(
CommandLineArgumentProvider {
(if (config.isClient) config.jvmArgs + originalTask.get().additionalClientArgs.get() else config.jvmArgs).map { config.replace(lazyTokens, it) } +
config.properties.map { (k, v) -> "-D${k}=${config.replace(lazyTokens, v)}" }
},
)
for ((key, value) in config.environment) spec.environment(key, config.replace(lazyTokens, value))
}

View File

@@ -13,12 +13,8 @@ SPDX-License-Identifier: MPL-2.0
<property name="tabWidth" value="4"/>
<property name="charset" value="UTF-8" />
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="module\-info\.java$"/>
</module>
<module name="SuppressionFilter">
<property name="file" value="${config_loc}/suppressions.xml" />
<property name="file" value="${config_loc}/suppressions.xml" />
</module>
<module name="BeforeExecutionExclusionFileFilter">

View File

@@ -21,5 +21,5 @@ SPDX-License-Identifier: MPL-2.0
<suppress checks="PackageName" files=".*[\\/]T[A-Za-z]+.java" />
<!-- Allow underscores in our test classes. -->
<suppress checks="MethodName" files=".*(Contract|Test).java" />
<suppress checks="MethodName" files=".*Contract.java" />
</suppressions>

12
config/gitpod/Dockerfile Normal file
View File

@@ -0,0 +1,12 @@
# SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
#
# SPDX-License-Identifier: MPL-2.0
FROM gitpod/workspace-base
USER gitpod
RUN sudo apt-get -q update \
&& sudo apt-get install -yq openjdk-16-jdk python3-pip npm \
&& sudo pip3 install pre-commit \
&& sudo update-java-alternatives --set java-1.16.0-openjdk-amd64

View File

@@ -19,7 +19,7 @@ In order to give the best results, a GPS constellation needs at least four compu
constellation is redundant, but it does not cause problems.
## Building a GPS constellation
<img alt="An example GPS constellation." src="../images/gps-constellation-example.png" class="big-image" />
<img alt="An example GPS constellation." src="/images/gps-constellation-example.png" class="big-image" />
We are going to build our GPS constellation as shown in the image above. You will need 4 computers and either 4 wireless
modems or 4 ender modems. Try not to mix ender and wireless modems together as you might get some odd behavior when your

View File

@@ -131,7 +131,7 @@ different.
First, we require the dfpwm module and call [`cc.audio.dfpwm.make_decoder`] to construct a new decoder. This decoder
accepts blocks of DFPWM data and converts it to a list of 8-bit amplitudes, which we can then play with our speaker.
As mentioned above, [`speaker.playAudio`] accepts at most 128×1024 samples in one go. DFPWM uses a single bit for each
As mentioned above, [`speaker.playAudio`] accepts at most 128×1024 samples in one go. DFPMW uses a single bit for each
sample, which means we want to process our audio in chunks of 16×1024 bytes (16KiB). In order to do this, we use
[`io.lines`], which provides a nice way to loop over chunks of a file. You can of course just use [`fs.open`] and
[`fs.ReadHandle.read`] if you prefer.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 304 KiB

View File

@@ -1,140 +0,0 @@
---
module: [kind=reference] computercraft_command
---
<!--
SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
SPDX-License-Identifier: MPL-2.0
-->
# The `/computercraft` command
CC: Tweaked provides a `/computercraft` command for server owners to manage running computers on a server.
## Permissions {#permissions}
As the `/computercraft` command is mostly intended for debugging and administrative purposes, its sub-commands typically
require you to have op (or similar).
- All players have access to the [`queue`] sub-command.
- On a multi-player server, all other commands require op.
- On a single-player world, the player can run the [`dump`], [`turn-on`]/[`shutdown`], and [`track`] sub-commands, even
when cheats are not enabled. The [`tp`] and [`view`] commands require cheats.
If a permission mod such as [LuckPerms] is installed[^permission], you can configure access to the individual
sub-commands. Each sub-command creates a `computercraft.command.NAME` permission node to control which players can
execute it.
[LuckPerms]: https://github.com/LuckPerms/LuckPerms/ "A permissions plugin for Minecraft servers."
[fabric-permission-api]: https://github.com/lucko/fabric-permissions-api "A simple permissions API for Fabric"
[^permission]: This supports any mod which uses Forge's permission API or [fabric-permission-api].
## Computer selectors {#computer-selectors}
Some commands (such as [`tp`] or [`turn-on`]) target a specific computer, or a list of computers. To specify which
computers to operate on, you must use "computer selectors".
Computer selectors are similar to Minecraft's [entity target selectors], but targeting computers instead. They allow
you to select one or more computers, based on a set of predicates.
The following predicates are supported:
- `id=<id>`: Select computer(s) with a specific id.
- `instance=<id>`: Select the computer with the given instance id.
- `family=<normal|advanced|command>`: Select computers based on their type.
- `label=<label>`: Select computers with the given label.
- `distance=<distance>`: Select computers within a specific distance of the player executing the command. This uses
Minecraft's [float range] syntax.
`#<id>` may also be used as a shorthand for `@c[id=<id>]`, to select computer(s) with a specific id.
### Examples:
- `/computercraft turn-on #12`: Turn on the computer(s) with an id of 12.
- `/computercraft shutdown @c[distance=..100]`: Shut down all computers with 100 blocks of the player.
[entity target selectors]: https://minecraft.wiki/w/Target_selectors "Target Selectors on the Minecraft wiki"
[Float range]: https://minecraft.wiki/w/Argument_types#minecraft:float_range
## Commands {#commands}
### `/computercraft dump` {#dump}
`/computercraft dump` prints a table of currently loaded computers, including their id, position, and whether they're
running. It can also be run with a single computer argument to dump more detailed information about a computer.
![A screenshot of a Minecraft world. In the chat box, there is a table listing 5 computers, with columns labelled
"Computer", "On" and "Position". Below that, is a more detailed list of information about Computer 0, including its
label ("My computer") and that it has a monitor on the right hand side](../images/computercraft-dump.png "An example of
running '/computercraft dump'")
Next to the computer id, there are several buttons to either [teleport][`tp`] to the computer, or [open its terminal
][`view`].
Computers are sorted by distance to the player, so nearby computers will appear earlier.
### `/computercraft turn-on [computers...]` {#turn-on}
Turn on one or more computers or, if no run with no arguments, all loaded computers.
#### Examples
- `/computercraft turn-on #0 #2`: Turn on computers with id 0 and 2.
- `/computercraft turn-on @c[family=command]`: Turn on all command computers.
### `/computercraft shutdown [computers...]` {#shutdown}
Shutdown one or more computers or, if no run with no arguments, all loaded computers.
This is sometimes useful when dealing with lag, as a way to ensure that ComputerCraft is not causing problems.
#### Examples
- `/computercraft shutdown`: Shut down all loaded computers.
- `/computercraft shutdown @c[distance=..10]`: Shut down all computers in a block radius.
### `/computercraft tp [computer]` {#tp}
Teleport to the given computer.
This is normally used from via the [`dump`] command interface rather than being invoked directly.
### `/computercraft view [computer]` {#view}
Open a terminal for the specified computer. This allows remotely viewing computers without having to interact with the
block.
This is normally used from via the [`dump`] command interface rather than being invoked directly.
### `/computercraft track` {#track}
The `/computercraft track` command allows you to enable profiling of computers. When a computer runs code, or interacts
with the Minecraft world, we time how long that takes. This timing information may then be queried, and used to find
computers which may be causing lag.
To enable the profiler, run `/computercraft track start`. Computers will then start recording metrics. Once enough data
has been gathered, run `/computercraft track stop` to stop profiling and display the recorded data.
![](../images/computercraft-track.png)
The table by default shows the number of times each computer has run, and how long it ran for (in total, and on
average). In the above screenshot, we can see one computer was particularly badly behaved, and ran for 7 seconds. The
buttons may be used to [teleport][`tp`] to the computer, or [open its terminal ][`view`], and inspect it further.
`/computercraft track dump` can be used to display this table at any point (including while profiling is still running).
Computers also record other information, such as how much server-thread time they consume, or their HTTP bandwidth
usage. The `dump` subcommand accepts a list of other fields to display, instead of the default timings.
#### Examples
- `/computercraft track dump server_tasks_count server_tasks`: Print the number of server-thread tasks each computer
executed, and how long they took in total.
- `/computercraft track dump http_upload http_download`: Print the number of bytes uploaded and downloaded by each
computer.
### `/computercraft queue` {#queue}
The queue subcommand allows non-operator players to queue a `computer_command` event on *command* computers.
This has a similar purpose to vanilla's [`/trigger`] command. Command computers may choose to listen to this event, and
then perform some action.
[`/trigger`]: https://minecraft.wiki/w/Commands/trigger "/trigger on the Minecraft wiki"
[`dump`]: #dump "/computercraft dump"
[`queue`]: #queue "/computercraft queue"
[`shutdown`]: #shutdown "/computercraft shutdown"
[`tp`]: #tp "/computercraft tp"
[`track`]: #track "/computercraft track"
[`turn-on`]: #turn-on "/computercraft turn-on"
[`view`]: #view "/computercraft view"
[computer selectors]: #computer-selectors "Computer selectors"

View File

@@ -2,15 +2,15 @@
#
# SPDX-License-Identifier: MPL-2.0
org.gradle.jvmargs=-Xmx3G -Dfile.encoding=UTF-8
org.gradle.jvmargs=-Xmx3G
org.gradle.parallel=true
kotlin.stdlib.default.dependency=false
kotlin.jvm.target.validation.mode=error
# Mod properties
isUnstable=false
modVersion=1.111.0
isUnstable=true
modVersion=1.109.6
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
mcVersion=1.20.1
mcVersion=1.20.4

View File

@@ -7,26 +7,26 @@
# Minecraft
# MC version is specified in gradle.properties, as we need that in settings.gradle.
# Remember to update corresponding versions in fabric.mod.json/mods.toml
fabric-api = "0.86.1+1.20.1"
fabric-loader = "0.14.21"
forge = "47.1.0"
forgeSpi = "7.0.1"
fabric-api = "0.93.1+1.20.4"
fabric-loader = "0.15.3"
neoForge = "20.4.161-beta"
neoForgeSpi = "8.0.1"
mixin = "0.8.5"
parchment = "2023.08.20"
parchmentMc = "1.20.1"
yarn = "1.20.1+build.10"
parchment = "2023.12.31"
parchmentMc = "1.20.3"
yarn = "1.20.4+build.3"
# Core dependencies (these versions are tied to the version Minecraft uses)
fastutil = "8.5.9"
guava = "31.1-jre"
netty = "4.1.82.Final"
slf4j = "2.0.1"
fastutil = "8.5.12"
guava = "32.1.2-jre"
netty = "4.1.97.Final"
slf4j = "2.0.7"
# Core dependencies (independent of Minecraft)
asm = "9.6"
autoService = "1.1.1"
checkerFramework = "3.42.0"
cobalt = "0.9.3"
cobalt = "0.9.1"
commonsCli = "1.6.0"
jetbrainsAnnotations = "24.1.0"
jsr305 = "3.0.2"
@@ -36,14 +36,14 @@ kotlin-coroutines = "1.7.3"
nightConfig = "3.6.7"
# Minecraft mods
emi = "1.0.8+1.20.1"
emi = "1.0.30+1.20.4"
fabricPermissions = "0.3.20230723"
iris = "1.6.4+1.20"
jei = "15.2.0.22"
modmenu = "7.1.0"
iris = "1.6.14+1.20.4"
jei = "17.3.0.48"
modmenu = "9.0.0"
moreRed = "4.0.0.4"
oculus = "1.2.5"
rei = "12.0.626"
rei = "14.0.688"
rubidium = "0.6.1"
sodium = "mc1.20-0.4.10"
@@ -51,28 +51,26 @@ sodium = "mc1.20-0.4.10"
hamcrest = "2.2"
jqwik = "1.8.2"
junit = "5.10.1"
jmh = "1.37"
# Build tools
cctJavadoc = "1.8.2"
checkstyle = "10.14.1"
curseForgeGradle = "1.0.14"
errorProne-core = "2.27.0"
checkstyle = "10.12.6"
curseForgeGradle = "1.1.18"
errorProne-core = "2.23.0"
errorProne-plugin = "3.1.0"
fabric-loom = "1.6.7"
forgeGradle = "6.0.21"
fabric-loom = "1.5.7"
githubRelease = "2.5.2"
gradleVersions = "0.50.0"
ideaExt = "1.1.7"
illuaminate = "0.1.0-71-g378d86e"
librarian = "1.+"
illuaminate = "0.1.0-44-g9ee0055"
lwjgl = "3.3.3"
minotaur = "2.+"
nullAway = "0.10.25"
minotaur = "2.8.7"
neoGradle = "7.0.93"
nullAway = "0.9.9"
spotless = "6.23.3"
taskTree = "2.1.1"
teavm = "0.10.0-SQUID.4"
vanillaExtract = "0.1.3"
teavm = "0.10.0-SQUID.2"
vanillaExtract = "0.1.1"
versionCatalogUpdate = "0.8.1"
[libraries]
@@ -84,7 +82,7 @@ checkerFramework = { module = "org.checkerframework:checker-qual", version.ref =
cobalt = { module = "cc.tweaked:cobalt", version.ref = "cobalt" }
commonsCli = { module = "commons-cli:commons-cli", version.ref = "commonsCli" }
fastutil = { module = "it.unimi.dsi:fastutil", version.ref = "fastutil" }
forgeSpi = { module = "net.minecraftforge:forgespi", version.ref = "forgeSpi" }
neoForgeSpi = { module = "net.neoforged:neoforgespi", version.ref = "neoForgeSpi" }
guava = { module = "com.google.guava:guava", version.ref = "guava" }
jetbrainsAnnotations = { module = "org.jetbrains:annotations", version.ref = "jetbrainsAnnotations" }
jsr305 = { module = "com.google.code.findbugs:jsr305", version.ref = "jsr305" }
@@ -106,9 +104,9 @@ fabric-junit = { module = "net.fabricmc:fabric-loader-junit", version.ref = "fab
fabricPermissions = { module = "me.lucko:fabric-permissions-api", version.ref = "fabricPermissions" }
emi = { module = "dev.emi:emi-xplat-mojmap", version.ref = "emi" }
iris = { module = "maven.modrinth:iris", version.ref = "iris" }
jei-api = { module = "mezz.jei:jei-1.20.1-common-api", version.ref = "jei" }
jei-fabric = { module = "mezz.jei:jei-1.20.1-fabric", version.ref = "jei" }
jei-forge = { module = "mezz.jei:jei-1.20.1-forge", version.ref = "jei" }
jei-api = { module = "mezz.jei:jei-1.20.4-common-api", version.ref = "jei" }
jei-fabric = { module = "mezz.jei:jei-1.20.4-fabric", version.ref = "jei" }
jei-forge = { module = "mezz.jei:jei-1.20.4-forge", version.ref = "jei" }
mixin = { module = "org.spongepowered:mixin", version.ref = "mixin" }
modmenu = { module = "com.terraformersmc:modmenu", version.ref = "modmenu" }
moreRed = { module = "commoble.morered:morered-1.20.1", version.ref = "moreRed" }
@@ -127,8 +125,6 @@ junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.re
junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" }
junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit" }
slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" }
jmh = { module = "org.openjdk.jmh:jmh-core", version.ref = "jmh" }
jmh-processor = { module = "org.openjdk.jmh:jmh-generator-annprocess", version.ref = "jmh" }
# LWJGL
lwjgl-bom = { module = "org.lwjgl:lwjgl-bom", version.ref = "lwjgl" }
@@ -146,15 +142,13 @@ errorProne-core = { module = "com.google.errorprone:error_prone_core", version.r
errorProne-plugin = { module = "net.ltgt.gradle:gradle-errorprone-plugin", version.ref = "errorProne-plugin" }
errorProne-testHelpers = { module = "com.google.errorprone:error_prone_test_helpers", version.ref = "errorProne-core" }
fabric-loom = { module = "net.fabricmc:fabric-loom", version.ref = "fabric-loom" }
forgeGradle = { module = "net.minecraftforge.gradle:ForgeGradle", version.ref = "forgeGradle" }
ideaExt = { module = "gradle.plugin.org.jetbrains.gradle.plugin.idea-ext:gradle-idea-ext", version.ref = "ideaExt" }
kotlin-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
librarian = { module = "org.parchmentmc:librarian", version.ref = "librarian" }
minotaur = { module = "com.modrinth.minotaur:Minotaur", version.ref = "minotaur" }
neoGradle-userdev = { module = "net.neoforged.gradle:userdev", version.ref = "neoGradle" }
nullAway = { module = "com.uber.nullaway:nullaway", version.ref = "nullAway" }
spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" }
teavm-classlib = { module = "org.teavm:teavm-classlib", version.ref = "teavm" }
teavm-core = { module = "org.teavm:teavm-core", version.ref = "teavm" }
teavm-jso = { module = "org.teavm:teavm-jso", version.ref = "teavm" }
teavm-jso-apis = { module = "org.teavm:teavm-jso-apis", version.ref = "teavm" }
teavm-jso-impl = { module = "org.teavm:teavm-jso-impl", version.ref = "teavm" }
@@ -166,11 +160,9 @@ vanillaExtract = { module = "cc.tweaked.vanilla-extract:plugin", version.ref = "
yarn = { module = "net.fabricmc:yarn", version.ref = "yarn" }
[plugins]
forgeGradle = { id = "net.minecraftforge.gradle", version.ref = "forgeGradle" }
githubRelease = { id = "com.github.breadmoirai.github-release", version.ref = "githubRelease" }
gradleVersions = { id = "com.github.ben-manes.versions", version.ref = "gradleVersions" }
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
librarian = { id = "org.parchmentmc.librarian.forgegradle", version.ref = "librarian" }
taskTree = { id = "com.dorongold.task-tree", version.ref = "taskTree" }
versionCatalogUpdate = { id = "nl.littlerobots.version-catalog-update", version.ref = "versionCatalogUpdate" }
@@ -181,7 +173,7 @@ kotlin = ["kotlin-stdlib", "kotlin-coroutines"]
# Minecraft
externalMods-common = ["jei-api", "nightConfig-core", "nightConfig-toml"]
externalMods-forge-compile = ["moreRed", "oculus", "jei-api"]
externalMods-forge-runtime = ["jei-forge"]
externalMods-forge-runtime = []
externalMods-fabric-compile = ["fabricPermissions", "iris", "jei-api", "rei-api", "rei-builtin"]
externalMods-fabric-runtime = ["jei-fabric", "modmenu"]

Binary file not shown.

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

20
gradlew.bat vendored
View File

@@ -43,11 +43,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
@@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail

View File

@@ -1,43 +0,0 @@
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api.client;
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.impl.client.ComputerCraftAPIClientService;
/**
* The public API for client-only code.
*
* @see dan200.computercraft.api.ComputerCraftAPI The main API
*/
public final class ComputerCraftAPIClient {
private ComputerCraftAPIClient() {
}
/**
* Register a {@link TurtleUpgradeModeller} for a class of turtle upgrades.
* <p>
* This may be called at any point after registry creation, though it is recommended to call it within your client
* setup step.
*
* @param serialiser The turtle upgrade serialiser.
* @param modeller The upgrade modeller.
* @param <T> The type of the turtle upgrade.
* @deprecated This method can lead to confusing load behaviour on Forge. Use
* {@code dan200.computercraft.api.client.FabricComputerCraftAPIClient#registerTurtleUpgradeModeller} on Fabric, or
* {@code dan200.computercraft.api.client.turtle.RegisterTurtleModellersEvent} on Forge.
*/
@Deprecated(forRemoval = true)
public static <T extends ITurtleUpgrade> void registerTurtleUpgradeModeller(TurtleUpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller) {
// TODO(1.20.4): Remove this
getInstance().registerTurtleUpgradeModeller(serialiser, modeller);
}
private static ComputerCraftAPIClientService getInstance() {
return ComputerCraftAPIClientService.get();
}
}

View File

@@ -5,7 +5,7 @@
package dan200.computercraft.api.client.turtle;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
/**
* A functional interface to register a {@link TurtleUpgradeModeller} for a class of turtle upgrades.
@@ -22,5 +22,5 @@ public interface RegisterTurtleUpgradeModeller {
* @param modeller The upgrade modeller.
* @param <T> The type of the turtle upgrade.
*/
<T extends ITurtleUpgrade> void register(TurtleUpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller);
<T extends ITurtleUpgrade> void register(UpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller);
}

View File

@@ -8,7 +8,6 @@ import dan200.computercraft.api.client.TransformedModel;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
@@ -31,30 +30,15 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
/**
* Obtain the model to be used when rendering a turtle peripheral.
* <p>
* When the current turtle is {@literal null}, this function should be constant for a given upgrade and side.
* When the current turtle is {@literal null}, this function should be constant for a given upgrade, side and data.
*
* @param upgrade The upgrade that you're getting the model for.
* @param turtle Access to the turtle that the upgrade resides on. This will be null when getting item models, unless
* {@link #getModel(ITurtleUpgrade, CompoundTag, TurtleSide)} is overriden.
* @param turtle Access to the turtle that the upgrade resides on. This will be null when getting item models.
* @param side Which side of the turtle (left or right) the upgrade resides on.
* @return The model that you wish to be used to render your upgrade.
*/
TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side);
/**
* Obtain the model to be used when rendering a turtle peripheral.
* <p>
* This is used when rendering the turtle's item model, and so no {@link ITurtleAccess} is available.
*
* @param upgrade The upgrade that you're getting the model for.
* @param data Upgrade data instance for current turtle side.
* @param side Which side of the turtle (left or right) the upgrade resides on.
* @return The model that you wish to be used to render your upgrade.
*/
default TransformedModel getModel(T upgrade, CompoundTag data, TurtleSide side) {
return getModel(upgrade, (ITurtleAccess) null, side);
}
TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, CompoundTag data);
/**
* Get a list of models that this turtle modeller depends on.
@@ -85,19 +69,6 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
return (TurtleUpgradeModeller<T>) TurtleUpgradeModellers.UPGRADE_ITEM;
}
/**
* Construct a {@link TurtleUpgradeModeller} which has a single model for the left and right side.
*
* @param left The model to use on the left.
* @param right The model to use on the right.
* @param <T> The type of the turtle upgrade.
* @return The constructed modeller.
*/
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ModelResourceLocation left, ModelResourceLocation right) {
// TODO(1.21.0): Remove this.
return sided((ResourceLocation) left, right);
}
/**
* Construct a {@link TurtleUpgradeModeller} which has a single model for the left and right side.
*
@@ -109,7 +80,7 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ResourceLocation left, ResourceLocation right) {
return new TurtleUpgradeModeller<>() {
@Override
public TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side) {
public TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, CompoundTag data) {
return TransformedModel.of(side == TurtleSide.LEFT ? left : right);
}

View File

@@ -12,7 +12,6 @@ import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.impl.client.ClientPlatformHelper;
import net.minecraft.client.Minecraft;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.item.ItemStack;
import org.joml.Matrix4f;
import javax.annotation.Nullable;
@@ -37,16 +36,8 @@ final class TurtleUpgradeModellers {
private static final class UpgradeItemModeller implements TurtleUpgradeModeller<ITurtleUpgrade> {
@Override
public TransformedModel getModel(ITurtleUpgrade upgrade, @Nullable ITurtleAccess turtle, TurtleSide side) {
return getModel(turtle == null ? upgrade.getCraftingItem() : upgrade.getUpgradeItem(turtle.getUpgradeNBTData(side)), side);
}
@Override
public TransformedModel getModel(ITurtleUpgrade upgrade, CompoundTag data, TurtleSide side) {
return getModel(upgrade.getUpgradeItem(data), side);
}
private TransformedModel getModel(ItemStack stack, TurtleSide side) {
public TransformedModel getModel(ITurtleUpgrade upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, CompoundTag data) {
var stack = upgrade.getUpgradeItem(data);
var model = Minecraft.getInstance().getItemRenderer().getItemModelShaper().getItemModel(stack);
if (stack.hasFoil()) model = ClientPlatformHelper.get().createdFoiledModel(model);
return new TransformedModel(model, side == TurtleSide.LEFT ? leftTransform : rightTransform);

View File

@@ -5,7 +5,6 @@
package dan200.computercraft.api.network.wired;
import dan200.computercraft.api.peripheral.IPeripheral;
import org.jetbrains.annotations.ApiStatus;
import java.util.Map;
@@ -23,7 +22,6 @@ import java.util.Map;
*
* @see WiredNode#getNetwork()
*/
@ApiStatus.NonExtendable
public interface WiredNetwork {
/**
* Create a connection between two nodes.
@@ -37,9 +35,7 @@ public interface WiredNetwork {
* @throws IllegalArgumentException If {@code left} and {@code right} are equal.
* @see WiredNode#connectTo(WiredNode)
* @see WiredNetwork#connect(WiredNode, WiredNode)
* @deprecated Use {@link WiredNode#connectTo(WiredNode)}
*/
@Deprecated
boolean connect(WiredNode left, WiredNode right);
/**
@@ -54,9 +50,7 @@ public interface WiredNetwork {
* @throws IllegalArgumentException If {@code left} and {@code right} are equal.
* @see WiredNode#disconnectFrom(WiredNode)
* @see WiredNetwork#connect(WiredNode, WiredNode)
* @deprecated Use {@link WiredNode#disconnectFrom(WiredNode)}
*/
@Deprecated
boolean disconnect(WiredNode left, WiredNode right);
/**
@@ -70,9 +64,7 @@ public interface WiredNetwork {
* only element.
* @throws IllegalArgumentException If the node is not in the network.
* @see WiredNode#remove()
* @deprecated Use {@link WiredNode#remove()}
*/
@Deprecated
boolean remove(WiredNode node);
/**
@@ -85,8 +77,6 @@ public interface WiredNetwork {
* @param peripherals The new peripherals for this node.
* @throws IllegalArgumentException If the node is not in the network.
* @see WiredNode#updatePeripherals(Map)
* @deprecated Use {@link WiredNode#updatePeripherals(Map)}
*/
@Deprecated
void updatePeripherals(WiredNode node, Map<String, IPeripheral> peripherals);
}

View File

@@ -6,7 +6,6 @@ package dan200.computercraft.api.network.wired;
import dan200.computercraft.api.network.PacketNetwork;
import dan200.computercraft.api.peripheral.IPeripheral;
import org.jetbrains.annotations.ApiStatus;
import java.util.Map;
@@ -23,7 +22,6 @@ import java.util.Map;
* Wired nodes also provide several convenience methods for interacting with a wired network. These should only ever
* be used on the main server thread.
*/
@ApiStatus.NonExtendable
public interface WiredNode extends PacketNetwork {
/**
* The associated element for this network node.
@@ -39,9 +37,7 @@ public interface WiredNode extends PacketNetwork {
* This should only be used on the server thread.
*
* @return This node's network.
* @deprecated Use the connect/disconnect/remove methods on {@link WiredNode}.
*/
@Deprecated
WiredNetwork getNetwork();
/**
@@ -51,9 +47,12 @@ public interface WiredNode extends PacketNetwork {
*
* @param node The other node to connect to.
* @return {@code true} if a connection was created or {@code false} if the connection already exists.
* @see WiredNetwork#connect(WiredNode, WiredNode)
* @see WiredNode#disconnectFrom(WiredNode)
*/
boolean connectTo(WiredNode node);
default boolean connectTo(WiredNode node) {
return getNetwork().connect(this, node);
}
/**
* Destroy a connection between this node and another.
@@ -62,9 +61,13 @@ public interface WiredNode extends PacketNetwork {
*
* @param node The other node to disconnect from.
* @return {@code true} if a connection was destroyed or {@code false} if no connection exists.
* @throws IllegalArgumentException If {@code node} is not on the same network.
* @see WiredNetwork#disconnect(WiredNode, WiredNode)
* @see WiredNode#connectTo(WiredNode)
*/
boolean disconnectFrom(WiredNode node);
default boolean disconnectFrom(WiredNode node) {
return getNetwork().disconnect(this, node);
}
/**
* Sever all connections this node has, removing it from this network.
@@ -75,8 +78,11 @@ public interface WiredNode extends PacketNetwork {
* @return Whether this node was removed from the network. One cannot remove a node from a network where it is the
* only element.
* @throws IllegalArgumentException If the node is not in the network.
* @see WiredNetwork#remove(WiredNode)
*/
boolean remove();
default boolean remove() {
return getNetwork().remove(this);
}
/**
* Mark this node's peripherals as having changed.
@@ -85,6 +91,9 @@ public interface WiredNode extends PacketNetwork {
* that your network element owns.
*
* @param peripherals The new peripherals for this node.
* @see WiredNetwork#updatePeripherals(WiredNode, Map)
*/
void updatePeripherals(Map<String, IPeripheral> peripherals);
default void updatePeripherals(Map<String, IPeripheral> peripherals) {
getNetwork().updatePeripherals(this, peripherals);
}
}

View File

@@ -4,15 +4,12 @@
package dan200.computercraft.api.pocket;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.upgrades.UpgradeBase;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.ItemStack;
import javax.annotation.Nullable;
import java.util.Map;
/**
* Wrapper class for pocket computers.
@@ -90,13 +87,4 @@ public interface IPocketAccess {
* entity} changes.
*/
void invalidatePeripheral();
/**
* Get a list of all upgrades for the pocket computer.
*
* @return A collection of all upgrade names.
* @deprecated This is a relic of a previous API, which no longer makes sense with newer versions of ComputerCraft.
*/
@Deprecated(forRemoval = true)
Map<ResourceLocation, IPeripheral> getUpgrades();
}

View File

@@ -6,6 +6,10 @@ package dan200.computercraft.api.pocket;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import dan200.computercraft.impl.ComputerCraftAPIService;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.Level;
import javax.annotation.Nullable;
@@ -13,16 +17,46 @@ import javax.annotation.Nullable;
/**
* A peripheral which can be equipped to the back side of a pocket computer.
* <p>
* Pocket upgrades are defined in two stages. First, on creates a {@link IPocketUpgrade} subclass and corresponding
* {@link PocketUpgradeSerialiser} instance, which are then registered in a Forge registry.
* Pocket upgrades are defined in two stages. First, one creates a {@link IPocketUpgrade} subclass and corresponding
* {@link UpgradeSerialiser} instance, which are then registered in a registry.
* <p>
* You then write a JSON file in your mod's {@literal data/} folder. This is then parsed when the world is loaded, and
* the upgrade registered internally. See the documentation in {@link PocketUpgradeSerialiser} for details on this process
* and where files should be located.
* the upgrade automatically registered. It is recommended this is done via {@linkplain PocketUpgradeDataProvider data
* generators}.
*
* @see PocketUpgradeSerialiser For how to register a pocket computer upgrade.
* <h2>Example</h2>
* <pre>{@code
* // We use Forge's DeferredRegister to register our serialiser. Fabric mods may register their serialiser directly.
* static final DeferredRegister<UpgradeSerialiser<? extends IPocketUpgrade>> SERIALISERS = DeferredRegister.create(IPocketUpgrade.serialiserRegistryKey(), "my_mod");
*
* // Register a new upgrade serialiser called "my_upgrade".
* public static final RegistryObject<UpgradeSerialiser<MyUpgrade>> MY_UPGRADE =
* SERIALISERS.register("my_upgrade", () -> UpgradeSerialiser.simple(MyUpgrade::new));
*
* // Then in your constructor
* SERIALISERS.register(bus);
* }</pre>
* <p>
* We can then define a new upgrade using JSON by placing the following in
* {@code data/<my_mod>/computercraft/pocket_upgrades/<my_upgrade_id>.json}.
* <pre>{@code
* {
* "type": my_mod:my_upgrade",
* }
* }</pre>
* <p>
* {@link PocketUpgradeDataProvider} provides a data provider to aid with generating these JSON files.
*/
public interface IPocketUpgrade extends UpgradeBase {
/**
* The registry key for upgrade serialisers.
*
* @return The registry key.
*/
static ResourceKey<Registry<UpgradeSerialiser<? extends IPocketUpgrade>>> serialiserRegistryKey() {
return ComputerCraftAPIService.get().pocketUpgradeRegistryId();
}
/**
* Creates a peripheral for the pocket computer.
* <p>

View File

@@ -5,6 +5,7 @@
package dan200.computercraft.api.pocket;
import dan200.computercraft.api.upgrades.UpgradeDataProvider;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import net.minecraft.data.DataGenerator;
import net.minecraft.data.PackOutput;
@@ -17,10 +18,11 @@ import java.util.function.Consumer;
* {@link #addUpgrades(Consumer)} function, construct each upgrade, and pass them off to the provided consumer to
* generate them.
*
* @see PocketUpgradeSerialiser
* @see IPocketUpgrade
* @see UpgradeSerialiser
*/
public abstract class PocketUpgradeDataProvider extends UpgradeDataProvider<IPocketUpgrade, PocketUpgradeSerialiser<?>> {
public abstract class PocketUpgradeDataProvider extends UpgradeDataProvider<IPocketUpgrade> {
public PocketUpgradeDataProvider(PackOutput output) {
super(output, "Pocket Computer Upgrades", "computercraft/pocket_upgrades", PocketUpgradeSerialiser.registryId());
super(output, "Pocket Computer Upgrades", "computercraft/pocket_upgrades", IPocketUpgrade.serialiserRegistryKey());
}
}

View File

@@ -1,79 +0,0 @@
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api.pocket;
import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import dan200.computercraft.impl.ComputerCraftAPIService;
import dan200.computercraft.impl.upgrades.SerialiserWithCraftingItem;
import dan200.computercraft.impl.upgrades.SimpleSerialiser;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer;
import java.util.function.BiFunction;
import java.util.function.Function;
/**
* Reads a {@link IPocketUpgrade} from disk and reads/writes it to a network packet.
* <p>
* This follows the same format as {@link dan200.computercraft.api.turtle.TurtleUpgradeSerialiser} - consult the
* documentation there for more information.
*
* @param <T> The type of pocket computer upgrade this is responsible for serialising.
* @see IPocketUpgrade
* @see PocketUpgradeDataProvider
*/
public interface PocketUpgradeSerialiser<T extends IPocketUpgrade> extends UpgradeSerialiser<T> {
/**
* The ID for the associated registry.
*
* @return The registry key.
*/
static ResourceKey<Registry<PocketUpgradeSerialiser<?>>> registryId() {
return ComputerCraftAPIService.get().pocketUpgradeRegistryId();
}
/**
* Create an upgrade serialiser for a simple upgrade. This is similar to a {@link SimpleCraftingRecipeSerializer},
* but for upgrades.
* <p>
* If you might want to vary the item, it's suggested you use {@link #simpleWithCustomItem(BiFunction)} instead.
*
* @param factory Generate a new upgrade with a specific ID.
* @param <T> The type of the generated upgrade.
* @return The serialiser for this upgrade
*/
static <T extends IPocketUpgrade> PocketUpgradeSerialiser<T> simple(Function<ResourceLocation, T> factory) {
final class Impl extends SimpleSerialiser<T> implements PocketUpgradeSerialiser<T> {
private Impl(Function<ResourceLocation, T> constructor) {
super(constructor);
}
}
return new Impl(factory);
}
/**
* Create an upgrade serialiser for a simple upgrade whose crafting item can be specified.
*
* @param factory Generate a new upgrade with a specific ID and crafting item. The returned upgrade's
* {@link UpgradeBase#getCraftingItem()} <strong>MUST</strong> equal the provided item.
* @param <T> The type of the generated upgrade.
* @return The serialiser for this upgrade.
* @see #simple(Function) For upgrades whose crafting stack should not vary.
*/
static <T extends IPocketUpgrade> PocketUpgradeSerialiser<T> simpleWithCustomItem(BiFunction<ResourceLocation, ItemStack, T> factory) {
final class Impl extends SerialiserWithCraftingItem<T> implements PocketUpgradeSerialiser<T> {
private Impl(BiFunction<ResourceLocation, ItemStack, T> factory) {
super(factory);
}
}
return new Impl(factory);
}
}

View File

@@ -6,8 +6,12 @@ package dan200.computercraft.api.turtle;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import dan200.computercraft.impl.ComputerCraftAPIService;
import net.minecraft.core.Direction;
import net.minecraft.core.Registry;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceKey;
import javax.annotation.Nullable;
@@ -16,15 +20,54 @@ import javax.annotation.Nullable;
* peripheral.
* <p>
* Turtle upgrades are defined in two stages. First, one creates a {@link ITurtleUpgrade} subclass and corresponding
* {@link TurtleUpgradeSerialiser} instance, which are then registered in a Forge registry.
* {@link UpgradeSerialiser} instance, which are then registered in a registry.
* <p>
* You then write a JSON file in your mod's {@literal data/} folder. This is then parsed when the world is loaded, and
* the upgrade registered internally. See the documentation in {@link TurtleUpgradeSerialiser} for details on this process
* and where files should be located.
* the upgrade automatically registered. It is recommended this is done via {@linkplain TurtleUpgradeDataProvider data
* generators}.
*
* @see TurtleUpgradeSerialiser For how to register a turtle upgrade.
* <h2>Example</h2>
* <pre>{@code
* // We use Forge's DeferredRegister to register our serialiser. Fabric mods may register their serialiser directly.
* static final DeferredRegister<UpgradeSerialiser<? extends ITurtleUpgrade>> SERIALISERS = DeferredRegister.create(ITurtleUpgrade.serialiserRegistryKey(), "my_mod");
*
* // Register a new upgrade serialiser called "my_upgrade".
* public static final RegistryObject<UpgradeSerialiser<MyUpgrade>> MY_UPGRADE =
* SERIALISERS.register( "my_upgrade", () -> TurtleUpgradeSerialiser.simple( MyUpgrade::new ) );
*
* // Then in your constructor
* SERIALISERS.register( bus );
* }</pre>
* <p>
* We can then define a new upgrade using JSON by placing the following in
* {@literal data/<my_mod>/computercraft/turtle_upgrades/<my_upgrade_id>.json}}.
*
* <pre>{@code
* {
* "type": my_mod:my_upgrade",
* }
* }</pre>
* <p>
* {@link TurtleUpgradeDataProvider} provides a data provider to aid with generating these JSON files.
* <p>
* Finally, we need to register a model for our upgrade, see
* {@link dan200.computercraft.api.client.turtle.TurtleUpgradeModeller} for more information.
*
* <pre>{@code
* // Register our model inside FMLClientSetupEvent
* ComputerCraftAPIClient.registerTurtleUpgradeModeller(MY_UPGRADE.get(), TurtleUpgradeModeller.flatItem())
* }</pre>
*/
public interface ITurtleUpgrade extends UpgradeBase {
/**
* The registry key for upgrade serialisers.
*
* @return The registry key.
*/
static ResourceKey<Registry<UpgradeSerialiser<? extends ITurtleUpgrade>>> serialiserRegistryKey() {
return ComputerCraftAPIService.get().turtleUpgradeRegistryId();
}
/**
* Return whether this turtle adds a tool or a peripheral to the turtle.
*

View File

@@ -7,8 +7,9 @@ package dan200.computercraft.api.turtle;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.ComputerCraftTags;
import dan200.computercraft.api.upgrades.UpgradeDataProvider;
import dan200.computercraft.impl.PlatformHelper;
import net.minecraft.core.registries.Registries;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import dan200.computercraft.impl.RegistryHelper;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.data.DataGenerator;
import net.minecraft.data.PackOutput;
import net.minecraft.resources.ResourceLocation;
@@ -29,13 +30,13 @@ import java.util.function.Consumer;
* {@link #addUpgrades(Consumer)} function, construct each upgrade, and pass them off to the provided consumer to
* generate them.
*
* @see TurtleUpgradeSerialiser
* @see ITurtleUpgrade
*/
public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITurtleUpgrade, TurtleUpgradeSerialiser<?>> {
public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITurtleUpgrade> {
private static final ResourceLocation TOOL_ID = new ResourceLocation(ComputerCraftAPI.MOD_ID, "tool");
public TurtleUpgradeDataProvider(PackOutput output) {
super(output, "Turtle Upgrades", "computercraft/turtle_upgrades", TurtleUpgradeSerialiser.registryId());
super(output, "Turtle Upgrades", "computercraft/turtle_upgrades", ITurtleUpgrade.serialiserRegistryKey());
}
/**
@@ -57,7 +58,7 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITur
*/
public static class ToolBuilder {
private final ResourceLocation id;
private final TurtleUpgradeSerialiser<?> serialiser;
private final UpgradeSerialiser<? extends ITurtleUpgrade> serialiser;
private final Item toolItem;
private @Nullable String adjective;
private @Nullable Item craftingItem;
@@ -66,7 +67,7 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITur
private boolean allowEnchantments = false;
private TurtleToolDurability consumeDurability = TurtleToolDurability.NEVER;
ToolBuilder(ResourceLocation id, TurtleUpgradeSerialiser<?> serialiser, Item toolItem) {
ToolBuilder(ResourceLocation id, UpgradeSerialiser<? extends ITurtleUpgrade> serialiser, Item toolItem) {
this.id = id;
this.serialiser = serialiser;
this.toolItem = toolItem;
@@ -149,12 +150,12 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITur
*
* @param add The callback given to {@link #addUpgrades(Consumer)}.
*/
public void add(Consumer<Upgrade<TurtleUpgradeSerialiser<?>>> add) {
public void add(Consumer<Upgrade<UpgradeSerialiser<? extends ITurtleUpgrade>>> add) {
add.accept(new Upgrade<>(id, serialiser, s -> {
s.addProperty("item", PlatformHelper.get().getRegistryKey(Registries.ITEM, toolItem).toString());
s.addProperty("item", RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, toolItem).toString());
if (adjective != null) s.addProperty("adjective", adjective);
if (craftingItem != null) {
s.addProperty("craftItem", PlatformHelper.get().getRegistryKey(Registries.ITEM, craftingItem).toString());
s.addProperty("craftItem", RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, craftingItem).toString());
}
if (damageMultiplier != null) s.addProperty("damageMultiplier", damageMultiplier);
if (breakable != null) s.addProperty("breakable", breakable.location().toString());

View File

@@ -1,109 +0,0 @@
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api.turtle;
import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import dan200.computercraft.impl.ComputerCraftAPIService;
import dan200.computercraft.impl.upgrades.SerialiserWithCraftingItem;
import dan200.computercraft.impl.upgrades.SimpleSerialiser;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer;
import java.util.function.BiFunction;
import java.util.function.Function;
/**
* Reads a {@link ITurtleUpgrade} from disk and reads/writes it to a network packet.
* <p>
* These should be registered in a {@link Registry} while the game is loading, much like {@link RecipeSerializer}s.
* <p>
* If your turtle upgrade doesn't have any associated configurable parameters (like most upgrades), you can use
* {@link #simple(Function)} or {@link #simpleWithCustomItem(BiFunction)} to create a basic upgrade serialiser.
*
* <h2>Example (Forge)</h2>
* <pre>{@code
* static final DeferredRegister<TurtleUpgradeSerialiser<?>> SERIALISERS = DeferredRegister.create( TurtleUpgradeSerialiser.TYPE, "my_mod" );
*
* // Register a new upgrade serialiser called "my_upgrade".
* public static final RegistryObject<TurtleUpgradeSerialiser<MyUpgrade>> MY_UPGRADE =
* SERIALISERS.register( "my_upgrade", () -> TurtleUpgradeSerialiser.simple( MyUpgrade::new ) );
*
* // Then in your constructor
* SERIALISERS.register( bus );
* }</pre>
* <p>
* We can then define a new upgrade using JSON by placing the following in
* {@literal data/<my_mod>/computercraft/turtle_upgrades/<my_upgrade_id>.json}}.
*
* <pre>{@code
* {
* "type": my_mod:my_upgrade",
* }
* }</pre>
* <p>
* Finally, we need to register a model for our upgrade. The way to do this varies on mod loader, see
* {@link dan200.computercraft.api.client.turtle.TurtleUpgradeModeller} for more information.
* <p>
* {@link TurtleUpgradeDataProvider} provides a data provider to aid with generating these JSON files.
*
* @param <T> The type of turtle upgrade this is responsible for serialising.
* @see ITurtleUpgrade
* @see TurtleUpgradeDataProvider
* @see dan200.computercraft.api.client.turtle.TurtleUpgradeModeller
*/
public interface TurtleUpgradeSerialiser<T extends ITurtleUpgrade> extends UpgradeSerialiser<T> {
/**
* The ID for the associated registry.
*
* @return The registry key.
*/
static ResourceKey<Registry<TurtleUpgradeSerialiser<?>>> registryId() {
return ComputerCraftAPIService.get().turtleUpgradeRegistryId();
}
/**
* Create an upgrade serialiser for a simple upgrade. This is similar to a {@link SimpleCraftingRecipeSerializer},
* but for upgrades.
* <p>
* If you might want to vary the item, it's suggested you use {@link #simpleWithCustomItem(BiFunction)} instead.
*
* @param factory Generate a new upgrade with a specific ID.
* @param <T> The type of the generated upgrade.
* @return The serialiser for this upgrade
*/
static <T extends ITurtleUpgrade> TurtleUpgradeSerialiser<T> simple(Function<ResourceLocation, T> factory) {
final class Impl extends SimpleSerialiser<T> implements TurtleUpgradeSerialiser<T> {
private Impl(Function<ResourceLocation, T> constructor) {
super(constructor);
}
}
return new Impl(factory);
}
/**
* Create an upgrade serialiser for a simple upgrade whose crafting item can be specified.
*
* @param factory Generate a new upgrade with a specific ID and crafting item. The returned upgrade's
* {@link UpgradeBase#getCraftingItem()} <strong>MUST</strong> equal the provided item.
* @param <T> The type of the generated upgrade.
* @return The serialiser for this upgrade.
* @see #simple(Function) For upgrades whose crafting stack should not vary.
*/
static <T extends ITurtleUpgrade> TurtleUpgradeSerialiser<T> simpleWithCustomItem(BiFunction<ResourceLocation, ItemStack, T> factory) {
final class Impl extends SerialiserWithCraftingItem<T> implements TurtleUpgradeSerialiser<T> {
private Impl(BiFunction<ResourceLocation, ItemStack, T> factory) {
super(factory);
}
}
return new Impl(factory);
}
}

View File

@@ -9,7 +9,6 @@ import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.impl.PlatformHelper;
import net.minecraft.Util;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
@@ -100,7 +99,7 @@ public interface UpgradeBase {
* The default check requires that any non-capability NBT is exactly the same as the
* crafting item, but this may be relaxed for your upgrade.
* <p>
* This is based on {@code net.minecraftforge.common.crafting.StrictNBTIngredient}'s check.
* This is based on {@code net.neoforged.common.crafting.StrictNBTIngredient}'s check.
*
* @param stack The stack to check. This is guaranteed to be non-empty and have the same item as
* {@link #getCraftingItem()}.
@@ -111,12 +110,12 @@ public interface UpgradeBase {
// A more expanded form of ItemStack.areShareTagsEqual, but allowing an empty tag to be equal to a
// null one.
var shareTag = PlatformHelper.get().getShareTag(stack);
var craftingShareTag = PlatformHelper.get().getShareTag(crafting);
if (shareTag == craftingShareTag) return true;
if (shareTag == null) return Objects.requireNonNull(craftingShareTag).isEmpty();
if (craftingShareTag == null) return shareTag.isEmpty();
return shareTag.equals(craftingShareTag);
var tag = stack.getTag();
var craftingTag = crafting.getTag();
if (tag == craftingTag) return true;
if (tag == null) return Objects.requireNonNull(craftingTag).isEmpty();
if (craftingTag == null) return tag.isEmpty();
return tag.equals(craftingTag);
}
/**

View File

@@ -6,19 +6,20 @@ package dan200.computercraft.api.upgrades;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.impl.PlatformHelper;
import dan200.computercraft.impl.RegistryHelper;
import dan200.computercraft.impl.upgrades.SerialiserWithCraftingItem;
import dan200.computercraft.impl.upgrades.SimpleSerialiser;
import net.minecraft.Util;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.Registries;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.data.CachedOutput;
import net.minecraft.data.DataProvider;
import net.minecraft.data.PackOutput;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nullable;
import java.util.*;
@@ -31,31 +32,31 @@ import java.util.function.Function;
* the other subclasses.
*
* @param <T> The base class of upgrades.
* @param <R> The upgrade serialiser to register for.
*/
public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends UpgradeSerialiser<? extends T>> implements DataProvider {
public abstract class UpgradeDataProvider<T extends UpgradeBase> implements DataProvider {
private final PackOutput output;
private final String name;
private final String folder;
private final ResourceKey<Registry<R>> registry;
private final Registry<UpgradeSerialiser<? extends T>> registry;
private @Nullable List<T> upgrades;
protected UpgradeDataProvider(PackOutput output, String name, String folder, ResourceKey<Registry<R>> registry) {
@ApiStatus.Internal
protected UpgradeDataProvider(PackOutput output, String name, String folder, ResourceKey<Registry<UpgradeSerialiser<? extends T>>> registry) {
this.output = output;
this.name = name;
this.folder = folder;
this.registry = registry;
this.registry = RegistryHelper.getRegistry(registry);
}
/**
* Register an upgrade using a "simple" serialiser (e.g. {@link TurtleUpgradeSerialiser#simple(Function)}).
* Register an upgrade using a {@linkplain UpgradeSerialiser#simple(Function) "simple" serialiser}.
*
* @param id The ID of the upgrade to create.
* @param serialiser The simple serialiser.
* @return The constructed upgrade, ready to be passed off to {@link #addUpgrades(Consumer)}'s consumer.
*/
public final Upgrade<R> simple(ResourceLocation id, R serialiser) {
public final Upgrade<UpgradeSerialiser<? extends T>> simple(ResourceLocation id, UpgradeSerialiser<? extends T> serialiser) {
if (!(serialiser instanceof SimpleSerialiser)) {
throw new IllegalStateException(serialiser + " must be a simple() seriaiser.");
}
@@ -65,20 +66,20 @@ public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends Upgra
}
/**
* Register an upgrade using a "simple" serialiser (e.g. {@link TurtleUpgradeSerialiser#simple(Function)}).
* Register an upgrade using a {@linkplain UpgradeSerialiser#simple(Function) simple serialiser}.
*
* @param id The ID of the upgrade to create.
* @param serialiser The simple serialiser.
* @param item The crafting upgrade for this item.
* @return The constructed upgrade, ready to be passed off to {@link #addUpgrades(Consumer)}'s consumer.
*/
public final Upgrade<R> simpleWithCustomItem(ResourceLocation id, R serialiser, Item item) {
public final Upgrade<UpgradeSerialiser<? extends T>> simpleWithCustomItem(ResourceLocation id, UpgradeSerialiser<? extends T> serialiser, Item item) {
if (!(serialiser instanceof SerialiserWithCraftingItem)) {
throw new IllegalStateException(serialiser + " must be a simpleWithCustomItem() serialiser.");
}
return new Upgrade<>(id, serialiser, s ->
s.addProperty("item", PlatformHelper.get().getRegistryKey(Registries.ITEM, item).toString())
s.addProperty("item", RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, item).toString())
);
}
@@ -94,7 +95,7 @@ public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends Upgra
*
* @param addUpgrade A callback used to register an upgrade.
*/
protected abstract void addUpgrades(Consumer<Upgrade<R>> addUpgrade);
protected abstract void addUpgrades(Consumer<Upgrade<UpgradeSerialiser<? extends T>>> addUpgrade);
@Override
public CompletableFuture<?> run(CachedOutput cache) {
@@ -107,7 +108,7 @@ public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends Upgra
if (!seen.add(upgrade.id())) throw new IllegalStateException("Duplicate upgrade " + upgrade.id());
var json = new JsonObject();
json.addProperty("type", PlatformHelper.get().getRegistryKey(registry, upgrade.serialiser()).toString());
json.addProperty("type", RegistryHelper.getKeyOrThrow(registry, upgrade.serialiser()).toString());
upgrade.serialise().accept(json);
futures.add(DataProvider.saveStable(cache, json, base.resolve(upgrade.id().getNamespace() + "/" + folder + "/" + upgrade.id().getPath() + ".json")));
@@ -129,9 +130,9 @@ public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends Upgra
return name;
}
public final R existingSerialiser(ResourceLocation id) {
var result = PlatformHelper.get().getRegistryObject(registry, id);
if (result == null) throw new IllegalArgumentException("No such serialiser " + registry);
public final UpgradeSerialiser<? extends T> existingSerialiser(ResourceLocation id) {
var result = registry.get(id);
if (result == null) throw new IllegalArgumentException("No such serialiser " + id);
return result;
}

View File

@@ -5,21 +5,40 @@
package dan200.computercraft.api.upgrades;
import com.google.gson.JsonObject;
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.pocket.PocketUpgradeDataProvider;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleUpgradeDataProvider;
import dan200.computercraft.impl.upgrades.SerialiserWithCraftingItem;
import dan200.computercraft.impl.upgrades.SimpleSerialiser;
import net.minecraft.core.Registry;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer;
import java.util.function.BiFunction;
import java.util.function.Function;
/**
* Base interface for upgrade serialisers. This should generally not be implemented directly, instead implementing one
* of {@link TurtleUpgradeSerialiser} or {@link PocketUpgradeSerialiser}.
* A serialiser for {@link ITurtleUpgrade} or {@link IPocketUpgrade}s.
* <p>
* However, it may sometimes be useful to implement this if you have some shared logic between upgrade types.
* These should be registered in a {@link Registry} while the game is loading, much like {@link RecipeSerializer}s.
* <p>
* This interface is very similar to {@link RecipeSerializer}; each serialiser should correspond to a specific upgrade
* class. Upgrades are then read from JSON files in datapacks, allowing multiple instances of the upgrade to be
* registered.
* <p>
* If your upgrade doesn't have any associated configurable parameters (like most upgrades), you can use
* {@link #simple(Function)} or {@link #simpleWithCustomItem(BiFunction)} to create a basic upgrade serialiser.
* <p>
* Upgrades may be data generated via a {@link UpgradeDataProvider} (see {@link TurtleUpgradeDataProvider} and
* {@link PocketUpgradeDataProvider}).
*
* @param <T> The upgrade that this class can serialise and deserialise.
* @see TurtleUpgradeSerialiser
* @see PocketUpgradeSerialiser
* @see ITurtleUpgrade
* @see IPocketUpgrade
*/
public interface UpgradeSerialiser<T extends UpgradeBase> {
/**
@@ -49,4 +68,30 @@ public interface UpgradeSerialiser<T extends UpgradeBase> {
*/
void toNetwork(FriendlyByteBuf buffer, T upgrade);
/**
* Create an upgrade serialiser for a simple upgrade. This is similar to a {@link SimpleCraftingRecipeSerializer},
* but for upgrades.
* <p>
* If you might want to vary the item, it's suggested you use {@link #simpleWithCustomItem(BiFunction)} instead.
*
* @param factory Generate a new upgrade with a specific ID.
* @param <T> The type of the generated upgrade.
* @return The serialiser for this upgrade
*/
static <T extends UpgradeBase> UpgradeSerialiser<T> simple(Function<ResourceLocation, T> factory) {
return new SimpleSerialiser<>(factory);
}
/**
* Create an upgrade serialiser for a simple upgrade whose crafting item can be specified.
*
* @param factory Generate a new upgrade with a specific ID and crafting item. The returned upgrade's
* {@link UpgradeBase#getCraftingItem()} <strong>MUST</strong> equal the provided item.
* @param <T> The type of the generated upgrade.
* @return The serialiser for this upgrade.
* @see #simple(Function) For upgrades whose crafting stack should not vary.
*/
static <T extends UpgradeBase> UpgradeSerialiser<T> simpleWithCustomItem(BiFunction<ResourceLocation, ItemStack, T> factory) {
return new SerialiserWithCraftingItem<>(factory);
}
}

View File

@@ -15,10 +15,11 @@ import dan200.computercraft.api.media.MediaProvider;
import dan200.computercraft.api.network.PacketNetwork;
import dan200.computercraft.api.network.wired.WiredElement;
import dan200.computercraft.api.network.wired.WiredNode;
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.redstone.BundledRedstoneProvider;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleRefuelHandler;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Registry;
@@ -67,9 +68,9 @@ public interface ComputerCraftAPIService {
void registerRefuelHandler(TurtleRefuelHandler handler);
ResourceKey<Registry<TurtleUpgradeSerialiser<?>>> turtleUpgradeRegistryId();
ResourceKey<Registry<UpgradeSerialiser<? extends ITurtleUpgrade>>> turtleUpgradeRegistryId();
ResourceKey<Registry<PocketUpgradeSerialiser<?>>> pocketUpgradeRegistryId();
ResourceKey<Registry<UpgradeSerialiser<? extends IPocketUpgrade>>> pocketUpgradeRegistryId();
DetailRegistry<ItemStack> getItemStackDetailRegistry();

View File

@@ -6,11 +6,6 @@ package dan200.computercraft.impl;
import com.google.gson.JsonObject;
import dan200.computercraft.api.upgrades.UpgradeDataProvider;
import net.minecraft.core.Registry;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nullable;
@@ -32,39 +27,6 @@ public interface PlatformHelper {
return instance == null ? Services.raise(PlatformHelper.class, Instance.ERROR) : instance;
}
/**
* Get the unique ID for a registered object.
*
* @param registry The registry to look up this object in.
* @param object The object to look up.
* @param <T> The type of object the registry stores.
* @return The registered object's ID.
* @throws IllegalArgumentException If the registry or object are not registered.
*/
<T> ResourceLocation getRegistryKey(ResourceKey<Registry<T>> registry, T object);
/**
* Look up an ID in a registry, returning the registered object.
*
* @param registry The registry to look up this object in.
* @param id The ID to look up.
* @param <T> The type of object the registry stores.
* @return The resolved registry object.
* @throws IllegalArgumentException If the registry or object are not registered.
*/
<T> T getRegistryObject(ResourceKey<Registry<T>> registry, ResourceLocation id);
/**
* Get the subset of an {@link ItemStack}'s {@linkplain ItemStack#getTag() tag} which is synced to the client.
*
* @param item The stack.
* @return The item's tag.
*/
@Nullable
default CompoundTag getShareTag(ItemStack item) {
return item.getTag();
}
/**
* Add a resource condition which requires a mod to be loaded. This should be used by data providers such as
* {@link UpgradeDataProvider}.

View File

@@ -0,0 +1,49 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.impl;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.ApiStatus;
/**
* Additioanl functions for working with {@linkplain Registry registries}.
*/
@ApiStatus.Internal
public final class RegistryHelper {
private RegistryHelper() {
}
/**
* Find a registry from a {@link ResourceKey}, throwing if it does not exist.
*
* @param id The id of the registry.
* @param <T> The contents of the registry
* @return The associated registry.
*/
@SuppressWarnings("unchecked")
public static <T> Registry<T> getRegistry(ResourceKey<Registry<T>> id) {
var registry = (Registry<T>) BuiltInRegistries.REGISTRY.get(id.location());
if (registry == null) throw new IllegalArgumentException("Unknown registry " + id);
return registry;
}
/**
* Get the key of a registry entry, throwing if it is not registered.
*
* @param registry The registry to look up in.
* @param object The object to look up.
* @param <T> The type of this registry.
* @return The ID of this object
* @see Registry#getResourceKey(Object)
*/
public static <T> ResourceLocation getKeyOrThrow(Registry<T> registry, T object) {
var key = registry.getResourceKey(object);
if (key.isEmpty()) throw new IllegalArgumentException(object + " was not registered in " + registry.key());
return key.get().location();
}
}

View File

@@ -29,7 +29,7 @@ public final class Services {
* @throws IllegalStateException When the service cannot be loaded.
*/
public static <T> T load(Class<T> klass) {
var services = ServiceLoader.load(klass, klass.getClassLoader()).stream().toList();
var services = ServiceLoader.load(klass).stream().toList();
return switch (services.size()) {
case 1 -> services.get(0).get();
case 0 -> throw new IllegalStateException("Cannot find service for " + klass.getName());

View File

@@ -23,27 +23,27 @@ import java.util.function.BiFunction;
* @param <T> The upgrade that this class can serialise and deserialise.
*/
@ApiStatus.Internal
public abstract class SerialiserWithCraftingItem<T extends UpgradeBase> implements UpgradeSerialiser<T> {
public final class SerialiserWithCraftingItem<T extends UpgradeBase> implements UpgradeSerialiser<T> {
private final BiFunction<ResourceLocation, ItemStack, T> factory;
protected SerialiserWithCraftingItem(BiFunction<ResourceLocation, ItemStack, T> factory) {
public SerialiserWithCraftingItem(BiFunction<ResourceLocation, ItemStack, T> factory) {
this.factory = factory;
}
@Override
public final T fromJson(ResourceLocation id, JsonObject object) {
public T fromJson(ResourceLocation id, JsonObject object) {
var item = GsonHelper.getAsItem(object, "item");
return factory.apply(id, new ItemStack(item));
}
@Override
public final T fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) {
public T fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) {
var item = buffer.readItem();
return factory.apply(id, item);
}
@Override
public final void toNetwork(FriendlyByteBuf buffer, T upgrade) {
public void toNetwork(FriendlyByteBuf buffer, T upgrade) {
buffer.writeItem(upgrade.getCraftingItem());
}
}

View File

@@ -21,7 +21,7 @@ import java.util.function.Function;
* @param <T> The upgrade that this class can serialise and deserialise.
*/
@ApiStatus.Internal
public abstract class SimpleSerialiser<T extends UpgradeBase> implements UpgradeSerialiser<T> {
public final class SimpleSerialiser<T extends UpgradeBase> implements UpgradeSerialiser<T> {
private final Function<ResourceLocation, T> constructor;
public SimpleSerialiser(Function<ResourceLocation, T> constructor) {
@@ -29,16 +29,16 @@ public abstract class SimpleSerialiser<T extends UpgradeBase> implements Upgrade
}
@Override
public final T fromJson(ResourceLocation id, JsonObject object) {
public T fromJson(ResourceLocation id, JsonObject object) {
return constructor.apply(id);
}
@Override
public final T fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) {
public T fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) {
return constructor.apply(id);
}
@Override
public final void toNetwork(FriendlyByteBuf buffer, T upgrade) {
public void toNetwork(FriendlyByteBuf buffer, T upgrade) {
}
}

View File

@@ -11,12 +11,6 @@ plugins {
id("cc-tweaked.publishing")
}
sourceSets {
main {
resources.srcDir("src/generated/resources")
}
}
minecraft {
accessWideners(
"src/main/resources/computercraft.accesswidener",
@@ -52,9 +46,6 @@ dependencies {
testImplementation(libs.bundles.test)
testRuntimeOnly(libs.bundles.testRuntime)
testImplementation(libs.jmh)
testAnnotationProcessor(libs.jmh.processor)
testModCompileOnly(libs.mixin)
testModImplementation(testFixtures(project(":core")))
testModImplementation(testFixtures(project(":common")))
@@ -111,21 +102,3 @@ val lintLua by tasks.registering(IlluaminateExec::class) {
doFirst { if (System.getenv("GITHUB_ACTIONS") != null) println("::add-matcher::.github/matchers/illuaminate.json") }
doLast { if (System.getenv("GITHUB_ACTIONS") != null) println("::remove-matcher owner=illuaminate::") }
}
val runData by tasks.registering(MergeTrees::class) {
output = layout.projectDirectory.dir("src/generated/resources")
for (loader in listOf("forge", "fabric")) {
mustRunAfter(":$loader:runData")
source {
input {
from(project(":$loader").layout.buildDirectory.dir("generatedResources"))
exclude(".cache")
}
output = project(":$loader").layout.projectDirectory.dir("src/generated/resources")
}
}
}
tasks.withType(GenerateModuleMetadata::class).configureEach { isEnabled = false }

View File

@@ -108,7 +108,7 @@ public final class ClientHooks {
*/
public static void addBlockDebugInfo(Consumer<String> addText) {
var minecraft = Minecraft.getInstance();
if (!minecraft.options.renderDebug || minecraft.level == null) return;
if (!minecraft.getDebugOverlay().showDebugScreen() || minecraft.level == null) return;
if (minecraft.hitResult == null || minecraft.hitResult.getType() != HitResult.Type.BLOCK) return;
var tile = minecraft.level.getBlockEntity(((BlockHitResult) minecraft.hitResult).getBlockPos());
@@ -138,7 +138,7 @@ public final class ClientHooks {
* @param addText A callback which adds a single line of text.
*/
public static void addGameDebugInfo(Consumer<String> addText) {
if (MonitorBlockEntityRenderer.hasRenderedThisFrame() && Minecraft.getInstance().options.renderDebug) {
if (MonitorBlockEntityRenderer.hasRenderedThisFrame() && Minecraft.getInstance().getDebugOverlay().showDebugScreen()) {
addText.accept("[CC:T] Monitor renderer: " + MonitorBlockEntityRenderer.currentRenderer());
}
}

View File

@@ -22,7 +22,6 @@ import dan200.computercraft.core.util.Colour;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.command.CommandComputerCraft;
import dan200.computercraft.shared.common.IColouredItem;
import dan200.computercraft.shared.computer.core.ComputerState;
import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
import dan200.computercraft.shared.computer.inventory.ViewComputerMenu;
@@ -32,6 +31,8 @@ import net.minecraft.Util;
import net.minecraft.client.Minecraft;
import net.minecraft.client.color.item.ItemColor;
import net.minecraft.client.gui.screens.MenuScreens;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.gui.screens.inventory.MenuAccess;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.ShaderInstance;
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
@@ -43,6 +44,8 @@ import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.PreparableReloadListener;
import net.minecraft.server.packs.resources.ResourceProvider;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.ItemLike;
@@ -78,34 +81,35 @@ public final class ClientRegistry {
/**
* Register any client-side objects which must be done on the main thread.
*
* @param itemProperties Callback to register item properties.
*/
public static void registerMainThread(RegisterItemProperty itemProperties) {
MenuScreens.<AbstractComputerMenu, ComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.COMPUTER.get(), ComputerScreen::new);
MenuScreens.<AbstractComputerMenu, ComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.POCKET_COMPUTER.get(), ComputerScreen::new);
MenuScreens.<AbstractComputerMenu, NoTermComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.POCKET_COMPUTER_NO_TERM.get(), NoTermComputerScreen::new);
MenuScreens.register(ModRegistry.Menus.TURTLE.get(), TurtleScreen::new);
MenuScreens.register(ModRegistry.Menus.PRINTER.get(), PrinterScreen::new);
MenuScreens.register(ModRegistry.Menus.DISK_DRIVE.get(), DiskDriveScreen::new);
MenuScreens.register(ModRegistry.Menus.PRINTOUT.get(), PrintoutScreen::new);
MenuScreens.<ViewComputerMenu, ComputerScreen<ViewComputerMenu>>register(ModRegistry.Menus.VIEW_COMPUTER.get(), ComputerScreen::new);
registerItemProperty(itemProperties, "state",
new UnclampedPropertyFunction((stack, world, player, random) -> {
var computer = ClientPocketComputers.get(stack);
return (computer == null ? ComputerState.OFF : computer.getState()).ordinal();
}),
public static void registerMainThread() {
registerItemProperty("state",
new UnclampedPropertyFunction((stack, world, player, random) -> ClientPocketComputers.get(stack).getState().ordinal()),
ModRegistry.Items.POCKET_COMPUTER_NORMAL, ModRegistry.Items.POCKET_COMPUTER_ADVANCED
);
registerItemProperty(itemProperties, "coloured",
registerItemProperty("coloured",
(stack, world, player, random) -> IColouredItem.getColourBasic(stack) != -1 ? 1 : 0,
ModRegistry.Items.POCKET_COMPUTER_NORMAL, ModRegistry.Items.POCKET_COMPUTER_ADVANCED
);
}
public static void registerMenuScreens(RegisterMenuScreen register) {
register.<AbstractComputerMenu, ComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.COMPUTER.get(), ComputerScreen::new);
register.<AbstractComputerMenu, ComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.POCKET_COMPUTER.get(), ComputerScreen::new);
register.<AbstractComputerMenu, NoTermComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.POCKET_COMPUTER_NO_TERM.get(), NoTermComputerScreen::new);
register.register(ModRegistry.Menus.TURTLE.get(), TurtleScreen::new);
register.register(ModRegistry.Menus.PRINTER.get(), PrinterScreen::new);
register.register(ModRegistry.Menus.DISK_DRIVE.get(), DiskDriveScreen::new);
register.register(ModRegistry.Menus.PRINTOUT.get(), PrintoutScreen::new);
register.<ViewComputerMenu, ComputerScreen<ViewComputerMenu>>register(ModRegistry.Menus.VIEW_COMPUTER.get(), ComputerScreen::new);
}
public interface RegisterMenuScreen {
<M extends AbstractContainerMenu, U extends Screen & MenuAccess<M>> void register(MenuType<? extends M> type, MenuScreens.ScreenConstructor<M, U> factory);
}
public static void registerTurtleModellers(RegisterTurtleUpgradeModeller register) {
register.register(ModRegistry.TurtleSerialisers.SPEAKER.get(), TurtleUpgradeModeller.sided(
new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_left"),
@@ -121,17 +125,9 @@ public final class ClientRegistry {
}
@SafeVarargs
private static void registerItemProperty(RegisterItemProperty itemProperties, String name, ClampedItemPropertyFunction getter, Supplier<? extends Item>... items) {
private static void registerItemProperty(String name, ClampedItemPropertyFunction getter, Supplier<? extends Item>... items) {
var id = new ResourceLocation(ComputerCraftAPI.MOD_ID, name);
for (var item : items) itemProperties.register(item.get(), id, getter);
}
/**
* Register an item property via {@link ItemProperties#register}. Forge and Fabric expose different methods, so we
* supply this via mod-loader-specific code.
*/
public interface RegisterItemProperty {
void register(Item item, ResourceLocation name, ClampedItemPropertyFunction property);
for (var item : items) ItemProperties.register(item.get(), id, getter);
}
public static void registerReloadListeners(Consumer<PreparableReloadListener> register, Minecraft minecraft) {
@@ -169,14 +165,17 @@ public final class ClientRegistry {
}
private static int getPocketColour(ItemStack stack, int layer) {
return switch (layer) {
default -> 0xFFFFFF;
case 1 -> IColouredItem.getColourBasic(stack); // Frame colour
case 2 -> { // Light colour
var computer = ClientPocketComputers.get(stack);
yield computer == null || computer.getLightState() == -1 ? Colour.BLACK.getHex() : computer.getLightState();
switch (layer) {
case 0:
default:
return 0xFFFFFF;
case 1: // Frame colour
return IColouredItem.getColourBasic(stack);
case 2: { // Light colour
var light = ClientPocketComputers.get(stack).getLightState();
return light == -1 ? Colour.BLACK.getHex() : light;
}
};
}
}
private static int getTurtleColour(ItemStack stack, int layer) {

View File

@@ -9,7 +9,6 @@ import dan200.computercraft.client.gui.widgets.DynamicImageButton;
import dan200.computercraft.client.gui.widgets.TerminalWidget;
import dan200.computercraft.client.network.ClientNetworking;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.util.Nullability;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.InputHandler;
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
@@ -19,7 +18,6 @@ import dan200.computercraft.shared.config.Config;
import dan200.computercraft.shared.network.server.UploadFileMessage;
import net.minecraft.ChatFormatting;
import net.minecraft.Util;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.events.GuiEventListener;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
@@ -98,8 +96,8 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
getTerminal().update();
if (uploadNagDeadline != Long.MAX_VALUE && Util.getNanos() >= uploadNagDeadline) {
new ItemToast(minecraft(), displayStack, NO_RESPONSE_TITLE, NO_RESPONSE_MSG, ItemToast.TRANSFER_NO_RESPONSE_TOKEN)
.showOrReplace(minecraft().getToasts());
new ItemToast(minecraft, displayStack, NO_RESPONSE_TITLE, NO_RESPONSE_MSG, ItemToast.TRANSFER_NO_RESPONSE_TOKEN)
.showOrReplace(minecraft.getToasts());
uploadNagDeadline = Long.MAX_VALUE;
}
}
@@ -127,7 +125,6 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
@Override
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
renderBackground(graphics);
super.render(graphics, mouseX, mouseY, partialTicks);
renderTooltip(graphics, mouseX, mouseY);
}
@@ -209,7 +206,7 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
return;
}
if (!toUpload.isEmpty()) UploadFileMessage.send(menu, toUpload, ClientNetworking::sendToServer);
if (toUpload.size() > 0) UploadFileMessage.send(menu, toUpload, ClientNetworking::sendToServer);
}
public void uploadResult(UploadResult result, @Nullable Component message) {
@@ -225,13 +222,9 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
}
private void alert(Component title, Component message) {
OptionScreen.show(minecraft(), title, message,
List.of(OptionScreen.newButton(OK, b -> minecraft().setScreen(this))),
() -> minecraft().setScreen(this)
OptionScreen.show(minecraft, title, message,
List.of(OptionScreen.newButton(OK, b -> minecraft.setScreen(this))),
() -> minecraft.setScreen(this)
);
}
private Minecraft minecraft() {
return Nullability.assertNonNull(minecraft);
}
}

View File

@@ -49,31 +49,31 @@ public final class ClientInputHandler implements InputHandler {
@Override
public void keyDown(int key, boolean repeat) {
ClientNetworking.sendToServer(new KeyEventServerMessage(menu, repeat ? KeyEventServerMessage.Action.REPEAT : KeyEventServerMessage.Action.DOWN, key));
ClientNetworking.sendToServer(new KeyEventServerMessage(menu, repeat ? KeyEventServerMessage.TYPE_REPEAT : KeyEventServerMessage.TYPE_DOWN, key));
}
@Override
public void keyUp(int key) {
ClientNetworking.sendToServer(new KeyEventServerMessage(menu, KeyEventServerMessage.Action.UP, key));
ClientNetworking.sendToServer(new KeyEventServerMessage(menu, KeyEventServerMessage.TYPE_UP, key));
}
@Override
public void mouseClick(int button, int x, int y) {
ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.Action.CLICK, button, x, y));
ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_CLICK, button, x, y));
}
@Override
public void mouseUp(int button, int x, int y) {
ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.Action.UP, button, x, y));
ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_UP, button, x, y));
}
@Override
public void mouseDrag(int button, int x, int y) {
ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.Action.DRAG, button, x, y));
ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_DRAG, button, x, y));
}
@Override
public void mouseScroll(int direction, int x, int y) {
ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.Action.SCROLL, direction, x, y));
ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_SCROLL, direction, x, y));
}
}

View File

@@ -28,7 +28,6 @@ public class DiskDriveScreen extends AbstractContainerScreen<DiskDriveMenu> {
@Override
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
renderBackground(graphics);
super.render(graphics, mouseX, mouseY, partialTicks);
renderTooltip(graphics, mouseX, mouseY);
}

View File

@@ -9,6 +9,7 @@ import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.toasts.Toast;
import net.minecraft.client.gui.components.toasts.ToastComponent;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.FormattedCharSequence;
import net.minecraft.world.item.ItemStack;
@@ -18,6 +19,7 @@ import java.util.List;
* A {@link Toast} implementation which displays an arbitrary message along with an optional {@link ItemStack}.
*/
public class ItemToast implements Toast {
private static final ResourceLocation TEXTURE = new ResourceLocation("toast/recipe");
public static final Object TRANSFER_NO_RESPONSE_TOKEN = new Object();
private static final long DISPLAY_TIME = 7000L;
@@ -79,7 +81,7 @@ public class ItemToast implements Toast {
}
if (width == 160 && message.size() <= 1) {
graphics.blit(TEXTURE, 0, 0, 0, 64, width, height());
graphics.blitSprite(TEXTURE, 0, 0, width, height());
} else {
var height = height();
@@ -109,14 +111,14 @@ public class ItemToast implements Toast {
}
private static void renderBackgroundRow(GuiGraphics graphics, int x, int u, int y, int height) {
var leftOffset = 5;
var leftOffset = u == 0 ? 20 : 5;
var rightOffset = Math.min(60, x - leftOffset);
graphics.blit(TEXTURE, 0, y, 0, 32 + u, leftOffset, height);
graphics.blitSprite(TEXTURE, 160, 32, 0, u, 0, y, leftOffset, height);
for (var k = leftOffset; k < x - rightOffset; k += 64) {
graphics.blit(TEXTURE, k, y, 32, 32 + u, Math.min(64, x - k - rightOffset), height);
graphics.blitSprite(TEXTURE, 160, 32, 32, u, k, y, Math.min(64, x - k - rightOffset), height);
}
graphics.blit(TEXTURE, x - rightOffset, y, 160 - rightOffset, 32 + u, rightOffset, height);
graphics.blitSprite(TEXTURE, 160, 32, 160 - rightOffset, u, x - rightOffset, y, rightOffset, height);
}
}

View File

@@ -6,10 +6,8 @@ package dan200.computercraft.client.gui;
import dan200.computercraft.client.gui.widgets.TerminalWidget;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.util.Nullability;
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
import net.minecraft.client.KeyMapping;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.gui.screens.inventory.MenuAccess;
@@ -18,7 +16,6 @@ import net.minecraft.world.entity.player.Inventory;
import org.lwjgl.glfw.GLFW;
import javax.annotation.Nullable;
import java.util.Objects;
import static dan200.computercraft.core.util.Nullability.assertNonNull;
@@ -47,8 +44,8 @@ public class NoTermComputerScreen<T extends AbstractComputerMenu> extends Screen
protected void init() {
// First ensure we're still grabbing the mouse, so the user can look around. Then reset bits of state that
// grabbing unsets.
minecraft().mouseHandler.grabMouse();
minecraft().screen = this;
minecraft.mouseHandler.grabMouse();
minecraft.screen = this;
KeyMapping.releaseAll();
super.init();
@@ -66,14 +63,14 @@ public class NoTermComputerScreen<T extends AbstractComputerMenu> extends Screen
}
@Override
public boolean mouseScrolled(double pMouseX, double pMouseY, double pDelta) {
Objects.requireNonNull(minecraft().player).getInventory().swapPaint(pDelta);
return super.mouseScrolled(pMouseX, pMouseY, pDelta);
public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, double scrollY) {
minecraft.player.getInventory().swapPaint(scrollX);
return super.mouseScrolled(mouseX, mouseY, scrollX, scrollY);
}
@Override
public void onClose() {
Objects.requireNonNull(minecraft().player).closeContainer();
minecraft.player.closeContainer();
super.onClose();
}
@@ -96,16 +93,12 @@ public class NoTermComputerScreen<T extends AbstractComputerMenu> extends Screen
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
super.render(graphics, mouseX, mouseY, partialTicks);
var font = minecraft().font;
var font = minecraft.font;
var lines = font.split(Component.translatable("gui.computercraft.pocket_computer_overlay"), (int) (width * 0.8));
var y = 10;
for (var line : lines) {
graphics.drawString(font, line, (width / 2) - (font.width(line) / 2), y, 0xFFFFFF, true);
graphics.drawString(font, line, (width / 2) - (minecraft.font.width(line) / 2), y, 0xFFFFFF, true);
y += 9;
}
}
private Minecraft minecraft() {
return Nullability.assertNonNull(minecraft);
}
}

View File

@@ -86,8 +86,6 @@ public final class OptionScreen extends Screen {
@Override
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
renderBackground(graphics);
// Render the actual texture.
graphics.blit(BACKGROUND, x, y, 0, 0, innerWidth, PADDING);
graphics.blit(BACKGROUND,

View File

@@ -30,7 +30,6 @@ public class PrinterScreen extends AbstractContainerScreen<PrinterMenu> {
@Override
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
renderBackground(graphics);
super.render(graphics, mouseX, mouseY, partialTicks);
renderTooltip(graphics, mouseX, mouseY);
}

View File

@@ -64,15 +64,15 @@ public class PrintoutScreen extends AbstractContainerScreen<HeldItemMenu> {
}
@Override
public boolean mouseScrolled(double x, double y, double delta) {
if (super.mouseScrolled(x, y, delta)) return true;
if (delta < 0) {
public boolean mouseScrolled(double x, double y, double deltaX, double deltaY) {
if (super.mouseScrolled(x, y, deltaX, deltaY)) return true;
if (deltaX < 0) {
// Scroll up goes to the next page
if (page < pages - 1) page++;
return true;
}
if (delta > 0) {
if (deltaX > 0) {
// Scroll down goes to the previous page
if (page > 0) page--;
return true;
@@ -91,14 +91,12 @@ public class PrintoutScreen extends AbstractContainerScreen<HeldItemMenu> {
}
@Override
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
public void renderBackground(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
// We must take the background further back in order to not overlap with our printed pages.
graphics.pose().pushPose();
graphics.pose().translate(0, 0, -1);
renderBackground(graphics);
super.renderBackground(graphics, mouseX, mouseY, partialTicks);
graphics.pose().popPose();
super.render(graphics, mouseX, mouseY, partialTicks);
}
@Override

View File

@@ -43,6 +43,10 @@ public class DynamicImageButton extends Button {
@Override
public void renderWidget(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
var message = this.message.get();
setMessage(message.message());
setTooltip(message.tooltip());
var texture = this.texture.get(isHoveredOrFocused());
RenderSystem.disableDepthTest();
@@ -50,14 +54,6 @@ public class DynamicImageButton extends Button {
RenderSystem.enableDepthTest();
}
@Override
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
var message = this.message.get();
setMessage(message.message());
setTooltip(message.tooltip());
super.render(graphics, mouseX, mouseY, partialTicks);
}
public record HintedMessage(Component message, Tooltip tooltip) {
public HintedMessage(Component message, @Nullable Component hint) {
this(

View File

@@ -195,7 +195,7 @@ public class TerminalWidget extends AbstractWidget {
}
@Override
public boolean mouseScrolled(double mouseX, double mouseY, double delta) {
public boolean mouseScrolled(double mouseX, double mouseY, double delta, double deltaY) {
if (!inTermRegion(mouseX, mouseY)) return false;
if (!hasMouseSupport() || delta == 0) return false;

View File

@@ -17,7 +17,6 @@ import dan200.computercraft.shared.computer.terminal.TerminalState;
import dan200.computercraft.shared.computer.upload.UploadResult;
import dan200.computercraft.shared.network.client.ClientNetworkContext;
import dan200.computercraft.shared.peripheral.monitor.MonitorBlockEntity;
import dan200.computercraft.shared.peripheral.speaker.EncodedAudio;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
@@ -28,6 +27,7 @@ import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import javax.annotation.Nullable;
import java.nio.ByteBuffer;
import java.util.UUID;
/**
@@ -49,7 +49,7 @@ public final class ClientNetworkContextImpl implements ClientNetworkContext {
}
@Override
public void handleMonitorData(BlockPos pos, @Nullable TerminalState terminal) {
public void handleMonitorData(BlockPos pos, TerminalState terminal) {
var player = Minecraft.getInstance().player;
if (player == null) return;
@@ -67,17 +67,19 @@ public final class ClientNetworkContextImpl implements ClientNetworkContext {
}
@Override
public void handlePocketComputerData(UUID instanceId, ComputerState state, int lightState, @Nullable TerminalState terminal) {
ClientPocketComputers.setState(instanceId, state, lightState, terminal);
public void handlePocketComputerData(int instanceId, ComputerState state, int lightState, TerminalState terminal) {
var computer = ClientPocketComputers.get(instanceId, terminal.colour);
computer.setState(state, lightState);
if (terminal.hasTerminal()) computer.setTerminal(terminal);
}
@Override
public void handlePocketComputerDeleted(UUID instanceId) {
public void handlePocketComputerDeleted(int instanceId) {
ClientPocketComputers.remove(instanceId);
}
@Override
public void handleSpeakerAudio(UUID source, SpeakerPosition.Message position, float volume, EncodedAudio buffer) {
public void handleSpeakerAudio(UUID source, SpeakerPosition.Message position, float volume, ByteBuffer buffer) {
SpeakerManager.getSound(source).playAudio(reifyPosition(position), volume, buffer);
}

View File

@@ -11,7 +11,7 @@ import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.core.BlockPos;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ServerGamePacketListener;
import net.minecraft.network.protocol.common.ServerCommonPacketListener;
import net.minecraft.sounds.SoundEvent;
import javax.annotation.Nullable;
@@ -27,7 +27,7 @@ public interface ClientPlatformHelper extends dan200.computercraft.impl.client.C
* @param message The messsge to convert.
* @return The converted message.
*/
Packet<ServerGamePacketListener> createPacket(NetworkMessage<ServerNetworkContext> message);
Packet<ServerCommonPacketListener> createPacket(NetworkMessage<ServerNetworkContext> message);
/**
* Render a {@link BakedModel}, using any loader-specific hooks.

View File

@@ -4,25 +4,21 @@
package dan200.computercraft.client.pocket;
import dan200.computercraft.shared.computer.core.ComputerState;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.terminal.TerminalState;
import dan200.computercraft.shared.network.client.PocketComputerDataMessage;
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.minecraft.world.item.ItemStack;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* Maps {@link ServerComputer#getInstanceUUID()} to locals {@link PocketComputerData}.
* Maps {@link ServerComputer#getInstanceID()} to locals {@link PocketComputerData}.
* <p>
* This is populated by {@link PocketComputerDataMessage} and accessed when rendering pocket computers
*/
public final class ClientPocketComputers {
private static final Map<UUID, PocketComputerData> instances = new HashMap<>();
private static final Int2ObjectMap<PocketComputerData> instances = new Int2ObjectOpenHashMap<>();
private ClientPocketComputers() {
}
@@ -31,29 +27,25 @@ public final class ClientPocketComputers {
instances.clear();
}
public static void remove(UUID id) {
public static void remove(int id) {
instances.remove(id);
}
/**
* Set the state of a pocket computer.
* Get or create a pocket computer.
*
* @param instanceId The instance ID of the pocket computer.
* @param state The computer state of the pocket computer.
* @param lightColour The current colour of the modem light.
* @param terminalData The current terminal contents.
* @param instanceId The instance ID of the pocket computer.
* @param advanced Whether this computer has an advanced terminal.
* @return The pocket computer data.
*/
public static void setState(UUID instanceId, ComputerState state, int lightColour, @Nullable TerminalState terminalData) {
public static PocketComputerData get(int instanceId, boolean advanced) {
var computer = instances.get(instanceId);
if (computer == null) {
instances.put(instanceId, new PocketComputerData(state, lightColour, terminalData));
} else {
computer.setState(state, lightColour, terminalData);
}
if (computer == null) instances.put(instanceId, computer = new PocketComputerData(advanced));
return computer;
}
public static @Nullable PocketComputerData get(ItemStack stack) {
var id = PocketComputerItem.getInstanceID(stack);
return id == null ? null : instances.get(id);
public static PocketComputerData get(ItemStack stack) {
var family = stack.getItem() instanceof PocketComputerItem computer ? computer.getFamily() : ComputerFamily.NORMAL;
return get(PocketComputerItem.getInstanceID(stack), family != ComputerFamily.NORMAL);
}
}

View File

@@ -4,13 +4,13 @@
package dan200.computercraft.client.pocket;
import dan200.computercraft.core.terminal.Terminal;
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.config.Config;
import dan200.computercraft.shared.pocket.core.PocketServerComputer;
import javax.annotation.Nullable;
/**
* Clientside data about a pocket computer.
* <p>
@@ -21,22 +21,20 @@ import javax.annotation.Nullable;
* @see ClientPocketComputers The registry which holds pocket computers.
* @see PocketServerComputer The server-side pocket computer.
*/
public final class PocketComputerData {
private @Nullable NetworkedTerminal terminal;
private ComputerState state;
private int lightColour;
public class PocketComputerData {
private final NetworkedTerminal terminal;
private ComputerState state = ComputerState.OFF;
private int lightColour = -1;
PocketComputerData(ComputerState state, int lightColour, @Nullable TerminalState terminalData) {
this.state = state;
this.lightColour = lightColour;
if (terminalData != null) terminal = terminalData.create();
public PocketComputerData(boolean colour) {
terminal = new NetworkedTerminal(Config.pocketTermWidth, Config.pocketTermHeight, colour);
}
public int getLightState() {
return state != ComputerState.OFF ? lightColour : -1;
}
public @Nullable NetworkedTerminal getTerminal() {
public Terminal getTerminal() {
return terminal;
}
@@ -44,16 +42,12 @@ public final class PocketComputerData {
return state;
}
void setState(ComputerState state, int lightColour, @Nullable TerminalState terminalData) {
public void setState(ComputerState state, int lightColour) {
this.state = state;
this.lightColour = lightColour;
}
if (terminalData != null) {
if (terminal == null) {
terminal = terminalData.create();
} else {
terminalData.apply(terminal);
}
}
public void setTerminal(TerminalState state) {
state.apply(terminal);
}
}

View File

@@ -17,8 +17,6 @@ import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.ItemStack;
import java.util.Objects;
/**
* A base class for items which have map-like rendering when held in the hand.
*
@@ -37,7 +35,7 @@ public abstract class ItemMapLikeRenderer {
protected abstract void renderItem(PoseStack transform, MultiBufferSource render, ItemStack stack, int light);
public void renderItemFirstPerson(PoseStack transform, MultiBufferSource render, int lightTexture, InteractionHand hand, float pitch, float equipProgress, float swingProgress, ItemStack stack) {
Player player = Objects.requireNonNull(Minecraft.getInstance().player);
Player player = Minecraft.getInstance().player;
transform.pushPose();
if (hand == InteractionHand.MAIN_HAND && player.getOffhandItem().isEmpty()) {

View File

@@ -11,7 +11,6 @@ import dan200.computercraft.client.pocket.ClientPocketComputers;
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
import dan200.computercraft.core.util.Colour;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.config.Config;
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.world.item.ItemStack;
@@ -33,16 +32,10 @@ public final class PocketItemRenderer extends ItemMapLikeRenderer {
@Override
protected void renderItem(PoseStack transform, MultiBufferSource bufferSource, ItemStack stack, int light) {
var computer = ClientPocketComputers.get(stack);
var terminal = computer == null ? null : computer.getTerminal();
var terminal = computer.getTerminal();
int termWidth, termHeight;
if (terminal == null) {
termWidth = Config.pocketTermWidth;
termHeight = Config.pocketTermHeight;
} else {
termWidth = terminal.getWidth();
termHeight = terminal.getHeight();
}
var termWidth = terminal.getWidth();
var termHeight = terminal.getHeight();
var width = termWidth * FONT_WIDTH + MARGIN * 2;
var height = termHeight * FONT_HEIGHT + MARGIN * 2;
@@ -67,15 +60,14 @@ public final class PocketItemRenderer extends ItemMapLikeRenderer {
renderFrame(matrix, bufferSource, family, frameColour, light, width, height);
// Render the light
var lightColour = computer == null || computer.getLightState() == -1 ? Colour.BLACK.getHex() : computer.getLightState();
var lightColour = ClientPocketComputers.get(stack).getLightState();
if (lightColour == -1) lightColour = Colour.BLACK.getHex();
renderLight(transform, bufferSource, lightColour, width, height);
var quadEmitter = FixedWidthFontRenderer.toVertexConsumer(transform, bufferSource.getBuffer(RenderTypes.TERMINAL));
if (terminal == null) {
FixedWidthFontRenderer.drawEmptyTerminal(quadEmitter, 0, 0, width, height);
} else {
FixedWidthFontRenderer.drawTerminal(quadEmitter, MARGIN, MARGIN, terminal, MARGIN, MARGIN, MARGIN, MARGIN);
}
FixedWidthFontRenderer.drawTerminal(
FixedWidthFontRenderer.toVertexConsumer(transform, bufferSource.getBuffer(RenderTypes.TERMINAL)),
MARGIN, MARGIN, terminal, MARGIN, MARGIN, MARGIN, MARGIN
);
transform.popPose();
}

View File

@@ -125,10 +125,10 @@ public class SpriteRenderer {
}
public static float u(TextureAtlasSprite sprite, int x, int width) {
return sprite.getU((double) x / width * 16);
return sprite.getU((float) x / width);
}
public static float v(TextureAtlasSprite sprite, int y, int height) {
return sprite.getV((double) y / height * 16);
return sprite.getV((float) y / height);
}
}

View File

@@ -55,7 +55,7 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
// Render the label
var label = turtle.getLabel();
var hit = renderer.cameraHitResult;
if (label != null && hit != null && hit.getType() == HitResult.Type.BLOCK && turtle.getBlockPos().equals(((BlockHitResult) hit).getBlockPos())) {
if (label != null && hit.getType() == HitResult.Type.BLOCK && turtle.getBlockPos().equals(((BlockHitResult) hit).getBlockPos())) {
var mc = Minecraft.getInstance();
var font = this.font;

View File

@@ -9,6 +9,7 @@ import com.mojang.blaze3d.platform.MemoryTracker;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.*;
import com.mojang.math.Axis;
import dan200.computercraft.annotations.ForgeOverride;
import dan200.computercraft.client.FrameInfo;
import dan200.computercraft.client.integration.ShaderMod;
import dan200.computercraft.client.render.RenderTypes;
@@ -25,6 +26,7 @@ import dan200.computercraft.shared.util.DirectionUtil;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
import net.minecraft.world.phys.AABB;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL11;
@@ -58,9 +60,9 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl
@Override
public void render(MonitorBlockEntity monitor, float partialTicks, PoseStack transform, MultiBufferSource bufferSource, int lightmapCoord, int overlayLight) {
// Render from the origin monitor
var originTerminal = monitor.getOriginClientMonitor();
if (originTerminal == null) return;
var originTerminal = monitor.getClientMonitor();
if (originTerminal == null) return;
var origin = originTerminal.getOrigin();
var renderState = originTerminal.getRenderState(MonitorRenderState::new);
var monitorPos = monitor.getBlockPos();
@@ -255,6 +257,11 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl
return Config.monitorDistance;
}
@ForgeOverride
public AABB getRenderBoundingBox(MonitorBlockEntity monitor) {
return monitor.getRenderBoundingBox();
}
/**
* Determine if any monitors were rendered this frame.
*

View File

@@ -5,7 +5,6 @@
package dan200.computercraft.client.sound;
import com.mojang.blaze3d.audio.Channel;
import dan200.computercraft.shared.peripheral.speaker.EncodedAudio;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPeripheral;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
import net.minecraft.client.sounds.AudioStream;
@@ -37,7 +36,7 @@ class DfpwmStream implements AudioStream {
/**
* The {@link Channel} which this sound is playing on.
*
* @see SpeakerInstance#playAudio(SpeakerPosition, float, EncodedAudio)
* @see SpeakerInstance#playAudio(SpeakerPosition, float, ByteBuffer)
*/
@Nullable
Channel channel;
@@ -45,23 +44,21 @@ class DfpwmStream implements AudioStream {
/**
* The underlying {@link SoundEngine} executor.
*
* @see SpeakerInstance#playAudio(SpeakerPosition, float, EncodedAudio)
* @see SpeakerInstance#playAudio(SpeakerPosition, float, ByteBuffer)
* @see SoundEngine#executor
*/
@Nullable
Executor executor;
private int charge = 0; // q
private int strength = 0; // s
private int lowPassCharge;
private boolean previousBit = false;
DfpwmStream() {
}
void push(EncodedAudio audio) {
var charge = audio.charge();
var strength = audio.strength();
var previousBit = audio.previousBit();
var input = audio.audio();
void push(ByteBuffer input) {
var readable = input.remaining();
var output = ByteBuffer.allocate(readable * 8).order(ByteOrder.nativeOrder());

View File

@@ -6,12 +6,12 @@ package dan200.computercraft.client.sound;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.core.util.Nullability;
import dan200.computercraft.shared.peripheral.speaker.EncodedAudio;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
import net.minecraft.client.Minecraft;
import net.minecraft.resources.ResourceLocation;
import javax.annotation.Nullable;
import java.nio.ByteBuffer;
/**
* An instance of a speaker, which is either playing a {@link DfpwmStream} stream or a normal sound.
@@ -25,7 +25,7 @@ public class SpeakerInstance {
SpeakerInstance() {
}
private void pushAudio(EncodedAudio buffer) {
private void pushAudio(ByteBuffer buffer) {
var sound = this.sound;
var stream = currentStream;
@@ -43,7 +43,7 @@ public class SpeakerInstance {
}
}
public void playAudio(SpeakerPosition position, float volume, EncodedAudio buffer) {
public void playAudio(SpeakerPosition position, float volume, ByteBuffer buffer) {
pushAudio(buffer);
var soundManager = Minecraft.getInstance().getSoundManager();

View File

@@ -10,6 +10,7 @@ import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.shared.turtle.upgrades.TurtleModem;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.Nullable;
@@ -40,7 +41,7 @@ public class TurtleModemModeller implements TurtleUpgradeModeller<TurtleModem> {
}
@Override
public TransformedModel getModel(TurtleModem upgrade, @Nullable ITurtleAccess turtle, TurtleSide side) {
public TransformedModel getModel(TurtleModem upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, CompoundTag data) {
var active = false;
if (turtle != null) {
var turtleNBT = turtle.getUpgradeNBTData(side);

View File

@@ -10,15 +10,13 @@ import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.impl.PlatformHelper;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import dan200.computercraft.impl.RegistryHelper;
import dan200.computercraft.impl.TurtleUpgrades;
import dan200.computercraft.impl.UpgradeManager;
import net.minecraft.client.Minecraft;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
import java.util.WeakHashMap;
@@ -29,12 +27,10 @@ import java.util.stream.Stream;
* A registry of {@link TurtleUpgradeModeller}s.
*/
public final class TurtleUpgradeModellers {
private static final Logger LOG = LoggerFactory.getLogger(TurtleUpgradeModellers.class);
private static final TurtleUpgradeModeller<ITurtleUpgrade> NULL_TURTLE_MODELLER = (upgrade, turtle, side) ->
private static final TurtleUpgradeModeller<ITurtleUpgrade> NULL_TURTLE_MODELLER = (upgrade, turtle, side, data) ->
new TransformedModel(Minecraft.getInstance().getModelManager().getMissingModel(), Transformation.identity());
private static final Map<TurtleUpgradeSerialiser<?>, TurtleUpgradeModeller<?>> turtleModels = new ConcurrentHashMap<>();
private static final Map<UpgradeSerialiser<? extends ITurtleUpgrade>, TurtleUpgradeModeller<?>> turtleModels = new ConcurrentHashMap<>();
private static volatile boolean fetchedModels;
/**
@@ -48,15 +44,12 @@ public final class TurtleUpgradeModellers {
private TurtleUpgradeModellers() {
}
public static <T extends ITurtleUpgrade> void register(TurtleUpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller) {
public static <T extends ITurtleUpgrade> void register(UpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller) {
if (fetchedModels) {
// TODO(1.20.4): Replace with an error.
LOG.warn(
"Turtle upgrade serialiser {} was registered too late, its models may not be loaded correctly. If you are " +
"the mod author, you may be using a deprecated API - see https://github.com/cc-tweaked/CC-Tweaked/pull/1684 " +
"for further information.",
PlatformHelper.get().getRegistryKey(TurtleUpgradeSerialiser.registryId(), serialiser)
);
throw new IllegalStateException(String.format(
"Turtle upgrade serialiser %s must be registered before models are baked.",
RegistryHelper.getKeyOrThrow(RegistryHelper.getRegistry(ITurtleUpgrade.serialiserRegistryKey()), serialiser)
));
}
if (turtleModels.putIfAbsent(serialiser, modeller) != null) {
@@ -67,13 +60,13 @@ public final class TurtleUpgradeModellers {
public static TransformedModel getModel(ITurtleUpgrade upgrade, ITurtleAccess access, TurtleSide side) {
@SuppressWarnings("unchecked")
var modeller = (TurtleUpgradeModeller<ITurtleUpgrade>) modelCache.computeIfAbsent(upgrade, TurtleUpgradeModellers::getModeller);
return modeller.getModel(upgrade, access, side);
return modeller.getModel(upgrade, access, side, access.getUpgradeNBTData(side));
}
public static TransformedModel getModel(ITurtleUpgrade upgrade, CompoundTag data, TurtleSide side) {
@SuppressWarnings("unchecked")
var modeller = (TurtleUpgradeModeller<ITurtleUpgrade>) modelCache.computeIfAbsent(upgrade, TurtleUpgradeModellers::getModeller);
return modeller.getModel(upgrade, data, side);
return modeller.getModel(upgrade, null, side, data);
}
private static TurtleUpgradeModeller<?> getModeller(ITurtleUpgrade upgradeA) {

View File

@@ -1,12 +0,0 @@
{
"parent": "computercraft:block/turtle_base",
"textures": {
"back": "computercraft:block/turtle_advanced_back",
"backpack": "computercraft:block/turtle_advanced_backpack",
"bottom": "computercraft:block/turtle_advanced_bottom",
"front": "computercraft:block/turtle_advanced_front",
"left": "computercraft:block/turtle_advanced_left",
"right": "computercraft:block/turtle_advanced_right",
"top": "computercraft:block/turtle_advanced_top"
}
}

View File

@@ -1,12 +0,0 @@
{
"parent": "computercraft:block/turtle_base",
"textures": {
"back": "computercraft:block/turtle_normal_back",
"backpack": "computercraft:block/turtle_normal_backpack",
"bottom": "computercraft:block/turtle_normal_bottom",
"front": "computercraft:block/turtle_normal_front",
"left": "computercraft:block/turtle_normal_left",
"right": "computercraft:block/turtle_normal_right",
"top": "computercraft:block/turtle_normal_top"
}
}

View File

@@ -38,9 +38,6 @@ import static net.minecraft.data.models.model.TextureMapping.getBlockTexture;
class BlockModelProvider {
private static final TextureSlot CURSOR = TextureSlot.create("cursor");
private static final TextureSlot LEFT = TextureSlot.create("left");
private static final TextureSlot RIGHT = TextureSlot.create("right");
private static final TextureSlot BACKPACK = TextureSlot.create("backpack");
private static final ModelTemplate COMPUTER_ON = new ModelTemplate(
Optional.of(new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/computer_on")),
@@ -61,7 +58,7 @@ class BlockModelProvider {
private static final ModelTemplate TURTLE = new ModelTemplate(
Optional.of(new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_base")),
Optional.empty(),
TextureSlot.FRONT, TextureSlot.BACK, TextureSlot.TOP, TextureSlot.BOTTOM, LEFT, RIGHT, BACKPACK
TextureSlot.TEXTURE
);
private static final ModelTemplate TURTLE_UPGRADE_LEFT = new ModelTemplate(
Optional.of(new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_upgrade_base_left")),
@@ -170,16 +167,7 @@ class BlockModelProvider {
}
private static void registerTurtle(BlockModelGenerators generators, TurtleBlock block) {
var model = TURTLE.create(block, new TextureMapping()
.put(TextureSlot.FRONT, getBlockTexture(block, "_front"))
.put(TextureSlot.BACK, getBlockTexture(block, "_back"))
.put(TextureSlot.TOP, getBlockTexture(block, "_top"))
.put(TextureSlot.BOTTOM, getBlockTexture(block, "_bottom"))
.put(LEFT, getBlockTexture(block, "_left"))
.put(RIGHT, getBlockTexture(block, "_right"))
.put(BACKPACK, getBlockTexture(block, "_backpack")),
generators.modelOutput
);
var model = TURTLE.create(block, TextureMapping.defaultTexture(block), generators.modelOutput);
generators.blockStateOutput.accept(
MultiVariantGenerator.multiVariant(block, Variant.variant().with(VariantProperties.MODEL, model))
.with(createHorizontalFacingDispatch())

View File

@@ -7,13 +7,16 @@ package dan200.computercraft.data;
import com.mojang.serialization.Codec;
import net.minecraft.data.DataProvider;
import net.minecraft.data.PackOutput;
import net.minecraft.data.loot.LootTableProvider.SubProviderEntry;
import net.minecraft.data.models.BlockModelGenerators;
import net.minecraft.data.models.ItemModelGenerators;
import net.minecraft.data.tags.TagsProvider;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.PackType;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import java.util.Set;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
@@ -33,9 +36,9 @@ public final class DataProviders {
var blockTags = generator.blockTags(TagProvider::blockTags);
generator.itemTags(TagProvider::itemTags, blockTags);
generator.add(out -> new net.minecraft.data.loot.LootTableProvider(out, Set.of(), LootTableProvider.getTables()));
generator.lootTable(LootTableProvider.getTables());
generator.add(out -> new ModelProvider(out, BlockModelProvider::addBlockModels, ItemModelProvider::addItemModels));
generator.models(BlockModelProvider::addBlockModels, ItemModelProvider::addItemModels);
generator.add(out -> new LanguageProvider(out, turtleUpgrades, pocketUpgrades));
@@ -54,8 +57,12 @@ public final class DataProviders {
<T> void addFromCodec(String name, PackType type, String directory, Codec<T> codec, Consumer<BiConsumer<ResourceLocation, T>> output);
void lootTable(List<SubProviderEntry> tables);
TagsProvider<Block> blockTags(Consumer<TagProvider.TagConsumer<Block>> tags);
TagsProvider<Item> itemTags(Consumer<TagProvider.ItemTagConsumer> tags, TagsProvider<Block> blocks);
void models(Consumer<BlockModelGenerators> blocks, Consumer<ItemModelGenerators> items);
}
}

View File

@@ -13,18 +13,16 @@ import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.core.metrics.Metric;
import dan200.computercraft.core.metrics.Metrics;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.command.arguments.ComputerSelector;
import dan200.computercraft.shared.computer.metrics.basic.Aggregate;
import dan200.computercraft.shared.computer.metrics.basic.AggregatedMetric;
import dan200.computercraft.shared.config.ConfigFile;
import dan200.computercraft.shared.config.ConfigSpec;
import dan200.computercraft.shared.platform.RegistryWrappers;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.data.CachedOutput;
import net.minecraft.data.DataProvider;
import net.minecraft.data.PackOutput;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import java.util.HashMap;
import java.util.Map;
@@ -167,19 +165,10 @@ public final class LanguageProvider implements DataProvider {
add("commands.computercraft.generic.exception", "Unhandled exception (%s)");
add("commands.computercraft.generic.additional_rows", "%d additional rows…");
// Argument types
add("argument.computercraft.computer.instance", "Unique instance ID");
add("argument.computercraft.computer.id", "Computer ID");
add("argument.computercraft.computer.label", "Computer label");
add("argument.computercraft.computer.distance", "Distance to entity");
add("argument.computercraft.computer.family", "Computer family");
// Exceptions
add("argument.computercraft.computer.no_matching", "No computers matching '%s'");
add("argument.computercraft.computer.many_matching", "Multiple computers matching '%s' (instances %s)");
add("argument.computercraft.tracking_field.no_field", "Unknown field '%s'");
add("argument.computercraft.argument_expected", "Argument expected");
add("argument.computercraft.unknown_computer_family", "Unknown computer family '%s'");
// Metrics
add(Metrics.COMPUTER_TASKS, "Tasks");
@@ -282,18 +271,17 @@ public final class LanguageProvider implements DataProvider {
private Stream<String> getExpectedKeys() {
return Stream.of(
RegistryWrappers.BLOCKS.stream()
.filter(x -> RegistryWrappers.BLOCKS.getKey(x).getNamespace().equals(ComputerCraftAPI.MOD_ID))
.map(Block::getDescriptionId),
RegistryWrappers.ITEMS.stream()
.filter(x -> RegistryWrappers.ITEMS.getKey(x).getNamespace().equals(ComputerCraftAPI.MOD_ID))
.map(Item::getDescriptionId),
BuiltInRegistries.BLOCK.holders()
.filter(x -> x.key().location().getNamespace().equals(ComputerCraftAPI.MOD_ID))
.map(x -> x.value().getDescriptionId()),
BuiltInRegistries.ITEM.holders()
.filter(x -> x.key().location().getNamespace().equals(ComputerCraftAPI.MOD_ID))
.map(x -> x.value().getDescriptionId()),
turtleUpgrades.getGeneratedUpgrades().stream().map(UpgradeBase::getUnlocalisedAdjective),
pocketUpgrades.getGeneratedUpgrades().stream().map(UpgradeBase::getUnlocalisedAdjective),
Metric.metrics().values().stream().map(x -> AggregatedMetric.TRANSLATION_PREFIX + x.name() + ".name"),
ConfigSpec.serverSpec.entries().map(ConfigFile.Entry::translationKey),
ConfigSpec.clientSpec.entries().map(ConfigFile.Entry::translationKey),
ComputerSelector.options().values().stream().map(ComputerSelector.Option::translationKey)
ConfigSpec.clientSpec.entries().map(ConfigFile.Entry::translationKey)
).flatMap(x -> x);
}

View File

@@ -5,8 +5,9 @@
package dan200.computercraft.data;
import com.google.gson.JsonElement;
import dan200.computercraft.shared.platform.RegistryWrappers;
import dan200.computercraft.impl.RegistryHelper;
import net.minecraft.Util;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.data.CachedOutput;
import net.minecraft.data.DataProvider;
import net.minecraft.data.PackOutput;
@@ -67,7 +68,7 @@ public class ModelProvider implements DataProvider {
blocks.accept(new BlockModelGenerators(addBlockState, addModel, explicitItems::add));
items.accept(new ItemModelGenerators(addModel));
for (var block : RegistryWrappers.BLOCKS) {
for (var block : BuiltInRegistries.BLOCK) {
if (!blockStates.containsKey(block)) continue;
var item = Item.BY_BLOCK.get(block);
@@ -80,7 +81,7 @@ public class ModelProvider implements DataProvider {
}
List<CompletableFuture<?>> futures = new ArrayList<>();
saveCollection(output, futures, blockStates, x -> blockStatePath.json(RegistryWrappers.BLOCKS.getKey(x)));
saveCollection(output, futures, blockStates, x -> blockStatePath.json(RegistryHelper.getKeyOrThrow(BuiltInRegistries.BLOCK, x)));
saveCollection(output, futures, models, modelPath::json);
return Util.sequenceFailFast(futures);
}

View File

@@ -5,8 +5,9 @@
package dan200.computercraft.data;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.pocket.PocketUpgradeDataProvider;
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import net.minecraft.data.PackOutput;
import net.minecraft.resources.ResourceLocation;
@@ -21,7 +22,7 @@ class PocketUpgradeProvider extends PocketUpgradeDataProvider {
}
@Override
protected void addUpgrades(Consumer<Upgrade<PocketUpgradeSerialiser<?>>> addUpgrade) {
protected void addUpgrades(Consumer<Upgrade<UpgradeSerialiser<? extends IPocketUpgrade>>> addUpgrade) {
addUpgrade.accept(simpleWithCustomItem(id("speaker"), PocketUpgradeSerialisers.SPEAKER.get(), Items.SPEAKER.get()));
simpleWithCustomItem(id("wireless_modem_normal"), PocketUpgradeSerialisers.WIRELESS_MODEM_NORMAL.get(), Items.WIRELESS_MODEM_NORMAL.get()).add(addUpgrade);
simpleWithCustomItem(id("wireless_modem_advanced"), PocketUpgradeSerialisers.WIRELESS_MODEM_ADVANCED.get(), Items.WIRELESS_MODEM_ADVANCED.get()).add(addUpgrade);

View File

@@ -5,34 +5,56 @@
package dan200.computercraft.data;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.mojang.authlib.GameProfile;
import com.mojang.serialization.JsonOps;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.pocket.PocketUpgradeDataProvider;
import dan200.computercraft.api.turtle.TurtleUpgradeDataProvider;
import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.core.util.Colour;
import dan200.computercraft.data.recipe.ShapedSpecBuilder;
import dan200.computercraft.data.recipe.ShapelessSpecBuilder;
import dan200.computercraft.impl.RegistryHelper;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.common.IColouredItem;
import dan200.computercraft.shared.common.ClearColourRecipe;
import dan200.computercraft.shared.common.ColourableRecipe;
import dan200.computercraft.shared.computer.recipe.ComputerUpgradeRecipe;
import dan200.computercraft.shared.media.items.DiskItem;
import dan200.computercraft.shared.media.recipes.DiskRecipe;
import dan200.computercraft.shared.media.recipes.PrintoutRecipe;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.platform.RecipeIngredients;
import dan200.computercraft.shared.platform.RegistryWrappers;
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
import dan200.computercraft.shared.pocket.recipes.PocketComputerUpgradeRecipe;
import dan200.computercraft.shared.recipe.CustomShapelessRecipe;
import dan200.computercraft.shared.recipe.ImpostorShapedRecipe;
import dan200.computercraft.shared.recipe.ImpostorShapelessRecipe;
import dan200.computercraft.shared.turtle.items.TurtleItem;
import dan200.computercraft.shared.turtle.recipes.TurtleOverlayRecipe;
import dan200.computercraft.shared.turtle.recipes.TurtleRecipe;
import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe;
import dan200.computercraft.shared.util.ColourUtils;
import net.minecraft.Util;
import net.minecraft.advancements.Criterion;
import net.minecraft.advancements.critereon.InventoryChangeTrigger;
import net.minecraft.advancements.critereon.ItemPredicate;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.data.PackOutput;
import net.minecraft.data.recipes.*;
import net.minecraft.data.recipes.RecipeCategory;
import net.minecraft.data.recipes.RecipeOutput;
import net.minecraft.data.recipes.ShapedRecipeBuilder;
import net.minecraft.data.recipes.ShapelessRecipeBuilder;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.item.*;
import net.minecraft.world.item.crafting.CraftingBookCategory;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.crafting.ShapedRecipe;
import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer;
import net.minecraft.world.item.crafting.Recipe;
import net.minecraft.world.level.ItemLike;
import net.minecraft.world.level.block.Blocks;
@@ -43,7 +65,7 @@ import java.util.function.Consumer;
import static dan200.computercraft.api.ComputerCraftTags.Items.COMPUTER;
import static dan200.computercraft.api.ComputerCraftTags.Items.WIRED_MODEM;
class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
private final RecipeIngredients ingredients = PlatformHelper.get().getRecipeIngredients();
private final TurtleUpgradeDataProvider turtleUpgrades;
private final PocketUpgradeDataProvider pocketUpgrades;
@@ -55,40 +77,37 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
}
@Override
public void buildRecipes(Consumer<FinishedRecipe> add) {
public void buildRecipes(RecipeOutput add) {
basicRecipes(add);
diskColours(add);
pocketUpgrades(add);
turtleUpgrades(add);
turtleOverlays(add);
addSpecial(add, ModRegistry.RecipeSerializers.PRINTOUT.get());
addSpecial(add, ModRegistry.RecipeSerializers.DISK.get());
addSpecial(add, ModRegistry.RecipeSerializers.DYEABLE_ITEM.get());
addSpecial(add, ModRegistry.RecipeSerializers.DYEABLE_ITEM_CLEAR.get());
addSpecial(add, ModRegistry.RecipeSerializers.TURTLE_UPGRADE.get());
addSpecial(add, ModRegistry.RecipeSerializers.POCKET_COMPUTER_UPGRADE.get());
addSpecial(add, new PrintoutRecipe(CraftingBookCategory.MISC));
addSpecial(add, new DiskRecipe(CraftingBookCategory.MISC));
addSpecial(add, new ColourableRecipe(CraftingBookCategory.MISC));
addSpecial(add, new ClearColourRecipe(CraftingBookCategory.MISC));
addSpecial(add, new TurtleUpgradeRecipe(CraftingBookCategory.MISC));
addSpecial(add, new PocketComputerUpgradeRecipe(CraftingBookCategory.MISC));
}
/**
* Register a crafting recipe for a disk of every dye colour.
*
* @param add The callback to add recipes.
* @param output The callback to add recipes.
*/
private void diskColours(Consumer<FinishedRecipe> add) {
private void diskColours(RecipeOutput output) {
for (var colour : Colour.VALUES) {
ShapelessRecipeBuilder
.shapeless(RecipeCategory.REDSTONE, ModRegistry.Items.DISK.get())
ShapelessSpecBuilder
.shapeless(RecipeCategory.REDSTONE, DiskItem.createFromIDAndColour(-1, null, colour.getHex()))
.requires(ingredients.redstone())
.requires(Items.PAPER)
.requires(DyeItem.byColor(ofColour(colour)))
.group("computercraft:disk")
.unlockedBy("has_drive", inventoryChange(ModRegistry.Blocks.DISK_DRIVE.get()))
.save(
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.IMPOSTOR_SHAPELESS.get(), add)
.withResultTag(x -> x.putInt(IColouredItem.NBT_COLOUR, colour.getHex())),
new ResourceLocation(ComputerCraftAPI.MOD_ID, "disk_" + (colour.ordinal() + 1))
);
.build(ImpostorShapelessRecipe::new)
.save(output, new ResourceLocation(ComputerCraftAPI.MOD_ID, "disk_" + (colour.ordinal() + 1)));
}
}
@@ -101,23 +120,22 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
*
* @param add The callback to add recipes.
*/
private void turtleUpgrades(Consumer<FinishedRecipe> add) {
private void turtleUpgrades(RecipeOutput add) {
for (var turtleItem : turtleItems()) {
var base = turtleItem.create(-1, null, -1, null, null, 0, null);
var name = RegistryWrappers.ITEMS.getKey(turtleItem);
var name = RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, turtleItem);
for (var upgrade : turtleUpgrades.getGeneratedUpgrades()) {
var result = turtleItem.create(-1, null, -1, null, UpgradeData.ofDefault(upgrade), -1, null);
ShapedRecipeBuilder
.shaped(RecipeCategory.REDSTONE, result.getItem())
ShapedSpecBuilder
.shaped(RecipeCategory.REDSTONE, turtleItem.create(-1, null, -1, null, UpgradeData.ofDefault(upgrade), -1, null))
.group(name.toString())
.pattern("#T")
.define('T', base.getItem())
.define('#', upgrade.getCraftingItem().getItem())
.unlockedBy("has_items",
inventoryChange(base.getItem(), upgrade.getCraftingItem().getItem()))
.unlockedBy("has_items", inventoryChange(base.getItem(), upgrade.getCraftingItem().getItem()))
.build(ImpostorShapedRecipe::new)
.save(
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.IMPOSTOR_SHAPED.get(), add).withResultTag(result.getTag()),
add,
name.withSuffix(String.format("/%s/%s", upgrade.getUpgradeID().getNamespace(), upgrade.getUpgradeID().getPath()))
);
}
@@ -133,31 +151,30 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
*
* @param add The callback to add recipes.
*/
private void pocketUpgrades(Consumer<FinishedRecipe> add) {
private void pocketUpgrades(RecipeOutput add) {
for (var pocket : pocketComputerItems()) {
var base = pocket.create(-1, null, -1, null);
var name = RegistryWrappers.ITEMS.getKey(pocket).withPath(x -> x.replace("pocket_computer_", "pocket_"));
var name = RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, pocket).withPath(x -> x.replace("pocket_computer_", "pocket_"));
for (var upgrade : pocketUpgrades.getGeneratedUpgrades()) {
var result = pocket.create(-1, null, -1, UpgradeData.ofDefault(upgrade));
ShapedRecipeBuilder
.shaped(RecipeCategory.REDSTONE, result.getItem())
ShapedSpecBuilder
.shaped(RecipeCategory.REDSTONE, pocket.create(-1, null, -1, UpgradeData.ofDefault(upgrade)))
.group(name.toString())
.pattern("#")
.pattern("P")
.define('P', base.getItem())
.define('#', upgrade.getCraftingItem().getItem())
.unlockedBy("has_items",
inventoryChange(base.getItem(), upgrade.getCraftingItem().getItem()))
.unlockedBy("has_items", inventoryChange(base.getItem(), upgrade.getCraftingItem().getItem()))
.build(ImpostorShapedRecipe::new)
.save(
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.IMPOSTOR_SHAPED.get(), add).withResultTag(result.getTag()),
add,
name.withSuffix(String.format("/%s/%s", upgrade.getUpgradeID().getNamespace(), upgrade.getUpgradeID().getPath()))
);
}
}
}
private void turtleOverlays(Consumer<FinishedRecipe> add) {
private void turtleOverlays(RecipeOutput add) {
turtleOverlay(add, "turtle_trans_overlay", x -> x
.unlockedBy("has_dye", inventoryChange(itemPredicate(ingredients.dye())))
.requires(ColourUtils.getDyeTag(DyeColor.LIGHT_BLUE))
@@ -178,28 +195,24 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
);
}
private void turtleOverlay(Consumer<FinishedRecipe> add, String overlay, Consumer<ShapelessRecipeBuilder> build) {
private void turtleOverlay(RecipeOutput add, String overlay, Consumer<ShapelessSpecBuilder> build) {
for (var turtleItem : turtleItems()) {
var base = turtleItem.create(-1, null, -1, null, null, 0, null);
var name = RegistryWrappers.ITEMS.getKey(turtleItem);
var name = RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, turtleItem);
var builder = ShapelessRecipeBuilder.shapeless(RecipeCategory.REDSTONE, base.getItem())
var builder = ShapelessSpecBuilder.shapeless(RecipeCategory.REDSTONE, base)
.group(name.withSuffix("_overlay").toString())
.unlockedBy("has_turtle", inventoryChange(base.getItem()));
build.accept(builder);
builder
.requires(base.getItem())
.save(
RecipeWrapper
.wrap(ModRegistry.RecipeSerializers.TURTLE_OVERLAY.get(), add)
.withExtraData(x -> x.addProperty("overlay", new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/" + overlay).toString())),
name.withSuffix("_overlays/" + overlay)
);
.build(s -> new TurtleOverlayRecipe(s, new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/" + overlay)))
.save(add, name.withSuffix("_overlays/" + overlay));
}
}
private void basicRecipes(Consumer<FinishedRecipe> add) {
private void basicRecipes(RecipeOutput add) {
ShapedRecipeBuilder
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.CABLE.get(), 6)
.pattern(" # ")
@@ -233,7 +246,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
.unlockedBy("has_components", inventoryChange(itemPredicate(ingredients.redstone()), itemPredicate(ingredients.goldIngot())))
.save(add);
ShapedRecipeBuilder
ShapedSpecBuilder
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.COMPUTER_ADVANCED.get())
.pattern("###")
.pattern("#C#")
@@ -241,10 +254,8 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
.define('#', ingredients.goldIngot())
.define('C', ModRegistry.Items.COMPUTER_NORMAL.get())
.unlockedBy("has_components", inventoryChange(itemPredicate(ModRegistry.Items.COMPUTER_NORMAL.get()), itemPredicate(ingredients.goldIngot())))
.save(
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get(), add),
new ResourceLocation(ComputerCraftAPI.MOD_ID, "computer_advanced_upgrade")
);
.build(ComputerUpgradeRecipe::new)
.save(add, new ResourceLocation(ComputerCraftAPI.MOD_ID, "computer_advanced_upgrade"));
ShapedRecipeBuilder
.shaped(RecipeCategory.REDSTONE, ModRegistry.Blocks.COMPUTER_COMMAND.get())
@@ -257,7 +268,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
.unlockedBy("has_components", inventoryChange(Blocks.COMMAND_BLOCK))
.save(add);
ShapedRecipeBuilder
ShapedSpecBuilder
.shaped(RecipeCategory.REDSTONE, ModRegistry.Blocks.TURTLE_NORMAL.get())
.pattern("###")
.pattern("#C#")
@@ -266,9 +277,10 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
.define('C', ModRegistry.Items.COMPUTER_NORMAL.get())
.define('I', ingredients.woodenChest())
.unlockedBy("has_computer", inventoryChange(ModRegistry.Items.COMPUTER_NORMAL.get()))
.save(RecipeWrapper.wrap(ModRegistry.RecipeSerializers.TURTLE.get(), add));
.buildOrThrow(TurtleRecipe::of)
.save(add);
ShapedRecipeBuilder
ShapedSpecBuilder
.shaped(RecipeCategory.REDSTONE, ModRegistry.Blocks.TURTLE_ADVANCED.get())
.pattern("###")
.pattern("#C#")
@@ -277,9 +289,10 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
.define('C', ModRegistry.Items.COMPUTER_ADVANCED.get())
.define('I', ingredients.woodenChest())
.unlockedBy("has_computer", inventoryChange(ModRegistry.Items.COMPUTER_NORMAL.get()))
.save(RecipeWrapper.wrap(ModRegistry.RecipeSerializers.TURTLE.get(), add));
.buildOrThrow(TurtleRecipe::of)
.save(add);
ShapedRecipeBuilder
ShapedSpecBuilder
.shaped(RecipeCategory.REDSTONE, ModRegistry.Blocks.TURTLE_ADVANCED.get())
.pattern("###")
.pattern("#C#")
@@ -288,10 +301,8 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
.define('C', ModRegistry.Items.TURTLE_NORMAL.get())
.define('B', ingredients.goldBlock())
.unlockedBy("has_components", inventoryChange(itemPredicate(ModRegistry.Items.TURTLE_NORMAL.get()), itemPredicate(ingredients.goldIngot())))
.save(
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get(), add),
new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_advanced_upgrade")
);
.build(ComputerUpgradeRecipe::new)
.save(add, new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_advanced_upgrade"));
ShapedRecipeBuilder
.shaped(RecipeCategory.REDSTONE, ModRegistry.Blocks.DISK_DRIVE.get())
@@ -347,7 +358,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
.unlockedBy("has_apple", inventoryChange(Items.GOLDEN_APPLE))
.save(add);
ShapedRecipeBuilder
ShapedSpecBuilder
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get())
.pattern("###")
.pattern("#C#")
@@ -355,10 +366,8 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
.define('#', ingredients.goldIngot())
.define('C', ModRegistry.Items.POCKET_COMPUTER_NORMAL.get())
.unlockedBy("has_components", inventoryChange(itemPredicate(ModRegistry.Items.POCKET_COMPUTER_NORMAL.get()), itemPredicate(ingredients.goldIngot())))
.save(
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get(), add),
new ResourceLocation(ComputerCraftAPI.MOD_ID, "pocket_computer_advanced_upgrade")
);
.build(ComputerUpgradeRecipe::new)
.save(add, new ResourceLocation(ComputerCraftAPI.MOD_ID, "pocket_computer_advanced_upgrade"));
ShapedRecipeBuilder
.shaped(RecipeCategory.REDSTONE, ModRegistry.Blocks.PRINTER.get())
@@ -425,57 +434,53 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
.unlockedBy("has_wireless", inventoryChange(ModRegistry.Blocks.WIRELESS_MODEM_NORMAL.get()))
.save(add);
ShapelessRecipeBuilder
.shapeless(RecipeCategory.DECORATIONS, Items.PLAYER_HEAD)
ShapelessSpecBuilder
.shapeless(RecipeCategory.DECORATIONS, playerHead("Cloudhunter", "6d074736-b1e9-4378-a99b-bd8777821c9c"))
.requires(ingredients.head())
.requires(ModRegistry.Items.MONITOR_NORMAL.get())
.unlockedBy("has_monitor", inventoryChange(ModRegistry.Items.MONITOR_NORMAL.get()))
.save(
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.SHAPELESS.get(), add)
.withResultTag(playerHead("Cloudhunter", "6d074736-b1e9-4378-a99b-bd8777821c9c")),
new ResourceLocation(ComputerCraftAPI.MOD_ID, "skull_cloudy")
);
.build(CustomShapelessRecipe::new)
.save(add, new ResourceLocation(ComputerCraftAPI.MOD_ID, "skull_cloudy"));
ShapelessRecipeBuilder
.shapeless(RecipeCategory.DECORATIONS, Items.PLAYER_HEAD)
ShapelessSpecBuilder
.shapeless(RecipeCategory.DECORATIONS, playerHead("dan200", "f3c8d69b-0776-4512-8434-d1b2165909eb"))
.requires(ingredients.head())
.requires(ModRegistry.Items.COMPUTER_ADVANCED.get())
.unlockedBy("has_computer", inventoryChange(ModRegistry.Items.COMPUTER_ADVANCED.get()))
.save(
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.SHAPELESS.get(), add)
.withResultTag(playerHead("dan200", "f3c8d69b-0776-4512-8434-d1b2165909eb")),
new ResourceLocation(ComputerCraftAPI.MOD_ID, "skull_dan200")
);
.build(CustomShapelessRecipe::new)
.save(add, new ResourceLocation(ComputerCraftAPI.MOD_ID, "skull_dan200"));
ShapelessRecipeBuilder
ShapelessSpecBuilder
.shapeless(RecipeCategory.REDSTONE, ModRegistry.Items.PRINTED_PAGES.get())
.requires(ModRegistry.Items.PRINTED_PAGE.get(), 2)
.requires(ingredients.string())
.unlockedBy("has_printer", inventoryChange(ModRegistry.Blocks.PRINTER.get()))
.save(RecipeWrapper.wrap(ModRegistry.RecipeSerializers.IMPOSTOR_SHAPELESS.get(), add));
.build(ImpostorShapelessRecipe::new)
.save(add);
ShapelessRecipeBuilder
ShapelessSpecBuilder
.shapeless(RecipeCategory.REDSTONE, ModRegistry.Items.PRINTED_BOOK.get())
.requires(ingredients.leather())
.requires(ModRegistry.Items.PRINTED_PAGE.get(), 1)
.requires(ingredients.string())
.unlockedBy("has_printer", inventoryChange(ModRegistry.Blocks.PRINTER.get()))
.save(RecipeWrapper.wrap(ModRegistry.RecipeSerializers.IMPOSTOR_SHAPELESS.get(), add));
.build(ImpostorShapelessRecipe::new)
.save(add);
}
private static DyeColor ofColour(Colour colour) {
return DyeColor.byId(15 - colour.ordinal());
}
private static InventoryChangeTrigger.TriggerInstance inventoryChange(TagKey<Item> stack) {
private static Criterion<InventoryChangeTrigger.TriggerInstance> inventoryChange(TagKey<Item> stack) {
return InventoryChangeTrigger.TriggerInstance.hasItems(itemPredicate(stack));
}
private static InventoryChangeTrigger.TriggerInstance inventoryChange(ItemLike... stack) {
private static Criterion<InventoryChangeTrigger.TriggerInstance> inventoryChange(ItemLike... stack) {
return InventoryChangeTrigger.TriggerInstance.hasItems(stack);
}
private static InventoryChangeTrigger.TriggerInstance inventoryChange(ItemPredicate... items) {
private static Criterion<InventoryChangeTrigger.TriggerInstance> inventoryChange(ItemPredicate... items) {
return InventoryChangeTrigger.TriggerInstance.hasItems(items);
}
@@ -488,11 +493,12 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
}
private static ItemPredicate itemPredicate(Ingredient ingredient) {
var json = ingredient.toJson();
var json = Util.getOrThrow(Ingredient.CODEC_NONEMPTY.encodeStart(JsonOps.INSTANCE, ingredient), JsonParseException::new);
if (!(json instanceof JsonObject object)) throw new IllegalStateException("Unknown ingredient " + json);
if (object.has("item")) {
return itemPredicate(ShapedRecipe.itemFromJson(object));
var item = Util.getOrThrow(ItemStack.ITEM_WITH_COUNT_CODEC.parse(JsonOps.INSTANCE, object), JsonParseException::new);
return itemPredicate(item.getItem());
} else if (object.has("tag")) {
return itemPredicate(TagKey.create(Registries.ITEM, new ResourceLocation(GsonHelper.getAsString(object, "tag"))));
} else {
@@ -500,15 +506,14 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
}
}
private static CompoundTag playerHead(String name, String uuid) {
private static ItemStack playerHead(String name, String uuid) {
var item = new ItemStack(Items.PLAYER_HEAD);
var owner = NbtUtils.writeGameProfile(new CompoundTag(), new GameProfile(UUID.fromString(uuid), name));
var tag = new CompoundTag();
tag.put(PlayerHeadItem.TAG_SKULL_OWNER, owner);
return tag;
item.getOrCreateTag().put(PlayerHeadItem.TAG_SKULL_OWNER, owner);
return item;
}
private static void addSpecial(Consumer<FinishedRecipe> add, SimpleCraftingRecipeSerializer<?> special) {
SpecialRecipeBuilder.special(special).save(add, RegistryWrappers.RECIPE_SERIALIZERS.getKey(special).toString());
private static void addSpecial(RecipeOutput add, Recipe<?> recipe) {
add.accept(RegistryHelper.getKeyOrThrow(BuiltInRegistries.RECIPE_SERIALIZER, recipe.getSerializer()), recipe, null);
}
}

View File

@@ -1,93 +0,0 @@
// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.data;
import com.google.gson.JsonObject;
import net.minecraft.data.recipes.FinishedRecipe;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.item.crafting.RecipeSerializer;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
/**
* Adapter for recipes which overrides the serializer and adds custom item NBT.
*/
final class RecipeWrapper implements Consumer<FinishedRecipe> {
private final Consumer<FinishedRecipe> add;
private final RecipeSerializer<?> serializer;
private final List<Consumer<JsonObject>> extend = new ArrayList<>(0);
RecipeWrapper(Consumer<FinishedRecipe> add, RecipeSerializer<?> serializer) {
this.add = add;
this.serializer = serializer;
}
public static RecipeWrapper wrap(RecipeSerializer<?> serializer, Consumer<FinishedRecipe> original) {
return new RecipeWrapper(original, serializer);
}
public RecipeWrapper withExtraData(Consumer<JsonObject> extra) {
extend.add(extra);
return this;
}
public RecipeWrapper withResultTag(@Nullable CompoundTag resultTag) {
if (resultTag == null) return this;
extend.add(json -> {
var object = GsonHelper.getAsJsonObject(json, "result");
object.addProperty("nbt", resultTag.toString());
});
return this;
}
public RecipeWrapper withResultTag(Consumer<CompoundTag> resultTag) {
var tag = new CompoundTag();
resultTag.accept(tag);
return withResultTag(tag);
}
@Override
public void accept(FinishedRecipe finishedRecipe) {
add.accept(new RecipeImpl(finishedRecipe, serializer, extend));
}
private record RecipeImpl(
FinishedRecipe recipe, RecipeSerializer<?> serializer, List<Consumer<JsonObject>> extend
) implements FinishedRecipe {
@Override
public void serializeRecipeData(JsonObject jsonObject) {
recipe.serializeRecipeData(jsonObject);
for (var extender : extend) extender.accept(jsonObject);
}
@Override
public ResourceLocation getId() {
return recipe.getId();
}
@Override
public RecipeSerializer<?> getType() {
return serializer;
}
@Nullable
@Override
public JsonObject serializeAdvancement() {
return recipe.serializeAdvancement();
}
@Nullable
@Override
public ResourceLocation getAdvancementId() {
return recipe.getAdvancementId();
}
}
}

View File

@@ -5,9 +5,9 @@
package dan200.computercraft.data;
import dan200.computercraft.api.ComputerCraftTags;
import dan200.computercraft.impl.RegistryHelper;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.integration.ExternalModTags;
import dan200.computercraft.shared.platform.RegistryWrappers;
import net.minecraft.core.Registry;
import net.minecraft.data.tags.ItemTagsProvider;
import net.minecraft.data.tags.TagsProvider;
import net.minecraft.tags.BlockTags;
@@ -82,12 +82,6 @@ class TagProvider {
);
tags.tag(BlockTags.WITHER_IMMUNE).add(ModRegistry.Blocks.COMPUTER_COMMAND.get());
tags.tag(ExternalModTags.Blocks.CREATE_BRITTLE).add(
ModRegistry.Blocks.CABLE.get(),
ModRegistry.Blocks.WIRELESS_MODEM_NORMAL.get(),
ModRegistry.Blocks.WIRELESS_MODEM_ADVANCED.get()
);
}
public static void itemTags(ItemTagConsumer tags) {
@@ -118,9 +112,9 @@ class TagProvider {
TagAppender<T> tag(TagKey<T> tag);
}
public record TagAppender<T>(RegistryWrappers.RegistryWrapper<T> registry, TagBuilder builder) {
public record TagAppender<T>(Registry<T> registry, TagBuilder builder) {
public TagAppender<T> add(T object) {
builder.addElement(registry.getKey(object));
builder.addElement(RegistryHelper.getKeyOrThrow(registry, object));
return this;
}

View File

@@ -6,8 +6,9 @@ package dan200.computercraft.data;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.ComputerCraftTags.Blocks;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleUpgradeDataProvider;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import net.minecraft.data.PackOutput;
import net.minecraft.resources.ResourceLocation;
@@ -22,7 +23,7 @@ class TurtleUpgradeProvider extends TurtleUpgradeDataProvider {
}
@Override
protected void addUpgrades(Consumer<Upgrade<TurtleUpgradeSerialiser<?>>> addUpgrade) {
protected void addUpgrades(Consumer<Upgrade<UpgradeSerialiser<? extends ITurtleUpgrade>>> addUpgrade) {
simpleWithCustomItem(id("speaker"), TurtleSerialisers.SPEAKER.get(), Items.SPEAKER.get()).add(addUpgrade);
simpleWithCustomItem(vanilla("crafting_table"), TurtleSerialisers.WORKBENCH.get(), net.minecraft.world.item.Items.CRAFTING_TABLE).add(addUpgrade);
simpleWithCustomItem(id("wireless_modem_normal"), TurtleSerialisers.WIRELESS_MODEM_NORMAL.get(), Items.WIRELESS_MODEM_NORMAL.get()).add(addUpgrade);

View File

@@ -0,0 +1,129 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.data.recipe;
import com.mojang.serialization.DataResult;
import dan200.computercraft.shared.recipe.RecipeProperties;
import net.minecraft.Util;
import net.minecraft.advancements.AdvancementRequirements;
import net.minecraft.advancements.AdvancementRewards;
import net.minecraft.advancements.Criterion;
import net.minecraft.advancements.critereon.RecipeUnlockedTrigger;
import net.minecraft.data.recipes.RecipeBuilder;
import net.minecraft.data.recipes.RecipeCategory;
import net.minecraft.data.recipes.RecipeOutput;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Recipe;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Function;
/**
* An abstract base class for creating recipes, in the style of {@link RecipeBuilder}.
*
* @param <S> The type of this class.
* @param <O> The output of this builder.
* @see ShapelessSpecBuilder
*/
public abstract class AbstractRecipeBuilder<S extends AbstractRecipeBuilder<S, O>, O> {
private final RecipeCategory category;
protected final ItemStack result;
private String group = "";
private final Map<String, Criterion<?>> criteria = new LinkedHashMap<>();
protected AbstractRecipeBuilder(RecipeCategory category, ItemStack result) {
this.category = category;
this.result = result;
}
/**
* Set the group for this recipe.
*
* @param group The new group.
* @return This object, for chaining.
*/
public final S group(String group) {
this.group = group;
return self();
}
/**
* Add a criterion to this recipe.
*
* @param name The name of the criterion.
* @param criterion The criterion to add.
* @return This object, for chaining.
*/
public final S unlockedBy(String name, Criterion<?> criterion) {
criteria.put(name, criterion);
return self();
}
/**
* Convert this builder into the output ({@link O}) object.
*
* @param properties The properties for this recipe.
* @return The built object.
*/
protected abstract O build(RecipeProperties properties);
/**
* Convert this builder into a concrete recipe.
*
* @param factory The recipe's constructor.
* @return The "built" recipe.
*/
public final FinishedRecipe build(Function<O, Recipe<?>> factory) {
var properties = new RecipeProperties(group, RecipeBuilder.determineBookCategory(category), true);
return new FinishedRecipe(factory.apply(build(properties)), result.getItem(), category, criteria);
}
/**
* Convert this builder into a concrete recipe.
*
* @param factory The recipe's constructor.
* @return The "built" recipe.
*/
public final FinishedRecipe buildOrThrow(Function<O, DataResult<? extends Recipe<?>>> factory) {
return build(s -> Util.getOrThrow(factory.apply(s), IllegalStateException::new));
}
@SuppressWarnings("unchecked")
private S self() {
return (S) this;
}
public static final class FinishedRecipe {
private final Recipe<?> recipe;
private final Item result;
private final RecipeCategory category;
private final Map<String, Criterion<?>> criteria;
private FinishedRecipe(Recipe<?> recipe, Item result, RecipeCategory category, Map<String, Criterion<?>> criteria) {
this.recipe = recipe;
this.result = result;
this.category = category;
this.criteria = criteria;
}
public void save(RecipeOutput output, ResourceLocation id) {
if (criteria.isEmpty()) throw new IllegalStateException("No way of obtaining recipe " + id);
var advancement = output.advancement()
.addCriterion("has_the_recipe", RecipeUnlockedTrigger.unlocked(id))
.rewards(AdvancementRewards.Builder.recipe(id))
.requirements(AdvancementRequirements.Strategy.OR);
for (var entry : criteria.entrySet()) advancement.addCriterion(entry.getKey(), entry.getValue());
output.accept(id, recipe, advancement.build(id.withPrefix("recipes/" + category.getFolderName() + "/")));
}
public void save(RecipeOutput output) {
save(output, RecipeBuilder.getDefaultRecipeId(result));
}
}
}

View File

@@ -0,0 +1,71 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.data.recipe;
import dan200.computercraft.shared.recipe.RecipeProperties;
import dan200.computercraft.shared.recipe.ShapedRecipeSpec;
import net.minecraft.data.recipes.RecipeCategory;
import net.minecraft.data.recipes.ShapedRecipeBuilder;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.crafting.ShapedRecipePattern;
import net.minecraft.world.level.ItemLike;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* A builder for {@link ShapedRecipeSpec}s, much like {@link ShapedRecipeBuilder}.
*/
public final class ShapedSpecBuilder extends AbstractRecipeBuilder<ShapedSpecBuilder, ShapedRecipeSpec> {
private final List<String> rows = new ArrayList<>();
private final Map<Character, Ingredient> key = new LinkedHashMap<>();
private ShapedSpecBuilder(RecipeCategory category, ItemStack result) {
super(category, result);
}
public static ShapedSpecBuilder shaped(RecipeCategory category, ItemStack result) {
return new ShapedSpecBuilder(category, result);
}
public static ShapedSpecBuilder shaped(RecipeCategory category, ItemLike result) {
return new ShapedSpecBuilder(category, new ItemStack(result));
}
public ShapedSpecBuilder define(char key, Ingredient ingredient) {
if (this.key.containsKey(key)) throw new IllegalArgumentException("Symbol '" + key + "' is already defined!");
if (key == ' ') throw new IllegalArgumentException("Symbol ' ' (whitespace) is reserved and cannot be defined");
this.key.put(key, ingredient);
return this;
}
public ShapedSpecBuilder define(char key, TagKey<Item> tag) {
return this.define(key, Ingredient.of(tag));
}
public ShapedSpecBuilder define(char key, ItemLike item) {
return this.define(key, Ingredient.of(item));
}
public ShapedSpecBuilder pattern(String pattern) {
if (!this.rows.isEmpty() && pattern.length() != this.rows.get(0).length()) {
throw new IllegalArgumentException("Pattern must be the same width on every line!");
} else {
this.rows.add(pattern);
return this;
}
}
@Override
protected ShapedRecipeSpec build(RecipeProperties properties) {
return new ShapedRecipeSpec(properties, ShapedRecipePattern.of(key, rows), result);
}
}

View File

@@ -0,0 +1,61 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.data.recipe;
import dan200.computercraft.shared.recipe.RecipeProperties;
import dan200.computercraft.shared.recipe.ShapelessRecipeSpec;
import net.minecraft.core.NonNullList;
import net.minecraft.data.recipes.RecipeCategory;
import net.minecraft.data.recipes.ShapelessRecipeBuilder;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.level.ItemLike;
/**
* A builder for {@link ShapelessRecipeSpec}s, much like {@link ShapelessRecipeBuilder}.
*/
public final class ShapelessSpecBuilder extends AbstractRecipeBuilder<ShapelessSpecBuilder, ShapelessRecipeSpec> {
private final NonNullList<Ingredient> ingredients = NonNullList.create();
private ShapelessSpecBuilder(RecipeCategory category, ItemStack result) {
super(category, result);
}
public static ShapelessSpecBuilder shapeless(RecipeCategory category, ItemStack result) {
return new ShapelessSpecBuilder(category, result);
}
public static ShapelessSpecBuilder shapeless(RecipeCategory category, ItemLike result) {
return new ShapelessSpecBuilder(category, new ItemStack(result));
}
public ShapelessSpecBuilder requires(Ingredient ingredient, int count) {
for (int i = 0; i < count; i++) ingredients.add(ingredient);
return this;
}
public ShapelessSpecBuilder requires(Ingredient ingredient) {
return requires(ingredient, 1);
}
public ShapelessSpecBuilder requires(ItemLike item) {
return requires(Ingredient.of(new ItemStack(item)));
}
public ShapelessSpecBuilder requires(ItemLike item, int count) {
return requires(Ingredient.of(new ItemStack(item)), count);
}
public ShapelessSpecBuilder requires(TagKey<Item> item) {
return requires(Ingredient.of(item));
}
@Override
protected ShapelessRecipeSpec build(RecipeProperties properties) {
return new ShapelessRecipeSpec(properties, ingredients, result);
}
}

View File

@@ -15,10 +15,11 @@ import dan200.computercraft.api.media.MediaProvider;
import dan200.computercraft.api.network.PacketNetwork;
import dan200.computercraft.api.network.wired.WiredElement;
import dan200.computercraft.api.network.wired.WiredNode;
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.redstone.BundledRedstoneProvider;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleRefuelHandler;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import dan200.computercraft.core.filesystem.WritableFileMount;
import dan200.computercraft.impl.detail.DetailRegistryImpl;
import dan200.computercraft.impl.network.wired.WiredNodeImpl;
@@ -44,8 +45,8 @@ public abstract class AbstractComputerCraftAPI implements ComputerCraftAPIServic
private final DetailRegistry<ItemStack> itemStackDetails = new DetailRegistryImpl<>(ItemDetails::fillBasic);
private final DetailRegistry<BlockReference> blockDetails = new DetailRegistryImpl<>(BlockDetails::fillBasic);
protected static final ResourceKey<Registry<TurtleUpgradeSerialiser<?>>> turtleUpgradeRegistryId = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_upgrade_serialiser"));
protected static final ResourceKey<Registry<PocketUpgradeSerialiser<?>>> pocketUpgradeRegistryId = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "pocket_upgrade_serialiser"));
protected static final ResourceKey<Registry<UpgradeSerialiser<? extends ITurtleUpgrade>>> turtleUpgradeRegistryId = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_upgrade_serialiser"));
protected static final ResourceKey<Registry<UpgradeSerialiser<? extends IPocketUpgrade>>> pocketUpgradeRegistryId = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "pocket_upgrade_serialiser"));
public static @Nullable InputStream getResourceFile(MinecraftServer server, String domain, String subPath) {
var manager = server.getResourceManager();
@@ -116,12 +117,12 @@ public abstract class AbstractComputerCraftAPI implements ComputerCraftAPIServic
}
@Override
public final ResourceKey<Registry<TurtleUpgradeSerialiser<?>>> turtleUpgradeRegistryId() {
public final ResourceKey<Registry<UpgradeSerialiser<? extends ITurtleUpgrade>>> turtleUpgradeRegistryId() {
return turtleUpgradeRegistryId;
}
@Override
public final ResourceKey<Registry<PocketUpgradeSerialiser<?>>> pocketUpgradeRegistryId() {
public final ResourceKey<Registry<UpgradeSerialiser<? extends IPocketUpgrade>>> pocketUpgradeRegistryId() {
return pocketUpgradeRegistryId;
}

View File

@@ -16,20 +16,18 @@ import javax.annotation.Nullable;
/**
* The registry for peripheral providers.
* <p>
* This lives in the {@code impl} package despite it not being part of the public API, in order to mirror Forge's class.
*/
public final class Peripherals {
private static final GenericPeripheralProvider<Runnable> genericProvider = new GenericPeripheralProvider<>();
private static final GenericPeripheralProvider genericProvider = new GenericPeripheralProvider();
private Peripherals() {
}
public static void addGenericLookup(ComponentLookup<? super Runnable> lookup) {
public static void addGenericLookup(ComponentLookup lookup) {
genericProvider.registerLookup(lookup);
}
public static @Nullable IPeripheral getGenericPeripheral(ServerLevel level, BlockPos pos, Direction side, @Nullable BlockEntity blockEntity, Runnable invalidate) {
return genericProvider.getPeripheral(level, pos, side, blockEntity, invalidate);
public static @Nullable IPeripheral getGenericPeripheral(ServerLevel level, BlockPos pos, Direction side, @Nullable BlockEntity blockEntity) {
return genericProvider.getPeripheral(level, pos, side, blockEntity);
}
}

View File

@@ -6,19 +6,18 @@ package dan200.computercraft.impl;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
import java.util.stream.Stream;
public final class PocketUpgrades {
private static final UpgradeManager<PocketUpgradeSerialiser<?>, IPocketUpgrade> registry = new UpgradeManager<>(
"pocket computer upgrade", "computercraft/pocket_upgrades", PocketUpgradeSerialiser.registryId()
private static final UpgradeManager<IPocketUpgrade> registry = new UpgradeManager<>(
"pocket computer upgrade", "computercraft/pocket_upgrades", IPocketUpgrade.serialiserRegistryKey()
);
private PocketUpgrades() {
}
public static UpgradeManager<PocketUpgradeSerialiser<?>, IPocketUpgrade> instance() {
public static UpgradeManager<IPocketUpgrade> instance() {
return registry;
}

View File

@@ -6,19 +6,18 @@ package dan200.computercraft.impl;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import java.util.stream.Stream;
public final class TurtleUpgrades {
private static final UpgradeManager<TurtleUpgradeSerialiser<?>, ITurtleUpgrade> registry = new UpgradeManager<>(
"turtle upgrade", "computercraft/turtle_upgrades", TurtleUpgradeSerialiser.registryId()
private static final UpgradeManager<ITurtleUpgrade> registry = new UpgradeManager<>(
"turtle upgrade", "computercraft/turtle_upgrades", ITurtleUpgrade.serialiserRegistryKey()
);
private TurtleUpgrades() {
}
public static UpgradeManager<TurtleUpgradeSerialiser<?>, ITurtleUpgrade> instance() {
public static UpgradeManager<ITurtleUpgrade> instance() {
return registry;
}

View File

@@ -31,27 +31,26 @@ import java.util.stream.Collectors;
/**
* Manages turtle and pocket computer upgrades.
*
* @param <R> The type of upgrade serialisers.
* @param <T> The type of upgrade.
* @see TurtleUpgrades
* @see PocketUpgrades
*/
public class UpgradeManager<R extends UpgradeSerialiser<? extends T>, T extends UpgradeBase> extends SimpleJsonResourceReloadListener {
public class UpgradeManager<T extends UpgradeBase> extends SimpleJsonResourceReloadListener {
private static final Logger LOG = LoggerFactory.getLogger(UpgradeManager.class);
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
public record UpgradeWrapper<R extends UpgradeSerialiser<? extends T>, T extends UpgradeBase>(
String id, T upgrade, R serialiser, String modId
public record UpgradeWrapper<T extends UpgradeBase>(
String id, T upgrade, UpgradeSerialiser<? extends T> serialiser, String modId
) {
}
private final String kind;
private final ResourceKey<Registry<R>> registry;
private final ResourceKey<Registry<UpgradeSerialiser<? extends T>>> registry;
private Map<String, UpgradeWrapper<R, T>> current = Map.of();
private Map<T, UpgradeWrapper<R, T>> currentWrappers = Map.of();
private Map<String, UpgradeWrapper<T>> current = Map.of();
private Map<T, UpgradeWrapper<T>> currentWrappers = Map.of();
public UpgradeManager(String kind, String path, ResourceKey<Registry<R>> registry) {
public UpgradeManager(String kind, String path, ResourceKey<Registry<UpgradeSerialiser<? extends T>>> registry) {
super(GSON, path);
this.kind = kind;
this.registry = registry;
@@ -64,7 +63,7 @@ public class UpgradeManager<R extends UpgradeSerialiser<? extends T>, T extends
}
@Nullable
public UpgradeWrapper<R, T> getWrapper(T upgrade) {
public UpgradeWrapper<T> getWrapper(T upgrade) {
return currentWrappers.get(upgrade);
}
@@ -92,16 +91,17 @@ public class UpgradeManager<R extends UpgradeSerialiser<? extends T>, T extends
return currentWrappers.keySet();
}
public Map<String, UpgradeWrapper<R, T>> getUpgradeWrappers() {
public Map<String, UpgradeWrapper<T>> getUpgradeWrappers() {
return current;
}
@Override
protected void apply(Map<ResourceLocation, JsonElement> upgrades, ResourceManager manager, ProfilerFiller profiler) {
Map<String, UpgradeWrapper<R, T>> newUpgrades = new HashMap<>();
var registry = RegistryHelper.getRegistry(this.registry);
Map<String, UpgradeWrapper<T>> newUpgrades = new HashMap<>();
for (var element : upgrades.entrySet()) {
try {
loadUpgrade(newUpgrades, element.getKey(), element.getValue());
loadUpgrade(registry, newUpgrades, element.getKey(), element.getValue());
} catch (IllegalArgumentException | JsonParseException e) {
LOG.error("Error loading {} {} from JSON file", kind, element.getKey(), e);
}
@@ -112,12 +112,12 @@ public class UpgradeManager<R extends UpgradeSerialiser<? extends T>, T extends
LOG.info("Loaded {} {}s", current.size(), kind);
}
private void loadUpgrade(Map<String, UpgradeWrapper<R, T>> current, ResourceLocation id, JsonElement json) {
private void loadUpgrade(Registry<UpgradeSerialiser<? extends T>> registry, Map<String, UpgradeWrapper<T>> current, ResourceLocation id, JsonElement json) {
var root = GsonHelper.convertToJsonObject(json, "top element");
if (!PlatformHelper.get().shouldLoadResource(root)) return;
var serialiserId = new ResourceLocation(GsonHelper.getAsString(root, "type"));
var serialiser = PlatformHelper.get().tryGetRegistryObject(registry, serialiserId);
var serialiser = registry.get(serialiserId);
if (serialiser == null) throw new JsonSyntaxException("Unknown upgrade type '" + serialiserId + "'");
// TODO: Can we track which mod this resource came from and use that instead? It's theoretically possible,
@@ -130,11 +130,11 @@ public class UpgradeManager<R extends UpgradeSerialiser<? extends T>, T extends
throw new IllegalArgumentException("Upgrade " + id + " from " + serialiser + " was incorrectly given id " + upgrade.getUpgradeID());
}
var result = new UpgradeWrapper<R, T>(id.toString(), upgrade, serialiser, modId);
var result = new UpgradeWrapper<T>(id.toString(), upgrade, serialiser, modId);
current.put(result.id(), result);
}
public void loadFromNetwork(Map<String, UpgradeWrapper<R, T>> newUpgrades) {
public void loadFromNetwork(Map<String, UpgradeWrapper<T>> newUpgrades) {
current = Collections.unmodifiableMap(newUpgrades);
currentWrappers = newUpgrades.values().stream().collect(Collectors.toUnmodifiableMap(UpgradeWrapper::upgrade, x -> x));
}

View File

@@ -4,66 +4,45 @@
package dan200.computercraft.impl.network.wired;
import org.jetbrains.annotations.Contract;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
/**
* Verifies certain elements of a network are well-formed.
* Verifies certain elements of a network are "well formed".
* <p>
* This adds substantial overhead to network modification, and so is only enabled when assertions are enabled.
* This adds substantial overhead to network modification, and so should only be enabled
* in a development environment.
*/
final class InvariantChecker {
public final class InvariantChecker {
private static final Logger LOG = LoggerFactory.getLogger(InvariantChecker.class);
private static final boolean ENABLED = false;
private InvariantChecker() {
}
static void checkNode(WiredNodeImpl node) {
assert checkNodeImpl(node) : "Node invariants failed. See logs.";
}
public static void checkNode(WiredNodeImpl node) {
if (!ENABLED) return;
private static boolean checkNodeImpl(WiredNodeImpl node) {
var okay = true;
if (node.currentSet != null) {
okay = false;
LOG.error("{}: currentSet was not cleared.", node);
var network = node.network;
if (network == null) {
LOG.error("Node's network is null", new Exception());
return;
}
var network = makeNullable(node.network);
if (network == null) {
okay = false;
LOG.error("{}: Node's network is null.", node);
} else if (makeNullable(network.nodes) == null || !network.nodes.contains(node)) {
okay = false;
LOG.error("{}: Node's network does not contain node.", node);
if (network.nodes == null || !network.nodes.contains(node)) {
LOG.error("Node's network does not contain node", new Exception());
}
for (var neighbour : node.neighbours) {
if (!neighbour.neighbours.contains(node)) {
okay = false;
LOG.error("{}: Neighbour {}'s neighbour set does not contain origianl node.", node, neighbour);
LOG.error("Neighbour is missing node", new Exception());
}
}
return okay;
}
static void checkNetwork(WiredNetworkImpl network) {
assert checkNetworkImpl(network) : "Network invariants failed. See logs.";
}
public static void checkNetwork(WiredNetworkImpl network) {
if (!ENABLED) return;
private static boolean checkNetworkImpl(WiredNetworkImpl network) {
var okay = true;
for (var node : network.nodes) okay &= checkNodeImpl(node);
return okay;
}
@Contract("")
private static <T> @Nullable T makeNullable(T object) {
return object;
for (var node : network.nodes) checkNode(node);
}
}

Some files were not shown because too many files have changed in this diff Show More