1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-10-16 14:37:39 +00:00

Compare commits

..

1 Commits

Author SHA1 Message Date
Jonathan Coates
94e7d2d03b Render pocket computers in tooltips 2024-10-24 18:49:14 +01:00
923 changed files with 13136 additions and 15430 deletions

View File

@@ -18,6 +18,11 @@ ij_any_if_brace_force = if_multiline
ij_any_for_brace_force = if_multiline
ij_any_spaces_within_array_initializer_braces = true
ij_kotlin_allow_trailing_comma = true
ij_kotlin_allow_trailing_comma_on_call_site = true
ij_kotlin_method_parameters_wrap = off
ij_kotlin_call_parameters_wrap = off
[*.md]
trim_trailing_whitespace = false
@@ -26,16 +31,3 @@ indent_size = 2
[*.yml]
indent_size = 2
[{*.kt,*.kts}]
ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL
ij_kotlin_continuation_indent_size = 4
ij_kotlin_spaces_around_equality_operators = true
ij_kotlin_allow_trailing_comma = true
ij_kotlin_allow_trailing_comma_on_call_site = true
# Prefer to handle these manually
ij_kotlin_method_parameters_wrap = off
ij_kotlin_call_parameters_wrap = off
ij_kotlin_extends_list_wrap = off

View File

@@ -6,8 +6,7 @@ body:
id: mc-version
attributes:
label: Minecraft Version
description: |
What version of Minecraft are you using? If your version is not listed, please try to reproduce on one of the supported versions.
description: What version of Minecraft are you using?
options:
- 1.20.1
- 1.21.x
@@ -27,5 +26,8 @@ body:
label: Details
description: |
Description of the bug. Please include the following:
- Logs: These will be located in the `logs/` directory of your Minecraft instance. This is always useful, even if it doesn't include errors, so please upload this!
- Detailed reproduction steps: sometimes I can spot a bug pretty easily, but often it's much more obscure. The more information I have to help reproduce it, the quicker it'll get fixed.
- Logs: These will be located in the `logs/` directory of your Minecraft
instance. Please upload them as a gist or directly into this editor.
- Detailed reproduction steps: sometimes I can spot a bug pretty easily,
but often it's much more obscure. The more information I have to help
reproduce it, the quicker it'll get fixed.

View File

@@ -14,7 +14,7 @@ jobs:
- name: 📥 Set up Java
uses: actions/setup-java@v4
with:
java-version: 21
java-version: 17
distribution: 'temurin'
- name: 📥 Setup Gradle
@@ -30,18 +30,6 @@ jobs:
- name: ⚒️ Build
run: ./gradlew assemble || ./gradlew assemble
- name: 📦 Prepare Jars
run: |
# Find the main jar and append the git hash onto it.
mkdir -p jars
find projects/forge/build/libs projects/fabric/build/libs -type f -regex '.*[0-9.]+\(-SNAPSHOT\)?\.jar$' -exec bash -c 'cp {} "jars/$(basename {} .jar)-$(git rev-parse HEAD).jar"' \;
- name: 📤 Upload Jar
uses: actions/upload-artifact@v4
with:
name: CC-Tweaked
path: ./jars
- name: Cache pre-commit
uses: actions/cache@v4
with:
@@ -62,10 +50,30 @@ jobs:
- name: 🧪 Run integration tests
run: ./gradlew runGametest
- name: 🧪 Run client tests
run: ./gradlew runGametestClient # Not checkClient, as no point running rendering tests.
# These are a little flaky on GH actions: its useful to run them, but don't break the build.
continue-on-error: true
- name: 🧪 Parse test reports
run: ./tools/parse-reports.py
if: ${{ failure() }}
- name: 📦 Prepare Jars
run: |
# Find the main jar and append the git hash onto it.
mkdir -p jars
find projects/forge/build/libs projects/fabric/build/libs -type f -regex '.*[0-9.]+\(-SNAPSHOT\)?\.jar$' -exec bash -c 'cp {} "jars/$(basename {} .jar)-$(git rev-parse HEAD).jar"' \;
- name: 📤 Upload Jar
uses: actions/upload-artifact@v4
with:
name: CC-Tweaked
path: ./jars
- name: 📤 Upload coverage
uses: codecov/codecov-action@v4
build-core:
strategy:
fail-fast: false
@@ -87,7 +95,7 @@ jobs:
- name: 📥 Set up Java
uses: actions/setup-java@v4
with:
java-version: 21
java-version: 17
distribution: 'temurin'
- name: 📥 Setup Gradle

View File

@@ -17,7 +17,7 @@ jobs:
- name: 📥 Set up Java
uses: actions/setup-java@v4
with:
java-version: 21
java-version: 17
distribution: 'temurin'
- name: 📥 Setup Gradle

1
.gitignore vendored
View File

@@ -27,7 +27,6 @@
*.iml
.idea
.gradle
.kotlin
*.DS_Store
/.classpath

View File

@@ -27,7 +27,7 @@ repos:
exclude: "^(.*\\.(bat)|LICENSE)$"
- repo: https://github.com/fsfe/reuse-tool
rev: v5.0.2
rev: v4.0.3
hooks:
- id: reuse
@@ -58,7 +58,6 @@ repos:
exclude: |
(?x)^(
projects/[a-z]+/src/generated|
projects/[a-z]+/src/examples/generatedResources|
projects/core/src/test/resources/test-rom/data/json-parsing/|
.*\.dfpwm
)

View File

@@ -22,13 +22,14 @@ If you have a bug, suggestion, or other feedback, the best thing to do is [file
use the issue templates - they provide a useful hint on what information to provide.
## Translations
Translations are managed through [CrowdIn], an online interface for managing language strings.
Translations are managed through [CrowdIn], an online interface for managing language strings. Translations may either
be contributed there, or directly via a pull request.
## Setting up a development environment
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 21 (JDK). This can be downloaded from [Adoptium].
- Java Development Kit 17 (JDK). This can be downloaded from [Adoptium].
- [Git](https://git-scm.com/).
- [NodeJS 20 or later][node].
@@ -48,12 +49,9 @@ If you want to run CC:T in a normal Minecraft instance, run `./gradlew assemble`
`projects/forge/build/libs` (for Forge) or `projects/fabric/build/libs` (for Fabric).
## Developing CC: Tweaked
Before making any major changes to CC: Tweaked, I'd recommend starting opening an issue or starting a discussion on
GitHub first. It's often helpful to discuss features before spending time developing them!
Once you're ready to start programming, have a read of the [the architecture document][architecture] first. While it's
not a comprehensive document, it gives a good hint of where you should start looking to make your changes. As always, if
you're not sure, [do ask the community][community]!
Before making any major changes to CC: Tweaked, I'd recommend you have a read of the [the architecture
document][architecture] first. While it's not a comprehensive document, it gives a good hint of where you should start
looking to make your changes. As always, if you're not sure, [do ask the community][community]!
### Testing
When making larger changes, it may be useful to write a test to make sure your code works as expected.

View File

@@ -11,13 +11,14 @@ SPDX-License-Identifier: MPL-2.0
</picture>
[![Current build status](https://github.com/cc-tweaked/CC-Tweaked/workflows/Build/badge.svg)](https://github.com/cc-tweaked/CC-Tweaked/actions "Current build status")
[![Download CC: Tweaked on CurseForge](https://img.shields.io/static/v1?label=Download&message=CC:%20Tweaked&color=E04E14&logoColor=E04E14&logo=CurseForge)][CurseForge]
[![Download CC: Tweaked on Modrinth](https://img.shields.io/static/v1?label=Download&color=00AF5C&logoColor=00AF5C&logo=Modrinth&message=CC:%20Tweaked)][Modrinth]
CC: Tweaked is a mod for Minecraft which adds programmable computers, turtles and more to the game. A fork of the
much-beloved [ComputerCraft], it continues its legacy with improved performance and stability, along with a wealth of
new features.
CC: Tweaked can be installed from [Modrinth]. It runs on both [Minecraft Forge] and [Fabric].
CC: Tweaked can be installed from [CurseForge] or [Modrinth]. It runs on both [Minecraft Forge] and [Fabric].
## Contributing
Any contribution is welcome, be that using the mod, reporting bugs or contributing code. If you want to get started
@@ -51,8 +52,9 @@ dependencies {
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-common-api:$cctVersion")
// Forge Gradle
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-forge-api:$cctVersion")
runtimeOnly("cc.tweaked:cc-tweaked-$mcVersion-forge:$cctVersion")
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"))
// Fabric Loom
modCompileOnly("cc.tweaked:cc-tweaked-$mcVersion-fabric-api:$cctVersion")
@@ -60,6 +62,19 @@ dependencies {
}
```
When using ForgeGradle, you may also need to add the following:
```groovy
minecraft {
runs {
configureEach {
property 'mixin.env.remapRefMap', 'true'
property 'mixin.env.refMapRemappingFile', "${buildDir}/createSrgToMcp/output.srg"
}
}
}
```
You should also be careful to only use classes within the `dan200.computercraft.api` package. Non-API classes are
subject to change at any point. If you depend on functionality outside the API (or need to mixin to CC:T), please file
an issue to let me know!
@@ -68,6 +83,7 @@ We bundle the API sources with the jar, so documentation should be easily viewab
the generated documentation [can be browsed online](https://tweaked.cc/javadoc/).
[computercraft]: https://github.com/dan200/ComputerCraft "ComputerCraft on GitHub"
[curseforge]: https://minecraft.curseforge.com/projects/cc-tweaked "Download CC: Tweaked from CurseForge"
[modrinth]: https://modrinth.com/mod/gu7yAYhd "Download CC: Tweaked from Modrinth"
[Minecraft Forge]: https://files.minecraftforge.net/ "Download Minecraft Forge."
[Fabric]: https://fabricmc.net/use/installer/ "Download Fabric."

View File

@@ -8,23 +8,18 @@ SPDX-PackageSupplier = "Jonathan Coates <git@squiddev.cc>"
SPDX-PackageDownloadLocation = "https://github.com/cc-tweaked/cc-tweaked"
[[annotations]]
# Generated/data files are CC0.
SPDX-FileCopyrightText = "The CC: Tweaked Developers"
SPDX-License-Identifier = "CC0-1.0"
path = [
# Generated/data files are CC0.
"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_upgrade/**",
"projects/common/src/testMod/resources/data/cctest/computercraft/turtle_upgrades/**",
"projects/common/src/testMod/resources/data/cctest/structures/**",
"projects/*/src/generated/**",
"projects/**/src/generated/**",
"projects/web/src/htmlTransform/export/index.json",
"projects/web/src/htmlTransform/export/items/minecraft/**",
# GitHub build scripts are CC0. While we could add a header to each file,
# it's unclear if it will break actions or issue templates in some way.
".github/**",
# Example mod is CC0.
"projects/*/src/examples/**"
]
[[annotations]]
@@ -35,15 +30,23 @@ path = [
"doc/images/**",
"package.json",
"package-lock.json",
"projects/*/src/*/resources/*.mixins.json",
"projects/fabric/src/*/resources/fabric.mod.json",
"projects/common/src/client/resources/computercraft-client.mixins.json",
"projects/common/src/main/resources/assets/minecraft/shaders/core/computercraft/monitor_tbo.json",
"projects/common/src/main/resources/computercraft.mixins.json",
"projects/common/src/testMod/resources/computercraft-gametest.mixins.json",
"projects/common/src/testMod/resources/data/computercraft/loot_tables/treasure_disk.json",
"projects/common/src/testMod/resources/pack.mcmeta",
"projects/core/src/main/resources/data/computercraft/lua/rom/modules/command/.ignoreme",
"projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/.ignoreme",
"projects/core/src/main/resources/data/computercraft/lua/rom/modules/turtle/.ignoreme",
"projects/core/src/main/resources/data/computercraft/lua/rom/motd.txt",
"projects/fabric-api/src/main/modJson/fabric.mod.json",
"projects/fabric/src/client/resources/computercraft-client.fabric.mixins.json",
"projects/fabric/src/main/resources/computercraft.fabric.mixins.json",
"projects/fabric/src/main/resources/fabric.mod.json",
"projects/fabric/src/testMod/resources/computercraft-gametest.fabric.mixins.json",
"projects/fabric/src/testMod/resources/fabric.mod.json",
"projects/forge/src/client/resources/computercraft-client.forge.mixins.json",
"projects/web/src/frontend/mount/.settings",
"projects/web/src/frontend/mount/example.nfp",
"projects/web/src/frontend/mount/example.nft",
@@ -70,7 +73,7 @@ path = [
]
[[annotations]]
# Community-contributed language files
# Community-contributed license files
SPDX-FileCopyrightText = "2017 The CC: Tweaked Developers"
SPDX-License-Identifier = "LicenseRef-CCPL"
path = [
@@ -84,11 +87,18 @@ path = [
]
[[annotations]]
# Community-contributed language files
# Community-contributed license files
SPDX-FileCopyrightText = "2017 The CC: Tweaked Developers"
SPDX-License-Identifier = "MPL-2.0"
path = "projects/common/src/main/resources/assets/computercraft/lang/**"
[[annotations]]
# GitHub build scripts are CC0. While we could add a header to each file,
# it's unclear if it will break actions or issue templates in some way.
SPDX-FileCopyrightText = "Jonathan Coates <git@squiddev.cc>"
SPDX-License-Identifier = "CC0-1.0"
path = ".github/**"
[[annotations]]
path = ["gradle/wrapper/**"]
SPDX-FileCopyrightText = "Gradle Inc"

View File

@@ -24,19 +24,21 @@ val mcVersion: String by extra
githubRelease {
token(findProperty("githubApiKey") as String? ?: "")
owner = "cc-tweaked"
repo = "CC-Tweaked"
targetCommitish = cct.gitBranch
owner.set("cc-tweaked")
repo.set("CC-Tweaked")
targetCommitish.set(cct.gitBranch)
tagName = "v$mcVersion-$modVersion"
releaseName = "[$mcVersion] $modVersion"
body = provider {
"## " + project(":core").file("src/main/resources/data/computercraft/lua/rom/help/whatsnew.md")
.readLines()
.takeWhile { it != "Type \"help changelog\" to see the full version history." }
.joinToString("\n").trim()
}
prerelease = isUnstable
tagName.set("v$mcVersion-$modVersion")
releaseName.set("[$mcVersion] $modVersion")
body.set(
provider {
"## " + project(":core").file("src/main/resources/data/computercraft/lua/rom/help/whatsnew.md")
.readLines()
.takeWhile { it != "Type \"help changelog\" to see the full version history." }
.joinToString("\n").trim()
},
)
prerelease.set(isUnstable)
}
tasks.publish { dependsOn(tasks.githubRelease) }
@@ -116,7 +118,7 @@ idea.project.settings.compiler.javac {
}
versionCatalogUpdate {
sortByKey = false
sortByKey.set(false)
pin { versions.addAll("fastutil", "guava", "netty", "slf4j") }
keep { keepUnusedLibraries = true }
keep { keepUnusedLibraries.set(true) }
}

View File

@@ -14,10 +14,18 @@ repositories {
mavenCentral()
gradlePluginPortal()
maven("https://maven.neoforged.net") {
name = "NeoForge"
maven("https://maven.minecraftforge.net") {
name = "Forge"
content {
includeGroup("net.neoforged")
includeGroup("net.minecraftforge")
includeGroup("net.minecraftforge.gradle")
}
}
maven("https://maven.parchmentmc.org") {
name = "Librarian"
content {
includeGroupByRegex("^org\\.parchmentmc.*")
}
}
@@ -41,10 +49,12 @@ dependencies {
implementation(libs.kotlin.plugin)
implementation(libs.spotless)
implementation(libs.curseForgeGradle)
implementation(libs.fabric.loom)
implementation(libs.forgeGradle)
implementation(libs.ideaExt)
implementation(libs.librarian)
implementation(libs.minotaur)
implementation(libs.modDevGradle)
implementation(libs.vanillaExtract)
}
@@ -68,7 +78,7 @@ gradlePlugin {
}
versionCatalogUpdate {
sortByKey = false
keep { keepUnusedLibraries = true }
catalogFile = file("../gradle/libs.versions.toml")
sortByKey.set(false)
keep { keepUnusedLibraries.set(true) }
catalogFile.set(file("../gradle/libs.versions.toml"))
}

View File

@@ -30,7 +30,7 @@ repositories {
loom {
splitEnvironmentSourceSets()
splitModDependencies = true
splitModDependencies.set(true)
}
MinecraftConfigurations.setup(project)

View File

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

View File

@@ -2,34 +2,44 @@
//
// SPDX-License-Identifier: MPL-2.0
import cc.tweaked.gradle.clientClasses
import cc.tweaked.gradle.commonClasses
/**
* Sets up the configurations for writing game tests.
*
* See notes in [cc.tweaked.gradle.MinecraftConfigurations] for the general design behind these cursed ideas.
*/
import cc.tweaked.gradle.MinecraftConfigurations
import cc.tweaked.gradle.clientClasses
import cc.tweaked.gradle.commonClasses
plugins {
kotlin("jvm")
id("cc-tweaked.kotlin-convention")
id("cc-tweaked.java-convention")
}
val main = sourceSets["main"]
val client = sourceSets["client"]
MinecraftConfigurations.createDerivedConfiguration(project, MinecraftConfigurations.DATAGEN)
MinecraftConfigurations.createDerivedConfiguration(project, MinecraftConfigurations.EXAMPLES)
MinecraftConfigurations.createDerivedConfiguration(project, MinecraftConfigurations.TEST_MOD)
// Both testMod and testFixtures inherit from the main and client classpath, just so we have access to Minecraft classes.
val testMod by sourceSets.creating {
compileClasspath += main.compileClasspath + client.compileClasspath
runtimeClasspath += main.runtimeClasspath + client.runtimeClasspath
}
// Set up generated resources
sourceSets.main { resources.srcDir("src/generated/resources") }
sourceSets.named("examples") { resources.srcDir("src/examples/generatedResources") }
configurations {
named(testMod.compileClasspathConfigurationName) {
shouldResolveConsistentlyWith(compileClasspath.get())
}
// Make sure our examples compile.
tasks.check { dependsOn(tasks.named("compileExamplesJava")) }
named(testMod.runtimeClasspathConfigurationName) {
shouldResolveConsistentlyWith(runtimeClasspath.get())
}
}
// Like the main test configurations, we're safe to depend on source set outputs.
dependencies {
add(testMod.implementationConfigurationName, main.output)
add(testMod.implementationConfigurationName, client.output)
}
// Similar to java-test-fixtures, but tries to avoid putting the obfuscated jar on the classpath.

View File

@@ -29,7 +29,7 @@ base.archivesName.convention("cc-tweaked-$mcVersion-${project.name}")
java {
toolchain {
languageVersion= CCTweakedPlugin.JAVA_VERSION
languageVersion.set(CCTweakedPlugin.JAVA_VERSION)
}
withSourcesJar()
@@ -44,6 +44,13 @@ repositories {
exclusiveContent {
forRepositories(mainMaven)
// Include the ForgeGradle repository if present. This requires that ForgeGradle is already present, which we
// enforce in our Forge overlay.
val fg =
project.extensions.findByType(net.minecraftforge.gradle.userdev.DependencyManagementExtension::class.java)
if (fg != null) forRepositories(fg.repository)
filter {
includeGroup("cc.tweaked")
// Things we mirror
@@ -57,6 +64,7 @@ repositories {
includeGroup("mezz.jei")
includeGroup("org.teavm")
includeModule("com.terraformersmc", "modmenu")
includeModule("me.lucko", "fabric-permissions-api")
}
}
}
@@ -78,16 +86,8 @@ dependencies {
// Configure default JavaCompile tasks with our arguments.
sourceSets.all {
tasks.named(compileJavaTaskName, JavaCompile::class.java) {
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",
),
)
// Processing just gives us "No processor claimed any of these annotations", so skip that!
options.compilerArgs.addAll(listOf("-Xlint", "-Xlint:-processing"))
options.errorprone {
check("InvalidBlockTag", CheckSeverity.OFF) // Broken by @cc.xyz
@@ -99,7 +99,6 @@ sourceSets.all {
check("OperatorPrecedence", CheckSeverity.OFF) // For now.
check("NonOverridingEquals", CheckSeverity.OFF) // Peripheral.equals makes this hard to avoid
check("FutureReturnValueIgnored", CheckSeverity.OFF) // Too many false positives with Netty
check("InvalidInlineTag", CheckSeverity.OFF) // Triggered by @snippet. Can be removed on Java 21.
check("NullAway", CheckSeverity.ERROR)
option(
@@ -156,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/21/docs/api/")
stdOptions.links("https://docs.oracle.com/en/java/javase/17/docs/api/")
}
}
@@ -170,8 +169,8 @@ tasks.test {
}
tasks.withType(JacocoReport::class.java).configureEach {
reports.xml.required = true
reports.html.required =true
reports.xml.required.set(true)
reports.html.required.set(true)
}
project.plugins.withType(CCTweakedPlugin::class.java) {
@@ -227,5 +226,6 @@ idea.module {
// Force Gradle to write to inherit the output directory from the parent, instead of writing to out/xxx/classes.
// This is required for Loom, and we patch Forge's run configurations to work there.
// TODO: Submit a patch to Forge to support ProjectRootManager.
inheritOutputDirs = true
}

View File

@@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
import cc.tweaked.gradle.CCTweakedPlugin
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm")
}
kotlin {
jvmToolchain {
languageVersion.set(CCTweakedPlugin.JAVA_VERSION)
}
}
tasks.withType(KotlinCompile::class.java).configureEach {
// So technically we shouldn't need to do this as the toolchain sets it above. However, the option only appears
// to be set when the task executes, so doesn't get picked up by IDEs.
kotlinOptions.jvmTarget = when {
CCTweakedPlugin.JAVA_VERSION.asInt() > 8 -> CCTweakedPlugin.JAVA_VERSION.toString()
else -> "1.${CCTweakedPlugin.JAVA_VERSION.asInt()}"
}
}

View File

@@ -2,9 +2,11 @@
//
// SPDX-License-Identifier: MPL-2.0
import net.darkhax.curseforgegradle.TaskPublishCurseForge
import cc.tweaked.gradle.setProvider
plugins {
id("net.darkhax.curseforgegradle")
id("com.modrinth.minotaur")
id("cc-tweaked.publishing")
}
@@ -23,17 +25,34 @@ val isUnstable = project.properties["isUnstable"] == "true"
val modVersion: String by extra
val mcVersion: String by extra
val publishCurseForge by tasks.registering(TaskPublishCurseForge::class) {
group = PublishingPlugin.PUBLISH_TASK_GROUP
description = "Upload artifacts to CurseForge"
apiToken = findProperty("curseForgeApiKey") ?: ""
enabled = apiToken != ""
val mainFile = upload("282001", modPublishing.output)
mainFile.changelog =
"Release notes can be found on the [GitHub repository](https://github.com/cc-tweaked/CC-Tweaked/releases/tag/v$mcVersion-$modVersion)."
mainFile.changelogType = "markdown"
mainFile.releaseType = if (isUnstable) "alpha" else "release"
mainFile.gameVersions.add(mcVersion)
}
tasks.publish { dependsOn(publishCurseForge) }
modrinth {
token = findProperty("modrinthApiKey") as String? ?: ""
projectId = "gu7yAYhd"
versionNumber = modVersion
versionName = modVersion
versionType = if (isUnstable) "alpha" else "release"
token.set(findProperty("modrinthApiKey") as String? ?: "")
projectId.set("gu7yAYhd")
versionNumber.set(modVersion)
versionName.set(modVersion)
versionType.set(if (isUnstable) "alpha" else "release")
uploadFile.setProvider(modPublishing.output)
gameVersions.add(mcVersion)
changelog = "Release notes can be found on the [GitHub repository](https://github.com/cc-tweaked/CC-Tweaked/releases/tag/v$mcVersion-$modVersion)."
changelog.set("Release notes can be found on the [GitHub repository](https://github.com/cc-tweaked/CC-Tweaked/releases/tag/v$mcVersion-$modVersion).")
syncBodyFrom = provider { rootProject.file("doc/mod-page.md").readText() }
syncBodyFrom.set(provider { rootProject.file("doc/mod-page.md").readText() })
}
tasks.publish { dependsOn(tasks.modrinth) }

View File

@@ -12,26 +12,25 @@ publishing {
register<MavenPublication>("maven") {
artifactId = base.archivesName.get()
from(components["java"])
suppressAllPomMetadataWarnings()
pom {
name = "CC: Tweaked"
description = "CC: Tweaked is a fork of ComputerCraft, adding programmable computers, turtles and more to Minecraft."
url = "https://github.com/cc-tweaked/CC-Tweaked"
name.set("CC: Tweaked")
description.set("CC: Tweaked is a fork of ComputerCraft, adding programmable computers, turtles and more to Minecraft.")
url.set("https://github.com/cc-tweaked/CC-Tweaked")
scm {
url = "https://github.com/cc-tweaked/CC-Tweaked.git"
url.set("https://github.com/cc-tweaked/CC-Tweaked.git")
}
issueManagement {
system = "github"
url = "https://github.com/cc-tweaked/CC-Tweaked/issues"
system.set("github")
url.set("https://github.com/cc-tweaked/CC-Tweaked/issues")
}
licenses {
license {
name = "ComputerCraft Public License, Version 1.0"
url = "https://github.com/cc-tweaked/CC-Tweaked/blob/HEAD/LICENSE"
name.set("ComputerCraft Public License, Version 1.0")
url.set("https://github.com/cc-tweaked/CC-Tweaked/blob/HEAD/LICENSE")
}
}
}

View File

@@ -11,10 +11,13 @@ import org.gradle.api.NamedDomainObjectProvider
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.Dependency
import org.gradle.api.attributes.TestSuiteType
import org.gradle.api.file.FileSystemOperations
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Provider
import org.gradle.api.provider.SetProperty
import org.gradle.api.reporting.ReportingExtension
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.bundling.Jar
import org.gradle.api.tasks.compile.JavaCompile
@@ -22,6 +25,7 @@ import org.gradle.api.tasks.javadoc.Javadoc
import org.gradle.language.base.plugins.LifecycleBasePlugin
import org.gradle.language.jvm.tasks.ProcessResources
import org.gradle.process.JavaForkOptions
import org.gradle.testing.jacoco.plugins.JacocoCoverageReport
import org.gradle.testing.jacoco.plugins.JacocoPluginExtension
import org.gradle.testing.jacoco.plugins.JacocoTaskExtension
import org.gradle.testing.jacoco.tasks.JacocoReport
@@ -32,36 +36,54 @@ import java.io.IOException
import java.net.URI
import java.util.regex.Pattern
abstract class CCTweakedExtension(private val project: Project) {
abstract class CCTweakedExtension(
private val project: Project,
private val fs: FileSystemOperations,
) {
/** Get the hash of the latest git commit. */
val gitHash: Provider<String> = gitProvider(project, "<no git hash>") {
ProcessHelpers.captureOut("git", "-C", project.rootProject.projectDir.absolutePath, "rev-parse", "HEAD").trim()
}
/** Get the current git branch. */
val gitBranch: Provider<String> =
gitProvider("<no git branch>", listOf("rev-parse", "--abbrev-ref", "HEAD")) { it.trim() }
val gitBranch: Provider<String> = gitProvider(project, "<no git branch>") {
ProcessHelpers.captureOut("git", "-C", project.rootProject.projectDir.absolutePath, "rev-parse", "--abbrev-ref", "HEAD")
.trim()
}
/** Get a list of all contributors to the project. */
val gitContributors: Provider<List<String>> =
gitProvider(listOf(), listOf("shortlog", "-ns", "--group=author", "--group=trailer:co-authored-by", "HEAD")) { input ->
input.lineSequence()
.filter { it.isNotEmpty() }
.map {
val matcher = COMMIT_COUNTS.matcher(it)
matcher.find()
matcher.group(1)
}
.filter { !IGNORED_USERS.contains(it) }
.toList()
.sortedWith(String.CASE_INSENSITIVE_ORDER)
}
val gitContributors: Provider<List<String>> = gitProvider(project, listOf()) {
ProcessHelpers.captureLines(
"git", "-C", project.rootProject.projectDir.absolutePath, "shortlog", "-ns",
"--group=author", "--group=trailer:co-authored-by", "HEAD",
)
.asSequence()
.map {
val matcher = COMMIT_COUNTS.matcher(it)
matcher.find()
matcher.group(1)
}
.filter { !IGNORED_USERS.contains(it) }
.toList()
.sortedWith(String.CASE_INSENSITIVE_ORDER)
}
/**
* References to other sources
*/
val sourceDirectories: SetProperty<SourceSetReference> = project.objects.setProperty(SourceSetReference::class.java)
/**
* Dependencies excluded from published artifacts.
*/
private val excludedDeps: ListProperty<Dependency> = project.objects.listProperty(Dependency::class.java)
/** All source sets referenced by this project. */
val sourceSets = sourceDirectories.map { x -> x.map { it.sourceSet } }
init {
sourceDirectories.finalizeValueOnRead()
excludedDeps.finalizeValueOnRead()
project.afterEvaluate { sourceDirectories.disallowChanges() }
}
@@ -87,13 +109,14 @@ abstract class CCTweakedExtension(private val project: Project) {
val otherJava = otherProject.extensions.getByType(JavaPluginExtension::class.java)
val main = otherJava.sourceSets.getByName("main")
val client = otherJava.sourceSets.getByName("client")
val testMod = otherJava.sourceSets.findByName("testMod")
val testFixtures = otherJava.sourceSets.findByName("testFixtures")
// Pull in sources from the other project.
extendSourceSet(otherProject, main)
extendSourceSet(otherProject, client)
for (sourceSet in listOf(MinecraftConfigurations.DATAGEN, MinecraftConfigurations.EXAMPLES, MinecraftConfigurations.TEST_MOD, "testFixtures")) {
otherJava.sourceSets.findByName(sourceSet)?.let { extendSourceSet(otherProject, it) }
}
if (testMod != null) extendSourceSet(otherProject, testMod)
if (testFixtures != null) extendSourceSet(otherProject, testFixtures)
// The extra source-processing tasks should include these files too.
project.tasks.named(main.javadocTaskName, Javadoc::class.java) { source(main.allJava, client.allJava) }
@@ -156,18 +179,23 @@ abstract class CCTweakedExtension(private val project: Project) {
}
fun <T> jacoco(task: NamedDomainObjectProvider<T>) where T : Task, T : JavaForkOptions {
val classDump = project.layout.buildDirectory.dir("jacocoClassDump/${task.name}")
val reportTaskName = "jacoco${task.name.capitalise()}Report"
val jacoco = project.extensions.getByType(JacocoPluginExtension::class.java)
task.configure {
finalizedBy(reportTaskName)
jacoco.applyTo(this)
doFirst("Clean class dump directory") { fs.delete { delete(classDump) } }
jacoco.applyTo(this)
extensions.configure(JacocoTaskExtension::class.java) {
excludes = listOf(
"dan200.computercraft.mixin.*", // Exclude mixins, as they're not executed at runtime.
"dan200.computercraft.shared.Capabilities$*", // Exclude capability tokens, as Forge rewrites them.
)
includes = listOf("dan200.computercraft.*")
classDumpDir = classDump.get().asFile
// Older versions of modlauncher don't include a protection domain (and thus no code
// source). Jacoco skips such classes by default, so we need to explicitly include them.
isIncludeNoLocationClasses = true
}
}
@@ -176,11 +204,15 @@ abstract class CCTweakedExtension(private val project: Project) {
description = "Generates code coverage report for the ${task.name} task."
executionData(task.get())
classDirectories.from(classDump)
// Don't want to use sourceSets(...) here as we don't use all class directories.
for (ref in this@CCTweakedExtension.sourceDirectories.get()) {
sourceDirectories.from(ref.sourceSet.allSource.sourceDirectories)
if (ref.classes) classDirectories.from(ref.sourceSet.output)
// Don't want to use sourceSets(...) here as we have a custom class directory.
for (ref in sourceSets.get()) sourceDirectories.from(ref.allSource.sourceDirectories)
}
project.extensions.configure(ReportingExtension::class.java) {
reports.register("${task.name}CodeCoverageReport", JacocoCoverageReport::class.java) {
testType.set(TestSuiteType.INTEGRATION_TEST)
}
}
}
@@ -220,31 +252,40 @@ abstract class CCTweakedExtension(private val project: Project) {
).resolve().single()
}
private fun <T> gitProvider(default: T, command: List<String>, process: (String) -> T): Provider<T> {
val baseResult = project.providers.exec {
commandLine = listOf("git", "-C", project.rootDir.absolutePath) + command
}
/**
* Exclude a dependency from being published in Maven.
*/
fun exclude(dep: Dependency) {
excludedDeps.add(dep)
}
return project.provider {
val res = try {
baseResult.standardOutput.asText.get()
} catch (e: IOException) {
project.logger.error("Cannot read Git repository: ${e.message}", e)
return@provider default
} catch (e: GradleException) {
project.logger.error("Cannot read Git repository: ${e.message}", e)
return@provider default
}
process(res)
}
/**
* Configure a [MavenDependencySpec].
*/
fun configureExcludes(spec: MavenDependencySpec) {
for (dep in excludedDeps.get()) spec.exclude(dep)
}
companion object {
private val COMMIT_COUNTS = Pattern.compile("""^\s*[0-9]+\s+(.*)$""")
private val IGNORED_USERS = setOf(
"GitHub", "Daniel Ratcliffe", "NotSquidDev", "Weblate",
"GitHub", "Daniel Ratcliffe", "Weblate",
)
private fun <T> gitProvider(project: Project, default: T, supplier: () -> T): Provider<T> {
return project.provider {
try {
supplier()
} catch (e: IOException) {
project.logger.error("Cannot read Git repository: ${e.message}")
default
} catch (e: GradleException) {
project.logger.error("Cannot read Git repository: ${e.message}")
default
}
}
}
private val isIdeSync: Boolean
get() = java.lang.Boolean.parseBoolean(System.getProperty("idea.sync.active", "false"))
}

View File

@@ -42,6 +42,6 @@ class CCTweakedPlugin : Plugin<Project> {
}
companion object {
val JAVA_VERSION = JavaLanguageVersion.of(21)
val JAVA_VERSION = JavaLanguageVersion.of(17)
}
}

View File

@@ -22,19 +22,19 @@ import org.gradle.language.base.plugins.LifecycleBasePlugin
abstract class DependencyCheck : DefaultTask() {
@get:Input
protected abstract val dependencies: ListProperty<DependencyResult>
abstract val configuration: ListProperty<Configuration>
/**
* A mapping of module coordinates (`group:module`) to versions, overriding the requested version.
*/
@get:Input
protected abstract val overrides: MapProperty<String, String>
abstract val overrides: MapProperty<String, String>
init {
description = "Check :core's dependencies are consistent with Minecraft's."
group = LifecycleBasePlugin.VERIFICATION_GROUP
dependencies.finalizeValueOnRead()
configuration.finalizeValueOnRead()
overrides.finalizeValueOnRead()
}
@@ -45,19 +45,13 @@ abstract class DependencyCheck : DefaultTask() {
overrides.putAll(project.provider { mutableMapOf(module.get().module.toString() to version) })
}
/**
* Add a configuration to check.
*/
fun configuration(configuration: Provider<Configuration>) {
// We can't store the Configuration in the cache, so store the resolved dependencies instead.
dependencies.addAll(configuration.map { it.incoming.resolutionResult.allDependencies })
}
@TaskAction
fun run() {
var ok = true
for (configuration in dependencies.get()) {
if (!check(configuration)) ok = false
for (configuration in configuration.get()) {
configuration.incoming.resolutionResult.allDependencies {
if (!check(this@allDependencies)) ok = false
}
}
if (!ok) {

View File

@@ -9,7 +9,6 @@ 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
@@ -47,21 +46,6 @@ 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].
*/

View File

@@ -0,0 +1,26 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package cc.tweaked.gradle
import net.minecraftforge.gradle.common.util.RunConfig
import net.minecraftforge.gradle.common.util.runs.setRunConfigInternal
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.tasks.JavaExec
import org.gradle.jvm.toolchain.JavaToolchainService
import java.nio.file.Files
/**
* Set [JavaExec] task to run a given [RunConfig].
*/
fun JavaExec.setRunConfig(config: RunConfig) {
dependsOn("prepareRuns")
setRunConfigInternal(project, this, config)
doFirst("Create working directory") { Files.createDirectories(workingDir.toPath()) }
javaLauncher.set(
project.extensions.getByType(JavaToolchainService::class.java)
.launcherFor(project.extensions.getByType(JavaPluginExtension::class.java).toolchain),
)
}

View File

@@ -0,0 +1,73 @@
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package cc.tweaked.gradle
import org.gradle.api.artifacts.Dependency
import org.gradle.api.artifacts.MinimalExternalModuleDependency
import org.gradle.api.artifacts.ProjectDependency
import org.gradle.api.plugins.BasePluginExtension
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.specs.Spec
/**
* A dependency in a POM file.
*/
data class MavenDependency(val groupId: String?, val artifactId: String?, val version: String?, val scope: String?)
/**
* A spec specifying which dependencies to include/exclude.
*/
class MavenDependencySpec {
private val excludeSpecs = mutableListOf<Spec<MavenDependency>>()
fun exclude(spec: Spec<MavenDependency>) {
excludeSpecs.add(spec)
}
fun exclude(dep: Dependency) {
exclude {
// We have to cheat a little for project dependencies, as the project name doesn't match the artifact group.
val name = when (dep) {
is ProjectDependency -> dep.dependencyProject.extensions.getByType(BasePluginExtension::class.java).archivesName.get()
else -> dep.name
}
(dep.group.isNullOrEmpty() || dep.group == it.groupId) &&
(name.isNullOrEmpty() || name == it.artifactId) &&
(dep.version.isNullOrEmpty() || dep.version == it.version)
}
}
fun exclude(dep: MinimalExternalModuleDependency) {
exclude {
dep.module.group == it.groupId && dep.module.name == it.artifactId
}
}
fun isIncluded(dep: MavenDependency) = !excludeSpecs.any { it.isSatisfiedBy(dep) }
}
/**
* Configure dependencies present in this publication's POM file.
*
* While this approach is very ugly, it's the easiest way to handle it!
*/
fun MavenPublication.mavenDependencies(action: MavenDependencySpec.() -> Unit) {
val spec = MavenDependencySpec()
action(spec)
pom.withXml {
val dependencies = XmlUtil.findChild(asNode(), "dependencies") ?: return@withXml
dependencies.children().map { it as groovy.util.Node }.forEach {
val dep = MavenDependency(
groupId = XmlUtil.findChild(it, "groupId")?.text(),
artifactId = XmlUtil.findChild(it, "artifactId")?.text(),
version = XmlUtil.findChild(it, "version")?.text(),
scope = XmlUtil.findChild(it, "scope")?.text(),
)
if (!spec.isIncluded(dep)) it.parent().remove(it)
}
}
}

View File

@@ -24,6 +24,7 @@ class MinecraftConfigurations private constructor(private val project: Project)
private val java = project.extensions.getByType(JavaPluginExtension::class.java)
private val sourceSets = java.sourceSets
private val configurations = project.configurations
private val objects = project.objects
private val main = sourceSets[SourceSet.MAIN_SOURCE_SET_NAME]
private val test = sourceSets[SourceSet.TEST_SOURCE_SET_NAME]
@@ -36,7 +37,13 @@ class MinecraftConfigurations private constructor(private val project: Project)
val client = sourceSets.maybeCreate("client")
// Ensure the client classpaths behave the same as the main ones.
consistentWithMain(client)
configurations.named(client.compileClasspathConfigurationName) {
shouldResolveConsistentlyWith(configurations[main.compileClasspathConfigurationName])
}
configurations.named(client.runtimeClasspathConfigurationName) {
shouldResolveConsistentlyWith(configurations[main.runtimeClasspathConfigurationName])
}
// Set up an API configuration for clients (to ensure it's consistent with the main source set).
val clientApi = configurations.maybeCreate(client.apiConfigurationName).apply {
@@ -78,16 +85,6 @@ class MinecraftConfigurations private constructor(private val project: Project)
setupBasic()
}
private fun consistentWithMain(sourceSet: SourceSet) {
configurations.named(sourceSet.compileClasspathConfigurationName) {
shouldResolveConsistentlyWith(configurations[main.compileClasspathConfigurationName])
}
configurations.named(sourceSet.runtimeClasspathConfigurationName) {
shouldResolveConsistentlyWith(configurations[main.runtimeClasspathConfigurationName])
}
}
private fun setupBasic() {
val client = sourceSets["client"]
@@ -99,30 +96,13 @@ class MinecraftConfigurations private constructor(private val project: Project)
val checkDependencyConsistency =
project.tasks.register("checkDependencyConsistency", DependencyCheck::class.java) {
// We need to check both the main and client classpath *configurations*, as the actual configuration
configuration(configurations.named(main.runtimeClasspathConfigurationName))
configuration(configurations.named(client.runtimeClasspathConfigurationName))
configuration.add(configurations.named(main.runtimeClasspathConfigurationName))
configuration.add(configurations.named(client.runtimeClasspathConfigurationName))
}
project.tasks.named("check") { dependsOn(checkDependencyConsistency) }
}
/**
* Create a new configuration that pulls in the main and client classes from the mod.
*/
private fun createDerivedConfiguration(name: String) {
val client = sourceSets["client"]
val sourceSet = sourceSets.create(name)
sourceSet.compileClasspath += main.compileClasspath + client.compileClasspath
sourceSet.runtimeClasspath += main.runtimeClasspath + client.runtimeClasspath
consistentWithMain(sourceSet)
project.dependencies.add(sourceSet.implementationConfigurationName, main.output)
project.dependencies.add(sourceSet.implementationConfigurationName, client.output)
}
companion object {
const val DATAGEN = "datagen"
const val EXAMPLES = "examples"
const val TEST_MOD = "testMod"
fun setupBasic(project: Project) {
MinecraftConfigurations(project).setupBasic()
}
@@ -130,10 +110,6 @@ class MinecraftConfigurations private constructor(private val project: Project)
fun setup(project: Project) {
MinecraftConfigurations(project).setup()
}
fun createDerivedConfiguration(project: Project, name: String) {
MinecraftConfigurations(project).createDerivedConfiguration(name)
}
}
}

View File

@@ -4,7 +4,7 @@
package cc.tweaked.gradle
import net.neoforged.moddevgradle.internal.RunGameTask
import net.minecraftforge.gradle.common.util.RunConfig
import org.gradle.api.GradleException
import org.gradle.api.file.FileSystemOperations
import org.gradle.api.invocation.Gradle
@@ -19,7 +19,6 @@ 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
/**
@@ -66,19 +65,11 @@ abstract class ClientJavaExec : JavaExec() {
setTestProperties()
}
fun copyFromForge(path: String) = copyFromForge(project.tasks.getByName(path, RunGameTask::class))
/**
* Set this task to run a given [RunGameTask].
* Set this task to run a given [RunConfig].
*/
fun copyFromForge(task: RunGameTask) {
copyFrom(task)
// Eagerly evaluate the behaviour of RunGameTask.exec
environment.putAll(task.environmentProperty.get())
classpath(task.classpathProvider)
workingDir = task.gameDirectory.get().asFile
fun setRunConfig(config: RunConfig) {
(this as JavaExec).setRunConfig(config)
setTestProperties() // setRunConfig may clobber some properties, ensure everything is set.
}
@@ -126,23 +117,6 @@ 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())

View File

@@ -11,9 +11,7 @@ import org.gradle.api.file.Directory
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.*
import org.gradle.process.ExecOperations
import java.io.File
import javax.inject.Inject
class NodePlugin : Plugin<Project> {
override fun apply(project: Project) {
@@ -45,12 +43,9 @@ abstract class NpmInstall : DefaultTask() {
@get:OutputDirectory
val nodeModules: Provider<Directory> = projectRoot.dir("node_modules")
@get:Inject
protected abstract val execOperations: ExecOperations
@TaskAction
fun install() {
execOperations.exec {
project.exec {
commandLine(ProcessHelpers.getExecutable("npm"), "ci")
workingDir = projectRoot.get().asFile
}

View File

@@ -4,10 +4,45 @@
package cc.tweaked.gradle
import org.codehaus.groovy.runtime.ProcessGroovyMethods
import org.gradle.api.GradleException
import java.io.BufferedReader
import java.io.File
import java.io.InputStreamReader
import java.nio.charset.StandardCharsets
internal object ProcessHelpers {
fun startProcess(vararg command: String): Process {
// Something randomly passes in "GIT_DIR=" as an environment variable which clobbers everything else. Don't
// inherit the environment array!
return ProcessBuilder()
.command(*command)
.redirectError(ProcessBuilder.Redirect.INHERIT)
.also { it.environment().clear() }
.start()
}
fun captureOut(vararg command: String): String {
val process = startProcess(*command)
process.outputStream.close()
val result = ProcessGroovyMethods.getText(process)
process.waitForOrThrow("Failed to run command")
return result
}
fun captureLines(vararg command: String): List<String> {
val process = startProcess(*command)
process.outputStream.close()
val out = BufferedReader(InputStreamReader(process.inputStream, StandardCharsets.UTF_8)).use { reader ->
reader.lines().filter { it.isNotEmpty() }.toList()
}
ProcessGroovyMethods.closeStreams(process)
process.waitForOrThrow("Failed to run command")
return out
}
fun onPath(name: String): Boolean {
val path = System.getenv("PATH") ?: return false
return path.splitToSequence(File.pathSeparator).any { File(it, name).exists() }

View File

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

View File

@@ -21,15 +21,6 @@ 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>
@@ -133,7 +124,7 @@ SPDX-License-Identifier: MPL-2.0
</module>
<module name="MethodTypeParameterName" />
<module name="PackageName">
<property name="format" value="^(dan200\.computercraft|cc\.tweaked|com\.example\.examplemod)(\.[a-z][a-z0-9]*)*" />
<property name="format" value="^(dan200\.computercraft|cc\.tweaked)(\.[a-z][a-z0-9]*)*" />
</module>
<module name="ParameterName" />
<module name="StaticVariableName">

View File

@@ -22,7 +22,4 @@ 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>

View File

@@ -1,13 +1,12 @@
# SPDX-FileCopyrightText: 2017 The CC: Tweaked Developers
#
# SPDX-License-Identifier: MPL-2.0
files:
- source: projects/common/src/generated/resources/assets/computercraft/lang/en_us.json
translation: /projects/common/src/main/resources/assets/computercraft/lang/%locale_with_underscore%.json
languages_mapping:
locale_with_underscore:
cs: cs_cz # Czech
cs: cs_cs # Czech
da: da_dk # Danish
de: de_de # German
es-ES: es_es # Spanish

View File

@@ -8,7 +8,7 @@ SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
SPDX-License-Identifier: MPL-2.0
-->
The [`monitor_resize`] event is fired when an adjacent or networked [monitor's][`monitor`] size is changed.
The [`monitor_resize`] event is fired when an adjacent or networked monitor's size is changed.
## Return Values
1. [`string`]: The event name.

View File

@@ -8,7 +8,7 @@ SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
SPDX-License-Identifier: MPL-2.0
-->
The [`monitor_touch`] event is fired when an adjacent or networked [Advanced Monitor][`monitor`] is right-clicked.
The [`monitor_touch`] event is fired when an adjacent or networked Advanced Monitor is right-clicked.
## Return Values
1. [`string`]: The event name.

View File

@@ -8,7 +8,7 @@ SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
SPDX-License-Identifier: MPL-2.0
-->
The [`event!redstone`] event is fired whenever any redstone inputs on the computer or [relay][`redstone_relay`] change.
The [`event!redstone`] event is fired whenever any redstone inputs on the computer change.
## Return values
1. [`string`]: The event name.
@@ -21,7 +21,3 @@ while true do
print("A redstone input has changed!")
end
```
## See also
- [The `redstone` API on computers][`module!redstone`]
- [The `redstone_relay` peripheral][`redstone_relay`]

View File

@@ -16,7 +16,7 @@ CC: Tweaked is a mod for Minecraft which adds programmable computers, turtles an
much-beloved [ComputerCraft], it continues its legacy with improved performance and stability, along with a wealth of
new features.
CC: Tweaked can be installed from [Modrinth]. It runs on both [Minecraft Forge] and [Fabric].
CC: Tweaked can be installed from [CurseForge] or [Modrinth]. It runs on both [Minecraft Forge] and [Fabric].
## Features
Controlled using the [Lua programming language][lua], CC: Tweaked's computers provides all the tools you need to start
@@ -62,6 +62,7 @@ CC: Tweaked lives on [GitHub]. If you've got any ideas, feedback or bugs please
[github]: https://github.com/cc-tweaked/CC-Tweaked/ "CC: Tweaked on GitHub"
[bug]: https://github.com/cc-tweaked/CC-Tweaked/issues/new/choose
[computercraft]: https://github.com/dan200/ComputerCraft "ComputerCraft on GitHub"
[curseforge]: https://minecraft.curseforge.com/projects/cc-tweaked "Download CC: Tweaked from CurseForge"
[modrinth]: https://modrinth.com/mod/gu7yAYhd "Download CC: Tweaked from Modrinth"
[forge]: https://files.minecraftforge.net/ "Download Minecraft Forge."
[Minecraft Forge]: https://files.minecraftforge.net/ "Download Minecraft Forge."

View File

@@ -8,10 +8,9 @@ org.gradle.parallel=true
kotlin.stdlib.default.dependency=false
kotlin.jvm.target.validation.mode=error
neogradle.subsystems.conventions.runs.enabled=false
isUnstable=true
modVersion=1.114.3
# Mod properties
isUnstable=false
modVersion=1.113.1
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
mcVersion=1.21.1
mcVersion=1.20.1

View File

@@ -1,2 +1,2 @@
#This file is generated by updateDaemonJvm
toolchainVersion=21
toolchainVersion=17

View File

@@ -6,76 +6,76 @@
# Minecraft
# MC version is specified in gradle.properties, as we need that in settings.gradle.
# Remember to update corresponding versions in fabric.mod.json/neoforge.mods.toml
fabric-api = "0.102.1+1.21.1"
fabric-loader = "0.15.11"
neoForge = "21.1.9"
neoForgeSpi = "8.0.1"
# 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"
mixin = "0.8.5"
parchment = "2024.07.28"
parchmentMc = "1.21"
yarn = "1.21.1+build.1"
parchment = "2023.08.20"
parchmentMc = "1.20.1"
yarn = "1.20.1+build.10"
# Core dependencies (these versions are tied to the version Minecraft uses)
fastutil = "8.5.12"
guava = "32.1.2-jre"
netty = "4.1.97.Final"
slf4j = "2.0.9"
fastutil = "8.5.9"
guava = "31.1-jre"
netty = "4.1.82.Final"
slf4j = "2.0.1"
# Core dependencies (independent of Minecraft)
asm = "9.6"
autoService = "1.1.1"
checkerFramework = "3.42.0"
cobalt = { strictly = "0.9.5" }
cobalt = "0.9.4"
commonsCli = "1.6.0"
jetbrainsAnnotations = "24.1.0"
jsr305 = "3.0.2"
jzlib = "1.1.3"
kotlin = "2.1.0"
kotlin-coroutines = "1.10.1"
nightConfig = "3.8.1"
kotlin = "1.9.21"
kotlin-coroutines = "1.7.3"
nightConfig = "3.6.7"
# Minecraft mods
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"
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"
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"
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"
create-fabric = "0.5.1-f-build.1467+mc1.20.1"
# Testing
hamcrest = "2.2"
jqwik = "1.8.2"
junit = "5.11.4"
junitPlatform = "1.11.4"
junit = "5.10.1"
jmh = "1.37"
# Build tools
cctJavadoc = "1.8.3"
checkstyle = "10.20.1"
cctJavadoc = "1.8.2"
checkstyle = "10.14.1"
curseForgeGradle = "1.0.14"
errorProne-core = "2.27.0"
errorProne-plugin = "3.1.0"
fabric-loom = "1.9.2"
fabric-loom = "1.7.1"
forgeGradle = "6.0.21"
githubRelease = "2.5.2"
gradleVersions = "0.50.0"
ideaExt = "1.1.7"
illuaminate = "0.1.0-74-gf1551d5"
illuaminate = "0.1.0-73-g43ee16c"
librarian = "1.+"
lwjgl = "3.3.3"
minotaur = "2.8.7"
modDevGradle = "2.0.74"
minotaur = "2.+"
nullAway = "0.10.25"
shadow = "8.3.1"
spotless = "6.23.3"
taskTree = "2.1.1"
teavm = "0.11.0-SQUID.1"
vanillaExtract = "0.2.0"
vanillaExtract = "0.1.3"
versionCatalogUpdate = "0.8.1"
[libraries]
@@ -87,7 +87,7 @@ checkerFramework = { module = "org.checkerframework:checker-qual", version.ref =
cobalt = { module = "cc.tweaked:cobalt", version.ref = "cobalt" }
commonsCli = { module = "commons-cli:commons-cli", version.ref = "commonsCli" }
fastutil = { module = "it.unimi.dsi:fastutil", version.ref = "fastutil" }
neoForgeSpi = { module = "net.neoforged:neoforgespi", version.ref = "neoForgeSpi" }
forgeSpi = { module = "net.minecraftforge:forgespi", version.ref = "forgeSpi" }
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" }
@@ -111,20 +111,19 @@ fabric-api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fab
fabric-junit = { module = "net.fabricmc:fabric-loader-junit", version.ref = "fabric-loader" }
fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" }
fabricPermissions = { module = "me.lucko:fabric-permissions-api", version.ref = "fabricPermissions" }
iris-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" }
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" }
mixin = { module = "org.spongepowered:mixin", version.ref = "mixin" }
mixinExtra = { module = "io.github.llamalad7:mixinextras-common", version.ref = "mixinExtra" }
modmenu = { module = "com.terraformersmc:modmenu", version.ref = "modmenu" }
moreRed = { module = "commoble.morered:morered-1.20.1", version.ref = "moreRed" }
oculus = { module = "maven.modrinth:oculus", version.ref = "oculus" }
rei-api = { module = "me.shedaniel:RoughlyEnoughItems-api", version.ref = "rei" }
rei-builtin = { module = "me.shedaniel:RoughlyEnoughItems-default-plugin", version.ref = "rei" }
rei-fabric = { module = "me.shedaniel:RoughlyEnoughItems-fabric", version.ref = "rei" }
sodium-fabric = { module = "maven.modrinth:sodium", version.ref = "sodium.fabric" }
sodium-forge = { module = "maven.modrinth:sodium", version.ref = "sodium.forge" }
rubidium = { module = "maven.modrinth:rubidium", version.ref = "rubidium" }
sodium = { module = "maven.modrinth:sodium", version.ref = "sodium" }
# Testing
hamcrest = { module = "org.hamcrest:hamcrest", version.ref = "hamcrest" }
@@ -133,7 +132,6 @@ jqwik-engine = { module = "net.jqwik:jqwik-engine", version.ref = "jqwik" }
junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" }
junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" }
junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit" }
junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher", version.ref = "junitPlatform" }
slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" }
jmh = { module = "org.openjdk.jmh:jmh-core", version.ref = "jmh" }
jmh-processor = { module = "org.openjdk.jmh:jmh-generator-annprocess", version.ref = "jmh" }
@@ -147,17 +145,19 @@ lwjgl-glfw = { module = "org.lwjgl:lwjgl-glfw" }
# Build tools
cctJavadoc = { module = "cc.tweaked:cct-javadoc", version.ref = "cctJavadoc" }
checkstyle = { module = "com.puppycrawl.tools:checkstyle", version.ref = "checkstyle" }
curseForgeGradle = { module = "net.darkhax.curseforgegradle:CurseForgeGradle", version.ref = "curseForgeGradle" }
errorProne-annotations = { module = "com.google.errorprone:error_prone_annotations", version.ref = "errorProne-core" }
errorProne-api = { module = "com.google.errorprone:error_prone_check_api", version.ref = "errorProne-core" }
errorProne-core = { module = "com.google.errorprone:error_prone_core", version.ref = "errorProne-core" }
errorProne-plugin = { module = "net.ltgt.gradle:gradle-errorprone-plugin", version.ref = "errorProne-plugin" }
errorProne-testHelpers = { module = "com.google.errorprone:error_prone_test_helpers", version.ref = "errorProne-core" }
fabric-loom = { module = "net.fabricmc:fabric-loom", version.ref = "fabric-loom" }
forgeGradle = { module = "net.minecraftforge.gradle:ForgeGradle", version.ref = "forgeGradle" }
ideaExt = { module = "gradle.plugin.org.jetbrains.gradle.plugin.idea-ext:gradle-idea-ext", version.ref = "ideaExt" }
kotlin-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
librarian = { module = "org.parchmentmc:librarian", version.ref = "librarian" }
minotaur = { module = "com.modrinth.minotaur:Minotaur", version.ref = "minotaur" }
nullAway = { module = "com.uber.nullaway:nullaway", version.ref = "nullAway" }
modDevGradle = { module = "net.neoforged:moddev-gradle", version.ref = "modDevGradle" }
spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" }
teavm-classlib = { module = "org.teavm:teavm-classlib", version.ref = "teavm" }
teavm-core = { module = "org.teavm:teavm-core", version.ref = "teavm" }
@@ -172,9 +172,11 @@ vanillaExtract = { module = "cc.tweaked.vanilla-extract:plugin", version.ref = "
yarn = { module = "net.fabricmc:yarn", version.ref = "yarn" }
[plugins]
forgeGradle = { id = "net.minecraftforge.gradle", version.ref = "forgeGradle" }
githubRelease = { id = "com.github.breadmoirai.github-release", version.ref = "githubRelease" }
gradleVersions = { id = "com.github.ben-manes.versions", version.ref = "gradleVersions" }
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
librarian = { id = "org.parchmentmc.librarian.forgegradle", version.ref = "librarian" }
shadow = { id = "com.gradleup.shadow", version.ref = "shadow" }
taskTree = { id = "com.dorongold.task-tree", version.ref = "taskTree" }
versionCatalogUpdate = { id = "nl.littlerobots.version-catalog-update", version.ref = "versionCatalogUpdate" }
@@ -184,15 +186,15 @@ annotations = ["jsr305", "checkerFramework", "jetbrainsAnnotations"]
kotlin = ["kotlin-stdlib", "kotlin-coroutines"]
# Minecraft
externalMods-common = ["iris-forge", "jei-api", "nightConfig-core", "nightConfig-toml"]
externalMods-forge-compile = ["moreRed", "iris-forge", "jei-api"]
externalMods-common = ["jei-api", "nightConfig-core", "nightConfig-toml"]
externalMods-forge-compile = ["moreRed", "oculus", "jei-api"]
externalMods-forge-runtime = ["jei-forge"]
externalMods-fabric-compile = ["fabricPermissions", "iris-fabric", "jei-api", "rei-api", "rei-builtin"]
externalMods-fabric-compile = ["fabricPermissions", "iris", "jei-api", "rei-api", "rei-builtin"]
externalMods-fabric-runtime = ["jei-fabric", "modmenu"]
# Testing
test = ["junit-jupiter-api", "junit-jupiter-params", "hamcrest", "jqwik-api"]
testRuntime = ["junit-jupiter-engine", "junit-platform-launcher", "jqwik-engine"]
testRuntime = ["junit-jupiter-engine", "jqwik-engine"]
# Build tools
teavm-api = ["teavm-jso", "teavm-jso-apis", "teavm-platform", "teavm-classlib", "teavm-metaprogramming-api"]

Binary file not shown.

View File

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

3
gradlew vendored
View File

@@ -86,7 +86,8 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum

1275
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -13,10 +13,10 @@
},
"devDependencies": {
"@rollup/plugin-node-resolve": "^15.2.1",
"@rollup/plugin-typescript": "^12.0.0",
"@rollup/plugin-typescript": "^11.0.0",
"@rollup/plugin-url": "^8.0.1",
"@swc/core": "^1.3.92",
"@types/node": "^22.0.0",
"@types/node": "^20.8.3",
"lightningcss": "^1.22.0",
"preact-render-to-string": "^6.2.1",
"rehype": "^13.0.0",

View File

@@ -8,8 +8,6 @@ plugins {
id("cc-tweaked.vanilla")
}
val mcVersion: String by extra
java {
withJavadocJar()
}
@@ -18,70 +16,9 @@ dependencies {
api(project(":core-api"))
}
val javadocOverview by tasks.registering(Copy::class) {
from("src/overview.html")
into(layout.buildDirectory.dir(name))
expand(
mapOf(
"mcVersion" to mcVersion,
"modVersion" to version,
),
)
}
tasks.javadoc {
title = "CC: Tweaked $version for Minecraft $mcVersion"
include("dan200/computercraft/api/**/*.java")
options {
(this as StandardJavadocDocletOptions)
inputs.files(javadocOverview)
overview(javadocOverview.get().destinationDir.resolve("overview.html").absolutePath)
groups = mapOf(
"Common" to listOf(
"dan200.computercraft.api",
"dan200.computercraft.api.lua",
"dan200.computercraft.api.peripheral",
),
"Upgrades" to listOf(
"dan200.computercraft.api.client.turtle",
"dan200.computercraft.api.pocket",
"dan200.computercraft.api.turtle",
"dan200.computercraft.api.upgrades",
),
)
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(),
)
val snippetSources = listOf(":common", ":fabric", ":forge").flatMap {
project(it).sourceSets["examples"].allSource.sourceDirectories
}
inputs.files(snippetSources)
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(),
)
}
}

View File

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

View File

@@ -1,84 +0,0 @@
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api.client;
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
import dan200.computercraft.impl.client.ClientPlatformHelper;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.Nullable;
import java.util.stream.Stream;
/**
* The location of a model to load. This may either be:
*
* <ul>
* <li>A {@link ModelResourceLocation}, referencing an already baked model (such as {@code minecraft:dirt#inventory}).</li>
* <li>
* A {@link ResourceLocation}, referencing a path to a model resource (such as {@code minecraft:item/dirt}.
* These models will be baked and stored in the {@link ModelManager} in a loader-specific way.
* </li>
* </ul>
*/
public final class ModelLocation {
/**
* The location of the model.
* <p>
* When {@link #resourceLocation} is null, this is the location of the model to load. When {@link #resourceLocation}
* is non-null, this is the "standalone" variant of the model resource this is used by NeoForge's implementation
* of {@link ClientPlatformHelper#getModel(ModelManager, ModelResourceLocation, ResourceLocation)} to fetch the
* model from the model manger. It is not used on Fabric.
*/
private final ModelResourceLocation modelLocation;
private final @Nullable ResourceLocation resourceLocation;
private ModelLocation(ModelResourceLocation modelLocation, @Nullable ResourceLocation resourceLocation) {
this.modelLocation = modelLocation;
this.resourceLocation = resourceLocation;
}
/**
* Create a {@link ModelLocation} from model in the model manager.
*
* @param location The name of the model to load.
* @return The new {@link ModelLocation} instance.
*/
public static ModelLocation ofModel(ModelResourceLocation location) {
return new ModelLocation(location, null);
}
/**
* Create a {@link ModelLocation} from a resource.
*
* @param location The location of the model resource, such as {@code minecraft:item/dirt}.
* @return The new {@link ModelLocation} instance.
*/
public static ModelLocation ofResource(ResourceLocation location) {
return new ModelLocation(new ModelResourceLocation(location, "standalone"), location);
}
/**
* Get this model from the provided model manager.
*
* @param manager The model manger.
* @return This model, or the missing model if it could not be found.
*/
public BakedModel getModel(ModelManager manager) {
return ClientPlatformHelper.get().getModel(manager, modelLocation, resourceLocation);
}
/**
* Get the models this model location depends on.
*
* @return A list of models that this model location depends on.
* @see TurtleUpgradeModeller#getDependencies()
*/
public Stream<ResourceLocation> getDependencies() {
return resourceLocation == null ? Stream.empty() : Stream.of(resourceLocation);
}
}

View File

@@ -13,47 +13,30 @@ 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 record TransformedModel(BakedModel model, 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 TransformedModel(BakedModel model) {
this(model, Transformation.identity());
this.model = Objects.requireNonNull(model);
matrix = 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));
@@ -63,4 +46,12 @@ public record TransformedModel(BakedModel model, Transformation matrix) {
var model = Minecraft.getInstance().getItemRenderer().getItemModelShaper().getItemModel(item);
return new TransformedModel(model, transform);
}
public BakedModel getModel() {
return model;
}
public Transformation getMatrix() {
return matrix;
}
}

View File

@@ -5,7 +5,7 @@
package dan200.computercraft.api.client.turtle;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.upgrades.UpgradeType;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
/**
* 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 type The turtle upgrade type.
* @param modeller The upgrade modeller.
* @param <T> The type of the turtle upgrade.
* @param serialiser The turtle upgrade serialiser.
* @param modeller The upgrade modeller.
* @param <T> The type of the turtle upgrade.
*/
<T extends ITurtleUpgrade> void register(UpgradeType<T> type, TurtleUpgradeModeller<T> modeller);
<T extends ITurtleUpgrade> void register(TurtleUpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller);
}

View File

@@ -4,31 +4,25 @@
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.core.component.DataComponentPatch;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import javax.annotation.Nullable;
import java.util.stream.Stream;
import java.util.Collection;
import java.util.List;
/**
* Provides models for a {@link ITurtleUpgrade}.
* <p>
* Use {@code dan200.computercraft.api.client.FabricComputerCraftAPIClient#registerTurtleUpgradeModeller} to register a
* modeller on Fabric and {@code dan200.computercraft.api.client.turtle.RegisterTurtleModellersEvent} to register one
* on Forge.
*
* <h2>Example</h2>
* <h3>Fabric</h3>
* {@snippet class=com.example.examplemod.FabricExampleModClient region=turtle_modellers}
*
* <h3>Forge</h3>
* {@snippet class=com.example.examplemod.FabricExampleModClient region=turtle_modellers}
* on Forge
*
* @param <T> The type of turtle upgrade this modeller applies to.
* @see RegisterTurtleUpgradeModeller For multi-loader registration support.
@@ -37,32 +31,47 @@ 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, side and data.
* When the current turtle is {@literal null}, this function should be constant for a given upgrade and side.
*
* @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.
* @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 side Which side of the turtle (left or right) the upgrade resides on.
* @param data Upgrade data instance for current turtle side.
* @return The model that you wish to be used to render your upgrade.
*/
TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, DataComponentPatch data);
TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side);
/**
* Get the models that this turtle modeller depends on.
* Obtain the model to be used when rendering a turtle peripheral.
* <p>
* Models included in this stream will be loaded and baked alongside item and block models, and so may be referenced
* 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);
}
/**
* Get a list of 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
* 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 Stream<ResourceLocation> getDependencies() {
return Stream.of();
default Collection<ResourceLocation> getDependencies() {
return List.of();
}
/**
* A basic {@link TurtleUpgradeModeller} which renders using the upgrade's {@linkplain ITurtleUpgrade#getUpgradeItem(DataComponentPatch)}
* A basic {@link TurtleUpgradeModeller} which renders using the upgrade's {@linkplain ITurtleUpgrade#getUpgradeItem(CompoundTag)}
* upgrade item}.
* <p>
* This uses appropriate transformations for "flat" items, namely those extending the {@literal minecraft:item/generated}
@@ -84,8 +93,9 @@ 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) {
return sided(ModelLocation.ofResource(left), ModelLocation.ofResource(right));
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ModelResourceLocation left, ModelResourceLocation right) {
// TODO(1.21.0): Remove this.
return sided((ResourceLocation) left, right);
}
/**
@@ -96,16 +106,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(ModelLocation left, ModelLocation right) {
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ResourceLocation left, ResourceLocation right) {
return new TurtleUpgradeModeller<>() {
@Override
public TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, DataComponentPatch data) {
public TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side) {
return TransformedModel.of(side == TurtleSide.LEFT ? left : right);
}
@Override
public Stream<ResourceLocation> getDependencies() {
return Stream.of(left, right).flatMap(ModelLocation::getDependencies);
public Collection<ResourceLocation> getDependencies() {
return List.of(left, right);
}
};
}

View File

@@ -11,7 +11,8 @@ 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.core.component.DataComponentPatch;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.item.ItemStack;
import org.joml.Matrix4f;
import javax.annotation.Nullable;
@@ -36,8 +37,16 @@ final class TurtleUpgradeModellers {
private static final class UpgradeItemModeller implements TurtleUpgradeModeller<ITurtleUpgrade> {
@Override
public TransformedModel getModel(ITurtleUpgrade upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, DataComponentPatch data) {
var stack = upgrade.getUpgradeItem(data);
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) {
var model = Minecraft.getInstance().getItemRenderer().getItemModelShaper().getItemModel(stack);
if (stack.hasFoil()) model = ClientPlatformHelper.get().createdFoiledModel(model);
return new TransformedModel(model, side == TurtleSide.LEFT ? leftTransform : rightTransform);

View File

@@ -4,7 +4,6 @@
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;
@@ -18,28 +17,13 @@ import javax.annotation.Nullable;
@ApiStatus.Internal
public interface ClientPlatformHelper {
/**
* Get a model from a resource.
* Equivalent to {@link ModelManager#getModel(ModelResourceLocation)} but for arbitrary {@link ResourceLocation}s.
*
* @param manager The model manager.
* @param resourceLocation The model resourceLocation.
* @param manager The model manager.
* @param location The model location.
* @return The baked model.
* @see ModelLocation
*/
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);
BakedModel getModel(ModelManager manager, ResourceLocation location);
/**
* Wrap this model in a version which renders a foil/enchantment glint.

View File

@@ -4,34 +4,35 @@
package dan200.computercraft.impl.client;
import dan200.computercraft.api.client.ComputerCraftAPIClient;
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.upgrades.UpgradeType;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.impl.Services;
import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nullable;
/**
* Backing interface for CC's client-side API.
* Backing interface for {@link ComputerCraftAPIClient}
* <p>
* Do <strong>NOT</strong> directly reference this class. It exists for internal use by the API.
*/
@ApiStatus.Internal
public interface FabricComputerCraftAPIClientService {
static FabricComputerCraftAPIClientService get() {
public interface ComputerCraftAPIClientService {
static ComputerCraftAPIClientService get() {
var instance = Instance.INSTANCE;
return instance == null ? Services.raise(FabricComputerCraftAPIClientService.class, Instance.ERROR) : instance;
return instance == null ? Services.raise(ComputerCraftAPIClientService.class, Instance.ERROR) : instance;
}
<T extends ITurtleUpgrade> void registerTurtleUpgradeModeller(UpgradeType<T> type, TurtleUpgradeModeller<T> modeller);
<T extends ITurtleUpgrade> void registerTurtleUpgradeModeller(TurtleUpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller);
final class Instance {
static final @Nullable FabricComputerCraftAPIClientService INSTANCE;
static final @Nullable ComputerCraftAPIClientService INSTANCE;
static final @Nullable Throwable ERROR;
static {
var helper = Services.tryLoad(FabricComputerCraftAPIClientService.class);
var helper = Services.tryLoad(ComputerCraftAPIClientService.class);
INSTANCE = helper.instance();
ERROR = helper.error();
}

View File

@@ -171,9 +171,16 @@ public final class ComputerCraftAPI {
* using {@link ILuaAPI#getModuleName()} to expose this library as a module instead of as a global.
* <p>
* This may be used with {@link IComputerSystem#getComponent(ComputerComponent)} to only attach APIs to specific
* computers. For example, one can add a new API just to turtles with the following code:
* computers. For example, one can add an additional API just to turtles with the following code:
*
* {@snippet class=com.example.examplemod.ExampleAPI region=register}
* <pre>{@code
* ComputerCraftAPI.registerAPIFactory(computer -> {
* // Read the turtle component.
* var turtle = computer.getComponent(ComputerComponents.TURTLE);
* // If present then add our API.
* return turtle == null ? null : new MyCustomTurtleApi(turtle);
* });
* }</pre>
*
* @param factory The factory for your API subclass.
* @see ILuaAPIFactory

View File

@@ -6,12 +6,10 @@ 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;
@@ -37,16 +35,8 @@ 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, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, name));
return TagKey.create(Registries.ITEM, new ResourceLocation(ComputerCraftAPI.MOD_ID, name));
}
}
@@ -85,13 +75,13 @@ public class ComputerCraftTags {
public static final TagKey<Block> TURTLE_HOE_BREAKABLE = make("turtle_hoe_harvestable");
/**
* Block which can be {@linkplain BlockState#useItemOn(ItemStack, Level, Player, InteractionHand, BlockHitResult) used}
* when calling {@code turtle.place()}.
* Block which can be {@linkplain BlockState#use(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, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, name));
return TagKey.create(Registries.BLOCK, new ResourceLocation(ComputerCraftAPI.MOD_ID, name));
}
}
}

View File

@@ -1,72 +0,0 @@
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api.detail;
import net.minecraft.core.component.DataComponentHolder;
import net.minecraft.core.component.DataComponentType;
import net.minecraft.world.item.ItemStack;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* An item detail provider for a specific {@linkplain DataComponentType data component} on {@link ItemStack}s or
* other {@link DataComponentHolder}.
*
* @param <T> The type of the component's contents.
*/
public abstract class ComponentDetailProvider<T> implements DetailProvider<DataComponentHolder> {
private final DataComponentType<T> component;
private final @Nullable String namespace;
/**
* Create a new component detail provider. Details will be inserted into a new sub-map named as per {@code namespace}.
*
* @param component The data component to provide details for.
* @param namespace The namespace to use for this provider.
*/
public ComponentDetailProvider(@Nullable String namespace, DataComponentType<T> component) {
Objects.requireNonNull(component);
this.component = component;
this.namespace = namespace;
}
/**
* Create a new component detail provider. Details will be inserted directly into the results.
*
* @param component The data component to provide details for.
*/
public ComponentDetailProvider(DataComponentType<T> component) {
this(null, component);
}
/**
* Provide additional details for the given data component. This method is called by {@code turtle.getItemDetail()}.
* New properties should be added to the given {@link Map}, {@code data}.
* <p>
* This method is always called on the server thread, so it is safe to interact with the world here, but you should
* take care to avoid long blocking operations as this will stall the server and other computers.
*
* @param data The full details to be returned for this item stack. New properties should be added to this map.
* @param item The component to provide details for.
*/
public abstract void provideComponentDetails(Map<? super String, Object> data, T item);
@Override
public final void provideDetails(Map<? super String, Object> data, DataComponentHolder holder) {
var value = holder.get(component);
if (value == null) return;
if (namespace == null) {
provideComponentDetails(data, value);
} else {
Map<? super String, Object> child = new HashMap<>();
provideComponentDetails(child, value);
data.put(namespace, child);
}
}
}

View File

@@ -7,13 +7,11 @@ 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 net.minecraft.world.item.JukeboxSong;
import javax.annotation.Nullable;
@@ -27,12 +25,11 @@ public interface IMedia {
/**
* Get a string representing the label of this item. Will be called via {@code disk.getLabel()} in lua.
*
* @param registries The currently loaded registries.
* @param stack The {@link ItemStack} to inspect.
* @param stack The {@link ItemStack} to inspect.
* @return The label. ie: "Dan's Programs".
*/
@Nullable
String getLabel(HolderLookup.Provider registries, ItemStack stack);
String getLabel(ItemStack stack);
/**
* Set a string representing the label of this item. Will be called vi {@code disk.setLabel()} in lua.
@@ -46,15 +43,26 @@ public interface IMedia {
}
/**
* If this disk represents an item with audio (like a record), get the corresponding {@link JukeboxSong}.
* If this disk represents an item with audio (like a record), get the readable name of the audio track. ie:
* "Jonathan Coulton - Still Alive"
*
* @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.
* @param stack The {@link ItemStack} to modify.
* @return The name, or null if this item does not represent an item with audio.
*/
@Nullable
default Holder<JukeboxSong> getAudio(HolderLookup.Provider registries, ItemStack stack) {
return JukeboxSong.fromStack(registries, stack).orElse(null);
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;
}
/**

View File

@@ -14,7 +14,7 @@ import dan200.computercraft.api.ComputerCraftAPI;
* as a proxy for all network objects. Whilst the node may change networks, an element's node should remain constant
* for its lifespan.
* <p>
* Elements are generally tied to a block or block entity in world. In such as case, one should provide the
* Elements are generally tied to a block or tile entity in world. In such as case, one should provide the
* {@link WiredElement} capability for the appropriate sides.
*/
public interface WiredElement extends WiredSender {

View File

@@ -0,0 +1,92 @@
// 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);
}

View File

@@ -11,7 +11,7 @@ import org.jetbrains.annotations.ApiStatus;
import java.util.Map;
/**
* A single node on a wired network.
* Wired nodes act as a layer between {@link WiredElement}s and {@link WiredNetwork}s.
* <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,6 +32,18 @@ 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>

View File

@@ -8,12 +8,10 @@ import dan200.computercraft.api.network.PacketSender;
/**
* An object on a wired network capable of sending packets.
* An object on a {@link WiredNetwork} 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 {
/**

View File

@@ -4,7 +4,8 @@
package dan200.computercraft.api.pocket;
import net.minecraft.network.chat.Component;
import dan200.computercraft.api.upgrades.UpgradeBase;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
@@ -14,20 +15,27 @@ 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 Component adjective;
private final ResourceLocation id;
private final String adjective;
private final ItemStack stack;
protected AbstractPocketUpgrade(Component adjective, ItemStack stack) {
protected AbstractPocketUpgrade(ResourceLocation id, String adjective, ItemStack stack) {
this.id = id;
this.adjective = adjective;
this.stack = stack;
}
protected AbstractPocketUpgrade(String adjective, ItemStack stack) {
this(Component.translatable(adjective), stack);
protected AbstractPocketUpgrade(ResourceLocation id, ItemStack stack) {
this(id, UpgradeBase.getDefaultAdjective(id), stack);
}
@Override
public final Component getAdjective() {
public final ResourceLocation getUpgradeID() {
return id;
}
@Override
public final String getUnlocalisedAdjective() {
return adjective;
}

View File

@@ -4,9 +4,11 @@
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.core.component.DataComponentPatch;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.ItemStack;
@@ -14,6 +16,7 @@ import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nullable;
import java.util.Map;
/**
* Wrapper class for pocket computers.
@@ -84,7 +87,7 @@ public interface IPocketAccess {
* Get the currently equipped upgrade.
*
* @return The currently equipped upgrade.
* @see #getUpgradeData()
* @see #getUpgradeNBTData()
* @see #setUpgrade(UpgradeData)
*/
@Nullable
@@ -106,20 +109,19 @@ public interface IPocketAccess {
* This is persisted between computer reboots and chunk loads.
*
* @return The upgrade's NBT.
* @see #setUpgradeData(DataComponentPatch)
* @see UpgradeBase#getUpgradeItem(DataComponentPatch)
* @see #updateUpgradeNBTData()
* @see UpgradeBase#getUpgradeItem(CompoundTag)
* @see UpgradeBase#getUpgradeData(ItemStack)
* @see #getUpgrade()
*/
DataComponentPatch getUpgradeData();
CompoundTag getUpgradeNBTData();
/**
* Update the upgrade-specific data.
* Mark the upgrade-specific NBT as dirty.
*
* @param data The new upgrade data.
* @see #getUpgradeData()
* @see #getUpgradeNBTData()
*/
void setUpgradeData(DataComponentPatch data);
void updateUpgradeNBTData();
/**
* Remove the current peripheral and create a new one.
@@ -128,4 +130,13 @@ public interface IPocketAccess {
* entity} changes.
*/
void invalidatePeripheral();
/**
* Get a list of all upgrades for the pocket computer.
*
* @return A collection of all upgrade names.
* @deprecated This is a relic of a previous API, which no longer makes sense with newer versions of ComputerCraft.
*/
@Deprecated(forRemoval = true)
Map<ResourceLocation, IPeripheral> getUpgrades();
}

View File

@@ -4,14 +4,8 @@
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;
@@ -19,32 +13,16 @@ import javax.annotation.Nullable;
/**
* A peripheral which can be equipped to the back side of a pocket computer.
* <p>
* Pocket upgrades are defined in two stages. First, one creates a {@link IPocketUpgrade} subclass and corresponding
* {@link UpgradeType} instance, which are then registered in a Minecraft registry.
* Pocket upgrades are defined in two stages. First, on creates a {@link IPocketUpgrade} subclass and corresponding
* {@link PocketUpgradeSerialiser} instance, which are then registered in a Forge registry.
* <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.
* the upgrade registered internally. See the documentation in {@link PocketUpgradeSerialiser} for details on this process
* and where files should be located.
*
* @see PocketUpgradeSerialiser For how to register a pocket computer upgrade.
*/
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>

View File

@@ -0,0 +1,26 @@
// 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());
}
}

View File

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

View File

@@ -4,7 +4,8 @@
package dan200.computercraft.api.turtle;
import net.minecraft.network.chat.Component;
import dan200.computercraft.api.upgrades.UpgradeBase;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
@@ -14,27 +15,34 @@ 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 Component adjective;
private final String adjective;
private final ItemStack stack;
protected AbstractTurtleUpgrade(TurtleUpgradeType type, Component adjective, ItemStack stack) {
protected AbstractTurtleUpgrade(ResourceLocation id, TurtleUpgradeType type, String adjective, ItemStack stack) {
this.id = id;
this.type = type;
this.adjective = adjective;
this.stack = stack;
}
protected AbstractTurtleUpgrade(TurtleUpgradeType type, String adjective, ItemStack stack) {
this(type, Component.translatable(adjective), stack);
protected AbstractTurtleUpgrade(ResourceLocation id, TurtleUpgradeType type, ItemStack stack) {
this(id, type, UpgradeBase.getDefaultAdjective(id), stack);
}
@Override
public final Component getAdjective() {
public final ResourceLocation getUpgradeID() {
return id;
}
@Override
public final String getUnlocalisedAdjective() {
return adjective;
}
@Override
public final TurtleUpgradeType getUpgradeType() {
public final TurtleUpgradeType getType() {
return type;
}

View File

@@ -12,7 +12,7 @@ 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.core.component.DataComponentPatch;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.Container;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
@@ -229,22 +229,37 @@ 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 #setUpgrade(TurtleSide, UpgradeData)
* @see #setUpgradeWithData(TurtleSide, UpgradeData)
*/
@Nullable
ITurtleUpgrade getUpgrade(TurtleSide side);
/**
* Returns the upgrade on the specified side of the turtle, along with its {@linkplain #getUpgradeData(TurtleSide)
* Returns the upgrade on the specified side of the turtle, along with its {@linkplain #getUpgradeNBTData(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 #setUpgrade(TurtleSide, UpgradeData)
* @see #setUpgradeWithData(TurtleSide, UpgradeData)
*/
@Nullable
UpgradeData<ITurtleUpgrade> getUpgradeWithData(TurtleSide side);
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));
}
/**
* Set the upgrade for a given side and its upgrade data.
@@ -253,7 +268,7 @@ public interface ITurtleAccess {
* @param upgrade The upgrade to set, may be {@code null} to clear.
* @see #getUpgradeWithData(TurtleSide)
*/
void setUpgrade(TurtleSide side, @Nullable UpgradeData<ITurtleUpgrade> upgrade);
void setUpgradeWithData(TurtleSide side, @Nullable UpgradeData<ITurtleUpgrade> upgrade);
/**
* Returns the peripheral created by the upgrade on the specified side of the turtle, if there is one.
@@ -267,23 +282,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 can
* call {@link #setUpgrade(TurtleSide, UpgradeData)} to modify it.
* 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.
*
* @param side The side to get the upgrade data for.
* @return The upgrade-specific data.
* @see #setUpgradeData(TurtleSide, DataComponentPatch)
* @see UpgradeBase#getUpgradeItem(DataComponentPatch)
* @see #updateUpgradeNBTData(TurtleSide)
* @see UpgradeBase#getUpgradeItem(CompoundTag)
* @see UpgradeBase#getUpgradeData(ItemStack)
*/
DataComponentPatch getUpgradeData(TurtleSide side);
CompoundTag getUpgradeNBTData(TurtleSide side);
/**
* Update the upgrade-specific data.
* 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.
*
* @param side The side to set the upgrade data for.
* @param data The new upgrade data.
* @see #getUpgradeData(TurtleSide)
* @param side The side to mark dirty.
* @see #updateUpgradeNBTData(TurtleSide)
*/
void setUpgradeData(TurtleSide side, DataComponentPatch data);
void updateUpgradeNBTData(TurtleSide side);
}

View File

@@ -4,129 +4,34 @@
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.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 net.minecraft.nbt.CompoundTag;
import javax.annotation.Nullable;
import java.util.function.Function;
/**
* The primary interface for defining an update for Turtles. A turtle update can either be a new tool, or a new
* peripheral.
* <p>
* Turtle upgrades are defined in two stages. First, one creates a {@link ITurtleUpgrade} subclass and corresponding
* {@link UpgradeType} instance, which are then registered in a Minecraft registry.
* {@link TurtleUpgradeSerialiser} instance, which are then registered in a Forge 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.
* the upgrade registered internally. See the documentation in {@link TurtleUpgradeSerialiser} for details on this process
* and where files should be located.
*
* <h2>Example</h2>
* <h3>Registering the upgrade type</h3>
* First, let's create a new class that implements {@link ITurtleUpgrade}. It is recommended to subclass
* {@link AbstractTurtleUpgrade}, as that provides a default implementation of most methods.
*
* {@snippet class=com.example.examplemod.ExampleTurtleUpgrade region=body}
*
* 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 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>
* {@snippet class=com.example.examplemod.FabricExampleMod region=turtle_upgrades}
*
* <h4>Forge</h4>
* {@snippet class=com.example.examplemod.ForgeExampleMod region=turtle_upgrades}
*
* <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 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 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_upgrade/<my_upgrade_id>.json}.
*
* {@snippet file=data/examplemod/computercraft/turtle_upgrade/example_turtle_upgrade.json}
*
* 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 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.TurtleUpgradeProvider region=body}
*
* 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}
* @see TurtleUpgradeSerialiser For how to register a turtle upgrade.
*/
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 getUpgradeType();
TurtleUpgradeType getType();
/**
* Will only be called for peripheral upgrades. Creates a peripheral for a turtle being placed using this upgrade.
@@ -185,7 +90,7 @@ public interface ITurtleUpgrade extends UpgradeBase {
* @param upgradeData Data that currently stored for this upgrade
* @return Filtered version of this data.
*/
default DataComponentPatch getPersistedData(DataComponentPatch upgradeData) {
default CompoundTag getPersistedData(CompoundTag upgradeData) {
return upgradeData;
}
}

View File

@@ -1,149 +0,0 @@
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api.turtle;
import dan200.computercraft.api.ComputerCraftTags;
import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.impl.ComputerCraftAPIService;
import dan200.computercraft.impl.upgrades.TurtleToolSpec;
import net.minecraft.core.component.DataComponents;
import net.minecraft.data.worldgen.BootstrapContext;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.Block;
import javax.annotation.Nullable;
import java.util.Optional;
/**
* A builder for custom turtle tool upgrades.
* <p>
* This can be used from your <a href="./ITurtleUpgrade.html#datagen">data generator</a> code in order to
* register turtle tools for your mod's tools.
*
* <h2>Example</h2>
* {@snippet class=com.example.examplemod.data.TurtleToolProvider region=body}
*/
public final class TurtleToolBuilder {
private final ResourceKey<ITurtleUpgrade> id;
private final Item item;
private Component adjective;
private float damageMultiplier = TurtleToolSpec.DEFAULT_DAMAGE_MULTIPLIER;
private @Nullable TagKey<Block> breakable;
private boolean allowEnchantments = false;
private TurtleToolDurability consumeDurability = TurtleToolDurability.NEVER;
private TurtleToolBuilder(ResourceKey<ITurtleUpgrade> id, Item item) {
this.id = id;
adjective = Component.translatable(UpgradeBase.getDefaultAdjective(id.location()));
this.item = item;
}
public static TurtleToolBuilder tool(ResourceLocation id, Item item) {
return new TurtleToolBuilder(ITurtleUpgrade.createKey(id), item);
}
public static TurtleToolBuilder tool(ResourceKey<ITurtleUpgrade> id, Item item) {
return new TurtleToolBuilder(id, item);
}
/**
* Get the id for this turtle tool.
*
* @return The upgrade id.
*/
public ResourceKey<ITurtleUpgrade> id() {
return id;
}
/**
* Specify a custom adjective for this tool. By default this takes its adjective from the upgrade id.
*
* @param adjective The new adjective to use.
* @return The tool builder, for further use.
*/
public TurtleToolBuilder adjective(Component adjective) {
this.adjective = adjective;
return this;
}
/**
* The amount of damage a swing of this tool will do. This is multiplied by {@link Attributes#ATTACK_DAMAGE} to
* get the final damage.
*
* @param damageMultiplier The damage multiplier.
* @return The tool builder, for further use.
*/
public TurtleToolBuilder damageMultiplier(float damageMultiplier) {
this.damageMultiplier = damageMultiplier;
return this;
}
/**
* Indicate that this upgrade allows items which have been {@linkplain ItemStack#isEnchanted() enchanted} or have
* {@linkplain DataComponents#ATTRIBUTE_MODIFIERS custom attribute modifiers}.
*
* @return The tool builder, for further use.
*/
public TurtleToolBuilder allowEnchantments() {
allowEnchantments = true;
return this;
}
/**
* Set when the tool will consume durability.
*
* @param durability The durability predicate.
* @return The tool builder, for further use.
*/
public TurtleToolBuilder consumeDurability(TurtleToolDurability durability) {
consumeDurability = durability;
return this;
}
/**
* Provide a list of breakable blocks. If not given, the tool can break all blocks. If given, only blocks
* in this tag, those in {@link ComputerCraftTags.Blocks#TURTLE_ALWAYS_BREAKABLE} and "insta-mine" ones can
* be broken.
*
* @param breakable The tag containing all blocks breakable by this item.
* @return The tool builder, for further use.
* @see ComputerCraftTags.Blocks
*/
public TurtleToolBuilder breakable(TagKey<Block> breakable) {
this.breakable = breakable;
return this;
}
/**
* Build the turtle tool upgrade.
*
* @return The constructed upgrade.
*/
public ITurtleUpgrade build() {
return ComputerCraftAPIService.get().createTurtleTool(new TurtleToolSpec(
adjective,
item,
damageMultiplier,
allowEnchantments,
consumeDurability,
Optional.ofNullable(breakable)
));
}
/**
* Build this upgrade and register it for datagen.
*
* @param upgrades The registry this upgrade should be added to.
*/
public void register(BootstrapContext<ITurtleUpgrade> upgrades) {
upgrades.register(id(), build());
}
}

View File

@@ -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 TurtleToolBuilder#consumeDurability(TurtleToolDurability)
* @see TurtleUpgradeDataProvider.ToolBuilder#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 DataComponents#ATTRIBUTE_MODIFIERS custom attribute modifiers}.
* {@linkplain ItemStack#getAttributeModifiers(EquipmentSlot) custom attribute modifiers}.
*/
WHEN_ENCHANTED("when_enchanted"),

View File

@@ -0,0 +1,168 @@
// 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.
*
* @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());
}
}));
}
}
}

View File

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

View File

@@ -9,34 +9,37 @@ 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.core.component.DataComponentPatch;
import net.minecraft.network.chat.Component;
import net.minecraft.nbt.CompoundTag;
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 {
/**
* Get the type of this upgrade.
* 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.
*
* @return The type of this upgrade.
* @return The unique ID for this upgrade.
*/
UpgradeType<?> getType();
ResourceLocation getUpgradeID();
/**
* 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.
* Return an unlocalised string to describe this type of computer in item names.
* <p>
* Examples of built-in adjectives are "Wireless", "Mining" and "Crafty".
*
* @return The text component for this upgrade's adjective.
* @return The localisation key for this upgrade's adjective.
*/
Component getAdjective();
String getUnlocalisedAdjective();
/**
* Return an item stack representing the type of item that a computer must be crafted
@@ -54,8 +57,8 @@ public interface UpgradeBase {
/**
* Returns the item stack representing a currently equipped turtle upgrade.
* <p>
* 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,
* 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,
* and the original item stack is returned.
* <p>
* By overriding this method, you can create a new {@link ItemStack} which contains enough data to
@@ -67,24 +70,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(DataComponentPatch upgradeData) {
default ItemStack getUpgradeItem(CompoundTag upgradeData) {
return getCraftingItem();
}
/**
* Extract upgrade data from an {@link ItemStack}.
* <p>
* This upgrade data will be available with {@link ITurtleAccess#getUpgradeData(TurtleSide)} or
* {@link IPocketAccess#getUpgradeData()}.
* This upgrade data will be available with {@link ITurtleAccess#getUpgradeNBTData(TurtleSide)} or
* {@link IPocketAccess#getUpgradeNBTData()}.
* <p>
* This should be an inverse to {@link #getUpgradeItem(DataComponentPatch)}.
* This should be an inverse to {@link #getUpgradeItem(CompoundTag)}.
*
* @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 DataComponentPatch getUpgradeData(ItemStack stack) {
return DataComponentPatch.EMPTY;
default CompoundTag getUpgradeData(ItemStack stack) {
return new CompoundTag();
}
/**
@@ -94,15 +97,26 @@ 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 NBT is exactly the same as the crafting item,
* but this may be relaxed for your upgrade.
* 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.
*
* @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) {
return ItemStack.isSameItemSameComponents(getCraftingItem(), 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);
}
/**
@@ -111,7 +125,7 @@ public interface UpgradeBase {
*
* @param id The upgrade ID.
* @return The generated adjective.
* @see #getAdjective()
* @see #getUnlocalisedAdjective()
*/
static String getDefaultAdjective(ResourceLocation id) {
return Util.makeDescriptionId("upgrade", id) + ".adjective";

View File

@@ -6,57 +6,60 @@ package dan200.computercraft.api.upgrades;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import net.minecraft.core.Holder;
import net.minecraft.core.component.DataComponentPatch;
import net.minecraft.nbt.CompoundTag;
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 holder The current upgrade holder.
* @param data The upgrade's data.
* @param <T> The type of upgrade, either {@link ITurtleUpgrade} or {@link IPocketUpgrade}.
* @param upgrade The current upgrade.
* @param data The upgrade's data.
* @param <T> The type of upgrade, either {@link ITurtleUpgrade} or {@link IPocketUpgrade}.
*/
public record UpgradeData<T extends UpgradeBase>(Holder.Reference<T> holder, DataComponentPatch data) {
public record UpgradeData<T extends UpgradeBase>(T upgrade, CompoundTag data) {
/**
* A utility method to construct a new {@link UpgradeData} instance.
*
* @param holder An upgrade.
* @param data The upgrade's data.
* @param <T> The type of upgrade.
* @param upgrade 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(Holder.Reference<T> holder, DataComponentPatch data) {
return new UpgradeData<>(holder, data);
public static <T extends UpgradeBase> UpgradeData<T> of(T upgrade, CompoundTag data) {
return new UpgradeData<>(upgrade, data);
}
/**
* Create an {@link UpgradeData} containing the default {@linkplain #data() data} for an upgrade.
*
* @param holder The upgrade instance.
* @param <T> The type of upgrade.
* @param upgrade The upgrade instance.
* @param <T> The type of upgrade.
* @return The default upgrade data.
*/
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");
public static <T extends UpgradeBase> UpgradeData<T> ofDefault(T upgrade) {
return of(upgrade, upgrade.getUpgradeData(upgrade.getCraftingItem()));
}
/**
* Get the current upgrade.
* Take a copy of a (possibly {@code null}) {@link UpgradeData} instance.
*
* @return The current upgrade.
* @param upgrade The copied upgrade data.
* @param <T> The type of upgrade.
* @return The newly created upgrade data.
*/
public T upgrade() {
return holder().value();
@Contract("!null -> !null; null -> null")
public static <T extends UpgradeBase> @Nullable UpgradeData<T> copyOf(@Nullable UpgradeData<T> upgrade) {
return upgrade == null ? null : upgrade.copy();
}
/**
* Get the {@linkplain UpgradeBase#getUpgradeItem(DataComponentPatch) upgrade item} for this upgrade.
* Get the {@linkplain UpgradeBase#getUpgradeItem(CompoundTag) 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}.
@@ -64,6 +67,16 @@ public record UpgradeData<T extends UpgradeBase>(Holder.Reference<T> holder, Dat
* @return This upgrade's item.
*/
public ItemStack getUpgradeItem() {
return upgrade().getUpgradeItem(data).copy();
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());
}
}

View File

@@ -0,0 +1,179 @@
// 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.
*/
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.
* <p>
* <strong>Example usage:</strong>
* <pre>{@code
* protected void addUpgrades(Consumer<Upgrade<TurtleUpgradeSerialiser<?>>> addUpgrade) {
* simple(new ResourceLocation("mymod", "speaker"), SPEAKER_SERIALISER.get()).add(addUpgrade);
* }
* }</pre>
*
* @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);
});
}
}
}

View File

@@ -0,0 +1,52 @@
// 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);
}

View File

@@ -1,88 +0,0 @@
// 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"));
}
}

View File

@@ -1,16 +0,0 @@
// 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> {
}

View File

@@ -4,7 +4,6 @@
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,12 +15,10 @@ import dan200.computercraft.api.media.MediaProvider;
import dan200.computercraft.api.network.PacketNetwork;
import dan200.computercraft.api.network.wired.WiredElement;
import dan200.computercraft.api.network.wired.WiredNode;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
import dan200.computercraft.api.redstone.BundledRedstoneProvider;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleRefuelHandler;
import dan200.computercraft.api.upgrades.UpgradeType;
import dan200.computercraft.impl.upgrades.TurtleToolSpec;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Registry;
@@ -70,15 +67,9 @@ public interface ComputerCraftAPIService {
void registerRefuelHandler(TurtleRefuelHandler handler);
ResourceKey<Registry<UpgradeType<? extends ITurtleUpgrade>>> turtleUpgradeRegistryId();
ResourceKey<Registry<TurtleUpgradeSerialiser<?>>> turtleUpgradeRegistryId();
Codec<ITurtleUpgrade> turtleUpgradeCodec();
ResourceKey<Registry<UpgradeType<? extends IPocketUpgrade>>> pocketUpgradeRegistryId();
ITurtleUpgrade createTurtleTool(TurtleToolSpec spec);
Codec<IPocketUpgrade> pocketUpgradeCodec();
ResourceKey<Registry<PocketUpgradeSerialiser<?>>> pocketUpgradeRegistryId();
DetailRegistry<ItemStack> getItemStackDetailRegistry();

View File

@@ -0,0 +1,92 @@
// 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() {
}
}
}

View File

@@ -0,0 +1,49 @@
// 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());
}
}

View File

@@ -0,0 +1,44 @@
// 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) {
}
}

View File

@@ -1,49 +0,0 @@
// 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));
}

View File

@@ -1,68 +0,0 @@
<!--
SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
SPDX-License-Identifier: MPL-2.0
-->
<!DOCTYPE HTML>
<html lang="en">
<body>
<p>
This is the documentation for CC: Tweaked $modVersion for Minecraft $mcVersion. Documentation for other versions of
Minecraft are available on the CC: Tweaked website:
<ul>
<li><a href="/mc-1.20.x/javadoc/">Minecraft 1.20.1</a>
<li><a href="/mc-1.21.x/javadoc/">Minecraft 1.21.1</a>
</ul>
<h1>Quick links</h1>
<p>
You probably want to start in the following places:
<ul>
<li>{@linkplain dan200.computercraft.api.peripheral Registering new peripherals}</li>
<li>
{@link dan200.computercraft.api.lua.LuaFunction} and {@link dan200.computercraft.api.lua.IArguments} for
adding methods to your peripheral or Lua objects.
</li>
<li>{@linkplain dan200.computercraft.api.turtle.ITurtleUpgrade Turtle upgrades}</li>
<li>{@linkplain dan200.computercraft.api.pocket.IPocketUpgrade Pocket upgrades}</li>
</ul>
<h1>Using</h1>
<p>
CC: Tweaked is hosted on my maven repo, and so is relatively simple to depend on. You may wish to add a soft (or
hard) dependency in your <code>mods.toml</code> file, with the appropriate version bounds, to ensure that API
functionality you depend on is present.
<pre class="language language-groovy"><code>repositories {
maven {
url "https://maven.squiddev.cc"
content { includeGroup("cc.tweaked") }
}
}
dependencies {
// Vanilla (i.e. for multi-loader systems)
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-common-api:$modVersion")
// Forge Gradle
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-core-api:$modVersion")
compileOnly(fg.deobf("cc.tweaked:cc-tweaked-$mcVersion-forge-api:$modVersion"))
runtimeOnly(fg.deobf("cc.tweaked:cc-tweaked-$mcVersion-forge:$modVersion"))
// Fabric Loom
modCompileOnly("cc.tweaked:cc-tweaked-$mcVersion-fabric-api:$modVersion")
modRuntimeOnly("cc.tweaked:cc-tweaked-$mcVersion-fabric:$modVersion")
}
</code></pre>
<p>
You should also be careful to only use classes within the <code>dan200.computercraft.api</code> package. Non-API
classes are subject to change at any point. If you depend on functionality outside the API (or need to mixin to
CC:T), please <a href="https://github.com/cc-tweaked/CC-Tweaked/discussions/new/choose">start a discussion</a> to
let me know!
</body>
</html>

View File

@@ -6,11 +6,17 @@ import cc.tweaked.gradle.*
plugins {
id("cc-tweaked.vanilla")
id("cc-tweaked.gametest")
id("cc-tweaked.illuaminate")
id("cc-tweaked.mod")
id("cc-tweaked.publishing")
}
sourceSets {
main {
resources.srcDir("src/generated/resources")
}
}
minecraft {
accessWideners(
"src/main/resources/computercraft.accesswidener",
@@ -23,7 +29,7 @@ configurations {
}
repositories {
maven("https://maven.neoforged.net/") {
maven("https://maven.minecraftforge.net/") {
content {
includeModule("org.spongepowered", "mixin")
}
@@ -36,8 +42,6 @@ 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") })
@@ -63,7 +67,7 @@ dependencies {
}
illuaminate {
version = libs.versions.illuaminate
version.set(libs.versions.illuaminate)
}
val luaJavadoc by tasks.registering(Javadoc::class) {
@@ -84,7 +88,11 @@ val luaJavadoc by tasks.registering(Javadoc::class) {
options.addStringOption("project-root", rootProject.file(".").absolutePath)
options.noTimestamp(false)
javadocTool = javaToolchains.javadocToolFor { languageVersion = CCTweakedPlugin.JAVA_VERSION }
javadocTool.set(
javaToolchains.javadocToolFor {
languageVersion.set(CCTweakedPlugin.JAVA_VERSION)
},
)
}
val lintLua by tasks.registering(IlluaminateExec::class) {
@@ -105,31 +113,20 @@ val lintLua by tasks.registering(IlluaminateExec::class) {
doLast { if (System.getenv("GITHUB_ACTIONS") != null) println("::remove-matcher owner=illuaminate::") }
}
fun MergeTrees.configureForDatagen(source: SourceSet, outputFolder: String) {
output = layout.projectDirectory.dir(outputFolder)
val runData by tasks.registering(MergeTrees::class) {
output = layout.projectDirectory.dir("src/generated/resources")
for (loader in listOf("forge", "fabric")) {
mustRunAfter(":$loader:$name")
mustRunAfter(":$loader:runData")
source {
input {
from(project(":$loader").layout.buildDirectory.dir(source.getTaskName("generateResources", null)))
from(project(":$loader").layout.buildDirectory.dir("generatedResources"))
exclude(".cache")
}
output = project(":$loader").layout.projectDirectory.dir(outputFolder)
output = project(":$loader").layout.projectDirectory.dir("src/generated/resources")
}
}
}
val runData by tasks.registering(MergeTrees::class) {
configureForDatagen(sourceSets.main.get(), "src/generated/resources")
}
val runExampleData by tasks.registering(MergeTrees::class) {
configureForDatagen(sourceSets.examples.get(), "src/examples/generatedResources")
}
// We can't create accurate module metadata for our additional capabilities, so disable it.
project.tasks.withType(GenerateModuleMetadata::class.java).configureEach {
isEnabled = false
}
tasks.withType(GenerateModuleMetadata::class).configureEach { isEnabled = false }

View File

@@ -64,9 +64,6 @@ public final class ClientHooks {
public static void onWorldUnload() {
MonitorRenderState.destroyAll();
SpeakerManager.reset();
}
public static void onDisconnect() {
ClientPocketComputers.reset();
}
@@ -111,7 +108,7 @@ public final class ClientHooks {
*/
public static void addBlockDebugInfo(Consumer<String> addText) {
var minecraft = Minecraft.getInstance();
if (!minecraft.getDebugOverlay().showDebugScreen() || minecraft.level == null) return;
if (!minecraft.options.renderDebug || 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 +128,8 @@ public final class ClientHooks {
}
private static void addTurtleUpgrade(Consumer<String> out, TurtleBlockEntity turtle, TurtleSide side) {
var upgrade = turtle.getAccess().getUpgradeWithData(side);
if (upgrade != null) out.accept(String.format("Upgrade[%s]: %s", side, upgrade.holder().key().location()));
var upgrade = turtle.getUpgrade(side);
if (upgrade != null) out.accept(String.format("Upgrade[%s]: %s", side, upgrade.getUpgradeID()));
}
/**
@@ -141,7 +138,7 @@ public final class ClientHooks {
* @param addText A callback which adds a single line of text.
*/
public static void addGameDebugInfo(Consumer<String> addText) {
if (MonitorBlockEntityRenderer.hasRenderedThisFrame() && Minecraft.getInstance().getDebugOverlay().showDebugScreen()) {
if (MonitorBlockEntityRenderer.hasRenderedThisFrame() && Minecraft.getInstance().options.renderDebug) {
addText.accept("[CC:T] Monitor renderer: " + MonitorBlockEntityRenderer.currentRenderer());
}
}

View File

@@ -22,17 +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.turtle.TurtleOverlay;
import dan200.computercraft.shared.media.items.TreasureDiskItem;
import net.minecraft.Util;
import net.minecraft.client.Minecraft;
import net.minecraft.client.color.item.ItemColor;
import net.minecraft.client.gui.screens.MenuScreens;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.gui.screens.inventory.MenuAccess;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.ShaderInstance;
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
@@ -43,19 +42,14 @@ 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 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;
@@ -89,6 +83,14 @@ 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);
@@ -97,41 +99,28 @@ public final class ClientRegistry {
ModRegistry.Items.POCKET_COMPUTER_NORMAL, ModRegistry.Items.POCKET_COMPUTER_ADVANCED
);
registerItemProperty(itemProperties, "coloured",
(stack, world, player, random) -> DyedItemColor.getOrDefault(stack, -1) != -1 ? 1 : 0,
(stack, world, player, random) -> IColouredItem.getColourBasic(stack) != -1 ? 1 : 0,
ModRegistry.Items.POCKET_COMPUTER_NORMAL, ModRegistry.Items.POCKET_COMPUTER_ADVANCED
);
}
public static void registerMenuScreens(RegisterMenuScreen register) {
register.<AbstractComputerMenu, ComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.COMPUTER.get(), ComputerScreen::new);
register.<AbstractComputerMenu, 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.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.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.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.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.WIRELESS_MODEM.get(), new TurtleModemModeller());
register.register(ModRegistry.TurtleUpgradeTypes.TOOL.get(), TurtleUpgradeModeller.flatItem());
register.register(ModRegistry.TurtleSerialisers.WIRELESS_MODEM_NORMAL.get(), new TurtleModemModeller(false));
register.register(ModRegistry.TurtleSerialisers.WIRELESS_MODEM_ADVANCED.get(), new TurtleModemModeller(true));
register.register(ModRegistry.TurtleSerialisers.TOOL.get(), TurtleUpgradeModeller.flatItem());
}
@SafeVarargs
private static void registerItemProperty(RegisterItemProperty itemProperties, String name, ClampedItemPropertyFunction getter, Supplier<? extends Item>... items) {
var id = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, name);
var id = new ResourceLocation(ComputerCraftAPI.MOD_ID, name);
for (var item : items) itemProperties.register(item.get(), id, getter);
}
@@ -147,25 +136,26 @@ public final class ClientRegistry {
register.accept(GuiSprites.initialise(minecraft.getTextureManager()));
}
private static final ResourceLocation[] EXTRA_MODELS = {
TurtleOverlay.ELF_MODEL,
TurtleBlockEntityRenderer.COLOUR_TURTLE_MODEL,
private static final String[] EXTRA_MODELS = new String[]{
"block/turtle_colour",
"block/turtle_elf_overlay",
"block/turtle_rainbow_overlay",
"block/turtle_trans_overlay",
};
public static void registerExtraModels(Consumer<ResourceLocation> register, Collection<ResourceLocation> extraModels) {
for (var model : EXTRA_MODELS) register.accept(model);
extraModels.forEach(register);
public static void registerExtraModels(Consumer<ResourceLocation> register) {
for (var model : EXTRA_MODELS) register.accept(new ResourceLocation(ComputerCraftAPI.MOD_ID, model));
TurtleUpgradeModellers.getDependencies().forEach(register);
}
public static void registerItemColours(BiConsumer<ItemColor, ItemLike> register) {
register.accept(
(stack, layer) -> layer == 1 ? DiskItem.getColour(stack) : -1,
(stack, layer) -> layer == 1 ? ((DiskItem) stack.getItem()).getColour(stack) : 0xFFFFFF,
ModRegistry.Items.DISK.get()
);
register.accept(
(stack, layer) -> layer == 1 ? DyedItemColor.getOrDefault(stack, Colour.BLUE.getARGB()) : -1,
(stack, layer) -> layer == 1 ? TreasureDiskItem.getColour(stack) : 0xFFFFFF,
ModRegistry.Items.TREASURE_DISK.get()
);
@@ -178,17 +168,17 @@ public final class ClientRegistry {
private static int getPocketColour(ItemStack stack, int layer) {
return switch (layer) {
default -> -1;
case 1 -> DyedItemColor.getOrDefault(stack, -1); // Frame colour
default -> 0xFFFFFF;
case 1 -> IColouredItem.getColourBasic(stack); // Frame colour
case 2 -> { // Light colour
var computer = ClientPocketComputers.get(stack);
yield computer == null || computer.getLightState() == -1 ? Colour.BLACK.getARGB() : FastColor.ARGB32.opaque(computer.getLightState());
yield computer == null || computer.getLightState() == -1 ? Colour.BLACK.getHex() : computer.getLightState();
}
};
}
private static int getTurtleColour(ItemStack stack, int layer) {
return layer == 0 ? DyedItemColor.getOrDefault(stack, -1) : -1;
return layer == 0 ? ((IColouredItem) stack.getItem()).getColour(stack) : 0xFFFFFF;
}
public static void registerShaders(ResourceProvider resources, BiConsumer<ShaderInstance, Consumer<ShaderInstance>> load) throws IOException {

View File

@@ -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.rescaleChat();
chat.refreshTrimmedMessage();
}
TableFormatter.super.display(table);

View File

@@ -0,0 +1,20 @@
// 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);
}
}

View File

@@ -127,6 +127,7 @@ 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);
}

View File

@@ -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 = ResourceLocation.fromNamespaceAndPath("computercraft", "textures/gui/disk_drive.png");
private static final ResourceLocation BACKGROUND = new ResourceLocation("computercraft", "textures/gui/disk_drive.png");
public DiskDriveScreen(DiskDriveMenu container, Inventory player, Component title) {
super(container, player, title);
@@ -28,6 +28,7 @@ public class DiskDriveScreen extends AbstractContainerScreen<DiskDriveMenu> {
@Override
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
renderBackground(graphics);
super.render(graphics, mouseX, mouseY, partialTicks);
renderTooltip(graphics, mouseX, mouseY);
}

View File

@@ -6,6 +6,7 @@ package dan200.computercraft.client.gui;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.client.render.ComputerBorderRenderer;
import dan200.computercraft.data.client.ClientDataProviders;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.texture.TextureManager;
@@ -20,7 +21,7 @@ import java.util.stream.Stream;
* Sprite sheet for all GUI texutres in the mod.
*/
public final class GuiSprites extends TextureAtlasHolder {
public static final ResourceLocation SPRITE_SHEET = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "gui");
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 static final ButtonTextures TURNED_OFF = button("turned_off");
@@ -34,16 +35,16 @@ public final class GuiSprites extends TextureAtlasHolder {
private static ButtonTextures button(String name) {
return new ButtonTextures(
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "gui/buttons/" + name),
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "gui/buttons/" + name + "_hover")
new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/buttons/" + name),
new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/buttons/" + name + "_hover")
);
}
private static ComputerTextures computer(String name, boolean pocket, boolean sidebar) {
return new ComputerTextures(
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
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
);
}
@@ -112,6 +113,7 @@ public final class GuiSprites extends TextureAtlasHolder {
* @param pocketBottom The texture for the bottom of a pocket computer.
* @param sidebar The texture for the computer sidebar.
* @see ComputerBorderRenderer
* @see ClientDataProviders
*/
public record ComputerTextures(
ResourceLocation border,

View File

@@ -9,7 +9,6 @@ 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;
@@ -19,7 +18,6 @@ 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;
@@ -81,7 +79,7 @@ public class ItemToast implements Toast {
}
if (width == 160 && message.size() <= 1) {
graphics.blitSprite(TEXTURE, 0, 0, width, height());
graphics.blit(TEXTURE, 0, 0, 0, 64, width, height());
} else {
var height = height();
@@ -111,14 +109,14 @@ public class ItemToast implements Toast {
}
private static void renderBackgroundRow(GuiGraphics graphics, int x, int u, int y, int height) {
var leftOffset = u == 0 ? 20 : 5;
var leftOffset = 5;
var rightOffset = Math.min(60, x - leftOffset);
graphics.blitSprite(TEXTURE, 160, 32, 0, u, 0, y, leftOffset, height);
graphics.blit(TEXTURE, 0, y, 0, 32 + u, leftOffset, height);
for (var k = leftOffset; k < x - rightOffset; k += 64) {
graphics.blitSprite(TEXTURE, 160, 32, 32, u, k, y, Math.min(64, x - k - rightOffset), height);
graphics.blit(TEXTURE, k, y, 32, 32 + u, Math.min(64, x - k - rightOffset), height);
}
graphics.blitSprite(TEXTURE, 160, 32, 160 - rightOffset, u, x - rightOffset, y, rightOffset, height);
graphics.blit(TEXTURE, x - rightOffset, y, 160 - rightOffset, 32 + u, rightOffset, height);
}
}

View File

@@ -66,9 +66,9 @@ public class NoTermComputerScreen<T extends AbstractComputerMenu> extends Screen
}
@Override
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);
public boolean mouseScrolled(double pMouseX, double pMouseY, double pDelta) {
Objects.requireNonNull(minecraft().player).getInventory().swapPaint(pDelta);
return super.mouseScrolled(pMouseX, pMouseY, pDelta);
}
@Override
@@ -105,11 +105,6 @@ 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);
}

View File

@@ -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 = ResourceLocation.fromNamespaceAndPath("computercraft", "textures/gui/blank_screen.png");
private static final ResourceLocation BACKGROUND = new ResourceLocation("computercraft", "textures/gui/blank_screen.png");
public static final int BUTTON_WIDTH = 100;
public static final int BUTTON_HEIGHT = 20;
@@ -86,6 +86,8 @@ public final class OptionScreen extends Screen {
@Override
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
renderBackground(graphics);
// Render the actual texture.
graphics.blit(BACKGROUND, x, y, 0, 0, innerWidth, PADDING);
graphics.blit(BACKGROUND,

View File

@@ -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 = ResourceLocation.fromNamespaceAndPath("computercraft", "textures/gui/printer.png");
private static final ResourceLocation BACKGROUND = new ResourceLocation("computercraft", "textures/gui/printer.png");
public PrinterScreen(PrinterMenu container, Inventory player, Component title) {
super(container, player, title);
@@ -30,6 +30,7 @@ public class PrinterScreen extends AbstractContainerScreen<PrinterMenu> {
@Override
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
renderBackground(graphics);
super.render(graphics, mouseX, mouseY, partialTicks);
renderTooltip(graphics, mouseX, mouseY);
}

View File

@@ -4,12 +4,14 @@
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.PrintoutData;
import dan200.computercraft.shared.media.items.PrintoutItem;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.inventory.AbstractContainerMenu;
@@ -17,6 +19,7 @@ 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.*;
@@ -37,8 +40,18 @@ public final class PrintoutScreen extends AbstractContainerScreen<PrintoutMenu>
}
private void setPrintout(ItemStack stack) {
page = 0;
printout = PrintoutInfo.of(PrintoutData.getOrEmpty(stack), stack.is(ModRegistry.Items.PRINTED_BOOK.get()));
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);
}
@Override
@@ -93,15 +106,15 @@ public final class PrintoutScreen extends AbstractContainerScreen<PrintoutMenu>
}
@Override
public boolean mouseScrolled(double x, double y, double deltaX, double deltaY) {
if (super.mouseScrolled(x, y, deltaX, deltaY)) return true;
if (deltaY < 0) {
public boolean mouseScrolled(double x, double y, double delta) {
if (super.mouseScrolled(x, y, delta)) return true;
if (delta < 0) {
// Scroll up goes to the next page
nextPage();
return true;
}
if (deltaY > 0) {
if (delta > 0) {
// Scroll down goes to the previous page
previousPage();
return true;
@@ -112,14 +125,23 @@ public final class PrintoutScreen extends AbstractContainerScreen<PrintoutMenu>
@Override
protected void renderBg(GuiGraphics graphics, float partialTicks, int mouseX, int mouseY) {
// Push the printout slightly forward, to avoid clipping into the background.
// 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.
graphics.pose().pushPose();
graphics.pose().translate(0, 0, 1);
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().translate(0, 0, -1);
renderBackground(graphics);
graphics.pose().popPose();
super.render(graphics, mouseX, mouseY, partialTicks);
}
@Override
@@ -128,19 +150,16 @@ public final class PrintoutScreen extends AbstractContainerScreen<PrintoutMenu>
}
record PrintoutInfo(int pages, boolean book, TextBuffer[] text, TextBuffer[] colour) {
public static final PrintoutInfo DEFAULT = of(PrintoutData.EMPTY, false);
public static final PrintoutInfo DEFAULT;
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());
}
static {
var textLines = new TextBuffer[PrintoutItem.LINES_PER_PAGE];
Arrays.fill(textLines, new TextBuffer(" ".repeat(PrintoutItem.LINE_MAX_LENGTH)));
var pages = Math.max(text.length / PrintoutData.LINES_PER_PAGE, 1);
return new PrintoutInfo(pages, book, text, colours);
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);
}
}
}

View File

@@ -23,8 +23,8 @@ import static dan200.computercraft.shared.turtle.inventory.TurtleMenu.*;
* The GUI for turtles.
*/
public class TurtleScreen extends AbstractComputerScreen<TurtleMenu> {
private static final ResourceLocation BACKGROUND_NORMAL = 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 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 int TEX_WIDTH = 278;
private static final int TEX_HEIGHT = 217;

View File

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

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