1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-10-19 16:07:38 +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_for_brace_force = if_multiline
ij_any_spaces_within_array_initializer_braces = true 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] [*.md]
trim_trailing_whitespace = false trim_trailing_whitespace = false
@@ -26,16 +31,3 @@ indent_size = 2
[*.yml] [*.yml]
indent_size = 2 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 id: mc-version
attributes: attributes:
label: Minecraft Version label: Minecraft Version
description: | description: What version of Minecraft are you using?
What version of Minecraft are you using? If your version is not listed, please try to reproduce on one of the supported versions.
options: options:
- 1.20.1 - 1.20.1
- 1.21.x - 1.21.x
@@ -27,5 +26,8 @@ body:
label: Details label: Details
description: | description: |
Description of the bug. Please include the following: 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! - Logs: These will be located in the `logs/` directory of your Minecraft
- 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. 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 - name: 📥 Set up Java
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
java-version: 21 java-version: 17
distribution: 'temurin' distribution: 'temurin'
- name: 📥 Setup Gradle - name: 📥 Setup Gradle
@@ -30,18 +30,6 @@ jobs:
- name: ⚒️ Build - name: ⚒️ Build
run: ./gradlew assemble || ./gradlew assemble 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 - name: Cache pre-commit
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
@@ -62,10 +50,30 @@ jobs:
- name: 🧪 Run integration tests - name: 🧪 Run integration tests
run: ./gradlew runGametest 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 - name: 🧪 Parse test reports
run: ./tools/parse-reports.py run: ./tools/parse-reports.py
if: ${{ failure() }} 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: build-core:
strategy: strategy:
fail-fast: false fail-fast: false
@@ -87,7 +95,7 @@ jobs:
- name: 📥 Set up Java - name: 📥 Set up Java
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
java-version: 21 java-version: 17
distribution: 'temurin' distribution: 'temurin'
- name: 📥 Setup Gradle - name: 📥 Setup Gradle

View File

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

1
.gitignore vendored
View File

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

View File

@@ -27,7 +27,7 @@ repos:
exclude: "^(.*\\.(bat)|LICENSE)$" exclude: "^(.*\\.(bat)|LICENSE)$"
- repo: https://github.com/fsfe/reuse-tool - repo: https://github.com/fsfe/reuse-tool
rev: v5.0.2 rev: v4.0.3
hooks: hooks:
- id: reuse - id: reuse
@@ -58,7 +58,6 @@ repos:
exclude: | exclude: |
(?x)^( (?x)^(
projects/[a-z]+/src/generated| projects/[a-z]+/src/generated|
projects/[a-z]+/src/examples/generatedResources|
projects/core/src/test/resources/test-rom/data/json-parsing/| projects/core/src/test/resources/test-rom/data/json-parsing/|
.*\.dfpwm .*\.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. use the issue templates - they provide a useful hint on what information to provide.
## Translations ## 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 ## Setting up a development environment
In order to develop CC: Tweaked, you'll need to download the source code and then run it. 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: - 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/). - [Git](https://git-scm.com/).
- [NodeJS 20 or later][node]. - [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). `projects/forge/build/libs` (for Forge) or `projects/fabric/build/libs` (for Fabric).
## Developing CC: Tweaked ## Developing CC: Tweaked
Before making any major changes to CC: Tweaked, I'd recommend starting opening an issue or starting a discussion on Before making any major changes to CC: Tweaked, I'd recommend you have a read of the [the architecture
GitHub first. It's often helpful to discuss features before spending time developing them! 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]!
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]!
### Testing ### Testing
When making larger changes, it may be useful to write a test to make sure your code works as expected. 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> </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") [![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] [![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 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 much-beloved [ComputerCraft], it continues its legacy with improved performance and stability, along with a wealth of
new features. 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 ## Contributing
Any contribution is welcome, be that using the mod, reporting bugs or contributing code. If you want to get started 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") compileOnly("cc.tweaked:cc-tweaked-$mcVersion-common-api:$cctVersion")
// Forge Gradle // Forge Gradle
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-forge-api:$cctVersion") compileOnly("cc.tweaked:cc-tweaked-$mcVersion-core-api:$cctVersion")
runtimeOnly("cc.tweaked:cc-tweaked-$mcVersion-forge:$cctVersion") compileOnly(fg.deobf("cc.tweaked:cc-tweaked-$mcVersion-forge-api:$cctVersion"))
runtimeOnly(fg.deobf("cc.tweaked:cc-tweaked-$mcVersion-forge:$cctVersion"))
// Fabric Loom // Fabric Loom
modCompileOnly("cc.tweaked:cc-tweaked-$mcVersion-fabric-api:$cctVersion") 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 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 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! 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/). the generated documentation [can be browsed online](https://tweaked.cc/javadoc/).
[computercraft]: https://github.com/dan200/ComputerCraft "ComputerCraft on GitHub" [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" [modrinth]: https://modrinth.com/mod/gu7yAYhd "Download CC: Tweaked from Modrinth"
[Minecraft Forge]: https://files.minecraftforge.net/ "Download Minecraft Forge." [Minecraft Forge]: https://files.minecraftforge.net/ "Download Minecraft Forge."
[Fabric]: https://fabricmc.net/use/installer/ "Download Fabric." [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" SPDX-PackageDownloadLocation = "https://github.com/cc-tweaked/cc-tweaked"
[[annotations]] [[annotations]]
# Generated/data files are CC0.
SPDX-FileCopyrightText = "The CC: Tweaked Developers" SPDX-FileCopyrightText = "The CC: Tweaked Developers"
SPDX-License-Identifier = "CC0-1.0" SPDX-License-Identifier = "CC0-1.0"
path = [ path = [
# Generated/data files are CC0.
"gradle/gradle-daemon-jvm.properties", "gradle/gradle-daemon-jvm.properties",
"projects/common/src/main/resources/assets/computercraft/sounds.json", "projects/common/src/main/resources/assets/computercraft/sounds.json",
"projects/common/src/main/resources/assets/computercraft/sounds/empty.ogg", "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/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/index.json",
"projects/web/src/htmlTransform/export/items/minecraft/**", "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]] [[annotations]]
@@ -35,15 +30,23 @@ path = [
"doc/images/**", "doc/images/**",
"package.json", "package.json",
"package-lock.json", "package-lock.json",
"projects/*/src/*/resources/*.mixins.json", "projects/common/src/client/resources/computercraft-client.mixins.json",
"projects/fabric/src/*/resources/fabric.mod.json",
"projects/common/src/main/resources/assets/minecraft/shaders/core/computercraft/monitor_tbo.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/data/computercraft/loot_tables/treasure_disk.json",
"projects/common/src/testMod/resources/pack.mcmeta", "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/command/.ignoreme",
"projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/.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/modules/turtle/.ignoreme",
"projects/core/src/main/resources/data/computercraft/lua/rom/motd.txt", "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/.settings",
"projects/web/src/frontend/mount/example.nfp", "projects/web/src/frontend/mount/example.nfp",
"projects/web/src/frontend/mount/example.nft", "projects/web/src/frontend/mount/example.nft",
@@ -70,7 +73,7 @@ path = [
] ]
[[annotations]] [[annotations]]
# Community-contributed language files # Community-contributed license files
SPDX-FileCopyrightText = "2017 The CC: Tweaked Developers" SPDX-FileCopyrightText = "2017 The CC: Tweaked Developers"
SPDX-License-Identifier = "LicenseRef-CCPL" SPDX-License-Identifier = "LicenseRef-CCPL"
path = [ path = [
@@ -84,11 +87,18 @@ path = [
] ]
[[annotations]] [[annotations]]
# Community-contributed language files # Community-contributed license files
SPDX-FileCopyrightText = "2017 The CC: Tweaked Developers" SPDX-FileCopyrightText = "2017 The CC: Tweaked Developers"
SPDX-License-Identifier = "MPL-2.0" SPDX-License-Identifier = "MPL-2.0"
path = "projects/common/src/main/resources/assets/computercraft/lang/**" 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]] [[annotations]]
path = ["gradle/wrapper/**"] path = ["gradle/wrapper/**"]
SPDX-FileCopyrightText = "Gradle Inc" SPDX-FileCopyrightText = "Gradle Inc"

View File

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

View File

@@ -14,10 +14,18 @@ repositories {
mavenCentral() mavenCentral()
gradlePluginPortal() gradlePluginPortal()
maven("https://maven.neoforged.net") { maven("https://maven.minecraftforge.net") {
name = "NeoForge" name = "Forge"
content { 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.kotlin.plugin)
implementation(libs.spotless) implementation(libs.spotless)
implementation(libs.curseForgeGradle)
implementation(libs.fabric.loom) implementation(libs.fabric.loom)
implementation(libs.forgeGradle)
implementation(libs.ideaExt) implementation(libs.ideaExt)
implementation(libs.librarian)
implementation(libs.minotaur) implementation(libs.minotaur)
implementation(libs.modDevGradle)
implementation(libs.vanillaExtract) implementation(libs.vanillaExtract)
} }
@@ -68,7 +78,7 @@ gradlePlugin {
} }
versionCatalogUpdate { versionCatalogUpdate {
sortByKey = false sortByKey.set(false)
keep { keepUnusedLibraries = true } keep { keepUnusedLibraries.set(true) }
catalogFile = file("../gradle/libs.versions.toml") catalogFile.set(file("../gradle/libs.versions.toml"))
} }

View File

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

View File

@@ -10,22 +10,21 @@ import cc.tweaked.gradle.IdeaRunConfigurations
import cc.tweaked.gradle.MinecraftConfigurations import cc.tweaked.gradle.MinecraftConfigurations
plugins { 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("cc-tweaked.java-convention")
id("net.neoforged.moddev") id("org.parchmentmc.librarian.forgegradle")
} }
plugins.apply(CCTweakedPlugin::class.java) plugins.apply(CCTweakedPlugin::class.java)
val mcVersion: String by extra val mcVersion: String by extra
neoForge { minecraft {
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs") 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 { accessTransformer(project(":forge").file("src/main/resources/META-INF/accesstransformer.cfg"))
minecraftVersion = libs.findVersion("parchmentMc").get().toString()
mappingsVersion = libs.findVersion("parchment").get().toString()
}
} }
MinecraftConfigurations.setup(project) MinecraftConfigurations.setup(project)
@@ -33,3 +32,13 @@ MinecraftConfigurations.setup(project)
extensions.configure(CCTweakedExtension::class.java) { extensions.configure(CCTweakedExtension::class.java) {
linters(minecraft = true, loader = "forge") 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 // SPDX-License-Identifier: MPL-2.0
import cc.tweaked.gradle.clientClasses
import cc.tweaked.gradle.commonClasses
/** /**
* Sets up the configurations for writing game tests. * Sets up the configurations for writing game tests.
* *
* See notes in [cc.tweaked.gradle.MinecraftConfigurations] for the general design behind these cursed ideas. * 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 { plugins {
kotlin("jvm") id("cc-tweaked.kotlin-convention")
id("cc-tweaked.java-convention") id("cc-tweaked.java-convention")
} }
val main = sourceSets["main"] val main = sourceSets["main"]
val client = sourceSets["client"] val client = sourceSets["client"]
MinecraftConfigurations.createDerivedConfiguration(project, MinecraftConfigurations.DATAGEN) // Both testMod and testFixtures inherit from the main and client classpath, just so we have access to Minecraft classes.
MinecraftConfigurations.createDerivedConfiguration(project, MinecraftConfigurations.EXAMPLES) val testMod by sourceSets.creating {
MinecraftConfigurations.createDerivedConfiguration(project, MinecraftConfigurations.TEST_MOD) compileClasspath += main.compileClasspath + client.compileClasspath
runtimeClasspath += main.runtimeClasspath + client.runtimeClasspath
}
// Set up generated resources configurations {
sourceSets.main { resources.srcDir("src/generated/resources") } named(testMod.compileClasspathConfigurationName) {
sourceSets.named("examples") { resources.srcDir("src/examples/generatedResources") } shouldResolveConsistentlyWith(compileClasspath.get())
}
// Make sure our examples compile. named(testMod.runtimeClasspathConfigurationName) {
tasks.check { dependsOn(tasks.named("compileExamplesJava")) } 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. // 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 { java {
toolchain { toolchain {
languageVersion= CCTweakedPlugin.JAVA_VERSION languageVersion.set(CCTweakedPlugin.JAVA_VERSION)
} }
withSourcesJar() withSourcesJar()
@@ -44,6 +44,13 @@ repositories {
exclusiveContent { exclusiveContent {
forRepositories(mainMaven) 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 { filter {
includeGroup("cc.tweaked") includeGroup("cc.tweaked")
// Things we mirror // Things we mirror
@@ -57,6 +64,7 @@ repositories {
includeGroup("mezz.jei") includeGroup("mezz.jei")
includeGroup("org.teavm") includeGroup("org.teavm")
includeModule("com.terraformersmc", "modmenu") includeModule("com.terraformersmc", "modmenu")
includeModule("me.lucko", "fabric-permissions-api")
} }
} }
} }
@@ -78,16 +86,8 @@ dependencies {
// Configure default JavaCompile tasks with our arguments. // Configure default JavaCompile tasks with our arguments.
sourceSets.all { sourceSets.all {
tasks.named(compileJavaTaskName, JavaCompile::class.java) { tasks.named(compileJavaTaskName, JavaCompile::class.java) {
// Processing just gives us "No processor claimed any of these annotations", so skip that!
options.compilerArgs.addAll( options.compilerArgs.addAll(listOf("-Xlint", "-Xlint:-processing"))
listOf(
"-Xlint",
// Processing just gives us "No processor claimed any of these annotations", so skip that!
"-Xlint:-processing",
// We violate this pattern too often for it to be a helpful warning. Something to improve one day!
"-Xlint:-this-escape",
),
)
options.errorprone { options.errorprone {
check("InvalidBlockTag", CheckSeverity.OFF) // Broken by @cc.xyz check("InvalidBlockTag", CheckSeverity.OFF) // Broken by @cc.xyz
@@ -99,7 +99,6 @@ sourceSets.all {
check("OperatorPrecedence", CheckSeverity.OFF) // For now. check("OperatorPrecedence", CheckSeverity.OFF) // For now.
check("NonOverridingEquals", CheckSeverity.OFF) // Peripheral.equals makes this hard to avoid check("NonOverridingEquals", CheckSeverity.OFF) // Peripheral.equals makes this hard to avoid
check("FutureReturnValueIgnored", CheckSeverity.OFF) // Too many false positives with Netty 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) check("NullAway", CheckSeverity.ERROR)
option( option(
@@ -156,7 +155,7 @@ tasks.javadoc {
options { options {
val stdOptions = this as StandardJavadocDocletOptions val stdOptions = this as StandardJavadocDocletOptions
stdOptions.addBooleanOption("Xdoclint:all,-missing", true) 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 { tasks.withType(JacocoReport::class.java).configureEach {
reports.xml.required = true reports.xml.required.set(true)
reports.html.required =true reports.html.required.set(true)
} }
project.plugins.withType(CCTweakedPlugin::class.java) { 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. // 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. // 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 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 // SPDX-License-Identifier: MPL-2.0
import net.darkhax.curseforgegradle.TaskPublishCurseForge
import cc.tweaked.gradle.setProvider import cc.tweaked.gradle.setProvider
plugins { plugins {
id("net.darkhax.curseforgegradle")
id("com.modrinth.minotaur") id("com.modrinth.minotaur")
id("cc-tweaked.publishing") id("cc-tweaked.publishing")
} }
@@ -23,17 +25,34 @@ val isUnstable = project.properties["isUnstable"] == "true"
val modVersion: String by extra val modVersion: String by extra
val mcVersion: 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 { modrinth {
token = findProperty("modrinthApiKey") as String? ?: "" token.set(findProperty("modrinthApiKey") as String? ?: "")
projectId = "gu7yAYhd" projectId.set("gu7yAYhd")
versionNumber = modVersion versionNumber.set(modVersion)
versionName = modVersion versionName.set(modVersion)
versionType = if (isUnstable) "alpha" else "release" versionType.set(if (isUnstable) "alpha" else "release")
uploadFile.setProvider(modPublishing.output) uploadFile.setProvider(modPublishing.output)
gameVersions.add(mcVersion) 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) } tasks.publish { dependsOn(tasks.modrinth) }

View File

@@ -12,26 +12,25 @@ publishing {
register<MavenPublication>("maven") { register<MavenPublication>("maven") {
artifactId = base.archivesName.get() artifactId = base.archivesName.get()
from(components["java"]) from(components["java"])
suppressAllPomMetadataWarnings()
pom { pom {
name = "CC: Tweaked" name.set("CC: Tweaked")
description = "CC: Tweaked is a fork of ComputerCraft, adding programmable computers, turtles and more to Minecraft." description.set("CC: Tweaked is a fork of ComputerCraft, adding programmable computers, turtles and more to Minecraft.")
url = "https://github.com/cc-tweaked/CC-Tweaked" url.set("https://github.com/cc-tweaked/CC-Tweaked")
scm { scm {
url = "https://github.com/cc-tweaked/CC-Tweaked.git" url.set("https://github.com/cc-tweaked/CC-Tweaked.git")
} }
issueManagement { issueManagement {
system = "github" system.set("github")
url = "https://github.com/cc-tweaked/CC-Tweaked/issues" url.set("https://github.com/cc-tweaked/CC-Tweaked/issues")
} }
licenses { licenses {
license { license {
name = "ComputerCraft Public License, Version 1.0" name.set("ComputerCraft Public License, Version 1.0")
url = "https://github.com/cc-tweaked/CC-Tweaked/blob/HEAD/LICENSE" 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.Project
import org.gradle.api.Task import org.gradle.api.Task
import org.gradle.api.artifacts.Dependency 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.plugins.JavaPluginExtension
import org.gradle.api.provider.ListProperty import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Provider import org.gradle.api.provider.Provider
import org.gradle.api.provider.SetProperty import org.gradle.api.provider.SetProperty
import org.gradle.api.reporting.ReportingExtension
import org.gradle.api.tasks.SourceSet import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.bundling.Jar import org.gradle.api.tasks.bundling.Jar
import org.gradle.api.tasks.compile.JavaCompile 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.base.plugins.LifecycleBasePlugin
import org.gradle.language.jvm.tasks.ProcessResources import org.gradle.language.jvm.tasks.ProcessResources
import org.gradle.process.JavaForkOptions 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.JacocoPluginExtension
import org.gradle.testing.jacoco.plugins.JacocoTaskExtension import org.gradle.testing.jacoco.plugins.JacocoTaskExtension
import org.gradle.testing.jacoco.tasks.JacocoReport import org.gradle.testing.jacoco.tasks.JacocoReport
@@ -32,36 +36,54 @@ import java.io.IOException
import java.net.URI import java.net.URI
import java.util.regex.Pattern 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. */ /** Get the current git branch. */
val gitBranch: Provider<String> = val gitBranch: Provider<String> = gitProvider(project, "<no git branch>") {
gitProvider("<no git branch>", listOf("rev-parse", "--abbrev-ref", "HEAD")) { it.trim() } ProcessHelpers.captureOut("git", "-C", project.rootProject.projectDir.absolutePath, "rev-parse", "--abbrev-ref", "HEAD")
.trim()
}
/** Get a list of all contributors to the project. */ /** Get a list of all contributors to the project. */
val gitContributors: Provider<List<String>> = val gitContributors: Provider<List<String>> = gitProvider(project, listOf()) {
gitProvider(listOf(), listOf("shortlog", "-ns", "--group=author", "--group=trailer:co-authored-by", "HEAD")) { input -> ProcessHelpers.captureLines(
input.lineSequence() "git", "-C", project.rootProject.projectDir.absolutePath, "shortlog", "-ns",
.filter { it.isNotEmpty() } "--group=author", "--group=trailer:co-authored-by", "HEAD",
.map { )
val matcher = COMMIT_COUNTS.matcher(it) .asSequence()
matcher.find() .map {
matcher.group(1) val matcher = COMMIT_COUNTS.matcher(it)
} matcher.find()
.filter { !IGNORED_USERS.contains(it) } matcher.group(1)
.toList() }
.sortedWith(String.CASE_INSENSITIVE_ORDER) .filter { !IGNORED_USERS.contains(it) }
} .toList()
.sortedWith(String.CASE_INSENSITIVE_ORDER)
}
/** /**
* References to other sources * References to other sources
*/ */
val sourceDirectories: SetProperty<SourceSetReference> = project.objects.setProperty(SourceSetReference::class.java) 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. */ /** All source sets referenced by this project. */
val sourceSets = sourceDirectories.map { x -> x.map { it.sourceSet } } val sourceSets = sourceDirectories.map { x -> x.map { it.sourceSet } }
init { init {
sourceDirectories.finalizeValueOnRead() sourceDirectories.finalizeValueOnRead()
excludedDeps.finalizeValueOnRead()
project.afterEvaluate { sourceDirectories.disallowChanges() } project.afterEvaluate { sourceDirectories.disallowChanges() }
} }
@@ -87,13 +109,14 @@ abstract class CCTweakedExtension(private val project: Project) {
val otherJava = otherProject.extensions.getByType(JavaPluginExtension::class.java) val otherJava = otherProject.extensions.getByType(JavaPluginExtension::class.java)
val main = otherJava.sourceSets.getByName("main") val main = otherJava.sourceSets.getByName("main")
val client = otherJava.sourceSets.getByName("client") 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. // Pull in sources from the other project.
extendSourceSet(otherProject, main) extendSourceSet(otherProject, main)
extendSourceSet(otherProject, client) extendSourceSet(otherProject, client)
for (sourceSet in listOf(MinecraftConfigurations.DATAGEN, MinecraftConfigurations.EXAMPLES, MinecraftConfigurations.TEST_MOD, "testFixtures")) { if (testMod != null) extendSourceSet(otherProject, testMod)
otherJava.sourceSets.findByName(sourceSet)?.let { extendSourceSet(otherProject, it) } if (testFixtures != null) extendSourceSet(otherProject, testFixtures)
}
// The extra source-processing tasks should include these files too. // The extra source-processing tasks should include these files too.
project.tasks.named(main.javadocTaskName, Javadoc::class.java) { source(main.allJava, client.allJava) } 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 { 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 reportTaskName = "jacoco${task.name.capitalise()}Report"
val jacoco = project.extensions.getByType(JacocoPluginExtension::class.java) val jacoco = project.extensions.getByType(JacocoPluginExtension::class.java)
task.configure { task.configure {
finalizedBy(reportTaskName) finalizedBy(reportTaskName)
jacoco.applyTo(this)
doFirst("Clean class dump directory") { fs.delete { delete(classDump) } }
jacoco.applyTo(this)
extensions.configure(JacocoTaskExtension::class.java) { extensions.configure(JacocoTaskExtension::class.java) {
excludes = listOf( includes = listOf("dan200.computercraft.*")
"dan200.computercraft.mixin.*", // Exclude mixins, as they're not executed at runtime. classDumpDir = classDump.get().asFile
"dan200.computercraft.shared.Capabilities$*", // Exclude capability tokens, as Forge rewrites them.
) // 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." description = "Generates code coverage report for the ${task.name} task."
executionData(task.get()) executionData(task.get())
classDirectories.from(classDump)
// Don't want to use sourceSets(...) here as we don't use all class directories. // Don't want to use sourceSets(...) here as we have a custom class directory.
for (ref in this@CCTweakedExtension.sourceDirectories.get()) { for (ref in sourceSets.get()) sourceDirectories.from(ref.allSource.sourceDirectories)
sourceDirectories.from(ref.sourceSet.allSource.sourceDirectories) }
if (ref.classes) classDirectories.from(ref.sourceSet.output)
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() ).resolve().single()
} }
private fun <T> gitProvider(default: T, command: List<String>, process: (String) -> T): Provider<T> { /**
val baseResult = project.providers.exec { * Exclude a dependency from being published in Maven.
commandLine = listOf("git", "-C", project.rootDir.absolutePath) + command */
} fun exclude(dep: Dependency) {
excludedDeps.add(dep)
}
return project.provider { /**
val res = try { * Configure a [MavenDependencySpec].
baseResult.standardOutput.asText.get() */
} catch (e: IOException) { fun configureExcludes(spec: MavenDependencySpec) {
project.logger.error("Cannot read Git repository: ${e.message}", e) for (dep in excludedDeps.get()) spec.exclude(dep)
return@provider default
} catch (e: GradleException) {
project.logger.error("Cannot read Git repository: ${e.message}", e)
return@provider default
}
process(res)
}
} }
companion object { companion object {
private val COMMIT_COUNTS = Pattern.compile("""^\s*[0-9]+\s+(.*)$""") private val COMMIT_COUNTS = Pattern.compile("""^\s*[0-9]+\s+(.*)$""")
private val IGNORED_USERS = setOf( 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 private val isIdeSync: Boolean
get() = java.lang.Boolean.parseBoolean(System.getProperty("idea.sync.active", "false")) get() = java.lang.Boolean.parseBoolean(System.getProperty("idea.sync.active", "false"))
} }

View File

@@ -42,6 +42,6 @@ class CCTweakedPlugin : Plugin<Project> {
} }
companion object { 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() { abstract class DependencyCheck : DefaultTask() {
@get:Input @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. * A mapping of module coordinates (`group:module`) to versions, overriding the requested version.
*/ */
@get:Input @get:Input
protected abstract val overrides: MapProperty<String, String> abstract val overrides: MapProperty<String, String>
init { init {
description = "Check :core's dependencies are consistent with Minecraft's." description = "Check :core's dependencies are consistent with Minecraft's."
group = LifecycleBasePlugin.VERIFICATION_GROUP group = LifecycleBasePlugin.VERIFICATION_GROUP
dependencies.finalizeValueOnRead() configuration.finalizeValueOnRead()
overrides.finalizeValueOnRead() overrides.finalizeValueOnRead()
} }
@@ -45,19 +45,13 @@ abstract class DependencyCheck : DefaultTask() {
overrides.putAll(project.provider { mutableMapOf(module.get().module.toString() to version) }) 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 @TaskAction
fun run() { fun run() {
var ok = true var ok = true
for (configuration in dependencies.get()) { for (configuration in configuration.get()) {
if (!check(configuration)) ok = false configuration.incoming.resolutionResult.allDependencies {
if (!check(this@allDependencies)) ok = false
}
} }
if (!ok) { 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.Property
import org.gradle.api.provider.Provider import org.gradle.api.provider.Provider
import org.gradle.api.tasks.JavaExec import org.gradle.api.tasks.JavaExec
import org.gradle.kotlin.dsl.getByName
import org.gradle.process.BaseExecSpec import org.gradle.process.BaseExecSpec
import org.gradle.process.JavaExecSpec import org.gradle.process.JavaExecSpec
import org.gradle.process.ProcessForkOptions import org.gradle.process.ProcessForkOptions
@@ -47,21 +46,6 @@ fun JavaExec.copyToFull(spec: JavaExec) {
copyToExec(spec) 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]. * 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 java = project.extensions.getByType(JavaPluginExtension::class.java)
private val sourceSets = java.sourceSets private val sourceSets = java.sourceSets
private val configurations = project.configurations private val configurations = project.configurations
private val objects = project.objects
private val main = sourceSets[SourceSet.MAIN_SOURCE_SET_NAME] private val main = sourceSets[SourceSet.MAIN_SOURCE_SET_NAME]
private val test = sourceSets[SourceSet.TEST_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") val client = sourceSets.maybeCreate("client")
// Ensure the client classpaths behave the same as the main ones. // 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). // Set up an API configuration for clients (to ensure it's consistent with the main source set).
val clientApi = configurations.maybeCreate(client.apiConfigurationName).apply { val clientApi = configurations.maybeCreate(client.apiConfigurationName).apply {
@@ -78,16 +85,6 @@ class MinecraftConfigurations private constructor(private val project: Project)
setupBasic() 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() { private fun setupBasic() {
val client = sourceSets["client"] val client = sourceSets["client"]
@@ -99,30 +96,13 @@ class MinecraftConfigurations private constructor(private val project: Project)
val checkDependencyConsistency = val checkDependencyConsistency =
project.tasks.register("checkDependencyConsistency", DependencyCheck::class.java) { project.tasks.register("checkDependencyConsistency", DependencyCheck::class.java) {
// We need to check both the main and client classpath *configurations*, as the actual configuration // We need to check both the main and client classpath *configurations*, as the actual configuration
configuration(configurations.named(main.runtimeClasspathConfigurationName)) configuration.add(configurations.named(main.runtimeClasspathConfigurationName))
configuration(configurations.named(client.runtimeClasspathConfigurationName)) configuration.add(configurations.named(client.runtimeClasspathConfigurationName))
} }
project.tasks.named("check") { dependsOn(checkDependencyConsistency) } 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 { companion object {
const val DATAGEN = "datagen"
const val EXAMPLES = "examples"
const val TEST_MOD = "testMod"
fun setupBasic(project: Project) { fun setupBasic(project: Project) {
MinecraftConfigurations(project).setupBasic() MinecraftConfigurations(project).setupBasic()
} }
@@ -130,10 +110,6 @@ class MinecraftConfigurations private constructor(private val project: Project)
fun setup(project: Project) { fun setup(project: Project) {
MinecraftConfigurations(project).setup() MinecraftConfigurations(project).setup()
} }
fun createDerivedConfiguration(project: Project, name: String) {
MinecraftConfigurations(project).createDerivedConfiguration(name)
}
} }
} }

View File

@@ -4,7 +4,7 @@
package cc.tweaked.gradle 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.GradleException
import org.gradle.api.file.FileSystemOperations import org.gradle.api.file.FileSystemOperations
import org.gradle.api.invocation.Gradle import org.gradle.api.invocation.Gradle
@@ -19,7 +19,6 @@ import java.nio.file.Files
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.function.Supplier import java.util.function.Supplier
import javax.inject.Inject import javax.inject.Inject
import kotlin.collections.set
import kotlin.random.Random import kotlin.random.Random
/** /**
@@ -66,19 +65,11 @@ abstract class ClientJavaExec : JavaExec() {
setTestProperties() 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) { fun setRunConfig(config: RunConfig) {
copyFrom(task) (this as JavaExec).setRunConfig(config)
// Eagerly evaluate the behaviour of RunGameTask.exec
environment.putAll(task.environmentProperty.get())
classpath(task.classpathProvider)
workingDir = task.gameDirectory.get().asFile
setTestProperties() // setRunConfig may clobber some properties, ensure everything is set. 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 @TaskAction
override fun exec() { override fun exec() {
Files.createDirectories(workingDir.toPath()) 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.file.DirectoryProperty
import org.gradle.api.provider.Provider import org.gradle.api.provider.Provider
import org.gradle.api.tasks.* import org.gradle.api.tasks.*
import org.gradle.process.ExecOperations
import java.io.File import java.io.File
import javax.inject.Inject
class NodePlugin : Plugin<Project> { class NodePlugin : Plugin<Project> {
override fun apply(project: Project) { override fun apply(project: Project) {
@@ -45,12 +43,9 @@ abstract class NpmInstall : DefaultTask() {
@get:OutputDirectory @get:OutputDirectory
val nodeModules: Provider<Directory> = projectRoot.dir("node_modules") val nodeModules: Provider<Directory> = projectRoot.dir("node_modules")
@get:Inject
protected abstract val execOperations: ExecOperations
@TaskAction @TaskAction
fun install() { fun install() {
execOperations.exec { project.exec {
commandLine(ProcessHelpers.getExecutable("npm"), "ci") commandLine(ProcessHelpers.getExecutable("npm"), "ci")
workingDir = projectRoot.get().asFile workingDir = projectRoot.get().asFile
} }

View File

@@ -4,10 +4,45 @@
package cc.tweaked.gradle package cc.tweaked.gradle
import org.codehaus.groovy.runtime.ProcessGroovyMethods
import org.gradle.api.GradleException import org.gradle.api.GradleException
import java.io.BufferedReader
import java.io.File import java.io.File
import java.io.InputStreamReader
import java.nio.charset.StandardCharsets
internal object ProcessHelpers { 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 { fun onPath(name: String): Boolean {
val path = System.getenv("PATH") ?: return false val path = System.getenv("PATH") ?: return false
return path.splitToSequence(File.pathSeparator).any { File(it, name).exists() } 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" /> <property name="file" value="${config_loc}/suppressions.xml" />
</module> </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"> <module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="render_old"/> <property name="fileNamePattern" value="render_old"/>
</module> </module>
@@ -133,7 +124,7 @@ SPDX-License-Identifier: MPL-2.0
</module> </module>
<module name="MethodTypeParameterName" /> <module name="MethodTypeParameterName" />
<module name="PackageName"> <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>
<module name="ParameterName" /> <module name="ParameterName" />
<module name="StaticVariableName"> <module name="StaticVariableName">

View File

@@ -22,7 +22,4 @@ SPDX-License-Identifier: MPL-2.0
<!-- Allow underscores in our test classes. --> <!-- Allow underscores in our test classes. -->
<suppress checks="MethodName" files=".*(Contract|Test).java" /> <suppress checks="MethodName" files=".*(Contract|Test).java" />
<!-- Allow underscores in Mixin classes -->
<suppress checks="TypeName" files=".*[\\/]mixin[\\/].*.java" />
</suppressions> </suppressions>

View File

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

View File

@@ -8,7 +8,7 @@ SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
SPDX-License-Identifier: MPL-2.0 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 ## Return Values
1. [`string`]: The event name. 1. [`string`]: The event name.

View File

@@ -8,7 +8,7 @@ SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
SPDX-License-Identifier: MPL-2.0 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 ## Return Values
1. [`string`]: The event name. 1. [`string`]: The event name.

View File

@@ -8,7 +8,7 @@ SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
SPDX-License-Identifier: MPL-2.0 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 ## Return values
1. [`string`]: The event name. 1. [`string`]: The event name.
@@ -21,7 +21,3 @@ while true do
print("A redstone input has changed!") print("A redstone input has changed!")
end 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 much-beloved [ComputerCraft], it continues its legacy with improved performance and stability, along with a wealth of
new features. 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 ## Features
Controlled using the [Lua programming language][lua], CC: Tweaked's computers provides all the tools you need to start 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" [github]: https://github.com/cc-tweaked/CC-Tweaked/ "CC: Tweaked on GitHub"
[bug]: https://github.com/cc-tweaked/CC-Tweaked/issues/new/choose [bug]: https://github.com/cc-tweaked/CC-Tweaked/issues/new/choose
[computercraft]: https://github.com/dan200/ComputerCraft "ComputerCraft on GitHub" [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" [modrinth]: https://modrinth.com/mod/gu7yAYhd "Download CC: Tweaked from Modrinth"
[forge]: https://files.minecraftforge.net/ "Download Minecraft Forge." [forge]: https://files.minecraftforge.net/ "Download Minecraft Forge."
[Minecraft 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.stdlib.default.dependency=false
kotlin.jvm.target.validation.mode=error kotlin.jvm.target.validation.mode=error
neogradle.subsystems.conventions.runs.enabled=false # Mod properties
isUnstable=false
isUnstable=true modVersion=1.113.1
modVersion=1.114.3
# Minecraft properties: We want to configure this here so we can read it in settings.gradle # 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 #This file is generated by updateDaemonJvm
toolchainVersion=21 toolchainVersion=17

View File

@@ -6,76 +6,76 @@
# Minecraft # Minecraft
# MC version is specified in gradle.properties, as we need that in settings.gradle. # 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 # Remember to update corresponding versions in fabric.mod.json/mods.toml
fabric-api = "0.102.1+1.21.1" fabric-api = "0.86.1+1.20.1"
fabric-loader = "0.15.11" fabric-loader = "0.14.21"
neoForge = "21.1.9" forge = "47.1.0"
neoForgeSpi = "8.0.1" forgeSpi = "7.0.1"
mixin = "0.8.5" mixin = "0.8.5"
parchment = "2024.07.28" parchment = "2023.08.20"
parchmentMc = "1.21" parchmentMc = "1.20.1"
yarn = "1.21.1+build.1" yarn = "1.20.1+build.10"
# Core dependencies (these versions are tied to the version Minecraft uses) # Core dependencies (these versions are tied to the version Minecraft uses)
fastutil = "8.5.12" fastutil = "8.5.9"
guava = "32.1.2-jre" guava = "31.1-jre"
netty = "4.1.97.Final" netty = "4.1.82.Final"
slf4j = "2.0.9" slf4j = "2.0.1"
# Core dependencies (independent of Minecraft) # Core dependencies (independent of Minecraft)
asm = "9.6" asm = "9.6"
autoService = "1.1.1" autoService = "1.1.1"
checkerFramework = "3.42.0" checkerFramework = "3.42.0"
cobalt = { strictly = "0.9.5" } cobalt = "0.9.4"
commonsCli = "1.6.0" commonsCli = "1.6.0"
jetbrainsAnnotations = "24.1.0" jetbrainsAnnotations = "24.1.0"
jsr305 = "3.0.2" jsr305 = "3.0.2"
jzlib = "1.1.3" jzlib = "1.1.3"
kotlin = "2.1.0" kotlin = "1.9.21"
kotlin-coroutines = "1.10.1" kotlin-coroutines = "1.7.3"
nightConfig = "3.8.1" nightConfig = "3.6.7"
# Minecraft mods # Minecraft mods
emi = "1.1.7+1.21" emi = "1.0.8+1.20.1"
fabricPermissions = "0.3.1" fabricPermissions = "0.3.20230723"
iris-fabric = "1.8.0-beta.3+1.21-fabric" iris = "1.6.4+1.20"
iris-forge = "1.8.0-beta.3+1.21-neoforge" jei = "15.2.0.22"
jei = "19.8.2.99" modmenu = "7.1.0"
modmenu = "11.0.0-rc.4"
moreRed = "4.0.0.4" moreRed = "4.0.0.4"
rei = "16.0.729" oculus = "1.2.5"
sodium-fabric = "mc1.21-0.6.0-beta.1-fabric" rei = "12.0.626"
sodium-forge = "mc1.21-0.6.0-beta.1-neoforge" rubidium = "0.6.1"
mixinExtra = "0.3.5" sodium = "mc1.20-0.4.10"
create-forge = "0.5.1.f-33" create-forge = "0.5.1.f-33"
create-fabric = "0.5.1-f-build.1467+mc1.20.1" create-fabric = "0.5.1-f-build.1467+mc1.20.1"
# Testing # Testing
hamcrest = "2.2" hamcrest = "2.2"
jqwik = "1.8.2" jqwik = "1.8.2"
junit = "5.11.4" junit = "5.10.1"
junitPlatform = "1.11.4"
jmh = "1.37" jmh = "1.37"
# Build tools # Build tools
cctJavadoc = "1.8.3" cctJavadoc = "1.8.2"
checkstyle = "10.20.1" checkstyle = "10.14.1"
curseForgeGradle = "1.0.14"
errorProne-core = "2.27.0" errorProne-core = "2.27.0"
errorProne-plugin = "3.1.0" errorProne-plugin = "3.1.0"
fabric-loom = "1.9.2" fabric-loom = "1.7.1"
forgeGradle = "6.0.21"
githubRelease = "2.5.2" githubRelease = "2.5.2"
gradleVersions = "0.50.0" gradleVersions = "0.50.0"
ideaExt = "1.1.7" ideaExt = "1.1.7"
illuaminate = "0.1.0-74-gf1551d5" illuaminate = "0.1.0-73-g43ee16c"
librarian = "1.+"
lwjgl = "3.3.3" lwjgl = "3.3.3"
minotaur = "2.8.7" minotaur = "2.+"
modDevGradle = "2.0.74"
nullAway = "0.10.25" nullAway = "0.10.25"
shadow = "8.3.1" shadow = "8.3.1"
spotless = "6.23.3" spotless = "6.23.3"
taskTree = "2.1.1" taskTree = "2.1.1"
teavm = "0.11.0-SQUID.1" teavm = "0.11.0-SQUID.1"
vanillaExtract = "0.2.0" vanillaExtract = "0.1.3"
versionCatalogUpdate = "0.8.1" versionCatalogUpdate = "0.8.1"
[libraries] [libraries]
@@ -87,7 +87,7 @@ checkerFramework = { module = "org.checkerframework:checker-qual", version.ref =
cobalt = { module = "cc.tweaked:cobalt", version.ref = "cobalt" } cobalt = { module = "cc.tweaked:cobalt", version.ref = "cobalt" }
commonsCli = { module = "commons-cli:commons-cli", version.ref = "commonsCli" } commonsCli = { module = "commons-cli:commons-cli", version.ref = "commonsCli" }
fastutil = { module = "it.unimi.dsi:fastutil", version.ref = "fastutil" } 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" } guava = { module = "com.google.guava:guava", version.ref = "guava" }
jetbrainsAnnotations = { module = "org.jetbrains:annotations", version.ref = "jetbrainsAnnotations" } jetbrainsAnnotations = { module = "org.jetbrains:annotations", version.ref = "jetbrainsAnnotations" }
jsr305 = { module = "com.google.code.findbugs:jsr305", version.ref = "jsr305" } 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-junit = { module = "net.fabricmc:fabric-loader-junit", version.ref = "fabric-loader" }
fabric-loader = { module = "net.fabricmc:fabric-loader", 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" } fabricPermissions = { module = "me.lucko:fabric-permissions-api", version.ref = "fabricPermissions" }
iris-fabric = { module = "maven.modrinth:iris", version.ref = "iris-fabric" } iris = { module = "maven.modrinth:iris", version.ref = "iris" }
iris-forge = { module = "maven.modrinth:iris", version.ref = "iris-forge" } jei-api = { module = "mezz.jei:jei-1.20.1-common-api", version.ref = "jei" }
jei-api = { module = "mezz.jei:jei-1.21-common-api", version.ref = "jei" } jei-fabric = { module = "mezz.jei:jei-1.20.1-fabric", version.ref = "jei" }
jei-fabric = { module = "mezz.jei:jei-1.21-fabric", version.ref = "jei" } jei-forge = { module = "mezz.jei:jei-1.20.1-forge", version.ref = "jei" }
jei-forge = { module = "mezz.jei:jei-1.21-neoforge", version.ref = "jei" }
mixin = { module = "org.spongepowered:mixin", version.ref = "mixin" } 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" } modmenu = { module = "com.terraformersmc:modmenu", version.ref = "modmenu" }
moreRed = { module = "commoble.morered:morered-1.20.1", version.ref = "moreRed" } 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-api = { module = "me.shedaniel:RoughlyEnoughItems-api", version.ref = "rei" }
rei-builtin = { module = "me.shedaniel:RoughlyEnoughItems-default-plugin", version.ref = "rei" } rei-builtin = { module = "me.shedaniel:RoughlyEnoughItems-default-plugin", version.ref = "rei" }
rei-fabric = { module = "me.shedaniel:RoughlyEnoughItems-fabric", version.ref = "rei" } rei-fabric = { module = "me.shedaniel:RoughlyEnoughItems-fabric", version.ref = "rei" }
sodium-fabric = { module = "maven.modrinth:sodium", version.ref = "sodium.fabric" } rubidium = { module = "maven.modrinth:rubidium", version.ref = "rubidium" }
sodium-forge = { module = "maven.modrinth:sodium", version.ref = "sodium.forge" } sodium = { module = "maven.modrinth:sodium", version.ref = "sodium" }
# Testing # Testing
hamcrest = { module = "org.hamcrest:hamcrest", version.ref = "hamcrest" } 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-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-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-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" } slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" }
jmh = { module = "org.openjdk.jmh:jmh-core", version.ref = "jmh" } jmh = { module = "org.openjdk.jmh:jmh-core", version.ref = "jmh" }
jmh-processor = { module = "org.openjdk.jmh:jmh-generator-annprocess", 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 # Build tools
cctJavadoc = { module = "cc.tweaked:cct-javadoc", version.ref = "cctJavadoc" } cctJavadoc = { module = "cc.tweaked:cct-javadoc", version.ref = "cctJavadoc" }
checkstyle = { module = "com.puppycrawl.tools:checkstyle", version.ref = "checkstyle" } 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-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-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-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-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" } 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" } 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" } 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" } 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" } minotaur = { module = "com.modrinth.minotaur:Minotaur", version.ref = "minotaur" }
nullAway = { module = "com.uber.nullaway:nullaway", version.ref = "nullAway" } 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" } spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" }
teavm-classlib = { module = "org.teavm:teavm-classlib", version.ref = "teavm" } teavm-classlib = { module = "org.teavm:teavm-classlib", version.ref = "teavm" }
teavm-core = { module = "org.teavm:teavm-core", 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" } yarn = { module = "net.fabricmc:yarn", version.ref = "yarn" }
[plugins] [plugins]
forgeGradle = { id = "net.minecraftforge.gradle", version.ref = "forgeGradle" }
githubRelease = { id = "com.github.breadmoirai.github-release", version.ref = "githubRelease" } githubRelease = { id = "com.github.breadmoirai.github-release", version.ref = "githubRelease" }
gradleVersions = { id = "com.github.ben-manes.versions", version.ref = "gradleVersions" } gradleVersions = { id = "com.github.ben-manes.versions", version.ref = "gradleVersions" }
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } 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" } shadow = { id = "com.gradleup.shadow", version.ref = "shadow" }
taskTree = { id = "com.dorongold.task-tree", version.ref = "taskTree" } taskTree = { id = "com.dorongold.task-tree", version.ref = "taskTree" }
versionCatalogUpdate = { id = "nl.littlerobots.version-catalog-update", version.ref = "versionCatalogUpdate" } versionCatalogUpdate = { id = "nl.littlerobots.version-catalog-update", version.ref = "versionCatalogUpdate" }
@@ -184,15 +186,15 @@ annotations = ["jsr305", "checkerFramework", "jetbrainsAnnotations"]
kotlin = ["kotlin-stdlib", "kotlin-coroutines"] kotlin = ["kotlin-stdlib", "kotlin-coroutines"]
# Minecraft # Minecraft
externalMods-common = ["iris-forge", "jei-api", "nightConfig-core", "nightConfig-toml"] externalMods-common = ["jei-api", "nightConfig-core", "nightConfig-toml"]
externalMods-forge-compile = ["moreRed", "iris-forge", "jei-api"] externalMods-forge-compile = ["moreRed", "oculus", "jei-api"]
externalMods-forge-runtime = ["jei-forge"] 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"] externalMods-fabric-runtime = ["jei-fabric", "modmenu"]
# Testing # Testing
test = ["junit-jupiter-api", "junit-jupiter-params", "hamcrest", "jqwik-api"] 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 # Build tools
teavm-api = ["teavm-jso", "teavm-jso-apis", "teavm-platform", "teavm-classlib", "teavm-metaprogramming-api"] 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 distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists 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 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

3
gradlew vendored
View File

@@ -86,7 +86,8 @@ done
# shellcheck disable=SC2034 # shellcheck disable=SC2034
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) # 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. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum 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": { "devDependencies": {
"@rollup/plugin-node-resolve": "^15.2.1", "@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", "@rollup/plugin-url": "^8.0.1",
"@swc/core": "^1.3.92", "@swc/core": "^1.3.92",
"@types/node": "^22.0.0", "@types/node": "^20.8.3",
"lightningcss": "^1.22.0", "lightningcss": "^1.22.0",
"preact-render-to-string": "^6.2.1", "preact-render-to-string": "^6.2.1",
"rehype": "^13.0.0", "rehype": "^13.0.0",

View File

@@ -8,8 +8,6 @@ plugins {
id("cc-tweaked.vanilla") id("cc-tweaked.vanilla")
} }
val mcVersion: String by extra
java { java {
withJavadocJar() withJavadocJar()
} }
@@ -18,70 +16,9 @@ dependencies {
api(project(":core-api")) 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 { tasks.javadoc {
title = "CC: Tweaked $version for Minecraft $mcVersion"
include("dan200/computercraft/api/**/*.java") 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. // 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 }) 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.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import java.util.Objects;
/** /**
* A model to render, combined with a transformation matrix to apply. * 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) { 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) { public static TransformedModel of(ModelResourceLocation location) {
var modelManager = Minecraft.getInstance().getModelManager(); var modelManager = Minecraft.getInstance().getModelManager();
return new TransformedModel(modelManager.getModel(location)); 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) { public static TransformedModel of(ResourceLocation location) {
var modelManager = Minecraft.getInstance().getModelManager(); var modelManager = Minecraft.getInstance().getModelManager();
return new TransformedModel(ClientPlatformHelper.get().getModel(modelManager, location)); 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); var model = Minecraft.getInstance().getItemRenderer().getItemModelShaper().getItemModel(item);
return new TransformedModel(model, transform); 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; package dan200.computercraft.api.client.turtle;
import dan200.computercraft.api.turtle.ITurtleUpgrade; 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. * 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}. * Register a {@link TurtleUpgradeModeller}.
* *
* @param type The turtle upgrade type. * @param serialiser The turtle upgrade serialiser.
* @param modeller The upgrade modeller. * @param modeller The upgrade modeller.
* @param <T> The type of the turtle upgrade. * @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; package dan200.computercraft.api.client.turtle;
import dan200.computercraft.api.client.ModelLocation;
import dan200.computercraft.api.client.TransformedModel; import dan200.computercraft.api.client.TransformedModel;
import dan200.computercraft.api.turtle.ITurtleAccess; import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.ITurtleUpgrade; import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide; import dan200.computercraft.api.turtle.TurtleSide;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.client.resources.model.UnbakedModel; import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.core.component.DataComponentPatch; import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.stream.Stream; import java.util.Collection;
import java.util.List;
/** /**
* Provides models for a {@link ITurtleUpgrade}. * Provides models for a {@link ITurtleUpgrade}.
* <p> * <p>
* Use {@code dan200.computercraft.api.client.FabricComputerCraftAPIClient#registerTurtleUpgradeModeller} to register a * 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 * modeller on Fabric and {@code dan200.computercraft.api.client.turtle.RegisterTurtleModellersEvent} to register one
* on Forge. * 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}
* *
* @param <T> The type of turtle upgrade this modeller applies to. * @param <T> The type of turtle upgrade this modeller applies to.
* @see RegisterTurtleUpgradeModeller For multi-loader registration support. * @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. * Obtain the model to be used when rendering a turtle peripheral.
* <p> * <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 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 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. * @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> * <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 {@link TransformedModel#of(ResourceLocation)}. You do not need to override this method if you will load models
* by other means. * by other means.
* *
* @return A list of models that this modeller depends on. * @return A list of models that this modeller depends on.
* @see UnbakedModel#getDependencies() * @see UnbakedModel#getDependencies()
*/ */
default Stream<ResourceLocation> getDependencies() { default Collection<ResourceLocation> getDependencies() {
return Stream.of(); 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}. * upgrade item}.
* <p> * <p>
* This uses appropriate transformations for "flat" items, namely those extending the {@literal minecraft:item/generated} * 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. * @param <T> The type of the turtle upgrade.
* @return The constructed modeller. * @return The constructed modeller.
*/ */
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ResourceLocation left, ResourceLocation right) { static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ModelResourceLocation left, ModelResourceLocation right) {
return sided(ModelLocation.ofResource(left), ModelLocation.ofResource(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. * @param <T> The type of the turtle upgrade.
* @return The constructed modeller. * @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<>() { return new TurtleUpgradeModeller<>() {
@Override @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); return TransformedModel.of(side == TurtleSide.LEFT ? left : right);
} }
@Override @Override
public Stream<ResourceLocation> getDependencies() { public Collection<ResourceLocation> getDependencies() {
return Stream.of(left, right).flatMap(ModelLocation::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.api.turtle.TurtleSide;
import dan200.computercraft.impl.client.ClientPlatformHelper; import dan200.computercraft.impl.client.ClientPlatformHelper;
import net.minecraft.client.Minecraft; 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 org.joml.Matrix4f;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -36,8 +37,16 @@ final class TurtleUpgradeModellers {
private static final class UpgradeItemModeller implements TurtleUpgradeModeller<ITurtleUpgrade> { private static final class UpgradeItemModeller implements TurtleUpgradeModeller<ITurtleUpgrade> {
@Override @Override
public TransformedModel getModel(ITurtleUpgrade upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, DataComponentPatch data) { public TransformedModel getModel(ITurtleUpgrade upgrade, @Nullable ITurtleAccess turtle, TurtleSide side) {
var stack = upgrade.getUpgradeItem(data); 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); var model = Minecraft.getInstance().getItemRenderer().getItemModelShaper().getItemModel(stack);
if (stack.hasFoil()) model = ClientPlatformHelper.get().createdFoiledModel(model); if (stack.hasFoil()) model = ClientPlatformHelper.get().createdFoiledModel(model);
return new TransformedModel(model, side == TurtleSide.LEFT ? leftTransform : rightTransform); return new TransformedModel(model, side == TurtleSide.LEFT ? leftTransform : rightTransform);

View File

@@ -4,7 +4,6 @@
package dan200.computercraft.impl.client; package dan200.computercraft.impl.client;
import dan200.computercraft.api.client.ModelLocation;
import dan200.computercraft.impl.Services; import dan200.computercraft.impl.Services;
import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.resources.model.BakedModel; import net.minecraft.client.resources.model.BakedModel;
@@ -18,28 +17,13 @@ import javax.annotation.Nullable;
@ApiStatus.Internal @ApiStatus.Internal
public interface ClientPlatformHelper { 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 manager The model manager.
* @param resourceLocation The model resourceLocation. * @param location The model location.
* @return The baked model. * @return The baked model.
* @see ModelLocation
*/ */
BakedModel getModel(ModelManager manager, ResourceLocation resourceLocation); BakedModel getModel(ModelManager manager, ResourceLocation location);
/**
* Set a model from a {@link ModelResourceLocation} or {@link ResourceLocation}.
* <p>
* This is largely equivalent to {@code resourceLocation == null ? manager.getModel(modelLocation) : getModel(manager, resourceLocation)},
* but allows pre-computing {@code modelLocation} (if needed).
*
* @param manager The model manager.
* @param modelLocation The location of the model to load.
* @param resourceLocation The location of the resource, if trying to load from a resource.
* @return The baked model.
* @see ModelLocation
*/
BakedModel getModel(ModelManager manager, ModelResourceLocation modelLocation, @Nullable ResourceLocation resourceLocation);
/** /**
* Wrap this model in a version which renders a foil/enchantment glint. * Wrap this model in a version which renders a foil/enchantment glint.

View File

@@ -4,34 +4,35 @@
package dan200.computercraft.impl.client; package dan200.computercraft.impl.client;
import dan200.computercraft.api.client.ComputerCraftAPIClient;
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller; import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
import dan200.computercraft.api.turtle.ITurtleUpgrade; import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.upgrades.UpgradeType; import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.impl.Services; import dan200.computercraft.impl.Services;
import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nullable; import javax.annotation.Nullable;
/** /**
* Backing interface for CC's client-side API. * Backing interface for {@link ComputerCraftAPIClient}
* <p> * <p>
* Do <strong>NOT</strong> directly reference this class. It exists for internal use by the API. * Do <strong>NOT</strong> directly reference this class. It exists for internal use by the API.
*/ */
@ApiStatus.Internal @ApiStatus.Internal
public interface FabricComputerCraftAPIClientService { public interface ComputerCraftAPIClientService {
static FabricComputerCraftAPIClientService get() { static ComputerCraftAPIClientService get() {
var instance = Instance.INSTANCE; 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 { final class Instance {
static final @Nullable FabricComputerCraftAPIClientService INSTANCE; static final @Nullable ComputerCraftAPIClientService INSTANCE;
static final @Nullable Throwable ERROR; static final @Nullable Throwable ERROR;
static { static {
var helper = Services.tryLoad(FabricComputerCraftAPIClientService.class); var helper = Services.tryLoad(ComputerCraftAPIClientService.class);
INSTANCE = helper.instance(); INSTANCE = helper.instance();
ERROR = helper.error(); 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. * using {@link ILuaAPI#getModuleName()} to expose this library as a module instead of as a global.
* <p> * <p>
* This may be used with {@link IComputerSystem#getComponent(ComputerComponent)} to only attach APIs to specific * 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. * @param factory The factory for your API subclass.
* @see ILuaAPIFactory * @see ILuaAPIFactory

View File

@@ -6,12 +6,10 @@ package dan200.computercraft.api;
import net.minecraft.core.registries.Registries; import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.ItemTags;
import net.minecraft.tags.TagKey; import net.minecraft.tags.TagKey;
import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.UseOnContext; import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block; 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"); 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) { 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"); 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} * Block which can be {@linkplain BlockState#use(Level, Player, InteractionHand, BlockHitResult) used} when
* when calling {@code turtle.place()}. * calling {@code turtle.place()}.
*/ */
public static final TagKey<Block> TURTLE_CAN_USE = make("turtle_can_use"); public static final TagKey<Block> TURTLE_CAN_USE = make("turtle_can_use");
private static TagKey<Block> make(String name) { 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.ComputerCraftAPI;
import dan200.computercraft.api.filesystem.Mount; import dan200.computercraft.api.filesystem.Mount;
import dan200.computercraft.api.filesystem.WritableMount; 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.MinecraftServer;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.JukeboxSong;
import javax.annotation.Nullable; 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. * 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". * @return The label. ie: "Dan's Programs".
*/ */
@Nullable @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. * 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 modify.
* @param stack The {@link ItemStack} to query. * @return The name, or null if this item does not represent an item with audio.
* @return The song, or null if this item does not represent an item with audio.
*/ */
@Nullable @Nullable
default Holder<JukeboxSong> getAudio(HolderLookup.Provider registries, ItemStack stack) { default String getAudioTitle(ItemStack stack) {
return JukeboxSong.fromStack(registries, stack).orElse(null); 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 * as a proxy for all network objects. Whilst the node may change networks, an element's node should remain constant
* for its lifespan. * for its lifespan.
* <p> * <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. * {@link WiredElement} capability for the appropriate sides.
*/ */
public interface WiredElement extends WiredSender { 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; 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> * <p>
* Firstly, a node acts as a packet network, capable of sending and receiving modem messages to connected nodes. These * 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. * methods may be safely used on any thread.
@@ -32,6 +32,18 @@ public interface WiredNode extends PacketNetwork {
*/ */
WiredElement getElement(); 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. * Create a connection from this node to another.
* <p> * <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> * <p>
* Unlike a regular {@link PacketSender}, this must be associated with the node you are attempting to * Unlike a regular {@link PacketSender}, this must be associated with the node you are attempting to
* to send the packet from. * to send the packet from.
*
* @see WiredElement
*/ */
public interface WiredSender extends PacketSender { public interface WiredSender extends PacketSender {
/** /**

View File

@@ -4,7 +4,8 @@
package dan200.computercraft.api.pocket; 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; 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. * One does not have to use this, but it does provide a convenient template.
*/ */
public abstract class AbstractPocketUpgrade implements IPocketUpgrade { public abstract class AbstractPocketUpgrade implements IPocketUpgrade {
private final Component adjective; private final ResourceLocation id;
private final String adjective;
private final ItemStack stack; 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.adjective = adjective;
this.stack = stack; this.stack = stack;
} }
protected AbstractPocketUpgrade(String adjective, ItemStack stack) { protected AbstractPocketUpgrade(ResourceLocation id, ItemStack stack) {
this(Component.translatable(adjective), stack); this(id, UpgradeBase.getDefaultAdjective(id), stack);
} }
@Override @Override
public final Component getAdjective() { public final ResourceLocation getUpgradeID() {
return id;
}
@Override
public final String getUnlocalisedAdjective() {
return adjective; return adjective;
} }

View File

@@ -4,9 +4,11 @@
package dan200.computercraft.api.pocket; package dan200.computercraft.api.pocket;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.upgrades.UpgradeBase; import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeData; 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.server.level.ServerLevel;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
@@ -14,6 +16,7 @@ import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Map;
/** /**
* Wrapper class for pocket computers. * Wrapper class for pocket computers.
@@ -84,7 +87,7 @@ public interface IPocketAccess {
* Get the currently equipped upgrade. * Get the currently equipped upgrade.
* *
* @return The currently equipped upgrade. * @return The currently equipped upgrade.
* @see #getUpgradeData() * @see #getUpgradeNBTData()
* @see #setUpgrade(UpgradeData) * @see #setUpgrade(UpgradeData)
*/ */
@Nullable @Nullable
@@ -106,20 +109,19 @@ public interface IPocketAccess {
* This is persisted between computer reboots and chunk loads. * This is persisted between computer reboots and chunk loads.
* *
* @return The upgrade's NBT. * @return The upgrade's NBT.
* @see #setUpgradeData(DataComponentPatch) * @see #updateUpgradeNBTData()
* @see UpgradeBase#getUpgradeItem(DataComponentPatch) * @see UpgradeBase#getUpgradeItem(CompoundTag)
* @see UpgradeBase#getUpgradeData(ItemStack) * @see UpgradeBase#getUpgradeData(ItemStack)
* @see #getUpgrade() * @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 #getUpgradeNBTData()
* @see #getUpgradeData()
*/ */
void setUpgradeData(DataComponentPatch data); void updateUpgradeNBTData();
/** /**
* Remove the current peripheral and create a new one. * Remove the current peripheral and create a new one.
@@ -128,4 +130,13 @@ public interface IPocketAccess {
* entity} changes. * entity} changes.
*/ */
void invalidatePeripheral(); 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; package dan200.computercraft.api.pocket;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.upgrades.UpgradeBase; 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 net.minecraft.world.level.Level;
import javax.annotation.Nullable; 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. * A peripheral which can be equipped to the back side of a pocket computer.
* <p> * <p>
* Pocket upgrades are defined in two stages. First, one creates a {@link IPocketUpgrade} subclass and corresponding * Pocket upgrades are defined in two stages. First, on creates a {@link IPocketUpgrade} subclass and corresponding
* {@link UpgradeType} instance, which are then registered in a Minecraft registry. * {@link PocketUpgradeSerialiser} instance, which are then registered in a Forge registry.
* <p> * <p>
* You then write a JSON file in your mod's {@literal data/} folder. This is then parsed when the world is loaded, and * 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 { 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. * Creates a peripheral for the pocket computer.
* <p> * <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; 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; 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. * One does not have to use this, but it does provide a convenient template.
*/ */
public abstract class AbstractTurtleUpgrade implements ITurtleUpgrade { public abstract class AbstractTurtleUpgrade implements ITurtleUpgrade {
private final ResourceLocation id;
private final TurtleUpgradeType type; private final TurtleUpgradeType type;
private final Component adjective; private final String adjective;
private final ItemStack stack; 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.type = type;
this.adjective = adjective; this.adjective = adjective;
this.stack = stack; this.stack = stack;
} }
protected AbstractTurtleUpgrade(TurtleUpgradeType type, String adjective, ItemStack stack) { protected AbstractTurtleUpgrade(ResourceLocation id, TurtleUpgradeType type, ItemStack stack) {
this(type, Component.translatable(adjective), stack); this(id, type, UpgradeBase.getDefaultAdjective(id), stack);
} }
@Override @Override
public final Component getAdjective() { public final ResourceLocation getUpgradeID() {
return id;
}
@Override
public final String getUnlocalisedAdjective() {
return adjective; return adjective;
} }
@Override @Override
public final TurtleUpgradeType getUpgradeType() { public final TurtleUpgradeType getType() {
return type; return type;
} }

View File

@@ -12,7 +12,7 @@ import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeData; import dan200.computercraft.api.upgrades.UpgradeData;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction; 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.Container;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
@@ -229,22 +229,37 @@ public interface ITurtleAccess {
* @param side The side to get the upgrade from. * @param side The side to get the upgrade from.
* @return The upgrade on the specified side of the turtle, if there is one. * @return The upgrade on the specified side of the turtle, if there is one.
* @see #getUpgradeWithData(TurtleSide) * @see #getUpgradeWithData(TurtleSide)
* @see #setUpgrade(TurtleSide, UpgradeData) * @see #setUpgradeWithData(TurtleSide, UpgradeData)
*/ */
@Nullable @Nullable
ITurtleUpgrade getUpgrade(TurtleSide side); 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}. * update data}.
* *
* @param side The side to get the upgrade from. * @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. * @return The upgrade on the specified side of the turtle, along with its upgrade data, if there is one.
* @see #getUpgradeWithData(TurtleSide) * @see #getUpgradeWithData(TurtleSide)
* @see #setUpgrade(TurtleSide, UpgradeData) * @see #setUpgradeWithData(TurtleSide, UpgradeData)
*/ */
@Nullable default @Nullable UpgradeData<ITurtleUpgrade> getUpgradeWithData(TurtleSide side) {
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. * 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. * @param upgrade The upgrade to set, may be {@code null} to clear.
* @see #getUpgradeWithData(TurtleSide) * @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. * 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. * Get an upgrade-specific NBT compound, which can be used to store arbitrary data.
* <p> * <p>
* This will be persisted across turtle restarts and chunk loads, as well as being synced to the client. You can * This will be persisted across turtle restarts and chunk loads, as well as being synced to the client. You must
* call {@link #setUpgrade(TurtleSide, UpgradeData)} to modify it. * call {@link #updateUpgradeNBTData(TurtleSide)} after modifying it.
* *
* @param side The side to get the upgrade data for. * @param side The side to get the upgrade data for.
* @return The upgrade-specific data. * @return The upgrade-specific data.
* @see #setUpgradeData(TurtleSide, DataComponentPatch) * @see #updateUpgradeNBTData(TurtleSide)
* @see UpgradeBase#getUpgradeItem(DataComponentPatch) * @see UpgradeBase#getUpgradeItem(CompoundTag)
* @see UpgradeBase#getUpgradeData(ItemStack) * @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 side The side to mark dirty.
* @param data The new upgrade data. * @see #updateUpgradeNBTData(TurtleSide)
* @see #getUpgradeData(TurtleSide)
*/ */
void setUpgradeData(TurtleSide side, DataComponentPatch data); void updateUpgradeNBTData(TurtleSide side);
} }

View File

@@ -4,129 +4,34 @@
package dan200.computercraft.api.turtle; package dan200.computercraft.api.turtle;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.upgrades.UpgradeBase; 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.Direction;
import net.minecraft.core.Registry; import net.minecraft.nbt.CompoundTag;
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 javax.annotation.Nullable; 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 * The primary interface for defining an update for Turtles. A turtle update can either be a new tool, or a new
* peripheral. * peripheral.
* <p> * <p>
* Turtle upgrades are defined in two stages. First, one creates a {@link ITurtleUpgrade} subclass and corresponding * 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> * <p>
* You then write a JSON file in your mod's {@literal data/} folder. This is then parsed when the world is loaded, and * 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> * @see TurtleUpgradeSerialiser For how to register a turtle upgrade.
* <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}
*/ */
public interface ITurtleUpgrade extends UpgradeBase { 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 whether this turtle adds a tool or a peripheral to the turtle.
* *
* @return The type of upgrade this is. * @return The type of upgrade this is.
* @see TurtleUpgradeType for the differences between them. * @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. * 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 * @param upgradeData Data that currently stored for this upgrade
* @return Filtered version of this data. * @return Filtered version of this data.
*/ */
default DataComponentPatch getPersistedData(DataComponentPatch upgradeData) { default CompoundTag getPersistedData(CompoundTag upgradeData) {
return 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; package dan200.computercraft.api.turtle;
import net.minecraft.core.component.DataComponents;
import net.minecraft.util.StringRepresentable; import net.minecraft.util.StringRepresentable;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
/** /**
* Indicates if an equipped turtle item will consume durability. * Indicates if an equipped turtle item will consume durability.
* *
* @see TurtleToolBuilder#consumeDurability(TurtleToolDurability) * @see TurtleUpgradeDataProvider.ToolBuilder#consumeDurability(TurtleToolDurability)
*/ */
public enum TurtleToolDurability implements StringRepresentable { 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 * 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"), 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.ITurtleAccess;
import dan200.computercraft.api.turtle.ITurtleUpgrade; import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide; import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.impl.PlatformHelper;
import net.minecraft.Util; import net.minecraft.Util;
import net.minecraft.core.component.DataComponentPatch; import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import java.util.Objects;
/** /**
* Common functionality between {@link ITurtleUpgrade} and {@link IPocketUpgrade}. * Common functionality between {@link ITurtleUpgrade} and {@link IPocketUpgrade}.
*/ */
public interface UpgradeBase { 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. * Return an unlocalised string to describe this type of computer in item names.
* <p>
* This should typically be a {@linkplain Component#translatable(String) translation key}, rather than a hard coded
* string.
* <p> * <p>
* Examples of built-in adjectives are "Wireless", "Mining" and "Crafty". * 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 * 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. * Returns the item stack representing a currently equipped turtle upgrade.
* <p> * <p>
* While upgrades can store upgrade data ({@link ITurtleAccess#getUpgradeData(TurtleSide)} and * While upgrades can store upgrade data ({@link ITurtleAccess#getUpgradeNBTData(TurtleSide)} and
* {@link IPocketAccess#getUpgradeData()}}, by default this data is discarded when an upgrade is unequipped, * {@link IPocketAccess#getUpgradeNBTData()}}, by default this data is discarded when an upgrade is unequipped,
* and the original item stack is returned. * and the original item stack is returned.
* <p> * <p>
* By overriding this method, you can create a new {@link ItemStack} which contains enough data to * 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. * @param upgradeData The current upgrade data. This should <strong>NOT</strong> be mutated.
* @return The item stack returned when unequipping. * @return The item stack returned when unequipping.
*/ */
default ItemStack getUpgradeItem(DataComponentPatch upgradeData) { default ItemStack getUpgradeItem(CompoundTag upgradeData) {
return getCraftingItem(); return getCraftingItem();
} }
/** /**
* Extract upgrade data from an {@link ItemStack}. * Extract upgrade data from an {@link ItemStack}.
* <p> * <p>
* This upgrade data will be available with {@link ITurtleAccess#getUpgradeData(TurtleSide)} or * This upgrade data will be available with {@link ITurtleAccess#getUpgradeNBTData(TurtleSide)} or
* {@link IPocketAccess#getUpgradeData()}. * {@link IPocketAccess#getUpgradeNBTData()}.
* <p> * <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 * @param stack The stack that was equipped by the turtle or pocket computer. This will have the same item as
* {@link #getCraftingItem()}. * {@link #getCraftingItem()}.
* @return The upgrade data that should be set on the turtle or pocket computer. * @return The upgrade data that should be set on the turtle or pocket computer.
*/ */
default DataComponentPatch getUpgradeData(ItemStack stack) { default CompoundTag getUpgradeData(ItemStack stack) {
return DataComponentPatch.EMPTY; return new CompoundTag();
} }
/** /**
@@ -94,15 +97,26 @@ public interface UpgradeBase {
* the original stack. In order to prevent people losing items with enchantments (or * 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. * repairing items with non-0 damage), we impose additional checks on the item.
* <p> * <p>
* The default check requires that any NBT is exactly the same as the crafting item, * The default check requires that any non-capability NBT is exactly the same as the
* but this may be relaxed for your upgrade. * 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 * @param stack The stack to check. This is guaranteed to be non-empty and have the same item as
* {@link #getCraftingItem()}. * {@link #getCraftingItem()}.
* @return If this stack may be used to equip this upgrade. * @return If this stack may be used to equip this upgrade.
*/ */
default boolean isItemSuitable(ItemStack stack) { 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. * @param id The upgrade ID.
* @return The generated adjective. * @return The generated adjective.
* @see #getAdjective() * @see #getUnlocalisedAdjective()
*/ */
static String getDefaultAdjective(ResourceLocation id) { static String getDefaultAdjective(ResourceLocation id) {
return Util.makeDescriptionId("upgrade", id) + ".adjective"; 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.pocket.IPocketUpgrade;
import dan200.computercraft.api.turtle.ITurtleUpgrade; import dan200.computercraft.api.turtle.ITurtleUpgrade;
import net.minecraft.core.Holder; import net.minecraft.nbt.CompoundTag;
import net.minecraft.core.component.DataComponentPatch;
import net.minecraft.world.item.ItemStack; 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. * 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 upgrade The current upgrade.
* @param data The upgrade's data. * @param data The upgrade's data.
* @param <T> The type of upgrade, either {@link ITurtleUpgrade} or {@link IPocketUpgrade}. * @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. * A utility method to construct a new {@link UpgradeData} instance.
* *
* @param holder An upgrade. * @param upgrade An upgrade.
* @param data The upgrade's data. * @param data The upgrade's data.
* @param <T> The type of upgrade. * @param <T> The type of upgrade.
* @return The new {@link UpgradeData} instance. * @return The new {@link UpgradeData} instance.
*/ */
public static <T extends UpgradeBase> UpgradeData<T> of(Holder.Reference<T> holder, DataComponentPatch data) { public static <T extends UpgradeBase> UpgradeData<T> of(T upgrade, CompoundTag data) {
return new UpgradeData<>(holder, data); return new UpgradeData<>(upgrade, data);
} }
/** /**
* Create an {@link UpgradeData} containing the default {@linkplain #data() data} for an upgrade. * Create an {@link UpgradeData} containing the default {@linkplain #data() data} for an upgrade.
* *
* @param holder The upgrade instance. * @param upgrade The upgrade instance.
* @param <T> The type of upgrade. * @param <T> The type of upgrade.
* @return The default upgrade data. * @return The default upgrade data.
*/ */
public static <T extends UpgradeBase> UpgradeData<T> ofDefault(Holder.Reference<T> holder) { public static <T extends UpgradeBase> UpgradeData<T> ofDefault(T upgrade) {
var upgrade = holder.value(); return of(upgrade, upgrade.getUpgradeData(upgrade.getCraftingItem()));
return of(holder, upgrade.getUpgradeData(upgrade.getCraftingItem()));
}
public UpgradeData {
if (!holder.isBound()) throw new IllegalArgumentException("Holder is not bound");
} }
/** /**
* 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() { @Contract("!null -> !null; null -> null")
return holder().value(); 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> * <p>
* This returns a defensive copy of the item, to prevent accidental mutation of the upgrade data or original * This returns a defensive copy of the item, to prevent accidental mutation of the upgrade data or original
* {@linkplain UpgradeBase#getCraftingItem() upgrade stack}. * {@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. * @return This upgrade's item.
*/ */
public ItemStack getUpgradeItem() { 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; package dan200.computercraft.impl;
import com.mojang.serialization.Codec;
import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.detail.BlockReference; import dan200.computercraft.api.detail.BlockReference;
import dan200.computercraft.api.detail.DetailRegistry; 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.PacketNetwork;
import dan200.computercraft.api.network.wired.WiredElement; import dan200.computercraft.api.network.wired.WiredElement;
import dan200.computercraft.api.network.wired.WiredNode; 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.redstone.BundledRedstoneProvider;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleRefuelHandler; import dan200.computercraft.api.turtle.TurtleRefuelHandler;
import dan200.computercraft.api.upgrades.UpgradeType; import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.impl.upgrades.TurtleToolSpec;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
import net.minecraft.core.Registry; import net.minecraft.core.Registry;
@@ -70,15 +67,9 @@ public interface ComputerCraftAPIService {
void registerRefuelHandler(TurtleRefuelHandler handler); void registerRefuelHandler(TurtleRefuelHandler handler);
ResourceKey<Registry<UpgradeType<? extends ITurtleUpgrade>>> turtleUpgradeRegistryId(); ResourceKey<Registry<TurtleUpgradeSerialiser<?>>> turtleUpgradeRegistryId();
Codec<ITurtleUpgrade> turtleUpgradeCodec(); ResourceKey<Registry<PocketUpgradeSerialiser<?>>> pocketUpgradeRegistryId();
ResourceKey<Registry<UpgradeType<? extends IPocketUpgrade>>> pocketUpgradeRegistryId();
ITurtleUpgrade createTurtleTool(TurtleToolSpec spec);
Codec<IPocketUpgrade> pocketUpgradeCodec();
DetailRegistry<ItemStack> getItemStackDetailRegistry(); 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 { plugins {
id("cc-tweaked.vanilla") id("cc-tweaked.vanilla")
id("cc-tweaked.gametest")
id("cc-tweaked.illuaminate") id("cc-tweaked.illuaminate")
id("cc-tweaked.mod")
id("cc-tweaked.publishing") id("cc-tweaked.publishing")
} }
sourceSets {
main {
resources.srcDir("src/generated/resources")
}
}
minecraft { minecraft {
accessWideners( accessWideners(
"src/main/resources/computercraft.accesswidener", "src/main/resources/computercraft.accesswidener",
@@ -23,7 +29,7 @@ configurations {
} }
repositories { repositories {
maven("https://maven.neoforged.net/") { maven("https://maven.minecraftforge.net/") {
content { content {
includeModule("org.spongepowered", "mixin") includeModule("org.spongepowered", "mixin")
} }
@@ -36,8 +42,6 @@ dependencies {
api(commonClasses(project(":common-api"))) api(commonClasses(project(":common-api")))
clientApi(clientClasses(project(":common-api"))) clientApi(clientClasses(project(":common-api")))
compileOnly(libs.mixin)
compileOnly(libs.mixinExtra)
compileOnly(libs.bundles.externalMods.common) compileOnly(libs.bundles.externalMods.common)
compileOnly(variantOf(libs.create.forge) { classifier("slim") }) { isTransitive = false } compileOnly(variantOf(libs.create.forge) { classifier("slim") }) { isTransitive = false }
clientCompileOnly(variantOf(libs.emi) { classifier("api") }) clientCompileOnly(variantOf(libs.emi) { classifier("api") })
@@ -63,7 +67,7 @@ dependencies {
} }
illuaminate { illuaminate {
version = libs.versions.illuaminate version.set(libs.versions.illuaminate)
} }
val luaJavadoc by tasks.registering(Javadoc::class) { 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.addStringOption("project-root", rootProject.file(".").absolutePath)
options.noTimestamp(false) 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) { 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::") } doLast { if (System.getenv("GITHUB_ACTIONS") != null) println("::remove-matcher owner=illuaminate::") }
} }
fun MergeTrees.configureForDatagen(source: SourceSet, outputFolder: String) { val runData by tasks.registering(MergeTrees::class) {
output = layout.projectDirectory.dir(outputFolder) output = layout.projectDirectory.dir("src/generated/resources")
for (loader in listOf("forge", "fabric")) { for (loader in listOf("forge", "fabric")) {
mustRunAfter(":$loader:$name") mustRunAfter(":$loader:runData")
source { source {
input { input {
from(project(":$loader").layout.buildDirectory.dir(source.getTaskName("generateResources", null))) from(project(":$loader").layout.buildDirectory.dir("generatedResources"))
exclude(".cache") 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) { tasks.withType(GenerateModuleMetadata::class).configureEach { isEnabled = false }
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
}

View File

@@ -64,9 +64,6 @@ public final class ClientHooks {
public static void onWorldUnload() { public static void onWorldUnload() {
MonitorRenderState.destroyAll(); MonitorRenderState.destroyAll();
SpeakerManager.reset(); SpeakerManager.reset();
}
public static void onDisconnect() {
ClientPocketComputers.reset(); ClientPocketComputers.reset();
} }
@@ -111,7 +108,7 @@ public final class ClientHooks {
*/ */
public static void addBlockDebugInfo(Consumer<String> addText) { public static void addBlockDebugInfo(Consumer<String> addText) {
var minecraft = Minecraft.getInstance(); 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; if (minecraft.hitResult == null || minecraft.hitResult.getType() != HitResult.Type.BLOCK) return;
var tile = minecraft.level.getBlockEntity(((BlockHitResult) minecraft.hitResult).getBlockPos()); 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) { private static void addTurtleUpgrade(Consumer<String> out, TurtleBlockEntity turtle, TurtleSide side) {
var upgrade = turtle.getAccess().getUpgradeWithData(side); var upgrade = turtle.getUpgrade(side);
if (upgrade != null) out.accept(String.format("Upgrade[%s]: %s", side, upgrade.holder().key().location())); 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. * @param addText A callback which adds a single line of text.
*/ */
public static void addGameDebugInfo(Consumer<String> addText) { 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()); 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.core.util.Colour;
import dan200.computercraft.shared.ModRegistry; import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.command.CommandComputerCraft; 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.ComputerState;
import dan200.computercraft.shared.computer.core.ServerContext; import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu; import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
import dan200.computercraft.shared.media.items.DiskItem; 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.Util;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.color.item.ItemColor; import net.minecraft.client.color.item.ItemColor;
import net.minecraft.client.gui.screens.MenuScreens; 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.multiplayer.ClientLevel;
import net.minecraft.client.renderer.ShaderInstance; import net.minecraft.client.renderer.ShaderInstance;
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider; 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.resources.ResourceLocation;
import net.minecraft.server.packs.resources.PreparableReloadListener; import net.minecraft.server.packs.resources.PreparableReloadListener;
import net.minecraft.server.packs.resources.ResourceProvider; import net.minecraft.server.packs.resources.ResourceProvider;
import net.minecraft.util.FastColor;
import net.minecraft.world.entity.LivingEntity; 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.Item;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.component.DyedItemColor;
import net.minecraft.world.level.ItemLike; import net.minecraft.world.level.ItemLike;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Collection;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Supplier; import java.util.function.Supplier;
@@ -89,6 +83,14 @@ public final class ClientRegistry {
* @param itemProperties Callback to register item properties. * @param itemProperties Callback to register item properties.
*/ */
public static void registerMainThread(RegisterItemProperty itemProperties) { 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", registerItemProperty(itemProperties, "state",
new UnclampedPropertyFunction((stack, world, player, random) -> { new UnclampedPropertyFunction((stack, world, player, random) -> {
var computer = ClientPocketComputers.get(stack); var computer = ClientPocketComputers.get(stack);
@@ -97,41 +99,28 @@ public final class ClientRegistry {
ModRegistry.Items.POCKET_COMPUTER_NORMAL, ModRegistry.Items.POCKET_COMPUTER_ADVANCED ModRegistry.Items.POCKET_COMPUTER_NORMAL, ModRegistry.Items.POCKET_COMPUTER_ADVANCED
); );
registerItemProperty(itemProperties, "coloured", 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 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) { public static void registerTurtleModellers(RegisterTurtleUpgradeModeller register) {
register.register(ModRegistry.TurtleUpgradeTypes.SPEAKER.get(), TurtleUpgradeModeller.sided( register.register(ModRegistry.TurtleSerialisers.SPEAKER.get(), TurtleUpgradeModeller.sided(
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_left"), new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_left"),
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_right") new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_right")
)); ));
register.register(ModRegistry.TurtleUpgradeTypes.WORKBENCH.get(), TurtleUpgradeModeller.sided( register.register(ModRegistry.TurtleSerialisers.WORKBENCH.get(), TurtleUpgradeModeller.sided(
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_left"), new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_left"),
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_right") new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_right")
)); ));
register.register(ModRegistry.TurtleUpgradeTypes.WIRELESS_MODEM.get(), new TurtleModemModeller()); register.register(ModRegistry.TurtleSerialisers.WIRELESS_MODEM_NORMAL.get(), new TurtleModemModeller(false));
register.register(ModRegistry.TurtleUpgradeTypes.TOOL.get(), TurtleUpgradeModeller.flatItem()); register.register(ModRegistry.TurtleSerialisers.WIRELESS_MODEM_ADVANCED.get(), new TurtleModemModeller(true));
register.register(ModRegistry.TurtleSerialisers.TOOL.get(), TurtleUpgradeModeller.flatItem());
} }
@SafeVarargs @SafeVarargs
private static void registerItemProperty(RegisterItemProperty itemProperties, String name, ClampedItemPropertyFunction getter, Supplier<? extends Item>... items) { 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); for (var item : items) itemProperties.register(item.get(), id, getter);
} }
@@ -147,25 +136,26 @@ public final class ClientRegistry {
register.accept(GuiSprites.initialise(minecraft.getTextureManager())); register.accept(GuiSprites.initialise(minecraft.getTextureManager()));
} }
private static final ResourceLocation[] EXTRA_MODELS = { private static final String[] EXTRA_MODELS = new String[]{
TurtleOverlay.ELF_MODEL, "block/turtle_colour",
TurtleBlockEntityRenderer.COLOUR_TURTLE_MODEL, "block/turtle_elf_overlay",
"block/turtle_rainbow_overlay",
"block/turtle_trans_overlay",
}; };
public static void registerExtraModels(Consumer<ResourceLocation> register, Collection<ResourceLocation> extraModels) { public static void registerExtraModels(Consumer<ResourceLocation> register) {
for (var model : EXTRA_MODELS) register.accept(model); for (var model : EXTRA_MODELS) register.accept(new ResourceLocation(ComputerCraftAPI.MOD_ID, model));
extraModels.forEach(register);
TurtleUpgradeModellers.getDependencies().forEach(register); TurtleUpgradeModellers.getDependencies().forEach(register);
} }
public static void registerItemColours(BiConsumer<ItemColor, ItemLike> register) { public static void registerItemColours(BiConsumer<ItemColor, ItemLike> register) {
register.accept( register.accept(
(stack, layer) -> layer == 1 ? DiskItem.getColour(stack) : -1, (stack, layer) -> layer == 1 ? ((DiskItem) stack.getItem()).getColour(stack) : 0xFFFFFF,
ModRegistry.Items.DISK.get() ModRegistry.Items.DISK.get()
); );
register.accept( 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() ModRegistry.Items.TREASURE_DISK.get()
); );
@@ -178,17 +168,17 @@ public final class ClientRegistry {
private static int getPocketColour(ItemStack stack, int layer) { private static int getPocketColour(ItemStack stack, int layer) {
return switch (layer) { return switch (layer) {
default -> -1; default -> 0xFFFFFF;
case 1 -> DyedItemColor.getOrDefault(stack, -1); // Frame colour case 1 -> IColouredItem.getColourBasic(stack); // Frame colour
case 2 -> { // Light colour case 2 -> { // Light colour
var computer = ClientPocketComputers.get(stack); 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) { 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 { 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()); var tag = createTag(table.getId());
if (chat.allMessages.removeIf(guiMessage -> guiMessage.tag() != null && Objects.equals(guiMessage.tag().logTag(), tag.logTag()))) { if (chat.allMessages.removeIf(guiMessage -> guiMessage.tag() != null && Objects.equals(guiMessage.tag().logTag(), tag.logTag()))) {
chat.rescaleChat(); chat.refreshTrimmedMessage();
} }
TableFormatter.super.display(table); 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 @Override
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) { public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
renderBackground(graphics);
super.render(graphics, mouseX, mouseY, partialTicks); super.render(graphics, mouseX, mouseY, partialTicks);
renderTooltip(graphics, mouseX, mouseY); renderTooltip(graphics, mouseX, mouseY);
} }

View File

@@ -15,7 +15,7 @@ import net.minecraft.world.entity.player.Inventory;
* The GUI for disk drives. * The GUI for disk drives.
*/ */
public class DiskDriveScreen extends AbstractContainerScreen<DiskDriveMenu> { 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) { public DiskDriveScreen(DiskDriveMenu container, Inventory player, Component title) {
super(container, player, title); super(container, player, title);
@@ -28,6 +28,7 @@ public class DiskDriveScreen extends AbstractContainerScreen<DiskDriveMenu> {
@Override @Override
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) { public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
renderBackground(graphics);
super.render(graphics, mouseX, mouseY, partialTicks); super.render(graphics, mouseX, mouseY, partialTicks);
renderTooltip(graphics, mouseX, mouseY); renderTooltip(graphics, mouseX, mouseY);
} }

View File

@@ -6,6 +6,7 @@ package dan200.computercraft.client.gui;
import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.client.render.ComputerBorderRenderer; import dan200.computercraft.client.render.ComputerBorderRenderer;
import dan200.computercraft.data.client.ClientDataProviders;
import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.computer.core.ComputerFamily;
import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.texture.TextureManager; 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. * Sprite sheet for all GUI texutres in the mod.
*/ */
public final class GuiSprites extends TextureAtlasHolder { 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 ResourceLocation TEXTURE = SPRITE_SHEET.withPath(x -> "textures/atlas/" + x + ".png");
public static final ButtonTextures TURNED_OFF = button("turned_off"); 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) { private static ButtonTextures button(String name) {
return new ButtonTextures( return new ButtonTextures(
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "gui/buttons/" + name), new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/buttons/" + name),
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "gui/buttons/" + name + "_hover") new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/buttons/" + name + "_hover")
); );
} }
private static ComputerTextures computer(String name, boolean pocket, boolean sidebar) { private static ComputerTextures computer(String name, boolean pocket, boolean sidebar) {
return new ComputerTextures( return new ComputerTextures(
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "gui/border_" + name), new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/border_" + name),
pocket ? ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "gui/pocket_bottom_" + name) : null, pocket ? new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/pocket_bottom_" + name) : null,
sidebar ? ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "gui/sidebar_" + 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 pocketBottom The texture for the bottom of a pocket computer.
* @param sidebar The texture for the computer sidebar. * @param sidebar The texture for the computer sidebar.
* @see ComputerBorderRenderer * @see ComputerBorderRenderer
* @see ClientDataProviders
*/ */
public record ComputerTextures( public record ComputerTextures(
ResourceLocation border, 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.Toast;
import net.minecraft.client.gui.components.toasts.ToastComponent; import net.minecraft.client.gui.components.toasts.ToastComponent;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.FormattedCharSequence; import net.minecraft.util.FormattedCharSequence;
import net.minecraft.world.item.ItemStack; 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}. * A {@link Toast} implementation which displays an arbitrary message along with an optional {@link ItemStack}.
*/ */
public class ItemToast implements Toast { public class ItemToast implements Toast {
private static final ResourceLocation TEXTURE = ResourceLocation.withDefaultNamespace("toast/recipe");
public static final Object TRANSFER_NO_RESPONSE_TOKEN = new Object(); public static final Object TRANSFER_NO_RESPONSE_TOKEN = new Object();
private static final long DISPLAY_TIME = 7000L; private static final long DISPLAY_TIME = 7000L;
@@ -81,7 +79,7 @@ public class ItemToast implements Toast {
} }
if (width == 160 && message.size() <= 1) { if (width == 160 && message.size() <= 1) {
graphics.blitSprite(TEXTURE, 0, 0, width, height()); graphics.blit(TEXTURE, 0, 0, 0, 64, width, height());
} else { } else {
var height = height(); 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) { 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); 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) { 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 @Override
public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, double scrollY) { public boolean mouseScrolled(double pMouseX, double pMouseY, double pDelta) {
Objects.requireNonNull(minecraft().player).getInventory().swapPaint(scrollY); Objects.requireNonNull(minecraft().player).getInventory().swapPaint(pDelta);
return super.mouseScrolled(mouseX, mouseY, scrollX, scrollY); return super.mouseScrolled(pMouseX, pMouseY, pDelta);
} }
@Override @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() { private Minecraft minecraft() {
return Nullability.assertNonNull(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. * When closed, it returns to the previous screen.
*/ */
public final class OptionScreen extends 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_WIDTH = 100;
public static final int BUTTON_HEIGHT = 20; public static final int BUTTON_HEIGHT = 20;
@@ -86,6 +86,8 @@ public final class OptionScreen extends Screen {
@Override @Override
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) { public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
renderBackground(graphics);
// Render the actual texture. // Render the actual texture.
graphics.blit(BACKGROUND, x, y, 0, 0, innerWidth, PADDING); graphics.blit(BACKGROUND, x, y, 0, 0, innerWidth, PADDING);
graphics.blit(BACKGROUND, graphics.blit(BACKGROUND,

View File

@@ -15,7 +15,7 @@ import net.minecraft.world.entity.player.Inventory;
* The GUI for printers. * The GUI for printers.
*/ */
public class PrinterScreen extends AbstractContainerScreen<PrinterMenu> { 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) { public PrinterScreen(PrinterMenu container, Inventory player, Component title) {
super(container, player, title); super(container, player, title);
@@ -30,6 +30,7 @@ public class PrinterScreen extends AbstractContainerScreen<PrinterMenu> {
@Override @Override
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) { public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
renderBackground(graphics);
super.render(graphics, mouseX, mouseY, partialTicks); super.render(graphics, mouseX, mouseY, partialTicks);
renderTooltip(graphics, mouseX, mouseY); renderTooltip(graphics, mouseX, mouseY);
} }

View File

@@ -4,12 +4,14 @@
package dan200.computercraft.client.gui; package dan200.computercraft.client.gui;
import com.mojang.blaze3d.vertex.Tesselator;
import dan200.computercraft.core.terminal.TextBuffer; import dan200.computercraft.core.terminal.TextBuffer;
import dan200.computercraft.shared.ModRegistry; import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.media.PrintoutMenu; 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.GuiGraphics;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.AbstractContainerMenu;
@@ -17,6 +19,7 @@ import net.minecraft.world.inventory.ContainerListener;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFW;
import java.util.Arrays;
import java.util.Objects; import java.util.Objects;
import static dan200.computercraft.client.render.PrintoutRenderer.*; import static dan200.computercraft.client.render.PrintoutRenderer.*;
@@ -37,8 +40,18 @@ public final class PrintoutScreen extends AbstractContainerScreen<PrintoutMenu>
} }
private void setPrintout(ItemStack stack) { private void setPrintout(ItemStack stack) {
page = 0; var text = PrintoutItem.getText(stack);
printout = PrintoutInfo.of(PrintoutData.getOrEmpty(stack), stack.is(ModRegistry.Items.PRINTED_BOOK.get())); 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 @Override
@@ -93,15 +106,15 @@ public final class PrintoutScreen extends AbstractContainerScreen<PrintoutMenu>
} }
@Override @Override
public boolean mouseScrolled(double x, double y, double deltaX, double deltaY) { public boolean mouseScrolled(double x, double y, double delta) {
if (super.mouseScrolled(x, y, deltaX, deltaY)) return true; if (super.mouseScrolled(x, y, delta)) return true;
if (deltaY < 0) { if (delta < 0) {
// Scroll up goes to the next page // Scroll up goes to the next page
nextPage(); nextPage();
return true; return true;
} }
if (deltaY > 0) { if (delta > 0) {
// Scroll down goes to the previous page // Scroll down goes to the previous page
previousPage(); previousPage();
return true; return true;
@@ -112,14 +125,23 @@ public final class PrintoutScreen extends AbstractContainerScreen<PrintoutMenu>
@Override @Override
protected void renderBg(GuiGraphics graphics, float partialTicks, int mouseX, int mouseY) { 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().pushPose();
graphics.pose().translate(0, 0, 1); graphics.pose().translate(0, 0, -1);
renderBackground(graphics);
drawBorder(graphics.pose(), graphics.bufferSource(), leftPos, topPos, 0, page, printout.pages(), printout.book(), FULL_BRIGHT_LIGHTMAP);
drawText(graphics.pose(), graphics.bufferSource(), leftPos + X_TEXT_MARGIN, topPos + Y_TEXT_MARGIN, PrintoutData.LINES_PER_PAGE * page, FULL_BRIGHT_LIGHTMAP, printout.text(), printout.colour());
graphics.pose().popPose(); graphics.pose().popPose();
super.render(graphics, mouseX, mouseY, partialTicks);
} }
@Override @Override
@@ -128,19 +150,16 @@ public final class PrintoutScreen extends AbstractContainerScreen<PrintoutMenu>
} }
record PrintoutInfo(int pages, boolean book, TextBuffer[] text, TextBuffer[] colour) { 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) { static {
var text = new TextBuffer[printout.lines().size()]; var textLines = new TextBuffer[PrintoutItem.LINES_PER_PAGE];
var colours = new TextBuffer[printout.lines().size()]; Arrays.fill(textLines, new TextBuffer(" ".repeat(PrintoutItem.LINE_MAX_LENGTH)));
for (var i = 0; i < text.length; i++) {
var line = printout.lines().get(i);
text[i] = new TextBuffer(line.text());
colours[i] = new TextBuffer(line.foreground());
}
var pages = Math.max(text.length / PrintoutData.LINES_PER_PAGE, 1); var colourLines = new TextBuffer[PrintoutItem.LINES_PER_PAGE];
return new PrintoutInfo(pages, book, text, colours); 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. * The GUI for turtles.
*/ */
public class TurtleScreen extends AbstractComputerScreen<TurtleMenu> { 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_NORMAL = new ResourceLocation(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_ADVANCED = new ResourceLocation(ComputerCraftAPI.MOD_ID, "textures/gui/turtle_advanced.png");
private static final int TEX_WIDTH = 278; private static final int TEX_WIDTH = 278;
private static final int TEX_HEIGHT = 217; private static final int TEX_HEIGHT = 217;

View File

@@ -43,10 +43,6 @@ public class DynamicImageButton extends Button {
@Override @Override
public void renderWidget(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) { 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()); var texture = this.texture.get(isHoveredOrFocused());
RenderSystem.disableDepthTest(); RenderSystem.disableDepthTest();
@@ -54,6 +50,14 @@ public class DynamicImageButton extends Button {
RenderSystem.enableDepthTest(); 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 record HintedMessage(Component message, Tooltip tooltip) {
public HintedMessage(Component message, @Nullable Component hint) { public HintedMessage(Component message, @Nullable Component hint) {
this( this(

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