mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-10-15 14:07:38 +00:00
Compare commits
147 Commits
v1.20.1-1.
...
v1.21.1-1.
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9cf0f85fcb | ||
![]() |
018ce7c8a5 | ||
![]() |
44726827b4 | ||
![]() |
2bf0aba455 | ||
![]() |
3cf914cb4c | ||
![]() |
180156ff1c | ||
![]() |
c6ba753568 | ||
![]() |
9f45c91925 | ||
![]() |
5fa724ed24 | ||
![]() |
fbf994e803 | ||
![]() |
8344c0a5c2 | ||
![]() |
a292d33830 | ||
![]() |
341d1c7bc2 | ||
![]() |
531eacfac7 | ||
![]() |
1f3da5205c | ||
![]() |
798ceefafe | ||
![]() |
7c0f79fc3c | ||
![]() |
b35cefc5dd | ||
![]() |
89dd521930 | ||
![]() |
9272e2efcd | ||
![]() |
69353a4fcf | ||
![]() |
ff363dca5a | ||
![]() |
1c51282426 | ||
![]() |
4a3a1c9275 | ||
![]() |
2557dd0af9 | ||
![]() |
b5c0c6e104 | ||
![]() |
876fd8ddb8 | ||
![]() |
ee3b1343b5 | ||
![]() |
b440b964b7 | ||
![]() |
5dfc401b45 | ||
![]() |
418c9be7ac | ||
![]() |
b491f6b11f | ||
![]() |
4344c3072f | ||
![]() |
947001104d | ||
![]() |
8711512769 | ||
![]() |
fdae94b3c1 | ||
![]() |
9c0ce27ce6 | ||
![]() |
c458360b18 | ||
![]() |
09ad6c1905 | ||
![]() |
0e1e8a72d3 | ||
![]() |
ffa6eadc26 | ||
![]() |
7c1e8e1951 | ||
![]() |
b805a34c2d | ||
![]() |
b03546a158 | ||
![]() |
582713467f | ||
![]() |
b6f41a0df5 | ||
![]() |
594738a022 | ||
![]() |
27f2ab364c | ||
![]() |
5a43273757 | ||
![]() |
97e28516fb | ||
![]() |
676fb5fb53 | ||
![]() |
08dc08b5a3 | ||
![]() |
8f4d4038f6 | ||
![]() |
63ba3fe274 | ||
![]() |
749b3df227 | ||
![]() |
b97634b717 | ||
![]() |
1b8344d0a3 | ||
![]() |
b42bc0a01a | ||
![]() |
70a7478529 | ||
![]() |
0cff73e2fc | ||
![]() |
a892739f8e | ||
![]() |
f8785a092f | ||
![]() |
598fc4aefd | ||
![]() |
dd7e8fcefc | ||
![]() |
29c8f96912 | ||
![]() |
b9267ecbfc | ||
![]() |
9d2c2db22b | ||
![]() |
6660966320 | ||
![]() |
3acb231f01 | ||
![]() |
16324e1eac | ||
![]() |
fa33949113 | ||
![]() |
0c04d9de47 | ||
![]() |
32f5c38485 | ||
![]() |
01fe949b3e | ||
![]() |
c03fce275e | ||
![]() |
0998acaa82 | ||
![]() |
12a44fed6f | ||
![]() |
3f8c3b026a | ||
![]() |
1278246cf7 | ||
![]() |
1e25fa9bc3 | ||
![]() |
74f707aaea | ||
![]() |
9bb62b047a | ||
![]() |
4f3663ccc9 | ||
![]() |
53425c1e76 | ||
![]() |
d697c47b80 | ||
![]() |
4710ee5bcc | ||
![]() |
4a532952d4 | ||
![]() |
7337b91692 | ||
![]() |
f04c699df6 | ||
![]() |
a3f8e653d4 | ||
![]() |
b7396f3796 | ||
![]() |
5eb50ecb06 | ||
![]() |
7af2c14327 | ||
![]() |
0056709999 | ||
![]() |
0c8e757314 | ||
![]() |
f39e86bb10 | ||
![]() |
63181e73a1 | ||
![]() |
4f3247a0e2 | ||
![]() |
4f15f4197b | ||
![]() |
0d8ac304c7 | ||
![]() |
fdd5f49369 | ||
![]() |
8bd4c3370e | ||
![]() |
3eb84ffedd | ||
![]() |
dad6874638 | ||
![]() |
45cb597ecc | ||
![]() |
16577783d3 | ||
![]() |
c179da28f0 | ||
![]() |
2765abf971 | ||
![]() |
2c740bb904 | ||
![]() |
0e4710a956 | ||
![]() |
aca1d43550 | ||
![]() |
f10e401aea | ||
![]() |
1a1623075f | ||
![]() |
54a95e07a4 | ||
![]() |
efd9a0f315 | ||
![]() |
28f75a0687 | ||
![]() |
4b102f16b3 | ||
![]() |
bb933d0100 | ||
![]() |
de078e3037 | ||
![]() |
eb584aa94d | ||
![]() |
ad70e2ad90 | ||
![]() |
2c0d8263d3 | ||
![]() |
94c864759d | ||
![]() |
2226df7224 | ||
![]() |
959bdaeb61 | ||
![]() |
06ac373e83 | ||
![]() |
0aca6a4dc9 | ||
![]() |
bf203bb1f3 | ||
![]() |
cd9840d1c1 | ||
![]() |
b9a002586c | ||
![]() |
a3b07909b0 | ||
![]() |
d7786ee4b9 | ||
![]() |
188806e8b0 | ||
![]() |
01407544c9 | ||
![]() |
bd2fd9d4c8 | ||
![]() |
5c457950d8 | ||
![]() |
75f3ecce18 | ||
![]() |
688fdc40a6 | ||
![]() |
22bd5309ba | ||
![]() |
c50d56d9fa | ||
![]() |
7b9a156abc | ||
![]() |
0a9e5c78f3 | ||
![]() |
da5885ef35 | ||
![]() |
240528cce5 | ||
![]() |
83f1f86888 | ||
![]() |
9c202bd1c2 | ||
![]() |
fc834cd97f |
@@ -27,7 +27,7 @@ indent_size = 2
|
||||
[*.yml]
|
||||
indent_size = 2
|
||||
|
||||
[{*.kt,*.kts}]
|
||||
[*.{kt,kts}]
|
||||
ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL
|
||||
ij_kotlin_continuation_indent_size = 4
|
||||
ij_kotlin_spaces_around_equality_operators = true
|
||||
@@ -39,3 +39,14 @@ ij_kotlin_allow_trailing_comma_on_call_site = true
|
||||
ij_kotlin_method_parameters_wrap = off
|
||||
ij_kotlin_call_parameters_wrap = off
|
||||
ij_kotlin_extends_list_wrap = off
|
||||
|
||||
ktlint_code_style = intellij_idea
|
||||
ktlint_standard_class-naming = disabled
|
||||
ktlint_standard_class-signature = disabled
|
||||
ktlint_standard_function-naming = disabled
|
||||
ktlint_standard_no-wildcard-imports = disabled
|
||||
|
||||
# FIXME: These two are disable right now as they're over-eager in putting things
|
||||
# on the same line. We should set max_line_length and handle this properly.
|
||||
ktlint_standard_function-signature = disabled
|
||||
ktlint_standard_function-expression-body = disabled
|
||||
|
3
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
3
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@@ -11,7 +11,8 @@ body:
|
||||
What version of Minecraft are you using? If your version is not listed, please try to reproduce on one of the supported versions.
|
||||
options:
|
||||
- 1.20.1
|
||||
- 1.21.x
|
||||
- 1.21.1
|
||||
- 1.21.7
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
|
4
.github/workflows/main-ci.yml
vendored
4
.github/workflows/main-ci.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
- name: 📥 Set up Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 17
|
||||
java-version: 21
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: 📥 Setup Gradle
|
||||
@@ -87,7 +87,7 @@ jobs:
|
||||
- name: 📥 Set up Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 17
|
||||
java-version: 21
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: 📥 Setup Gradle
|
||||
|
2
.github/workflows/make-doc.yml
vendored
2
.github/workflows/make-doc.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
- name: 📥 Set up Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 17
|
||||
java-version: 21
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: 📥 Setup Gradle
|
||||
|
@@ -6,7 +6,7 @@
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.4.0
|
||||
rev: v5.0.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
|
@@ -28,7 +28,7 @@ Translations are managed through [CrowdIn], an online interface for managing lan
|
||||
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 17 (JDK). This can be downloaded from [Adoptium].
|
||||
- Java Development Kit 21 (JDK). This can be downloaded from [Adoptium].
|
||||
- [Git](https://git-scm.com/).
|
||||
- [NodeJS 20 or later][node].
|
||||
|
||||
@@ -88,8 +88,8 @@ You'll first need to [set up a development environment as above](#setting-up-a-d
|
||||
|
||||
Once this is set up, you can now run `./gradlew docWebsite`. This generates documentation from our Lua and Java code,
|
||||
writing the resulting HTML into `./projects/web/build/site`, which can then be opened in a browser. When iterating on
|
||||
documentation, you can instead run `./gradlew docWebsite -t`, which will rebuild documentation every time you change a
|
||||
file.
|
||||
documentation, you can instead run `./gradlew :web:assemble -x :web:compileTeaVM -t`, which will rebuild documentation
|
||||
every time you change a file.
|
||||
|
||||
Documentation is built using [illuaminate] which, while not currently documented (somewhat ironic), is largely the same
|
||||
as [ldoc][ldoc]. Documentation comments are written in Markdown, though note that we do not support many GitHub-specific
|
||||
|
@@ -51,9 +51,8 @@ dependencies {
|
||||
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-common-api:$cctVersion")
|
||||
|
||||
// Forge Gradle
|
||||
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-core-api:$cctVersion")
|
||||
compileOnly(fg.deobf("cc.tweaked:cc-tweaked-$mcVersion-forge-api:$cctVersion"))
|
||||
runtimeOnly(fg.deobf("cc.tweaked:cc-tweaked-$mcVersion-forge:$cctVersion"))
|
||||
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-forge-api:$cctVersion")
|
||||
runtimeOnly("cc.tweaked:cc-tweaked-$mcVersion-forge:$cctVersion")
|
||||
|
||||
// Fabric Loom
|
||||
modCompileOnly("cc.tweaked:cc-tweaked-$mcVersion-fabric-api:$cctVersion")
|
||||
|
@@ -15,7 +15,7 @@ path = [
|
||||
"gradle/gradle-daemon-jvm.properties",
|
||||
"projects/common/src/main/resources/assets/computercraft/sounds.json",
|
||||
"projects/common/src/main/resources/assets/computercraft/sounds/empty.ogg",
|
||||
"projects/common/src/testMod/resources/data/cctest/computercraft/turtle_upgrades/**",
|
||||
"projects/common/src/testMod/resources/data/cctest/computercraft/turtle_upgrade/**",
|
||||
"projects/common/src/testMod/resources/data/cctest/structures/**",
|
||||
"projects/*/src/generated/**",
|
||||
"projects/web/src/htmlTransform/export/index.json",
|
||||
|
@@ -6,21 +6,20 @@
|
||||
|
||||
import cc.tweaked.gradle.CCTweakedExtension
|
||||
import cc.tweaked.gradle.CCTweakedPlugin
|
||||
import cc.tweaked.gradle.IdeaRunConfigurations
|
||||
import cc.tweaked.gradle.MinecraftConfigurations
|
||||
|
||||
plugins {
|
||||
id("cc-tweaked.java-convention")
|
||||
id("net.neoforged.moddev.legacyforge")
|
||||
id("net.neoforged.moddev")
|
||||
}
|
||||
|
||||
plugins.apply(CCTweakedPlugin::class.java)
|
||||
|
||||
val mcVersion: String by extra
|
||||
|
||||
legacyForge {
|
||||
neoForge {
|
||||
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
|
||||
version = "${mcVersion}-${libs.findVersion("forge").get()}"
|
||||
version = libs.findVersion("neoForge").get().toString()
|
||||
|
||||
parchment {
|
||||
minecraftVersion = libs.findVersion("parchmentMc").get().toString()
|
||||
|
@@ -29,7 +29,7 @@ base.archivesName.convention("cc-tweaked-$mcVersion-${project.name}")
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion= CCTweakedPlugin.JAVA_VERSION
|
||||
languageVersion = CCTweakedPlugin.JAVA_VERSION
|
||||
}
|
||||
|
||||
withSourcesJar()
|
||||
@@ -48,7 +48,7 @@ repositories {
|
||||
includeGroup("cc.tweaked")
|
||||
// Things we mirror
|
||||
includeGroup("com.simibubi.create")
|
||||
includeGroup("commoble.morered")
|
||||
includeGroup("net.commoble.morered")
|
||||
includeGroup("dev.architectury")
|
||||
includeGroup("dev.emi")
|
||||
includeGroup("maven.modrinth")
|
||||
@@ -57,7 +57,6 @@ repositories {
|
||||
includeGroup("mezz.jei")
|
||||
includeGroup("org.teavm")
|
||||
includeModule("com.terraformersmc", "modmenu")
|
||||
includeModule("me.lucko", "fabric-permissions-api")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,14 +78,22 @@ dependencies {
|
||||
// Configure default JavaCompile tasks with our arguments.
|
||||
sourceSets.all {
|
||||
tasks.named(compileJavaTaskName, JavaCompile::class.java) {
|
||||
// Processing just gives us "No processor claimed any of these annotations", so skip that!
|
||||
options.compilerArgs.addAll(listOf("-Xlint", "-Xlint:-processing"))
|
||||
|
||||
options.compilerArgs.addAll(
|
||||
listOf(
|
||||
"-Xlint",
|
||||
// Processing just gives us "No processor claimed any of these annotations", so skip that!
|
||||
"-Xlint:-processing",
|
||||
// We violate this pattern too often for it to be a helpful warning. Something to improve one day!
|
||||
"-Xlint:-this-escape",
|
||||
),
|
||||
)
|
||||
|
||||
options.errorprone {
|
||||
check("InvalidBlockTag", CheckSeverity.OFF) // Broken by @cc.xyz
|
||||
check("InvalidParam", CheckSeverity.OFF) // Broken by records.
|
||||
check("InlineMeSuggester", CheckSeverity.OFF) // Minecraft uses @Deprecated liberally
|
||||
// Too many false positives right now. Maybe we need an indirection for it later on.
|
||||
check("AssignmentExpression", CheckSeverity.OFF) // I'm a bad person.
|
||||
check("ReferenceEquality", CheckSeverity.OFF)
|
||||
check("EnumOrdinal", CheckSeverity.OFF) // For now. We could replace most of these with EnumMap.
|
||||
check("OperatorPrecedence", CheckSeverity.OFF) // For now.
|
||||
@@ -115,7 +122,6 @@ tasks.compileTestJava {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
tasks.withType(JavaCompile::class.java).configureEach {
|
||||
options.encoding = "UTF-8"
|
||||
}
|
||||
@@ -149,7 +155,7 @@ tasks.javadoc {
|
||||
options {
|
||||
val stdOptions = this as StandardJavadocDocletOptions
|
||||
stdOptions.addBooleanOption("Xdoclint:all,-missing", true)
|
||||
stdOptions.links("https://docs.oracle.com/en/java/javase/17/docs/api/")
|
||||
stdOptions.links("https://docs.oracle.com/en/java/javase/21/docs/api/")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +170,7 @@ tasks.test {
|
||||
|
||||
tasks.withType(JacocoReport::class.java).configureEach {
|
||||
reports.xml.required = true
|
||||
reports.html.required =true
|
||||
reports.html.required = true
|
||||
}
|
||||
|
||||
project.plugins.withType(CCTweakedPlugin::class.java) {
|
||||
@@ -188,30 +194,23 @@ spotless {
|
||||
fun FormatExtension.defaults() {
|
||||
endWithNewline()
|
||||
trimTrailingWhitespace()
|
||||
indentWithSpaces(4)
|
||||
leadingTabsToSpaces(4)
|
||||
}
|
||||
|
||||
java {
|
||||
defaults()
|
||||
importOrder("", "javax|java", "\\#")
|
||||
removeUnusedImports()
|
||||
}
|
||||
|
||||
val ktlintConfig = mapOf(
|
||||
"ktlint_standard_no-wildcard-imports" to "disabled",
|
||||
"ktlint_standard_class-naming" to "disabled",
|
||||
"ktlint_standard_function-naming" to "disabled",
|
||||
"ij_kotlin_allow_trailing_comma" to "true",
|
||||
"ij_kotlin_allow_trailing_comma_on_call_site" to "true",
|
||||
)
|
||||
|
||||
kotlinGradle {
|
||||
defaults()
|
||||
ktlint().editorConfigOverride(ktlintConfig)
|
||||
ktlint()
|
||||
}
|
||||
|
||||
kotlin {
|
||||
defaults()
|
||||
ktlint().editorConfigOverride(ktlintConfig)
|
||||
ktlint()
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -42,6 +42,6 @@ class CCTweakedPlugin : Plugin<Project> {
|
||||
}
|
||||
|
||||
companion object {
|
||||
val JAVA_VERSION = JavaLanguageVersion.of(17)
|
||||
val JAVA_VERSION = JavaLanguageVersion.of(21)
|
||||
}
|
||||
}
|
||||
|
@@ -5,10 +5,8 @@
|
||||
package cc.tweaked.gradle
|
||||
|
||||
import org.gradle.api.file.DirectoryProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.tasks.AbstractExecTask
|
||||
import org.gradle.api.tasks.OutputDirectory
|
||||
import java.io.File
|
||||
|
||||
abstract class ExecToDir : AbstractExecTask<ExecToDir>(ExecToDir::class.java) {
|
||||
@get:OutputDirectory
|
||||
|
@@ -9,6 +9,7 @@ import org.gradle.api.file.FileSystemLocation
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.provider.Provider
|
||||
import org.gradle.api.tasks.JavaExec
|
||||
import org.gradle.kotlin.dsl.getByName
|
||||
import org.gradle.process.BaseExecSpec
|
||||
import org.gradle.process.JavaExecSpec
|
||||
import org.gradle.process.ProcessForkOptions
|
||||
@@ -46,6 +47,21 @@ fun JavaExec.copyToFull(spec: JavaExec) {
|
||||
copyToExec(spec)
|
||||
}
|
||||
|
||||
/**
|
||||
* Base this [JavaExec] task on an existing task.
|
||||
*/
|
||||
fun JavaExec.copyFromTask(task: JavaExec) {
|
||||
for (dep in task.dependsOn) dependsOn(dep)
|
||||
task.copyToFull(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Base this [JavaExec] task on an existing task.
|
||||
*/
|
||||
fun JavaExec.copyFromTask(task: String) {
|
||||
copyFromTask(project.tasks.getByName<JavaExec>(task))
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy additional [BaseExecSpec] options which aren't handled by [ProcessForkOptions.copyTo].
|
||||
*/
|
||||
|
@@ -25,7 +25,6 @@ import javax.xml.xpath.XPathFactory
|
||||
* Would be good to PR some (or all) of these changes upstream at some point.
|
||||
*
|
||||
* @see net.fabricmc.loom.configuration.ide.idea.IdeaSyncTask
|
||||
* @see net.minecraftforge.gradle.common.util.runs.IntellijRunGenerator
|
||||
*/
|
||||
internal class IdeaRunConfigurations(project: Project) {
|
||||
private val rootProject = project.rootProject
|
||||
@@ -35,22 +34,6 @@ internal class IdeaRunConfigurations(project: Project) {
|
||||
private val writer = TransformerFactory.newInstance().newTransformer()
|
||||
|
||||
private val ideaDir = rootProject.file(".idea/")
|
||||
private val buildDir: Lazy<String?> = lazy {
|
||||
val ideaMisc = ideaDir.resolve("misc.xml")
|
||||
|
||||
try {
|
||||
val doc = Files.newBufferedReader(ideaMisc.toPath()).use {
|
||||
documentBuilder.parse(InputSource(it))
|
||||
}
|
||||
val node =
|
||||
xpath.evaluate("//component[@name=\"ProjectRootManager\"]/output", doc, XPathConstants.NODE) as Node
|
||||
val attr = node.attributes.getNamedItem("url") as Attr
|
||||
attr.value.removePrefix("file://")
|
||||
} catch (e: Exception) {
|
||||
LOGGER.error("Failed to find root directory", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun patch() = synchronized(LOCK) {
|
||||
val runConfigDir = ideaDir.resolve("runConfigurations")
|
||||
@@ -58,10 +41,9 @@ internal class IdeaRunConfigurations(project: Project) {
|
||||
|
||||
Files.list(runConfigDir.toPath()).use {
|
||||
for (configuration in it) {
|
||||
val filename = configuration.fileName.toString();
|
||||
val filename = configuration.fileName.toString()
|
||||
when {
|
||||
filename.endsWith("_fabric.xml") -> patchFabric(configuration)
|
||||
filename.startsWith("forge_") && filename.endsWith(".xml") -> patchForge(configuration)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
@@ -72,65 +54,6 @@ internal class IdeaRunConfigurations(project: Project) {
|
||||
setXml("//configuration", "folderName") { "Fabric" }
|
||||
}
|
||||
|
||||
private fun patchForge(path: Path) = withXml(path) {
|
||||
val configId = path.fileName.toString().removePrefix("forge_").removeSuffix(".xml")
|
||||
val sourceSet = forgeConfigs[configId]
|
||||
if (sourceSet == null) {
|
||||
LOGGER.error("[{}] Cannot map run configuration to a known source set", path)
|
||||
return@withXml
|
||||
}
|
||||
|
||||
setXml("//configuration", "folderName") { "Forge" }
|
||||
setXml("//configuration/module", "name") { "${rootProject.name}.forge.$sourceSet" }
|
||||
|
||||
if (buildDir.value == null) return@withXml
|
||||
setXml("//configuration/envs/env[@name=\"MOD_CLASSES\"]", "value") { classpath ->
|
||||
val classes = classpath!!.split(':')
|
||||
val newClasses = mutableListOf<String>()
|
||||
fun appendUnique(x: String) {
|
||||
if (!newClasses.contains(x)) newClasses.add(x)
|
||||
}
|
||||
|
||||
for (entry in classes) {
|
||||
if (!entry.contains("/out/")) {
|
||||
appendUnique(entry)
|
||||
continue
|
||||
}
|
||||
|
||||
val match = CLASSPATH_ENTRY.matchEntire(entry)
|
||||
if (match != null) {
|
||||
val modId = match.groups["modId"]!!.value
|
||||
val proj = match.groups["proj"]!!.value
|
||||
var component = match.groups["component"]!!.value
|
||||
if (component == "production") component = "main"
|
||||
|
||||
appendUnique(forgeModEntry(modId, proj, component))
|
||||
} else {
|
||||
LOGGER.warn("[{}] Unknown classpath entry {}", path, entry)
|
||||
appendUnique(entry)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure common code is on the classpath
|
||||
for (proj in listOf("common", "common-api")) {
|
||||
for (component in listOf("main", "client")) {
|
||||
appendUnique(forgeModEntry("computercraft", proj, component))
|
||||
}
|
||||
}
|
||||
|
||||
if (newClasses.any { it.startsWith("cctest%%") }) {
|
||||
appendUnique(forgeModEntry("cctest", "core", "testFixtures"))
|
||||
appendUnique(forgeModEntry("cctest", "common", "testFixtures"))
|
||||
appendUnique(forgeModEntry("cctest", "common", "testMod"))
|
||||
}
|
||||
|
||||
newClasses.joinToString(":")
|
||||
}
|
||||
}
|
||||
|
||||
private fun forgeModEntry(mod: String, project: String, component: String) =
|
||||
"$mod%%${buildDir.value}/production/${rootProject.name}.$project.$component"
|
||||
|
||||
private fun LocatedDocument.setXml(xpath: String, attribute: String, value: (String?) -> String) {
|
||||
val node = this@IdeaRunConfigurations.xpath.evaluate(xpath, document, XPathConstants.NODE) as Node?
|
||||
if (node == null) {
|
||||
@@ -159,16 +82,5 @@ internal class IdeaRunConfigurations(project: Project) {
|
||||
companion object {
|
||||
private val LOGGER = Logging.getLogger(IdeaRunConfigurations::class.java)
|
||||
private val LOCK = Any()
|
||||
|
||||
private val CLASSPATH_ENTRY =
|
||||
Regex("(?<modId>[a-z]+)%%\\\$PROJECT_DIR\\\$/projects/(?<proj>[a-z-]+)/out/(?<component>\\w+)/(?<type>[a-z]+)\$")
|
||||
|
||||
private val forgeConfigs = mapOf(
|
||||
"runClient" to "client",
|
||||
"runData" to "main",
|
||||
"runGameTestServer" to "testMod",
|
||||
"runServer" to "main",
|
||||
"runTestClient" to "testMod",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@@ -19,6 +19,7 @@ import java.nio.file.Files
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.function.Supplier
|
||||
import javax.inject.Inject
|
||||
import kotlin.collections.set
|
||||
import kotlin.random.Random
|
||||
|
||||
/**
|
||||
@@ -125,6 +126,23 @@ abstract class ClientJavaExec : JavaExec() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure Iris to use Complementary Shaders.
|
||||
*/
|
||||
fun withComplementaryShaders() {
|
||||
val cct = project.extensions.getByType(CCTweakedExtension::class.java)
|
||||
|
||||
withFileFrom(workingDir.resolve("shaderpacks/ComplementaryShaders_v4.6.zip")) {
|
||||
cct.downloadFile("Complementary Shaders", "https://edge.forgecdn.net/files/3951/170/ComplementaryShaders_v4.6.zip")
|
||||
}
|
||||
withFileContents(workingDir.resolve("config/iris.properties")) {
|
||||
"""
|
||||
enableShaders=true
|
||||
shaderPack=ComplementaryShaders_v4.6.zip
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
override fun exec() {
|
||||
Files.createDirectories(workingDir.toPath())
|
||||
|
@@ -1,16 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package cc.tweaked.gradle
|
||||
|
||||
import groovy.util.Node
|
||||
import groovy.util.NodeList
|
||||
|
||||
object XmlUtil {
|
||||
fun findChild(node: Node, name: String): Node? = when (val child = node.get(name)) {
|
||||
is Node -> child
|
||||
is NodeList -> child.singleOrNull() as Node?
|
||||
else -> null
|
||||
}
|
||||
}
|
@@ -21,6 +21,15 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<property name="file" value="${config_loc}/suppressions.xml" />
|
||||
</module>
|
||||
|
||||
<!--
|
||||
Checkstyle doesn't support @snippet (https://github.com/checkstyle/checkstyle/issues/11455),
|
||||
so suppress warnings nearby
|
||||
-->
|
||||
<module name="SuppressWithNearbyTextFilter">
|
||||
<property name="nearbyTextPattern" value="@snippet" />
|
||||
<property name="lineRange" value="20" />
|
||||
</module>
|
||||
|
||||
<module name="BeforeExecutionExclusionFileFilter">
|
||||
<property name="fileNamePattern" value="render_old"/>
|
||||
</module>
|
||||
@@ -92,7 +101,10 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<module name="InvalidJavadocPosition" />
|
||||
<module name="JavadocBlockTagLocation" />
|
||||
<module name="JavadocMethod"/>
|
||||
<module name="JavadocType"/>
|
||||
<module name="JavadocType">
|
||||
<!-- Seems to complain about @hidden!? -->
|
||||
<property name="allowUnknownTags" value="true" />
|
||||
</module>
|
||||
<module name="JavadocStyle">
|
||||
<property name="checkHtml" value="false" />
|
||||
</module>
|
||||
@@ -143,7 +155,10 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<module name="NoWhitespaceAfter">
|
||||
<property name="tokens" value="AT,INC,DEC,UNARY_MINUS,UNARY_PLUS,BNOT,LNOT,DOT,ARRAY_DECLARATOR,INDEX_OP,METHOD_REF" />
|
||||
</module>
|
||||
<module name="NoWhitespaceBefore" />
|
||||
<module name="NoWhitespaceBefore">
|
||||
<!-- Allow whitespace before "..." for @Nullable annotations -->
|
||||
<property name="tokens" value="COMMA,SEMI,POST_INC,POST_DEC,LABELED_STAT" />
|
||||
</module>
|
||||
<!-- TODO: Decide on an OperatorWrap style. -->
|
||||
<module name="ParenPad" />
|
||||
<module name="SeparatorWrap">
|
||||
|
@@ -22,4 +22,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
<!-- Allow underscores in our test classes. -->
|
||||
<suppress checks="MethodName" files=".*(Contract|Test).java" />
|
||||
|
||||
<!-- Allow underscores in Mixin classes -->
|
||||
<suppress checks="TypeName" files=".*[\\/]mixin[\\/].*.java" />
|
||||
</suppressions>
|
||||
|
@@ -12,6 +12,7 @@ files:
|
||||
de: de_de # German
|
||||
es-ES: es_es # Spanish
|
||||
fr: fr_fr # French
|
||||
hu: hu_hu # Hungarian
|
||||
it: it_it # Italian
|
||||
ja: ja_jp # Japanese
|
||||
ko: ko_kr # Korean
|
||||
|
@@ -8,9 +8,11 @@ org.gradle.parallel=true
|
||||
kotlin.stdlib.default.dependency=false
|
||||
kotlin.jvm.target.validation.mode=error
|
||||
|
||||
neogradle.subsystems.conventions.runs.enabled=false
|
||||
|
||||
# Mod properties
|
||||
isUnstable=false
|
||||
modVersion=1.115.0
|
||||
isUnstable=true
|
||||
modVersion=1.116.1
|
||||
|
||||
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
|
||||
mcVersion=1.20.1
|
||||
mcVersion=1.21.1
|
||||
|
@@ -1,2 +1,2 @@
|
||||
#This file is generated by updateDaemonJvm
|
||||
toolchainVersion=17
|
||||
toolchainVersion=21
|
||||
|
@@ -6,47 +6,48 @@
|
||||
|
||||
# Minecraft
|
||||
# MC version is specified in gradle.properties, as we need that in settings.gradle.
|
||||
# Remember to update corresponding versions in fabric.mod.json/mods.toml
|
||||
fabric-api = "0.86.1+1.20.1"
|
||||
fabric-loader = "0.14.21"
|
||||
forge = "47.1.0"
|
||||
forgeSpi = "7.0.1"
|
||||
# Remember to update corresponding versions in fabric.mod.json/neoforge.mods.toml
|
||||
fabric-api = "0.102.1+1.21.1"
|
||||
fabric-loader = "0.15.11"
|
||||
neoForge = "21.1.9"
|
||||
neoMergeTool = "2.0.0"
|
||||
mixin = "0.8.5"
|
||||
parchment = "2023.08.20"
|
||||
parchmentMc = "1.20.1"
|
||||
yarn = "1.20.1+build.10"
|
||||
parchment = "2024.07.28"
|
||||
parchmentMc = "1.21"
|
||||
yarn = "1.21.1+build.1"
|
||||
|
||||
# Core dependencies (these versions are tied to the version Minecraft uses)
|
||||
fastutil = "8.5.9"
|
||||
guava = "31.1-jre"
|
||||
netty = "4.1.82.Final"
|
||||
slf4j = "2.0.1"
|
||||
fastutil = "8.5.12"
|
||||
guava = "32.1.2-jre"
|
||||
netty = "4.1.97.Final"
|
||||
slf4j = "2.0.9"
|
||||
|
||||
# Core dependencies (independent of Minecraft)
|
||||
asm = "9.6"
|
||||
autoService = "1.1.1"
|
||||
checkerFramework = "3.42.0"
|
||||
cobalt = { strictly = "0.9.5" }
|
||||
cobalt = { strictly = "0.9.6" }
|
||||
commonsCli = "1.6.0"
|
||||
jetbrainsAnnotations = "24.1.0"
|
||||
jsr305 = "3.0.2"
|
||||
jspecify = "1.0.0"
|
||||
jzlib = "1.1.3"
|
||||
kotlin = "2.1.0"
|
||||
kotlin = "2.1.10"
|
||||
kotlin-coroutines = "1.10.1"
|
||||
nightConfig = "3.8.1"
|
||||
|
||||
# Minecraft mods
|
||||
emi = "1.0.8+1.20.1"
|
||||
fabricPermissions = "0.3.20230723"
|
||||
iris = "1.6.4+1.20"
|
||||
jei = "15.2.0.22"
|
||||
modmenu = "7.1.0"
|
||||
moreRed = "4.0.0.4"
|
||||
oculus = "1.2.5"
|
||||
rei = "12.0.626"
|
||||
rubidium = "0.6.1"
|
||||
sodium = "mc1.20-0.4.10"
|
||||
create-forge = "0.5.1.f-33"
|
||||
emi = "1.1.7+1.21"
|
||||
fabricPermissions = "0.3.1"
|
||||
iris-fabric = "1.8.0-beta.3+1.21-fabric"
|
||||
iris-forge = "1.8.0-beta.3+1.21-neoforge"
|
||||
jei = "19.8.2.99"
|
||||
modmenu = "11.0.0-rc.4"
|
||||
moreRed = "6.0.0.3"
|
||||
rei = "16.0.729"
|
||||
sodium-fabric = "mc1.21-0.6.0-beta.1-fabric"
|
||||
sodium-forge = "mc1.21-0.6.0-beta.1-neoforge"
|
||||
mixinExtra = "0.3.5"
|
||||
create-forge = "6.0.0-6"
|
||||
create-fabric = "0.5.1-f-build.1467+mc1.20.1"
|
||||
|
||||
# Testing
|
||||
@@ -57,24 +58,24 @@ junitPlatform = "1.11.4"
|
||||
jmh = "1.37"
|
||||
|
||||
# Build tools
|
||||
cctJavadoc = "1.8.3"
|
||||
checkstyle = "10.14.1"
|
||||
errorProne-core = "2.27.0"
|
||||
errorProne-plugin = "3.1.0"
|
||||
fabric-loom = "1.9.2"
|
||||
cctJavadoc = "1.8.4"
|
||||
checkstyle = "10.23.1"
|
||||
errorProne-core = "2.38.0"
|
||||
errorProne-plugin = "4.1.0"
|
||||
fabric-loom = "1.10.4"
|
||||
githubRelease = "2.5.2"
|
||||
gradleVersions = "0.50.0"
|
||||
ideaExt = "1.1.7"
|
||||
illuaminate = "0.1.0-74-gf1551d5"
|
||||
illuaminate = "0.1.0-83-g1131f68"
|
||||
lwjgl = "3.3.3"
|
||||
minotaur = "2.8.7"
|
||||
modDevGradle = "2.0.74"
|
||||
nullAway = "0.10.25"
|
||||
modDevGradle = "2.0.95"
|
||||
nullAway = "0.12.7"
|
||||
shadow = "8.3.1"
|
||||
spotless = "6.23.3"
|
||||
spotless = "7.0.2"
|
||||
taskTree = "2.1.1"
|
||||
teavm = "0.11.0-SQUID.1"
|
||||
vanillaExtract = "0.2.0"
|
||||
teavm = "0.13.0-SQUID.1"
|
||||
vanillaExtract = "0.2.1"
|
||||
versionCatalogUpdate = "0.8.1"
|
||||
|
||||
[libraries]
|
||||
@@ -86,10 +87,10 @@ checkerFramework = { module = "org.checkerframework:checker-qual", version.ref =
|
||||
cobalt = { module = "cc.tweaked:cobalt", version.ref = "cobalt" }
|
||||
commonsCli = { module = "commons-cli:commons-cli", version.ref = "commonsCli" }
|
||||
fastutil = { module = "it.unimi.dsi:fastutil", version.ref = "fastutil" }
|
||||
forgeSpi = { module = "net.minecraftforge:forgespi", version.ref = "forgeSpi" }
|
||||
neoMergeTool = { module = "net.neoforged:mergetool", version.ref = "neoMergeTool" }
|
||||
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" }
|
||||
jspecify = { module = "org.jspecify:jspecify", version.ref = "jspecify" }
|
||||
jzlib = { module = "com.jcraft:jzlib", version.ref = "jzlib" }
|
||||
kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlin-coroutines" }
|
||||
kotlin-platform = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "kotlin" }
|
||||
@@ -104,25 +105,26 @@ slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
|
||||
|
||||
# Minecraft mods
|
||||
create-fabric = { module = "com.simibubi.create:create-fabric-1.20.1", version.ref = "create-fabric" }
|
||||
create-forge = { module = "com.simibubi.create:create-1.20.1", version.ref = "create-forge" }
|
||||
create-forge = { module = "com.simibubi.create:create-1.21.1", version.ref = "create-forge" }
|
||||
emi = { module = "dev.emi:emi-xplat-mojmap", version.ref = "emi" }
|
||||
fabric-api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabric-api" }
|
||||
fabric-junit = { module = "net.fabricmc:fabric-loader-junit", version.ref = "fabric-loader" }
|
||||
fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" }
|
||||
fabricPermissions = { module = "me.lucko:fabric-permissions-api", version.ref = "fabricPermissions" }
|
||||
iris = { module = "maven.modrinth:iris", version.ref = "iris" }
|
||||
jei-api = { module = "mezz.jei:jei-1.20.1-common-api", version.ref = "jei" }
|
||||
jei-fabric = { module = "mezz.jei:jei-1.20.1-fabric", version.ref = "jei" }
|
||||
jei-forge = { module = "mezz.jei:jei-1.20.1-forge", version.ref = "jei" }
|
||||
iris-fabric = { module = "maven.modrinth:iris", version.ref = "iris-fabric" }
|
||||
iris-forge = { module = "maven.modrinth:iris", version.ref = "iris-forge" }
|
||||
jei-api = { module = "mezz.jei:jei-1.21-common-api", version.ref = "jei" }
|
||||
jei-fabric = { module = "mezz.jei:jei-1.21-fabric", version.ref = "jei" }
|
||||
jei-forge = { module = "mezz.jei:jei-1.21-neoforge", version.ref = "jei" }
|
||||
mixin = { module = "org.spongepowered:mixin", version.ref = "mixin" }
|
||||
mixinExtra = { module = "io.github.llamalad7:mixinextras-common", version.ref = "mixinExtra" }
|
||||
modmenu = { module = "com.terraformersmc:modmenu", version.ref = "modmenu" }
|
||||
moreRed = { module = "commoble.morered:morered-1.20.1", version.ref = "moreRed" }
|
||||
oculus = { module = "maven.modrinth:oculus", version.ref = "oculus" }
|
||||
moreRed = { module = "net.commoble.morered:morered-1.21.1", version.ref = "moreRed" }
|
||||
rei-api = { module = "me.shedaniel:RoughlyEnoughItems-api", version.ref = "rei" }
|
||||
rei-builtin = { module = "me.shedaniel:RoughlyEnoughItems-default-plugin", version.ref = "rei" }
|
||||
rei-fabric = { module = "me.shedaniel:RoughlyEnoughItems-fabric", version.ref = "rei" }
|
||||
rubidium = { module = "maven.modrinth:rubidium", version.ref = "rubidium" }
|
||||
sodium = { module = "maven.modrinth:sodium", version.ref = "sodium" }
|
||||
sodium-fabric = { module = "maven.modrinth:sodium", version.ref = "sodium.fabric" }
|
||||
sodium-forge = { module = "maven.modrinth:sodium", version.ref = "sodium.forge" }
|
||||
|
||||
# Testing
|
||||
hamcrest = { module = "org.hamcrest:hamcrest", version.ref = "hamcrest" }
|
||||
@@ -178,14 +180,14 @@ taskTree = { id = "com.dorongold.task-tree", version.ref = "taskTree" }
|
||||
versionCatalogUpdate = { id = "nl.littlerobots.version-catalog-update", version.ref = "versionCatalogUpdate" }
|
||||
|
||||
[bundles]
|
||||
annotations = ["jsr305", "checkerFramework", "jetbrainsAnnotations"]
|
||||
annotations = ["checkerFramework", "jetbrainsAnnotations", "jspecify"]
|
||||
kotlin = ["kotlin-stdlib", "kotlin-coroutines"]
|
||||
|
||||
# Minecraft
|
||||
externalMods-common = ["jei-api", "nightConfig-core", "nightConfig-toml"]
|
||||
externalMods-forge-compile = ["moreRed", "oculus", "jei-api"]
|
||||
externalMods-common = ["iris-forge", "jei-api", "nightConfig-core", "nightConfig-toml"]
|
||||
externalMods-forge-compile = ["moreRed", "iris-forge", "jei-api"]
|
||||
externalMods-forge-runtime = ["jei-forge"]
|
||||
externalMods-fabric-compile = ["fabricPermissions", "iris", "jei-api", "rei-api", "rei-builtin"]
|
||||
externalMods-fabric-compile = ["fabricPermissions", "iris-fabric", "jei-api", "rei-api", "rei-builtin"]
|
||||
externalMods-fabric-runtime = ["jei-fabric", "modmenu"]
|
||||
|
||||
# Testing
|
||||
|
960
package-lock.json
generated
960
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -12,11 +12,11 @@
|
||||
"tslib": "^2.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-node-resolve": "^15.2.1",
|
||||
"@rollup/plugin-node-resolve": "^16.0.0",
|
||||
"@rollup/plugin-typescript": "^12.0.0",
|
||||
"@rollup/plugin-url": "^8.0.1",
|
||||
"@swc/core": "^1.3.92",
|
||||
"@types/node": "^22.0.0",
|
||||
"@types/node": "^24.0.0",
|
||||
"lightningcss": "^1.22.0",
|
||||
"preact-render-to-string": "^6.2.1",
|
||||
"rehype": "^13.0.0",
|
||||
|
@@ -63,16 +63,25 @@ tasks.javadoc {
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
taglets("cc.tweaked.javadoc.SnippetTaglet")
|
||||
tagletPath(configurations.detachedConfiguration(dependencies.project(":lints")).toList())
|
||||
|
||||
val snippetSources = listOf(":common", ":fabric", ":forge").flatMap {
|
||||
project(it).sourceSets["examples"].allSource.sourceDirectories
|
||||
}
|
||||
inputs.files(snippetSources)
|
||||
jFlags("-Dcc.snippet-path=" + snippetSources.joinToString(File.pathSeparator) { it.absolutePath })
|
||||
addPathOption("-snippet-path").value = snippetSources
|
||||
}
|
||||
|
||||
// Include the core-api in our javadoc export. This is wrong, but it means we can export a single javadoc dump.
|
||||
source(project(":core-api").sourceSets.main.map { it.allJava })
|
||||
|
||||
options {
|
||||
this as StandardJavadocDocletOptions
|
||||
addBooleanOption("-allow-script-in-comments", true)
|
||||
bottom(
|
||||
"""
|
||||
<script src="https://cdn.jsdelivr.net/npm/prismjs@v1.29.0/components/prism-core.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/prismjs@v1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
|
||||
<link href=" https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism.min.css " rel="stylesheet">
|
||||
""".trimIndent(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@@ -1,43 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.api.client;
|
||||
|
||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
|
||||
import dan200.computercraft.impl.client.ComputerCraftAPIClientService;
|
||||
|
||||
/**
|
||||
* The public API for client-only code.
|
||||
*
|
||||
* @see dan200.computercraft.api.ComputerCraftAPI The main API
|
||||
*/
|
||||
public final class ComputerCraftAPIClient {
|
||||
private ComputerCraftAPIClient() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a {@link TurtleUpgradeModeller} for a class of turtle upgrades.
|
||||
* <p>
|
||||
* This may be called at any point after registry creation, though it is recommended to call it within your client
|
||||
* setup step.
|
||||
*
|
||||
* @param serialiser The turtle upgrade serialiser.
|
||||
* @param modeller The upgrade modeller.
|
||||
* @param <T> The type of the turtle upgrade.
|
||||
* @deprecated This method can lead to confusing load behaviour on Forge. Use
|
||||
* {@code dan200.computercraft.api.client.FabricComputerCraftAPIClient#registerTurtleUpgradeModeller} on Fabric, or
|
||||
* {@code dan200.computercraft.api.client.turtle.RegisterTurtleModellersEvent} on Forge.
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public static <T extends ITurtleUpgrade> void registerTurtleUpgradeModeller(TurtleUpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller) {
|
||||
// TODO(1.20.4): Remove this
|
||||
getInstance().registerTurtleUpgradeModeller(serialiser, modeller);
|
||||
}
|
||||
|
||||
private static ComputerCraftAPIClientService getInstance() {
|
||||
return ComputerCraftAPIClientService.get();
|
||||
}
|
||||
}
|
@@ -0,0 +1,84 @@
|
||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.api.client;
|
||||
|
||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
|
||||
import dan200.computercraft.impl.client.ClientPlatformHelper;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.client.resources.model.ModelManager;
|
||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* The location of a model to load. This may either be:
|
||||
*
|
||||
* <ul>
|
||||
* <li>A {@link ModelResourceLocation}, referencing an already baked model (such as {@code minecraft:dirt#inventory}).</li>
|
||||
* <li>
|
||||
* A {@link ResourceLocation}, referencing a path to a model resource (such as {@code minecraft:item/dirt}.
|
||||
* These models will be baked and stored in the {@link ModelManager} in a loader-specific way.
|
||||
* </li>
|
||||
* </ul>
|
||||
*/
|
||||
public final class ModelLocation {
|
||||
/**
|
||||
* The location of the model.
|
||||
* <p>
|
||||
* When {@link #resourceLocation} is null, this is the location of the model to load. When {@link #resourceLocation}
|
||||
* is non-null, this is the "standalone" variant of the model resource — this is used by NeoForge's implementation
|
||||
* of {@link ClientPlatformHelper#getModel(ModelManager, ModelResourceLocation, ResourceLocation)} to fetch the
|
||||
* model from the model manger. It is not used on Fabric.
|
||||
*/
|
||||
private final ModelResourceLocation modelLocation;
|
||||
private final @Nullable ResourceLocation resourceLocation;
|
||||
|
||||
private ModelLocation(ModelResourceLocation modelLocation, @Nullable ResourceLocation resourceLocation) {
|
||||
this.modelLocation = modelLocation;
|
||||
this.resourceLocation = resourceLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link ModelLocation} from model in the model manager.
|
||||
*
|
||||
* @param location The name of the model to load.
|
||||
* @return The new {@link ModelLocation} instance.
|
||||
*/
|
||||
public static ModelLocation ofModel(ModelResourceLocation location) {
|
||||
return new ModelLocation(location, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link ModelLocation} from a resource.
|
||||
*
|
||||
* @param location The location of the model resource, such as {@code minecraft:item/dirt}.
|
||||
* @return The new {@link ModelLocation} instance.
|
||||
*/
|
||||
public static ModelLocation ofResource(ResourceLocation location) {
|
||||
return new ModelLocation(new ModelResourceLocation(location, "standalone"), location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this model from the provided model manager.
|
||||
*
|
||||
* @param manager The model manger.
|
||||
* @return This model, or the missing model if it could not be found.
|
||||
*/
|
||||
public BakedModel getModel(ModelManager manager) {
|
||||
return ClientPlatformHelper.get().getModel(manager, modelLocation, resourceLocation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the models this model location depends on.
|
||||
*
|
||||
* @return A list of models that this model location depends on.
|
||||
* @see TurtleUpgradeModeller#getDependencies()
|
||||
*/
|
||||
public Stream<ResourceLocation> getDependencies() {
|
||||
return resourceLocation == null ? Stream.empty() : Stream.of(resourceLocation);
|
||||
}
|
||||
}
|
@@ -13,30 +13,47 @@ import net.minecraft.client.resources.model.ModelResourceLocation;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A model to render, combined with a transformation matrix to apply.
|
||||
*
|
||||
* @param model The model.
|
||||
* @param matrix The transformation matrix.
|
||||
*/
|
||||
public final class TransformedModel {
|
||||
private final BakedModel model;
|
||||
private final Transformation matrix;
|
||||
|
||||
public TransformedModel(BakedModel model, Transformation matrix) {
|
||||
this.model = Objects.requireNonNull(model);
|
||||
this.matrix = Objects.requireNonNull(matrix);
|
||||
}
|
||||
|
||||
public record TransformedModel(BakedModel model, Transformation matrix) {
|
||||
public TransformedModel(BakedModel model) {
|
||||
this.model = Objects.requireNonNull(model);
|
||||
matrix = Transformation.identity();
|
||||
this(model, Transformation.identity());
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up a model in the model bakery and construct a {@link TransformedModel} with no transformation.
|
||||
*
|
||||
* @param location The location of the model to load.
|
||||
* @return The new {@link TransformedModel} instance.
|
||||
*/
|
||||
public static TransformedModel of(ModelLocation location) {
|
||||
var modelManager = Minecraft.getInstance().getModelManager();
|
||||
return new TransformedModel(location.getModel(modelManager));
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up a model in the model bakery and construct a {@link TransformedModel} with no transformation.
|
||||
*
|
||||
* @param location The location of the model to load.
|
||||
* @return The new {@link TransformedModel} instance.
|
||||
* @see ModelLocation#ofModel(ModelResourceLocation)
|
||||
*/
|
||||
public static TransformedModel of(ModelResourceLocation location) {
|
||||
var modelManager = Minecraft.getInstance().getModelManager();
|
||||
return new TransformedModel(modelManager.getModel(location));
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up a model in the model bakery and construct a {@link TransformedModel} with no transformation.
|
||||
*
|
||||
* @param location The location of the model to load.
|
||||
* @return The new {@link TransformedModel} instance.
|
||||
* @see ModelLocation#ofResource(ResourceLocation)
|
||||
*/
|
||||
public static TransformedModel of(ResourceLocation location) {
|
||||
var modelManager = Minecraft.getInstance().getModelManager();
|
||||
return new TransformedModel(ClientPlatformHelper.get().getModel(modelManager, location));
|
||||
@@ -46,12 +63,4 @@ public final class TransformedModel {
|
||||
var model = Minecraft.getInstance().getItemRenderer().getItemModelShaper().getItemModel(item);
|
||||
return new TransformedModel(model, transform);
|
||||
}
|
||||
|
||||
public BakedModel getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public Transformation getMatrix() {
|
||||
return matrix;
|
||||
}
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@
|
||||
package dan200.computercraft.api.client.turtle;
|
||||
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
|
||||
import dan200.computercraft.api.upgrades.UpgradeType;
|
||||
|
||||
/**
|
||||
* A functional interface to register a {@link TurtleUpgradeModeller} for a class of turtle upgrades.
|
||||
@@ -18,9 +18,9 @@ public interface RegisterTurtleUpgradeModeller {
|
||||
/**
|
||||
* Register a {@link TurtleUpgradeModeller}.
|
||||
*
|
||||
* @param serialiser The turtle upgrade serialiser.
|
||||
* @param modeller The upgrade modeller.
|
||||
* @param <T> The type of the turtle upgrade.
|
||||
* @param type The turtle upgrade type.
|
||||
* @param modeller The upgrade modeller.
|
||||
* @param <T> The type of the turtle upgrade.
|
||||
*/
|
||||
<T extends ITurtleUpgrade> void register(TurtleUpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller);
|
||||
<T extends ITurtleUpgrade> void register(UpgradeType<T> type, TurtleUpgradeModeller<T> modeller);
|
||||
}
|
||||
|
@@ -4,18 +4,17 @@
|
||||
|
||||
package dan200.computercraft.api.client.turtle;
|
||||
|
||||
import dan200.computercraft.api.client.ModelLocation;
|
||||
import dan200.computercraft.api.client.TransformedModel;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
||||
import net.minecraft.client.resources.model.UnbakedModel;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Provides models for a {@link ITurtleUpgrade}.
|
||||
@@ -38,47 +37,32 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
|
||||
/**
|
||||
* Obtain the model to be used when rendering a turtle peripheral.
|
||||
* <p>
|
||||
* When the current turtle is {@literal null}, this function should be constant for a given upgrade and side.
|
||||
* When the current turtle is {@literal null}, this function should be constant for a given upgrade, side and data.
|
||||
*
|
||||
* @param upgrade The upgrade that you're getting the model for.
|
||||
* @param turtle Access to the turtle that the upgrade resides on. This will be null when getting item models, unless
|
||||
* {@link #getModel(ITurtleUpgrade, CompoundTag, TurtleSide)} is overriden.
|
||||
* @param turtle Access to the turtle that the upgrade resides on. This will be null when getting item models.
|
||||
* @param side Which side of the turtle (left or right) the upgrade resides on.
|
||||
* @return The model that you wish to be used to render your upgrade.
|
||||
*/
|
||||
TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side);
|
||||
|
||||
/**
|
||||
* Obtain the model to be used when rendering a turtle peripheral.
|
||||
* <p>
|
||||
* This is used when rendering the turtle's item model, and so no {@link ITurtleAccess} is available.
|
||||
*
|
||||
* @param upgrade The upgrade that you're getting the model for.
|
||||
* @param data Upgrade data instance for current turtle side.
|
||||
* @param side Which side of the turtle (left or right) the upgrade resides on.
|
||||
* @return The model that you wish to be used to render your upgrade.
|
||||
*/
|
||||
default TransformedModel getModel(T upgrade, CompoundTag data, TurtleSide side) {
|
||||
return getModel(upgrade, (ITurtleAccess) null, side);
|
||||
}
|
||||
|
||||
TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, DataComponentPatch data);
|
||||
|
||||
/**
|
||||
* Get a list of models that this turtle modeller depends on.
|
||||
* Get the models that this turtle modeller depends on.
|
||||
* <p>
|
||||
* Models included in this list will be loaded and baked alongside item and block models, and so may be referenced
|
||||
* Models included in this stream will be loaded and baked alongside item and block models, and so may be referenced
|
||||
* by {@link TransformedModel#of(ResourceLocation)}. You do not need to override this method if you will load models
|
||||
* by other means.
|
||||
*
|
||||
* @return A list of models that this modeller depends on.
|
||||
* @see UnbakedModel#getDependencies()
|
||||
*/
|
||||
default Collection<ResourceLocation> getDependencies() {
|
||||
return List.of();
|
||||
default Stream<ResourceLocation> getDependencies() {
|
||||
return Stream.of();
|
||||
}
|
||||
|
||||
/**
|
||||
* A basic {@link TurtleUpgradeModeller} which renders using the upgrade's {@linkplain ITurtleUpgrade#getUpgradeItem(CompoundTag)}
|
||||
* A basic {@link TurtleUpgradeModeller} which renders using the upgrade's {@linkplain ITurtleUpgrade#getUpgradeItem(DataComponentPatch)}
|
||||
* upgrade item}.
|
||||
* <p>
|
||||
* This uses appropriate transformations for "flat" items, namely those extending the {@literal minecraft:item/generated}
|
||||
@@ -100,9 +84,8 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
|
||||
* @param <T> The type of the turtle upgrade.
|
||||
* @return The constructed modeller.
|
||||
*/
|
||||
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ModelResourceLocation left, ModelResourceLocation right) {
|
||||
// TODO(1.21.0): Remove this.
|
||||
return sided((ResourceLocation) left, right);
|
||||
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ResourceLocation left, ResourceLocation right) {
|
||||
return sided(ModelLocation.ofResource(left), ModelLocation.ofResource(right));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,16 +96,16 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
|
||||
* @param <T> The type of the turtle upgrade.
|
||||
* @return The constructed modeller.
|
||||
*/
|
||||
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ResourceLocation left, ResourceLocation right) {
|
||||
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ModelLocation left, ModelLocation right) {
|
||||
return new TurtleUpgradeModeller<>() {
|
||||
@Override
|
||||
public TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side) {
|
||||
public TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, DataComponentPatch data) {
|
||||
return TransformedModel.of(side == TurtleSide.LEFT ? left : right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ResourceLocation> getDependencies() {
|
||||
return List.of(left, right);
|
||||
public Stream<ResourceLocation> getDependencies() {
|
||||
return Stream.of(left, right).flatMap(ModelLocation::getDependencies);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@@ -11,11 +11,9 @@ import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.impl.client.ClientPlatformHelper;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
import org.joml.Matrix4f;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
final class TurtleUpgradeModellers {
|
||||
private static final Transformation leftTransform = getMatrixFor(-0.4065f);
|
||||
@@ -37,16 +35,8 @@ final class TurtleUpgradeModellers {
|
||||
|
||||
private static final class UpgradeItemModeller implements TurtleUpgradeModeller<ITurtleUpgrade> {
|
||||
@Override
|
||||
public TransformedModel getModel(ITurtleUpgrade upgrade, @Nullable ITurtleAccess turtle, TurtleSide side) {
|
||||
return getModel(turtle == null ? upgrade.getCraftingItem() : upgrade.getUpgradeItem(turtle.getUpgradeNBTData(side)), side);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransformedModel getModel(ITurtleUpgrade upgrade, CompoundTag data, TurtleSide side) {
|
||||
return getModel(upgrade.getUpgradeItem(data), side);
|
||||
}
|
||||
|
||||
private TransformedModel getModel(ItemStack stack, TurtleSide side) {
|
||||
public TransformedModel getModel(ITurtleUpgrade upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, DataComponentPatch data) {
|
||||
var stack = upgrade.getUpgradeItem(data);
|
||||
var model = Minecraft.getInstance().getItemRenderer().getItemModelShaper().getItemModel(stack);
|
||||
if (stack.hasFoil()) model = ClientPlatformHelper.get().createdFoiledModel(model);
|
||||
return new TransformedModel(model, side == TurtleSide.LEFT ? leftTransform : rightTransform);
|
||||
|
@@ -4,6 +4,7 @@
|
||||
|
||||
package dan200.computercraft.impl.client;
|
||||
|
||||
import dan200.computercraft.api.client.ModelLocation;
|
||||
import dan200.computercraft.impl.Services;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
@@ -11,19 +12,33 @@ import net.minecraft.client.resources.model.ModelManager;
|
||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public interface ClientPlatformHelper {
|
||||
/**
|
||||
* Equivalent to {@link ModelManager#getModel(ModelResourceLocation)} but for arbitrary {@link ResourceLocation}s.
|
||||
* Get a model from a resource.
|
||||
*
|
||||
* @param manager The model manager.
|
||||
* @param location The model location.
|
||||
* @param manager The model manager.
|
||||
* @param resourceLocation The model resourceLocation.
|
||||
* @return The baked model.
|
||||
* @see ModelLocation
|
||||
*/
|
||||
BakedModel getModel(ModelManager manager, ResourceLocation location);
|
||||
BakedModel getModel(ModelManager manager, ResourceLocation resourceLocation);
|
||||
|
||||
/**
|
||||
* Set a model from a {@link ModelResourceLocation} or {@link ResourceLocation}.
|
||||
* <p>
|
||||
* This is largely equivalent to {@code resourceLocation == null ? manager.getModel(modelLocation) : getModel(manager, resourceLocation)},
|
||||
* but allows pre-computing {@code modelLocation} (if needed).
|
||||
*
|
||||
* @param manager The model manager.
|
||||
* @param modelLocation The location of the model to load.
|
||||
* @param resourceLocation The location of the resource, if trying to load from a resource.
|
||||
* @return The baked model.
|
||||
* @see ModelLocation
|
||||
*/
|
||||
BakedModel getModel(ModelManager manager, ModelResourceLocation modelLocation, @Nullable ResourceLocation resourceLocation);
|
||||
|
||||
/**
|
||||
* Wrap this model in a version which renders a foil/enchantment glint.
|
||||
|
@@ -26,8 +26,7 @@ import net.minecraft.core.Direction;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* The static entry point to the ComputerCraft API.
|
||||
@@ -148,7 +147,10 @@ public final class ComputerCraftAPI {
|
||||
*
|
||||
* @param provider The media provider to register.
|
||||
* @see MediaProvider
|
||||
* @deprecated Prefer {@code dan200.computercraft.api.media.MediaLookup} (Fabric) or
|
||||
* {@code dan200.computercraft.api.media.MediaCapability} (NeoForge).
|
||||
*/
|
||||
@Deprecated
|
||||
public static void registerMediaProvider(MediaProvider provider) {
|
||||
getInstance().registerMediaProvider(provider);
|
||||
}
|
||||
|
@@ -6,10 +6,12 @@ package dan200.computercraft.api;
|
||||
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.tags.ItemTags;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.context.UseOnContext;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
@@ -26,6 +28,20 @@ public class ComputerCraftTags {
|
||||
public static final TagKey<Item> WIRED_MODEM = make("wired_modem");
|
||||
public static final TagKey<Item> MONITOR = make("monitor");
|
||||
|
||||
/**
|
||||
* Floppy disks. Both the read/write version, and treasure disks.
|
||||
*
|
||||
* @since 1.116.0
|
||||
*/
|
||||
public static final TagKey<Item> DISKS = make("disks");
|
||||
|
||||
/**
|
||||
* All pocket computers.
|
||||
*
|
||||
* @since 1.116.0
|
||||
*/
|
||||
public static final TagKey<Item> POCKET_COMPUTERS = make("pocket_computers");
|
||||
|
||||
/**
|
||||
* Items which can be {@linkplain Item#use(Level, Player, InteractionHand) used} when calling
|
||||
* {@code turtle.place()}.
|
||||
@@ -35,8 +51,16 @@ public class ComputerCraftTags {
|
||||
*/
|
||||
public static final TagKey<Item> TURTLE_CAN_PLACE = make("turtle_can_place");
|
||||
|
||||
/**
|
||||
* Items which can be dyed.
|
||||
* <p>
|
||||
* This is similar to {@link ItemTags#DYEABLE}, but allows cleaning the item with a sponge, rather than in a
|
||||
* cauldron.
|
||||
*/
|
||||
public static final TagKey<Item> DYEABLE = make("dyeable");
|
||||
|
||||
private static TagKey<Item> make(String name) {
|
||||
return TagKey.create(Registries.ITEM, new ResourceLocation(ComputerCraftAPI.MOD_ID, name));
|
||||
return TagKey.create(Registries.ITEM, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, name));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,13 +99,13 @@ public class ComputerCraftTags {
|
||||
public static final TagKey<Block> TURTLE_HOE_BREAKABLE = make("turtle_hoe_harvestable");
|
||||
|
||||
/**
|
||||
* Block which can be {@linkplain BlockState#use(Level, Player, InteractionHand, BlockHitResult) used} when
|
||||
* calling {@code turtle.place()}.
|
||||
* Block which can be {@linkplain BlockState#useItemOn(ItemStack, Level, Player, InteractionHand, BlockHitResult) used}
|
||||
* when calling {@code turtle.place()}.
|
||||
*/
|
||||
public static final TagKey<Block> TURTLE_CAN_USE = make("turtle_can_use");
|
||||
|
||||
private static TagKey<Block> make(String name) {
|
||||
return TagKey.create(Registries.BLOCK, new ResourceLocation(ComputerCraftAPI.MOD_ID, name));
|
||||
return TagKey.create(Registries.BLOCK, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -6,8 +6,8 @@ package dan200.computercraft.api.detail;
|
||||
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
@@ -8,8 +8,7 @@ import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* A reference to a block in the world, used by block detail providers.
|
||||
|
@@ -0,0 +1,72 @@
|
||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.api.detail;
|
||||
|
||||
import net.minecraft.core.component.DataComponentHolder;
|
||||
import net.minecraft.core.component.DataComponentType;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* An item detail provider for a specific {@linkplain DataComponentType data component} on {@link ItemStack}s or
|
||||
* other {@link DataComponentHolder}.
|
||||
*
|
||||
* @param <T> The type of the component's contents.
|
||||
*/
|
||||
public abstract class ComponentDetailProvider<T> implements DetailProvider<DataComponentHolder> {
|
||||
private final DataComponentType<T> component;
|
||||
private final @Nullable String namespace;
|
||||
|
||||
/**
|
||||
* Create a new component detail provider. Details will be inserted into a new sub-map named as per {@code namespace}.
|
||||
*
|
||||
* @param component The data component to provide details for.
|
||||
* @param namespace The namespace to use for this provider.
|
||||
*/
|
||||
public ComponentDetailProvider(@Nullable String namespace, DataComponentType<T> component) {
|
||||
Objects.requireNonNull(component);
|
||||
this.component = component;
|
||||
this.namespace = namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new component detail provider. Details will be inserted directly into the results.
|
||||
*
|
||||
* @param component The data component to provide details for.
|
||||
*/
|
||||
public ComponentDetailProvider(DataComponentType<T> component) {
|
||||
this(null, component);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide additional details for the given data component. This method is called by {@code turtle.getItemDetail()}.
|
||||
* New properties should be added to the given {@link Map}, {@code data}.
|
||||
* <p>
|
||||
* This method is always called on the server thread, so it is safe to interact with the world here, but you should
|
||||
* take care to avoid long blocking operations as this will stall the server and other computers.
|
||||
*
|
||||
* @param data The full details to be returned for this item stack. New properties should be added to this map.
|
||||
* @param component The component to provide details for.
|
||||
*/
|
||||
public abstract void provideComponentDetails(Map<? super String, Object> data, T component);
|
||||
|
||||
@Override
|
||||
public final void provideDetails(Map<? super String, Object> data, DataComponentHolder holder) {
|
||||
var value = holder.get(component);
|
||||
if (value == null) return;
|
||||
|
||||
if (namespace == null) {
|
||||
provideComponentDetails(data, value);
|
||||
} else {
|
||||
Map<? super String, Object> child = new HashMap<>();
|
||||
provideComponentDetails(child, value);
|
||||
data.put(namespace, child);
|
||||
}
|
||||
}
|
||||
}
|
@@ -14,6 +14,7 @@ import java.util.Map;
|
||||
*
|
||||
* @param <T> The type of object that this provider can provide details for.
|
||||
* @see DetailRegistry
|
||||
* @see dan200.computercraft.api.detail An overview of the detail system.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface DetailProvider<T> {
|
||||
|
@@ -17,6 +17,7 @@ import java.util.Map;
|
||||
* also in this package.
|
||||
*
|
||||
* @param <T> The type of object that this registry provides details for.
|
||||
* @see dan200.computercraft.api.detail An overview of the detail system.
|
||||
*/
|
||||
@ApiStatus.NonExtendable
|
||||
public interface DetailRegistry<T> {
|
||||
|
@@ -17,6 +17,9 @@ public class VanillaDetailRegistries {
|
||||
* <p>
|
||||
* This instance's {@link DetailRegistry#getBasicDetails(Object)} is thread safe (assuming the stack is immutable)
|
||||
* and may be called from the computer thread.
|
||||
* <p>
|
||||
* This does not have special handling for {@linkplain ItemStack#isEmpty() empty item stacks}, and so the returned
|
||||
* details will be an empty stack of air. Callers should generally check for empty stacks before calling this.
|
||||
*/
|
||||
public static final DetailRegistry<ItemStack> ITEM_STACK = ComputerCraftAPIService.get().getItemStackDetailRegistry();
|
||||
|
||||
|
@@ -0,0 +1,48 @@
|
||||
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
/**
|
||||
* The detail system provides a standard way for mods to return descriptions of common game objects, such as blocks or
|
||||
* items, as well as registering additional detail to be included in those descriptions.
|
||||
* <p>
|
||||
* For instance, the built-in {@code turtle.getItemDetail()} method uses
|
||||
* {@linkplain dan200.computercraft.api.detail.VanillaDetailRegistries#ITEM_STACK in order to provide information about}
|
||||
* the selected item:
|
||||
*
|
||||
* <pre class="language language-lua">{@code
|
||||
* local item = turtle.getItemDetail(nil, true)
|
||||
* --[[
|
||||
* item = {
|
||||
* name = "minecraft:wheat",
|
||||
* displayName = "Wheat",
|
||||
* count = 1,
|
||||
* maxCount = 64,
|
||||
* tags = {},
|
||||
* }
|
||||
* ]]
|
||||
* }</pre>
|
||||
*
|
||||
* <h2>Built-in detail providers</h2>
|
||||
* While you can define your own detail providers (perhaps for types from your own mod), CC comes with several built-in
|
||||
* detail registries for vanilla and mod-loader objects:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link dan200.computercraft.api.detail.VanillaDetailRegistries}, for vanilla objects</li>
|
||||
* <li>{@code dan200.computercraft.api.detail.ForgeDetailRegistries} for Forge-specific objects</li>
|
||||
* <li>{@code dan200.computercraft.api.detail.FabricDetailRegistries} for Fabric-specific objects</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Example: Returning details from methods</h2>
|
||||
* Here we define a {@code getHeldItem()} method for pocket computers which finds the currently held item of the player
|
||||
* and returns it to the user using {@link dan200.computercraft.api.detail.VanillaDetailRegistries#ITEM_STACK} and
|
||||
* {@link dan200.computercraft.api.detail.DetailRegistry#getDetails(java.lang.Object)}.
|
||||
*
|
||||
* {@snippet class=com.example.examplemod.ExamplePocketPeripheral region=details}
|
||||
*
|
||||
* <h2>Example: Registering custom detail registries</h2>
|
||||
* Here we define a new detail provider for items that includes the nutrition and saturation values in the returned object.
|
||||
*
|
||||
* {@snippet class=com.example.examplemod.ExampleMod region=details}
|
||||
*/
|
||||
package dan200.computercraft.api.detail;
|
@@ -9,8 +9,7 @@ import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* An interface passed to {@link ILuaAPIFactory} in order to provide additional information
|
||||
|
@@ -5,8 +5,7 @@
|
||||
package dan200.computercraft.api.lua;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Construct an {@link ILuaAPI} for a computer.
|
||||
|
@@ -7,29 +7,32 @@ package dan200.computercraft.api.media;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.filesystem.Mount;
|
||||
import dan200.computercraft.api.filesystem.WritableMount;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.sounds.SoundEvent;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import net.minecraft.world.item.JukeboxSong;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Represents an item that can be placed in a disk drive and used by a Computer.
|
||||
* <p>
|
||||
* Implement this interface on your {@link Item} class to allow it to be used in the drive. Alternatively, register
|
||||
* a {@link MediaProvider}.
|
||||
* Implement this interface on your {@link Item} class to allow it to be used in the drive, or register via
|
||||
* {@code dan200.computercraft.api.media.MediaLookup} (Fabric) or {@code dan200.computercraft.api.media.MediaCapability}
|
||||
* (NeoForge).
|
||||
*/
|
||||
public interface IMedia {
|
||||
/**
|
||||
* Get a string representing the label of this item. Will be called via {@code disk.getLabel()} in lua.
|
||||
*
|
||||
* @param stack The {@link ItemStack} to inspect.
|
||||
* @param registries The currently loaded registries.
|
||||
* @param stack The {@link ItemStack} to inspect.
|
||||
* @return The label. ie: "Dan's Programs".
|
||||
*/
|
||||
@Nullable
|
||||
String getLabel(ItemStack stack);
|
||||
String getLabel(HolderLookup.Provider registries, ItemStack stack);
|
||||
|
||||
/**
|
||||
* Set a string representing the label of this item. Will be called vi {@code disk.setLabel()} in lua.
|
||||
@@ -43,26 +46,15 @@ public interface IMedia {
|
||||
}
|
||||
|
||||
/**
|
||||
* If this disk represents an item with audio (like a record), get the readable name of the audio track. ie:
|
||||
* "Jonathan Coulton - Still Alive"
|
||||
* If this disk represents an item with audio (like a record), get the corresponding {@link JukeboxSong}.
|
||||
*
|
||||
* @param stack The {@link ItemStack} to modify.
|
||||
* @return The name, or null if this item does not represent an item with audio.
|
||||
* @param registries The currently loaded registries.
|
||||
* @param stack The {@link ItemStack} to query.
|
||||
* @return The song, or null if this item does not represent an item with audio.
|
||||
*/
|
||||
@Nullable
|
||||
default String getAudioTitle(ItemStack stack) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this disk represents an item with audio (like a record), get the resource name of the audio track to play.
|
||||
*
|
||||
* @param stack The {@link ItemStack} to modify.
|
||||
* @return The name, or null if this item does not represent an item with audio.
|
||||
*/
|
||||
@Nullable
|
||||
default SoundEvent getAudio(ItemStack stack) {
|
||||
return null;
|
||||
default Holder<JukeboxSong> getAudio(HolderLookup.Provider registries, ItemStack stack) {
|
||||
return JukeboxSong.fromStack(registries, stack).orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -5,8 +5,7 @@
|
||||
package dan200.computercraft.api.media;
|
||||
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* This interface is used to provide {@link IMedia} implementations for {@link ItemStack}.
|
||||
|
@@ -6,8 +6,8 @@ package dan200.computercraft.api.media;
|
||||
|
||||
import dan200.computercraft.impl.ComputerCraftAPIService;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
|
@@ -1,92 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2018 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.api.network.wired;
|
||||
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A wired network is composed of one of more {@link WiredNode}s, a set of connections between them, and a series
|
||||
* of peripherals.
|
||||
* <p>
|
||||
* Networks from a connected graph. This means there is some path between all nodes on the network. Further more, if
|
||||
* there is some path between two nodes then they must be on the same network. {@link WiredNetwork} will automatically
|
||||
* handle the merging and splitting of networks (and thus changing of available nodes and peripherals) as connections
|
||||
* change.
|
||||
* <p>
|
||||
* This does mean one can not rely on the network remaining consistent between subsequent operations. Consequently,
|
||||
* it is generally preferred to use the methods provided by {@link WiredNode}.
|
||||
*
|
||||
* @see WiredNode#getNetwork()
|
||||
*/
|
||||
@ApiStatus.NonExtendable
|
||||
public interface WiredNetwork {
|
||||
/**
|
||||
* Create a connection between two nodes.
|
||||
* <p>
|
||||
* This should only be used on the server thread.
|
||||
*
|
||||
* @param left The first node to connect
|
||||
* @param right The second node to connect
|
||||
* @return {@code true} if a connection was created or {@code false} if the connection already exists.
|
||||
* @throws IllegalStateException If neither node is on the network.
|
||||
* @throws IllegalArgumentException If {@code left} and {@code right} are equal.
|
||||
* @see WiredNode#connectTo(WiredNode)
|
||||
* @see WiredNetwork#connect(WiredNode, WiredNode)
|
||||
* @deprecated Use {@link WiredNode#connectTo(WiredNode)}
|
||||
*/
|
||||
@Deprecated
|
||||
boolean connect(WiredNode left, WiredNode right);
|
||||
|
||||
/**
|
||||
* Destroy a connection between this node and another.
|
||||
* <p>
|
||||
* This should only be used on the server thread.
|
||||
*
|
||||
* @param left The first node in the connection.
|
||||
* @param right The second node in the connection.
|
||||
* @return {@code true} if a connection was destroyed or {@code false} if no connection exists.
|
||||
* @throws IllegalArgumentException If either node is not on the network.
|
||||
* @throws IllegalArgumentException If {@code left} and {@code right} are equal.
|
||||
* @see WiredNode#disconnectFrom(WiredNode)
|
||||
* @see WiredNetwork#connect(WiredNode, WiredNode)
|
||||
* @deprecated Use {@link WiredNode#disconnectFrom(WiredNode)}
|
||||
*/
|
||||
@Deprecated
|
||||
boolean disconnect(WiredNode left, WiredNode right);
|
||||
|
||||
/**
|
||||
* Sever all connections this node has, removing it from this network.
|
||||
* <p>
|
||||
* This should only be used on the server thread. You should only call this on nodes
|
||||
* that your network element owns.
|
||||
*
|
||||
* @param node The node to remove
|
||||
* @return Whether this node was removed from the network. One cannot remove a node from a network where it is the
|
||||
* only element.
|
||||
* @throws IllegalArgumentException If the node is not in the network.
|
||||
* @see WiredNode#remove()
|
||||
* @deprecated Use {@link WiredNode#remove()}
|
||||
*/
|
||||
@Deprecated
|
||||
boolean remove(WiredNode node);
|
||||
|
||||
/**
|
||||
* Update the peripherals a node provides.
|
||||
* <p>
|
||||
* This should only be used on the server thread. You should only call this on nodes
|
||||
* that your network element owns.
|
||||
*
|
||||
* @param node The node to attach peripherals for.
|
||||
* @param peripherals The new peripherals for this node.
|
||||
* @throws IllegalArgumentException If the node is not in the network.
|
||||
* @see WiredNode#updatePeripherals(Map)
|
||||
* @deprecated Use {@link WiredNode#updatePeripherals(Map)}
|
||||
*/
|
||||
@Deprecated
|
||||
void updatePeripherals(WiredNode node, Map<String, IPeripheral> peripherals);
|
||||
}
|
@@ -11,7 +11,7 @@ import org.jetbrains.annotations.ApiStatus;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Wired nodes act as a layer between {@link WiredElement}s and {@link WiredNetwork}s.
|
||||
* A single node on a wired network.
|
||||
* <p>
|
||||
* Firstly, a node acts as a packet network, capable of sending and receiving modem messages to connected nodes. These
|
||||
* methods may be safely used on any thread.
|
||||
@@ -32,18 +32,6 @@ public interface WiredNode extends PacketNetwork {
|
||||
*/
|
||||
WiredElement getElement();
|
||||
|
||||
/**
|
||||
* The network this node is currently connected to. Note that this may change
|
||||
* after any network operation, so it should not be cached.
|
||||
* <p>
|
||||
* This should only be used on the server thread.
|
||||
*
|
||||
* @return This node's network.
|
||||
* @deprecated Use the connect/disconnect/remove methods on {@link WiredNode}.
|
||||
*/
|
||||
@Deprecated
|
||||
WiredNetwork getNetwork();
|
||||
|
||||
/**
|
||||
* Create a connection from this node to another.
|
||||
* <p>
|
||||
|
@@ -8,10 +8,12 @@ import dan200.computercraft.api.network.PacketSender;
|
||||
|
||||
|
||||
/**
|
||||
* An object on a {@link WiredNetwork} capable of sending packets.
|
||||
* An object on a wired network capable of sending packets.
|
||||
* <p>
|
||||
* Unlike a regular {@link PacketSender}, this must be associated with the node you are attempting to
|
||||
* to send the packet from.
|
||||
*
|
||||
* @see WiredElement
|
||||
*/
|
||||
public interface WiredSender extends PacketSender {
|
||||
/**
|
||||
|
@@ -4,8 +4,7 @@
|
||||
|
||||
package dan200.computercraft.api.pocket;
|
||||
|
||||
import dan200.computercraft.api.upgrades.UpgradeBase;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
|
||||
@@ -15,27 +14,20 @@ import net.minecraft.world.item.ItemStack;
|
||||
* One does not have to use this, but it does provide a convenient template.
|
||||
*/
|
||||
public abstract class AbstractPocketUpgrade implements IPocketUpgrade {
|
||||
private final ResourceLocation id;
|
||||
private final String adjective;
|
||||
private final Component adjective;
|
||||
private final ItemStack stack;
|
||||
|
||||
protected AbstractPocketUpgrade(ResourceLocation id, String adjective, ItemStack stack) {
|
||||
this.id = id;
|
||||
protected AbstractPocketUpgrade(Component adjective, ItemStack stack) {
|
||||
this.adjective = adjective;
|
||||
this.stack = stack;
|
||||
}
|
||||
|
||||
protected AbstractPocketUpgrade(ResourceLocation id, ItemStack stack) {
|
||||
this(id, UpgradeBase.getDefaultAdjective(id), stack);
|
||||
protected AbstractPocketUpgrade(String adjective, ItemStack stack) {
|
||||
this(Component.translatable(adjective), stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ResourceLocation getUpgradeID() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getUnlocalisedAdjective() {
|
||||
public final Component getAdjective() {
|
||||
return adjective;
|
||||
}
|
||||
|
||||
|
@@ -4,19 +4,15 @@
|
||||
|
||||
package dan200.computercraft.api.pocket;
|
||||
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.api.upgrades.UpgradeBase;
|
||||
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Map;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Wrapper class for pocket computers.
|
||||
@@ -87,7 +83,7 @@ public interface IPocketAccess {
|
||||
* Get the currently equipped upgrade.
|
||||
*
|
||||
* @return The currently equipped upgrade.
|
||||
* @see #getUpgradeNBTData()
|
||||
* @see #getUpgradeData()
|
||||
* @see #setUpgrade(UpgradeData)
|
||||
*/
|
||||
@Nullable
|
||||
@@ -109,19 +105,20 @@ public interface IPocketAccess {
|
||||
* This is persisted between computer reboots and chunk loads.
|
||||
*
|
||||
* @return The upgrade's NBT.
|
||||
* @see #updateUpgradeNBTData()
|
||||
* @see UpgradeBase#getUpgradeItem(CompoundTag)
|
||||
* @see #setUpgradeData(DataComponentPatch)
|
||||
* @see UpgradeBase#getUpgradeItem(DataComponentPatch)
|
||||
* @see UpgradeBase#getUpgradeData(ItemStack)
|
||||
* @see #getUpgrade()
|
||||
*/
|
||||
CompoundTag getUpgradeNBTData();
|
||||
DataComponentPatch getUpgradeData();
|
||||
|
||||
/**
|
||||
* Mark the upgrade-specific NBT as dirty.
|
||||
* Update the upgrade-specific data.
|
||||
*
|
||||
* @see #getUpgradeNBTData()
|
||||
* @param data The new upgrade data.
|
||||
* @see #getUpgradeData()
|
||||
*/
|
||||
void updateUpgradeNBTData();
|
||||
void setUpgradeData(DataComponentPatch data);
|
||||
|
||||
/**
|
||||
* Remove the current peripheral and create a new one.
|
||||
@@ -130,13 +127,4 @@ public interface IPocketAccess {
|
||||
* entity} changes.
|
||||
*/
|
||||
void invalidatePeripheral();
|
||||
|
||||
/**
|
||||
* Get a list of all upgrades for the pocket computer.
|
||||
*
|
||||
* @return A collection of all upgrade names.
|
||||
* @deprecated This is a relic of a previous API, which no longer makes sense with newer versions of ComputerCraft.
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
Map<ResourceLocation, IPeripheral> getUpgrades();
|
||||
}
|
||||
|
@@ -4,22 +4,46 @@
|
||||
|
||||
package dan200.computercraft.api.pocket;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.api.upgrades.UpgradeBase;
|
||||
import dan200.computercraft.api.upgrades.UpgradeType;
|
||||
import dan200.computercraft.impl.ComputerCraftAPIService;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* A peripheral which can be equipped to the back side of a pocket computer.
|
||||
* <p>
|
||||
* Pocket upgrades are defined in two stages. First, on creates a {@link IPocketUpgrade} subclass and corresponding
|
||||
* {@link PocketUpgradeSerialiser} instance, which are then registered in a Minecraft registry.
|
||||
* Pocket upgrades are defined in two stages. First, one creates a {@link IPocketUpgrade} subclass and corresponding
|
||||
* {@link UpgradeType} instance, which are then registered in a Minecraft registry.
|
||||
* <p>
|
||||
* You then write a JSON file in your mod's {@literal data/} folder. This is then parsed when the world is loaded, and
|
||||
* the upgrade registered internally.
|
||||
*/
|
||||
public interface IPocketUpgrade extends UpgradeBase {
|
||||
ResourceKey<Registry<IPocketUpgrade>> REGISTRY = ResourceKey.createRegistryKey(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "pocket_upgrade"));
|
||||
|
||||
/**
|
||||
* The registry key for pocket upgrade types.
|
||||
*
|
||||
* @return The registry key.
|
||||
*/
|
||||
static ResourceKey<Registry<UpgradeType<? extends IPocketUpgrade>>> typeRegistry() {
|
||||
return ComputerCraftAPIService.get().pocketUpgradeRegistryId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type of this upgrade.
|
||||
*
|
||||
* @return The type of this upgrade.
|
||||
*/
|
||||
@Override
|
||||
UpgradeType<? extends IPocketUpgrade> getType();
|
||||
|
||||
/**
|
||||
* Creates a peripheral for the pocket computer.
|
||||
* <p>
|
||||
|
@@ -1,26 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.api.pocket;
|
||||
|
||||
import dan200.computercraft.api.upgrades.UpgradeDataProvider;
|
||||
import net.minecraft.data.DataGenerator;
|
||||
import net.minecraft.data.PackOutput;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* A data provider to generate pocket computer upgrades.
|
||||
* <p>
|
||||
* This should be subclassed and registered to a {@link DataGenerator.PackGenerator}. Override the
|
||||
* {@link #addUpgrades(Consumer)} function, construct each upgrade, and pass them off to the provided consumer to
|
||||
* generate them.
|
||||
*
|
||||
* @see PocketUpgradeSerialiser
|
||||
*/
|
||||
public abstract class PocketUpgradeDataProvider extends UpgradeDataProvider<IPocketUpgrade, PocketUpgradeSerialiser<?>> {
|
||||
public PocketUpgradeDataProvider(PackOutput output) {
|
||||
super(output, "Pocket Computer Upgrades", "computercraft/pocket_upgrades", PocketUpgradeSerialiser.registryId());
|
||||
}
|
||||
}
|
@@ -1,79 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.api.pocket;
|
||||
|
||||
import dan200.computercraft.api.upgrades.UpgradeBase;
|
||||
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
|
||||
import dan200.computercraft.impl.ComputerCraftAPIService;
|
||||
import dan200.computercraft.impl.upgrades.SerialiserWithCraftingItem;
|
||||
import dan200.computercraft.impl.upgrades.SimpleSerialiser;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Reads a {@link IPocketUpgrade} from disk and reads/writes it to a network packet.
|
||||
* <p>
|
||||
* This follows the same format as {@link dan200.computercraft.api.turtle.TurtleUpgradeSerialiser} - consult the
|
||||
* documentation there for more information.
|
||||
*
|
||||
* @param <T> The type of pocket computer upgrade this is responsible for serialising.
|
||||
* @see IPocketUpgrade
|
||||
* @see PocketUpgradeDataProvider
|
||||
*/
|
||||
public interface PocketUpgradeSerialiser<T extends IPocketUpgrade> extends UpgradeSerialiser<T> {
|
||||
/**
|
||||
* The ID for the associated registry.
|
||||
*
|
||||
* @return The registry key.
|
||||
*/
|
||||
static ResourceKey<Registry<PocketUpgradeSerialiser<?>>> registryId() {
|
||||
return ComputerCraftAPIService.get().pocketUpgradeRegistryId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an upgrade serialiser for a simple upgrade. This is similar to a {@link SimpleCraftingRecipeSerializer},
|
||||
* but for upgrades.
|
||||
* <p>
|
||||
* If you might want to vary the item, it's suggested you use {@link #simpleWithCustomItem(BiFunction)} instead.
|
||||
*
|
||||
* @param factory Generate a new upgrade with a specific ID.
|
||||
* @param <T> The type of the generated upgrade.
|
||||
* @return The serialiser for this upgrade
|
||||
*/
|
||||
static <T extends IPocketUpgrade> PocketUpgradeSerialiser<T> simple(Function<ResourceLocation, T> factory) {
|
||||
final class Impl extends SimpleSerialiser<T> implements PocketUpgradeSerialiser<T> {
|
||||
private Impl(Function<ResourceLocation, T> constructor) {
|
||||
super(constructor);
|
||||
}
|
||||
}
|
||||
|
||||
return new Impl(factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an upgrade serialiser for a simple upgrade whose crafting item can be specified.
|
||||
*
|
||||
* @param factory Generate a new upgrade with a specific ID and crafting item. The returned upgrade's
|
||||
* {@link UpgradeBase#getCraftingItem()} <strong>MUST</strong> equal the provided item.
|
||||
* @param <T> The type of the generated upgrade.
|
||||
* @return The serialiser for this upgrade.
|
||||
* @see #simple(Function) For upgrades whose crafting stack should not vary.
|
||||
*/
|
||||
static <T extends IPocketUpgrade> PocketUpgradeSerialiser<T> simpleWithCustomItem(BiFunction<ResourceLocation, ItemStack, T> factory) {
|
||||
final class Impl extends SerialiserWithCraftingItem<T> implements PocketUpgradeSerialiser<T> {
|
||||
private Impl(BiFunction<ResourceLocation, ItemStack, T> factory) {
|
||||
super(factory);
|
||||
}
|
||||
}
|
||||
|
||||
return new Impl(factory);
|
||||
}
|
||||
}
|
@@ -4,8 +4,7 @@
|
||||
|
||||
package dan200.computercraft.api.turtle;
|
||||
|
||||
import dan200.computercraft.api.upgrades.UpgradeBase;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
|
||||
@@ -15,34 +14,27 @@ import net.minecraft.world.item.ItemStack;
|
||||
* One does not have to use this, but it does provide a convenient template.
|
||||
*/
|
||||
public abstract class AbstractTurtleUpgrade implements ITurtleUpgrade {
|
||||
private final ResourceLocation id;
|
||||
private final TurtleUpgradeType type;
|
||||
private final String adjective;
|
||||
private final Component adjective;
|
||||
private final ItemStack stack;
|
||||
|
||||
protected AbstractTurtleUpgrade(ResourceLocation id, TurtleUpgradeType type, String adjective, ItemStack stack) {
|
||||
this.id = id;
|
||||
protected AbstractTurtleUpgrade(TurtleUpgradeType type, Component adjective, ItemStack stack) {
|
||||
this.type = type;
|
||||
this.adjective = adjective;
|
||||
this.stack = stack;
|
||||
}
|
||||
|
||||
protected AbstractTurtleUpgrade(ResourceLocation id, TurtleUpgradeType type, ItemStack stack) {
|
||||
this(id, type, UpgradeBase.getDefaultAdjective(id), stack);
|
||||
protected AbstractTurtleUpgrade(TurtleUpgradeType type, String adjective, ItemStack stack) {
|
||||
this(type, Component.translatable(adjective), stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ResourceLocation getUpgradeID() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getUnlocalisedAdjective() {
|
||||
public final Component getAdjective() {
|
||||
return adjective;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final TurtleUpgradeType getType() {
|
||||
public final TurtleUpgradeType getUpgradeType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
|
@@ -12,13 +12,12 @@ import dan200.computercraft.api.upgrades.UpgradeBase;
|
||||
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.Level;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* The interface passed to turtle by turtles, providing methods that they can call.
|
||||
@@ -229,37 +228,22 @@ public interface ITurtleAccess {
|
||||
* @param side The side to get the upgrade from.
|
||||
* @return The upgrade on the specified side of the turtle, if there is one.
|
||||
* @see #getUpgradeWithData(TurtleSide)
|
||||
* @see #setUpgradeWithData(TurtleSide, UpgradeData)
|
||||
* @see #setUpgrade(TurtleSide, UpgradeData)
|
||||
*/
|
||||
@Nullable
|
||||
ITurtleUpgrade getUpgrade(TurtleSide side);
|
||||
|
||||
/**
|
||||
* Returns the upgrade on the specified side of the turtle, along with its {@linkplain #getUpgradeNBTData(TurtleSide)
|
||||
* Returns the upgrade on the specified side of the turtle, along with its {@linkplain #getUpgradeData(TurtleSide)
|
||||
* update data}.
|
||||
*
|
||||
* @param side The side to get the upgrade from.
|
||||
* @return The upgrade on the specified side of the turtle, along with its upgrade data, if there is one.
|
||||
* @see #getUpgradeWithData(TurtleSide)
|
||||
* @see #setUpgradeWithData(TurtleSide, UpgradeData)
|
||||
* @see #setUpgrade(TurtleSide, UpgradeData)
|
||||
*/
|
||||
default @Nullable UpgradeData<ITurtleUpgrade> getUpgradeWithData(TurtleSide side) {
|
||||
var upgrade = getUpgrade(side);
|
||||
return upgrade == null ? null : UpgradeData.of(upgrade, getUpgradeNBTData(side));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the upgrade for a given side, resetting peripherals and clearing upgrade specific data.
|
||||
*
|
||||
* @param side The side to set the upgrade on.
|
||||
* @param upgrade The upgrade to set, may be {@code null} to clear.
|
||||
* @see #getUpgrade(TurtleSide)
|
||||
* @deprecated Use {@link #setUpgradeWithData(TurtleSide, UpgradeData)}
|
||||
*/
|
||||
@Deprecated
|
||||
default void setUpgrade(TurtleSide side, @Nullable ITurtleUpgrade upgrade) {
|
||||
setUpgradeWithData(side, upgrade == null ? null : UpgradeData.ofDefault(upgrade));
|
||||
}
|
||||
@Nullable
|
||||
UpgradeData<ITurtleUpgrade> getUpgradeWithData(TurtleSide side);
|
||||
|
||||
/**
|
||||
* Set the upgrade for a given side and its upgrade data.
|
||||
@@ -268,7 +252,7 @@ public interface ITurtleAccess {
|
||||
* @param upgrade The upgrade to set, may be {@code null} to clear.
|
||||
* @see #getUpgradeWithData(TurtleSide)
|
||||
*/
|
||||
void setUpgradeWithData(TurtleSide side, @Nullable UpgradeData<ITurtleUpgrade> upgrade);
|
||||
void setUpgrade(TurtleSide side, @Nullable UpgradeData<ITurtleUpgrade> upgrade);
|
||||
|
||||
/**
|
||||
* Returns the peripheral created by the upgrade on the specified side of the turtle, if there is one.
|
||||
@@ -282,23 +266,23 @@ public interface ITurtleAccess {
|
||||
/**
|
||||
* Get an upgrade-specific NBT compound, which can be used to store arbitrary data.
|
||||
* <p>
|
||||
* This will be persisted across turtle restarts and chunk loads, as well as being synced to the client. You must
|
||||
* call {@link #updateUpgradeNBTData(TurtleSide)} after modifying it.
|
||||
* This will be persisted across turtle restarts and chunk loads, as well as being synced to the client. You can
|
||||
* call {@link #setUpgrade(TurtleSide, UpgradeData)} to modify it.
|
||||
*
|
||||
* @param side The side to get the upgrade data for.
|
||||
* @return The upgrade-specific data.
|
||||
* @see #updateUpgradeNBTData(TurtleSide)
|
||||
* @see UpgradeBase#getUpgradeItem(CompoundTag)
|
||||
* @see #setUpgradeData(TurtleSide, DataComponentPatch)
|
||||
* @see UpgradeBase#getUpgradeItem(DataComponentPatch)
|
||||
* @see UpgradeBase#getUpgradeData(ItemStack)
|
||||
*/
|
||||
CompoundTag getUpgradeNBTData(TurtleSide side);
|
||||
DataComponentPatch getUpgradeData(TurtleSide side);
|
||||
|
||||
/**
|
||||
* Mark the upgrade-specific data as dirty on a specific side. This is required for the data to be synced to the
|
||||
* client and persisted.
|
||||
* Update the upgrade-specific data.
|
||||
*
|
||||
* @param side The side to mark dirty.
|
||||
* @see #updateUpgradeNBTData(TurtleSide)
|
||||
* @param side The side to set the upgrade data for.
|
||||
* @param data The new upgrade data.
|
||||
* @see #getUpgradeData(TurtleSide)
|
||||
*/
|
||||
void updateUpgradeNBTData(TurtleSide side);
|
||||
void setUpgradeData(TurtleSide side, DataComponentPatch data);
|
||||
}
|
||||
|
@@ -4,38 +4,45 @@
|
||||
|
||||
package dan200.computercraft.api.turtle;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.api.upgrades.UpgradeBase;
|
||||
import dan200.computercraft.api.upgrades.UpgradeType;
|
||||
import dan200.computercraft.impl.ComputerCraftAPIService;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.core.RegistrySetBuilder.PatchedRegistries;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.Items;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* The primary interface for defining an update for Turtles. A turtle update can either be a new tool, or a new
|
||||
* peripheral.
|
||||
* <p>
|
||||
* Turtle upgrades are defined in two stages. First, one creates a {@link ITurtleUpgrade} subclass and corresponding
|
||||
* {@link TurtleUpgradeSerialiser} instance, which are then registered in a Minecraft registry.
|
||||
* {@link UpgradeType} instance, which are then registered in a Minecraft registry.
|
||||
* <p>
|
||||
* You then write a JSON file in your mod's {@literal data/} folder. This is then parsed when the world is loaded, and
|
||||
* the upgrade automatically registered.
|
||||
*
|
||||
* <h2>Example</h2>
|
||||
* <h3>Registering the upgrade serialiser</h3>
|
||||
* <h3>Registering the upgrade type</h3>
|
||||
* First, let's create a new class that implements {@link ITurtleUpgrade}. It is recommended to subclass
|
||||
* {@link AbstractTurtleUpgrade}, as that provides a default implementation of most methods.
|
||||
* <p>
|
||||
*
|
||||
* {@snippet class=com.example.examplemod.ExampleTurtleUpgrade region=body}
|
||||
* <p>
|
||||
* Now we must construct a new upgrade serialiser. In most cases, you can use one of the helper methods
|
||||
* (e.g. {@link TurtleUpgradeSerialiser#simpleWithCustomItem(BiFunction)}), rather than defining your own implementation.
|
||||
*
|
||||
* Now we must construct a new upgrade type. In most cases, you can use one of the helper methods (e.g.
|
||||
* {@link UpgradeType#simpleWithCustomItem(Function)}), rather than defining your own implementation.
|
||||
*
|
||||
* {@snippet class=com.example.examplemod.ExampleMod region=turtle_upgrades}
|
||||
*
|
||||
* We now must register this upgrade serialiser. This is done the same way as you'd register blocks, items, or other
|
||||
* We now must register this upgrade type. This is done the same way as you'd register blocks, items, or other
|
||||
* Minecraft objects. The approach to do this will depend on mod-loader.
|
||||
*
|
||||
* <h4>Fabric</h4>
|
||||
@@ -46,40 +53,80 @@ import java.util.function.BiFunction;
|
||||
*
|
||||
* <h3>Rendering the upgrade</h3>
|
||||
* Next, we need to register a model for our upgrade. This is done by registering a
|
||||
* {@link dan200.computercraft.api.client.turtle.TurtleUpgradeModeller} for your upgrade serialiser.
|
||||
* {@link dan200.computercraft.api.client.turtle.TurtleUpgradeModeller} for your upgrade type.
|
||||
*
|
||||
* <h4>Fabric</h4>
|
||||
* {@snippet class=com.example.examplemod.FabricExampleModClient region=turtle_modellers}
|
||||
*
|
||||
*
|
||||
* <h4>Forge</h4>
|
||||
* {@snippet class=com.example.examplemod.FabricExampleModClient region=turtle_modellers}
|
||||
*
|
||||
* <h3>Registering the upgrade itself</h3>
|
||||
* <h3 id="datagen">Registering the upgrade itself</h3>
|
||||
* Upgrades themselves are loaded from datapacks when a level is loaded. In order to register our new upgrade, we must
|
||||
* create a new JSON file at {@code data/<my_mod>/computercraft/turtle_upgrades/<my_upgrade_id>.json}.
|
||||
* create a new JSON file at {@code data/<my_mod>/computercraft/turtle_upgrade/<my_upgrade_id>.json}.
|
||||
*
|
||||
* {@snippet file = data/examplemod/computercraft/turtle_upgrades/example_turtle_upgrade.json}
|
||||
* {@snippet file=data/examplemod/computercraft/turtle_upgrade/example_turtle_upgrade.json}
|
||||
*
|
||||
* The {@code "type"} field points to the ID of the upgrade serialiser we've just registered, while the other fields
|
||||
* are read by the serialiser itself. As our upgrade was defined with {@link TurtleUpgradeSerialiser#simpleWithCustomItem(BiFunction)}, the
|
||||
* The {@code "type"} field points to the ID of the upgrade type we've just registered, while the other fields are read
|
||||
* by the type itself. As our upgrade was defined with {@link UpgradeType#simpleWithCustomItem(Function)}, the
|
||||
* {@code "item"} field will construct our upgrade with {@link Items#COMPASS}.
|
||||
* <p>
|
||||
* Rather than manually creating the file, it is recommended to data-generators to generate this file. This can be done
|
||||
* with {@link TurtleUpgradeDataProvider}.
|
||||
* Rather than manually creating the file, it is recommended to use data-generators to generate this file. First, we
|
||||
* register our new upgrades into a {@linkplain PatchedRegistries patched registry}.
|
||||
*
|
||||
* {@snippet class=com.example.examplemod.data.TurtleDataProvider region=body}
|
||||
* {@snippet class=com.example.examplemod.data.TurtleUpgradeProvider region=body}
|
||||
*
|
||||
* @see TurtleUpgradeSerialiser Registering a turtle upgrade.
|
||||
* Next, we must write these upgrades to disk. Vanilla does not have complete support for this yet, so this must be done
|
||||
* with mod-loader-specific APIs.
|
||||
*
|
||||
* <h4>Fabric</h4>
|
||||
* {@snippet class=com.example.examplemod.FabricExampleModDataGenerator region=turtle_upgrades}
|
||||
*
|
||||
* <h4>Forge</h4>
|
||||
* {@snippet class=com.example.examplemod.ForgeExampleModDataGenerator region=turtle_upgrades}
|
||||
*/
|
||||
public interface ITurtleUpgrade extends UpgradeBase {
|
||||
/**
|
||||
* The registry in which turtle upgrades are stored.
|
||||
*/
|
||||
ResourceKey<Registry<ITurtleUpgrade>> REGISTRY = ResourceKey.createRegistryKey(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "turtle_upgrade"));
|
||||
|
||||
/**
|
||||
* Create a {@link ResourceKey} for a turtle upgrade given a {@link ResourceLocation}.
|
||||
* <p>
|
||||
* This should only be called from within data generation code. Do not hard code references to your upgrades!
|
||||
*
|
||||
* @param id The id of the turtle upgrade.
|
||||
* @return The upgrade registry key.
|
||||
*/
|
||||
static ResourceKey<ITurtleUpgrade> createKey(ResourceLocation id) {
|
||||
return ResourceKey.create(REGISTRY, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* The registry key for turtle upgrade types.
|
||||
*
|
||||
* @return The registry key.
|
||||
*/
|
||||
static ResourceKey<Registry<UpgradeType<? extends ITurtleUpgrade>>> typeRegistry() {
|
||||
return ComputerCraftAPIService.get().turtleUpgradeRegistryId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type of this upgrade.
|
||||
*
|
||||
* @return The type of this upgrade.
|
||||
*/
|
||||
@Override
|
||||
UpgradeType<? extends ITurtleUpgrade> getType();
|
||||
|
||||
/**
|
||||
* Return whether this turtle adds a tool or a peripheral to the turtle.
|
||||
*
|
||||
* @return The type of upgrade this is.
|
||||
* @see TurtleUpgradeType for the differences between them.
|
||||
*/
|
||||
TurtleUpgradeType getType();
|
||||
TurtleUpgradeType getUpgradeType();
|
||||
|
||||
/**
|
||||
* Will only be called for peripheral upgrades. Creates a peripheral for a turtle being placed using this upgrade.
|
||||
@@ -138,7 +185,7 @@ public interface ITurtleUpgrade extends UpgradeBase {
|
||||
* @param upgradeData Data that currently stored for this upgrade
|
||||
* @return Filtered version of this data.
|
||||
*/
|
||||
default CompoundTag getPersistedData(CompoundTag upgradeData) {
|
||||
default DataComponentPatch getPersistedData(DataComponentPatch upgradeData) {
|
||||
return upgradeData;
|
||||
}
|
||||
}
|
||||
|
@@ -5,8 +5,7 @@
|
||||
package dan200.computercraft.api.turtle;
|
||||
|
||||
import net.minecraft.core.Direction;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Used to indicate the result of executing a turtle command.
|
||||
@@ -60,9 +59,9 @@ public final class TurtleCommandResult {
|
||||
|
||||
private final boolean success;
|
||||
private final @Nullable String errorMessage;
|
||||
private final @Nullable Object[] results;
|
||||
private final @Nullable Object @Nullable [] results;
|
||||
|
||||
private TurtleCommandResult(boolean success, @Nullable String errorMessage, @Nullable Object[] results) {
|
||||
private TurtleCommandResult(boolean success, @Nullable String errorMessage, @Nullable Object @Nullable [] results) {
|
||||
this.success = success;
|
||||
this.errorMessage = errorMessage;
|
||||
this.results = results;
|
||||
@@ -92,8 +91,7 @@ public final class TurtleCommandResult {
|
||||
*
|
||||
* @return The command's result, or {@code null} if it was a failure.
|
||||
*/
|
||||
@Nullable
|
||||
public Object[] getResults() {
|
||||
public @Nullable Object @Nullable [] getResults() {
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,149 @@
|
||||
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.api.turtle;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftTags;
|
||||
import dan200.computercraft.api.upgrades.UpgradeBase;
|
||||
import dan200.computercraft.impl.ComputerCraftAPIService;
|
||||
import dan200.computercraft.impl.upgrades.TurtleToolSpec;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.data.worldgen.BootstrapContext;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.entity.ai.attributes.Attributes;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* A builder for custom turtle tool upgrades.
|
||||
* <p>
|
||||
* This can be used from your <a href="./ITurtleUpgrade.html#datagen">data generator</a> code in order to
|
||||
* register turtle tools for your mod's tools.
|
||||
*
|
||||
* <h2>Example</h2>
|
||||
* {@snippet class=com.example.examplemod.data.TurtleToolProvider region=body}
|
||||
*/
|
||||
public final class TurtleToolBuilder {
|
||||
private final ResourceKey<ITurtleUpgrade> id;
|
||||
private final Item item;
|
||||
private Component adjective;
|
||||
private float damageMultiplier = TurtleToolSpec.DEFAULT_DAMAGE_MULTIPLIER;
|
||||
private @Nullable TagKey<Block> breakable;
|
||||
private boolean allowEnchantments = false;
|
||||
private TurtleToolDurability consumeDurability = TurtleToolDurability.NEVER;
|
||||
|
||||
private TurtleToolBuilder(ResourceKey<ITurtleUpgrade> id, Item item) {
|
||||
this.id = id;
|
||||
adjective = Component.translatable(UpgradeBase.getDefaultAdjective(id.location()));
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
public static TurtleToolBuilder tool(ResourceLocation id, Item item) {
|
||||
return new TurtleToolBuilder(ITurtleUpgrade.createKey(id), item);
|
||||
}
|
||||
|
||||
public static TurtleToolBuilder tool(ResourceKey<ITurtleUpgrade> id, Item item) {
|
||||
return new TurtleToolBuilder(id, item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the id for this turtle tool.
|
||||
*
|
||||
* @return The upgrade id.
|
||||
*/
|
||||
public ResourceKey<ITurtleUpgrade> id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a custom adjective for this tool. By default this takes its adjective from the upgrade id.
|
||||
*
|
||||
* @param adjective The new adjective to use.
|
||||
* @return The tool builder, for further use.
|
||||
*/
|
||||
public TurtleToolBuilder adjective(Component adjective) {
|
||||
this.adjective = adjective;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The amount of damage a swing of this tool will do. This is multiplied by {@link Attributes#ATTACK_DAMAGE} to
|
||||
* get the final damage.
|
||||
*
|
||||
* @param damageMultiplier The damage multiplier.
|
||||
* @return The tool builder, for further use.
|
||||
*/
|
||||
public TurtleToolBuilder damageMultiplier(float damageMultiplier) {
|
||||
this.damageMultiplier = damageMultiplier;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that this upgrade allows items which have been {@linkplain ItemStack#isEnchanted() enchanted} or have
|
||||
* {@linkplain DataComponents#ATTRIBUTE_MODIFIERS custom attribute modifiers}.
|
||||
*
|
||||
* @return The tool builder, for further use.
|
||||
*/
|
||||
public TurtleToolBuilder allowEnchantments() {
|
||||
allowEnchantments = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set when the tool will consume durability.
|
||||
*
|
||||
* @param durability The durability predicate.
|
||||
* @return The tool builder, for further use.
|
||||
*/
|
||||
public TurtleToolBuilder consumeDurability(TurtleToolDurability durability) {
|
||||
consumeDurability = durability;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a list of breakable blocks. If not given, the tool can break all blocks. If given, only blocks
|
||||
* in this tag, those in {@link ComputerCraftTags.Blocks#TURTLE_ALWAYS_BREAKABLE} and "insta-mine" ones can
|
||||
* be broken.
|
||||
*
|
||||
* @param breakable The tag containing all blocks breakable by this item.
|
||||
* @return The tool builder, for further use.
|
||||
* @see ComputerCraftTags.Blocks
|
||||
*/
|
||||
public TurtleToolBuilder breakable(TagKey<Block> breakable) {
|
||||
this.breakable = breakable;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the turtle tool upgrade.
|
||||
*
|
||||
* @return The constructed upgrade.
|
||||
*/
|
||||
public ITurtleUpgrade build() {
|
||||
return ComputerCraftAPIService.get().createTurtleTool(new TurtleToolSpec(
|
||||
adjective,
|
||||
item,
|
||||
damageMultiplier,
|
||||
allowEnchantments,
|
||||
consumeDurability,
|
||||
Optional.ofNullable(breakable)
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build this upgrade and register it for datagen.
|
||||
*
|
||||
* @param upgrades The registry this upgrade should be added to.
|
||||
*/
|
||||
public void register(BootstrapContext<ITurtleUpgrade> upgrades) {
|
||||
upgrades.register(id(), build());
|
||||
}
|
||||
}
|
@@ -4,14 +4,14 @@
|
||||
|
||||
package dan200.computercraft.api.turtle;
|
||||
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.util.StringRepresentable;
|
||||
import net.minecraft.world.entity.EquipmentSlot;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
/**
|
||||
* Indicates if an equipped turtle item will consume durability.
|
||||
*
|
||||
* @see TurtleUpgradeDataProvider.ToolBuilder#consumeDurability(TurtleToolDurability)
|
||||
* @see TurtleToolBuilder#consumeDurability(TurtleToolDurability)
|
||||
*/
|
||||
public enum TurtleToolDurability implements StringRepresentable {
|
||||
/**
|
||||
@@ -21,7 +21,7 @@ public enum TurtleToolDurability implements StringRepresentable {
|
||||
|
||||
/**
|
||||
* The equipped tool consumes durability if it is {@linkplain ItemStack#isEnchanted() enchanted} or has
|
||||
* {@linkplain ItemStack#getAttributeModifiers(EquipmentSlot) custom attribute modifiers}.
|
||||
* {@linkplain DataComponents#ATTRIBUTE_MODIFIERS custom attribute modifiers}.
|
||||
*/
|
||||
WHEN_ENCHANTED("when_enchanted"),
|
||||
|
||||
|
@@ -1,171 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.api.turtle;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.ComputerCraftTags;
|
||||
import dan200.computercraft.api.upgrades.UpgradeDataProvider;
|
||||
import dan200.computercraft.impl.PlatformHelper;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.data.DataGenerator;
|
||||
import net.minecraft.data.PackOutput;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.entity.EquipmentSlot;
|
||||
import net.minecraft.world.entity.ai.attributes.Attributes;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* A data provider to generate turtle upgrades.
|
||||
* <p>
|
||||
* This should be subclassed and registered to a {@link DataGenerator.PackGenerator}. Override the
|
||||
* {@link #addUpgrades(Consumer)} function, construct each upgrade, and pass them off to the provided consumer to
|
||||
* generate them.
|
||||
*
|
||||
* <h2>Example</h2>
|
||||
* {@snippet class=com.example.examplemod.data.TurtleDataProvider region=body}
|
||||
*
|
||||
* @see TurtleUpgradeSerialiser
|
||||
*/
|
||||
public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITurtleUpgrade, TurtleUpgradeSerialiser<?>> {
|
||||
private static final ResourceLocation TOOL_ID = new ResourceLocation(ComputerCraftAPI.MOD_ID, "tool");
|
||||
|
||||
public TurtleUpgradeDataProvider(PackOutput output) {
|
||||
super(output, "Turtle Upgrades", "computercraft/turtle_upgrades", TurtleUpgradeSerialiser.registryId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new turtle tool upgrade, such as a pickaxe or shovel.
|
||||
*
|
||||
* @param id The ID of this tool.
|
||||
* @param item The item used for tool actions. Note, this doesn't inherit all properties of the tool, you may need
|
||||
* to specify {@link ToolBuilder#damageMultiplier(float)} and {@link ToolBuilder#breakable(TagKey)}.
|
||||
* @return A tool builder,
|
||||
*/
|
||||
public final ToolBuilder tool(ResourceLocation id, Item item) {
|
||||
return new ToolBuilder(id, existingSerialiser(TOOL_ID), item);
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for custom turtle tool upgrades.
|
||||
*
|
||||
* @see #tool(ResourceLocation, Item)
|
||||
*/
|
||||
public static class ToolBuilder {
|
||||
private final ResourceLocation id;
|
||||
private final TurtleUpgradeSerialiser<?> serialiser;
|
||||
private final Item toolItem;
|
||||
private @Nullable String adjective;
|
||||
private @Nullable Item craftingItem;
|
||||
private @Nullable Float damageMultiplier = null;
|
||||
private @Nullable TagKey<Block> breakable;
|
||||
private boolean allowEnchantments = false;
|
||||
private TurtleToolDurability consumeDurability = TurtleToolDurability.NEVER;
|
||||
|
||||
ToolBuilder(ResourceLocation id, TurtleUpgradeSerialiser<?> serialiser, Item toolItem) {
|
||||
this.id = id;
|
||||
this.serialiser = serialiser;
|
||||
this.toolItem = toolItem;
|
||||
craftingItem = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a custom adjective for this tool. By default this takes its adjective from the tool item.
|
||||
*
|
||||
* @param adjective The new adjective to use.
|
||||
* @return The tool builder, for further use.
|
||||
*/
|
||||
public ToolBuilder adjective(String adjective) {
|
||||
this.adjective = adjective;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a custom item which is used to craft this upgrade. By default this is the same as the provided tool
|
||||
* item, but you may wish to override it.
|
||||
*
|
||||
* @param craftingItem The item used to craft this upgrade.
|
||||
* @return The tool builder, for further use.
|
||||
*/
|
||||
public ToolBuilder craftingItem(Item craftingItem) {
|
||||
this.craftingItem = craftingItem;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The amount of damage a swing of this tool will do. This is multiplied by {@link Attributes#ATTACK_DAMAGE} to
|
||||
* get the final damage.
|
||||
*
|
||||
* @param damageMultiplier The damage multiplier.
|
||||
* @return The tool builder, for further use.
|
||||
*/
|
||||
public ToolBuilder damageMultiplier(float damageMultiplier) {
|
||||
this.damageMultiplier = damageMultiplier;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that this upgrade allows items which have been {@linkplain ItemStack#isEnchanted() enchanted} or have
|
||||
* {@linkplain ItemStack#getAttributeModifiers(EquipmentSlot) custom attribute modifiers}.
|
||||
*
|
||||
* @return The tool builder, for further use.
|
||||
*/
|
||||
public ToolBuilder allowEnchantments() {
|
||||
allowEnchantments = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set when the tool will consume durability.
|
||||
*
|
||||
* @param durability The durability predicate.
|
||||
* @return The tool builder, for further use.
|
||||
*/
|
||||
public ToolBuilder consumeDurability(TurtleToolDurability durability) {
|
||||
consumeDurability = durability;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a list of breakable blocks. If not given, the tool can break all blocks. If given, only blocks
|
||||
* in this tag, those in {@link ComputerCraftTags.Blocks#TURTLE_ALWAYS_BREAKABLE} and "insta-mine" ones can
|
||||
* be broken.
|
||||
*
|
||||
* @param breakable The tag containing all blocks breakable by this item.
|
||||
* @return The tool builder, for further use.
|
||||
* @see ComputerCraftTags.Blocks
|
||||
*/
|
||||
public ToolBuilder breakable(TagKey<Block> breakable) {
|
||||
this.breakable = breakable;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register this as an upgrade.
|
||||
*
|
||||
* @param add The callback given to {@link #addUpgrades(Consumer)}.
|
||||
*/
|
||||
public void add(Consumer<Upgrade<TurtleUpgradeSerialiser<?>>> add) {
|
||||
add.accept(new Upgrade<>(id, serialiser, s -> {
|
||||
s.addProperty("item", PlatformHelper.get().getRegistryKey(Registries.ITEM, toolItem).toString());
|
||||
if (adjective != null) s.addProperty("adjective", adjective);
|
||||
if (craftingItem != null) {
|
||||
s.addProperty("craftItem", PlatformHelper.get().getRegistryKey(Registries.ITEM, craftingItem).toString());
|
||||
}
|
||||
if (damageMultiplier != null) s.addProperty("damageMultiplier", damageMultiplier);
|
||||
if (breakable != null) s.addProperty("breakable", breakable.location().toString());
|
||||
if (allowEnchantments) s.addProperty("allowEnchantments", true);
|
||||
if (consumeDurability != TurtleToolDurability.NEVER) {
|
||||
s.addProperty("consumeDurability", consumeDurability.getSerializedName());
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,83 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.api.turtle;
|
||||
|
||||
import dan200.computercraft.api.upgrades.UpgradeBase;
|
||||
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
|
||||
import dan200.computercraft.impl.ComputerCraftAPIService;
|
||||
import dan200.computercraft.impl.upgrades.SerialiserWithCraftingItem;
|
||||
import dan200.computercraft.impl.upgrades.SimpleSerialiser;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.crafting.RecipeSerializer;
|
||||
import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Reads a {@link ITurtleUpgrade} from disk and reads/writes it to a network packet.
|
||||
* <p>
|
||||
* These should be registered in a {@link Registry} while the game is loading, much like {@link RecipeSerializer}s.
|
||||
* <p>
|
||||
* If your turtle upgrade doesn't have any associated configurable parameters (like most upgrades), you can use
|
||||
* {@link #simple(Function)} or {@link #simpleWithCustomItem(BiFunction)} to create a basic upgrade serialiser.
|
||||
*
|
||||
* @param <T> The type of turtle upgrade this is responsible for serialising.
|
||||
* @see ITurtleUpgrade
|
||||
* @see TurtleUpgradeDataProvider
|
||||
* @see dan200.computercraft.api.client.turtle.TurtleUpgradeModeller
|
||||
*/
|
||||
public interface TurtleUpgradeSerialiser<T extends ITurtleUpgrade> extends UpgradeSerialiser<T> {
|
||||
/**
|
||||
* The ID for the associated registry.
|
||||
*
|
||||
* @return The registry key.
|
||||
*/
|
||||
static ResourceKey<Registry<TurtleUpgradeSerialiser<?>>> registryId() {
|
||||
return ComputerCraftAPIService.get().turtleUpgradeRegistryId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an upgrade serialiser for a simple upgrade. This is similar to a {@link SimpleCraftingRecipeSerializer},
|
||||
* but for upgrades.
|
||||
* <p>
|
||||
* If you might want to vary the item, it's suggested you use {@link #simpleWithCustomItem(BiFunction)} instead.
|
||||
*
|
||||
* @param factory Generate a new upgrade with a specific ID.
|
||||
* @param <T> The type of the generated upgrade.
|
||||
* @return The serialiser for this upgrade
|
||||
*/
|
||||
static <T extends ITurtleUpgrade> TurtleUpgradeSerialiser<T> simple(Function<ResourceLocation, T> factory) {
|
||||
final class Impl extends SimpleSerialiser<T> implements TurtleUpgradeSerialiser<T> {
|
||||
private Impl(Function<ResourceLocation, T> constructor) {
|
||||
super(constructor);
|
||||
}
|
||||
}
|
||||
|
||||
return new Impl(factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an upgrade serialiser for a simple upgrade whose crafting item can be specified.
|
||||
*
|
||||
* @param factory Generate a new upgrade with a specific ID and crafting item. The returned upgrade's
|
||||
* {@link UpgradeBase#getCraftingItem()} <strong>MUST</strong> equal the provided item.
|
||||
* @param <T> The type of the generated upgrade.
|
||||
* @return The serialiser for this upgrade.
|
||||
* @see #simple(Function) For upgrades whose crafting stack should not vary.
|
||||
*/
|
||||
static <T extends ITurtleUpgrade> TurtleUpgradeSerialiser<T> simpleWithCustomItem(BiFunction<ResourceLocation, ItemStack, T> factory) {
|
||||
final class Impl extends SerialiserWithCraftingItem<T> implements TurtleUpgradeSerialiser<T> {
|
||||
private Impl(BiFunction<ResourceLocation, ItemStack, T> factory) {
|
||||
super(factory);
|
||||
}
|
||||
}
|
||||
|
||||
return new Impl(factory);
|
||||
}
|
||||
}
|
@@ -9,37 +9,34 @@ import dan200.computercraft.api.pocket.IPocketUpgrade;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.impl.PlatformHelper;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Common functionality between {@link ITurtleUpgrade} and {@link IPocketUpgrade}.
|
||||
*/
|
||||
public interface UpgradeBase {
|
||||
/**
|
||||
* Gets a unique identifier representing this type of turtle upgrade. eg: "computercraft:wireless_modem"
|
||||
* or "my_mod:my_upgrade".
|
||||
* <p>
|
||||
* You should use a unique resource domain to ensure this upgrade is uniquely identified.
|
||||
* The upgrade will fail registration if an already used ID is specified.
|
||||
* Get the type of this upgrade.
|
||||
*
|
||||
* @return The unique ID for this upgrade.
|
||||
* @return The type of this upgrade.
|
||||
*/
|
||||
ResourceLocation getUpgradeID();
|
||||
UpgradeType<?> getType();
|
||||
|
||||
/**
|
||||
* Return an unlocalised string to describe this type of computer in item names.
|
||||
* A description of this upgrade for use in item names.
|
||||
* <p>
|
||||
* This should typically be a {@linkplain Component#translatable(String) translation key}, rather than a hard coded
|
||||
* string.
|
||||
* <p>
|
||||
* Examples of built-in adjectives are "Wireless", "Mining" and "Crafty".
|
||||
*
|
||||
* @return The localisation key for this upgrade's adjective.
|
||||
* @return The text component for this upgrade's adjective.
|
||||
*/
|
||||
String getUnlocalisedAdjective();
|
||||
Component getAdjective();
|
||||
|
||||
/**
|
||||
* Return an item stack representing the type of item that a computer must be crafted
|
||||
@@ -57,8 +54,8 @@ public interface UpgradeBase {
|
||||
/**
|
||||
* Returns the item stack representing a currently equipped turtle upgrade.
|
||||
* <p>
|
||||
* While upgrades can store upgrade data ({@link ITurtleAccess#getUpgradeNBTData(TurtleSide)} and
|
||||
* {@link IPocketAccess#getUpgradeNBTData()}}, by default this data is discarded when an upgrade is unequipped,
|
||||
* While upgrades can store upgrade data ({@link ITurtleAccess#getUpgradeData(TurtleSide)} and
|
||||
* {@link IPocketAccess#getUpgradeData()}}, by default this data is discarded when an upgrade is unequipped,
|
||||
* and the original item stack is returned.
|
||||
* <p>
|
||||
* By overriding this method, you can create a new {@link ItemStack} which contains enough data to
|
||||
@@ -70,24 +67,24 @@ public interface UpgradeBase {
|
||||
* @param upgradeData The current upgrade data. This should <strong>NOT</strong> be mutated.
|
||||
* @return The item stack returned when unequipping.
|
||||
*/
|
||||
default ItemStack getUpgradeItem(CompoundTag upgradeData) {
|
||||
default ItemStack getUpgradeItem(DataComponentPatch upgradeData) {
|
||||
return getCraftingItem();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract upgrade data from an {@link ItemStack}.
|
||||
* <p>
|
||||
* This upgrade data will be available with {@link ITurtleAccess#getUpgradeNBTData(TurtleSide)} or
|
||||
* {@link IPocketAccess#getUpgradeNBTData()}.
|
||||
* This upgrade data will be available with {@link ITurtleAccess#getUpgradeData(TurtleSide)} or
|
||||
* {@link IPocketAccess#getUpgradeData()}.
|
||||
* <p>
|
||||
* This should be an inverse to {@link #getUpgradeItem(CompoundTag)}.
|
||||
* This should be an inverse to {@link #getUpgradeItem(DataComponentPatch)}.
|
||||
*
|
||||
* @param stack The stack that was equipped by the turtle or pocket computer. This will have the same item as
|
||||
* {@link #getCraftingItem()}.
|
||||
* @return The upgrade data that should be set on the turtle or pocket computer.
|
||||
*/
|
||||
default CompoundTag getUpgradeData(ItemStack stack) {
|
||||
return new CompoundTag();
|
||||
default DataComponentPatch getUpgradeData(ItemStack stack) {
|
||||
return DataComponentPatch.EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,26 +94,15 @@ public interface UpgradeBase {
|
||||
* the original stack. In order to prevent people losing items with enchantments (or
|
||||
* repairing items with non-0 damage), we impose additional checks on the item.
|
||||
* <p>
|
||||
* The default check requires that any non-capability NBT is exactly the same as the
|
||||
* crafting item, but this may be relaxed for your upgrade.
|
||||
* <p>
|
||||
* This is based on {@code net.minecraftforge.common.crafting.StrictNBTIngredient}'s check.
|
||||
* The default check requires that any NBT is exactly the same as the crafting item,
|
||||
* but this may be relaxed for your upgrade.
|
||||
*
|
||||
* @param stack The stack to check. This is guaranteed to be non-empty and have the same item as
|
||||
* {@link #getCraftingItem()}.
|
||||
* @return If this stack may be used to equip this upgrade.
|
||||
*/
|
||||
default boolean isItemSuitable(ItemStack stack) {
|
||||
var crafting = getCraftingItem();
|
||||
|
||||
// A more expanded form of ItemStack.areShareTagsEqual, but allowing an empty tag to be equal to a
|
||||
// null one.
|
||||
var shareTag = PlatformHelper.get().getShareTag(stack);
|
||||
var craftingShareTag = PlatformHelper.get().getShareTag(crafting);
|
||||
if (shareTag == craftingShareTag) return true;
|
||||
if (shareTag == null) return Objects.requireNonNull(craftingShareTag).isEmpty();
|
||||
if (craftingShareTag == null) return shareTag.isEmpty();
|
||||
return shareTag.equals(craftingShareTag);
|
||||
return ItemStack.isSameItemSameComponents(getCraftingItem(), stack);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,7 +111,7 @@ public interface UpgradeBase {
|
||||
*
|
||||
* @param id The upgrade ID.
|
||||
* @return The generated adjective.
|
||||
* @see #getUnlocalisedAdjective()
|
||||
* @see #getAdjective()
|
||||
*/
|
||||
static String getDefaultAdjective(ResourceLocation id) {
|
||||
return Util.makeDescriptionId("upgrade", id) + ".adjective";
|
||||
|
@@ -6,60 +6,57 @@ package dan200.computercraft.api.upgrades;
|
||||
|
||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* An upgrade (i.e. a {@link ITurtleUpgrade}) and its current upgrade data.
|
||||
* <p>
|
||||
* <strong>IMPORTANT:</strong> The {@link #data()} in an upgrade data is often a reference to the original upgrade data.
|
||||
* Be careful to take a {@linkplain #copy() defensive copy} if you plan to use the data in this upgrade.
|
||||
*
|
||||
* @param upgrade The current upgrade.
|
||||
* @param data The upgrade's data.
|
||||
* @param <T> The type of upgrade, either {@link ITurtleUpgrade} or {@link IPocketUpgrade}.
|
||||
* @param holder The current upgrade holder.
|
||||
* @param data The upgrade's data.
|
||||
* @param <T> The type of upgrade, either {@link ITurtleUpgrade} or {@link IPocketUpgrade}.
|
||||
*/
|
||||
public record UpgradeData<T extends UpgradeBase>(T upgrade, CompoundTag data) {
|
||||
public record UpgradeData<T extends UpgradeBase>(Holder.Reference<T> holder, DataComponentPatch data) {
|
||||
/**
|
||||
* A utility method to construct a new {@link UpgradeData} instance.
|
||||
*
|
||||
* @param upgrade An upgrade.
|
||||
* @param data The upgrade's data.
|
||||
* @param <T> The type of upgrade.
|
||||
* @param holder An upgrade.
|
||||
* @param data The upgrade's data.
|
||||
* @param <T> The type of upgrade.
|
||||
* @return The new {@link UpgradeData} instance.
|
||||
*/
|
||||
public static <T extends UpgradeBase> UpgradeData<T> of(T upgrade, CompoundTag data) {
|
||||
return new UpgradeData<>(upgrade, data);
|
||||
public static <T extends UpgradeBase> UpgradeData<T> of(Holder.Reference<T> holder, DataComponentPatch data) {
|
||||
return new UpgradeData<>(holder, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link UpgradeData} containing the default {@linkplain #data() data} for an upgrade.
|
||||
*
|
||||
* @param upgrade The upgrade instance.
|
||||
* @param <T> The type of upgrade.
|
||||
* @param holder The upgrade instance.
|
||||
* @param <T> The type of upgrade.
|
||||
* @return The default upgrade data.
|
||||
*/
|
||||
public static <T extends UpgradeBase> UpgradeData<T> ofDefault(T upgrade) {
|
||||
return of(upgrade, upgrade.getUpgradeData(upgrade.getCraftingItem()));
|
||||
public static <T extends UpgradeBase> UpgradeData<T> ofDefault(Holder.Reference<T> holder) {
|
||||
var upgrade = holder.value();
|
||||
return of(holder, upgrade.getUpgradeData(upgrade.getCraftingItem()));
|
||||
}
|
||||
|
||||
public UpgradeData {
|
||||
if (!holder.isBound()) throw new IllegalArgumentException("Holder is not bound");
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a copy of a (possibly {@code null}) {@link UpgradeData} instance.
|
||||
* Get the current upgrade.
|
||||
*
|
||||
* @param upgrade The copied upgrade data.
|
||||
* @param <T> The type of upgrade.
|
||||
* @return The newly created upgrade data.
|
||||
* @return The current upgrade.
|
||||
*/
|
||||
@Contract("!null -> !null; null -> null")
|
||||
public static <T extends UpgradeBase> @Nullable UpgradeData<T> copyOf(@Nullable UpgradeData<T> upgrade) {
|
||||
return upgrade == null ? null : upgrade.copy();
|
||||
public T upgrade() {
|
||||
return holder().value();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@linkplain UpgradeBase#getUpgradeItem(CompoundTag) upgrade item} for this upgrade.
|
||||
* Get the {@linkplain UpgradeBase#getUpgradeItem(DataComponentPatch) upgrade item} for this upgrade.
|
||||
* <p>
|
||||
* This returns a defensive copy of the item, to prevent accidental mutation of the upgrade data or original
|
||||
* {@linkplain UpgradeBase#getCraftingItem() upgrade stack}.
|
||||
@@ -67,16 +64,6 @@ public record UpgradeData<T extends UpgradeBase>(T upgrade, CompoundTag data) {
|
||||
* @return This upgrade's item.
|
||||
*/
|
||||
public ItemStack getUpgradeItem() {
|
||||
return upgrade.getUpgradeItem(data).copy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a copy of this {@link UpgradeData}. This returns a new instance with the same upgrade and a fresh copy of
|
||||
* the upgrade data.
|
||||
*
|
||||
* @return A copy of the current instance.
|
||||
*/
|
||||
public UpgradeData<T> copy() {
|
||||
return new UpgradeData<>(upgrade(), data().copy());
|
||||
return upgrade().getUpgradeItem(data).copy();
|
||||
}
|
||||
}
|
||||
|
@@ -1,177 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.api.upgrades;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
|
||||
import dan200.computercraft.impl.PlatformHelper;
|
||||
import dan200.computercraft.impl.upgrades.SerialiserWithCraftingItem;
|
||||
import dan200.computercraft.impl.upgrades.SimpleSerialiser;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.data.CachedOutput;
|
||||
import net.minecraft.data.DataProvider;
|
||||
import net.minecraft.data.PackOutput;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.Item;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* A data generator/provider for turtle and pocket computer upgrades. This should not be extended directly, instead see
|
||||
* the other subclasses.
|
||||
*
|
||||
* @param <T> The base class of upgrades.
|
||||
* @param <R> The upgrade serialiser to register for.
|
||||
* @see dan200.computercraft.api.turtle.TurtleUpgradeDataProvider
|
||||
* @see dan200.computercraft.api.pocket.PocketUpgradeDataProvider
|
||||
*/
|
||||
public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends UpgradeSerialiser<? extends T>> implements DataProvider {
|
||||
private final PackOutput output;
|
||||
private final String name;
|
||||
private final String folder;
|
||||
private final ResourceKey<Registry<R>> registry;
|
||||
|
||||
private @Nullable List<T> upgrades;
|
||||
|
||||
protected UpgradeDataProvider(PackOutput output, String name, String folder, ResourceKey<Registry<R>> registry) {
|
||||
this.output = output;
|
||||
this.name = name;
|
||||
this.folder = folder;
|
||||
this.registry = registry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an upgrade using a "simple" serialiser (e.g. {@link TurtleUpgradeSerialiser#simple(Function)}).
|
||||
*
|
||||
* @param id The ID of the upgrade to create.
|
||||
* @param serialiser The simple serialiser.
|
||||
* @return The constructed upgrade, ready to be passed off to {@link #addUpgrades(Consumer)}'s consumer.
|
||||
*/
|
||||
public final Upgrade<R> simple(ResourceLocation id, R serialiser) {
|
||||
if (!(serialiser instanceof SimpleSerialiser)) {
|
||||
throw new IllegalStateException(serialiser + " must be a simple() seriaiser.");
|
||||
}
|
||||
|
||||
return new Upgrade<>(id, serialiser, s -> {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an upgrade using a "simple" serialiser (e.g. {@link TurtleUpgradeSerialiser#simple(Function)}).
|
||||
*
|
||||
* @param id The ID of the upgrade to create.
|
||||
* @param serialiser The simple serialiser.
|
||||
* @param item The crafting upgrade for this item.
|
||||
* @return The constructed upgrade, ready to be passed off to {@link #addUpgrades(Consumer)}'s consumer.
|
||||
*/
|
||||
public final Upgrade<R> simpleWithCustomItem(ResourceLocation id, R serialiser, Item item) {
|
||||
if (!(serialiser instanceof SerialiserWithCraftingItem)) {
|
||||
throw new IllegalStateException(serialiser + " must be a simpleWithCustomItem() serialiser.");
|
||||
}
|
||||
|
||||
return new Upgrade<>(id, serialiser, s ->
|
||||
s.addProperty("item", PlatformHelper.get().getRegistryKey(Registries.ITEM, item).toString())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all turtle or pocket computer upgrades.
|
||||
*
|
||||
* <h4>Example</h4>
|
||||
* {@snippet class=com.example.examplemod.data.TurtleDataProvider region=body}
|
||||
*
|
||||
* @param addUpgrade A callback used to register an upgrade.
|
||||
*/
|
||||
protected abstract void addUpgrades(Consumer<Upgrade<R>> addUpgrade);
|
||||
|
||||
@Override
|
||||
public CompletableFuture<?> run(CachedOutput cache) {
|
||||
var base = output.getOutputFolder().resolve("data");
|
||||
|
||||
Set<ResourceLocation> seen = new HashSet<>();
|
||||
List<T> upgrades = new ArrayList<>();
|
||||
List<CompletableFuture<?>> futures = new ArrayList<>();
|
||||
addUpgrades(upgrade -> {
|
||||
if (!seen.add(upgrade.id())) throw new IllegalStateException("Duplicate upgrade " + upgrade.id());
|
||||
|
||||
var json = new JsonObject();
|
||||
json.addProperty("type", PlatformHelper.get().getRegistryKey(registry, upgrade.serialiser()).toString());
|
||||
upgrade.serialise().accept(json);
|
||||
|
||||
futures.add(DataProvider.saveStable(cache, json, base.resolve(upgrade.id().getNamespace() + "/" + folder + "/" + upgrade.id().getPath() + ".json")));
|
||||
|
||||
try {
|
||||
var result = upgrade.serialiser().fromJson(upgrade.id(), json);
|
||||
upgrades.add(result);
|
||||
} catch (IllegalArgumentException | JsonParseException e) {
|
||||
LOGGER.error("Failed to parse {} {}", name, upgrade.id(), e);
|
||||
}
|
||||
});
|
||||
|
||||
this.upgrades = Collections.unmodifiableList(upgrades);
|
||||
return Util.sequenceFailFast(futures);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public final R existingSerialiser(ResourceLocation id) {
|
||||
var result = PlatformHelper.get().getRegistryObject(registry, id);
|
||||
if (result == null) throw new IllegalArgumentException("No such serialiser " + registry);
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<T> getGeneratedUpgrades() {
|
||||
if (upgrades == null) throw new IllegalStateException("Upgrades have not been generated yet");
|
||||
return upgrades;
|
||||
}
|
||||
|
||||
/**
|
||||
* A constructed upgrade instance, produced {@link #addUpgrades(Consumer)}.
|
||||
*
|
||||
* @param id The ID for this upgrade.
|
||||
* @param serialiser The serialiser which reads and writes this upgrade.
|
||||
* @param serialise Augment the generated JSON with additional fields.
|
||||
* @param <R> The type of upgrade serialiser.
|
||||
*/
|
||||
public record Upgrade<R extends UpgradeSerialiser<?>>(
|
||||
ResourceLocation id, R serialiser, Consumer<JsonObject> serialise
|
||||
) {
|
||||
/**
|
||||
* Convenience method for registering an upgrade.
|
||||
*
|
||||
* @param add The callback given to {@link #addUpgrades(Consumer)}
|
||||
*/
|
||||
public void add(Consumer<Upgrade<R>> add) {
|
||||
add.accept(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new {@link Upgrade} which requires the given mod to be present.
|
||||
* <p>
|
||||
* This uses mod-loader-specific hooks (Forge's crafting conditions and Fabric's resource conditions). If using
|
||||
* this in a multi-loader setup, you must generate resources separately for the two loaders.
|
||||
*
|
||||
* @param modId The id of the mod.
|
||||
* @return A new upgrade instance.
|
||||
*/
|
||||
public Upgrade<R> requireMod(String modId) {
|
||||
return new Upgrade<>(id, serialiser, json -> {
|
||||
PlatformHelper.get().addRequiredModCondition(json, modId);
|
||||
serialise.accept(json);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,52 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.api.upgrades;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
|
||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
|
||||
/**
|
||||
* Base interface for upgrade serialisers. This should generally not be implemented directly, instead implementing one
|
||||
* of {@link TurtleUpgradeSerialiser} or {@link PocketUpgradeSerialiser}.
|
||||
* <p>
|
||||
* However, it may sometimes be useful to implement this if you have some shared logic between upgrade types.
|
||||
*
|
||||
* @param <T> The upgrade that this class can serialise and deserialise.
|
||||
* @see TurtleUpgradeSerialiser
|
||||
* @see PocketUpgradeSerialiser
|
||||
*/
|
||||
public interface UpgradeSerialiser<T extends UpgradeBase> {
|
||||
/**
|
||||
* Read this upgrade from a JSON file in a datapack.
|
||||
*
|
||||
* @param id The ID of this upgrade.
|
||||
* @param object The JSON object to load this upgrade from.
|
||||
* @return The constructed upgrade, with a {@link UpgradeBase#getUpgradeID()} equal to {@code id}.
|
||||
* @see net.minecraft.util.GsonHelper For additional JSON helper methods.
|
||||
*/
|
||||
T fromJson(ResourceLocation id, JsonObject object);
|
||||
|
||||
/**
|
||||
* Read this upgrade from a network packet, sent from the server.
|
||||
*
|
||||
* @param id The ID of this upgrade.
|
||||
* @param buffer The buffer object to read this upgrade from.
|
||||
* @return The constructed upgrade, with a {@link UpgradeBase#getUpgradeID()} equal to {@code id}.
|
||||
*/
|
||||
T fromNetwork(ResourceLocation id, FriendlyByteBuf buffer);
|
||||
|
||||
/**
|
||||
* Write this upgrade to a network packet, to be sent to the client.
|
||||
*
|
||||
* @param buffer The buffer object to write this upgrade to
|
||||
* @param upgrade The upgrade to write.
|
||||
*/
|
||||
void toNetwork(FriendlyByteBuf buffer, T upgrade);
|
||||
|
||||
}
|
@@ -0,0 +1,88 @@
|
||||
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.api.upgrades;
|
||||
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.crafting.Recipe;
|
||||
import net.minecraft.world.level.storage.loot.functions.LootItemFunction;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* The type of a {@linkplain ITurtleUpgrade turtle} or {@linkplain IPocketUpgrade pocket} upgrade.
|
||||
* <p>
|
||||
* Turtle and pocket computer upgrades are registered using Minecraft's dynamic registry system. As a result, they
|
||||
* follow a similar design to other dynamic content, such as {@linkplain Recipe recipes} or {@link LootItemFunction
|
||||
* loot functions}.
|
||||
* <p>
|
||||
* While the {@link ITurtleUpgrade}/{@link IPocketUpgrade} class should contain the core logic of the upgrade, they are
|
||||
* not registered directly. Instead, each upgrade class has a corresponding {@link UpgradeType}, which is responsible
|
||||
* for loading the upgrade from a datapack. The upgrade type should then be registered in its appropriate registry
|
||||
* ({@link ITurtleUpgrade#typeRegistry()}, {@link IPocketUpgrade#typeRegistry()}).
|
||||
* <p>
|
||||
* In order to register the actual upgrade, a JSON file referencing your upgrade type should be added to a datapack. It
|
||||
* is recommended to do this via the data generators.
|
||||
*
|
||||
* <h2 id="datagen">Data Generation</h2>
|
||||
* As turtle and pocket upgrades are just loaded using vanilla's dynamic loaders, one may use the same data generation
|
||||
* tools as you would for any other dynamic registry.
|
||||
* <p>
|
||||
* See <a href="../turtle/ITurtleUpgrade.html#datagen">the turtle upgrade docs</a> for a concrete example.
|
||||
*
|
||||
* @param <T> The upgrade subclass that this upgrade type represents.
|
||||
* @see ITurtleUpgrade
|
||||
* @see IPocketUpgrade
|
||||
*/
|
||||
public interface UpgradeType<T extends UpgradeBase> {
|
||||
/**
|
||||
* The codec to read and write this upgrade from a datapack.
|
||||
*
|
||||
* @return The codec for this upgrade.
|
||||
*/
|
||||
MapCodec<T> codec();
|
||||
|
||||
/**
|
||||
* Create a new upgrade type.
|
||||
*
|
||||
* @param codec The codec
|
||||
* @param <T> The type of the generated upgrade.
|
||||
* @return The newly created upgrade type.
|
||||
*/
|
||||
static <T extends UpgradeBase> UpgradeType<T> create(MapCodec<T> codec) {
|
||||
return new UpgradeTypeImpl<>(codec);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an upgrade type for an upgrade that takes no arguments.
|
||||
* <p>
|
||||
* If you might want to vary the item, it's suggested you use {@link #simpleWithCustomItem(Function)} instead.
|
||||
*
|
||||
* @param instance Generate a new upgrade with a specific ID.
|
||||
* @param <T> The type of the generated upgrade.
|
||||
* @return A new upgrade type.
|
||||
*/
|
||||
static <T extends UpgradeBase> UpgradeType<T> simple(T instance) {
|
||||
return create(MapCodec.unit(instance));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an upgrade type for a simple upgrade whose crafting item can be specified.
|
||||
*
|
||||
* @param factory Generate a new upgrade with a specific ID and crafting item. The returned upgrade's
|
||||
* {@link UpgradeBase#getCraftingItem()} <strong>MUST</strong> equal the provided item.
|
||||
* @param <T> The type of the generated upgrade.
|
||||
* @return A new upgrade type.
|
||||
* @see #simple(UpgradeBase) For upgrades whose crafting stack should not vary.
|
||||
*/
|
||||
static <T extends UpgradeBase> UpgradeType<T> simpleWithCustomItem(Function<ItemStack, T> factory) {
|
||||
return create(BuiltInRegistries.ITEM.byNameCodec()
|
||||
.xmap(x -> factory.apply(new ItemStack(x)), x -> x.getCraftingItem().getItem())
|
||||
.fieldOf("item"));
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.api.upgrades;
|
||||
|
||||
import com.mojang.serialization.MapCodec;
|
||||
|
||||
/**
|
||||
* Simple implementation of {@link UpgradeType}.
|
||||
*
|
||||
* @param codec The codec to read/write upgrades with.
|
||||
* @param <T> The upgrade subclass that this upgrade type represents.
|
||||
*/
|
||||
record UpgradeTypeImpl<T extends UpgradeBase>(MapCodec<T> codec) implements UpgradeType<T> {
|
||||
}
|
@@ -4,6 +4,7 @@
|
||||
|
||||
package dan200.computercraft.impl;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.detail.BlockReference;
|
||||
import dan200.computercraft.api.detail.DetailRegistry;
|
||||
@@ -16,10 +17,12 @@ import dan200.computercraft.api.media.PrintoutContents;
|
||||
import dan200.computercraft.api.network.PacketNetwork;
|
||||
import dan200.computercraft.api.network.wired.WiredElement;
|
||||
import dan200.computercraft.api.network.wired.WiredNode;
|
||||
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
|
||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
|
||||
import dan200.computercraft.api.redstone.BundledRedstoneProvider;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.turtle.TurtleRefuelHandler;
|
||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
|
||||
import dan200.computercraft.api.upgrades.UpgradeType;
|
||||
import dan200.computercraft.impl.upgrades.TurtleToolSpec;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.Registry;
|
||||
@@ -28,8 +31,7 @@ import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.Level;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Backing interface for {@link ComputerCraftAPI}
|
||||
@@ -68,9 +70,15 @@ public interface ComputerCraftAPIService {
|
||||
|
||||
void registerRefuelHandler(TurtleRefuelHandler handler);
|
||||
|
||||
ResourceKey<Registry<TurtleUpgradeSerialiser<?>>> turtleUpgradeRegistryId();
|
||||
ResourceKey<Registry<UpgradeType<? extends ITurtleUpgrade>>> turtleUpgradeRegistryId();
|
||||
|
||||
ResourceKey<Registry<PocketUpgradeSerialiser<?>>> pocketUpgradeRegistryId();
|
||||
Codec<ITurtleUpgrade> turtleUpgradeCodec();
|
||||
|
||||
ResourceKey<Registry<UpgradeType<? extends IPocketUpgrade>>> pocketUpgradeRegistryId();
|
||||
|
||||
ITurtleUpgrade createTurtleTool(TurtleToolSpec spec);
|
||||
|
||||
Codec<IPocketUpgrade> pocketUpgradeCodec();
|
||||
|
||||
DetailRegistry<ItemStack> getItemStackDetailRegistry();
|
||||
|
||||
|
@@ -1,92 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.impl;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import dan200.computercraft.api.upgrades.UpgradeDataProvider;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Abstraction layer for Forge and Fabric. See implementations for more details.
|
||||
* <p>
|
||||
* Do <strong>NOT</strong> directly reference this class. It exists for internal use by the API.
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
public interface PlatformHelper {
|
||||
/**
|
||||
* Get the current {@link PlatformHelper} instance.
|
||||
*
|
||||
* @return The current instance.
|
||||
*/
|
||||
static PlatformHelper get() {
|
||||
var instance = Instance.INSTANCE;
|
||||
return instance == null ? Services.raise(PlatformHelper.class, Instance.ERROR) : instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the unique ID for a registered object.
|
||||
*
|
||||
* @param registry The registry to look up this object in.
|
||||
* @param object The object to look up.
|
||||
* @param <T> The type of object the registry stores.
|
||||
* @return The registered object's ID.
|
||||
* @throws IllegalArgumentException If the registry or object are not registered.
|
||||
*/
|
||||
<T> ResourceLocation getRegistryKey(ResourceKey<Registry<T>> registry, T object);
|
||||
|
||||
/**
|
||||
* Look up an ID in a registry, returning the registered object.
|
||||
*
|
||||
* @param registry The registry to look up this object in.
|
||||
* @param id The ID to look up.
|
||||
* @param <T> The type of object the registry stores.
|
||||
* @return The resolved registry object.
|
||||
* @throws IllegalArgumentException If the registry or object are not registered.
|
||||
*/
|
||||
<T> T getRegistryObject(ResourceKey<Registry<T>> registry, ResourceLocation id);
|
||||
|
||||
/**
|
||||
* Get the subset of an {@link ItemStack}'s {@linkplain ItemStack#getTag() tag} which is synced to the client.
|
||||
*
|
||||
* @param item The stack.
|
||||
* @return The item's tag.
|
||||
*/
|
||||
@Nullable
|
||||
default CompoundTag getShareTag(ItemStack item) {
|
||||
return item.getTag();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a resource condition which requires a mod to be loaded. This should be used by data providers such as
|
||||
* {@link UpgradeDataProvider}.
|
||||
*
|
||||
* @param object The JSON object we're generating.
|
||||
* @param modId The mod ID that we require.
|
||||
*/
|
||||
void addRequiredModCondition(JsonObject object, String modId);
|
||||
|
||||
final class Instance {
|
||||
static final @Nullable PlatformHelper INSTANCE;
|
||||
static final @Nullable Throwable ERROR;
|
||||
|
||||
static {
|
||||
// We don't want class initialisation to fail here (as that results in confusing errors). Instead, capture
|
||||
// the error and rethrow it when accessing. This should be JITted away in the common case.
|
||||
var helper = Services.tryLoad(PlatformHelper.class);
|
||||
INSTANCE = helper.instance();
|
||||
ERROR = helper.error();
|
||||
}
|
||||
|
||||
private Instance() {
|
||||
}
|
||||
}
|
||||
}
|
@@ -5,8 +5,8 @@
|
||||
package dan200.computercraft.impl;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
|
@@ -5,8 +5,8 @@
|
||||
package dan200.computercraft.impl;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
@@ -1,49 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.impl.upgrades;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import dan200.computercraft.api.upgrades.UpgradeBase;
|
||||
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* Simple serialiser which returns a constant upgrade with a custom crafting item.
|
||||
* <p>
|
||||
* Do <strong>NOT</strong> directly reference this class. It exists for internal use by the API.
|
||||
*
|
||||
* @param <T> The upgrade that this class can serialise and deserialise.
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
public abstract class SerialiserWithCraftingItem<T extends UpgradeBase> implements UpgradeSerialiser<T> {
|
||||
private final BiFunction<ResourceLocation, ItemStack, T> factory;
|
||||
|
||||
protected SerialiserWithCraftingItem(BiFunction<ResourceLocation, ItemStack, T> factory) {
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final T fromJson(ResourceLocation id, JsonObject object) {
|
||||
var item = GsonHelper.getAsItem(object, "item");
|
||||
return factory.apply(id, new ItemStack(item));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final T fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) {
|
||||
var item = buffer.readItem();
|
||||
return factory.apply(id, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void toNetwork(FriendlyByteBuf buffer, T upgrade) {
|
||||
buffer.writeItem(upgrade.getCraftingItem());
|
||||
}
|
||||
}
|
@@ -1,44 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.impl.upgrades;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import dan200.computercraft.api.upgrades.UpgradeBase;
|
||||
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Simple serialiser which returns a constant upgrade.
|
||||
* <p>
|
||||
* Do <strong>NOT</strong> directly reference this class. It exists for internal use by the API.
|
||||
*
|
||||
* @param <T> The upgrade that this class can serialise and deserialise.
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
public abstract class SimpleSerialiser<T extends UpgradeBase> implements UpgradeSerialiser<T> {
|
||||
private final Function<ResourceLocation, T> constructor;
|
||||
|
||||
public SimpleSerialiser(Function<ResourceLocation, T> constructor) {
|
||||
this.constructor = constructor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final T fromJson(ResourceLocation id, JsonObject object) {
|
||||
return constructor.apply(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final T fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) {
|
||||
return constructor.apply(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void toNetwork(FriendlyByteBuf buffer, T upgrade) {
|
||||
}
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.impl.upgrades;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||
import dan200.computercraft.api.turtle.TurtleToolDurability;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.ComponentSerialization;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* The template for a turtle tool.
|
||||
*
|
||||
* @param adjective The adjective for this tool.
|
||||
* @param item The tool used.
|
||||
* @param damageMultiplier The damage multiplier for this tool.
|
||||
* @param allowEnchantments Whether to allow enchantments.
|
||||
* @param consumeDurability When to consume durability.
|
||||
* @param breakable The items breakable by this tool.
|
||||
*/
|
||||
public record TurtleToolSpec(
|
||||
Component adjective,
|
||||
Item item,
|
||||
float damageMultiplier,
|
||||
boolean allowEnchantments,
|
||||
TurtleToolDurability consumeDurability,
|
||||
Optional<TagKey<Block>> breakable
|
||||
) {
|
||||
public static final float DEFAULT_DAMAGE_MULTIPLIER = 3.0f;
|
||||
|
||||
public static final MapCodec<TurtleToolSpec> CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
|
||||
ComponentSerialization.CODEC.fieldOf("adjective").forGetter(TurtleToolSpec::adjective),
|
||||
BuiltInRegistries.ITEM.byNameCodec().fieldOf("item").forGetter(TurtleToolSpec::item),
|
||||
Codec.FLOAT.optionalFieldOf("damageMultiplier", DEFAULT_DAMAGE_MULTIPLIER).forGetter(TurtleToolSpec::damageMultiplier),
|
||||
Codec.BOOL.optionalFieldOf("allowEnchantments", false).forGetter(TurtleToolSpec::allowEnchantments),
|
||||
TurtleToolDurability.CODEC.optionalFieldOf("consumeDurability", TurtleToolDurability.NEVER).forGetter(TurtleToolSpec::consumeDurability),
|
||||
TagKey.codec(Registries.BLOCK).optionalFieldOf("breakable").forGetter(TurtleToolSpec::breakable)
|
||||
).apply(instance, TurtleToolSpec::new));
|
||||
}
|
@@ -64,5 +64,65 @@ dependencies {
|
||||
CC:T), please <a href="https://github.com/cc-tweaked/CC-Tweaked/discussions/new/choose">start a discussion</a> to
|
||||
let me know!
|
||||
|
||||
|
||||
<h1>Updating from Minecraft 1.20.1 to 1.21.1</h1>
|
||||
|
||||
<h2>Peripherals</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<p>
|
||||
On NeoForge, the peripheral capability has migrated to NeoForge's new capability system.
|
||||
<code>dan200.computercraft.api.peripheral.PeripheralCapability</code> can be used to register a peripheral.
|
||||
<code>IPeripheralProvider</code> has also been removed, as capabilities can now be used for arbitrary
|
||||
blocks.
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
{@linkplain dan200.computercraft.api.peripheral Read more on registering peripherals}.
|
||||
|
||||
<h2>Turtle and pocket upgrades</h2>
|
||||
Turtle and pocket upgrades have been migrated to use Minecraft's dynamic registries. While upgrades themselves have not
|
||||
changed much, the interface for registering them is dramatically different.
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<p>
|
||||
<code>TurtleUpgradeSerialiser</code> and <code>PocketUpgradeSerialiser</code> have been unified into a
|
||||
single {@link dan200.computercraft.api.upgrades.UpgradeType} class
|
||||
<ul>
|
||||
<li>
|
||||
Replace <code>TurtleUpgradeSerialiser.registryId()</code> with
|
||||
{@link dan200.computercraft.api.turtle.ITurtleUpgrade#typeRegistry()} and <code>PocketUpgradeSerialiser.registryId()</code>
|
||||
with {@link dan200.computercraft.api.pocket.IPocketUpgrade#typeRegistry()}.
|
||||
<li>
|
||||
Replace all other usages of <code>TurtleUpgradeSerialiser</code> and <code>PocketUpgradeSerialiser</code>
|
||||
with {@link dan200.computercraft.api.upgrades.UpgradeType}.
|
||||
</ul>
|
||||
|
||||
<li>
|
||||
Upgrades are now (de)serialised using codecs, rather than manually reading from JSON and encoding/decoding
|
||||
network packets. Instead of subclassing {@link dan200.computercraft.api.upgrades.UpgradeType}, it is recommended
|
||||
you use {@link dan200.computercraft.api.upgrades.UpgradeType#create} to create a new type from a
|
||||
<code>MapCodec</code>.
|
||||
|
||||
<li>
|
||||
Upgrades are no longer aware of their ID, and so cannot compute their adjective. The adjective must now either
|
||||
be hard-coded, or read as part of the codec.
|
||||
|
||||
<li>
|
||||
The upgrade data providers have been removed, in favour of mod-loaders built-in support for dynamic registries.
|
||||
I'm afraid it's probably easier if you delete your existing upgrade datagen code and start from scratch. See
|
||||
<a href="./dan200/computercraft/api/turtle/ITurtleUpgrade.html#datagen">the <code>ITurtleUpgrade</code>
|
||||
documentation for an example</a>.
|
||||
|
||||
<li>
|
||||
Upgrades now store their additional data ({@link dan200.computercraft.api.turtle.ITurtleAccess#getUpgradeData},
|
||||
{@link dan200.computercraft.api.pocket.IPocketAccess#getUpgradeData()}) as an immutable component map, rather
|
||||
than a compound tag.
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
{@linkplain dan200.computercraft.api.turtle.ITurtleUpgrade Read more on registering turtle upgrades}.
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
@@ -23,7 +23,7 @@ configurations {
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven("https://maven.minecraftforge.net/") {
|
||||
maven("https://maven.neoforged.net/") {
|
||||
content {
|
||||
includeModule("org.spongepowered", "mixin")
|
||||
}
|
||||
@@ -36,8 +36,9 @@ dependencies {
|
||||
api(commonClasses(project(":common-api")))
|
||||
clientApi(clientClasses(project(":common-api")))
|
||||
|
||||
compileOnly(libs.mixin)
|
||||
compileOnly(libs.mixinExtra)
|
||||
compileOnly(libs.bundles.externalMods.common)
|
||||
compileOnly(variantOf(libs.create.forge) { classifier("slim") }) { isTransitive = false }
|
||||
clientCompileOnly(variantOf(libs.emi) { classifier("api") })
|
||||
|
||||
annotationProcessorEverywhere(libs.autoService)
|
||||
|
@@ -38,8 +38,8 @@ import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.HitResult;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
@@ -111,7 +111,7 @@ public final class ClientHooks {
|
||||
*/
|
||||
public static void addBlockDebugInfo(Consumer<String> addText) {
|
||||
var minecraft = Minecraft.getInstance();
|
||||
if (!minecraft.options.renderDebug || minecraft.level == null) return;
|
||||
if (!minecraft.getDebugOverlay().showDebugScreen() || minecraft.level == null) return;
|
||||
if (minecraft.hitResult == null || minecraft.hitResult.getType() != HitResult.Type.BLOCK) return;
|
||||
|
||||
var tile = minecraft.level.getBlockEntity(((BlockHitResult) minecraft.hitResult).getBlockPos());
|
||||
@@ -131,8 +131,8 @@ public final class ClientHooks {
|
||||
}
|
||||
|
||||
private static void addTurtleUpgrade(Consumer<String> out, TurtleBlockEntity turtle, TurtleSide side) {
|
||||
var upgrade = turtle.getUpgrade(side);
|
||||
if (upgrade != null) out.accept(String.format("Upgrade[%s]: %s", side, upgrade.getUpgradeID()));
|
||||
var upgrade = turtle.getAccess().getUpgradeWithData(side);
|
||||
if (upgrade != null) out.accept(String.format("Upgrade[%s]: %s", side, upgrade.holder().key().location()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,7 +141,7 @@ public final class ClientHooks {
|
||||
* @param addText A callback which adds a single line of text.
|
||||
*/
|
||||
public static void addGameDebugInfo(Consumer<String> addText) {
|
||||
if (MonitorBlockEntityRenderer.hasRenderedThisFrame() && Minecraft.getInstance().options.renderDebug) {
|
||||
if (MonitorBlockEntityRenderer.hasRenderedThisFrame() && Minecraft.getInstance().getDebugOverlay().showDebugScreen()) {
|
||||
addText.accept("[CC:T] Monitor renderer: " + MonitorBlockEntityRenderer.currentRenderer());
|
||||
}
|
||||
}
|
||||
|
@@ -22,16 +22,16 @@ import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
|
||||
import dan200.computercraft.core.util.Colour;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.command.CommandComputerCraft;
|
||||
import dan200.computercraft.shared.common.IColouredItem;
|
||||
import dan200.computercraft.shared.computer.core.ComputerState;
|
||||
import dan200.computercraft.shared.computer.core.ServerContext;
|
||||
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
|
||||
import dan200.computercraft.shared.media.items.DiskItem;
|
||||
import dan200.computercraft.shared.media.items.TreasureDiskItem;
|
||||
import dan200.computercraft.shared.turtle.TurtleOverlay;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.color.item.ItemColor;
|
||||
import net.minecraft.client.gui.screens.MenuScreens;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.client.gui.screens.inventory.MenuAccess;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.client.renderer.ShaderInstance;
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
|
||||
@@ -40,16 +40,22 @@ import net.minecraft.client.renderer.item.ClampedItemPropertyFunction;
|
||||
import net.minecraft.client.renderer.item.ItemProperties;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.resources.PreparableReloadListener;
|
||||
import net.minecraft.server.packs.resources.ResourceProvider;
|
||||
import net.minecraft.util.FastColor;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
import net.minecraft.world.inventory.MenuType;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.component.DyedItemColor;
|
||||
import net.minecraft.world.level.ItemLike;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
@@ -63,6 +69,8 @@ import java.util.function.Supplier;
|
||||
* @see ModRegistry The common registry for actual game objects.
|
||||
*/
|
||||
public final class ClientRegistry {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ClientRegistry.class);
|
||||
|
||||
private ClientRegistry() {
|
||||
}
|
||||
|
||||
@@ -83,14 +91,6 @@ public final class ClientRegistry {
|
||||
* @param itemProperties Callback to register item properties.
|
||||
*/
|
||||
public static void registerMainThread(RegisterItemProperty itemProperties) {
|
||||
MenuScreens.<AbstractComputerMenu, ComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.COMPUTER.get(), ComputerScreen::new);
|
||||
MenuScreens.<AbstractComputerMenu, NoTermComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.POCKET_COMPUTER_NO_TERM.get(), NoTermComputerScreen::new);
|
||||
MenuScreens.register(ModRegistry.Menus.TURTLE.get(), TurtleScreen::new);
|
||||
|
||||
MenuScreens.register(ModRegistry.Menus.PRINTER.get(), PrinterScreen::new);
|
||||
MenuScreens.register(ModRegistry.Menus.DISK_DRIVE.get(), DiskDriveScreen::new);
|
||||
MenuScreens.register(ModRegistry.Menus.PRINTOUT.get(), PrintoutScreen::new);
|
||||
|
||||
registerItemProperty(itemProperties, "state",
|
||||
new UnclampedPropertyFunction((stack, world, player, random) -> {
|
||||
var computer = ClientPocketComputers.get(stack);
|
||||
@@ -99,28 +99,41 @@ public final class ClientRegistry {
|
||||
ModRegistry.Items.POCKET_COMPUTER_NORMAL, ModRegistry.Items.POCKET_COMPUTER_ADVANCED
|
||||
);
|
||||
registerItemProperty(itemProperties, "coloured",
|
||||
(stack, world, player, random) -> IColouredItem.getColourBasic(stack) != -1 ? 1 : 0,
|
||||
(stack, world, player, random) -> DyedItemColor.getOrDefault(stack, -1) != -1 ? 1 : 0,
|
||||
ModRegistry.Items.POCKET_COMPUTER_NORMAL, ModRegistry.Items.POCKET_COMPUTER_ADVANCED
|
||||
);
|
||||
}
|
||||
|
||||
public static void registerMenuScreens(RegisterMenuScreen register) {
|
||||
register.<AbstractComputerMenu, ComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.COMPUTER.get(), ComputerScreen::new);
|
||||
register.<AbstractComputerMenu, NoTermComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.POCKET_COMPUTER_NO_TERM.get(), NoTermComputerScreen::new);
|
||||
register.register(ModRegistry.Menus.TURTLE.get(), TurtleScreen::new);
|
||||
|
||||
register.register(ModRegistry.Menus.PRINTER.get(), PrinterScreen::new);
|
||||
register.register(ModRegistry.Menus.DISK_DRIVE.get(), DiskDriveScreen::new);
|
||||
register.register(ModRegistry.Menus.PRINTOUT.get(), PrintoutScreen::new);
|
||||
}
|
||||
|
||||
public interface RegisterMenuScreen {
|
||||
<M extends AbstractContainerMenu, U extends Screen & MenuAccess<M>> void register(MenuType<? extends M> type, MenuScreens.ScreenConstructor<M, U> factory);
|
||||
}
|
||||
|
||||
public static void registerTurtleModellers(RegisterTurtleUpgradeModeller register) {
|
||||
register.register(ModRegistry.TurtleSerialisers.SPEAKER.get(), TurtleUpgradeModeller.sided(
|
||||
new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_left"),
|
||||
new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_right")
|
||||
register.register(ModRegistry.TurtleUpgradeTypes.SPEAKER.get(), TurtleUpgradeModeller.sided(
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_left"),
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_right")
|
||||
));
|
||||
register.register(ModRegistry.TurtleSerialisers.WORKBENCH.get(), TurtleUpgradeModeller.sided(
|
||||
new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_left"),
|
||||
new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_right")
|
||||
register.register(ModRegistry.TurtleUpgradeTypes.WORKBENCH.get(), TurtleUpgradeModeller.sided(
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_left"),
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_right")
|
||||
));
|
||||
register.register(ModRegistry.TurtleSerialisers.WIRELESS_MODEM_NORMAL.get(), new TurtleModemModeller(false));
|
||||
register.register(ModRegistry.TurtleSerialisers.WIRELESS_MODEM_ADVANCED.get(), new TurtleModemModeller(true));
|
||||
register.register(ModRegistry.TurtleSerialisers.TOOL.get(), TurtleUpgradeModeller.flatItem());
|
||||
register.register(ModRegistry.TurtleUpgradeTypes.WIRELESS_MODEM.get(), new TurtleModemModeller());
|
||||
register.register(ModRegistry.TurtleUpgradeTypes.TOOL.get(), TurtleUpgradeModeller.flatItem());
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
private static void registerItemProperty(RegisterItemProperty itemProperties, String name, ClampedItemPropertyFunction getter, Supplier<? extends Item>... items) {
|
||||
var id = new ResourceLocation(ComputerCraftAPI.MOD_ID, name);
|
||||
var id = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, name);
|
||||
for (var item : items) itemProperties.register(item.get(), id, getter);
|
||||
}
|
||||
|
||||
@@ -132,30 +145,25 @@ public final class ClientRegistry {
|
||||
void register(Item item, ResourceLocation name, ClampedItemPropertyFunction property);
|
||||
}
|
||||
|
||||
public static void registerReloadListeners(Consumer<PreparableReloadListener> register, Minecraft minecraft) {
|
||||
register.accept(GuiSprites.initialise(minecraft.getTextureManager()));
|
||||
}
|
||||
|
||||
private static final String[] EXTRA_MODELS = new String[]{
|
||||
"block/turtle_colour",
|
||||
"block/turtle_elf_overlay",
|
||||
"block/turtle_rainbow_overlay",
|
||||
"block/turtle_trans_overlay",
|
||||
private static final ResourceLocation[] EXTRA_MODELS = {
|
||||
TurtleOverlay.ELF_MODEL,
|
||||
TurtleBlockEntityRenderer.COLOUR_TURTLE_MODEL,
|
||||
};
|
||||
|
||||
public static void registerExtraModels(Consumer<ResourceLocation> register) {
|
||||
for (var model : EXTRA_MODELS) register.accept(new ResourceLocation(ComputerCraftAPI.MOD_ID, model));
|
||||
public static void registerExtraModels(Consumer<ResourceLocation> register, Collection<ResourceLocation> extraModels) {
|
||||
for (var model : EXTRA_MODELS) register.accept(model);
|
||||
extraModels.forEach(register);
|
||||
TurtleUpgradeModellers.getDependencies().forEach(register);
|
||||
}
|
||||
|
||||
public static void registerItemColours(BiConsumer<ItemColor, ItemLike> register) {
|
||||
register.accept(
|
||||
(stack, layer) -> layer == 1 ? ((DiskItem) stack.getItem()).getColour(stack) : 0xFFFFFF,
|
||||
(stack, layer) -> layer == 1 ? DyedItemColor.getOrDefault(stack, Colour.WHITE.getARGB()) : -1,
|
||||
ModRegistry.Items.DISK.get()
|
||||
);
|
||||
|
||||
register.accept(
|
||||
(stack, layer) -> layer == 1 ? TreasureDiskItem.getColour(stack) : 0xFFFFFF,
|
||||
(stack, layer) -> layer == 1 ? DyedItemColor.getOrDefault(stack, Colour.BLUE.getARGB()) : -1,
|
||||
ModRegistry.Items.TREASURE_DISK.get()
|
||||
);
|
||||
|
||||
@@ -168,21 +176,32 @@ public final class ClientRegistry {
|
||||
|
||||
private static int getPocketColour(ItemStack stack, int layer) {
|
||||
return switch (layer) {
|
||||
default -> 0xFFFFFF;
|
||||
case 1 -> IColouredItem.getColourBasic(stack); // Frame colour
|
||||
default -> -1;
|
||||
case 1 -> DyedItemColor.getOrDefault(stack, -1); // Frame colour
|
||||
case 2 -> { // Light colour
|
||||
var computer = ClientPocketComputers.get(stack);
|
||||
yield computer == null || computer.getLightState() == -1 ? Colour.BLACK.getHex() : computer.getLightState();
|
||||
yield computer == null || computer.getLightState() == -1 ? Colour.BLACK.getARGB() : FastColor.ARGB32.opaque(computer.getLightState());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static int getTurtleColour(ItemStack stack, int layer) {
|
||||
return layer == 0 ? ((IColouredItem) stack.getItem()).getColour(stack) : 0xFFFFFF;
|
||||
return layer == 0 ? DyedItemColor.getOrDefault(stack, -1) : -1;
|
||||
}
|
||||
|
||||
public static void registerShaders(ResourceProvider resources, BiConsumer<ShaderInstance, Consumer<ShaderInstance>> load) throws IOException {
|
||||
RenderTypes.registerShaders(resources, load);
|
||||
RenderTypes.registerShaders(resources, (name, create, onLoaded) -> {
|
||||
ShaderInstance shader;
|
||||
try {
|
||||
shader = create.get();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to load {}", name, e);
|
||||
onLoaded.accept(null);
|
||||
return;
|
||||
}
|
||||
|
||||
load.accept(shader, onLoaded);
|
||||
});
|
||||
}
|
||||
|
||||
private record UnclampedPropertyFunction(
|
||||
|
@@ -15,8 +15,8 @@ import net.minecraft.client.gui.components.ChatComponent;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.util.Mth;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
@@ -75,7 +75,7 @@ public class ClientTableFormatter implements TableFormatter {
|
||||
|
||||
var tag = createTag(table.getId());
|
||||
if (chat.allMessages.removeIf(guiMessage -> guiMessage.tag() != null && Objects.equals(guiMessage.tag().logTag(), tag.logTag()))) {
|
||||
chat.refreshTrimmedMessage();
|
||||
chat.rescaleChat();
|
||||
}
|
||||
|
||||
TableFormatter.super.display(table);
|
||||
|
@@ -1,20 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.client;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
|
||||
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
|
||||
import dan200.computercraft.impl.client.ComputerCraftAPIClientService;
|
||||
|
||||
@AutoService(ComputerCraftAPIClientService.class)
|
||||
public final class ComputerCraftAPIClientImpl implements ComputerCraftAPIClientService {
|
||||
@Override
|
||||
public <T extends ITurtleUpgrade> void registerTurtleUpgradeModeller(TurtleUpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller) {
|
||||
TurtleUpgradeModellers.register(serialiser, modeller);
|
||||
}
|
||||
}
|
@@ -26,11 +26,11 @@ import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.Files;
|
||||
@@ -127,7 +127,6 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
|
||||
|
||||
@Override
|
||||
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
|
||||
renderBackground(graphics);
|
||||
super.render(graphics, mouseX, mouseY, partialTicks);
|
||||
renderTooltip(graphics, mouseX, mouseY);
|
||||
}
|
||||
|
@@ -6,9 +6,7 @@ package dan200.computercraft.client.gui;
|
||||
|
||||
import dan200.computercraft.client.gui.widgets.ComputerSidebar;
|
||||
import dan200.computercraft.client.gui.widgets.TerminalWidget;
|
||||
import dan200.computercraft.client.render.ComputerBorderRenderer;
|
||||
import dan200.computercraft.client.render.RenderTypes;
|
||||
import dan200.computercraft.client.render.SpriteRenderer;
|
||||
import dan200.computercraft.core.util.Nullability;
|
||||
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.network.chat.Component;
|
||||
@@ -40,14 +38,15 @@ public final class ComputerScreen<T extends AbstractComputerMenu> extends Abstra
|
||||
public void renderBg(GuiGraphics graphics, float partialTicks, int mouseX, int mouseY) {
|
||||
// Draw a border around the terminal
|
||||
var terminal = getTerminal();
|
||||
var spriteRenderer = SpriteRenderer.createForGui(graphics, RenderTypes.GUI_SPRITES);
|
||||
var computerTextures = GuiSprites.getComputerTextures(family);
|
||||
|
||||
ComputerBorderRenderer.render(
|
||||
spriteRenderer, computerTextures,
|
||||
terminal.getX(), terminal.getY(), terminal.getWidth(), terminal.getHeight(), false
|
||||
graphics.blitSprite(
|
||||
computerTextures.border(),
|
||||
terminal.getX() - BORDER, terminal.getY() - BORDER, terminal.getWidth() + BORDER * 2, terminal.getHeight() + BORDER * 2
|
||||
);
|
||||
graphics.blitSprite(
|
||||
Nullability.assertNonNull(computerTextures.sidebar()),
|
||||
leftPos, topPos + sidebarYOffset, AbstractComputerMenu.SIDEBAR_WIDTH, ComputerSidebar.HEIGHT
|
||||
);
|
||||
ComputerSidebar.renderBackground(spriteRenderer, computerTextures, leftPos, topPos + sidebarYOffset);
|
||||
graphics.flush(); // Flush to ensure background textures are drawn before foreground.
|
||||
}
|
||||
}
|
||||
|
@@ -15,7 +15,7 @@ import net.minecraft.world.entity.player.Inventory;
|
||||
* The GUI for disk drives.
|
||||
*/
|
||||
public class DiskDriveScreen extends AbstractContainerScreen<DiskDriveMenu> {
|
||||
private static final ResourceLocation BACKGROUND = new ResourceLocation("computercraft", "textures/gui/disk_drive.png");
|
||||
private static final ResourceLocation BACKGROUND = ResourceLocation.fromNamespaceAndPath("computercraft", "textures/gui/disk_drive.png");
|
||||
|
||||
public DiskDriveScreen(DiskDriveMenu container, Inventory player, Component title) {
|
||||
super(container, player, title);
|
||||
@@ -28,7 +28,6 @@ public class DiskDriveScreen extends AbstractContainerScreen<DiskDriveMenu> {
|
||||
|
||||
@Override
|
||||
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
|
||||
renderBackground(graphics);
|
||||
super.render(graphics, mouseX, mouseY, partialTicks);
|
||||
renderTooltip(graphics, mouseX, mouseY);
|
||||
}
|
||||
|
@@ -7,22 +7,16 @@ package dan200.computercraft.client.gui;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.client.render.ComputerBorderRenderer;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.client.renderer.texture.TextureManager;
|
||||
import net.minecraft.client.resources.TextureAtlasHolder;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Sprite sheet for all GUI texutres in the mod.
|
||||
*/
|
||||
public final class GuiSprites extends TextureAtlasHolder {
|
||||
public static final ResourceLocation SPRITE_SHEET = new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui");
|
||||
public static final ResourceLocation TEXTURE = SPRITE_SHEET.withPath(x -> "textures/atlas/" + x + ".png");
|
||||
|
||||
public final class GuiSprites {
|
||||
public static final ButtonTextures TURNED_OFF = button("turned_off");
|
||||
public static final ButtonTextures TURNED_ON = button("turned_on");
|
||||
public static final ButtonTextures TERMINATE = button("terminate");
|
||||
@@ -32,52 +26,24 @@ public final class GuiSprites extends TextureAtlasHolder {
|
||||
public static final ComputerTextures COMPUTER_COMMAND = computer("command", false, true);
|
||||
public static final ComputerTextures COMPUTER_COLOUR = computer("colour", true, false);
|
||||
|
||||
public static final ResourceLocation TURTLE_NORMAL_SELECTED_SLOT = new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/sprites/turtle_normal_selected_slot");
|
||||
public static final ResourceLocation TURTLE_ADVANCED_SELECTED_SLOT = new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/sprites/turtle_advanced_selected_slot");
|
||||
private GuiSprites() {
|
||||
}
|
||||
|
||||
private static ButtonTextures button(String name) {
|
||||
return new ButtonTextures(
|
||||
new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/sprites/buttons/" + name),
|
||||
new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/sprites/buttons/" + name + "_hover")
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "buttons/" + name),
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "buttons/" + name + "_hover")
|
||||
);
|
||||
}
|
||||
|
||||
private static ComputerTextures computer(String name, boolean pocket, boolean sidebar) {
|
||||
return new ComputerTextures(
|
||||
new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/border_" + name),
|
||||
pocket ? new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/pocket_bottom_" + name) : null,
|
||||
sidebar ? new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/sidebar_" + name) : null
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "gui/border_" + name),
|
||||
pocket ? ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "gui/pocket_bottom_" + name) : null,
|
||||
sidebar ? ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "gui/sidebar_" + name) : null
|
||||
);
|
||||
}
|
||||
|
||||
private static @Nullable GuiSprites instance;
|
||||
|
||||
private GuiSprites(TextureManager textureManager) {
|
||||
super(textureManager, TEXTURE, SPRITE_SHEET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the singleton {@link GuiSprites} instance.
|
||||
*
|
||||
* @param textureManager The current texture manager.
|
||||
* @return The singleton {@link GuiSprites} instance, to register as resource reload listener.
|
||||
*/
|
||||
public static GuiSprites initialise(TextureManager textureManager) {
|
||||
if (instance != null) throw new IllegalStateException("GuiSprites has already been initialised");
|
||||
return instance = new GuiSprites(textureManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup a texture on the atlas.
|
||||
*
|
||||
* @param texture The texture to find.
|
||||
* @return The sprite on the atlas.
|
||||
*/
|
||||
public static TextureAtlasSprite get(ResourceLocation texture) {
|
||||
if (instance == null) throw new IllegalStateException("GuiSprites has not been initialised");
|
||||
return instance.getSprite(texture);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate textures to use for a particular computer family.
|
||||
*
|
||||
@@ -99,12 +65,8 @@ public final class GuiSprites extends TextureAtlasHolder {
|
||||
* @param active The texture for the button when it is active (hovered or focused).
|
||||
*/
|
||||
public record ButtonTextures(ResourceLocation normal, ResourceLocation active) {
|
||||
public TextureAtlasSprite get(boolean active) {
|
||||
return GuiSprites.get(active ? this.active : normal);
|
||||
}
|
||||
|
||||
public Stream<ResourceLocation> textures() {
|
||||
return Stream.of(normal, active);
|
||||
public ResourceLocation get(boolean isActive) {
|
||||
return isActive ? active : normal;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -9,6 +9,7 @@ import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.components.toasts.Toast;
|
||||
import net.minecraft.client.gui.components.toasts.ToastComponent;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.FormattedCharSequence;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
@@ -18,6 +19,7 @@ import java.util.List;
|
||||
* A {@link Toast} implementation which displays an arbitrary message along with an optional {@link ItemStack}.
|
||||
*/
|
||||
public class ItemToast implements Toast {
|
||||
private static final ResourceLocation TEXTURE = ResourceLocation.withDefaultNamespace("toast/recipe");
|
||||
public static final Object TRANSFER_NO_RESPONSE_TOKEN = new Object();
|
||||
|
||||
private static final long DISPLAY_TIME = 7000L;
|
||||
@@ -79,7 +81,7 @@ public class ItemToast implements Toast {
|
||||
}
|
||||
|
||||
if (width == 160 && message.size() <= 1) {
|
||||
graphics.blit(TEXTURE, 0, 0, 0, 64, width, height());
|
||||
graphics.blitSprite(TEXTURE, 0, 0, width, height());
|
||||
} else {
|
||||
|
||||
var height = height();
|
||||
@@ -109,14 +111,14 @@ public class ItemToast implements Toast {
|
||||
}
|
||||
|
||||
private static void renderBackgroundRow(GuiGraphics graphics, int x, int u, int y, int height) {
|
||||
var leftOffset = 5;
|
||||
var leftOffset = u == 0 ? 20 : 5;
|
||||
var rightOffset = Math.min(60, x - leftOffset);
|
||||
|
||||
graphics.blit(TEXTURE, 0, y, 0, 32 + u, leftOffset, height);
|
||||
graphics.blitSprite(TEXTURE, 160, 32, 0, u, 0, y, leftOffset, height);
|
||||
for (var k = leftOffset; k < x - rightOffset; k += 64) {
|
||||
graphics.blit(TEXTURE, k, y, 32, 32 + u, Math.min(64, x - k - rightOffset), height);
|
||||
graphics.blitSprite(TEXTURE, 160, 32, 32, u, k, y, Math.min(64, x - k - rightOffset), height);
|
||||
}
|
||||
|
||||
graphics.blit(TEXTURE, x - rightOffset, y, 160 - rightOffset, 32 + u, rightOffset, height);
|
||||
graphics.blitSprite(TEXTURE, 160, 32, 160 - rightOffset, u, x - rightOffset, y, rightOffset, height);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,40 @@
|
||||
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.client.gui;
|
||||
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
/**
|
||||
* Supports for converting/translating key codes.
|
||||
*/
|
||||
public class KeyConverter {
|
||||
/**
|
||||
* GLFW's key events refer to the physical key code, rather than the "actual" key code (with keyboard layout
|
||||
* applied).
|
||||
* <p>
|
||||
* This makes sense for WASD-style input, but is a right pain for keyboard shortcuts — this function attempts to
|
||||
* translate those keys back to their "actual" key code. See also
|
||||
* <a href="https://github.com/glfw/glfw/issues/1502"> this discussion on GLFW's GitHub.</a>
|
||||
*
|
||||
* @param key The current key code.
|
||||
* @param scanCode The current scan code.
|
||||
* @return The translated key code.
|
||||
*/
|
||||
public static int physicalToActual(int key, int scanCode) {
|
||||
var name = GLFW.glfwGetKeyName(key, scanCode);
|
||||
if (name == null || name.length() != 1) return key;
|
||||
|
||||
// If we've got a single character as the key name, treat that as the ASCII value of the key,
|
||||
// and map that back to a key code.
|
||||
var character = name.charAt(0);
|
||||
|
||||
// 0-9 and A-Z map directly to their GLFW key (they're the same ASCII code).
|
||||
if ((character >= '0' && character <= '9') || (character >= 'A' && character <= 'Z')) return character;
|
||||
// a-z map to GLFW_KEY_{A,Z}
|
||||
if (character >= 'a' && character <= 'z') return GLFW.GLFW_KEY_A + (character - 'a');
|
||||
|
||||
return key;
|
||||
}
|
||||
}
|
@@ -15,9 +15,9 @@ import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.client.gui.screens.inventory.MenuAccess;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Objects;
|
||||
|
||||
import static dan200.computercraft.core.util.Nullability.assertNonNull;
|
||||
@@ -66,9 +66,9 @@ public class NoTermComputerScreen<T extends AbstractComputerMenu> extends Screen
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseScrolled(double pMouseX, double pMouseY, double pDelta) {
|
||||
Objects.requireNonNull(minecraft().player).getInventory().swapPaint(pDelta);
|
||||
return super.mouseScrolled(pMouseX, pMouseY, pDelta);
|
||||
public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, double scrollY) {
|
||||
Objects.requireNonNull(minecraft().player).getInventory().swapPaint(scrollY);
|
||||
return super.mouseScrolled(mouseX, mouseY, scrollX, scrollY);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -105,6 +105,11 @@ public class NoTermComputerScreen<T extends AbstractComputerMenu> extends Screen
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderBackground(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) {
|
||||
// Skip rendering the background.
|
||||
}
|
||||
|
||||
private Minecraft minecraft() {
|
||||
return Nullability.assertNonNull(minecraft);
|
||||
}
|
||||
|
@@ -12,8 +12,8 @@ import net.minecraft.client.gui.components.MultiLineLabel;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.List;
|
||||
|
||||
import static dan200.computercraft.core.util.Nullability.assertNonNull;
|
||||
@@ -24,7 +24,7 @@ import static dan200.computercraft.core.util.Nullability.assertNonNull;
|
||||
* When closed, it returns to the previous screen.
|
||||
*/
|
||||
public final class OptionScreen extends Screen {
|
||||
private static final ResourceLocation BACKGROUND = new ResourceLocation("computercraft", "textures/gui/blank_screen.png");
|
||||
private static final ResourceLocation BACKGROUND = ResourceLocation.fromNamespaceAndPath("computercraft", "textures/gui/blank_screen.png");
|
||||
|
||||
public static final int BUTTON_WIDTH = 100;
|
||||
public static final int BUTTON_HEIGHT = 20;
|
||||
@@ -86,8 +86,6 @@ public final class OptionScreen extends Screen {
|
||||
|
||||
@Override
|
||||
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
|
||||
renderBackground(graphics);
|
||||
|
||||
// Render the actual texture.
|
||||
graphics.blit(BACKGROUND, x, y, 0, 0, innerWidth, PADDING);
|
||||
graphics.blit(BACKGROUND,
|
||||
|
@@ -15,7 +15,7 @@ import net.minecraft.world.entity.player.Inventory;
|
||||
* The GUI for printers.
|
||||
*/
|
||||
public class PrinterScreen extends AbstractContainerScreen<PrinterMenu> {
|
||||
private static final ResourceLocation BACKGROUND = new ResourceLocation("computercraft", "textures/gui/printer.png");
|
||||
private static final ResourceLocation BACKGROUND = ResourceLocation.fromNamespaceAndPath("computercraft", "textures/gui/printer.png");
|
||||
|
||||
public PrinterScreen(PrinterMenu container, Inventory player, Component title) {
|
||||
super(container, player, title);
|
||||
@@ -30,7 +30,6 @@ public class PrinterScreen extends AbstractContainerScreen<PrinterMenu> {
|
||||
|
||||
@Override
|
||||
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
|
||||
renderBackground(graphics);
|
||||
super.render(graphics, mouseX, mouseY, partialTicks);
|
||||
renderTooltip(graphics, mouseX, mouseY);
|
||||
}
|
||||
|
@@ -4,14 +4,12 @@
|
||||
|
||||
package dan200.computercraft.client.gui;
|
||||
|
||||
import com.mojang.blaze3d.vertex.Tesselator;
|
||||
import dan200.computercraft.core.terminal.TextBuffer;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.media.PrintoutMenu;
|
||||
import dan200.computercraft.shared.media.items.PrintoutItem;
|
||||
import dan200.computercraft.shared.media.items.PrintoutData;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
@@ -19,7 +17,6 @@ import net.minecraft.world.inventory.ContainerListener;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
import static dan200.computercraft.client.render.PrintoutRenderer.*;
|
||||
@@ -40,18 +37,8 @@ public final class PrintoutScreen extends AbstractContainerScreen<PrintoutMenu>
|
||||
}
|
||||
|
||||
private void setPrintout(ItemStack stack) {
|
||||
var text = PrintoutItem.getText(stack);
|
||||
var textBuffers = new TextBuffer[text.length];
|
||||
for (var i = 0; i < textBuffers.length; i++) textBuffers[i] = new TextBuffer(text[i]);
|
||||
|
||||
var colours = PrintoutItem.getColours(stack);
|
||||
var colourBuffers = new TextBuffer[colours.length];
|
||||
for (var i = 0; i < colours.length; i++) colourBuffers[i] = new TextBuffer(colours[i]);
|
||||
|
||||
var pages = Math.max(text.length / PrintoutItem.LINES_PER_PAGE, 1);
|
||||
var book = stack.is(ModRegistry.Items.PRINTED_BOOK.get());
|
||||
|
||||
printout = new PrintoutInfo(pages, book, textBuffers, colourBuffers);
|
||||
page = 0;
|
||||
printout = PrintoutInfo.of(PrintoutData.getOrEmpty(stack), stack.is(ModRegistry.Items.PRINTED_BOOK.get()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -106,15 +93,15 @@ public final class PrintoutScreen extends AbstractContainerScreen<PrintoutMenu>
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseScrolled(double x, double y, double delta) {
|
||||
if (super.mouseScrolled(x, y, delta)) return true;
|
||||
if (delta < 0) {
|
||||
public boolean mouseScrolled(double x, double y, double deltaX, double deltaY) {
|
||||
if (super.mouseScrolled(x, y, deltaX, deltaY)) return true;
|
||||
if (deltaY < 0) {
|
||||
// Scroll up goes to the next page
|
||||
nextPage();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (delta > 0) {
|
||||
if (deltaY > 0) {
|
||||
// Scroll down goes to the previous page
|
||||
previousPage();
|
||||
return true;
|
||||
@@ -125,23 +112,14 @@ public final class PrintoutScreen extends AbstractContainerScreen<PrintoutMenu>
|
||||
|
||||
@Override
|
||||
protected void renderBg(GuiGraphics graphics, float partialTicks, int mouseX, int mouseY) {
|
||||
// Draw the printout
|
||||
var renderer = MultiBufferSource.immediate(Tesselator.getInstance().getBuilder());
|
||||
|
||||
drawBorder(graphics.pose(), renderer, leftPos, topPos, 0, page, printout.pages(), printout.book(), FULL_BRIGHT_LIGHTMAP);
|
||||
drawText(graphics.pose(), renderer, leftPos + X_TEXT_MARGIN, topPos + Y_TEXT_MARGIN, PrintoutItem.LINES_PER_PAGE * page, FULL_BRIGHT_LIGHTMAP, printout.text(), printout.colour());
|
||||
renderer.endBatch();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
|
||||
// We must take the background further back in order to not overlap with our printed pages.
|
||||
// Push the printout slightly forward, to avoid clipping into the background.
|
||||
graphics.pose().pushPose();
|
||||
graphics.pose().translate(0, 0, -1);
|
||||
renderBackground(graphics);
|
||||
graphics.pose().popPose();
|
||||
graphics.pose().translate(0, 0, 1);
|
||||
|
||||
super.render(graphics, mouseX, mouseY, partialTicks);
|
||||
drawBorder(graphics.pose(), graphics.bufferSource(), leftPos, topPos, 0, page, printout.pages(), printout.book(), FULL_BRIGHT_LIGHTMAP);
|
||||
drawText(graphics.pose(), graphics.bufferSource(), leftPos + X_TEXT_MARGIN, topPos + Y_TEXT_MARGIN, PrintoutData.LINES_PER_PAGE * page, FULL_BRIGHT_LIGHTMAP, printout.text(), printout.colour());
|
||||
|
||||
graphics.pose().popPose();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -149,17 +127,21 @@ public final class PrintoutScreen extends AbstractContainerScreen<PrintoutMenu>
|
||||
// Skip rendering labels.
|
||||
}
|
||||
|
||||
@SuppressWarnings("ArrayRecordComponent")
|
||||
record PrintoutInfo(int pages, boolean book, TextBuffer[] text, TextBuffer[] colour) {
|
||||
public static final PrintoutInfo DEFAULT;
|
||||
public static final PrintoutInfo DEFAULT = of(PrintoutData.EMPTY, false);
|
||||
|
||||
static {
|
||||
var textLines = new TextBuffer[PrintoutItem.LINES_PER_PAGE];
|
||||
Arrays.fill(textLines, new TextBuffer(" ".repeat(PrintoutItem.LINE_MAX_LENGTH)));
|
||||
public static PrintoutInfo of(PrintoutData printout, boolean book) {
|
||||
var text = new TextBuffer[printout.lines().size()];
|
||||
var colours = new TextBuffer[printout.lines().size()];
|
||||
for (var i = 0; i < text.length; i++) {
|
||||
var line = printout.lines().get(i);
|
||||
text[i] = new TextBuffer(line.text());
|
||||
colours[i] = new TextBuffer(line.foreground());
|
||||
}
|
||||
|
||||
var colourLines = new TextBuffer[PrintoutItem.LINES_PER_PAGE];
|
||||
Arrays.fill(colourLines, new TextBuffer("f".repeat(PrintoutItem.LINE_MAX_LENGTH)));
|
||||
|
||||
DEFAULT = new PrintoutInfo(1, false, textLines, colourLines);
|
||||
var pages = Math.max(text.length / PrintoutData.LINES_PER_PAGE, 1);
|
||||
return new PrintoutInfo(pages, book, text, colours);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -7,8 +7,7 @@ package dan200.computercraft.client.gui;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.client.gui.widgets.ComputerSidebar;
|
||||
import dan200.computercraft.client.gui.widgets.TerminalWidget;
|
||||
import dan200.computercraft.client.render.RenderTypes;
|
||||
import dan200.computercraft.client.render.SpriteRenderer;
|
||||
import dan200.computercraft.core.util.Nullability;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
|
||||
import dan200.computercraft.shared.turtle.inventory.TurtleMenu;
|
||||
@@ -23,8 +22,11 @@ import static dan200.computercraft.shared.turtle.inventory.TurtleMenu.*;
|
||||
* The GUI for turtles.
|
||||
*/
|
||||
public class TurtleScreen extends AbstractComputerScreen<TurtleMenu> {
|
||||
private static final ResourceLocation BACKGROUND_NORMAL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "textures/gui/turtle_normal.png");
|
||||
private static final ResourceLocation BACKGROUND_ADVANCED = new ResourceLocation(ComputerCraftAPI.MOD_ID, "textures/gui/turtle_advanced.png");
|
||||
private static final ResourceLocation BACKGROUND_NORMAL = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "textures/gui/turtle_normal.png");
|
||||
private static final ResourceLocation BACKGROUND_ADVANCED = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "textures/gui/turtle_advanced.png");
|
||||
|
||||
private static final ResourceLocation SELECTED_NORMAL = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "turtle_normal_selected_slot");
|
||||
private static final ResourceLocation SELECTED_ADVANCED = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "turtle_advanced_selected_slot");
|
||||
|
||||
private static final int TEX_WIDTH = 278;
|
||||
private static final int TEX_HEIGHT = 217;
|
||||
@@ -54,15 +56,16 @@ public class TurtleScreen extends AbstractComputerScreen<TurtleMenu> {
|
||||
if (slot >= 0) {
|
||||
var slotX = slot % 4;
|
||||
var slotY = slot / 4;
|
||||
graphics.blit(
|
||||
leftPos + TURTLE_START_X - 2 + slotX * 18, topPos + PLAYER_START_Y - 2 + slotY * 18, 0, 22, 22,
|
||||
GuiSprites.get(advanced ? GuiSprites.TURTLE_ADVANCED_SELECTED_SLOT : GuiSprites.TURTLE_NORMAL_SELECTED_SLOT)
|
||||
graphics.blitSprite(
|
||||
advanced ? SELECTED_ADVANCED : SELECTED_NORMAL,
|
||||
leftPos + TURTLE_START_X - 2 + slotX * 18, topPos + PLAYER_START_Y - 2 + slotY * 18, 0, 22, 22
|
||||
);
|
||||
}
|
||||
|
||||
// Render sidebar
|
||||
var spriteRenderer = SpriteRenderer.createForGui(graphics, RenderTypes.GUI_SPRITES);
|
||||
ComputerSidebar.renderBackground(spriteRenderer, GuiSprites.getComputerTextures(family), leftPos, topPos + sidebarYOffset);
|
||||
graphics.flush(); // Flush to ensure background textures are drawn before foreground.
|
||||
graphics.blitSprite(
|
||||
Nullability.assertNonNull(GuiSprites.getComputerTextures(family).sidebar()),
|
||||
leftPos, topPos + sidebarYOffset, AbstractComputerMenu.SIDEBAR_WIDTH, ComputerSidebar.HEIGHT
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -6,9 +6,7 @@ package dan200.computercraft.client.gui.widgets;
|
||||
|
||||
import dan200.computercraft.client.gui.GuiSprites;
|
||||
import dan200.computercraft.client.gui.widgets.DynamicImageButton.HintedMessage;
|
||||
import dan200.computercraft.client.render.SpriteRenderer;
|
||||
import dan200.computercraft.shared.computer.core.InputHandler;
|
||||
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
|
||||
import net.minecraft.client.gui.components.AbstractWidget;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
@@ -24,12 +22,9 @@ public final class ComputerSidebar {
|
||||
private static final int ICON_MARGIN = 2;
|
||||
|
||||
private static final int CORNERS_BORDER = 3;
|
||||
private static final int FULL_BORDER = CORNERS_BORDER + ICON_MARGIN;
|
||||
|
||||
private static final int BUTTONS = 2;
|
||||
private static final int HEIGHT = (ICON_HEIGHT + ICON_MARGIN * 2) * BUTTONS + CORNERS_BORDER * 2;
|
||||
|
||||
private static final int TEX_HEIGHT = 14;
|
||||
public static final int HEIGHT = (ICON_HEIGHT + ICON_MARGIN * 2) * BUTTONS + CORNERS_BORDER * 2;
|
||||
|
||||
private ComputerSidebar() {
|
||||
}
|
||||
@@ -63,14 +58,6 @@ public final class ComputerSidebar {
|
||||
));
|
||||
}
|
||||
|
||||
public static void renderBackground(SpriteRenderer renderer, GuiSprites.ComputerTextures textures, int x, int y) {
|
||||
var texture = textures.sidebar();
|
||||
if (texture == null) throw new NullPointerException(textures + " has no sidebar texture");
|
||||
var sprite = GuiSprites.get(texture);
|
||||
|
||||
renderer.blitVerticalSliced(sprite, x, y, AbstractComputerMenu.SIDEBAR_WIDTH, HEIGHT, FULL_BORDER, FULL_BORDER, TEX_HEIGHT);
|
||||
}
|
||||
|
||||
private static void toggleComputer(BooleanSupplier isOn, InputHandler input) {
|
||||
if (isOn.getAsBoolean()) {
|
||||
input.shutdown();
|
||||
|
@@ -10,10 +10,10 @@ import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.components.Tooltip;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
@@ -21,11 +21,11 @@ import java.util.function.Supplier;
|
||||
* dynamically.
|
||||
*/
|
||||
public class DynamicImageButton extends Button {
|
||||
private final Boolean2ObjectFunction<TextureAtlasSprite> texture;
|
||||
private final Boolean2ObjectFunction<ResourceLocation> texture;
|
||||
private final Supplier<HintedMessage> message;
|
||||
|
||||
public DynamicImageButton(
|
||||
int x, int y, int width, int height, Boolean2ObjectFunction<TextureAtlasSprite> texture, OnPress onPress,
|
||||
int x, int y, int width, int height, Boolean2ObjectFunction<ResourceLocation> texture, OnPress onPress,
|
||||
HintedMessage message
|
||||
) {
|
||||
this(x, y, width, height, texture, onPress, () -> message);
|
||||
@@ -33,7 +33,7 @@ public class DynamicImageButton extends Button {
|
||||
|
||||
public DynamicImageButton(
|
||||
int x, int y, int width, int height,
|
||||
Boolean2ObjectFunction<TextureAtlasSprite> texture,
|
||||
Boolean2ObjectFunction<ResourceLocation> texture,
|
||||
OnPress onPress, Supplier<HintedMessage> message
|
||||
) {
|
||||
super(x, y, width, height, Component.empty(), onPress, DEFAULT_NARRATION);
|
||||
@@ -43,19 +43,15 @@ public class DynamicImageButton extends Button {
|
||||
|
||||
@Override
|
||||
public void renderWidget(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
|
||||
var texture = this.texture.get(isHoveredOrFocused());
|
||||
|
||||
RenderSystem.disableDepthTest();
|
||||
graphics.blit(getX(), getY(), 0, width, height, texture);
|
||||
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);
|
||||
|
||||
var texture = this.texture.get(isHoveredOrFocused());
|
||||
|
||||
RenderSystem.disableDepthTest();
|
||||
graphics.blitSprite(texture, getX(), getY(), 0, width, height);
|
||||
RenderSystem.enableDepthTest();
|
||||
}
|
||||
|
||||
public record HintedMessage(Component message, Tooltip tooltip) {
|
||||
|
@@ -4,7 +4,7 @@
|
||||
|
||||
package dan200.computercraft.client.gui.widgets;
|
||||
|
||||
import com.mojang.blaze3d.vertex.Tesselator;
|
||||
import dan200.computercraft.client.gui.KeyConverter;
|
||||
import dan200.computercraft.client.render.RenderTypes;
|
||||
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
@@ -16,7 +16,6 @@ import net.minecraft.client.gui.components.AbstractWidget;
|
||||
import net.minecraft.client.gui.narration.NarratedElementType;
|
||||
import net.minecraft.client.gui.narration.NarrationElementOutput;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
@@ -85,7 +84,7 @@ public class TerminalWidget extends AbstractWidget {
|
||||
}
|
||||
|
||||
if ((modifiers & GLFW.GLFW_MOD_CONTROL) != 0) {
|
||||
switch (key) {
|
||||
switch (KeyConverter.physicalToActual(key, scancode)) {
|
||||
case GLFW.GLFW_KEY_T -> {
|
||||
if (terminateTimer < 0) terminateTimer = 0;
|
||||
}
|
||||
@@ -121,7 +120,7 @@ public class TerminalWidget extends AbstractWidget {
|
||||
computer.keyUp(key);
|
||||
}
|
||||
|
||||
switch (key) {
|
||||
switch (KeyConverter.physicalToActual(key, scancode)) {
|
||||
case GLFW.GLFW_KEY_T -> terminateTimer = -1;
|
||||
case GLFW.GLFW_KEY_R -> rebootTimer = -1;
|
||||
case GLFW.GLFW_KEY_S -> shutdownTimer = -1;
|
||||
@@ -192,16 +191,16 @@ public class TerminalWidget extends AbstractWidget {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseScrolled(double mouseX, double mouseY, double delta) {
|
||||
public boolean mouseScrolled(double mouseX, double mouseY, double deltaX, double deltaY) {
|
||||
if (!inTermRegion(mouseX, mouseY)) return false;
|
||||
if (!hasMouseSupport() || delta == 0) return false;
|
||||
if (!hasMouseSupport() || deltaY == 0) return false;
|
||||
|
||||
var charX = (int) ((mouseX - innerX) / FONT_WIDTH);
|
||||
var charY = (int) ((mouseY - innerY) / FONT_HEIGHT);
|
||||
charX = Math.min(Math.max(charX, 0), terminal.getWidth() - 1);
|
||||
charY = Math.min(Math.max(charY, 0), terminal.getHeight() - 1);
|
||||
|
||||
computer.mouseScroll(delta < 0 ? 1 : -1, charX + 1, charY + 1);
|
||||
computer.mouseScroll(deltaY < 0 ? 1 : -1, charX + 1, charY + 1);
|
||||
|
||||
lastMouseX = charX;
|
||||
lastMouseY = charY;
|
||||
@@ -256,15 +255,12 @@ public class TerminalWidget extends AbstractWidget {
|
||||
public void renderWidget(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
|
||||
if (!visible) return;
|
||||
|
||||
var bufferSource = MultiBufferSource.immediate(Tesselator.getInstance().getBuilder());
|
||||
var emitter = FixedWidthFontRenderer.toVertexConsumer(graphics.pose(), bufferSource.getBuffer(RenderTypes.TERMINAL));
|
||||
var emitter = FixedWidthFontRenderer.toVertexConsumer(graphics.pose(), graphics.bufferSource().getBuffer(RenderTypes.TERMINAL));
|
||||
|
||||
FixedWidthFontRenderer.drawTerminal(
|
||||
emitter,
|
||||
(float) innerX, (float) innerY, terminal, (float) MARGIN, (float) MARGIN, (float) MARGIN, (float) MARGIN
|
||||
);
|
||||
|
||||
bufferSource.endBatch();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -8,10 +8,10 @@ import com.google.auto.service.AutoService;
|
||||
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||
import dan200.computercraft.client.render.RenderTypes;
|
||||
import dan200.computercraft.client.render.text.DirectFixedWidthFontRenderer;
|
||||
import dan200.computercraft.shared.util.ARGB32;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import dan200.computercraft.shared.platform.PlatformHelper;
|
||||
import net.irisshaders.iris.api.v0.IrisApi;
|
||||
import net.irisshaders.iris.api.v0.IrisTextVertexSink;
|
||||
import net.minecraft.util.FastColor;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Optional;
|
||||
@@ -21,7 +21,7 @@ import java.util.function.IntFunction;
|
||||
public class IrisShaderMod implements ShaderMod.Provider {
|
||||
@Override
|
||||
public Optional<ShaderMod> get() {
|
||||
return FabricLoader.getInstance().isModLoaded("iris") ? Optional.of(new Impl()) : Optional.empty();
|
||||
return PlatformHelper.get().isModLoaded("iris") ? Optional.of(new Impl()) : Optional.empty();
|
||||
}
|
||||
|
||||
private static final class Impl extends ShaderMod {
|
||||
@@ -56,7 +56,7 @@ public class IrisShaderMod implements ShaderMod.Provider {
|
||||
|
||||
@Override
|
||||
public void quad(float x1, float y1, float x2, float y2, float z, int colour, float u1, float v1, float u2, float v2) {
|
||||
sink.quad(x1, y1, x2, y2, z, ARGB32.toABGR32(colour), u1, v1, u2, v2, RenderTypes.FULL_BRIGHT_LIGHTMAP);
|
||||
sink.quad(x1, y1, x2, y2, z, FastColor.ABGR32.fromArgb32(colour), u1, v1, u2, v2, RenderTypes.FULL_BRIGHT_LIGHTMAP);
|
||||
}
|
||||
}
|
||||
}
|
@@ -6,12 +6,15 @@ package dan200.computercraft.client.integration.emi;
|
||||
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.integration.RecipeModHelpers;
|
||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
||||
import dan200.computercraft.shared.turtle.items.TurtleItem;
|
||||
import dev.emi.emi.api.EmiEntrypoint;
|
||||
import dev.emi.emi.api.EmiPlugin;
|
||||
import dev.emi.emi.api.EmiRegistry;
|
||||
import dev.emi.emi.api.stack.Comparison;
|
||||
import dev.emi.emi.api.stack.EmiStack;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import java.util.function.BiPredicate;
|
||||
@@ -25,15 +28,17 @@ public class EMIComputerCraft implements EmiPlugin {
|
||||
|
||||
registry.setDefaultComparison(ModRegistry.Items.POCKET_COMPUTER_NORMAL.get(), pocketComparison);
|
||||
registry.setDefaultComparison(ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get(), pocketComparison);
|
||||
|
||||
for (var stack : RecipeModHelpers.getExtraStacks(Minecraft.getInstance().level.registryAccess())) {
|
||||
registry.addEmiStack(EmiStack.of(stack));
|
||||
}
|
||||
}
|
||||
|
||||
private static final Comparison turtleComparison = compareStacks((left, right) ->
|
||||
left.getItem() instanceof TurtleItem turtle
|
||||
&& turtle.getUpgrade(left, TurtleSide.LEFT) == turtle.getUpgrade(right, TurtleSide.LEFT)
|
||||
&& turtle.getUpgrade(left, TurtleSide.RIGHT) == turtle.getUpgrade(right, TurtleSide.RIGHT));
|
||||
private static final Comparison turtleComparison = compareStacks((left, right)
|
||||
-> TurtleItem.getUpgrade(left, TurtleSide.LEFT) == TurtleItem.getUpgrade(right, TurtleSide.LEFT)
|
||||
&& TurtleItem.getUpgrade(left, TurtleSide.RIGHT) == TurtleItem.getUpgrade(right, TurtleSide.RIGHT));
|
||||
|
||||
private static final Comparison pocketComparison = compareStacks((left, right) ->
|
||||
left.getItem() instanceof PocketComputerItem && PocketComputerItem.getUpgrade(left) == PocketComputerItem.getUpgrade(right));
|
||||
private static final Comparison pocketComparison = compareStacks((left, right) -> PocketComputerItem.getUpgrade(left) == PocketComputerItem.getUpgrade(right));
|
||||
|
||||
private static Comparison compareStacks(BiPredicate<ItemStack, ItemStack> test) {
|
||||
return Comparison.of((left, right) -> {
|
||||
|
@@ -8,7 +8,6 @@ import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.integration.RecipeModHelpers;
|
||||
import dan200.computercraft.shared.media.items.DiskItem;
|
||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
||||
import dan200.computercraft.shared.turtle.items.TurtleItem;
|
||||
import mezz.jei.api.IModPlugin;
|
||||
@@ -19,8 +18,11 @@ import mezz.jei.api.ingredients.subtypes.IIngredientSubtypeInterpreter;
|
||||
import mezz.jei.api.registration.IAdvancedRegistration;
|
||||
import mezz.jei.api.registration.ISubtypeRegistration;
|
||||
import mezz.jei.api.runtime.IJeiRuntime;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.component.DyedItemColor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -28,7 +30,7 @@ import java.util.List;
|
||||
public class JEIComputerCraft implements IModPlugin {
|
||||
@Override
|
||||
public ResourceLocation getPluginUid() {
|
||||
return new ResourceLocation(ComputerCraftAPI.MOD_ID, "jei");
|
||||
return ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "jei");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -44,7 +46,7 @@ public class JEIComputerCraft implements IModPlugin {
|
||||
|
||||
@Override
|
||||
public void registerAdvanced(IAdvancedRegistration registry) {
|
||||
registry.addRecipeManagerPlugin(new RecipeResolver());
|
||||
registry.addRecipeManagerPlugin(new RecipeResolver(getRegistryAccess()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -52,7 +54,7 @@ public class JEIComputerCraft implements IModPlugin {
|
||||
var registry = runtime.getRecipeManager();
|
||||
|
||||
// Register all turtles/pocket computers (not just vanilla upgrades) as upgrades on JEI.
|
||||
var upgradeItems = RecipeModHelpers.getExtraStacks();
|
||||
var upgradeItems = RecipeModHelpers.getExtraStacks(getRegistryAccess());
|
||||
if (!upgradeItems.isEmpty()) {
|
||||
runtime.getIngredientManager().addIngredientsAtRuntime(VanillaTypes.ITEM_STACK, upgradeItems);
|
||||
}
|
||||
@@ -60,7 +62,7 @@ public class JEIComputerCraft implements IModPlugin {
|
||||
// Hide all upgrade recipes
|
||||
var category = registry.createRecipeLookup(RecipeTypes.CRAFTING);
|
||||
category.get().forEach(wrapper -> {
|
||||
if (RecipeModHelpers.shouldRemoveRecipe(wrapper.getId())) {
|
||||
if (RecipeModHelpers.shouldRemoveRecipe(wrapper.id())) {
|
||||
registry.hideRecipes(RecipeTypes.CRAFTING, List.of(wrapper));
|
||||
}
|
||||
});
|
||||
@@ -70,17 +72,14 @@ public class JEIComputerCraft implements IModPlugin {
|
||||
* Distinguishes turtles by upgrades and family.
|
||||
*/
|
||||
private static final IIngredientSubtypeInterpreter<ItemStack> turtleSubtype = (stack, ctx) -> {
|
||||
var item = stack.getItem();
|
||||
if (!(item instanceof TurtleItem turtle)) return IIngredientSubtypeInterpreter.NONE;
|
||||
|
||||
var name = new StringBuilder("turtle:");
|
||||
|
||||
// Add left and right upgrades to the identifier
|
||||
var left = turtle.getUpgrade(stack, TurtleSide.LEFT);
|
||||
var right = turtle.getUpgrade(stack, TurtleSide.RIGHT);
|
||||
if (left != null) name.append(left.getUpgradeID());
|
||||
var left = TurtleItem.getUpgradeWithData(stack, TurtleSide.LEFT);
|
||||
var right = TurtleItem.getUpgradeWithData(stack, TurtleSide.RIGHT);
|
||||
if (left != null) name.append(left.holder().key().location());
|
||||
if (left != null && right != null) name.append('|');
|
||||
if (right != null) name.append(right.getUpgradeID());
|
||||
if (right != null) name.append(right.holder().key().location());
|
||||
|
||||
return name.toString();
|
||||
};
|
||||
@@ -89,14 +88,11 @@ public class JEIComputerCraft implements IModPlugin {
|
||||
* Distinguishes pocket computers by upgrade and family.
|
||||
*/
|
||||
private static final IIngredientSubtypeInterpreter<ItemStack> pocketSubtype = (stack, ctx) -> {
|
||||
var item = stack.getItem();
|
||||
if (!(item instanceof PocketComputerItem)) return IIngredientSubtypeInterpreter.NONE;
|
||||
|
||||
var name = new StringBuilder("pocket:");
|
||||
|
||||
// Add the upgrade to the identifier
|
||||
var upgrade = PocketComputerItem.getUpgrade(stack);
|
||||
if (upgrade != null) name.append(upgrade.getUpgradeID());
|
||||
var upgrade = PocketComputerItem.getUpgradeWithData(stack);
|
||||
if (upgrade != null) name.append(upgrade.holder().key().location());
|
||||
|
||||
return name.toString();
|
||||
};
|
||||
@@ -104,11 +100,9 @@ public class JEIComputerCraft implements IModPlugin {
|
||||
/**
|
||||
* Distinguishes disks by colour.
|
||||
*/
|
||||
private static final IIngredientSubtypeInterpreter<ItemStack> diskSubtype = (stack, ctx) -> {
|
||||
var item = stack.getItem();
|
||||
if (!(item instanceof DiskItem disk)) return IIngredientSubtypeInterpreter.NONE;
|
||||
private static final IIngredientSubtypeInterpreter<ItemStack> diskSubtype = (stack, ctx) -> Integer.toString(DyedItemColor.getOrDefault(stack, -1));
|
||||
|
||||
var colour = disk.getColour(stack);
|
||||
return colour == -1 ? IIngredientSubtypeInterpreter.NONE : String.format("%06x", colour);
|
||||
};
|
||||
private static RegistryAccess getRegistryAccess() {
|
||||
return Minecraft.getInstance().level.registryAccess();
|
||||
}
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@
|
||||
|
||||
package dan200.computercraft.client.integration.jei;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.shared.integration.UpgradeRecipeGenerator;
|
||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
||||
import dan200.computercraft.shared.turtle.items.TurtleItem;
|
||||
@@ -12,13 +13,28 @@ import mezz.jei.api.recipe.IFocus;
|
||||
import mezz.jei.api.recipe.RecipeType;
|
||||
import mezz.jei.api.recipe.advanced.IRecipeManagerPlugin;
|
||||
import mezz.jei.api.recipe.category.IRecipeCategory;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.crafting.CraftingRecipe;
|
||||
import net.minecraft.world.item.crafting.RecipeHolder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
class RecipeResolver implements IRecipeManagerPlugin {
|
||||
private final UpgradeRecipeGenerator<CraftingRecipe> resolver = new UpgradeRecipeGenerator<>(x -> x);
|
||||
private final UpgradeRecipeGenerator<RecipeHolder<CraftingRecipe>> resolver;
|
||||
|
||||
/**
|
||||
* We need to generate unique ids for each recipe, as JEI will attempt to deduplicate them otherwise.
|
||||
*/
|
||||
private int nextId = 0;
|
||||
|
||||
RecipeResolver(HolderLookup.Provider registries) {
|
||||
resolver = new UpgradeRecipeGenerator<>(
|
||||
x -> new RecipeHolder<>(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "upgrade_" + nextId++), x),
|
||||
registries
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <V> List<RecipeType<?>> getRecipeTypes(IFocus<V> focus) {
|
||||
@@ -44,8 +60,8 @@ class RecipeResolver implements IRecipeManagerPlugin {
|
||||
}
|
||||
|
||||
return switch (focus.getRole()) {
|
||||
case INPUT -> cast(resolver.findRecipesWithInput(stack));
|
||||
case OUTPUT -> cast(resolver.findRecipesWithOutput(stack));
|
||||
case INPUT -> cast(RecipeTypes.CRAFTING, resolver.findRecipesWithInput(stack));
|
||||
case OUTPUT -> cast(RecipeTypes.CRAFTING, resolver.findRecipesWithOutput(stack));
|
||||
default -> List.of();
|
||||
};
|
||||
}
|
||||
@@ -55,8 +71,8 @@ class RecipeResolver implements IRecipeManagerPlugin {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private static <T, U> List<T> cast(List<U> from) {
|
||||
@SuppressWarnings({ "unchecked", "rawtypes", "UnusedVariable" })
|
||||
private static <T, U> List<T> cast(RecipeType<U> ignoredType, List<U> from) {
|
||||
return (List) from;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,68 @@
|
||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.client.model;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.JsonOps;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.shared.turtle.TurtleOverlay;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* A list of extra models to load on the client.
|
||||
* <p>
|
||||
* This is largely intended for use with {@linkplain TurtleOverlay turtle overlays}. As overlays are stored in a dynamic
|
||||
* registry, they are not available when resources are loaded, and so we need a way to request the overlays' models be
|
||||
* loaded.
|
||||
*
|
||||
* @param models The models to load.
|
||||
*/
|
||||
public record ExtraModels(List<ResourceLocation> models) {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ExtraModels.class);
|
||||
private static final Gson GSON = new Gson();
|
||||
|
||||
/**
|
||||
* The path where the extra models are listed.
|
||||
*/
|
||||
public static final ResourceLocation PATH = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "extra_models.json");
|
||||
|
||||
/**
|
||||
* The coded used to store the extra model file.
|
||||
*/
|
||||
public static final Codec<ExtraModels> CODEC = ResourceLocation.CODEC.listOf().xmap(ExtraModels::new, ExtraModels::models);
|
||||
|
||||
/**
|
||||
* Get the list of all extra models to load.
|
||||
*
|
||||
* @param resources The current resource manager.
|
||||
* @return A set of all resources to load.
|
||||
*/
|
||||
public static Collection<ResourceLocation> loadAll(ResourceManager resources) {
|
||||
Set<ResourceLocation> out = new HashSet<>();
|
||||
|
||||
for (var path : resources.getResourceStack(PATH)) {
|
||||
ExtraModels models;
|
||||
try (var stream = path.openAsReader()) {
|
||||
models = ExtraModels.CODEC.parse(JsonOps.INSTANCE, GSON.fromJson(stream, JsonElement.class)).getOrThrow(JsonParseException::new);
|
||||
} catch (IOException | RuntimeException e) {
|
||||
LOG.error("Failed to load extra models from {}", path.sourcePackId());
|
||||
continue;
|
||||
}
|
||||
|
||||
out.addAll(models.models());
|
||||
}
|
||||
|
||||
return Collections.unmodifiableCollection(out);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user