mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-11-07 08:52:59 +00:00
Compare commits
141 Commits
v1.20.1-1.
...
v1.20.1-1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c9caffb10f | ||
|
|
4675583e1c | ||
|
|
afe16cc593 | ||
|
|
0abd107348 | ||
|
|
cef4b4906b | ||
|
|
04900dc82f | ||
|
|
9b63cc81b1 | ||
|
|
9eead7a0ec | ||
|
|
ad97b2922b | ||
|
|
52986f8d73 | ||
|
|
ab00580389 | ||
|
|
128ac2f109 | ||
|
|
5d8c46c7e6 | ||
|
|
1a5dc92bd4 | ||
|
|
98b2d3f310 | ||
|
|
e92c2d02f8 | ||
|
|
f8ef40d378 | ||
|
|
61f9b1d0c6 | ||
|
|
ffb62dfa02 | ||
|
|
6fb291112d | ||
|
|
7ee821e9c9 | ||
|
|
b7df91349a | ||
|
|
cb8e06af2a | ||
|
|
6478fca7a2 | ||
|
|
3493159a05 | ||
|
|
eead67e314 | ||
|
|
3b8813cf8f | ||
|
|
a9191a4d4e | ||
|
|
451a2593ce | ||
|
|
d38b1da974 | ||
|
|
6e374579a4 | ||
|
|
4daa2a2b6a | ||
|
|
84b6edab82 | ||
|
|
31aaf46d09 | ||
|
|
2d11b51c62 | ||
|
|
a0f759527d | ||
|
|
385e4210fa | ||
|
|
d2896473f2 | ||
|
|
f14cb2a3d1 | ||
|
|
8db5c6bc3a | ||
|
|
f26e443e81 | ||
|
|
033378333f | ||
|
|
ebeaa757a9 | ||
|
|
57b1a65db3 | ||
|
|
27c72a4571 | ||
|
|
f284328656 | ||
|
|
6b83c63991 | ||
|
|
b27526bd21 | ||
|
|
cb25f6c08a | ||
|
|
d38b1d04e7 | ||
|
|
9ccee75a99 | ||
|
|
359c8d6652 | ||
|
|
1788afacfc | ||
|
|
f695f22d8a | ||
|
|
bc03090ca4 | ||
|
|
a617d0d566 | ||
|
|
36599b321e | ||
|
|
1d6e3f4fc0 | ||
|
|
30dc4cb38c | ||
|
|
be4512d1c3 | ||
|
|
e6ee292850 | ||
|
|
9d36f72bad | ||
|
|
b5923c4462 | ||
|
|
4d1e689719 | ||
|
|
9d4af07568 | ||
|
|
89294f4a22 | ||
|
|
133b51b092 | ||
|
|
272010e945 | ||
|
|
e0889c613a | ||
|
|
f115d43d07 | ||
|
|
8be6b1b772 | ||
|
|
104d5e70de | ||
|
|
e3bda2f763 | ||
|
|
234f69e8e5 | ||
|
|
ed3a17f9b9 | ||
|
|
0349c2b1f9 | ||
|
|
03f9e6bd6d | ||
|
|
9d8c933a14 | ||
|
|
78bb3da58c | ||
|
|
39a5e40c92 | ||
|
|
763ba51919 | ||
|
|
cf6ec8c28f | ||
|
|
95d3b646b2 | ||
|
|
488f66eead | ||
|
|
1f7d245876 | ||
|
|
af12b3a0ea | ||
|
|
eb3e8ba677 | ||
|
|
2043939531 | ||
|
|
84a799d27a | ||
|
|
fe826f5c9c | ||
|
|
f8b7422294 | ||
|
|
b343c01216 | ||
|
|
76968f2f28 | ||
|
|
1d365f5a0b | ||
|
|
7b240cbf7e | ||
|
|
d272a327c7 | ||
|
|
0c0556a5bc | ||
|
|
87345c6b2e | ||
|
|
784e623776 | ||
|
|
bcb3e9bd53 | ||
|
|
c30bffbd0f | ||
|
|
91c41856c5 | ||
|
|
18c9723308 | ||
|
|
aee382ed70 | ||
|
|
6656da5877 | ||
|
|
09e521727f | ||
|
|
cab66a2d6e | ||
|
|
8eabd4f303 | ||
|
|
e3ced84885 | ||
|
|
0929ab577d | ||
|
|
2228733abc | ||
|
|
e67c94d1bd | ||
|
|
ae5a661a47 | ||
|
|
0ff58cdc3e | ||
|
|
1747c74770 | ||
|
|
71669cf49c | ||
|
|
bd327e37eb | ||
|
|
bdce9a8170 | ||
|
|
7e5598d084 | ||
|
|
440fca6535 | ||
|
|
6635edd35c | ||
|
|
93ad40efbb | ||
|
|
27dc8b5b2c | ||
|
|
3ebdf7ef5e | ||
|
|
905d4cb091 | ||
|
|
e7ab05d064 | ||
|
|
6ec34b42e5 | ||
|
|
ab785a0906 | ||
|
|
4541decd40 | ||
|
|
747a5a53b4 | ||
|
|
c0643fadca | ||
|
|
0a31de43c2 | ||
|
|
96b6947ef2 | ||
|
|
e7a1065bfc | ||
|
|
663eecff0c | ||
|
|
e6125bcf60 | ||
|
|
0d6c6e7ae7 | ||
|
|
ae71eb3cae | ||
|
|
3188197447 | ||
|
|
6c8b391dab | ||
|
|
b1248e4901 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -7,7 +7,9 @@
|
|||||||
/logs
|
/logs
|
||||||
/build
|
/build
|
||||||
/projects/*/logs
|
/projects/*/logs
|
||||||
|
/projects/fabric/fabricloader.log
|
||||||
/projects/*/build
|
/projects/*/build
|
||||||
|
/projects/*/src/test/generated_tests/
|
||||||
/buildSrc/build
|
/buildSrc/build
|
||||||
/out
|
/out
|
||||||
/buildSrc/out
|
/buildSrc/out
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ repos:
|
|||||||
name: Check Java codestyle
|
name: Check Java codestyle
|
||||||
files: ".*\\.java$"
|
files: ".*\\.java$"
|
||||||
language: system
|
language: system
|
||||||
entry: ./gradlew checkstyleMain checkstyleTest
|
entry: ./gradlew checkstyle
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
require_serial: true
|
require_serial: true
|
||||||
- id: illuaminate
|
- id: illuaminate
|
||||||
|
|||||||
29
.reuse/dep5
29
.reuse/dep5
@@ -10,8 +10,8 @@ Files:
|
|||||||
projects/common/src/testMod/resources/data/cctest/structures/*
|
projects/common/src/testMod/resources/data/cctest/structures/*
|
||||||
projects/fabric/src/generated/*
|
projects/fabric/src/generated/*
|
||||||
projects/forge/src/generated/*
|
projects/forge/src/generated/*
|
||||||
projects/web/src/export/index.json
|
projects/web/src/htmlTransform/export/index.json
|
||||||
projects/web/src/export/items/minecraft/*
|
projects/web/src/htmlTransform/export/items/minecraft/*
|
||||||
Comment: Generated/data files are CC0.
|
Comment: Generated/data files are CC0.
|
||||||
Copyright: The CC: Tweaked Developers
|
Copyright: The CC: Tweaked Developers
|
||||||
License: CC0-1.0
|
License: CC0-1.0
|
||||||
@@ -37,10 +37,10 @@ Files:
|
|||||||
projects/fabric/src/testMod/resources/computercraft-gametest.fabric.mixins.json
|
projects/fabric/src/testMod/resources/computercraft-gametest.fabric.mixins.json
|
||||||
projects/fabric/src/testMod/resources/fabric.mod.json
|
projects/fabric/src/testMod/resources/fabric.mod.json
|
||||||
projects/forge/src/client/resources/computercraft-client.forge.mixins.json
|
projects/forge/src/client/resources/computercraft-client.forge.mixins.json
|
||||||
projects/web/src/mount/.settings
|
projects/web/src/frontend/mount/.settings
|
||||||
projects/web/src/mount/example.nfp
|
projects/web/src/frontend/mount/example.nfp
|
||||||
projects/web/src/mount/example.nft
|
projects/web/src/frontend/mount/example.nft
|
||||||
projects/web/src/mount/expr_template.lua
|
projects/web/src/frontend/mount/expr_template.lua
|
||||||
projects/web/tsconfig.json
|
projects/web/tsconfig.json
|
||||||
Comment: Several assets where it's inconvenient to create a .license file.
|
Comment: Several assets where it's inconvenient to create a .license file.
|
||||||
Copyright: The CC: Tweaked Developers
|
Copyright: The CC: Tweaked Developers
|
||||||
@@ -53,19 +53,32 @@ Files:
|
|||||||
projects/common/src/main/resources/assets/computercraft/textures/*
|
projects/common/src/main/resources/assets/computercraft/textures/*
|
||||||
projects/common/src/main/resources/pack.mcmeta
|
projects/common/src/main/resources/pack.mcmeta
|
||||||
projects/common/src/main/resources/pack.png
|
projects/common/src/main/resources/pack.png
|
||||||
|
projects/core/src/main/resources/assets/computercraft/textures/gui/term_font.png
|
||||||
projects/core/src/main/resources/data/computercraft/lua/rom/autorun/.ignoreme
|
projects/core/src/main/resources/data/computercraft/lua/rom/autorun/.ignoreme
|
||||||
projects/core/src/main/resources/data/computercraft/lua/rom/help/*
|
projects/core/src/main/resources/data/computercraft/lua/rom/help/*
|
||||||
projects/core/src/main/resources/data/computercraft/lua/rom/programs/fun/advanced/levels/*
|
projects/core/src/main/resources/data/computercraft/lua/rom/programs/fun/advanced/levels/*
|
||||||
projects/web/src/export/items/computercraft/*
|
projects/web/src/htmlTransform/export/items/computercraft/*
|
||||||
Comment: Bulk-license original assets as CCPL.
|
Comment: Bulk-license original assets as CCPL.
|
||||||
Copyright: 2011 Daniel Ratcliffe
|
Copyright: 2011 Daniel Ratcliffe
|
||||||
License: LicenseRef-CCPL
|
License: LicenseRef-CCPL
|
||||||
|
|
||||||
|
Files:
|
||||||
|
projects/common/src/main/resources/assets/computercraft/lang/cs_cz.json
|
||||||
|
projects/common/src/main/resources/assets/computercraft/lang/ko_kr.json
|
||||||
|
projects/common/src/main/resources/assets/computercraft/lang/pl_pl.json
|
||||||
|
projects/common/src/main/resources/assets/computercraft/lang/pt_br.json
|
||||||
|
projects/common/src/main/resources/assets/computercraft/lang/ru_ru.json
|
||||||
|
projects/common/src/main/resources/assets/computercraft/lang/uk_ua.json
|
||||||
|
projects/common/src/main/resources/assets/computercraft/lang/zh_cn.json
|
||||||
|
Comment: Community-contributed license files
|
||||||
|
Copyright: 2017 The CC: Tweaked Developers
|
||||||
|
License: LicenseRef-CCPL
|
||||||
|
|
||||||
Files:
|
Files:
|
||||||
projects/common/src/main/resources/assets/computercraft/lang/*
|
projects/common/src/main/resources/assets/computercraft/lang/*
|
||||||
Comment: Community-contributed license files
|
Comment: Community-contributed license files
|
||||||
Copyright: 2017 The CC: Tweaked Developers
|
Copyright: 2017 The CC: Tweaked Developers
|
||||||
License: LicenseRef-CCPL
|
License: MPL-2.0
|
||||||
|
|
||||||
Files:
|
Files:
|
||||||
.github/*
|
.github/*
|
||||||
|
|||||||
@@ -100,7 +100,6 @@ about how you can build on that until you've covered everything!
|
|||||||
[new-issue]: https://github.com/cc-tweaked/CC-Tweaked/issues/new/choose "Create a new issue"
|
[new-issue]: https://github.com/cc-tweaked/CC-Tweaked/issues/new/choose "Create a new issue"
|
||||||
[community]: README.md#community "Get in touch with the community."
|
[community]: README.md#community "Get in touch with the community."
|
||||||
[Adoptium]: https://adoptium.net/temurin/releases?version=17 "Download OpenJDK 17"
|
[Adoptium]: https://adoptium.net/temurin/releases?version=17 "Download OpenJDK 17"
|
||||||
[checkstyle]: https://checkstyle.org/
|
|
||||||
[illuaminate]: https://github.com/SquidDev/illuaminate/ "Illuaminate on GitHub"
|
[illuaminate]: https://github.com/SquidDev/illuaminate/ "Illuaminate on GitHub"
|
||||||
[weblate]: https://i18n.tweaked.cc/projects/cc-tweaked/minecraft/ "CC: Tweaked weblate instance"
|
[weblate]: https://i18n.tweaked.cc/projects/cc-tweaked/minecraft/ "CC: Tweaked weblate instance"
|
||||||
[docs]: https://tweaked.cc/ "CC: Tweaked documentation"
|
[docs]: https://tweaked.cc/ "CC: Tweaked documentation"
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ repositories {
|
|||||||
url "https://squiddev.cc/maven/"
|
url "https://squiddev.cc/maven/"
|
||||||
content {
|
content {
|
||||||
includeGroup("cc.tweaked")
|
includeGroup("cc.tweaked")
|
||||||
includeModule("org.squiddev", "Cobalt")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,8 +75,8 @@ minecraft {
|
|||||||
```
|
```
|
||||||
|
|
||||||
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, file an issue, and we can look into
|
subject to change at any point. If you depend on functionality outside the API (or need to mixin to CC:T), please file
|
||||||
exposing more features.
|
an issue to let me know!
|
||||||
|
|
||||||
We bundle the API sources with the jar, so documentation should be easily viewable within your editor. Alternatively,
|
We bundle the API sources with the jar, so documentation should be easily viewable within your editor. Alternatively,
|
||||||
the generated documentation [can be browsed online](https://tweaked.cc/javadoc/).
|
the generated documentation [can be browsed online](https://tweaked.cc/javadoc/).
|
||||||
|
|||||||
@@ -2,13 +2,19 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
import cc.tweaked.gradle.JUnitExt
|
||||||
|
import net.fabricmc.loom.api.LoomGradleExtensionAPI
|
||||||
|
import net.fabricmc.loom.util.gradle.SourceSetHelper
|
||||||
import org.jetbrains.gradle.ext.compiler
|
import org.jetbrains.gradle.ext.compiler
|
||||||
|
import org.jetbrains.gradle.ext.runConfigurations
|
||||||
import org.jetbrains.gradle.ext.settings
|
import org.jetbrains.gradle.ext.settings
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
publishing
|
publishing
|
||||||
alias(libs.plugins.taskTree)
|
alias(libs.plugins.taskTree)
|
||||||
alias(libs.plugins.githubRelease)
|
alias(libs.plugins.githubRelease)
|
||||||
|
alias(libs.plugins.gradleVersions)
|
||||||
|
alias(libs.plugins.versionCatalogUpdate)
|
||||||
id("org.jetbrains.gradle.plugin.idea-ext")
|
id("org.jetbrains.gradle.plugin.idea-ext")
|
||||||
id("cc-tweaked")
|
id("cc-tweaked")
|
||||||
}
|
}
|
||||||
@@ -38,6 +44,50 @@ githubRelease {
|
|||||||
|
|
||||||
tasks.publish { dependsOn(tasks.githubRelease) }
|
tasks.publish { dependsOn(tasks.githubRelease) }
|
||||||
|
|
||||||
|
idea.project.settings.runConfigurations {
|
||||||
|
register<JUnitExt>("Core Tests") {
|
||||||
|
vmParameters = "-ea"
|
||||||
|
moduleName = "${idea.project.name}.core.test"
|
||||||
|
packageName = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
register<JUnitExt>("CraftOS Tests") {
|
||||||
|
vmParameters = "-ea"
|
||||||
|
moduleName = "${idea.project.name}.core.test"
|
||||||
|
className = "dan200.computercraft.core.ComputerTestDelegate"
|
||||||
|
}
|
||||||
|
|
||||||
|
register<JUnitExt>("CraftOS Tests (Fast)") {
|
||||||
|
vmParameters = "-ea -Dcc.skip_keywords=slow"
|
||||||
|
moduleName = "${idea.project.name}.core.test"
|
||||||
|
className = "dan200.computercraft.core.ComputerTestDelegate"
|
||||||
|
}
|
||||||
|
|
||||||
|
register<JUnitExt>("Common Tests") {
|
||||||
|
vmParameters = "-ea"
|
||||||
|
moduleName = "${idea.project.name}.common.test"
|
||||||
|
packageName = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
register<JUnitExt>("Fabric Tests") {
|
||||||
|
val fabricProject = evaluationDependsOn(":fabric")
|
||||||
|
val classPathGroup = fabricProject.extensions.getByType<LoomGradleExtensionAPI>().mods
|
||||||
|
.joinToString(File.pathSeparator + File.pathSeparator) { modSettings ->
|
||||||
|
SourceSetHelper.getClasspath(modSettings, project).joinToString(File.pathSeparator) { it.absolutePath }
|
||||||
|
}
|
||||||
|
|
||||||
|
vmParameters = "-ea -Dfabric.classPathGroups=$classPathGroup"
|
||||||
|
moduleName = "${idea.project.name}.fabric.test"
|
||||||
|
packageName = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
register<JUnitExt>("Forge Tests") {
|
||||||
|
vmParameters = "-ea"
|
||||||
|
moduleName = "${idea.project.name}.forge.test"
|
||||||
|
packageName = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
idea.project.settings.compiler.javac {
|
idea.project.settings.compiler.javac {
|
||||||
// We want ErrorProne to be present when compiling via IntelliJ, as it offers some helpful warnings
|
// We want ErrorProne to be present when compiling via IntelliJ, as it offers some helpful warnings
|
||||||
// and errors. Loop through our source sets and find the appropriate flags.
|
// and errors. Loop through our source sets and find the appropriate flags.
|
||||||
@@ -54,3 +104,9 @@ idea.project.settings.compiler.javac {
|
|||||||
}
|
}
|
||||||
.toMap()
|
.toMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
versionCatalogUpdate {
|
||||||
|
sortByKey.set(false)
|
||||||
|
pin { versions.addAll("fastutil", "guava", "netty", "slf4j") }
|
||||||
|
keep { keepUnusedLibraries.set(true) }
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
plugins {
|
plugins {
|
||||||
`java-gradle-plugin`
|
`java-gradle-plugin`
|
||||||
`kotlin-dsl`
|
`kotlin-dsl`
|
||||||
|
alias(libs.plugins.gradleVersions)
|
||||||
|
alias(libs.plugins.versionCatalogUpdate)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Duplicated in settings.gradle.kts
|
// Duplicated in settings.gradle.kts
|
||||||
@@ -27,19 +29,19 @@ repositories {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
maven("https://repo.spongepowered.org/repository/maven-public/") {
|
|
||||||
name = "Sponge"
|
|
||||||
content {
|
|
||||||
includeGroup("org.spongepowered")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
maven("https://maven.fabricmc.net/") {
|
maven("https://maven.fabricmc.net/") {
|
||||||
name = "Fabric"
|
name = "Fabric"
|
||||||
content {
|
content {
|
||||||
includeGroup("net.fabricmc")
|
includeGroup("net.fabricmc")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
maven("https://squiddev.cc/maven") {
|
||||||
|
name = "SquidDev"
|
||||||
|
content {
|
||||||
|
includeGroup("cc.tweaked.vanilla-extract")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@@ -50,10 +52,10 @@ dependencies {
|
|||||||
implementation(libs.curseForgeGradle)
|
implementation(libs.curseForgeGradle)
|
||||||
implementation(libs.fabric.loom)
|
implementation(libs.fabric.loom)
|
||||||
implementation(libs.forgeGradle)
|
implementation(libs.forgeGradle)
|
||||||
|
implementation(libs.ideaExt)
|
||||||
implementation(libs.librarian)
|
implementation(libs.librarian)
|
||||||
implementation(libs.minotaur)
|
implementation(libs.minotaur)
|
||||||
implementation(libs.vineflower)
|
implementation(libs.vanillaExtract)
|
||||||
implementation(libs.vanillaGradle)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gradlePlugin {
|
gradlePlugin {
|
||||||
@@ -74,3 +76,9 @@ gradlePlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
versionCatalogUpdate {
|
||||||
|
sortByKey.set(false)
|
||||||
|
keep { keepUnusedLibraries.set(true) }
|
||||||
|
catalogFile.set(file("../gradle/libs.versions.toml"))
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import cc.tweaked.gradle.MinecraftConfigurations
|
|||||||
plugins {
|
plugins {
|
||||||
`java-library`
|
`java-library`
|
||||||
id("fabric-loom")
|
id("fabric-loom")
|
||||||
id("io.github.juuxel.loom-vineflower")
|
|
||||||
id("cc-tweaked.java-convention")
|
id("cc-tweaked.java-convention")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,10 +40,6 @@ repositories {
|
|||||||
|
|
||||||
val mainMaven = maven("https://squiddev.cc/maven") {
|
val mainMaven = maven("https://squiddev.cc/maven") {
|
||||||
name = "SquidDev"
|
name = "SquidDev"
|
||||||
content {
|
|
||||||
// Until https://github.com/SpongePowered/Mixin/pull/593 is merged
|
|
||||||
includeModule("org.spongepowered", "mixin")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exclusiveContent {
|
exclusiveContent {
|
||||||
@@ -57,7 +53,6 @@ repositories {
|
|||||||
|
|
||||||
filter {
|
filter {
|
||||||
includeGroup("cc.tweaked")
|
includeGroup("cc.tweaked")
|
||||||
includeModule("org.squiddev", "Cobalt")
|
|
||||||
// Things we mirror
|
// Things we mirror
|
||||||
includeGroup("commoble.morered")
|
includeGroup("commoble.morered")
|
||||||
includeGroup("dev.architectury")
|
includeGroup("dev.architectury")
|
||||||
@@ -66,6 +61,7 @@ repositories {
|
|||||||
includeGroup("me.shedaniel.cloth")
|
includeGroup("me.shedaniel.cloth")
|
||||||
includeGroup("me.shedaniel")
|
includeGroup("me.shedaniel")
|
||||||
includeGroup("mezz.jei")
|
includeGroup("mezz.jei")
|
||||||
|
includeGroup("org.teavm")
|
||||||
includeModule("com.terraformersmc", "modmenu")
|
includeModule("com.terraformersmc", "modmenu")
|
||||||
includeModule("me.lucko", "fabric-permissions-api")
|
includeModule("me.lucko", "fabric-permissions-api")
|
||||||
}
|
}
|
||||||
@@ -76,6 +72,12 @@ dependencies {
|
|||||||
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
|
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
|
||||||
checkstyle(libs.findLibrary("checkstyle").get())
|
checkstyle(libs.findLibrary("checkstyle").get())
|
||||||
|
|
||||||
|
constraints {
|
||||||
|
checkstyle("org.codehaus.plexus:plexus-container-default:2.1.1") {
|
||||||
|
because("2.1.0 depends on deprecated Google collections module")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
errorprone(libs.findLibrary("errorProne-core").get())
|
errorprone(libs.findLibrary("errorProne-core").get())
|
||||||
errorprone(libs.findLibrary("nullAway").get())
|
errorprone(libs.findLibrary("nullAway").get())
|
||||||
}
|
}
|
||||||
@@ -99,11 +101,16 @@ sourceSets.all {
|
|||||||
check("FutureReturnValueIgnored", CheckSeverity.OFF) // Too many false positives with Netty
|
check("FutureReturnValueIgnored", CheckSeverity.OFF) // Too many false positives with Netty
|
||||||
|
|
||||||
check("NullAway", CheckSeverity.ERROR)
|
check("NullAway", CheckSeverity.ERROR)
|
||||||
option("NullAway:AnnotatedPackages", listOf("dan200.computercraft", "net.fabricmc.fabric.api").joinToString(","))
|
option(
|
||||||
|
"NullAway:AnnotatedPackages",
|
||||||
|
listOf("dan200.computercraft", "cc.tweaked", "net.fabricmc.fabric.api").joinToString(","),
|
||||||
|
)
|
||||||
option("NullAway:ExcludedFieldAnnotations", listOf("org.spongepowered.asm.mixin.Shadow").joinToString(","))
|
option("NullAway:ExcludedFieldAnnotations", listOf("org.spongepowered.asm.mixin.Shadow").joinToString(","))
|
||||||
option("NullAway:CastToNonNullMethod", "dan200.computercraft.core.util.Nullability.assertNonNull")
|
option("NullAway:CastToNonNullMethod", "dan200.computercraft.core.util.Nullability.assertNonNull")
|
||||||
option("NullAway:CheckOptionalEmptiness")
|
option("NullAway:CheckOptionalEmptiness")
|
||||||
option("NullAway:AcknowledgeRestrictiveAnnotations")
|
option("NullAway:AcknowledgeRestrictiveAnnotations")
|
||||||
|
|
||||||
|
excludedPaths = ".*/jmh_generated/.*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -174,6 +181,12 @@ project.plugins.withType(CCTweakedPlugin::class.java) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.register("checkstyle") {
|
||||||
|
description = "Run Checkstyle on all sources"
|
||||||
|
group = LifecycleBasePlugin.VERIFICATION_GROUP
|
||||||
|
dependsOn(tasks.withType(Checkstyle::class.java))
|
||||||
|
}
|
||||||
|
|
||||||
spotless {
|
spotless {
|
||||||
encoding = StandardCharsets.UTF_8
|
encoding = StandardCharsets.UTF_8
|
||||||
lineEndings = LineEnding.UNIX
|
lineEndings = LineEnding.UNIX
|
||||||
@@ -192,6 +205,7 @@ spotless {
|
|||||||
val ktlintConfig = mapOf(
|
val ktlintConfig = mapOf(
|
||||||
"ktlint_standard_no-wildcard-imports" to "disabled",
|
"ktlint_standard_no-wildcard-imports" to "disabled",
|
||||||
"ktlint_standard_class-naming" to "disabled",
|
"ktlint_standard_class-naming" to "disabled",
|
||||||
|
"ktlint_standard_function-naming" to "disabled",
|
||||||
"ij_kotlin_allow_trailing_comma" to "true",
|
"ij_kotlin_allow_trailing_comma" to "true",
|
||||||
"ij_kotlin_allow_trailing_comma_on_call_site" to "true",
|
"ij_kotlin_allow_trailing_comma_on_call_site" to "true",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ val publishCurseForge by tasks.registering(TaskPublishCurseForge::class) {
|
|||||||
apiToken = findProperty("curseForgeApiKey") ?: ""
|
apiToken = findProperty("curseForgeApiKey") ?: ""
|
||||||
enabled = apiToken != ""
|
enabled = apiToken != ""
|
||||||
|
|
||||||
val mainFile = upload("282001", modPublishing.output.get().archiveFile)
|
val mainFile = upload("282001", modPublishing.output)
|
||||||
mainFile.changelog =
|
mainFile.changelog =
|
||||||
"Release notes can be found on the [GitHub repository](https://github.com/cc-tweaked/CC-Tweaked/releases/tag/v$mcVersion-$modVersion)."
|
"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.changelogType = "markdown"
|
||||||
|
|||||||
@@ -10,25 +10,31 @@ import cc.tweaked.gradle.MinecraftConfigurations
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("cc-tweaked.java-convention")
|
id("cc-tweaked.java-convention")
|
||||||
id("org.spongepowered.gradle.vanilla")
|
id("cc.tweaked.vanilla-extract")
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins.apply(CCTweakedPlugin::class.java)
|
plugins.apply(CCTweakedPlugin::class.java)
|
||||||
|
|
||||||
val mcVersion: String by extra
|
val mcVersion: String by extra
|
||||||
|
|
||||||
|
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
|
||||||
|
|
||||||
minecraft {
|
minecraft {
|
||||||
version(mcVersion)
|
version(mcVersion)
|
||||||
|
|
||||||
|
mappings {
|
||||||
|
parchment(libs.findVersion("parchmentMc").get().toString(), libs.findVersion("parchment").get().toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
unpick(libs.findLibrary("yarn").get())
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
|
|
||||||
|
|
||||||
// Depend on error prone annotations to silence a lot of compile warnings.
|
// Depend on error prone annotations to silence a lot of compile warnings.
|
||||||
compileOnlyApi(libs.findLibrary("errorProne.annotations").get())
|
compileOnly(libs.findLibrary("errorProne.annotations").get())
|
||||||
}
|
}
|
||||||
|
|
||||||
MinecraftConfigurations.setup(project)
|
MinecraftConfigurations.setupBasic(project)
|
||||||
|
|
||||||
extensions.configure(CCTweakedExtension::class.java) {
|
extensions.configure(CCTweakedExtension::class.java) {
|
||||||
linters(minecraft = true, loader = null)
|
linters(minecraft = true, loader = null)
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ import org.gradle.api.GradleException
|
|||||||
import org.gradle.api.NamedDomainObjectProvider
|
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.attributes.TestSuiteType
|
import org.gradle.api.attributes.TestSuiteType
|
||||||
import org.gradle.api.file.FileSystemOperations
|
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.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.reporting.ReportingExtension
|
||||||
@@ -73,11 +75,17 @@ abstract class CCTweakedExtension(
|
|||||||
*/
|
*/
|
||||||
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() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,7 +181,7 @@ abstract class CCTweakedExtension(
|
|||||||
}
|
}
|
||||||
|
|
||||||
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.buildDir.resolve("jacocoClassDump/${task.name}")
|
val classDump = project.layout.buildDirectory.dir("jacocoClassDump/${task.name}")
|
||||||
val reportTaskName = "jacoco${task.name.capitalized()}Report"
|
val reportTaskName = "jacoco${task.name.capitalized()}Report"
|
||||||
|
|
||||||
val jacoco = project.extensions.getByType(JacocoPluginExtension::class.java)
|
val jacoco = project.extensions.getByType(JacocoPluginExtension::class.java)
|
||||||
@@ -185,7 +193,7 @@ abstract class CCTweakedExtension(
|
|||||||
jacoco.applyTo(this)
|
jacoco.applyTo(this)
|
||||||
extensions.configure(JacocoTaskExtension::class.java) {
|
extensions.configure(JacocoTaskExtension::class.java) {
|
||||||
includes = listOf("dan200.computercraft.*")
|
includes = listOf("dan200.computercraft.*")
|
||||||
classDumpDir = classDump
|
classDumpDir = classDump.get().asFile
|
||||||
|
|
||||||
// Older versions of modlauncher don't include a protection domain (and thus no code
|
// 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.
|
// source). Jacoco skips such classes by default, so we need to explicitly include them.
|
||||||
@@ -246,6 +254,20 @@ abstract class CCTweakedExtension(
|
|||||||
).resolve().single()
|
).resolve().single()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exclude a dependency from being published in Maven.
|
||||||
|
*/
|
||||||
|
fun exclude(dep: Dependency) {
|
||||||
|
excludedDeps.add(dep)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure a [MavenDependencySpec].
|
||||||
|
*/
|
||||||
|
fun configureExcludes(spec: MavenDependencySpec) {
|
||||||
|
for (dep in excludedDeps.get()) spec.exclude(dep)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
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(
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ import org.gradle.api.Project
|
|||||||
import org.gradle.api.plugins.JavaPlugin
|
import org.gradle.api.plugins.JavaPlugin
|
||||||
import org.gradle.api.plugins.JavaPluginExtension
|
import org.gradle.api.plugins.JavaPluginExtension
|
||||||
import org.gradle.jvm.toolchain.JavaLanguageVersion
|
import org.gradle.jvm.toolchain.JavaLanguageVersion
|
||||||
|
import org.gradle.plugins.ide.idea.model.IdeaModel
|
||||||
|
import org.jetbrains.gradle.ext.IdeaExtPlugin
|
||||||
|
import org.jetbrains.gradle.ext.runConfigurations
|
||||||
|
import org.jetbrains.gradle.ext.settings
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures projects to match a shared configuration.
|
* Configures projects to match a shared configuration.
|
||||||
@@ -21,6 +25,20 @@ class CCTweakedPlugin : Plugin<Project> {
|
|||||||
val sourceSets = project.extensions.getByType(JavaPluginExtension::class.java).sourceSets
|
val sourceSets = project.extensions.getByType(JavaPluginExtension::class.java).sourceSets
|
||||||
cct.sourceDirectories.add(SourceSetReference.internal(sourceSets.getByName("main")))
|
cct.sourceDirectories.add(SourceSetReference.internal(sourceSets.getByName("main")))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
project.plugins.withType(IdeaExtPlugin::class.java) { extendIdea(project) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extend the [IdeaExtPlugin] plugin's `runConfiguration` container to also support [JUnitExt].
|
||||||
|
*/
|
||||||
|
private fun extendIdea(project: Project) {
|
||||||
|
val ideaModel = project.extensions.findByName("idea") as IdeaModel? ?: return
|
||||||
|
val ideaProject = ideaModel.project ?: return
|
||||||
|
|
||||||
|
ideaProject.settings.runConfigurations {
|
||||||
|
registerFactory(JUnitExt::class.java) { name -> project.objects.newInstance(JUnitExt::class.java, name) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package cc.tweaked.gradle
|
||||||
|
|
||||||
|
import org.gradle.api.DefaultTask
|
||||||
|
import org.gradle.api.GradleException
|
||||||
|
import org.gradle.api.artifacts.Configuration
|
||||||
|
import org.gradle.api.artifacts.MinimalExternalModuleDependency
|
||||||
|
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
|
||||||
|
import org.gradle.api.artifacts.component.ModuleComponentSelector
|
||||||
|
import org.gradle.api.artifacts.component.ProjectComponentIdentifier
|
||||||
|
import org.gradle.api.artifacts.result.DependencyResult
|
||||||
|
import org.gradle.api.artifacts.result.ResolvedDependencyResult
|
||||||
|
import org.gradle.api.provider.ListProperty
|
||||||
|
import org.gradle.api.provider.MapProperty
|
||||||
|
import org.gradle.api.provider.Provider
|
||||||
|
import org.gradle.api.tasks.Input
|
||||||
|
import org.gradle.api.tasks.TaskAction
|
||||||
|
import org.gradle.language.base.plugins.LifecycleBasePlugin
|
||||||
|
|
||||||
|
abstract class DependencyCheck : DefaultTask() {
|
||||||
|
@get:Input
|
||||||
|
abstract val configuration: ListProperty<Configuration>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A mapping of module coordinates (`group:module`) to versions, overriding the requested version.
|
||||||
|
*/
|
||||||
|
@get:Input
|
||||||
|
abstract val overrides: MapProperty<String, String>
|
||||||
|
|
||||||
|
init {
|
||||||
|
description = "Check :core's dependencies are consistent with Minecraft's."
|
||||||
|
group = LifecycleBasePlugin.VERIFICATION_GROUP
|
||||||
|
|
||||||
|
configuration.finalizeValueOnRead()
|
||||||
|
overrides.finalizeValueOnRead()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override a module with a different version.
|
||||||
|
*/
|
||||||
|
fun override(module: Provider<MinimalExternalModuleDependency>, version: String) {
|
||||||
|
overrides.putAll(project.provider { mutableMapOf(module.get().module.toString() to version) })
|
||||||
|
}
|
||||||
|
|
||||||
|
@TaskAction
|
||||||
|
fun run() {
|
||||||
|
var ok = true
|
||||||
|
for (configuration in configuration.get()) {
|
||||||
|
configuration.incoming.resolutionResult.allDependencies {
|
||||||
|
if (!check(this@allDependencies)) ok = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ok) {
|
||||||
|
throw GradleException("Mismatched versions in Minecraft dependencies. gradle/libs.versions.toml may need updating.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun check(dependency: DependencyResult): Boolean {
|
||||||
|
if (dependency !is ResolvedDependencyResult) {
|
||||||
|
logger.warn("Found unexpected dependency result {}", dependency)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip dependencies on non-modules.
|
||||||
|
val requested = dependency.requested
|
||||||
|
if (requested !is ModuleComponentSelector) return true
|
||||||
|
|
||||||
|
// If this dependency is specified within some project (so is non-transitive), or is pulled in via Minecraft,
|
||||||
|
// then check for consistency.
|
||||||
|
// It would be nice to be smarter about transitive dependencies, but avoiding false positives is hard.
|
||||||
|
val from = dependency.from.id
|
||||||
|
if (
|
||||||
|
from is ProjectComponentIdentifier ||
|
||||||
|
from is ModuleComponentIdentifier && (from.group == "net.minecraft" || from.group == "io.netty")
|
||||||
|
) {
|
||||||
|
// If the version is different between the requested and selected version, report an error.
|
||||||
|
val selected = dependency.selected.moduleVersion!!.version
|
||||||
|
val requestedVersion = overrides.get()["${requested.group}:${requested.module}"] ?: requested.version
|
||||||
|
if (requestedVersion != selected) {
|
||||||
|
logger.error("Requested dependency {} (via {}) but got version {}", requested, from, selected)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
package cc.tweaked.gradle
|
package cc.tweaked.gradle
|
||||||
|
|
||||||
|
import org.gradle.api.file.DirectoryProperty
|
||||||
import org.gradle.api.provider.Property
|
import org.gradle.api.provider.Property
|
||||||
import org.gradle.api.tasks.AbstractExecTask
|
import org.gradle.api.tasks.AbstractExecTask
|
||||||
import org.gradle.api.tasks.OutputDirectory
|
import org.gradle.api.tasks.OutputDirectory
|
||||||
@@ -11,5 +12,5 @@ import java.io.File
|
|||||||
|
|
||||||
abstract class ExecToDir : AbstractExecTask<ExecToDir>(ExecToDir::class.java) {
|
abstract class ExecToDir : AbstractExecTask<ExecToDir>(ExecToDir::class.java) {
|
||||||
@get:OutputDirectory
|
@get:OutputDirectory
|
||||||
abstract val output: Property<File>
|
abstract val output: DirectoryProperty
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
package cc.tweaked.gradle
|
package cc.tweaked.gradle
|
||||||
|
|
||||||
import org.gradle.api.artifacts.dsl.DependencyHandler
|
import org.gradle.api.artifacts.dsl.DependencyHandler
|
||||||
|
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
|
||||||
@@ -124,3 +125,33 @@ class CloseScope : AutoCloseable {
|
|||||||
|
|
||||||
/** Proxy method to avoid overload ambiguity. */
|
/** Proxy method to avoid overload ambiguity. */
|
||||||
fun <T> Property<T>.setProvider(provider: Provider<out T>) = set(provider)
|
fun <T> Property<T>.setProvider(provider: Provider<out T>) = set(provider)
|
||||||
|
|
||||||
|
/** Short-cut method to get the absolute path of a [FileSystemLocation] provider. */
|
||||||
|
fun Provider<out FileSystemLocation>.getAbsolutePath(): String = get().asFile.absolutePath
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the version immediately after the provided version.
|
||||||
|
*
|
||||||
|
* For example, given "1.2.3", this will return "1.2.4".
|
||||||
|
*/
|
||||||
|
fun getNextVersion(version: String): String {
|
||||||
|
// Split a version like x.y.z-SNAPSHOT into x.y.z and -SNAPSHOT
|
||||||
|
val dashIndex = version.indexOf('-')
|
||||||
|
val mainVersion = if (dashIndex < 0) version else version.substring(0, dashIndex)
|
||||||
|
|
||||||
|
// Find the last component in x.y.z and increment it.
|
||||||
|
val lastIndex = mainVersion.lastIndexOf('.')
|
||||||
|
if (lastIndex < 0) throw IllegalArgumentException("Cannot parse version format \"$version\"")
|
||||||
|
val lastVersion = try {
|
||||||
|
version.substring(lastIndex + 1).toInt()
|
||||||
|
} catch (e: NumberFormatException) {
|
||||||
|
throw IllegalArgumentException("Cannot parse version format \"$version\"", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then append all components together.
|
||||||
|
val out = StringBuilder()
|
||||||
|
out.append(version, 0, lastIndex + 1)
|
||||||
|
out.append(lastVersion + 1)
|
||||||
|
if (dashIndex >= 0) out.append(version, dashIndex, version.length)
|
||||||
|
return out.toString()
|
||||||
|
}
|
||||||
|
|||||||
23
buildSrc/src/main/kotlin/cc/tweaked/gradle/IdeaExt.kt
Normal file
23
buildSrc/src/main/kotlin/cc/tweaked/gradle/IdeaExt.kt
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package cc.tweaked.gradle
|
||||||
|
|
||||||
|
import org.jetbrains.gradle.ext.JUnit
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A version of [JUnit] with a functional [className].
|
||||||
|
*
|
||||||
|
* See [#92](https://github.com/JetBrains/gradle-idea-ext-plugin/issues/92).
|
||||||
|
*/
|
||||||
|
open class JUnitExt @Inject constructor(nameParam: String) : JUnit(nameParam) {
|
||||||
|
override fun toMap(): MutableMap<String, *> {
|
||||||
|
val map = HashMap(super.toMap())
|
||||||
|
// Should be "class" instead of "className".
|
||||||
|
// See https://github.com/JetBrains/intellij-community/blob/9ba394021dc73a3926f13d6d6cdf434f9ee7046d/plugins/junit/src/com/intellij/execution/junit/JUnitRunConfigurationImporter.kt#L39
|
||||||
|
map["class"] = className
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,8 @@ package cc.tweaked.gradle
|
|||||||
|
|
||||||
import org.gradle.api.artifacts.Dependency
|
import org.gradle.api.artifacts.Dependency
|
||||||
import org.gradle.api.artifacts.MinimalExternalModuleDependency
|
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.publish.maven.MavenPublication
|
||||||
import org.gradle.api.specs.Spec
|
import org.gradle.api.specs.Spec
|
||||||
|
|
||||||
@@ -26,8 +28,13 @@ class MavenDependencySpec {
|
|||||||
|
|
||||||
fun exclude(dep: Dependency) {
|
fun exclude(dep: Dependency) {
|
||||||
exclude {
|
exclude {
|
||||||
|
// We have to cheat a little for project dependencies, as the project name doesn't match the artifact group.
|
||||||
|
val name = when (dep) {
|
||||||
|
is ProjectDependency -> dep.dependencyProject.extensions.getByType(BasePluginExtension::class.java).archivesName.get()
|
||||||
|
else -> dep.name
|
||||||
|
}
|
||||||
(dep.group.isNullOrEmpty() || dep.group == it.groupId) &&
|
(dep.group.isNullOrEmpty() || dep.group == it.groupId) &&
|
||||||
(dep.name.isNullOrEmpty() || dep.name == it.artifactId) &&
|
(name.isNullOrEmpty() || name == it.artifactId) &&
|
||||||
(dep.version.isNullOrEmpty() || dep.version == it.version)
|
(dep.version.isNullOrEmpty() || dep.version == it.version)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,23 +4,17 @@
|
|||||||
|
|
||||||
package cc.tweaked.gradle
|
package cc.tweaked.gradle
|
||||||
|
|
||||||
|
import cc.tweaked.vanillaextract.configurations.Capabilities
|
||||||
|
import cc.tweaked.vanillaextract.configurations.MinecraftSetup
|
||||||
import org.gradle.api.Project
|
import org.gradle.api.Project
|
||||||
import org.gradle.api.artifacts.Configuration
|
|
||||||
import org.gradle.api.artifacts.ModuleDependency
|
import org.gradle.api.artifacts.ModuleDependency
|
||||||
import org.gradle.api.artifacts.dsl.DependencyHandler
|
import org.gradle.api.artifacts.dsl.DependencyHandler
|
||||||
import org.gradle.api.attributes.Bundling
|
|
||||||
import org.gradle.api.attributes.Category
|
|
||||||
import org.gradle.api.attributes.LibraryElements
|
|
||||||
import org.gradle.api.attributes.Usage
|
|
||||||
import org.gradle.api.attributes.java.TargetJvmVersion
|
|
||||||
import org.gradle.api.capabilities.Capability
|
|
||||||
import org.gradle.api.plugins.BasePlugin
|
import org.gradle.api.plugins.BasePlugin
|
||||||
import org.gradle.api.plugins.JavaPluginExtension
|
import org.gradle.api.plugins.JavaPluginExtension
|
||||||
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.javadoc.Javadoc
|
import org.gradle.api.tasks.javadoc.Javadoc
|
||||||
import org.gradle.kotlin.dsl.get
|
import org.gradle.kotlin.dsl.get
|
||||||
import org.gradle.kotlin.dsl.named
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This sets up a separate client-only source set, and extends that and the main/common source set with additional
|
* This sets up a separate client-only source set, and extends that and the main/common source set with additional
|
||||||
@@ -59,31 +53,13 @@ class MinecraftConfigurations private constructor(private val project: Project)
|
|||||||
}
|
}
|
||||||
configurations.named(client.implementationConfigurationName) { extendsFrom(clientApi) }
|
configurations.named(client.implementationConfigurationName) { extendsFrom(clientApi) }
|
||||||
|
|
||||||
/*
|
|
||||||
Now add outgoing variants for the main and common source sets that we can consume downstream. This is possibly
|
|
||||||
the worst way to do things, but unfortunately the alternatives don't actually work very well:
|
|
||||||
|
|
||||||
- Just using source set outputs: This means dependencies don't propagate, which means when :fabric depends
|
|
||||||
on :fabric-api, we don't inherit the fake :common-api in IDEA.
|
|
||||||
|
|
||||||
- Having separate common/main jars: Nice in principle, but unfortunately Forge needs a separate deobf jar
|
|
||||||
task (as the original jar is obfuscated), and IDEA is not able to map its output back to a source set.
|
|
||||||
|
|
||||||
This works for now, but is incredibly brittle. It's part of the reason we can't use testFixtures inside our
|
|
||||||
MC projects, as that adds a project(self) -> test dependency, which would pull in the jar instead.
|
|
||||||
|
|
||||||
Note we register a fake client jar here. It's not actually needed, but is there to make sure IDEA has
|
|
||||||
a way to tell that client classes are needed at runtime.
|
|
||||||
|
|
||||||
I'm so sorry, deeply aware how cursed this is.
|
|
||||||
*/
|
|
||||||
setupOutgoing(main, "CommonOnly")
|
|
||||||
project.tasks.register(client.jarTaskName, Jar::class.java) {
|
project.tasks.register(client.jarTaskName, Jar::class.java) {
|
||||||
description = "An empty jar standing in for the client classes."
|
description = "An empty jar standing in for the client classes."
|
||||||
group = BasePlugin.BUILD_GROUP
|
group = BasePlugin.BUILD_GROUP
|
||||||
archiveClassifier.set("client")
|
archiveClassifier.set("client")
|
||||||
}
|
}
|
||||||
setupOutgoing(client)
|
|
||||||
|
MinecraftSetup(project).setupOutgoingConfigurations()
|
||||||
|
|
||||||
// Reset the client classpath (Loom configures it slightly differently to this) and add a main -> client
|
// Reset the client classpath (Loom configures it slightly differently to this) and add a main -> client
|
||||||
// dependency. Here we /can/ use source set outputs as we add transitive deps by patching the classpath. Nasty,
|
// dependency. Here we /can/ use source set outputs as we add transitive deps by patching the classpath. Nasty,
|
||||||
@@ -106,88 +82,39 @@ class MinecraftConfigurations private constructor(private val project: Project)
|
|||||||
project.tasks.named("jar", Jar::class.java) { from(client.output) }
|
project.tasks.named("jar", Jar::class.java) { from(client.output) }
|
||||||
project.tasks.named("sourcesJar", Jar::class.java) { from(client.allSource) }
|
project.tasks.named("sourcesJar", Jar::class.java) { from(client.allSource) }
|
||||||
|
|
||||||
|
setupBasic()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupBasic() {
|
||||||
|
val client = sourceSets["client"]
|
||||||
|
|
||||||
project.extensions.configure(CCTweakedExtension::class.java) {
|
project.extensions.configure(CCTweakedExtension::class.java) {
|
||||||
sourceDirectories.add(SourceSetReference.internal(client))
|
sourceDirectories.add(SourceSetReference.internal(client))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun setupOutgoing(sourceSet: SourceSet, suffix: String = "") {
|
// Register a task to check there are no conflicts with the core project.
|
||||||
setupOutgoing("${sourceSet.apiElementsConfigurationName}$suffix", sourceSet, objects.named(Usage.JAVA_API)) {
|
val checkDependencyConsistency =
|
||||||
description = "API elements for ${sourceSet.name}"
|
project.tasks.register("checkDependencyConsistency", DependencyCheck::class.java) {
|
||||||
extendsFrom(configurations[sourceSet.apiConfigurationName])
|
// We need to check both the main and client classpath *configurations*, as the actual configuration
|
||||||
}
|
configuration.add(configurations.named(main.runtimeClasspathConfigurationName))
|
||||||
|
configuration.add(configurations.named(client.runtimeClasspathConfigurationName))
|
||||||
setupOutgoing("${sourceSet.runtimeElementsConfigurationName}$suffix", sourceSet, objects.named(Usage.JAVA_RUNTIME)) {
|
|
||||||
description = "Runtime elements for ${sourceSet.name}"
|
|
||||||
extendsFrom(configurations[sourceSet.implementationConfigurationName], configurations[sourceSet.runtimeOnlyConfigurationName])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set up an outgoing configuration for a specific source set. We set an additional "main" or "client" capability
|
|
||||||
* (depending on the source set name) which allows downstream projects to consume them separately (see
|
|
||||||
* [DependencyHandler.commonClasses] and [DependencyHandler.clientClasses]).
|
|
||||||
*/
|
|
||||||
private fun setupOutgoing(name: String, sourceSet: SourceSet, usage: Usage, configure: Configuration.() -> Unit) {
|
|
||||||
configurations.register(name) {
|
|
||||||
isVisible = false
|
|
||||||
isCanBeConsumed = true
|
|
||||||
isCanBeResolved = false
|
|
||||||
|
|
||||||
configure(this)
|
|
||||||
|
|
||||||
attributes {
|
|
||||||
attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.LIBRARY))
|
|
||||||
attribute(Usage.USAGE_ATTRIBUTE, usage)
|
|
||||||
attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EXTERNAL))
|
|
||||||
attributeProvider(
|
|
||||||
TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE,
|
|
||||||
java.toolchain.languageVersion.map { it.asInt() },
|
|
||||||
)
|
|
||||||
attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.JAR))
|
|
||||||
}
|
}
|
||||||
|
project.tasks.named("check") { dependsOn(checkDependencyConsistency) }
|
||||||
outgoing {
|
|
||||||
capability(BasicOutgoingCapability(project, sourceSet.name))
|
|
||||||
|
|
||||||
// We have two outgoing variants here: the original jar and the classes.
|
|
||||||
artifact(project.tasks.named(sourceSet.jarTaskName))
|
|
||||||
|
|
||||||
variants.create("classes") {
|
|
||||||
attributes.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.CLASSES))
|
|
||||||
sourceSet.output.classesDirs.forEach { artifact(it) { builtBy(sourceSet.output) } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
fun setupBasic(project: Project) {
|
||||||
|
MinecraftConfigurations(project).setupBasic()
|
||||||
|
}
|
||||||
|
|
||||||
fun setup(project: Project) {
|
fun setup(project: Project) {
|
||||||
MinecraftConfigurations(project).setup()
|
MinecraftConfigurations(project).setup()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class BasicIncomingCapability(private val module: ModuleDependency, private val name: String) : Capability {
|
fun DependencyHandler.clientClasses(notation: Any): ModuleDependency =
|
||||||
override fun getGroup(): String = module.group!!
|
Capabilities.clientClasses(create(notation) as ModuleDependency)
|
||||||
override fun getName(): String = "${module.name}-$name"
|
|
||||||
override fun getVersion(): String? = null
|
|
||||||
}
|
|
||||||
|
|
||||||
private class BasicOutgoingCapability(private val project: Project, private val name: String) : Capability {
|
fun DependencyHandler.commonClasses(notation: Any): ModuleDependency =
|
||||||
override fun getGroup(): String = project.group.toString()
|
Capabilities.commonClasses(create(notation) as ModuleDependency)
|
||||||
override fun getName(): String = "${project.name}-$name"
|
|
||||||
override fun getVersion(): String = project.version.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun DependencyHandler.clientClasses(notation: Any): ModuleDependency {
|
|
||||||
val dep = create(notation) as ModuleDependency
|
|
||||||
dep.capabilities { requireCapability(BasicIncomingCapability(dep, "client")) }
|
|
||||||
return dep
|
|
||||||
}
|
|
||||||
|
|
||||||
fun DependencyHandler.commonClasses(notation: Any): ModuleDependency {
|
|
||||||
val dep = create(notation) as ModuleDependency
|
|
||||||
dep.capabilities { requireCapability(BasicIncomingCapability(dep, "main")) }
|
|
||||||
return dep
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ abstract class ClientJavaExec : JavaExec() {
|
|||||||
if (!clientDebug) systemProperty("cctest.client", "")
|
if (!clientDebug) systemProperty("cctest.client", "")
|
||||||
if (renderdoc) environment("LD_PRELOAD", "/usr/lib/librenderdoc.so")
|
if (renderdoc) environment("LD_PRELOAD", "/usr/lib/librenderdoc.so")
|
||||||
systemProperty("cctest.gametest-report", testResults.get().asFile.absoluteFile)
|
systemProperty("cctest.gametest-report", testResults.get().asFile.absoluteFile)
|
||||||
workingDir(project.buildDir.resolve("gametest").resolve(name))
|
workingDir(project.layout.buildDirectory.dir("gametest/$name"))
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
|||||||
@@ -13,8 +13,12 @@ SPDX-License-Identifier: MPL-2.0
|
|||||||
<property name="tabWidth" value="4"/>
|
<property name="tabWidth" value="4"/>
|
||||||
<property name="charset" value="UTF-8" />
|
<property name="charset" value="UTF-8" />
|
||||||
|
|
||||||
|
<module name="BeforeExecutionExclusionFileFilter">
|
||||||
|
<property name="fileNamePattern" value="module\-info\.java$"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
<module name="SuppressionFilter">
|
<module name="SuppressionFilter">
|
||||||
<property name="file" value="${config_loc}/suppressions.xml" />
|
<property name="file" value="${config_loc}/suppressions.xml" />
|
||||||
</module>
|
</module>
|
||||||
|
|
||||||
<module name="BeforeExecutionExclusionFileFilter">
|
<module name="BeforeExecutionExclusionFileFilter">
|
||||||
@@ -112,7 +116,9 @@ SPDX-License-Identifier: MPL-2.0
|
|||||||
<module name="LambdaParameterName" />
|
<module name="LambdaParameterName" />
|
||||||
<module name="LocalFinalVariableName" />
|
<module name="LocalFinalVariableName" />
|
||||||
<module name="LocalVariableName" />
|
<module name="LocalVariableName" />
|
||||||
<module name="MemberName" />
|
<module name="MemberName">
|
||||||
|
<property name="format" value="^\$?[a-z][a-zA-Z0-9]*$" />
|
||||||
|
</module>
|
||||||
<module name="MethodName">
|
<module name="MethodName">
|
||||||
<property name="format" value="^(computercraft\$)?[a-z][a-zA-Z0-9]*$" />
|
<property name="format" value="^(computercraft\$)?[a-z][a-zA-Z0-9]*$" />
|
||||||
</module>
|
</module>
|
||||||
@@ -122,7 +128,7 @@ SPDX-License-Identifier: MPL-2.0
|
|||||||
</module>
|
</module>
|
||||||
<module name="ParameterName" />
|
<module name="ParameterName" />
|
||||||
<module name="StaticVariableName">
|
<module name="StaticVariableName">
|
||||||
<property name="format" value="^[a-z][a-zA-Z0-9]*|CAPABILITY(_[A-Z_]+)?$" />
|
<property name="format" value="^[a-z][a-zA-Z0-9]*$" />
|
||||||
</module>
|
</module>
|
||||||
<module name="TypeName" />
|
<module name="TypeName" />
|
||||||
|
|
||||||
|
|||||||
@@ -16,4 +16,10 @@ SPDX-License-Identifier: MPL-2.0
|
|||||||
|
|
||||||
<!-- The commands API is documented in Lua. -->
|
<!-- The commands API is documented in Lua. -->
|
||||||
<suppress checks="SummaryJavadocCheck" files=".*[\\/]CommandAPI.java" />
|
<suppress checks="SummaryJavadocCheck" files=".*[\\/]CommandAPI.java" />
|
||||||
|
|
||||||
|
<!-- Allow putting files in other packages if they look like our TeaVM stubs. -->
|
||||||
|
<suppress checks="PackageName" files=".*[\\/]T[A-Za-z]+.java" />
|
||||||
|
|
||||||
|
<!-- Allow underscores in our test classes. -->
|
||||||
|
<suppress checks="MethodName" files=".*(Contract|Test).java" />
|
||||||
</suppressions>
|
</suppressions>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ see: key To listen to any key press.
|
|||||||
<!--
|
<!--
|
||||||
SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
||||||
|
|
||||||
SPDX-License-Identifier: LicenseRef-CCPL
|
SPDX-License-Identifier: MPL-2.0
|
||||||
-->
|
-->
|
||||||
|
|
||||||
The [`char`] event is fired when a character is typed on the keyboard.
|
The [`char`] event is fired when a character is typed on the keyboard.
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ SPDX-License-Identifier: MPL-2.0
|
|||||||
The [`file_transfer`] event is queued when a user drags-and-drops a file on an open computer.
|
The [`file_transfer`] event is queued when a user drags-and-drops a file on an open computer.
|
||||||
|
|
||||||
This event contains a single argument of type [`TransferredFiles`], which can be used to [get the files to be
|
This event contains a single argument of type [`TransferredFiles`], which can be used to [get the files to be
|
||||||
transferred][`TransferredFiles.getFiles`]. Each file returned is a [binary file handle][`fs.BinaryReadHandle`] with an
|
transferred][`TransferredFiles.getFiles`]. Each file returned is a [binary file handle][`fs.ReadHandle`] with an
|
||||||
additional [getName][`TransferredFile.getName`] method.
|
additional [getName][`TransferredFile.getName`] method.
|
||||||
|
|
||||||
## Return values
|
## Return values
|
||||||
@@ -29,7 +29,7 @@ for _, file in ipairs(files.getFiles()) do
|
|||||||
local size = file.seek("end")
|
local size = file.seek("end")
|
||||||
file.seek("set", 0)
|
file.seek("set", 0)
|
||||||
|
|
||||||
print(file.getName() .. " " .. file.getSize())
|
print(file.getName() .. " " .. size)
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ module: [kind=event] key
|
|||||||
<!--
|
<!--
|
||||||
SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
||||||
|
|
||||||
SPDX-License-Identifier: LicenseRef-CCPL
|
SPDX-License-Identifier: MPL-2.0
|
||||||
-->
|
-->
|
||||||
|
|
||||||
This event is fired when any key is pressed while the terminal is focused.
|
This event is fired when any key is pressed while the terminal is focused.
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ see: keys For a lookup table of the given keys.
|
|||||||
<!--
|
<!--
|
||||||
SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
||||||
|
|
||||||
SPDX-License-Identifier: LicenseRef-CCPL
|
SPDX-License-Identifier: MPL-2.0
|
||||||
-->
|
-->
|
||||||
|
|
||||||
Fired whenever a key is released (or the terminal is closed while a key was being pressed).
|
Fired whenever a key is released (or the terminal is closed while a key was being pressed).
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ module: [kind=event] mouse_click
|
|||||||
<!--
|
<!--
|
||||||
SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
||||||
|
|
||||||
SPDX-License-Identifier: LicenseRef-CCPL
|
SPDX-License-Identifier: MPL-2.0
|
||||||
-->
|
-->
|
||||||
|
|
||||||
This event is fired when the terminal is clicked with a mouse. This event is only fired on advanced computers (including
|
This event is fired when the terminal is clicked with a mouse. This event is only fired on advanced computers (including
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ see: mouse_click For when a mouse button is initially pressed.
|
|||||||
<!--
|
<!--
|
||||||
SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
||||||
|
|
||||||
SPDX-License-Identifier: LicenseRef-CCPL
|
SPDX-License-Identifier: MPL-2.0
|
||||||
-->
|
-->
|
||||||
|
|
||||||
This event is fired every time the mouse is moved while a mouse button is being held.
|
This event is fired every time the mouse is moved while a mouse button is being held.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ module: [kind=event] mouse_scroll
|
|||||||
<!--
|
<!--
|
||||||
SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
||||||
|
|
||||||
SPDX-License-Identifier: LicenseRef-CCPL
|
SPDX-License-Identifier: MPL-2.0
|
||||||
-->
|
-->
|
||||||
|
|
||||||
This event is fired when a mouse wheel is scrolled in the terminal.
|
This event is fired when a mouse wheel is scrolled in the terminal.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ module: [kind=event] mouse_up
|
|||||||
<!--
|
<!--
|
||||||
SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
||||||
|
|
||||||
SPDX-License-Identifier: LicenseRef-CCPL
|
SPDX-License-Identifier: MPL-2.0
|
||||||
-->
|
-->
|
||||||
|
|
||||||
This event is fired when a mouse button is released or a held mouse leaves the computer's terminal.
|
This event is fired when a mouse button is released or a held mouse leaves the computer's terminal.
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ In order to give the best results, a GPS constellation needs at least four compu
|
|||||||
constellation is redundant, but it does not cause problems.
|
constellation is redundant, but it does not cause problems.
|
||||||
|
|
||||||
## Building a GPS constellation
|
## Building a GPS constellation
|
||||||
<img alt="An example GPS constellation." src="/images/gps-constellation-example.png" class="big-image" />
|
<img alt="An example GPS constellation." src="../images/gps-constellation-example.png" class="big-image" />
|
||||||
|
|
||||||
We are going to build our GPS constellation as shown in the image above. You will need 4 computers and either 4 wireless
|
We are going to build our GPS constellation as shown in the image above. You will need 4 computers and either 4 wireless
|
||||||
modems or 4 ender modems. Try not to mix ender and wireless modems together as you might get some odd behavior when your
|
modems or 4 ender modems. Try not to mix ender and wireless modems together as you might get some odd behavior when your
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ accepts blocks of DFPWM data and converts it to a list of 8-bit amplitudes, whic
|
|||||||
As mentioned above, [`speaker.playAudio`] accepts at most 128×1024 samples in one go. DFPMW uses a single bit for each
|
As mentioned above, [`speaker.playAudio`] accepts at most 128×1024 samples in one go. DFPMW uses a single bit for each
|
||||||
sample, which means we want to process our audio in chunks of 16×1024 bytes (16KiB). In order to do this, we use
|
sample, which means we want to process our audio in chunks of 16×1024 bytes (16KiB). In order to do this, we use
|
||||||
[`io.lines`], which provides a nice way to loop over chunks of a file. You can of course just use [`fs.open`] and
|
[`io.lines`], which provides a nice way to loop over chunks of a file. You can of course just use [`fs.open`] and
|
||||||
[`fs.BinaryReadHandle.read`] if you prefer.
|
[`fs.ReadHandle.read`] if you prefer.
|
||||||
|
|
||||||
## Processing audio
|
## Processing audio
|
||||||
As mentioned near the beginning of this guide, PCM audio is pretty easy to work with as it's just a list of amplitudes.
|
As mentioned near the beginning of this guide, PCM audio is pretty easy to work with as it's just a list of amplitudes.
|
||||||
|
|||||||
BIN
doc/images/computercraft-dump.png
Normal file
BIN
doc/images/computercraft-dump.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 254 KiB |
BIN
doc/images/computercraft-track.png
Normal file
BIN
doc/images/computercraft-track.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 304 KiB |
81
doc/reference/breaking_changes.md
Normal file
81
doc/reference/breaking_changes.md
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
---
|
||||||
|
module: [kind=reference] breaking_changes
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: 2019 The CC: Tweaked Developers
|
||||||
|
|
||||||
|
SPDX-License-Identifier: MPL-2.0
|
||||||
|
-->
|
||||||
|
|
||||||
|
# Incompatibilities between versions
|
||||||
|
|
||||||
|
CC: Tweaked tries to remain as compatible between versions as possible, meaning most programs written for older version
|
||||||
|
of the mod should run fine on later versions.
|
||||||
|
|
||||||
|
> [External peripherals][!WARNING]
|
||||||
|
>
|
||||||
|
> While CC: Tweaked is relatively stable across versions, this may not be true for other mods which add their own
|
||||||
|
> peripherals. Older programs which interact with external blocks may not work on newer versions of the game.
|
||||||
|
|
||||||
|
However, some changes to the underlying game, or CC: Tweaked's own internals may break some programs. This page serves
|
||||||
|
as documentation for breaking changes and "gotchas" one should look out for between versions.
|
||||||
|
|
||||||
|
## CC: Tweaked 1.109.0 to 1.109.3 {#cct-1.109}
|
||||||
|
|
||||||
|
- Update to Lua 5.2:
|
||||||
|
- Support for Lua 5.0's pseudo-argument `arg` has been removed. You should always use `...` for varargs.
|
||||||
|
- Environments are no longer baked into the runtime, and instead use the `_ENV` local or upvalue. `getfenv`/`setfenv`
|
||||||
|
now only work on Lua functions with an `_ENV` upvalue. `getfenv` will return the global environment when called
|
||||||
|
with other functions, and `setfenv` will have no effect.
|
||||||
|
- `load`/`loadstring` defaults to using the global environment (`_G`) rather than the current coroutine's
|
||||||
|
environment.
|
||||||
|
- Support for dumping functions (`string.dump`) and loading binary chunks has been removed.
|
||||||
|
- `math.random` now uses Lua 5.4's random number generator.
|
||||||
|
|
||||||
|
- File handles, HTTP requests and websockets now always use the original bytes rather than encoding/decoding to UTF-8.
|
||||||
|
|
||||||
|
## Minecraft 1.13 {#mc-1.13}
|
||||||
|
- The "key code" for [`key`] and [`key_up`] events has changed, due to Minecraft updating to LWJGL 3. Make sure you're
|
||||||
|
using the constants provided by the [`keys`] API, rather than hard-coding numerical values.
|
||||||
|
|
||||||
|
Related to this change, the numpad enter key now has a different key code to the enter key. You may need to adjust
|
||||||
|
your programs to handle both. (Note, the `keys.numpadEnter` constant was defined in pre-1.13 versions of CC, but the
|
||||||
|
`keys.enter` constant was queued when the key was pressed)
|
||||||
|
|
||||||
|
- Minecraft 1.13 removed the concept of item damage and block metadata (see ["The Flattening"][flattening]). As a
|
||||||
|
result `turtle.inspect` no longer provides block metadata, and `turtle.getItemDetail` no longer provides damage.
|
||||||
|
|
||||||
|
- Block states (`turtle.inspect().state`) should provide all the same information as block metadata, but in a much
|
||||||
|
more understandable format.
|
||||||
|
|
||||||
|
- Item and block names now represent a unique item type. For instance, wool is split into 16 separate items
|
||||||
|
(`minecraft:white_wool`, etc...) rather than a single `minecraft:wool` with each meta/damage value specifying the
|
||||||
|
colour.
|
||||||
|
|
||||||
|
- Custom ROMs are now provided using data packs rather than resource packs. This should mostly be a matter of renaming
|
||||||
|
the "assets" folder to "data", and placing it in "datapacks", but there are a couple of other gotchas to look out
|
||||||
|
for:
|
||||||
|
|
||||||
|
- Data packs [impose some restrictions on file names][legal_data_pack]. As a result, your programs and directories
|
||||||
|
must all be lower case.
|
||||||
|
- Due to how data packs are read by CC: Tweaked, you may need to use the `/reload` command to see changes to your
|
||||||
|
pack show up on the computer.
|
||||||
|
|
||||||
|
See [the example datapack][datapack-example] for how to get started.
|
||||||
|
|
||||||
|
- Turtles can now be waterlogged and move "through" water sources rather than breaking them.
|
||||||
|
|
||||||
|
## CC: Tweaked 1.88.0 {#cc-1.88}
|
||||||
|
- Unlabelled computers and turtles now keep their ID when broken, meaning that unlabelled computers/items do not stack.
|
||||||
|
|
||||||
|
## ComputerCraft 1.80pr1 {#cc-1.80}
|
||||||
|
- Programs run via `shell.run` are now started in their own isolated environment. This means globals set by programs
|
||||||
|
will not be accessible outside of this program.
|
||||||
|
|
||||||
|
- Programs containing `/` are looked up in the current directory and are no longer looked up on the path. For instance,
|
||||||
|
you can no longer type `turtle/excavate` to run `/rom/programs/turtle/excavate.lua`.
|
||||||
|
|
||||||
|
[flattening]: https://minecraft.wiki/w/Java_Edition_1.13/Flattening
|
||||||
|
[legal_data_pack]: https://minecraft.gamepedia.com/Tutorials/Creating_a_data_pack#Legal_characters
|
||||||
|
[datapack-example]: https://github.com/cc-tweaked/datapack-example "An example datapack for CC: Tweaked"
|
||||||
140
doc/reference/command.md
Normal file
140
doc/reference/command.md
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
---
|
||||||
|
module: [kind=reference] computercraft_command
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||||
|
|
||||||
|
SPDX-License-Identifier: MPL-2.0
|
||||||
|
-->
|
||||||
|
|
||||||
|
# The `/computercraft` command
|
||||||
|
CC: Tweaked provides a `/computercraft` command for server owners to manage running computers on a server.
|
||||||
|
|
||||||
|
## Permissions {#permissions}
|
||||||
|
As the `/computercraft` command is mostly intended for debugging and administrative purposes, its sub-commands typically
|
||||||
|
require you to have op (or similar).
|
||||||
|
|
||||||
|
- All players have access to the [`queue`] sub-command.
|
||||||
|
- On a multi-player server, all other commands require op.
|
||||||
|
- On a single-player world, the player can run the [`dump`], [`turn-on`]/[`shutdown`], and [`track`] sub-commands, even
|
||||||
|
when cheats are not enabled. The [`tp`] and [`view`] commands require cheats.
|
||||||
|
|
||||||
|
If a permission mod such as [LuckPerms] is installed[^permission], you can configure access to the individual
|
||||||
|
sub-commands. Each sub-command creates a `computercraft.command.NAME` permission node to control which players can
|
||||||
|
execute it.
|
||||||
|
|
||||||
|
[LuckPerms]: https://github.com/LuckPerms/LuckPerms/ "A permissions plugin for Minecraft servers."
|
||||||
|
[fabric-permission-api]: https://github.com/lucko/fabric-permissions-api "A simple permissions API for Fabric"
|
||||||
|
|
||||||
|
[^permission]: This supports any mod which uses Forge's permission API or [fabric-permission-api].
|
||||||
|
|
||||||
|
## Computer selectors {#computer-selectors}
|
||||||
|
Some commands (such as [`tp`] or [`turn-on`]) target a specific computer, or a list of computers. To specify which
|
||||||
|
computers to operate on, you must use "computer selectors".
|
||||||
|
|
||||||
|
Computer selectors are similar to Minecraft's [entity target selectors], but targeting computers instead. They allow
|
||||||
|
you to select one or more computers, based on a set of predicates.
|
||||||
|
|
||||||
|
The following predicates are supported:
|
||||||
|
- `id=<id>`: Select computer(s) with a specific id.
|
||||||
|
- `instance=<id>`: Select the computer with the given instance id.
|
||||||
|
- `family=<normal|advanced|command>`: Select computers based on their type.
|
||||||
|
- `label=<label>`: Select computers with the given label.
|
||||||
|
- `distance=<distance>`: Select computers within a specific distance of the player executing the command. This uses
|
||||||
|
Minecraft's [float range] syntax.
|
||||||
|
|
||||||
|
`#<id>` may also be used as a shorthand for `@c[id=<id>]`, to select computer(s) with a specific id.
|
||||||
|
|
||||||
|
### Examples:
|
||||||
|
- `/computercraft turn-on #12`: Turn on the computer(s) with an id of 12.
|
||||||
|
- `/computercraft shutdown @c[distance=..100]`: Shut down all computers with 100 blocks of the player.
|
||||||
|
|
||||||
|
[entity target selectors]: https://minecraft.wiki/w/Target_selectors "Target Selectors on the Minecraft wiki"
|
||||||
|
[Float range]: https://minecraft.wiki/w/Argument_types#minecraft:float_range
|
||||||
|
|
||||||
|
## Commands {#commands}
|
||||||
|
### `/computercraft dump` {#dump}
|
||||||
|
`/computercraft dump` prints a table of currently loaded computers, including their id, position, and whether they're
|
||||||
|
running. It can also be run with a single computer argument to dump more detailed information about a computer.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Next to the computer id, there are several buttons to either [teleport][`tp`] to the computer, or [open its terminal
|
||||||
|
][`view`].
|
||||||
|
|
||||||
|
Computers are sorted by distance to the player, so nearby computers will appear earlier.
|
||||||
|
|
||||||
|
### `/computercraft turn-on [computers...]` {#turn-on}
|
||||||
|
Turn on one or more computers or, if no run with no arguments, all loaded computers.
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
- `/computercraft turn-on #0 #2`: Turn on computers with id 0 and 2.
|
||||||
|
- `/computercraft turn-on @c[family=command]`: Turn on all command computers.
|
||||||
|
|
||||||
|
### `/computercraft shutdown [computers...]` {#shutdown}
|
||||||
|
Shutdown one or more computers or, if no run with no arguments, all loaded computers.
|
||||||
|
|
||||||
|
This is sometimes useful when dealing with lag, as a way to ensure that ComputerCraft is not causing problems.
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
- `/computercraft shutdown`: Shut down all loaded computers.
|
||||||
|
- `/computercraft shutdown @c[distance=..10]`: Shut down all computers in a block radius.
|
||||||
|
|
||||||
|
### `/computercraft tp [computer]` {#tp}
|
||||||
|
Teleport to the given computer.
|
||||||
|
|
||||||
|
This is normally used from via the [`dump`] command interface rather than being invoked directly.
|
||||||
|
|
||||||
|
### `/computercraft view [computer]` {#view}
|
||||||
|
Open a terminal for the specified computer. This allows remotely viewing computers without having to interact with the
|
||||||
|
block.
|
||||||
|
|
||||||
|
This is normally used from via the [`dump`] command interface rather than being invoked directly.
|
||||||
|
|
||||||
|
### `/computercraft track` {#track}
|
||||||
|
The `/computercraft track` command allows you to enable profiling of computers. When a computer runs code, or interacts
|
||||||
|
with the Minecraft world, we time how long that takes. This timing information may then be queried, and used to find
|
||||||
|
computers which may be causing lag.
|
||||||
|
|
||||||
|
To enable the profiler, run `/computercraft track start`. Computers will then start recording metrics. Once enough data
|
||||||
|
has been gathered, run `/computercraft track stop` to stop profiling and display the recorded data.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The table by default shows the number of times each computer has run, and how long it ran for (in total, and on
|
||||||
|
average). In the above screenshot, we can see one computer was particularly badly behaved, and ran for 7 seconds. The
|
||||||
|
buttons may be used to [teleport][`tp`] to the computer, or [open its terminal ][`view`], and inspect it further.
|
||||||
|
|
||||||
|
`/computercraft track dump` can be used to display this table at any point (including while profiling is still running).
|
||||||
|
|
||||||
|
Computers also record other information, such as how much server-thread time they consume, or their HTTP bandwidth
|
||||||
|
usage. The `dump` subcommand accepts a list of other fields to display, instead of the default timings.
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
- `/computercraft track dump server_tasks_count server_tasks`: Print the number of server-thread tasks each computer
|
||||||
|
executed, and how long they took in total.
|
||||||
|
- `/computercraft track dump http_upload http_download`: Print the number of bytes uploaded and downloaded by each
|
||||||
|
computer.
|
||||||
|
|
||||||
|
|
||||||
|
### `/computercraft queue` {#queue}
|
||||||
|
The queue subcommand allows non-operator players to queue a `computer_command` event on *command* computers.
|
||||||
|
|
||||||
|
This has a similar purpose to vanilla's [`/trigger`] command. Command computers may choose to listen to this event, and
|
||||||
|
then perform some action.
|
||||||
|
|
||||||
|
[`/trigger`]: https://minecraft.wiki/w/Commands/trigger "/trigger on the Minecraft wiki"
|
||||||
|
|
||||||
|
|
||||||
|
[`dump`]: #dump "/computercraft dump"
|
||||||
|
[`queue`]: #queue "/computercraft queue"
|
||||||
|
[`shutdown`]: #shutdown "/computercraft shutdown"
|
||||||
|
[`tp`]: #tp "/computercraft tp"
|
||||||
|
[`track`]: #track "/computercraft track"
|
||||||
|
[`turn-on`]: #turn-on "/computercraft turn-on"
|
||||||
|
[`view`]: #view "/computercraft view"
|
||||||
|
[computer selectors]: #computer-selectors "Computer selectors"
|
||||||
@@ -9,17 +9,19 @@ SPDX-License-Identifier: MPL-2.0
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
# Lua 5.2/5.3 features in CC: Tweaked
|
# Lua 5.2/5.3 features in CC: Tweaked
|
||||||
CC: Tweaked is based off of the Cobalt Lua runtime, which uses Lua 5.1. However, Cobalt and CC:T implement additional features from Lua 5.2 and 5.3 (as well as some deprecated 5.0 features) that are not available in base 5.1. This page lists all of the compatibility for these newer versions.
|
CC: Tweaked is based off of the Cobalt Lua runtime, which uses Lua 5.2. However, Cobalt and CC:T implement additional
|
||||||
|
features from Lua 5.2 and 5.3 (as well as some deprecated 5.0 and 5.1 features). This page lists all of the
|
||||||
|
compatibility for these newer versions.
|
||||||
|
|
||||||
## Lua 5.2
|
## Lua 5.2
|
||||||
| Feature | Supported? | Notes |
|
| Feature | Supported? | Notes |
|
||||||
|---------------------------------------------------------------|------------|-------------------------------------------------------------------|
|
|---------------------------------------------------------------|------------|-------------------------------------------------------------------|
|
||||||
| `goto`/labels | ❌ | |
|
| `goto`/labels | ✔ | |
|
||||||
| `_ENV` | 🔶 | The `_ENV` global points to `getfenv()`, but it cannot be set. |
|
| `_ENV` | ✔ | |
|
||||||
| `\z` escape | ✔ | |
|
| `\z` escape | ✔ | |
|
||||||
| `\xNN` escape | ✔ | |
|
| `\xNN` escape | ✔ | |
|
||||||
| Hex literal fractional/exponent parts | ✔ | |
|
| Hex literal fractional/exponent parts | ✔ | |
|
||||||
| Empty statements | ❌ | |
|
| Empty statements | ✔ | |
|
||||||
| `__len` metamethod | ✔ | |
|
| `__len` metamethod | ✔ | |
|
||||||
| `__ipairs` metamethod | ❌ | Deprecated in Lua 5.3. `ipairs` uses `__len`/`__index` instead. |
|
| `__ipairs` metamethod | ❌ | Deprecated in Lua 5.3. `ipairs` uses `__len`/`__index` instead. |
|
||||||
| `__pairs` metamethod | ✔ | |
|
| `__pairs` metamethod | ✔ | |
|
||||||
@@ -27,12 +29,12 @@ CC: Tweaked is based off of the Cobalt Lua runtime, which uses Lua 5.1. However,
|
|||||||
| `collectgarbage` isrunning, generational, incremental options | ❌ | `collectgarbage` does not exist in CC:T. |
|
| `collectgarbage` isrunning, generational, incremental options | ❌ | `collectgarbage` does not exist in CC:T. |
|
||||||
| New `load` syntax | ✔ | |
|
| New `load` syntax | ✔ | |
|
||||||
| `loadfile` mode parameter | ✔ | Supports both 5.1 and 5.2+ syntax. |
|
| `loadfile` mode parameter | ✔ | Supports both 5.1 and 5.2+ syntax. |
|
||||||
| Removed `loadstring` | 🔶 | Only if `disable_lua51_features` is enabled in the configuration. |
|
| Removed `loadstring` | ❌ | |
|
||||||
| Removed `getfenv`, `setfenv` | 🔶 | Only if `disable_lua51_features` is enabled in the configuration. |
|
| Removed `getfenv`, `setfenv` | 🔶 | Only supports closures with an `_ENV` upvalue. |
|
||||||
| `rawlen` function | ✔ | |
|
| `rawlen` function | ✔ | |
|
||||||
| Negative index to `select` | ✔ | |
|
| Negative index to `select` | ✔ | |
|
||||||
| Removed `unpack` | 🔶 | Only if `disable_lua51_features` is enabled in the configuration. |
|
| Removed `unpack` | ❌ | |
|
||||||
| Arguments to `xpcall` | ✔ | |
|
| Arguments to `xpcall` | ✔ | |
|
||||||
| Second return value from `coroutine.running` | ✔ | |
|
| Second return value from `coroutine.running` | ✔ | |
|
||||||
| Removed `module` | ✔ | |
|
| Removed `module` | ✔ | |
|
||||||
| `package.loaders` -> `package.searchers` | ❌ | |
|
| `package.loaders` -> `package.searchers` | ❌ | |
|
||||||
@@ -40,14 +42,14 @@ CC: Tweaked is based off of the Cobalt Lua runtime, which uses Lua 5.1. However,
|
|||||||
| `package.config` | ✔ | |
|
| `package.config` | ✔ | |
|
||||||
| `package.searchpath` | ✔ | |
|
| `package.searchpath` | ✔ | |
|
||||||
| Removed `package.seeall` | ✔ | |
|
| Removed `package.seeall` | ✔ | |
|
||||||
| `string.dump` on functions with upvalues (blanks them out) | ✔ | |
|
| `string.dump` on functions with upvalues (blanks them out) | ❌ | `string.dump` is not supported |
|
||||||
| `string.rep` separator | ✔ | |
|
| `string.rep` separator | ✔ | |
|
||||||
| `%g` match group | ❌ | |
|
| `%g` match group | ❌ | |
|
||||||
| Removal of `%z` match group | ❌ | |
|
| Removal of `%z` match group | ❌ | |
|
||||||
| Removed `table.maxn` | 🔶 | Only if `disable_lua51_features` is enabled in the configuration. |
|
| Removed `table.maxn` | ❌ | |
|
||||||
| `table.pack`/`table.unpack` | ✔ | |
|
| `table.pack`/`table.unpack` | ✔ | |
|
||||||
| `math.log` base argument | ✔ | |
|
| `math.log` base argument | ✔ | |
|
||||||
| Removed `math.log10` | 🔶 | Only if `disable_lua51_features` is enabled in the configuration. |
|
| Removed `math.log10` | ❌ | |
|
||||||
| `*L` mode to `file:read` | ✔ | |
|
| `*L` mode to `file:read` | ✔ | |
|
||||||
| `os.execute` exit type + return value | ❌ | `os.execute` does not exist in CC:T. |
|
| `os.execute` exit type + return value | ❌ | `os.execute` does not exist in CC:T. |
|
||||||
| `os.exit` close argument | ❌ | `os.exit` does not exist in CC:T. |
|
| `os.exit` close argument | ❌ | `os.exit` does not exist in CC:T. |
|
||||||
@@ -61,7 +63,7 @@ CC: Tweaked is based off of the Cobalt Lua runtime, which uses Lua 5.1. However,
|
|||||||
| Tail call hooks | ❌ | |
|
| Tail call hooks | ❌ | |
|
||||||
| `=` prefix for chunks | ✔ | |
|
| `=` prefix for chunks | ✔ | |
|
||||||
| Yield across C boundary | ✔ | |
|
| Yield across C boundary | ✔ | |
|
||||||
| Removal of ambiguity error | ❌ | |
|
| Removal of ambiguity error | ✔ | |
|
||||||
| Identifiers may no longer use locale-dependent letters | ✔ | |
|
| Identifiers may no longer use locale-dependent letters | ✔ | |
|
||||||
| Ephemeron tables | ❌ | |
|
| Ephemeron tables | ❌ | |
|
||||||
| Identical functions may be reused | ❌ | Removed in Lua 5.4 |
|
| Identical functions may be reused | ❌ | Removed in Lua 5.4 |
|
||||||
|
|||||||
@@ -95,10 +95,10 @@ function pullEventRaw(filter) end
|
|||||||
-- nearest multiple of 0.05.
|
-- nearest multiple of 0.05.
|
||||||
function sleep(time) end
|
function sleep(time) end
|
||||||
|
|
||||||
--- Get the current CraftOS version (for example, `CraftOS 1.8`).
|
--- Get the current CraftOS version (for example, `CraftOS 1.9`).
|
||||||
--
|
--
|
||||||
-- This is defined by `bios.lua`. For the current version of CC:Tweaked, this
|
-- This is defined by `bios.lua`. For the current version of CC:Tweaked, this
|
||||||
-- should return `CraftOS 1.8`.
|
-- should return `CraftOS 1.9`.
|
||||||
--
|
--
|
||||||
-- @treturn string The current CraftOS version.
|
-- @treturn string The current CraftOS version.
|
||||||
-- @usage os.version()
|
-- @usage os.version()
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ kotlin.jvm.target.validation.mode=error
|
|||||||
|
|
||||||
# Mod properties
|
# Mod properties
|
||||||
isUnstable=false
|
isUnstable=false
|
||||||
modVersion=1.108.1
|
modVersion=1.110.0
|
||||||
|
|
||||||
# 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.20.1
|
mcVersion=1.20.1
|
||||||
|
|||||||
@@ -10,27 +10,30 @@
|
|||||||
fabric-api = "0.86.1+1.20.1"
|
fabric-api = "0.86.1+1.20.1"
|
||||||
fabric-loader = "0.14.21"
|
fabric-loader = "0.14.21"
|
||||||
forge = "47.1.0"
|
forge = "47.1.0"
|
||||||
forgeSpi = "6.0.0"
|
forgeSpi = "7.0.1"
|
||||||
mixin = "0.8.5"
|
mixin = "0.8.5"
|
||||||
parchment = "2023.08.20"
|
parchment = "2023.08.20"
|
||||||
parchmentMc = "1.20.1"
|
parchmentMc = "1.20.1"
|
||||||
|
yarn = "1.20.1+build.10"
|
||||||
|
|
||||||
# Normal dependencies
|
# Core dependencies (these versions are tied to the version Minecraft uses)
|
||||||
asm = "9.5"
|
|
||||||
autoService = "1.1.1"
|
|
||||||
checkerFramework = "3.32.0"
|
|
||||||
cobalt = "0.7.3"
|
|
||||||
cobalt-next = "0.7.4" # Not a real version, used to constrain the version we accept.
|
|
||||||
fastutil = "8.5.9"
|
fastutil = "8.5.9"
|
||||||
guava = "31.1-jre"
|
guava = "31.1-jre"
|
||||||
jetbrainsAnnotations = "24.0.1"
|
netty = "4.1.82.Final"
|
||||||
|
slf4j = "2.0.1"
|
||||||
|
|
||||||
|
# Core dependencies (independent of Minecraft)
|
||||||
|
asm = "9.6"
|
||||||
|
autoService = "1.1.1"
|
||||||
|
checkerFramework = "3.42.0"
|
||||||
|
cobalt = "0.9.2"
|
||||||
|
commonsCli = "1.6.0"
|
||||||
|
jetbrainsAnnotations = "24.1.0"
|
||||||
jsr305 = "3.0.2"
|
jsr305 = "3.0.2"
|
||||||
jzlib = "1.1.3"
|
jzlib = "1.1.3"
|
||||||
kotlin = "1.8.10"
|
kotlin = "1.9.21"
|
||||||
kotlin-coroutines = "1.6.4"
|
kotlin-coroutines = "1.7.3"
|
||||||
netty = "4.1.82.Final"
|
|
||||||
nightConfig = "3.6.7"
|
nightConfig = "3.6.7"
|
||||||
slf4j = "1.7.36"
|
|
||||||
|
|
||||||
# Minecraft mods
|
# Minecraft mods
|
||||||
emi = "1.0.8+1.20.1"
|
emi = "1.0.8+1.20.1"
|
||||||
@@ -45,37 +48,41 @@ rubidium = "0.6.1"
|
|||||||
sodium = "mc1.20-0.4.10"
|
sodium = "mc1.20-0.4.10"
|
||||||
|
|
||||||
# Testing
|
# Testing
|
||||||
byteBuddy = "1.14.7"
|
|
||||||
hamcrest = "2.2"
|
hamcrest = "2.2"
|
||||||
jqwik = "1.7.4"
|
jqwik = "1.8.2"
|
||||||
junit = "5.10.0"
|
junit = "5.10.1"
|
||||||
|
jmh = "1.37"
|
||||||
|
|
||||||
# Build tools
|
# Build tools
|
||||||
cctJavadoc = "1.8.0"
|
cctJavadoc = "1.8.2"
|
||||||
checkstyle = "10.12.3"
|
checkstyle = "10.14.1"
|
||||||
curseForgeGradle = "1.0.14"
|
curseForgeGradle = "1.0.14"
|
||||||
errorProne-core = "2.21.1"
|
errorProne-core = "2.23.0"
|
||||||
errorProne-plugin = "3.1.0"
|
errorProne-plugin = "3.1.0"
|
||||||
fabric-loom = "1.3.7"
|
fabric-loom = "1.5.7"
|
||||||
forgeGradle = "6.0.8"
|
forgeGradle = "6.0.20"
|
||||||
githubRelease = "2.4.1"
|
githubRelease = "2.5.2"
|
||||||
|
gradleVersions = "0.50.0"
|
||||||
ideaExt = "1.1.7"
|
ideaExt = "1.1.7"
|
||||||
illuaminate = "0.1.0-40-g975cbc3"
|
illuaminate = "0.1.0-69-gf294ab2"
|
||||||
librarian = "1.+"
|
librarian = "1.+"
|
||||||
|
lwjgl = "3.3.3"
|
||||||
minotaur = "2.+"
|
minotaur = "2.+"
|
||||||
mixinGradle = "0.7.+"
|
|
||||||
nullAway = "0.9.9"
|
nullAway = "0.9.9"
|
||||||
spotless = "6.21.0"
|
spotless = "6.23.3"
|
||||||
taskTree = "2.1.1"
|
taskTree = "2.1.1"
|
||||||
vanillaGradle = "0.2.1-SNAPSHOT"
|
teavm = "0.10.0-SQUID.3"
|
||||||
vineflower = "1.11.0"
|
vanillaExtract = "0.1.2"
|
||||||
|
versionCatalogUpdate = "0.8.1"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
# Normal dependencies
|
# Normal dependencies
|
||||||
asm = { module = "org.ow2.asm:asm", version.ref = "asm" }
|
asm = { module = "org.ow2.asm:asm", version.ref = "asm" }
|
||||||
|
asm-commons = { module = "org.ow2.asm:asm-commons", version.ref = "asm" }
|
||||||
autoService = { module = "com.google.auto.service:auto-service", version.ref = "autoService" }
|
autoService = { module = "com.google.auto.service:auto-service", version.ref = "autoService" }
|
||||||
checkerFramework = { module = "org.checkerframework:checker-qual", version.ref = "checkerFramework" }
|
checkerFramework = { module = "org.checkerframework:checker-qual", version.ref = "checkerFramework" }
|
||||||
cobalt = { module = "org.squiddev:Cobalt", version.ref = "cobalt" }
|
cobalt = { module = "cc.tweaked:cobalt", version.ref = "cobalt" }
|
||||||
|
commonsCli = { module = "commons-cli:commons-cli", version.ref = "commonsCli" }
|
||||||
fastutil = { module = "it.unimi.dsi:fastutil", version.ref = "fastutil" }
|
fastutil = { module = "it.unimi.dsi:fastutil", version.ref = "fastutil" }
|
||||||
forgeSpi = { module = "net.minecraftforge:forgespi", version.ref = "forgeSpi" }
|
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" }
|
||||||
@@ -95,6 +102,7 @@ slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
|
|||||||
# Minecraft mods
|
# Minecraft mods
|
||||||
fabric-api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabric-api" }
|
fabric-api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabric-api" }
|
||||||
fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" }
|
fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" }
|
||||||
|
fabric-junit = { module = "net.fabricmc:fabric-loader-junit", version.ref = "fabric-loader" }
|
||||||
fabricPermissions = { module = "me.lucko:fabric-permissions-api", version.ref = "fabricPermissions" }
|
fabricPermissions = { module = "me.lucko:fabric-permissions-api", version.ref = "fabricPermissions" }
|
||||||
emi = { module = "dev.emi:emi-xplat-mojmap", version.ref = "emi" }
|
emi = { module = "dev.emi:emi-xplat-mojmap", version.ref = "emi" }
|
||||||
iris = { module = "maven.modrinth:iris", version.ref = "iris" }
|
iris = { module = "maven.modrinth:iris", version.ref = "iris" }
|
||||||
@@ -112,8 +120,6 @@ rubidium = { module = "maven.modrinth:rubidium", version.ref = "rubidium" }
|
|||||||
sodium = { module = "maven.modrinth:sodium", version.ref = "sodium" }
|
sodium = { module = "maven.modrinth:sodium", version.ref = "sodium" }
|
||||||
|
|
||||||
# Testing
|
# Testing
|
||||||
byteBuddyAgent = { module = "net.bytebuddy:byte-buddy-agent", version.ref = "byteBuddy" }
|
|
||||||
byteBuddy = { module = "net.bytebuddy:byte-buddy", version.ref = "byteBuddy" }
|
|
||||||
hamcrest = { module = "org.hamcrest:hamcrest", version.ref = "hamcrest" }
|
hamcrest = { module = "org.hamcrest:hamcrest", version.ref = "hamcrest" }
|
||||||
jqwik-api = { module = "net.jqwik:jqwik-api", version.ref = "jqwik" }
|
jqwik-api = { module = "net.jqwik:jqwik-api", version.ref = "jqwik" }
|
||||||
jqwik-engine = { module = "net.jqwik:jqwik-engine", version.ref = "jqwik" }
|
jqwik-engine = { module = "net.jqwik:jqwik-engine", version.ref = "jqwik" }
|
||||||
@@ -121,6 +127,14 @@ junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.re
|
|||||||
junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" }
|
junit-jupiter-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" }
|
||||||
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-processor = { module = "org.openjdk.jmh:jmh-generator-annprocess", version.ref = "jmh" }
|
||||||
|
|
||||||
|
# LWJGL
|
||||||
|
lwjgl-bom = { module = "org.lwjgl:lwjgl-bom", version.ref = "lwjgl" }
|
||||||
|
lwjgl-core = { module = "org.lwjgl:lwjgl" }
|
||||||
|
lwjgl-opengl = { module = "org.lwjgl:lwjgl-opengl" }
|
||||||
|
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" }
|
||||||
@@ -133,34 +147,47 @@ errorProne-plugin = { module = "net.ltgt.gradle:gradle-errorprone-plugin", versi
|
|||||||
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" }
|
forgeGradle = { module = "net.minecraftforge.gradle:ForgeGradle", version.ref = "forgeGradle" }
|
||||||
|
ideaExt = { module = "gradle.plugin.org.jetbrains.gradle.plugin.idea-ext:gradle-idea-ext", version.ref = "ideaExt" }
|
||||||
kotlin-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
|
kotlin-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
|
||||||
librarian = { module = "org.parchmentmc:librarian", version.ref = "librarian" }
|
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" }
|
||||||
spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" }
|
spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" }
|
||||||
vanillaGradle = { module = "org.spongepowered:vanillagradle", version.ref = "vanillaGradle" }
|
teavm-classlib = { module = "org.teavm:teavm-classlib", version.ref = "teavm" }
|
||||||
vineflower = { module = "io.github.juuxel:loom-vineflower", version.ref = "vineflower" }
|
teavm-jso = { module = "org.teavm:teavm-jso", version.ref = "teavm" }
|
||||||
|
teavm-jso-apis = { module = "org.teavm:teavm-jso-apis", version.ref = "teavm" }
|
||||||
|
teavm-jso-impl = { module = "org.teavm:teavm-jso-impl", version.ref = "teavm" }
|
||||||
|
teavm-metaprogramming-api = { module = "org.teavm:teavm-metaprogramming-api", version.ref = "teavm" }
|
||||||
|
teavm-metaprogramming-impl = { module = "org.teavm:teavm-metaprogramming-impl", version.ref = "teavm" }
|
||||||
|
teavm-platform = { module = "org.teavm:teavm-platform", version.ref = "teavm" }
|
||||||
|
teavm-tooling = { module = "org.teavm:teavm-tooling", version.ref = "teavm" }
|
||||||
|
vanillaExtract = { module = "cc.tweaked.vanilla-extract:plugin", version.ref = "vanillaExtract" }
|
||||||
|
yarn = { module = "net.fabricmc:yarn", version.ref = "yarn" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
forgeGradle = { id = "net.minecraftforge.gradle", version.ref = "forgeGradle" }
|
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" }
|
||||||
ideaExt = { id = "org.jetbrains.gradle.plugin.idea-ext", version.ref = "ideaExt" }
|
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" }
|
librarian = { id = "org.parchmentmc.librarian.forgegradle", version.ref = "librarian" }
|
||||||
mixinGradle = { id = "org.spongepowered.mixin", version.ref = "mixinGradle" }
|
|
||||||
taskTree = { id = "com.dorongold.task-tree", version.ref = "taskTree" }
|
taskTree = { id = "com.dorongold.task-tree", version.ref = "taskTree" }
|
||||||
|
versionCatalogUpdate = { id = "nl.littlerobots.version-catalog-update", version.ref = "versionCatalogUpdate" }
|
||||||
|
|
||||||
[bundles]
|
[bundles]
|
||||||
|
annotations = ["jsr305", "checkerFramework", "jetbrainsAnnotations"]
|
||||||
kotlin = ["kotlin-stdlib", "kotlin-coroutines"]
|
kotlin = ["kotlin-stdlib", "kotlin-coroutines"]
|
||||||
|
|
||||||
# Minecraft
|
# Minecraft
|
||||||
externalMods-common = ["jei-api", "nightConfig-core", "nightConfig-toml"]
|
externalMods-common = ["jei-api", "nightConfig-core", "nightConfig-toml"]
|
||||||
externalMods-forge-compile = ["moreRed", "oculus", "jei-api"]
|
externalMods-forge-compile = ["moreRed", "oculus", "jei-api"]
|
||||||
externalMods-forge-runtime = ["jei-forge"]
|
externalMods-forge-runtime = ["jei-forge"]
|
||||||
externalMods-fabric = ["nightConfig-core", "nightConfig-toml"]
|
|
||||||
externalMods-fabric-compile = ["fabricPermissions", "iris", "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", "jqwik-engine"]
|
testRuntime = ["junit-jupiter-engine", "jqwik-engine"]
|
||||||
|
|
||||||
|
# Build tools
|
||||||
|
teavm-api = ["teavm-jso", "teavm-jso-apis", "teavm-platform", "teavm-classlib", "teavm-metaprogramming-api"]
|
||||||
|
teavm-tooling = ["teavm-tooling", "teavm-metaprogramming-impl", "teavm-jso-impl"]
|
||||||
|
|||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
3
gradle/wrapper/gradle-wrapper.properties
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,7 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
22
gradlew
vendored
22
gradlew
vendored
@@ -83,7 +83,8 @@ done
|
|||||||
# This is normally unused
|
# This is normally unused
|
||||||
# shellcheck disable=SC2034
|
# shellcheck disable=SC2034
|
||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=${0##*/}
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
|
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || 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
|
||||||
@@ -130,10 +131,13 @@ location of your Java installation."
|
|||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
JAVACMD=java
|
JAVACMD=java
|
||||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
if ! command -v java >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
@@ -141,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
|||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
max*)
|
max*)
|
||||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
# shellcheck disable=SC3045
|
# shellcheck disable=SC2039,SC3045
|
||||||
MAX_FD=$( ulimit -H -n ) ||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
warn "Could not query maximum file descriptor limit"
|
warn "Could not query maximum file descriptor limit"
|
||||||
esac
|
esac
|
||||||
@@ -149,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
|||||||
'' | soft) :;; #(
|
'' | soft) :;; #(
|
||||||
*)
|
*)
|
||||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
# shellcheck disable=SC3045
|
# shellcheck disable=SC2039,SC3045
|
||||||
ulimit -n "$MAX_FD" ||
|
ulimit -n "$MAX_FD" ||
|
||||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
esac
|
esac
|
||||||
@@ -198,11 +202,11 @@ fi
|
|||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
# Collect all arguments for the java command;
|
# Collect all arguments for the java command:
|
||||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||||
# shell script including quotes and variable substitutions, so put them in
|
# and any embedded shellness will be escaped.
|
||||||
# double quotes to make sure that they get re-expanded; and
|
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||||
# * put everything else in single quotes, so that it's not re-expanded.
|
# treated as '${Hostname}' itself on the command line.
|
||||||
|
|
||||||
set -- \
|
set -- \
|
||||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
|||||||
20
gradlew.bat
vendored
20
gradlew.bat
vendored
@@ -43,11 +43,11 @@ set JAVA_EXE=java.exe
|
|||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if %ERRORLEVEL% equ 0 goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
@@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
|||||||
|
|
||||||
if exist "%JAVA_EXE%" goto execute
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
|
|||||||
@@ -2,15 +2,15 @@
|
|||||||
|
|
||||||
; SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
; SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
||||||
;
|
;
|
||||||
; SPDX-License-Identifier: LicenseRef-CCPL
|
; SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
(sources
|
(sources
|
||||||
/doc/
|
/doc/
|
||||||
/projects/forge/build/docs/luaJavadoc/
|
/projects/common/build/docs/luaJavadoc/
|
||||||
/projects/core/src/main/resources/data/computercraft/lua/bios.lua
|
/projects/core/src/main/resources/data/computercraft/lua/bios.lua
|
||||||
/projects/core/src/main/resources/data/computercraft/lua/rom/
|
/projects/core/src/main/resources/data/computercraft/lua/rom/
|
||||||
/projects/core/src/test/resources/test-rom
|
/projects/core/src/test/resources/test-rom
|
||||||
/projects/web/src/mount)
|
/projects/web/src/frontend/mount)
|
||||||
|
|
||||||
(doc
|
(doc
|
||||||
; Also defined in projects/web/build.gradle.kts
|
; Also defined in projects/web/build.gradle.kts
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
(url https://tweaked.cc/)
|
(url https://tweaked.cc/)
|
||||||
(source-link https://github.com/cc-tweaked/CC-Tweaked/blob/${commit}/${path}#L${line})
|
(source-link https://github.com/cc-tweaked/CC-Tweaked/blob/${commit}/${path}#L${line})
|
||||||
|
|
||||||
(styles /projects/web/src/styles.css)
|
(styles /projects/web/build/rollup/index.css)
|
||||||
(scripts /projects/web/build/rollup/index.js)
|
(scripts /projects/web/build/rollup/index.js)
|
||||||
(head doc/head.html))
|
(head doc/head.html))
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
|
|
||||||
(library-path
|
(library-path
|
||||||
/doc/stub/
|
/doc/stub/
|
||||||
/projects/forge/build/docs/luaJavadoc/
|
/projects/common/build/docs/luaJavadoc/
|
||||||
|
|
||||||
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/
|
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/
|
||||||
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/command/
|
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/command/
|
||||||
@@ -77,7 +77,6 @@
|
|||||||
(globals
|
(globals
|
||||||
:max
|
:max
|
||||||
_CC_DEFAULT_SETTINGS
|
_CC_DEFAULT_SETTINGS
|
||||||
_CC_DISABLE_LUA51_FEATURES
|
|
||||||
_HOST
|
_HOST
|
||||||
;; Ideally we'd pick these up from bios.lua, but illuaminate currently
|
;; Ideally we'd pick these up from bios.lua, but illuaminate currently
|
||||||
;; isn't smart enough.
|
;; isn't smart enough.
|
||||||
@@ -89,7 +88,7 @@
|
|||||||
(/doc/stub/
|
(/doc/stub/
|
||||||
/projects/core/src/main/resources/data/computercraft/lua/bios.lua
|
/projects/core/src/main/resources/data/computercraft/lua/bios.lua
|
||||||
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/
|
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/
|
||||||
/projects/forge/build/docs/luaJavadoc/)
|
/projects/common/build/docs/luaJavadoc/)
|
||||||
(linters -var:unused-global)
|
(linters -var:unused-global)
|
||||||
(lint (allow-toplevel-global true)))
|
(lint (allow-toplevel-global true)))
|
||||||
|
|
||||||
@@ -106,6 +105,10 @@
|
|||||||
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/turtle/turtle.lua)
|
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/turtle/turtle.lua)
|
||||||
(linters -var:deprecated))
|
(linters -var:deprecated))
|
||||||
|
|
||||||
|
;; Suppress unused variable warnings in the parser.
|
||||||
|
(at /projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/cc/internal/syntax/parser.lua
|
||||||
|
(linters -var:unused))
|
||||||
|
|
||||||
(at /projects/core/src/test/resources/test-rom
|
(at /projects/core/src/test/resources/test-rom
|
||||||
; We should still be able to test deprecated members.
|
; We should still be able to test deprecated members.
|
||||||
(linters -var:deprecated)
|
(linters -var:deprecated)
|
||||||
@@ -115,4 +118,4 @@
|
|||||||
:max sleep write
|
:max sleep write
|
||||||
cct_test describe expect howlci fail it pending stub before_each)))
|
cct_test describe expect howlci fail it pending stub before_each)))
|
||||||
|
|
||||||
(at /projects/web/src/mount/expr_template.lua (lint (globals :max __expr__)))
|
(at /projects/web/src/frontend/mount/expr_template.lua (lint (globals :max __expr__)))
|
||||||
|
|||||||
4721
package-lock.json
generated
4721
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
24
package.json
24
package.json
@@ -6,24 +6,24 @@
|
|||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@squid-dev/cc-web-term": "^2.0.0",
|
||||||
"preact": "^10.5.5",
|
"preact": "^10.5.5",
|
||||||
|
"setimmediate": "^1.0.5",
|
||||||
"tslib": "^2.0.3"
|
"tslib": "^2.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-terser": "^0.4.0",
|
"@rollup/plugin-node-resolve": "^15.2.1",
|
||||||
"@rollup/plugin-typescript": "^11.0.0",
|
"@rollup/plugin-typescript": "^11.0.0",
|
||||||
"@rollup/plugin-url": "^8.0.1",
|
"@rollup/plugin-url": "^8.0.1",
|
||||||
"@types/glob": "^8.1.0",
|
"@swc/core": "^1.3.92",
|
||||||
"@types/react-dom": "^18.0.5",
|
"@types/node": "^20.8.3",
|
||||||
"glob": "^10.3.4",
|
"lightningcss": "^1.22.0",
|
||||||
"react-dom": "^18.1.0",
|
"preact-render-to-string": "^6.2.1",
|
||||||
"react": "^18.1.0",
|
"rehype": "^13.0.0",
|
||||||
"rehype-highlight": "^6.0.0",
|
"rehype-highlight": "^7.0.0",
|
||||||
"rehype-react": "^7.1.1",
|
"rehype-react": "^8.0.0",
|
||||||
"rehype": "^12.0.0",
|
"rollup": "^4.0.0",
|
||||||
"requirejs": "^2.3.6",
|
"tsx": "^4.7.0",
|
||||||
"rollup": "^3.19.1",
|
|
||||||
"ts-node": "^10.8.0",
|
|
||||||
"typescript": "^5.2.2"
|
"typescript": "^5.2.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,9 @@ mentioning:
|
|||||||
- `lints`: This defines an [ErrorProne] plugin which adds a couple of compile-time checks to our code. This is what
|
- `lints`: This defines an [ErrorProne] plugin which adds a couple of compile-time checks to our code. This is what
|
||||||
enforces that no client-specific code is used inside the `main` source set (and a couple of other things!).
|
enforces that no client-specific code is used inside the `main` source set (and a couple of other things!).
|
||||||
|
|
||||||
|
- `standalone`: This contains a standalone UI for computers, allowing debugging and development of CraftOS without
|
||||||
|
launching Minecraft.
|
||||||
|
|
||||||
- `web`: This contains the additional tooling for building [the documentation website][tweaked.cc], such as support for
|
- `web`: This contains the additional tooling for building [the documentation website][tweaked.cc], such as support for
|
||||||
rendering recipes
|
rendering recipes
|
||||||
|
|
||||||
|
|||||||
@@ -27,8 +27,13 @@ public final class ComputerCraftAPIClient {
|
|||||||
* @param serialiser The turtle upgrade serialiser.
|
* @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.
|
||||||
|
* @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) {
|
public static <T extends ITurtleUpgrade> void registerTurtleUpgradeModeller(TurtleUpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller) {
|
||||||
|
// TODO(1.20.4): Remove this
|
||||||
getInstance().registerTurtleUpgradeModeller(serialiser, modeller);
|
getInstance().registerTurtleUpgradeModeller(serialiser, modeller);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.api.client.turtle;
|
||||||
|
|
||||||
|
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||||
|
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A functional interface to register a {@link TurtleUpgradeModeller} for a class of turtle upgrades.
|
||||||
|
* <p>
|
||||||
|
* This interface is largely intended to be used from multi-loader code, to allow sharing registration code between
|
||||||
|
* multiple loaders.
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface RegisterTurtleUpgradeModeller {
|
||||||
|
/**
|
||||||
|
* Register a {@link TurtleUpgradeModeller}.
|
||||||
|
*
|
||||||
|
* @param serialiser The turtle upgrade serialiser.
|
||||||
|
* @param modeller The upgrade modeller.
|
||||||
|
* @param <T> The type of the turtle upgrade.
|
||||||
|
*/
|
||||||
|
<T extends ITurtleUpgrade> void register(TurtleUpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller);
|
||||||
|
}
|
||||||
@@ -4,12 +4,10 @@
|
|||||||
|
|
||||||
package dan200.computercraft.api.client.turtle;
|
package dan200.computercraft.api.client.turtle;
|
||||||
|
|
||||||
import dan200.computercraft.api.client.ComputerCraftAPIClient;
|
|
||||||
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 dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
|
|
||||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
import net.minecraft.client.resources.model.ModelResourceLocation;
|
||||||
import net.minecraft.client.resources.model.UnbakedModel;
|
import net.minecraft.client.resources.model.UnbakedModel;
|
||||||
import net.minecraft.nbt.CompoundTag;
|
import net.minecraft.nbt.CompoundTag;
|
||||||
@@ -21,9 +19,13 @@ import java.util.List;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides models for a {@link ITurtleUpgrade}.
|
* Provides models for a {@link ITurtleUpgrade}.
|
||||||
|
* <p>
|
||||||
|
* Use {@code dan200.computercraft.api.client.FabricComputerCraftAPIClient#registerTurtleUpgradeModeller} to register a
|
||||||
|
* modeller on Fabric and {@code dan200.computercraft.api.client.turtle.RegisterTurtleModellersEvent} to register one
|
||||||
|
* on Forge
|
||||||
*
|
*
|
||||||
* @param <T> The type of turtle upgrade this modeller applies to.
|
* @param <T> The type of turtle upgrade this modeller applies to.
|
||||||
* @see ComputerCraftAPIClient#registerTurtleUpgradeModeller(TurtleUpgradeSerialiser, TurtleUpgradeModeller) To register a modeller.
|
* @see RegisterTurtleUpgradeModeller For multi-loader registration support.
|
||||||
*/
|
*/
|
||||||
public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
|
public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -46,6 +46,14 @@ public class ComputerCraftTags {
|
|||||||
public static final TagKey<Block> WIRED_MODEM = make("wired_modem");
|
public static final TagKey<Block> WIRED_MODEM = make("wired_modem");
|
||||||
public static final TagKey<Block> MONITOR = make("monitor");
|
public static final TagKey<Block> MONITOR = make("monitor");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blocks which should be ignored by a {@code peripheral_hub} peripheral.
|
||||||
|
* <p>
|
||||||
|
* This should include blocks which themselves expose a peripheral hub (such as {@linkplain #WIRED_MODEM wired
|
||||||
|
* modems}).
|
||||||
|
*/
|
||||||
|
public static final TagKey<Block> PERIPHERAL_HUB_IGNORE = make("peripheral_hub_ignore");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Blocks which can be broken by any turtle tool.
|
* Blocks which can be broken by any turtle tool.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
package dan200.computercraft.api.network.wired;
|
package dan200.computercraft.api.network.wired;
|
||||||
|
|
||||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -22,6 +23,7 @@ import java.util.Map;
|
|||||||
*
|
*
|
||||||
* @see WiredNode#getNetwork()
|
* @see WiredNode#getNetwork()
|
||||||
*/
|
*/
|
||||||
|
@ApiStatus.NonExtendable
|
||||||
public interface WiredNetwork {
|
public interface WiredNetwork {
|
||||||
/**
|
/**
|
||||||
* Create a connection between two nodes.
|
* Create a connection between two nodes.
|
||||||
@@ -35,7 +37,9 @@ public interface WiredNetwork {
|
|||||||
* @throws IllegalArgumentException If {@code left} and {@code right} are equal.
|
* @throws IllegalArgumentException If {@code left} and {@code right} are equal.
|
||||||
* @see WiredNode#connectTo(WiredNode)
|
* @see WiredNode#connectTo(WiredNode)
|
||||||
* @see WiredNetwork#connect(WiredNode, WiredNode)
|
* @see WiredNetwork#connect(WiredNode, WiredNode)
|
||||||
|
* @deprecated Use {@link WiredNode#connectTo(WiredNode)}
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
boolean connect(WiredNode left, WiredNode right);
|
boolean connect(WiredNode left, WiredNode right);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -50,7 +54,9 @@ public interface WiredNetwork {
|
|||||||
* @throws IllegalArgumentException If {@code left} and {@code right} are equal.
|
* @throws IllegalArgumentException If {@code left} and {@code right} are equal.
|
||||||
* @see WiredNode#disconnectFrom(WiredNode)
|
* @see WiredNode#disconnectFrom(WiredNode)
|
||||||
* @see WiredNetwork#connect(WiredNode, WiredNode)
|
* @see WiredNetwork#connect(WiredNode, WiredNode)
|
||||||
|
* @deprecated Use {@link WiredNode#disconnectFrom(WiredNode)}
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
boolean disconnect(WiredNode left, WiredNode right);
|
boolean disconnect(WiredNode left, WiredNode right);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -64,7 +70,9 @@ public interface WiredNetwork {
|
|||||||
* only element.
|
* only element.
|
||||||
* @throws IllegalArgumentException If the node is not in the network.
|
* @throws IllegalArgumentException If the node is not in the network.
|
||||||
* @see WiredNode#remove()
|
* @see WiredNode#remove()
|
||||||
|
* @deprecated Use {@link WiredNode#remove()}
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
boolean remove(WiredNode node);
|
boolean remove(WiredNode node);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -77,6 +85,8 @@ public interface WiredNetwork {
|
|||||||
* @param peripherals The new peripherals for this node.
|
* @param peripherals The new peripherals for this node.
|
||||||
* @throws IllegalArgumentException If the node is not in the network.
|
* @throws IllegalArgumentException If the node is not in the network.
|
||||||
* @see WiredNode#updatePeripherals(Map)
|
* @see WiredNode#updatePeripherals(Map)
|
||||||
|
* @deprecated Use {@link WiredNode#updatePeripherals(Map)}
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
void updatePeripherals(WiredNode node, Map<String, IPeripheral> peripherals);
|
void updatePeripherals(WiredNode node, Map<String, IPeripheral> peripherals);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ package dan200.computercraft.api.network.wired;
|
|||||||
|
|
||||||
import dan200.computercraft.api.network.PacketNetwork;
|
import dan200.computercraft.api.network.PacketNetwork;
|
||||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -22,6 +23,7 @@ import java.util.Map;
|
|||||||
* Wired nodes also provide several convenience methods for interacting with a wired network. These should only ever
|
* Wired nodes also provide several convenience methods for interacting with a wired network. These should only ever
|
||||||
* be used on the main server thread.
|
* be used on the main server thread.
|
||||||
*/
|
*/
|
||||||
|
@ApiStatus.NonExtendable
|
||||||
public interface WiredNode extends PacketNetwork {
|
public interface WiredNode extends PacketNetwork {
|
||||||
/**
|
/**
|
||||||
* The associated element for this network node.
|
* The associated element for this network node.
|
||||||
@@ -37,7 +39,9 @@ public interface WiredNode extends PacketNetwork {
|
|||||||
* This should only be used on the server thread.
|
* This should only be used on the server thread.
|
||||||
*
|
*
|
||||||
* @return This node's network.
|
* @return This node's network.
|
||||||
|
* @deprecated Use the connect/disconnect/remove methods on {@link WiredNode}.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
WiredNetwork getNetwork();
|
WiredNetwork getNetwork();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,12 +51,9 @@ public interface WiredNode extends PacketNetwork {
|
|||||||
*
|
*
|
||||||
* @param node The other node to connect to.
|
* @param node The other node to connect to.
|
||||||
* @return {@code true} if a connection was created or {@code false} if the connection already exists.
|
* @return {@code true} if a connection was created or {@code false} if the connection already exists.
|
||||||
* @see WiredNetwork#connect(WiredNode, WiredNode)
|
|
||||||
* @see WiredNode#disconnectFrom(WiredNode)
|
* @see WiredNode#disconnectFrom(WiredNode)
|
||||||
*/
|
*/
|
||||||
default boolean connectTo(WiredNode node) {
|
boolean connectTo(WiredNode node);
|
||||||
return getNetwork().connect(this, node);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Destroy a connection between this node and another.
|
* Destroy a connection between this node and another.
|
||||||
@@ -61,13 +62,9 @@ public interface WiredNode extends PacketNetwork {
|
|||||||
*
|
*
|
||||||
* @param node The other node to disconnect from.
|
* @param node The other node to disconnect from.
|
||||||
* @return {@code true} if a connection was destroyed or {@code false} if no connection exists.
|
* @return {@code true} if a connection was destroyed or {@code false} if no connection exists.
|
||||||
* @throws IllegalArgumentException If {@code node} is not on the same network.
|
|
||||||
* @see WiredNetwork#disconnect(WiredNode, WiredNode)
|
|
||||||
* @see WiredNode#connectTo(WiredNode)
|
* @see WiredNode#connectTo(WiredNode)
|
||||||
*/
|
*/
|
||||||
default boolean disconnectFrom(WiredNode node) {
|
boolean disconnectFrom(WiredNode node);
|
||||||
return getNetwork().disconnect(this, node);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sever all connections this node has, removing it from this network.
|
* Sever all connections this node has, removing it from this network.
|
||||||
@@ -78,11 +75,8 @@ public interface WiredNode extends PacketNetwork {
|
|||||||
* @return Whether this node was removed from the network. One cannot remove a node from a network where it is the
|
* @return Whether this node was removed from the network. One cannot remove a node from a network where it is the
|
||||||
* only element.
|
* only element.
|
||||||
* @throws IllegalArgumentException If the node is not in the network.
|
* @throws IllegalArgumentException If the node is not in the network.
|
||||||
* @see WiredNetwork#remove(WiredNode)
|
|
||||||
*/
|
*/
|
||||||
default boolean remove() {
|
boolean remove();
|
||||||
return getNetwork().remove(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark this node's peripherals as having changed.
|
* Mark this node's peripherals as having changed.
|
||||||
@@ -91,9 +85,6 @@ public interface WiredNode extends PacketNetwork {
|
|||||||
* that your network element owns.
|
* that your network element owns.
|
||||||
*
|
*
|
||||||
* @param peripherals The new peripherals for this node.
|
* @param peripherals The new peripherals for this node.
|
||||||
* @see WiredNetwork#updatePeripherals(WiredNode, Map)
|
|
||||||
*/
|
*/
|
||||||
default void updatePeripherals(Map<String, IPeripheral> peripherals) {
|
void updatePeripherals(Map<String, IPeripheral> peripherals);
|
||||||
getNetwork().updatePeripherals(this, peripherals);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,13 +48,8 @@ import java.util.function.Function;
|
|||||||
* }
|
* }
|
||||||
* }</pre>
|
* }</pre>
|
||||||
* <p>
|
* <p>
|
||||||
* Finally, we need to register a model for our upgrade. This is done with
|
* 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.ComputerCraftAPIClient#registerTurtleUpgradeModeller}:
|
* {@link dan200.computercraft.api.client.turtle.TurtleUpgradeModeller} for more information.
|
||||||
*
|
|
||||||
* <pre>{@code
|
|
||||||
* // Register our model inside FMLClientSetupEvent
|
|
||||||
* ComputerCraftAPIClient.registerTurtleUpgradeModeller(MY_UPGRADE.get(), TurtleUpgradeModeller.flatItem())
|
|
||||||
* }</pre>
|
|
||||||
* <p>
|
* <p>
|
||||||
* {@link TurtleUpgradeDataProvider} provides a data provider to aid with generating these JSON files.
|
* {@link TurtleUpgradeDataProvider} provides a data provider to aid with generating these JSON files.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ import net.minecraft.data.PackOutput;
|
|||||||
import net.minecraft.resources.ResourceKey;
|
import net.minecraft.resources.ResourceKey;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.world.item.Item;
|
import net.minecraft.world.item.Item;
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -36,8 +34,6 @@ import java.util.function.Function;
|
|||||||
* @param <R> The upgrade serialiser to register for.
|
* @param <R> The upgrade serialiser to register for.
|
||||||
*/
|
*/
|
||||||
public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends UpgradeSerialiser<? extends T>> implements DataProvider {
|
public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends UpgradeSerialiser<? extends T>> implements DataProvider {
|
||||||
private static final Logger LOGGER = LogManager.getLogger();
|
|
||||||
|
|
||||||
private final PackOutput output;
|
private final PackOutput output;
|
||||||
private final String name;
|
private final String name;
|
||||||
private final String folder;
|
private final String folder;
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ public final class Services {
|
|||||||
* @throws IllegalStateException When the service cannot be loaded.
|
* @throws IllegalStateException When the service cannot be loaded.
|
||||||
*/
|
*/
|
||||||
public static <T> T load(Class<T> klass) {
|
public static <T> T load(Class<T> klass) {
|
||||||
var services = ServiceLoader.load(klass).stream().toList();
|
var services = ServiceLoader.load(klass, klass.getClassLoader()).stream().toList();
|
||||||
return switch (services.size()) {
|
return switch (services.size()) {
|
||||||
case 1 -> services.get(0).get();
|
case 1 -> services.get(0).get();
|
||||||
case 0 -> throw new IllegalStateException("Cannot find service for " + klass.getName());
|
case 0 -> throw new IllegalStateException("Cannot find service for " + klass.getName());
|
||||||
|
|||||||
@@ -2,14 +2,13 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
import cc.tweaked.gradle.annotationProcessorEverywhere
|
import cc.tweaked.gradle.*
|
||||||
import cc.tweaked.gradle.clientClasses
|
|
||||||
import cc.tweaked.gradle.commonClasses
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("cc-tweaked.publishing")
|
|
||||||
id("cc-tweaked.vanilla")
|
id("cc-tweaked.vanilla")
|
||||||
id("cc-tweaked.gametest")
|
id("cc-tweaked.gametest")
|
||||||
|
id("cc-tweaked.illuaminate")
|
||||||
|
id("cc-tweaked.publishing")
|
||||||
}
|
}
|
||||||
|
|
||||||
minecraft {
|
minecraft {
|
||||||
@@ -19,6 +18,18 @@ minecraft {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
register("cctJavadoc")
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
maven("https://maven.minecraftforge.net/") {
|
||||||
|
content {
|
||||||
|
includeModule("org.spongepowered", "mixin")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// Pull in our other projects. See comments in MinecraftConfigurations on this nastiness.
|
// Pull in our other projects. See comments in MinecraftConfigurations on this nastiness.
|
||||||
implementation(project(":core"))
|
implementation(project(":core"))
|
||||||
@@ -28,7 +39,6 @@ dependencies {
|
|||||||
compileOnly(libs.bundles.externalMods.common)
|
compileOnly(libs.bundles.externalMods.common)
|
||||||
clientCompileOnly(variantOf(libs.emi) { classifier("api") })
|
clientCompileOnly(variantOf(libs.emi) { classifier("api") })
|
||||||
|
|
||||||
compileOnly(libs.mixin)
|
|
||||||
annotationProcessorEverywhere(libs.autoService)
|
annotationProcessorEverywhere(libs.autoService)
|
||||||
testFixturesAnnotationProcessor(libs.autoService)
|
testFixturesAnnotationProcessor(libs.autoService)
|
||||||
|
|
||||||
@@ -36,7 +46,62 @@ dependencies {
|
|||||||
testImplementation(libs.bundles.test)
|
testImplementation(libs.bundles.test)
|
||||||
testRuntimeOnly(libs.bundles.testRuntime)
|
testRuntimeOnly(libs.bundles.testRuntime)
|
||||||
|
|
||||||
|
testImplementation(libs.jmh)
|
||||||
|
testAnnotationProcessor(libs.jmh.processor)
|
||||||
|
|
||||||
|
testModCompileOnly(libs.mixin)
|
||||||
testModImplementation(testFixtures(project(":core")))
|
testModImplementation(testFixtures(project(":core")))
|
||||||
testModImplementation(testFixtures(project(":common")))
|
testModImplementation(testFixtures(project(":common")))
|
||||||
testModImplementation(libs.bundles.kotlin)
|
testModImplementation(libs.bundles.kotlin)
|
||||||
|
|
||||||
|
testFixturesImplementation(testFixtures(project(":core")))
|
||||||
|
|
||||||
|
"cctJavadoc"(libs.cctJavadoc)
|
||||||
|
}
|
||||||
|
|
||||||
|
illuaminate {
|
||||||
|
version.set(libs.versions.illuaminate)
|
||||||
|
}
|
||||||
|
|
||||||
|
val luaJavadoc by tasks.registering(Javadoc::class) {
|
||||||
|
description = "Generates documentation for Java-side Lua functions."
|
||||||
|
group = JavaBasePlugin.DOCUMENTATION_GROUP
|
||||||
|
|
||||||
|
val sourceSets = listOf(sourceSets.main.get(), project(":core").sourceSets.main.get())
|
||||||
|
for (sourceSet in sourceSets) {
|
||||||
|
source(sourceSet.java)
|
||||||
|
classpath += sourceSet.compileClasspath
|
||||||
|
}
|
||||||
|
|
||||||
|
destinationDir = layout.buildDirectory.dir("docs/luaJavadoc").get().asFile
|
||||||
|
|
||||||
|
val options = options as StandardJavadocDocletOptions
|
||||||
|
options.docletpath = configurations["cctJavadoc"].files.toList()
|
||||||
|
options.doclet = "cc.tweaked.javadoc.LuaDoclet"
|
||||||
|
options.addStringOption("project-root", rootProject.file(".").absolutePath)
|
||||||
|
options.noTimestamp(false)
|
||||||
|
|
||||||
|
javadocTool.set(
|
||||||
|
javaToolchains.javadocToolFor {
|
||||||
|
languageVersion.set(CCTweakedPlugin.JAVA_VERSION)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val lintLua by tasks.registering(IlluaminateExec::class) {
|
||||||
|
group = JavaBasePlugin.VERIFICATION_GROUP
|
||||||
|
description = "Lint Lua (and Lua docs) with illuaminate"
|
||||||
|
|
||||||
|
// Config files
|
||||||
|
inputs.file(rootProject.file("illuaminate.sexp")).withPropertyName("illuaminate.sexp")
|
||||||
|
// Sources
|
||||||
|
inputs.files(rootProject.fileTree("doc")).withPropertyName("docs")
|
||||||
|
inputs.files(project(":core").fileTree("src/main/resources/data/computercraft/lua")).withPropertyName("lua rom")
|
||||||
|
inputs.files(luaJavadoc)
|
||||||
|
|
||||||
|
args = listOf("lint")
|
||||||
|
workingDir = rootProject.projectDir
|
||||||
|
|
||||||
|
doFirst { if (System.getenv("GITHUB_ACTIONS") != null) println("::add-matcher::.github/matchers/illuaminate.json") }
|
||||||
|
doLast { if (System.getenv("GITHUB_ACTIONS") != null) println("::remove-matcher owner=illuaminate::") }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,6 @@ import dan200.computercraft.client.render.monitor.MonitorRenderState;
|
|||||||
import dan200.computercraft.client.sound.SpeakerManager;
|
import dan200.computercraft.client.sound.SpeakerManager;
|
||||||
import dan200.computercraft.shared.CommonHooks;
|
import dan200.computercraft.shared.CommonHooks;
|
||||||
import dan200.computercraft.shared.ModRegistry;
|
import dan200.computercraft.shared.ModRegistry;
|
||||||
import dan200.computercraft.shared.command.CommandComputerCraft;
|
|
||||||
import dan200.computercraft.shared.computer.core.ServerContext;
|
|
||||||
import dan200.computercraft.shared.media.items.PrintoutItem;
|
import dan200.computercraft.shared.media.items.PrintoutItem;
|
||||||
import dan200.computercraft.shared.peripheral.modem.wired.CableBlock;
|
import dan200.computercraft.shared.peripheral.modem.wired.CableBlock;
|
||||||
import dan200.computercraft.shared.peripheral.modem.wired.CableModemVariant;
|
import dan200.computercraft.shared.peripheral.modem.wired.CableModemVariant;
|
||||||
@@ -28,7 +26,6 @@ import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
|||||||
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
|
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
|
||||||
import dan200.computercraft.shared.util.PauseAwareTimer;
|
import dan200.computercraft.shared.util.PauseAwareTimer;
|
||||||
import dan200.computercraft.shared.util.WorldUtil;
|
import dan200.computercraft.shared.util.WorldUtil;
|
||||||
import net.minecraft.Util;
|
|
||||||
import net.minecraft.client.Camera;
|
import net.minecraft.client.Camera;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.renderer.MultiBufferSource;
|
import net.minecraft.client.renderer.MultiBufferSource;
|
||||||
@@ -43,7 +40,6 @@ import net.minecraft.world.phys.BlockHitResult;
|
|||||||
import net.minecraft.world.phys.HitResult;
|
import net.minecraft.world.phys.HitResult;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.io.File;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -71,10 +67,6 @@ public final class ClientHooks {
|
|||||||
ClientPocketComputers.reset();
|
ClientPocketComputers.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean onChatMessage(String message) {
|
|
||||||
return handleOpenComputerCommand(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean drawHighlight(PoseStack transform, MultiBufferSource bufferSource, Camera camera, BlockHitResult hit) {
|
public static boolean drawHighlight(PoseStack transform, MultiBufferSource bufferSource, Camera camera, BlockHitResult hit) {
|
||||||
return CableHighlightRenderer.drawHighlight(transform, bufferSource, camera, hit)
|
return CableHighlightRenderer.drawHighlight(transform, bufferSource, camera, hit)
|
||||||
|| MonitorHighlightRenderer.drawHighlight(transform, bufferSource, camera, hit);
|
|| MonitorHighlightRenderer.drawHighlight(transform, bufferSource, camera, hit);
|
||||||
@@ -109,34 +101,6 @@ public final class ClientHooks {
|
|||||||
SpeakerManager.onPlayStreaming(engine, channel, stream);
|
SpeakerManager.onPlayStreaming(engine, channel, stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle the {@link CommandComputerCraft#OPEN_COMPUTER} "clientside command". This isn't a true command, as we
|
|
||||||
* don't want it to actually be visible to the user.
|
|
||||||
*
|
|
||||||
* @param message The current chat message.
|
|
||||||
* @return Whether to cancel sending this message.
|
|
||||||
*/
|
|
||||||
private static boolean handleOpenComputerCommand(String message) {
|
|
||||||
if (!message.startsWith(CommandComputerCraft.OPEN_COMPUTER)) return false;
|
|
||||||
|
|
||||||
var server = Minecraft.getInstance().getSingleplayerServer();
|
|
||||||
if (server == null) return false;
|
|
||||||
|
|
||||||
var idStr = message.substring(CommandComputerCraft.OPEN_COMPUTER.length()).trim();
|
|
||||||
int id;
|
|
||||||
try {
|
|
||||||
id = Integer.parseInt(idStr);
|
|
||||||
} catch (NumberFormatException ignore) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var file = new File(ServerContext.get(server).storageDir().toFile(), "computer/" + id);
|
|
||||||
if (!file.isDirectory()) return false;
|
|
||||||
|
|
||||||
Util.getPlatform().openFile(file);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add additional information about the currently targeted block to the debug screen.
|
* Add additional information about the currently targeted block to the debug screen.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -4,8 +4,12 @@
|
|||||||
|
|
||||||
package dan200.computercraft.client;
|
package dan200.computercraft.client;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.CommandDispatcher;
|
||||||
|
import com.mojang.brigadier.arguments.IntegerArgumentType;
|
||||||
|
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||||
|
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
|
||||||
import dan200.computercraft.api.ComputerCraftAPI;
|
import dan200.computercraft.api.ComputerCraftAPI;
|
||||||
import dan200.computercraft.api.client.ComputerCraftAPIClient;
|
import dan200.computercraft.api.client.turtle.RegisterTurtleUpgradeModeller;
|
||||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
|
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
|
||||||
import dan200.computercraft.client.gui.*;
|
import dan200.computercraft.client.gui.*;
|
||||||
import dan200.computercraft.client.pocket.ClientPocketComputers;
|
import dan200.computercraft.client.pocket.ClientPocketComputers;
|
||||||
@@ -16,11 +20,15 @@ import dan200.computercraft.client.turtle.TurtleModemModeller;
|
|||||||
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
|
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.common.IColouredItem;
|
import dan200.computercraft.shared.common.IColouredItem;
|
||||||
|
import dan200.computercraft.shared.computer.core.ComputerState;
|
||||||
|
import dan200.computercraft.shared.computer.core.ServerContext;
|
||||||
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
|
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
|
||||||
import dan200.computercraft.shared.computer.inventory.ViewComputerMenu;
|
import dan200.computercraft.shared.computer.inventory.ViewComputerMenu;
|
||||||
import dan200.computercraft.shared.media.items.DiskItem;
|
import dan200.computercraft.shared.media.items.DiskItem;
|
||||||
import dan200.computercraft.shared.media.items.TreasureDiskItem;
|
import dan200.computercraft.shared.media.items.TreasureDiskItem;
|
||||||
|
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;
|
||||||
@@ -30,6 +38,7 @@ import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
|
|||||||
import net.minecraft.client.renderer.blockentity.BlockEntityRenderers;
|
import net.minecraft.client.renderer.blockentity.BlockEntityRenderers;
|
||||||
import net.minecraft.client.renderer.item.ClampedItemPropertyFunction;
|
import net.minecraft.client.renderer.item.ClampedItemPropertyFunction;
|
||||||
import net.minecraft.client.renderer.item.ItemProperties;
|
import net.minecraft.client.renderer.item.ItemProperties;
|
||||||
|
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;
|
||||||
@@ -39,6 +48,7 @@ import net.minecraft.world.item.ItemStack;
|
|||||||
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.IOException;
|
import java.io.IOException;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
@@ -60,18 +70,6 @@ public final class ClientRegistry {
|
|||||||
* Register any client-side objects which don't have to be done on the main thread.
|
* Register any client-side objects which don't have to be done on the main thread.
|
||||||
*/
|
*/
|
||||||
public static void register() {
|
public static void register() {
|
||||||
ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.SPEAKER.get(), TurtleUpgradeModeller.sided(
|
|
||||||
new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_left"),
|
|
||||||
new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_right")
|
|
||||||
));
|
|
||||||
ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.WORKBENCH.get(), TurtleUpgradeModeller.sided(
|
|
||||||
new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_left"),
|
|
||||||
new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_right")
|
|
||||||
));
|
|
||||||
ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.WIRELESS_MODEM_NORMAL.get(), new TurtleModemModeller(false));
|
|
||||||
ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.WIRELESS_MODEM_ADVANCED.get(), new TurtleModemModeller(true));
|
|
||||||
ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.TOOL.get(), TurtleUpgradeModeller.flatItem());
|
|
||||||
|
|
||||||
BlockEntityRenderers.register(ModRegistry.BlockEntities.MONITOR_NORMAL.get(), MonitorBlockEntityRenderer::new);
|
BlockEntityRenderers.register(ModRegistry.BlockEntities.MONITOR_NORMAL.get(), MonitorBlockEntityRenderer::new);
|
||||||
BlockEntityRenderers.register(ModRegistry.BlockEntities.MONITOR_ADVANCED.get(), MonitorBlockEntityRenderer::new);
|
BlockEntityRenderers.register(ModRegistry.BlockEntities.MONITOR_ADVANCED.get(), MonitorBlockEntityRenderer::new);
|
||||||
BlockEntityRenderers.register(ModRegistry.BlockEntities.TURTLE_NORMAL.get(), TurtleBlockEntityRenderer::new);
|
BlockEntityRenderers.register(ModRegistry.BlockEntities.TURTLE_NORMAL.get(), TurtleBlockEntityRenderer::new);
|
||||||
@@ -94,7 +92,10 @@ public final class ClientRegistry {
|
|||||||
MenuScreens.<ViewComputerMenu, ComputerScreen<ViewComputerMenu>>register(ModRegistry.Menus.VIEW_COMPUTER.get(), ComputerScreen::new);
|
MenuScreens.<ViewComputerMenu, ComputerScreen<ViewComputerMenu>>register(ModRegistry.Menus.VIEW_COMPUTER.get(), ComputerScreen::new);
|
||||||
|
|
||||||
registerItemProperty("state",
|
registerItemProperty("state",
|
||||||
new UnclampedPropertyFunction((stack, world, player, random) -> ClientPocketComputers.get(stack).getState().ordinal()),
|
new UnclampedPropertyFunction((stack, world, player, random) -> {
|
||||||
|
var computer = ClientPocketComputers.get(stack);
|
||||||
|
return (computer == null ? ComputerState.OFF : computer.getState()).ordinal();
|
||||||
|
}),
|
||||||
ModRegistry.Items.POCKET_COMPUTER_NORMAL, ModRegistry.Items.POCKET_COMPUTER_ADVANCED
|
ModRegistry.Items.POCKET_COMPUTER_NORMAL, ModRegistry.Items.POCKET_COMPUTER_ADVANCED
|
||||||
);
|
);
|
||||||
registerItemProperty("coloured",
|
registerItemProperty("coloured",
|
||||||
@@ -103,6 +104,20 @@ public final class ClientRegistry {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void registerTurtleModellers(RegisterTurtleUpgradeModeller register) {
|
||||||
|
register.register(ModRegistry.TurtleSerialisers.SPEAKER.get(), TurtleUpgradeModeller.sided(
|
||||||
|
new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_left"),
|
||||||
|
new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_right")
|
||||||
|
));
|
||||||
|
register.register(ModRegistry.TurtleSerialisers.WORKBENCH.get(), TurtleUpgradeModeller.sided(
|
||||||
|
new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_left"),
|
||||||
|
new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_right")
|
||||||
|
));
|
||||||
|
register.register(ModRegistry.TurtleSerialisers.WIRELESS_MODEM_NORMAL.get(), new TurtleModemModeller(false));
|
||||||
|
register.register(ModRegistry.TurtleSerialisers.WIRELESS_MODEM_ADVANCED.get(), new TurtleModemModeller(true));
|
||||||
|
register.register(ModRegistry.TurtleSerialisers.TOOL.get(), TurtleUpgradeModeller.flatItem());
|
||||||
|
}
|
||||||
|
|
||||||
@SafeVarargs
|
@SafeVarargs
|
||||||
private static void registerItemProperty(String name, ClampedItemPropertyFunction getter, Supplier<? extends Item>... items) {
|
private static void registerItemProperty(String name, ClampedItemPropertyFunction getter, Supplier<? extends Item>... items) {
|
||||||
var id = new ResourceLocation(ComputerCraftAPI.MOD_ID, name);
|
var id = new ResourceLocation(ComputerCraftAPI.MOD_ID, name);
|
||||||
@@ -144,17 +159,14 @@ public final class ClientRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static int getPocketColour(ItemStack stack, int layer) {
|
private static int getPocketColour(ItemStack stack, int layer) {
|
||||||
switch (layer) {
|
return switch (layer) {
|
||||||
case 0:
|
default -> 0xFFFFFF;
|
||||||
default:
|
case 1 -> IColouredItem.getColourBasic(stack); // Frame colour
|
||||||
return 0xFFFFFF;
|
case 2 -> { // Light colour
|
||||||
case 1: // Frame colour
|
var computer = ClientPocketComputers.get(stack);
|
||||||
return IColouredItem.getColourBasic(stack);
|
yield computer == null || computer.getLightState() == -1 ? Colour.BLACK.getHex() : computer.getLightState();
|
||||||
case 2: { // Light colour
|
|
||||||
var light = ClientPocketComputers.get(stack).getLightState();
|
|
||||||
return light == -1 ? Colour.BLACK.getHex() : light;
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int getTurtleColour(ItemStack stack, int layer) {
|
private static int getTurtleColour(ItemStack stack, int layer) {
|
||||||
@@ -179,4 +191,45 @@ public final class ClientRegistry {
|
|||||||
return function.unclampedCall(stack, level, entity, layer);
|
return function.unclampedCall(stack, level, entity, layer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register client-side commands.
|
||||||
|
*
|
||||||
|
* @param dispatcher The dispatcher to register the commands to.
|
||||||
|
* @param sendError A function to send an error message.
|
||||||
|
* @param <T> The type of the client-side command context.
|
||||||
|
*/
|
||||||
|
public static <T> void registerClientCommands(CommandDispatcher<T> dispatcher, BiConsumer<T, Component> sendError) {
|
||||||
|
dispatcher.register(LiteralArgumentBuilder.<T>literal(CommandComputerCraft.CLIENT_OPEN_FOLDER)
|
||||||
|
.requires(x -> Minecraft.getInstance().getSingleplayerServer() != null)
|
||||||
|
.then(RequiredArgumentBuilder.<T, Integer>argument("computer_id", IntegerArgumentType.integer(0))
|
||||||
|
.executes(c -> handleOpenComputerCommand(c.getSource(), sendError, c.getArgument("computer_id", Integer.class)))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the {@link CommandComputerCraft#CLIENT_OPEN_FOLDER} command.
|
||||||
|
*
|
||||||
|
* @param context The command context.
|
||||||
|
* @param sendError A function to send an error message.
|
||||||
|
* @param id The computer's id.
|
||||||
|
* @param <T> The type of the client-side command context.
|
||||||
|
* @return {@code 1} if a folder was opened, {@code 0} otherwise.
|
||||||
|
*/
|
||||||
|
private static <T> int handleOpenComputerCommand(T context, BiConsumer<T, Component> sendError, int id) {
|
||||||
|
var server = Minecraft.getInstance().getSingleplayerServer();
|
||||||
|
if (server == null) {
|
||||||
|
sendError.accept(context, Component.literal("Not on a single-player server"));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var file = new File(ServerContext.get(server).storageDir().toFile(), "computer/" + id);
|
||||||
|
if (!file.isDirectory()) {
|
||||||
|
sendError.accept(context, Component.literal("Computer's folder does not exist"));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Util.getPlatform().openFile(file);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ package dan200.computercraft.client.gui;
|
|||||||
import dan200.computercraft.client.gui.widgets.ComputerSidebar;
|
import dan200.computercraft.client.gui.widgets.ComputerSidebar;
|
||||||
import dan200.computercraft.client.gui.widgets.DynamicImageButton;
|
import dan200.computercraft.client.gui.widgets.DynamicImageButton;
|
||||||
import dan200.computercraft.client.gui.widgets.TerminalWidget;
|
import dan200.computercraft.client.gui.widgets.TerminalWidget;
|
||||||
import dan200.computercraft.client.platform.ClientPlatformHelper;
|
import dan200.computercraft.client.network.ClientNetworking;
|
||||||
import dan200.computercraft.core.terminal.Terminal;
|
import dan200.computercraft.core.terminal.Terminal;
|
||||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||||
import dan200.computercraft.shared.computer.core.InputHandler;
|
import dan200.computercraft.shared.computer.core.InputHandler;
|
||||||
@@ -19,6 +19,7 @@ import dan200.computercraft.shared.network.server.UploadFileMessage;
|
|||||||
import net.minecraft.ChatFormatting;
|
import net.minecraft.ChatFormatting;
|
||||||
import net.minecraft.Util;
|
import net.minecraft.Util;
|
||||||
import net.minecraft.client.gui.GuiGraphics;
|
import net.minecraft.client.gui.GuiGraphics;
|
||||||
|
import net.minecraft.client.gui.components.events.GuiEventListener;
|
||||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||||
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;
|
||||||
@@ -33,7 +34,6 @@ import java.nio.ByteBuffer;
|
|||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@@ -145,6 +145,11 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
|
|||||||
|| super.mouseDragged(x, y, button, deltaX, deltaY);
|
|| super.mouseDragged(x, y, button, deltaX, deltaY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFocused(@Nullable GuiEventListener listener) {
|
||||||
|
// Don't clear and re-focus if we're already focused.
|
||||||
|
if (listener != getFocused()) super.setFocused(listener);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void renderLabels(GuiGraphics graphics, int mouseX, int mouseY) {
|
protected void renderLabels(GuiGraphics graphics, int mouseX, int mouseY) {
|
||||||
@@ -202,7 +207,7 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toUpload.size() > 0) UploadFileMessage.send(menu, toUpload, ClientPlatformHelper.get()::sendToServer);
|
if (toUpload.size() > 0) UploadFileMessage.send(menu, toUpload, ClientNetworking::sendToServer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void uploadResult(UploadResult result, @Nullable Component message) {
|
public void uploadResult(UploadResult result, @Nullable Component message) {
|
||||||
@@ -219,7 +224,7 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
|
|||||||
|
|
||||||
private void alert(Component title, Component message) {
|
private void alert(Component title, Component message) {
|
||||||
OptionScreen.show(minecraft, title, message,
|
OptionScreen.show(minecraft, title, message,
|
||||||
Collections.singletonList(OptionScreen.newButton(OK, b -> minecraft.setScreen(this))),
|
List.of(OptionScreen.newButton(OK, b -> minecraft.setScreen(this))),
|
||||||
() -> minecraft.setScreen(this)
|
() -> minecraft.setScreen(this)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
package dan200.computercraft.client.gui;
|
package dan200.computercraft.client.gui;
|
||||||
|
|
||||||
import dan200.computercraft.client.platform.ClientPlatformHelper;
|
import dan200.computercraft.client.network.ClientNetworking;
|
||||||
import dan200.computercraft.shared.computer.core.InputHandler;
|
import dan200.computercraft.shared.computer.core.InputHandler;
|
||||||
import dan200.computercraft.shared.computer.menu.ComputerMenu;
|
import dan200.computercraft.shared.computer.menu.ComputerMenu;
|
||||||
import dan200.computercraft.shared.network.server.ComputerActionServerMessage;
|
import dan200.computercraft.shared.network.server.ComputerActionServerMessage;
|
||||||
@@ -29,51 +29,51 @@ public final class ClientInputHandler implements InputHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void turnOn() {
|
public void turnOn() {
|
||||||
ClientPlatformHelper.get().sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.TURN_ON));
|
ClientNetworking.sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.TURN_ON));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
ClientPlatformHelper.get().sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.SHUTDOWN));
|
ClientNetworking.sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.SHUTDOWN));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reboot() {
|
public void reboot() {
|
||||||
ClientPlatformHelper.get().sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.REBOOT));
|
ClientNetworking.sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.REBOOT));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void queueEvent(String event, @Nullable Object[] arguments) {
|
public void queueEvent(String event, @Nullable Object[] arguments) {
|
||||||
ClientPlatformHelper.get().sendToServer(new QueueEventServerMessage(menu, event, arguments));
|
ClientNetworking.sendToServer(new QueueEventServerMessage(menu, event, arguments));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void keyDown(int key, boolean repeat) {
|
public void keyDown(int key, boolean repeat) {
|
||||||
ClientPlatformHelper.get().sendToServer(new KeyEventServerMessage(menu, repeat ? KeyEventServerMessage.TYPE_REPEAT : KeyEventServerMessage.TYPE_DOWN, key));
|
ClientNetworking.sendToServer(new KeyEventServerMessage(menu, repeat ? KeyEventServerMessage.TYPE_REPEAT : KeyEventServerMessage.TYPE_DOWN, key));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void keyUp(int key) {
|
public void keyUp(int key) {
|
||||||
ClientPlatformHelper.get().sendToServer(new KeyEventServerMessage(menu, KeyEventServerMessage.TYPE_UP, key));
|
ClientNetworking.sendToServer(new KeyEventServerMessage(menu, KeyEventServerMessage.TYPE_UP, key));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mouseClick(int button, int x, int y) {
|
public void mouseClick(int button, int x, int y) {
|
||||||
ClientPlatformHelper.get().sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_CLICK, button, x, y));
|
ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_CLICK, button, x, y));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mouseUp(int button, int x, int y) {
|
public void mouseUp(int button, int x, int y) {
|
||||||
ClientPlatformHelper.get().sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_UP, button, x, y));
|
ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_UP, button, x, y));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mouseDrag(int button, int x, int y) {
|
public void mouseDrag(int button, int x, int y) {
|
||||||
ClientPlatformHelper.get().sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_DRAG, button, x, y));
|
ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_DRAG, button, x, y));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mouseScroll(int direction, int x, int y) {
|
public void mouseScroll(int direction, int x, int y) {
|
||||||
ClientPlatformHelper.get().sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_SCROLL, direction, x, y));
|
ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_SCROLL, direction, x, y));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import com.mojang.blaze3d.vertex.Tesselator;
|
|||||||
import dan200.computercraft.client.render.RenderTypes;
|
import dan200.computercraft.client.render.RenderTypes;
|
||||||
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
|
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
|
||||||
import dan200.computercraft.core.terminal.Terminal;
|
import dan200.computercraft.core.terminal.Terminal;
|
||||||
|
import dan200.computercraft.core.util.StringUtil;
|
||||||
import dan200.computercraft.shared.computer.core.InputHandler;
|
import dan200.computercraft.shared.computer.core.InputHandler;
|
||||||
import net.minecraft.SharedConstants;
|
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.gui.GuiGraphics;
|
import net.minecraft.client.gui.GuiGraphics;
|
||||||
import net.minecraft.client.gui.components.AbstractWidget;
|
import net.minecraft.client.gui.components.AbstractWidget;
|
||||||
@@ -112,26 +112,8 @@ public class TerminalWidget extends AbstractWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void paste() {
|
private void paste() {
|
||||||
var clipboard = Minecraft.getInstance().keyboardHandler.getClipboard();
|
var clipboard = StringUtil.normaliseClipboardString(Minecraft.getInstance().keyboardHandler.getClipboard());
|
||||||
|
if (!clipboard.isEmpty()) computer.queueEvent("paste", new Object[]{ clipboard });
|
||||||
// Clip to the first occurrence of \r or \n
|
|
||||||
var newLineIndex1 = clipboard.indexOf('\r');
|
|
||||||
var newLineIndex2 = clipboard.indexOf('\n');
|
|
||||||
if (newLineIndex1 >= 0 && newLineIndex2 >= 0) {
|
|
||||||
clipboard = clipboard.substring(0, Math.min(newLineIndex1, newLineIndex2));
|
|
||||||
} else if (newLineIndex1 >= 0) {
|
|
||||||
clipboard = clipboard.substring(0, newLineIndex1);
|
|
||||||
} else if (newLineIndex2 >= 0) {
|
|
||||||
clipboard = clipboard.substring(0, newLineIndex2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter the string
|
|
||||||
clipboard = SharedConstants.filterText(clipboard);
|
|
||||||
if (!clipboard.isEmpty()) {
|
|
||||||
// Clip to 512 characters and queue the event
|
|
||||||
if (clipboard.length() > 512) clipboard = clipboard.substring(0, 512);
|
|
||||||
computer.queueEvent("paste", new Object[]{ clipboard });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -264,7 +246,7 @@ public class TerminalWidget extends AbstractWidget {
|
|||||||
keysDown.clear();
|
keysDown.clear();
|
||||||
|
|
||||||
// When blurring, we should make the last mouse button go up
|
// When blurring, we should make the last mouse button go up
|
||||||
if (lastMouseButton > 0) {
|
if (lastMouseButton >= 0) {
|
||||||
computer.mouseUp(lastMouseButton + 1, lastMouseX + 1, lastMouseY + 1);
|
computer.mouseUp(lastMouseButton + 1, lastMouseX + 1, lastMouseY + 1);
|
||||||
lastMouseButton = -1;
|
lastMouseButton = -1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.client.network;
|
||||||
|
|
||||||
|
import dan200.computercraft.client.platform.ClientPlatformHelper;
|
||||||
|
import dan200.computercraft.shared.network.NetworkMessage;
|
||||||
|
import dan200.computercraft.shared.network.server.ServerNetworkContext;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Methods for sending packets from clients to the server.
|
||||||
|
*/
|
||||||
|
public final class ClientNetworking {
|
||||||
|
private ClientNetworking() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a network message to the server.
|
||||||
|
*
|
||||||
|
* @param message The message to send.
|
||||||
|
*/
|
||||||
|
public static void sendToServer(NetworkMessage<ServerNetworkContext> message) {
|
||||||
|
var connection = Minecraft.getInstance().getConnection();
|
||||||
|
if (connection != null) connection.send(ClientPlatformHelper.get().createPacket(message));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
package dan200.computercraft.client.platform;
|
package dan200.computercraft.client.platform;
|
||||||
|
|
||||||
|
import com.google.auto.service.AutoService;
|
||||||
import dan200.computercraft.client.ClientTableFormatter;
|
import dan200.computercraft.client.ClientTableFormatter;
|
||||||
import dan200.computercraft.client.gui.AbstractComputerScreen;
|
import dan200.computercraft.client.gui.AbstractComputerScreen;
|
||||||
import dan200.computercraft.client.gui.OptionScreen;
|
import dan200.computercraft.client.gui.OptionScreen;
|
||||||
@@ -16,12 +17,13 @@ import dan200.computercraft.shared.computer.terminal.TerminalState;
|
|||||||
import dan200.computercraft.shared.computer.upload.UploadResult;
|
import dan200.computercraft.shared.computer.upload.UploadResult;
|
||||||
import dan200.computercraft.shared.network.client.ClientNetworkContext;
|
import dan200.computercraft.shared.network.client.ClientNetworkContext;
|
||||||
import dan200.computercraft.shared.peripheral.monitor.MonitorBlockEntity;
|
import dan200.computercraft.shared.peripheral.monitor.MonitorBlockEntity;
|
||||||
|
import dan200.computercraft.shared.peripheral.speaker.EncodedAudio;
|
||||||
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
|
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
|
||||||
import io.netty.buffer.ByteBuf;
|
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.sounds.SoundEvent;
|
||||||
import net.minecraft.world.entity.player.Player;
|
import net.minecraft.world.entity.player.Player;
|
||||||
import net.minecraft.world.level.Level;
|
import net.minecraft.world.level.Level;
|
||||||
|
|
||||||
@@ -29,18 +31,17 @@ import javax.annotation.Nullable;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The base implementation of {@link ClientNetworkContext}.
|
* The client-side implementation of {@link ClientNetworkContext}.
|
||||||
* <p>
|
|
||||||
* This should be extended by mod loader specific modules with the remaining abstract methods.
|
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractClientNetworkContext implements ClientNetworkContext {
|
@AutoService(ClientNetworkContext.class)
|
||||||
|
public final class ClientNetworkContextImpl implements ClientNetworkContext {
|
||||||
@Override
|
@Override
|
||||||
public final void handleChatTable(TableBuilder table) {
|
public void handleChatTable(TableBuilder table) {
|
||||||
ClientTableFormatter.INSTANCE.display(table);
|
ClientTableFormatter.INSTANCE.display(table);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void handleComputerTerminal(int containerId, TerminalState terminal) {
|
public void handleComputerTerminal(int containerId, TerminalState terminal) {
|
||||||
Player player = Minecraft.getInstance().player;
|
Player player = Minecraft.getInstance().player;
|
||||||
if (player != null && player.containerMenu.containerId == containerId && player.containerMenu instanceof ComputerMenu menu) {
|
if (player != null && player.containerMenu.containerId == containerId && player.containerMenu instanceof ComputerMenu menu) {
|
||||||
menu.updateTerminal(terminal);
|
menu.updateTerminal(terminal);
|
||||||
@@ -48,7 +49,7 @@ public abstract class AbstractClientNetworkContext implements ClientNetworkConte
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void handleMonitorData(BlockPos pos, TerminalState terminal) {
|
public void handleMonitorData(BlockPos pos, TerminalState terminal) {
|
||||||
var player = Minecraft.getInstance().player;
|
var player = Minecraft.getInstance().player;
|
||||||
if (player == null) return;
|
if (player == null) return;
|
||||||
|
|
||||||
@@ -59,44 +60,44 @@ public abstract class AbstractClientNetworkContext implements ClientNetworkConte
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void handlePocketComputerData(int instanceId, ComputerState state, int lightState, TerminalState terminal) {
|
public void handlePlayRecord(BlockPos pos, @Nullable SoundEvent sound, @Nullable String name) {
|
||||||
var computer = ClientPocketComputers.get(instanceId, terminal.colour);
|
var mc = Minecraft.getInstance();
|
||||||
computer.setState(state, lightState);
|
ClientPlatformHelper.get().playStreamingMusic(pos, sound);
|
||||||
if (terminal.hasTerminal()) computer.setTerminal(terminal);
|
if (name != null) mc.gui.setNowPlaying(Component.literal(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void handlePocketComputerDeleted(int instanceId) {
|
public void handlePocketComputerData(UUID instanceId, ComputerState state, int lightState, TerminalState terminal) {
|
||||||
|
ClientPocketComputers.setState(instanceId, state, lightState, terminal);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handlePocketComputerDeleted(UUID instanceId) {
|
||||||
ClientPocketComputers.remove(instanceId);
|
ClientPocketComputers.remove(instanceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void handleSpeakerAudio(UUID source, SpeakerPosition.Message position, float volume) {
|
public void handleSpeakerAudio(UUID source, SpeakerPosition.Message position, float volume, EncodedAudio buffer) {
|
||||||
SpeakerManager.getSound(source).playAudio(reifyPosition(position), volume);
|
SpeakerManager.getSound(source).playAudio(reifyPosition(position), volume, buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void handleSpeakerAudioPush(UUID source, ByteBuf buffer) {
|
public void handleSpeakerMove(UUID source, SpeakerPosition.Message position) {
|
||||||
SpeakerManager.getSound(source).pushAudio(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final void handleSpeakerMove(UUID source, SpeakerPosition.Message position) {
|
|
||||||
SpeakerManager.moveSound(source, reifyPosition(position));
|
SpeakerManager.moveSound(source, reifyPosition(position));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void handleSpeakerPlay(UUID source, SpeakerPosition.Message position, ResourceLocation sound, float volume, float pitch) {
|
public void handleSpeakerPlay(UUID source, SpeakerPosition.Message position, ResourceLocation sound, float volume, float pitch) {
|
||||||
SpeakerManager.getSound(source).playSound(reifyPosition(position), sound, volume, pitch);
|
SpeakerManager.getSound(source).playSound(reifyPosition(position), sound, volume, pitch);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void handleSpeakerStop(UUID source) {
|
public void handleSpeakerStop(UUID source) {
|
||||||
SpeakerManager.stopSound(source);
|
SpeakerManager.stopSound(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void handleUploadResult(int containerId, UploadResult result, @Nullable Component errorMessage) {
|
public void handleUploadResult(int containerId, UploadResult result, @Nullable Component errorMessage) {
|
||||||
var minecraft = Minecraft.getInstance();
|
var minecraft = Minecraft.getInstance();
|
||||||
|
|
||||||
var screen = OptionScreen.unwrap(minecraft.screen);
|
var screen = OptionScreen.unwrap(minecraft.screen);
|
||||||
@@ -9,6 +9,10 @@ import dan200.computercraft.shared.network.NetworkMessage;
|
|||||||
import dan200.computercraft.shared.network.server.ServerNetworkContext;
|
import dan200.computercraft.shared.network.server.ServerNetworkContext;
|
||||||
import net.minecraft.client.renderer.MultiBufferSource;
|
import net.minecraft.client.renderer.MultiBufferSource;
|
||||||
import net.minecraft.client.resources.model.BakedModel;
|
import net.minecraft.client.resources.model.BakedModel;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.network.protocol.Packet;
|
||||||
|
import net.minecraft.network.protocol.game.ServerGamePacketListener;
|
||||||
|
import net.minecraft.sounds.SoundEvent;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@@ -18,11 +22,12 @@ public interface ClientPlatformHelper extends dan200.computercraft.impl.client.C
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a network message to the server.
|
* Convert a serverbound {@link NetworkMessage} to a Minecraft {@link Packet}.
|
||||||
*
|
*
|
||||||
* @param message The message to send.
|
* @param message The messsge to convert.
|
||||||
|
* @return The converted message.
|
||||||
*/
|
*/
|
||||||
void sendToServer(NetworkMessage<ServerNetworkContext> message);
|
Packet<ServerGamePacketListener> createPacket(NetworkMessage<ServerNetworkContext> message);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render a {@link BakedModel}, using any loader-specific hooks.
|
* Render a {@link BakedModel}, using any loader-specific hooks.
|
||||||
@@ -35,4 +40,13 @@ public interface ClientPlatformHelper extends dan200.computercraft.impl.client.C
|
|||||||
* @param tints Block colour tints to apply to the model.
|
* @param tints Block colour tints to apply to the model.
|
||||||
*/
|
*/
|
||||||
void renderBakedModel(PoseStack transform, MultiBufferSource buffers, BakedModel model, int lightmapCoord, int overlayLight, @Nullable int[] tints);
|
void renderBakedModel(PoseStack transform, MultiBufferSource buffers, BakedModel model, int lightmapCoord, int overlayLight, @Nullable int[] tints);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play a record at a particular position.
|
||||||
|
*
|
||||||
|
* @param pos The position to play this record.
|
||||||
|
* @param sound The record to play, or {@code null} to stop it.
|
||||||
|
* @see net.minecraft.client.renderer.LevelRenderer#playStreamingMusic(SoundEvent, BlockPos)
|
||||||
|
*/
|
||||||
|
void playStreamingMusic(BlockPos pos, @Nullable SoundEvent sound);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,21 +4,26 @@
|
|||||||
|
|
||||||
package dan200.computercraft.client.pocket;
|
package dan200.computercraft.client.pocket;
|
||||||
|
|
||||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
import dan200.computercraft.shared.computer.core.ComputerState;
|
||||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||||
|
import dan200.computercraft.shared.computer.terminal.NetworkedTerminal;
|
||||||
|
import dan200.computercraft.shared.computer.terminal.TerminalState;
|
||||||
import dan200.computercraft.shared.network.client.PocketComputerDataMessage;
|
import dan200.computercraft.shared.network.client.PocketComputerDataMessage;
|
||||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps {@link ServerComputer#getInstanceID()} to locals {@link PocketComputerData}.
|
* Maps {@link ServerComputer#getInstanceUUID()} to locals {@link PocketComputerData}.
|
||||||
* <p>
|
* <p>
|
||||||
* This is populated by {@link PocketComputerDataMessage} and accessed when rendering pocket computers
|
* This is populated by {@link PocketComputerDataMessage} and accessed when rendering pocket computers
|
||||||
*/
|
*/
|
||||||
public final class ClientPocketComputers {
|
public final class ClientPocketComputers {
|
||||||
private static final Int2ObjectMap<PocketComputerData> instances = new Int2ObjectOpenHashMap<>();
|
private static final Map<UUID, PocketComputerData> instances = new HashMap<>();
|
||||||
|
|
||||||
private ClientPocketComputers() {
|
private ClientPocketComputers() {
|
||||||
}
|
}
|
||||||
@@ -27,25 +32,32 @@ public final class ClientPocketComputers {
|
|||||||
instances.clear();
|
instances.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void remove(int id) {
|
public static void remove(UUID id) {
|
||||||
instances.remove(id);
|
instances.remove(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get or create a pocket computer.
|
* Set the state of a pocket computer.
|
||||||
*
|
*
|
||||||
* @param instanceId The instance ID of the pocket computer.
|
* @param instanceId The instance ID of the pocket computer.
|
||||||
* @param advanced Whether this computer has an advanced terminal.
|
* @param state The computer state of the pocket computer.
|
||||||
* @return The pocket computer data.
|
* @param lightColour The current colour of the modem light.
|
||||||
|
* @param terminalData The current terminal contents.
|
||||||
*/
|
*/
|
||||||
public static PocketComputerData get(int instanceId, boolean advanced) {
|
public static void setState(UUID instanceId, ComputerState state, int lightColour, TerminalState terminalData) {
|
||||||
var computer = instances.get(instanceId);
|
var computer = instances.get(instanceId);
|
||||||
if (computer == null) instances.put(instanceId, computer = new PocketComputerData(advanced));
|
if (computer == null) {
|
||||||
return computer;
|
var terminal = new NetworkedTerminal(terminalData.width, terminalData.height, terminalData.colour);
|
||||||
|
instances.put(instanceId, computer = new PocketComputerData(state, lightColour, terminal));
|
||||||
|
} else {
|
||||||
|
computer.setState(state, lightColour);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (terminalData.hasTerminal()) terminalData.apply(computer.getTerminal());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PocketComputerData get(ItemStack stack) {
|
public static @Nullable PocketComputerData get(ItemStack stack) {
|
||||||
var family = stack.getItem() instanceof PocketComputerItem computer ? computer.getFamily() : ComputerFamily.NORMAL;
|
var id = PocketComputerItem.getInstanceID(stack);
|
||||||
return get(PocketComputerItem.getInstanceID(stack), family != ComputerFamily.NORMAL);
|
return id == null ? null : instances.get(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,8 @@
|
|||||||
|
|
||||||
package dan200.computercraft.client.pocket;
|
package dan200.computercraft.client.pocket;
|
||||||
|
|
||||||
import dan200.computercraft.core.terminal.Terminal;
|
|
||||||
import dan200.computercraft.shared.computer.core.ComputerState;
|
import dan200.computercraft.shared.computer.core.ComputerState;
|
||||||
import dan200.computercraft.shared.computer.terminal.NetworkedTerminal;
|
import dan200.computercraft.shared.computer.terminal.NetworkedTerminal;
|
||||||
import dan200.computercraft.shared.computer.terminal.TerminalState;
|
|
||||||
import dan200.computercraft.shared.config.Config;
|
|
||||||
import dan200.computercraft.shared.pocket.core.PocketServerComputer;
|
import dan200.computercraft.shared.pocket.core.PocketServerComputer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -21,20 +18,22 @@ import dan200.computercraft.shared.pocket.core.PocketServerComputer;
|
|||||||
* @see ClientPocketComputers The registry which holds pocket computers.
|
* @see ClientPocketComputers The registry which holds pocket computers.
|
||||||
* @see PocketServerComputer The server-side pocket computer.
|
* @see PocketServerComputer The server-side pocket computer.
|
||||||
*/
|
*/
|
||||||
public class PocketComputerData {
|
public final class PocketComputerData {
|
||||||
private final NetworkedTerminal terminal;
|
private final NetworkedTerminal terminal;
|
||||||
private ComputerState state = ComputerState.OFF;
|
private ComputerState state;
|
||||||
private int lightColour = -1;
|
private int lightColour;
|
||||||
|
|
||||||
public PocketComputerData(boolean colour) {
|
PocketComputerData(ComputerState state, int lightColour, NetworkedTerminal terminal) {
|
||||||
terminal = new NetworkedTerminal(Config.pocketTermWidth, Config.pocketTermHeight, colour);
|
this.state = state;
|
||||||
|
this.lightColour = lightColour;
|
||||||
|
this.terminal = terminal;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getLightState() {
|
public int getLightState() {
|
||||||
return state != ComputerState.OFF ? lightColour : -1;
|
return state != ComputerState.OFF ? lightColour : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Terminal getTerminal() {
|
public NetworkedTerminal getTerminal() {
|
||||||
return terminal;
|
return terminal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,12 +41,8 @@ public class PocketComputerData {
|
|||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setState(ComputerState state, int lightColour) {
|
void setState(ComputerState state, int lightColour) {
|
||||||
this.state = state;
|
this.state = state;
|
||||||
this.lightColour = lightColour;
|
this.lightColour = lightColour;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTerminal(TerminalState state) {
|
|
||||||
state.apply(terminal);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import dan200.computercraft.client.pocket.ClientPocketComputers;
|
|||||||
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
|
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
|
||||||
import dan200.computercraft.core.util.Colour;
|
import dan200.computercraft.core.util.Colour;
|
||||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||||
|
import dan200.computercraft.shared.config.Config;
|
||||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
||||||
import net.minecraft.client.renderer.MultiBufferSource;
|
import net.minecraft.client.renderer.MultiBufferSource;
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
@@ -32,10 +33,16 @@ public final class PocketItemRenderer extends ItemMapLikeRenderer {
|
|||||||
@Override
|
@Override
|
||||||
protected void renderItem(PoseStack transform, MultiBufferSource bufferSource, ItemStack stack, int light) {
|
protected void renderItem(PoseStack transform, MultiBufferSource bufferSource, ItemStack stack, int light) {
|
||||||
var computer = ClientPocketComputers.get(stack);
|
var computer = ClientPocketComputers.get(stack);
|
||||||
var terminal = computer.getTerminal();
|
var terminal = computer == null ? null : computer.getTerminal();
|
||||||
|
|
||||||
var termWidth = terminal.getWidth();
|
int termWidth, termHeight;
|
||||||
var termHeight = terminal.getHeight();
|
if (terminal == null) {
|
||||||
|
termWidth = Config.pocketTermWidth;
|
||||||
|
termHeight = Config.pocketTermHeight;
|
||||||
|
} else {
|
||||||
|
termWidth = terminal.getWidth();
|
||||||
|
termHeight = terminal.getHeight();
|
||||||
|
}
|
||||||
|
|
||||||
var width = termWidth * FONT_WIDTH + MARGIN * 2;
|
var width = termWidth * FONT_WIDTH + MARGIN * 2;
|
||||||
var height = termHeight * FONT_HEIGHT + MARGIN * 2;
|
var height = termHeight * FONT_HEIGHT + MARGIN * 2;
|
||||||
@@ -60,14 +67,15 @@ public final class PocketItemRenderer extends ItemMapLikeRenderer {
|
|||||||
renderFrame(matrix, bufferSource, family, frameColour, light, width, height);
|
renderFrame(matrix, bufferSource, family, frameColour, light, width, height);
|
||||||
|
|
||||||
// Render the light
|
// Render the light
|
||||||
var lightColour = ClientPocketComputers.get(stack).getLightState();
|
var lightColour = computer == null || computer.getLightState() == -1 ? Colour.BLACK.getHex() : computer.getLightState();
|
||||||
if (lightColour == -1) lightColour = Colour.BLACK.getHex();
|
|
||||||
renderLight(transform, bufferSource, lightColour, width, height);
|
renderLight(transform, bufferSource, lightColour, width, height);
|
||||||
|
|
||||||
FixedWidthFontRenderer.drawTerminal(
|
var quadEmitter = FixedWidthFontRenderer.toVertexConsumer(transform, bufferSource.getBuffer(RenderTypes.TERMINAL));
|
||||||
FixedWidthFontRenderer.toVertexConsumer(transform, bufferSource.getBuffer(RenderTypes.TERMINAL)),
|
if (terminal == null) {
|
||||||
MARGIN, MARGIN, terminal, MARGIN, MARGIN, MARGIN, MARGIN
|
FixedWidthFontRenderer.drawEmptyTerminal(quadEmitter, 0, 0, width, height);
|
||||||
);
|
} else {
|
||||||
|
FixedWidthFontRenderer.drawTerminal(quadEmitter, MARGIN, MARGIN, terminal, MARGIN, MARGIN, MARGIN, MARGIN);
|
||||||
|
}
|
||||||
|
|
||||||
transform.popPose();
|
transform.popPose();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import dan200.computercraft.api.ComputerCraftAPI;
|
|||||||
import dan200.computercraft.api.turtle.TurtleSide;
|
import dan200.computercraft.api.turtle.TurtleSide;
|
||||||
import dan200.computercraft.client.platform.ClientPlatformHelper;
|
import dan200.computercraft.client.platform.ClientPlatformHelper;
|
||||||
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
|
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
|
||||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
|
||||||
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
|
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
|
||||||
import dan200.computercraft.shared.util.Holiday;
|
import dan200.computercraft.shared.util.Holiday;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
@@ -21,7 +20,6 @@ import net.minecraft.client.renderer.blockentity.BlockEntityRenderDispatcher;
|
|||||||
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
|
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
|
||||||
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
|
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
|
||||||
import net.minecraft.client.resources.model.BakedModel;
|
import net.minecraft.client.resources.model.BakedModel;
|
||||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.world.phys.BlockHitResult;
|
import net.minecraft.world.phys.BlockHitResult;
|
||||||
import net.minecraft.world.phys.HitResult;
|
import net.minecraft.world.phys.HitResult;
|
||||||
@@ -29,8 +27,6 @@ import net.minecraft.world.phys.HitResult;
|
|||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBlockEntity> {
|
public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBlockEntity> {
|
||||||
private static final ModelResourceLocation NORMAL_TURTLE_MODEL = new ModelResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_normal", "inventory");
|
|
||||||
private static final ModelResourceLocation ADVANCED_TURTLE_MODEL = new ModelResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_advanced", "inventory");
|
|
||||||
private static final ResourceLocation COLOUR_TURTLE_MODEL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_colour");
|
private static final ResourceLocation COLOUR_TURTLE_MODEL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_colour");
|
||||||
private static final ResourceLocation ELF_OVERLAY_MODEL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_elf_overlay");
|
private static final ResourceLocation ELF_OVERLAY_MODEL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_elf_overlay");
|
||||||
|
|
||||||
@@ -42,13 +38,6 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
|
|||||||
font = context.getFont();
|
font = context.getFont();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ResourceLocation getTurtleModel(ComputerFamily family, boolean coloured) {
|
|
||||||
return switch (family) {
|
|
||||||
default -> coloured ? COLOUR_TURTLE_MODEL : NORMAL_TURTLE_MODEL;
|
|
||||||
case ADVANCED -> coloured ? COLOUR_TURTLE_MODEL : ADVANCED_TURTLE_MODEL;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @Nullable ResourceLocation getTurtleOverlayModel(@Nullable ResourceLocation overlay, boolean christmas) {
|
public static @Nullable ResourceLocation getTurtleOverlayModel(@Nullable ResourceLocation overlay, boolean christmas) {
|
||||||
if (overlay != null) return overlay;
|
if (overlay != null) return overlay;
|
||||||
if (christmas) return ELF_OVERLAY_MODEL;
|
if (christmas) return ELF_OVERLAY_MODEL;
|
||||||
@@ -78,7 +67,6 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
|
|||||||
var matrix = transform.last().pose();
|
var matrix = transform.last().pose();
|
||||||
var opacity = (int) (mc.options.getBackgroundOpacity(0.25f) * 255) << 24;
|
var opacity = (int) (mc.options.getBackgroundOpacity(0.25f) * 255) << 24;
|
||||||
var width = -font.width(label) / 2.0f;
|
var width = -font.width(label) / 2.0f;
|
||||||
// TODO: Check this looks okay
|
|
||||||
font.drawInBatch(label, width, (float) 0, 0x20ffffff, false, matrix, buffers, Font.DisplayMode.SEE_THROUGH, opacity, lightmapCoord);
|
font.drawInBatch(label, width, (float) 0, 0x20ffffff, false, matrix, buffers, Font.DisplayMode.SEE_THROUGH, opacity, lightmapCoord);
|
||||||
font.drawInBatch(label, width, (float) 0, 0xffffffff, false, matrix, buffers, Font.DisplayMode.NORMAL, 0, lightmapCoord);
|
font.drawInBatch(label, width, (float) 0, 0xffffffff, false, matrix, buffers, Font.DisplayMode.NORMAL, 0, lightmapCoord);
|
||||||
|
|
||||||
@@ -96,10 +84,18 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
|
|||||||
|
|
||||||
// Render the turtle
|
// Render the turtle
|
||||||
var colour = turtle.getColour();
|
var colour = turtle.getColour();
|
||||||
var family = turtle.getFamily();
|
|
||||||
var overlay = turtle.getOverlay();
|
var overlay = turtle.getOverlay();
|
||||||
|
|
||||||
renderModel(transform, buffers, lightmapCoord, overlayLight, getTurtleModel(family, colour != -1), colour == -1 ? null : new int[]{ colour });
|
if (colour == -1) {
|
||||||
|
// Render the turtle using its item model.
|
||||||
|
var modelManager = Minecraft.getInstance().getItemRenderer().getItemModelShaper();
|
||||||
|
var model = modelManager.getItemModel(turtle.getBlockState().getBlock().asItem());
|
||||||
|
if (model == null) model = modelManager.getModelManager().getMissingModel();
|
||||||
|
renderModel(transform, buffers, lightmapCoord, overlayLight, model, null);
|
||||||
|
} else {
|
||||||
|
// Otherwise render it using the colour item.
|
||||||
|
renderModel(transform, buffers, lightmapCoord, overlayLight, COLOUR_TURTLE_MODEL, new int[]{ colour });
|
||||||
|
}
|
||||||
|
|
||||||
// Render the overlay
|
// Render the overlay
|
||||||
var overlayModel = getTurtleOverlayModel(overlay, Holiday.getCurrent() == Holiday.CHRISTMAS);
|
var overlayModel = getTurtleOverlayModel(overlay, Holiday.getCurrent() == Holiday.CHRISTMAS);
|
||||||
|
|||||||
@@ -58,9 +58,9 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl
|
|||||||
@Override
|
@Override
|
||||||
public void render(MonitorBlockEntity monitor, float partialTicks, PoseStack transform, MultiBufferSource bufferSource, int lightmapCoord, int overlayLight) {
|
public void render(MonitorBlockEntity monitor, float partialTicks, PoseStack transform, MultiBufferSource bufferSource, int lightmapCoord, int overlayLight) {
|
||||||
// Render from the origin monitor
|
// Render from the origin monitor
|
||||||
var originTerminal = monitor.getClientMonitor();
|
var originTerminal = monitor.getOriginClientMonitor();
|
||||||
|
|
||||||
if (originTerminal == null) return;
|
if (originTerminal == null) return;
|
||||||
|
|
||||||
var origin = originTerminal.getOrigin();
|
var origin = originTerminal.getOrigin();
|
||||||
var renderState = originTerminal.getRenderState(MonitorRenderState::new);
|
var renderState = originTerminal.getRenderState(MonitorRenderState::new);
|
||||||
var monitorPos = monitor.getBlockPos();
|
var monitorPos = monitor.getBlockPos();
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ import dan200.computercraft.core.terminal.TextBuffer;
|
|||||||
import dan200.computercraft.core.util.Colour;
|
import dan200.computercraft.core.util.Colour;
|
||||||
import net.minecraft.client.renderer.ShaderInstance;
|
import net.minecraft.client.renderer.ShaderInstance;
|
||||||
import net.minecraft.server.packs.resources.ResourceProvider;
|
import net.minecraft.server.packs.resources.ResourceProvider;
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
import org.lwjgl.opengl.GL13;
|
import org.lwjgl.opengl.GL13;
|
||||||
import org.lwjgl.opengl.GL31;
|
import org.lwjgl.opengl.GL31;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -36,12 +36,12 @@ import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.get
|
|||||||
* @see RenderTypes#getMonitorTextureBufferShader()
|
* @see RenderTypes#getMonitorTextureBufferShader()
|
||||||
*/
|
*/
|
||||||
public class MonitorTextureBufferShader extends ShaderInstance {
|
public class MonitorTextureBufferShader extends ShaderInstance {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(MonitorTextureBufferShader.class);
|
||||||
|
|
||||||
public static final int UNIFORM_SIZE = 4 * 4 * 16 + 4 + 4 + 2 * 4 + 4;
|
public static final int UNIFORM_SIZE = 4 * 4 * 16 + 4 + 4 + 2 * 4 + 4;
|
||||||
|
|
||||||
static final int TEXTURE_INDEX = GL13.GL_TEXTURE3;
|
static final int TEXTURE_INDEX = GL13.GL_TEXTURE3;
|
||||||
|
|
||||||
private static final Logger LOGGER = LogManager.getLogger();
|
|
||||||
|
|
||||||
private final int monitorData;
|
private final int monitorData;
|
||||||
private int uniformBuffer = 0;
|
private int uniformBuffer = 0;
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ public class MonitorTextureBufferShader extends ShaderInstance {
|
|||||||
private Uniform getUniformChecked(String name) {
|
private Uniform getUniformChecked(String name) {
|
||||||
var uniform = getUniform(name);
|
var uniform = getUniform(name);
|
||||||
if (uniform == null) {
|
if (uniform == null) {
|
||||||
LOGGER.warn("Monitor shader {} should have uniform {}, but it was not present.", getName(), name);
|
LOG.warn("Monitor shader {} should have uniform {}, but it was not present.", getName(), name);
|
||||||
}
|
}
|
||||||
|
|
||||||
return uniform;
|
return uniform;
|
||||||
|
|||||||
@@ -5,8 +5,9 @@
|
|||||||
package dan200.computercraft.client.sound;
|
package dan200.computercraft.client.sound;
|
||||||
|
|
||||||
import com.mojang.blaze3d.audio.Channel;
|
import com.mojang.blaze3d.audio.Channel;
|
||||||
|
import dan200.computercraft.shared.peripheral.speaker.EncodedAudio;
|
||||||
import dan200.computercraft.shared.peripheral.speaker.SpeakerPeripheral;
|
import dan200.computercraft.shared.peripheral.speaker.SpeakerPeripheral;
|
||||||
import io.netty.buffer.ByteBuf;
|
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
|
||||||
import net.minecraft.client.sounds.AudioStream;
|
import net.minecraft.client.sounds.AudioStream;
|
||||||
import net.minecraft.client.sounds.SoundEngine;
|
import net.minecraft.client.sounds.SoundEngine;
|
||||||
import org.lwjgl.BufferUtils;
|
import org.lwjgl.BufferUtils;
|
||||||
@@ -36,7 +37,7 @@ class DfpwmStream implements AudioStream {
|
|||||||
/**
|
/**
|
||||||
* The {@link Channel} which this sound is playing on.
|
* The {@link Channel} which this sound is playing on.
|
||||||
*
|
*
|
||||||
* @see SpeakerInstance#pushAudio(ByteBuf)
|
* @see SpeakerInstance#playAudio(SpeakerPosition, float, EncodedAudio)
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
Channel channel;
|
Channel channel;
|
||||||
@@ -44,26 +45,28 @@ class DfpwmStream implements AudioStream {
|
|||||||
/**
|
/**
|
||||||
* The underlying {@link SoundEngine} executor.
|
* The underlying {@link SoundEngine} executor.
|
||||||
*
|
*
|
||||||
* @see SpeakerInstance#pushAudio(ByteBuf)
|
* @see SpeakerInstance#playAudio(SpeakerPosition, float, EncodedAudio)
|
||||||
* @see SoundEngine#executor
|
* @see SoundEngine#executor
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
Executor executor;
|
Executor executor;
|
||||||
|
|
||||||
private int charge = 0; // q
|
|
||||||
private int strength = 0; // s
|
|
||||||
private int lowPassCharge;
|
private int lowPassCharge;
|
||||||
private boolean previousBit = false;
|
|
||||||
|
|
||||||
DfpwmStream() {
|
DfpwmStream() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void push(ByteBuf input) {
|
void push(EncodedAudio audio) {
|
||||||
var readable = input.readableBytes();
|
var charge = audio.charge();
|
||||||
|
var strength = audio.strength();
|
||||||
|
var previousBit = audio.previousBit();
|
||||||
|
var input = audio.audio();
|
||||||
|
|
||||||
|
var readable = input.remaining();
|
||||||
var output = ByteBuffer.allocate(readable * 8).order(ByteOrder.nativeOrder());
|
var output = ByteBuffer.allocate(readable * 8).order(ByteOrder.nativeOrder());
|
||||||
|
|
||||||
for (var i = 0; i < readable; i++) {
|
for (var i = 0; i < readable; i++) {
|
||||||
var inputByte = input.readByte();
|
var inputByte = input.get();
|
||||||
for (var j = 0; j < 8; j++) {
|
for (var j = 0; j < 8; j++) {
|
||||||
var currentBit = (inputByte & 1) != 0;
|
var currentBit = (inputByte & 1) != 0;
|
||||||
var target = currentBit ? 127 : -128;
|
var target = currentBit ? 127 : -128;
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ package dan200.computercraft.client.sound;
|
|||||||
|
|
||||||
import dan200.computercraft.api.ComputerCraftAPI;
|
import dan200.computercraft.api.ComputerCraftAPI;
|
||||||
import dan200.computercraft.core.util.Nullability;
|
import dan200.computercraft.core.util.Nullability;
|
||||||
|
import dan200.computercraft.shared.peripheral.speaker.EncodedAudio;
|
||||||
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
|
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
|
||||||
import io.netty.buffer.ByteBuf;
|
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ public class SpeakerInstance {
|
|||||||
SpeakerInstance() {
|
SpeakerInstance() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void pushAudio(ByteBuf buffer) {
|
private void pushAudio(EncodedAudio buffer) {
|
||||||
var sound = this.sound;
|
var sound = this.sound;
|
||||||
|
|
||||||
var stream = currentStream;
|
var stream = currentStream;
|
||||||
@@ -43,7 +43,9 @@ public class SpeakerInstance {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void playAudio(SpeakerPosition position, float volume) {
|
public void playAudio(SpeakerPosition position, float volume, EncodedAudio buffer) {
|
||||||
|
pushAudio(buffer);
|
||||||
|
|
||||||
var soundManager = Minecraft.getInstance().getSoundManager();
|
var soundManager = Minecraft.getInstance().getSoundManager();
|
||||||
|
|
||||||
if (sound != null && sound.stream != currentStream) {
|
if (sound != null && sound.stream != currentStream) {
|
||||||
|
|||||||
@@ -11,11 +11,14 @@ 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.api.turtle.TurtleUpgradeSerialiser;
|
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
|
||||||
|
import dan200.computercraft.impl.PlatformHelper;
|
||||||
import dan200.computercraft.impl.TurtleUpgrades;
|
import dan200.computercraft.impl.TurtleUpgrades;
|
||||||
import dan200.computercraft.impl.UpgradeManager;
|
import dan200.computercraft.impl.UpgradeManager;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.nbt.CompoundTag;
|
import net.minecraft.nbt.CompoundTag;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
@@ -24,14 +27,15 @@ import java.util.stream.Stream;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A registry of {@link TurtleUpgradeModeller}s.
|
* A registry of {@link TurtleUpgradeModeller}s.
|
||||||
*
|
|
||||||
* @see dan200.computercraft.api.client.ComputerCraftAPIClient#registerTurtleUpgradeModeller(TurtleUpgradeSerialiser, TurtleUpgradeModeller)
|
|
||||||
*/
|
*/
|
||||||
public final class TurtleUpgradeModellers {
|
public final class TurtleUpgradeModellers {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(TurtleUpgradeModellers.class);
|
||||||
|
|
||||||
private static final TurtleUpgradeModeller<ITurtleUpgrade> NULL_TURTLE_MODELLER = (upgrade, turtle, side) ->
|
private static final TurtleUpgradeModeller<ITurtleUpgrade> NULL_TURTLE_MODELLER = (upgrade, turtle, side) ->
|
||||||
new TransformedModel(Minecraft.getInstance().getModelManager().getMissingModel(), Transformation.identity());
|
new TransformedModel(Minecraft.getInstance().getModelManager().getMissingModel(), Transformation.identity());
|
||||||
|
|
||||||
private static final Map<TurtleUpgradeSerialiser<?>, TurtleUpgradeModeller<?>> turtleModels = new ConcurrentHashMap<>();
|
private static final Map<TurtleUpgradeSerialiser<?>, TurtleUpgradeModeller<?>> turtleModels = new ConcurrentHashMap<>();
|
||||||
|
private static volatile boolean fetchedModels;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In order to avoid a double lookup of {@link ITurtleUpgrade} to {@link UpgradeManager.UpgradeWrapper} to
|
* In order to avoid a double lookup of {@link ITurtleUpgrade} to {@link UpgradeManager.UpgradeWrapper} to
|
||||||
@@ -45,12 +49,18 @@ public final class TurtleUpgradeModellers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static <T extends ITurtleUpgrade> void register(TurtleUpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller) {
|
public static <T extends ITurtleUpgrade> void register(TurtleUpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller) {
|
||||||
synchronized (turtleModels) {
|
if (fetchedModels) {
|
||||||
if (turtleModels.containsKey(serialiser)) {
|
// TODO(1.20.4): Replace with an error.
|
||||||
throw new IllegalStateException("Modeller already registered for serialiser");
|
LOG.warn(
|
||||||
}
|
"Turtle upgrade serialiser {} was registered too late, its models may not be loaded correctly. If you are " +
|
||||||
|
"the mod author, you may be using a deprecated API - see https://github.com/cc-tweaked/CC-Tweaked/pull/1684 " +
|
||||||
|
"for further information.",
|
||||||
|
PlatformHelper.get().getRegistryKey(TurtleUpgradeSerialiser.registryId(), serialiser)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
turtleModels.put(serialiser, modeller);
|
if (turtleModels.putIfAbsent(serialiser, modeller) != null) {
|
||||||
|
throw new IllegalStateException("Modeller already registered for serialiser");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,6 +85,7 @@ public final class TurtleUpgradeModellers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Stream<ResourceLocation> getDependencies() {
|
public static Stream<ResourceLocation> getDependencies() {
|
||||||
|
fetchedModels = true;
|
||||||
return turtleModels.values().stream().flatMap(x -> x.getDependencies().stream());
|
return turtleModels.values().stream().flatMap(x -> x.getDependencies().stream());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
package dan200.computercraft.mixin.client;
|
|
||||||
|
|
||||||
import dan200.computercraft.client.ClientHooks;
|
|
||||||
import net.minecraft.client.multiplayer.ClientPacketListener;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
|
||||||
|
|
||||||
@Mixin(ClientPacketListener.class)
|
|
||||||
class ClientPacketListenerMixin {
|
|
||||||
@Inject(method = "sendUnsignedCommand", at = @At("HEAD"), cancellable = true)
|
|
||||||
void commandUnsigned(String message, CallbackInfoReturnable<Boolean> ci) {
|
|
||||||
if (ClientHooks.onChatMessage(message)) ci.setReturnValue(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"required": true,
|
|
||||||
"package": "dan200.computercraft.mixin.client",
|
|
||||||
"minVersion": "0.8",
|
|
||||||
"compatibilityLevel": "JAVA_17",
|
|
||||||
"injectors": {
|
|
||||||
"defaultRequire": 1
|
|
||||||
},
|
|
||||||
"client": [
|
|
||||||
"ClientPacketListenerMixin"
|
|
||||||
],
|
|
||||||
"refmap": "client-computercraft.refmap.json"
|
|
||||||
}
|
|
||||||
@@ -13,6 +13,7 @@ import dan200.computercraft.api.upgrades.UpgradeBase;
|
|||||||
import dan200.computercraft.core.metrics.Metric;
|
import dan200.computercraft.core.metrics.Metric;
|
||||||
import dan200.computercraft.core.metrics.Metrics;
|
import dan200.computercraft.core.metrics.Metrics;
|
||||||
import dan200.computercraft.shared.ModRegistry;
|
import dan200.computercraft.shared.ModRegistry;
|
||||||
|
import dan200.computercraft.shared.command.arguments.ComputerSelector;
|
||||||
import dan200.computercraft.shared.computer.metrics.basic.Aggregate;
|
import dan200.computercraft.shared.computer.metrics.basic.Aggregate;
|
||||||
import dan200.computercraft.shared.computer.metrics.basic.AggregatedMetric;
|
import dan200.computercraft.shared.computer.metrics.basic.AggregatedMetric;
|
||||||
import dan200.computercraft.shared.config.ConfigFile;
|
import dan200.computercraft.shared.config.ConfigFile;
|
||||||
@@ -166,14 +167,24 @@ public final class LanguageProvider implements DataProvider {
|
|||||||
add("commands.computercraft.generic.exception", "Unhandled exception (%s)");
|
add("commands.computercraft.generic.exception", "Unhandled exception (%s)");
|
||||||
add("commands.computercraft.generic.additional_rows", "%d additional rows…");
|
add("commands.computercraft.generic.additional_rows", "%d additional rows…");
|
||||||
|
|
||||||
|
// Argument types
|
||||||
|
add("argument.computercraft.computer.instance", "Unique instance ID");
|
||||||
|
add("argument.computercraft.computer.id", "Computer ID");
|
||||||
|
add("argument.computercraft.computer.label", "Computer label");
|
||||||
|
add("argument.computercraft.computer.distance", "Distance to entity");
|
||||||
|
add("argument.computercraft.computer.family", "Computer family");
|
||||||
|
|
||||||
|
// Exceptions
|
||||||
add("argument.computercraft.computer.no_matching", "No computers matching '%s'");
|
add("argument.computercraft.computer.no_matching", "No computers matching '%s'");
|
||||||
add("argument.computercraft.computer.many_matching", "Multiple computers matching '%s' (instances %s)");
|
add("argument.computercraft.computer.many_matching", "Multiple computers matching '%s' (instances %s)");
|
||||||
add("argument.computercraft.tracking_field.no_field", "Unknown field '%s'");
|
add("argument.computercraft.tracking_field.no_field", "Unknown field '%s'");
|
||||||
add("argument.computercraft.argument_expected", "Argument expected");
|
add("argument.computercraft.argument_expected", "Argument expected");
|
||||||
|
add("argument.computercraft.unknown_computer_family", "Unknown computer family '%s'");
|
||||||
|
|
||||||
// Metrics
|
// Metrics
|
||||||
add(Metrics.COMPUTER_TASKS, "Tasks");
|
add(Metrics.COMPUTER_TASKS, "Tasks");
|
||||||
add(Metrics.SERVER_TASKS, "Server tasks");
|
add(Metrics.SERVER_TASKS, "Server tasks");
|
||||||
|
add(Metrics.JAVA_ALLOCATION, "Java Allocations");
|
||||||
add(Metrics.PERIPHERAL_OPS, "Peripheral calls");
|
add(Metrics.PERIPHERAL_OPS, "Peripheral calls");
|
||||||
add(Metrics.FS_OPS, "Filesystem operations");
|
add(Metrics.FS_OPS, "Filesystem operations");
|
||||||
add(Metrics.HTTP_REQUESTS, "HTTP requests");
|
add(Metrics.HTTP_REQUESTS, "HTTP requests");
|
||||||
@@ -213,7 +224,6 @@ public final class LanguageProvider implements DataProvider {
|
|||||||
addConfigEntry(ConfigSpec.floppySpaceLimit, "Floppy Disk space limit (bytes)");
|
addConfigEntry(ConfigSpec.floppySpaceLimit, "Floppy Disk space limit (bytes)");
|
||||||
addConfigEntry(ConfigSpec.uploadMaxSize, "File upload size limit (bytes)");
|
addConfigEntry(ConfigSpec.uploadMaxSize, "File upload size limit (bytes)");
|
||||||
addConfigEntry(ConfigSpec.maximumFilesOpen, "Maximum files open per computer");
|
addConfigEntry(ConfigSpec.maximumFilesOpen, "Maximum files open per computer");
|
||||||
addConfigEntry(ConfigSpec.disableLua51Features, "Disable Lua 5.1 features");
|
|
||||||
addConfigEntry(ConfigSpec.defaultComputerSettings, "Default Computer settings");
|
addConfigEntry(ConfigSpec.defaultComputerSettings, "Default Computer settings");
|
||||||
addConfigEntry(ConfigSpec.logComputerErrors, "Log computer errors");
|
addConfigEntry(ConfigSpec.logComputerErrors, "Log computer errors");
|
||||||
addConfigEntry(ConfigSpec.commandRequireCreative, "Command computers require creative");
|
addConfigEntry(ConfigSpec.commandRequireCreative, "Command computers require creative");
|
||||||
@@ -282,7 +292,8 @@ public final class LanguageProvider implements DataProvider {
|
|||||||
pocketUpgrades.getGeneratedUpgrades().stream().map(UpgradeBase::getUnlocalisedAdjective),
|
pocketUpgrades.getGeneratedUpgrades().stream().map(UpgradeBase::getUnlocalisedAdjective),
|
||||||
Metric.metrics().values().stream().map(x -> AggregatedMetric.TRANSLATION_PREFIX + x.name() + ".name"),
|
Metric.metrics().values().stream().map(x -> AggregatedMetric.TRANSLATION_PREFIX + x.name() + ".name"),
|
||||||
ConfigSpec.serverSpec.entries().map(ConfigFile.Entry::translationKey),
|
ConfigSpec.serverSpec.entries().map(ConfigFile.Entry::translationKey),
|
||||||
ConfigSpec.clientSpec.entries().map(ConfigFile.Entry::translationKey)
|
ConfigSpec.clientSpec.entries().map(ConfigFile.Entry::translationKey),
|
||||||
|
ComputerSelector.options().values().stream().map(ComputerSelector.Option::translationKey)
|
||||||
).flatMap(x -> x);
|
).flatMap(x -> x);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.data;
|
||||||
|
|
||||||
|
import com.google.common.hash.HashCode;
|
||||||
|
import com.google.common.hash.HashFunction;
|
||||||
|
import com.google.common.hash.Hashing;
|
||||||
|
import net.minecraft.data.CachedOutput;
|
||||||
|
import net.minecraft.data.DataProvider;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps an existing {@link DataProvider}, passing generated JSON through {@link PrettyJsonWriter}.
|
||||||
|
*
|
||||||
|
* @param provider The provider to wrap.
|
||||||
|
* @param <T> The type of the provider to wrap.
|
||||||
|
*/
|
||||||
|
public record PrettyDataProvider<T extends DataProvider>(T provider) implements DataProvider {
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<?> run(CachedOutput cachedOutput) {
|
||||||
|
return provider.run(new Output(cachedOutput));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return provider.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
private record Output(CachedOutput output) implements CachedOutput {
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
private static final HashFunction HASH_FUNCTION = Hashing.sha1();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeIfNeeded(Path path, byte[] bytes, HashCode hashCode) throws IOException {
|
||||||
|
if (path.getFileName().toString().endsWith(".json")) {
|
||||||
|
bytes = PrettyJsonWriter.reformat(bytes);
|
||||||
|
hashCode = HASH_FUNCTION.hashBytes(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
output.writeIfNeeded(path, bytes, hashCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,11 +22,10 @@ import java.util.List;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Alternative version of {@link JsonWriter} which attempts to lay out the JSON in a more compact format.
|
* Alternative version of {@link JsonWriter} which attempts to lay out the JSON in a more compact format.
|
||||||
* <p>
|
*
|
||||||
* Yes, this is at least a little deranged.
|
* @see PrettyDataProvider
|
||||||
*/
|
*/
|
||||||
public class PrettyJsonWriter extends JsonWriter {
|
public class PrettyJsonWriter extends JsonWriter {
|
||||||
public static final boolean ENABLED = System.getProperty("cct.pretty-json") != null;
|
|
||||||
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
|
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
|
||||||
|
|
||||||
private static final int MAX_WIDTH = 120;
|
private static final int MAX_WIDTH = 120;
|
||||||
@@ -44,17 +43,6 @@ public class PrettyJsonWriter extends JsonWriter {
|
|||||||
this.out = out;
|
this.out = out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a JSON writer. This will either be a pretty or normal version, depending on whether the global flag is
|
|
||||||
* set.
|
|
||||||
*
|
|
||||||
* @param out The writer to emit to.
|
|
||||||
* @return The constructed JSON writer.
|
|
||||||
*/
|
|
||||||
public static JsonWriter createWriter(Writer out) {
|
|
||||||
return ENABLED ? new PrettyJsonWriter(out) : new JsonWriter(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reformat a JSON string with our pretty printer.
|
* Reformat a JSON string with our pretty printer.
|
||||||
*
|
*
|
||||||
@@ -62,8 +50,6 @@ public class PrettyJsonWriter extends JsonWriter {
|
|||||||
* @return The reformatted string.
|
* @return The reformatted string.
|
||||||
*/
|
*/
|
||||||
public static byte[] reformat(byte[] contents) {
|
public static byte[] reformat(byte[] contents) {
|
||||||
if (!ENABLED) return contents;
|
|
||||||
|
|
||||||
JsonElement object;
|
JsonElement object;
|
||||||
try (var reader = new InputStreamReader(new ByteArrayInputStream(contents), StandardCharsets.UTF_8)) {
|
try (var reader = new InputStreamReader(new ByteArrayInputStream(contents), StandardCharsets.UTF_8)) {
|
||||||
object = GSON.fromJson(reader, JsonElement.class);
|
object = GSON.fromJson(reader, JsonElement.class);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
package dan200.computercraft.data;
|
package dan200.computercraft.data;
|
||||||
|
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
|
import com.mojang.authlib.GameProfile;
|
||||||
import dan200.computercraft.api.ComputerCraftAPI;
|
import dan200.computercraft.api.ComputerCraftAPI;
|
||||||
import dan200.computercraft.api.pocket.PocketUpgradeDataProvider;
|
import dan200.computercraft.api.pocket.PocketUpgradeDataProvider;
|
||||||
import dan200.computercraft.api.turtle.TurtleUpgradeDataProvider;
|
import dan200.computercraft.api.turtle.TurtleUpgradeDataProvider;
|
||||||
@@ -12,7 +13,6 @@ import dan200.computercraft.api.upgrades.UpgradeData;
|
|||||||
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.common.IColouredItem;
|
import dan200.computercraft.shared.common.IColouredItem;
|
||||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
|
||||||
import dan200.computercraft.shared.platform.PlatformHelper;
|
import dan200.computercraft.shared.platform.PlatformHelper;
|
||||||
import dan200.computercraft.shared.platform.RecipeIngredients;
|
import dan200.computercraft.shared.platform.RecipeIngredients;
|
||||||
import dan200.computercraft.shared.platform.RegistryWrappers;
|
import dan200.computercraft.shared.platform.RegistryWrappers;
|
||||||
@@ -25,22 +25,19 @@ import net.minecraft.core.registries.Registries;
|
|||||||
import net.minecraft.data.PackOutput;
|
import net.minecraft.data.PackOutput;
|
||||||
import net.minecraft.data.recipes.*;
|
import net.minecraft.data.recipes.*;
|
||||||
import net.minecraft.nbt.CompoundTag;
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.nbt.NbtUtils;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.tags.TagKey;
|
import net.minecraft.tags.TagKey;
|
||||||
import net.minecraft.util.GsonHelper;
|
import net.minecraft.util.GsonHelper;
|
||||||
import net.minecraft.world.item.DyeColor;
|
import net.minecraft.world.item.*;
|
||||||
import net.minecraft.world.item.DyeItem;
|
|
||||||
import net.minecraft.world.item.Item;
|
|
||||||
import net.minecraft.world.item.Items;
|
|
||||||
import net.minecraft.world.item.crafting.Ingredient;
|
import net.minecraft.world.item.crafting.Ingredient;
|
||||||
import net.minecraft.world.item.crafting.RecipeSerializer;
|
|
||||||
import net.minecraft.world.item.crafting.ShapedRecipe;
|
import net.minecraft.world.item.crafting.ShapedRecipe;
|
||||||
import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer;
|
import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer;
|
||||||
import net.minecraft.world.level.ItemLike;
|
import net.minecraft.world.level.ItemLike;
|
||||||
import net.minecraft.world.level.block.Blocks;
|
import net.minecraft.world.level.block.Blocks;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.UUID;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import static dan200.computercraft.api.ComputerCraftTags.Items.COMPUTER;
|
import static dan200.computercraft.api.ComputerCraftTags.Items.COMPUTER;
|
||||||
@@ -107,14 +104,13 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
|||||||
private void turtleUpgrades(Consumer<FinishedRecipe> add) {
|
private void turtleUpgrades(Consumer<FinishedRecipe> add) {
|
||||||
for (var turtleItem : turtleItems()) {
|
for (var turtleItem : turtleItems()) {
|
||||||
var base = turtleItem.create(-1, null, -1, null, null, 0, null);
|
var base = turtleItem.create(-1, null, -1, null, null, 0, null);
|
||||||
|
var name = RegistryWrappers.ITEMS.getKey(turtleItem);
|
||||||
var nameId = turtleItem.getFamily().name().toLowerCase(Locale.ROOT);
|
|
||||||
|
|
||||||
for (var upgrade : turtleUpgrades.getGeneratedUpgrades()) {
|
for (var upgrade : turtleUpgrades.getGeneratedUpgrades()) {
|
||||||
var result = turtleItem.create(-1, null, -1, null, UpgradeData.ofDefault(upgrade), -1, null);
|
var result = turtleItem.create(-1, null, -1, null, UpgradeData.ofDefault(upgrade), -1, null);
|
||||||
ShapedRecipeBuilder
|
ShapedRecipeBuilder
|
||||||
.shaped(RecipeCategory.REDSTONE, result.getItem())
|
.shaped(RecipeCategory.REDSTONE, result.getItem())
|
||||||
.group(String.format("%s:turtle_%s", ComputerCraftAPI.MOD_ID, nameId))
|
.group(name.toString())
|
||||||
.pattern("#T")
|
.pattern("#T")
|
||||||
.define('T', base.getItem())
|
.define('T', base.getItem())
|
||||||
.define('#', upgrade.getCraftingItem().getItem())
|
.define('#', upgrade.getCraftingItem().getItem())
|
||||||
@@ -122,9 +118,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
|||||||
inventoryChange(base.getItem(), upgrade.getCraftingItem().getItem()))
|
inventoryChange(base.getItem(), upgrade.getCraftingItem().getItem()))
|
||||||
.save(
|
.save(
|
||||||
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.IMPOSTOR_SHAPED.get(), add).withResultTag(result.getTag()),
|
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.IMPOSTOR_SHAPED.get(), add).withResultTag(result.getTag()),
|
||||||
new ResourceLocation(ComputerCraftAPI.MOD_ID, String.format("turtle_%s/%s/%s",
|
name.withSuffix(String.format("/%s/%s", upgrade.getUpgradeID().getNamespace(), upgrade.getUpgradeID().getPath()))
|
||||||
nameId, upgrade.getUpgradeID().getNamespace(), upgrade.getUpgradeID().getPath()
|
|
||||||
))
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,15 +136,13 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
|||||||
private void pocketUpgrades(Consumer<FinishedRecipe> add) {
|
private void pocketUpgrades(Consumer<FinishedRecipe> add) {
|
||||||
for (var pocket : pocketComputerItems()) {
|
for (var pocket : pocketComputerItems()) {
|
||||||
var base = pocket.create(-1, null, -1, null);
|
var base = pocket.create(-1, null, -1, null);
|
||||||
if (base.isEmpty()) continue;
|
var name = RegistryWrappers.ITEMS.getKey(pocket).withPath(x -> x.replace("pocket_computer_", "pocket_"));
|
||||||
|
|
||||||
var nameId = pocket.getFamily().name().toLowerCase(Locale.ROOT);
|
|
||||||
|
|
||||||
for (var upgrade : pocketUpgrades.getGeneratedUpgrades()) {
|
for (var upgrade : pocketUpgrades.getGeneratedUpgrades()) {
|
||||||
var result = pocket.create(-1, null, -1, UpgradeData.ofDefault(upgrade));
|
var result = pocket.create(-1, null, -1, UpgradeData.ofDefault(upgrade));
|
||||||
ShapedRecipeBuilder
|
ShapedRecipeBuilder
|
||||||
.shaped(RecipeCategory.REDSTONE, result.getItem())
|
.shaped(RecipeCategory.REDSTONE, result.getItem())
|
||||||
.group(String.format("%s:pocket_%s", ComputerCraftAPI.MOD_ID, nameId))
|
.group(name.toString())
|
||||||
.pattern("#")
|
.pattern("#")
|
||||||
.pattern("P")
|
.pattern("P")
|
||||||
.define('P', base.getItem())
|
.define('P', base.getItem())
|
||||||
@@ -159,9 +151,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
|||||||
inventoryChange(base.getItem(), upgrade.getCraftingItem().getItem()))
|
inventoryChange(base.getItem(), upgrade.getCraftingItem().getItem()))
|
||||||
.save(
|
.save(
|
||||||
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.IMPOSTOR_SHAPED.get(), add).withResultTag(result.getTag()),
|
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.IMPOSTOR_SHAPED.get(), add).withResultTag(result.getTag()),
|
||||||
new ResourceLocation(ComputerCraftAPI.MOD_ID, String.format("pocket_%s/%s/%s",
|
name.withSuffix(String.format("/%s/%s", upgrade.getUpgradeID().getNamespace(), upgrade.getUpgradeID().getPath()))
|
||||||
nameId, upgrade.getUpgradeID().getNamespace(), upgrade.getUpgradeID().getPath()
|
|
||||||
))
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -191,12 +181,10 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
|||||||
private void turtleOverlay(Consumer<FinishedRecipe> add, String overlay, Consumer<ShapelessRecipeBuilder> build) {
|
private void turtleOverlay(Consumer<FinishedRecipe> add, String overlay, Consumer<ShapelessRecipeBuilder> build) {
|
||||||
for (var turtleItem : turtleItems()) {
|
for (var turtleItem : turtleItems()) {
|
||||||
var base = turtleItem.create(-1, null, -1, null, null, 0, null);
|
var base = turtleItem.create(-1, null, -1, null, null, 0, null);
|
||||||
|
var name = RegistryWrappers.ITEMS.getKey(turtleItem);
|
||||||
var nameId = turtleItem.getFamily().name().toLowerCase(Locale.ROOT);
|
|
||||||
var group = "%s:turtle_%s_overlay".formatted(ComputerCraftAPI.MOD_ID, nameId);
|
|
||||||
|
|
||||||
var builder = ShapelessRecipeBuilder.shapeless(RecipeCategory.REDSTONE, base.getItem())
|
var builder = ShapelessRecipeBuilder.shapeless(RecipeCategory.REDSTONE, base.getItem())
|
||||||
.group(group)
|
.group(name.withSuffix("_overlay").toString())
|
||||||
.unlockedBy("has_turtle", inventoryChange(base.getItem()));
|
.unlockedBy("has_turtle", inventoryChange(base.getItem()));
|
||||||
build.accept(builder);
|
build.accept(builder);
|
||||||
builder
|
builder
|
||||||
@@ -205,7 +193,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
|||||||
RecipeWrapper
|
RecipeWrapper
|
||||||
.wrap(ModRegistry.RecipeSerializers.TURTLE_OVERLAY.get(), add)
|
.wrap(ModRegistry.RecipeSerializers.TURTLE_OVERLAY.get(), add)
|
||||||
.withExtraData(x -> x.addProperty("overlay", new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/" + overlay).toString())),
|
.withExtraData(x -> x.addProperty("overlay", new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/" + overlay).toString())),
|
||||||
new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_%s_overlays/%s".formatted(nameId, overlay))
|
name.withSuffix("_overlays/" + overlay)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -254,7 +242,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
|||||||
.define('C', ModRegistry.Items.COMPUTER_NORMAL.get())
|
.define('C', ModRegistry.Items.COMPUTER_NORMAL.get())
|
||||||
.unlockedBy("has_components", inventoryChange(itemPredicate(ModRegistry.Items.COMPUTER_NORMAL.get()), itemPredicate(ingredients.goldIngot())))
|
.unlockedBy("has_components", inventoryChange(itemPredicate(ModRegistry.Items.COMPUTER_NORMAL.get()), itemPredicate(ingredients.goldIngot())))
|
||||||
.save(
|
.save(
|
||||||
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get(), add).withExtraData(family(ComputerFamily.ADVANCED)),
|
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get(), add),
|
||||||
new ResourceLocation(ComputerCraftAPI.MOD_ID, "computer_advanced_upgrade")
|
new ResourceLocation(ComputerCraftAPI.MOD_ID, "computer_advanced_upgrade")
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -278,7 +266,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
|||||||
.define('C', ModRegistry.Items.COMPUTER_NORMAL.get())
|
.define('C', ModRegistry.Items.COMPUTER_NORMAL.get())
|
||||||
.define('I', ingredients.woodenChest())
|
.define('I', ingredients.woodenChest())
|
||||||
.unlockedBy("has_computer", inventoryChange(ModRegistry.Items.COMPUTER_NORMAL.get()))
|
.unlockedBy("has_computer", inventoryChange(ModRegistry.Items.COMPUTER_NORMAL.get()))
|
||||||
.save(RecipeWrapper.wrap(ModRegistry.RecipeSerializers.TURTLE.get(), add).withExtraData(family(ComputerFamily.NORMAL)));
|
.save(RecipeWrapper.wrap(ModRegistry.RecipeSerializers.TURTLE.get(), add));
|
||||||
|
|
||||||
ShapedRecipeBuilder
|
ShapedRecipeBuilder
|
||||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Blocks.TURTLE_ADVANCED.get())
|
.shaped(RecipeCategory.REDSTONE, ModRegistry.Blocks.TURTLE_ADVANCED.get())
|
||||||
@@ -289,7 +277,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
|||||||
.define('C', ModRegistry.Items.COMPUTER_ADVANCED.get())
|
.define('C', ModRegistry.Items.COMPUTER_ADVANCED.get())
|
||||||
.define('I', ingredients.woodenChest())
|
.define('I', ingredients.woodenChest())
|
||||||
.unlockedBy("has_computer", inventoryChange(ModRegistry.Items.COMPUTER_NORMAL.get()))
|
.unlockedBy("has_computer", inventoryChange(ModRegistry.Items.COMPUTER_NORMAL.get()))
|
||||||
.save(RecipeWrapper.wrap(ModRegistry.RecipeSerializers.TURTLE.get(), add).withExtraData(family(ComputerFamily.ADVANCED)));
|
.save(RecipeWrapper.wrap(ModRegistry.RecipeSerializers.TURTLE.get(), add));
|
||||||
|
|
||||||
ShapedRecipeBuilder
|
ShapedRecipeBuilder
|
||||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Blocks.TURTLE_ADVANCED.get())
|
.shaped(RecipeCategory.REDSTONE, ModRegistry.Blocks.TURTLE_ADVANCED.get())
|
||||||
@@ -301,7 +289,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
|||||||
.define('B', ingredients.goldBlock())
|
.define('B', ingredients.goldBlock())
|
||||||
.unlockedBy("has_components", inventoryChange(itemPredicate(ModRegistry.Items.TURTLE_NORMAL.get()), itemPredicate(ingredients.goldIngot())))
|
.unlockedBy("has_components", inventoryChange(itemPredicate(ModRegistry.Items.TURTLE_NORMAL.get()), itemPredicate(ingredients.goldIngot())))
|
||||||
.save(
|
.save(
|
||||||
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get(), add).withExtraData(family(ComputerFamily.ADVANCED)),
|
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get(), add),
|
||||||
new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_advanced_upgrade")
|
new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_advanced_upgrade")
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -368,7 +356,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
|||||||
.define('C', ModRegistry.Items.POCKET_COMPUTER_NORMAL.get())
|
.define('C', ModRegistry.Items.POCKET_COMPUTER_NORMAL.get())
|
||||||
.unlockedBy("has_components", inventoryChange(itemPredicate(ModRegistry.Items.POCKET_COMPUTER_NORMAL.get()), itemPredicate(ingredients.goldIngot())))
|
.unlockedBy("has_components", inventoryChange(itemPredicate(ModRegistry.Items.POCKET_COMPUTER_NORMAL.get()), itemPredicate(ingredients.goldIngot())))
|
||||||
.save(
|
.save(
|
||||||
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get(), add).withExtraData(family(ComputerFamily.ADVANCED)),
|
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get(), add),
|
||||||
new ResourceLocation(ComputerCraftAPI.MOD_ID, "pocket_computer_advanced_upgrade")
|
new ResourceLocation(ComputerCraftAPI.MOD_ID, "pocket_computer_advanced_upgrade")
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -443,7 +431,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
|||||||
.requires(ModRegistry.Items.MONITOR_NORMAL.get())
|
.requires(ModRegistry.Items.MONITOR_NORMAL.get())
|
||||||
.unlockedBy("has_monitor", inventoryChange(ModRegistry.Items.MONITOR_NORMAL.get()))
|
.unlockedBy("has_monitor", inventoryChange(ModRegistry.Items.MONITOR_NORMAL.get()))
|
||||||
.save(
|
.save(
|
||||||
RecipeWrapper.wrap(RecipeSerializer.SHAPELESS_RECIPE, add)
|
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.SHAPELESS.get(), add)
|
||||||
.withResultTag(playerHead("Cloudhunter", "6d074736-b1e9-4378-a99b-bd8777821c9c")),
|
.withResultTag(playerHead("Cloudhunter", "6d074736-b1e9-4378-a99b-bd8777821c9c")),
|
||||||
new ResourceLocation(ComputerCraftAPI.MOD_ID, "skull_cloudy")
|
new ResourceLocation(ComputerCraftAPI.MOD_ID, "skull_cloudy")
|
||||||
);
|
);
|
||||||
@@ -454,7 +442,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
|||||||
.requires(ModRegistry.Items.COMPUTER_ADVANCED.get())
|
.requires(ModRegistry.Items.COMPUTER_ADVANCED.get())
|
||||||
.unlockedBy("has_computer", inventoryChange(ModRegistry.Items.COMPUTER_ADVANCED.get()))
|
.unlockedBy("has_computer", inventoryChange(ModRegistry.Items.COMPUTER_ADVANCED.get()))
|
||||||
.save(
|
.save(
|
||||||
RecipeWrapper.wrap(RecipeSerializer.SHAPELESS_RECIPE, add)
|
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.SHAPELESS.get(), add)
|
||||||
.withResultTag(playerHead("dan200", "f3c8d69b-0776-4512-8434-d1b2165909eb")),
|
.withResultTag(playerHead("dan200", "f3c8d69b-0776-4512-8434-d1b2165909eb")),
|
||||||
new ResourceLocation(ComputerCraftAPI.MOD_ID, "skull_dan200")
|
new ResourceLocation(ComputerCraftAPI.MOD_ID, "skull_dan200")
|
||||||
);
|
);
|
||||||
@@ -513,19 +501,13 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static CompoundTag playerHead(String name, String uuid) {
|
private static CompoundTag playerHead(String name, String uuid) {
|
||||||
var owner = new CompoundTag();
|
var owner = NbtUtils.writeGameProfile(new CompoundTag(), new GameProfile(UUID.fromString(uuid), name));
|
||||||
owner.putString("Name", name);
|
|
||||||
owner.putString("Id", uuid);
|
|
||||||
|
|
||||||
var tag = new CompoundTag();
|
var tag = new CompoundTag();
|
||||||
tag.put("SkullOwner", owner);
|
tag.put(PlayerHeadItem.TAG_SKULL_OWNER, owner);
|
||||||
return tag;
|
return tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Consumer<JsonObject> family(ComputerFamily family) {
|
|
||||||
return json -> json.addProperty("family", family.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void addSpecial(Consumer<FinishedRecipe> add, SimpleCraftingRecipeSerializer<?> special) {
|
private static void addSpecial(Consumer<FinishedRecipe> add, SimpleCraftingRecipeSerializer<?> special) {
|
||||||
SpecialRecipeBuilder.special(special).save(add, RegistryWrappers.RECIPE_SERIALIZERS.getKey(special).toString());
|
SpecialRecipeBuilder.special(special).save(add, RegistryWrappers.RECIPE_SERIALIZERS.getKey(special).toString());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ class TagProvider {
|
|||||||
tags.tag(ComputerCraftTags.Blocks.WIRED_MODEM).add(ModRegistry.Blocks.CABLE.get(), ModRegistry.Blocks.WIRED_MODEM_FULL.get());
|
tags.tag(ComputerCraftTags.Blocks.WIRED_MODEM).add(ModRegistry.Blocks.CABLE.get(), ModRegistry.Blocks.WIRED_MODEM_FULL.get());
|
||||||
tags.tag(ComputerCraftTags.Blocks.MONITOR).add(ModRegistry.Blocks.MONITOR_NORMAL.get(), ModRegistry.Blocks.MONITOR_ADVANCED.get());
|
tags.tag(ComputerCraftTags.Blocks.MONITOR).add(ModRegistry.Blocks.MONITOR_NORMAL.get(), ModRegistry.Blocks.MONITOR_ADVANCED.get());
|
||||||
|
|
||||||
|
tags.tag(ComputerCraftTags.Blocks.PERIPHERAL_HUB_IGNORE).addTag(ComputerCraftTags.Blocks.WIRED_MODEM);
|
||||||
|
|
||||||
tags.tag(ComputerCraftTags.Blocks.TURTLE_ALWAYS_BREAKABLE).addTag(BlockTags.LEAVES).add(
|
tags.tag(ComputerCraftTags.Blocks.TURTLE_ALWAYS_BREAKABLE).addTag(BlockTags.LEAVES).add(
|
||||||
Blocks.BAMBOO, Blocks.BAMBOO_SAPLING // Bamboo isn't instabreak for some odd reason.
|
Blocks.BAMBOO, Blocks.BAMBOO_SAPLING // Bamboo isn't instabreak for some odd reason.
|
||||||
);
|
);
|
||||||
@@ -56,7 +58,10 @@ class TagProvider {
|
|||||||
|
|
||||||
tags.tag(ComputerCraftTags.Blocks.TURTLE_SWORD_BREAKABLE).addTag(BlockTags.WOOL).add(Blocks.COBWEB);
|
tags.tag(ComputerCraftTags.Blocks.TURTLE_SWORD_BREAKABLE).addTag(BlockTags.WOOL).add(Blocks.COBWEB);
|
||||||
|
|
||||||
tags.tag(ComputerCraftTags.Blocks.TURTLE_CAN_USE).addTag(BlockTags.CAULDRONS).addTag(BlockTags.BEEHIVES);
|
tags.tag(ComputerCraftTags.Blocks.TURTLE_CAN_USE)
|
||||||
|
.addTag(BlockTags.BEEHIVES)
|
||||||
|
.addTag(BlockTags.CAULDRONS)
|
||||||
|
.add(Blocks.COMPOSTER);
|
||||||
|
|
||||||
// Make all blocks aside from command computer mineable.
|
// Make all blocks aside from command computer mineable.
|
||||||
tags.tag(BlockTags.MINEABLE_WITH_PICKAXE).add(
|
tags.tag(BlockTags.MINEABLE_WITH_PICKAXE).add(
|
||||||
@@ -74,6 +79,8 @@ class TagProvider {
|
|||||||
ModRegistry.Blocks.WIRED_MODEM_FULL.get(),
|
ModRegistry.Blocks.WIRED_MODEM_FULL.get(),
|
||||||
ModRegistry.Blocks.CABLE.get()
|
ModRegistry.Blocks.CABLE.get()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
tags.tag(BlockTags.WITHER_IMMUNE).add(ModRegistry.Blocks.COMPUTER_COMMAND.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void itemTags(ItemTagConsumer tags) {
|
public static void itemTags(ItemTagConsumer tags) {
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener;
|
|||||||
import net.minecraft.util.GsonHelper;
|
import net.minecraft.util.GsonHelper;
|
||||||
import net.minecraft.util.profiling.ProfilerFiller;
|
import net.minecraft.util.profiling.ProfilerFiller;
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.slf4j.Logger;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@@ -37,7 +37,7 @@ import java.util.stream.Collectors;
|
|||||||
* @see PocketUpgrades
|
* @see PocketUpgrades
|
||||||
*/
|
*/
|
||||||
public class UpgradeManager<R extends UpgradeSerialiser<? extends T>, T extends UpgradeBase> extends SimpleJsonResourceReloadListener {
|
public class UpgradeManager<R extends UpgradeSerialiser<? extends T>, T extends UpgradeBase> extends SimpleJsonResourceReloadListener {
|
||||||
private static final Logger LOGGER = LogManager.getLogger();
|
private static final Logger LOG = LoggerFactory.getLogger(UpgradeManager.class);
|
||||||
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
|
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
|
||||||
|
|
||||||
public record UpgradeWrapper<R extends UpgradeSerialiser<? extends T>, T extends UpgradeBase>(
|
public record UpgradeWrapper<R extends UpgradeSerialiser<? extends T>, T extends UpgradeBase>(
|
||||||
@@ -48,8 +48,8 @@ public class UpgradeManager<R extends UpgradeSerialiser<? extends T>, T extends
|
|||||||
private final String kind;
|
private final String kind;
|
||||||
private final ResourceKey<Registry<R>> registry;
|
private final ResourceKey<Registry<R>> registry;
|
||||||
|
|
||||||
private Map<String, UpgradeWrapper<R, T>> current = Collections.emptyMap();
|
private Map<String, UpgradeWrapper<R, T>> current = Map.of();
|
||||||
private Map<T, UpgradeWrapper<R, T>> currentWrappers = Collections.emptyMap();
|
private Map<T, UpgradeWrapper<R, T>> currentWrappers = Map.of();
|
||||||
|
|
||||||
public UpgradeManager(String kind, String path, ResourceKey<Registry<R>> registry) {
|
public UpgradeManager(String kind, String path, ResourceKey<Registry<R>> registry) {
|
||||||
super(GSON, path);
|
super(GSON, path);
|
||||||
@@ -103,13 +103,13 @@ public class UpgradeManager<R extends UpgradeSerialiser<? extends T>, T extends
|
|||||||
try {
|
try {
|
||||||
loadUpgrade(newUpgrades, element.getKey(), element.getValue());
|
loadUpgrade(newUpgrades, element.getKey(), element.getValue());
|
||||||
} catch (IllegalArgumentException | JsonParseException e) {
|
} catch (IllegalArgumentException | JsonParseException e) {
|
||||||
LOGGER.error("Error loading {} {} from JSON file", kind, element.getKey(), e);
|
LOG.error("Error loading {} {} from JSON file", kind, element.getKey(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
current = Collections.unmodifiableMap(newUpgrades);
|
current = Collections.unmodifiableMap(newUpgrades);
|
||||||
currentWrappers = newUpgrades.values().stream().collect(Collectors.toUnmodifiableMap(UpgradeWrapper::upgrade, x -> x));
|
currentWrappers = newUpgrades.values().stream().collect(Collectors.toUnmodifiableMap(UpgradeWrapper::upgrade, x -> x));
|
||||||
LOGGER.info("Loaded {} {}s", current.size(), kind);
|
LOG.info("Loaded {} {}s", current.size(), kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadUpgrade(Map<String, UpgradeWrapper<R, T>> current, ResourceLocation id, JsonElement json) {
|
private void loadUpgrade(Map<String, UpgradeWrapper<R, T>> current, ResourceLocation id, JsonElement json) {
|
||||||
@@ -123,7 +123,7 @@ public class UpgradeManager<R extends UpgradeSerialiser<? extends T>, T extends
|
|||||||
// TODO: Can we track which mod this resource came from and use that instead? It's theoretically possible,
|
// TODO: Can we track which mod this resource came from and use that instead? It's theoretically possible,
|
||||||
// but maybe not ideal for datapacks.
|
// but maybe not ideal for datapacks.
|
||||||
var modId = id.getNamespace();
|
var modId = id.getNamespace();
|
||||||
if (modId.equals("minecraft") || modId.equals("")) modId = ComputerCraftAPI.MOD_ID;
|
if (modId.equals("minecraft") || modId.isEmpty()) modId = ComputerCraftAPI.MOD_ID;
|
||||||
|
|
||||||
var upgrade = serialiser.fromJson(id, root);
|
var upgrade = serialiser.fromJson(id, root);
|
||||||
if (!upgrade.getUpgradeID().equals(id)) {
|
if (!upgrade.getUpgradeID().equals(id)) {
|
||||||
|
|||||||
@@ -4,45 +4,66 @@
|
|||||||
|
|
||||||
package dan200.computercraft.impl.network.wired;
|
package dan200.computercraft.impl.network.wired;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Contract;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifies certain elements of a network are "well formed".
|
* Verifies certain elements of a network are well-formed.
|
||||||
* <p>
|
* <p>
|
||||||
* This adds substantial overhead to network modification, and so should only be enabled
|
* This adds substantial overhead to network modification, and so is only enabled when assertions are enabled.
|
||||||
* in a development environment.
|
|
||||||
*/
|
*/
|
||||||
public final class InvariantChecker {
|
final class InvariantChecker {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(InvariantChecker.class);
|
private static final Logger LOG = LoggerFactory.getLogger(InvariantChecker.class);
|
||||||
private static final boolean ENABLED = false;
|
|
||||||
|
|
||||||
private InvariantChecker() {
|
private InvariantChecker() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void checkNode(WiredNodeImpl node) {
|
static void checkNode(WiredNodeImpl node) {
|
||||||
if (!ENABLED) return;
|
assert checkNodeImpl(node) : "Node invariants failed. See logs.";
|
||||||
|
}
|
||||||
|
|
||||||
var network = node.network;
|
private static boolean checkNodeImpl(WiredNodeImpl node) {
|
||||||
if (network == null) {
|
var okay = true;
|
||||||
LOG.error("Node's network is null", new Exception());
|
|
||||||
return;
|
if (node.currentSet != null) {
|
||||||
|
okay = false;
|
||||||
|
LOG.error("{}: currentSet was not cleared.", node);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (network.nodes == null || !network.nodes.contains(node)) {
|
var network = makeNullable(node.network);
|
||||||
LOG.error("Node's network does not contain node", new Exception());
|
if (network == null) {
|
||||||
|
okay = false;
|
||||||
|
LOG.error("{}: Node's network is null.", node);
|
||||||
|
} else if (makeNullable(network.nodes) == null || !network.nodes.contains(node)) {
|
||||||
|
okay = false;
|
||||||
|
LOG.error("{}: Node's network does not contain node.", node);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var neighbour : node.neighbours) {
|
for (var neighbour : node.neighbours) {
|
||||||
if (!neighbour.neighbours.contains(node)) {
|
if (!neighbour.neighbours.contains(node)) {
|
||||||
LOG.error("Neighbour is missing node", new Exception());
|
okay = false;
|
||||||
|
LOG.error("{}: Neighbour {}'s neighbour set does not contain origianl node.", node, neighbour);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return okay;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void checkNetwork(WiredNetworkImpl network) {
|
static void checkNetwork(WiredNetworkImpl network) {
|
||||||
if (!ENABLED) return;
|
assert checkNetworkImpl(network) : "Network invariants failed. See logs.";
|
||||||
|
}
|
||||||
|
|
||||||
for (var node : network.nodes) checkNode(node);
|
private static boolean checkNetworkImpl(WiredNetworkImpl network) {
|
||||||
|
var okay = true;
|
||||||
|
for (var node : network.nodes) okay &= checkNodeImpl(node);
|
||||||
|
return okay;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Contract("")
|
||||||
|
private static <T> @Nullable T makeNullable(T object) {
|
||||||
|
return object;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.impl.network.wired;
|
||||||
|
|
||||||
|
import dan200.computercraft.api.network.wired.WiredNode;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A disjoint-set/union-find of {@link WiredNodeImpl}s.
|
||||||
|
* <p>
|
||||||
|
* Rather than actually maintaining a list of included nodes, wired nodes store {@linkplain WiredNodeImpl#currentSet the
|
||||||
|
* set they're part of}. This means that we can only have one disjoint-set at once, but that is not a problem in
|
||||||
|
* practice.
|
||||||
|
*
|
||||||
|
* @see WiredNodeImpl#currentSet
|
||||||
|
* @see WiredNetworkImpl#remove(WiredNode)
|
||||||
|
* @see <a href="https://en.wikipedia.org/wiki/Disjoint-set_data_structure">Disjoint-set data structure</a>
|
||||||
|
*/
|
||||||
|
class NodeSet {
|
||||||
|
private NodeSet parent = this;
|
||||||
|
private int size = 1;
|
||||||
|
private @Nullable WiredNetworkImpl network;
|
||||||
|
|
||||||
|
private boolean isRoot() {
|
||||||
|
return parent == this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve this union, finding the root {@link NodeSet}.
|
||||||
|
*
|
||||||
|
* @return The root union.
|
||||||
|
*/
|
||||||
|
NodeSet find() {
|
||||||
|
var self = this;
|
||||||
|
while (!self.isRoot()) self = self.parent = self.parent.parent;
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the size of this node set.
|
||||||
|
*
|
||||||
|
* @return The size of the set.
|
||||||
|
*/
|
||||||
|
int size() {
|
||||||
|
return find().size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a node to this {@link NodeSet}.
|
||||||
|
*
|
||||||
|
* @param node The node to add to the set.
|
||||||
|
*/
|
||||||
|
void addNode(WiredNodeImpl node) {
|
||||||
|
if (!isRoot()) throw new IllegalStateException("Cannot grow a non-root set.");
|
||||||
|
if (node.currentSet != null) throw new IllegalArgumentException("Node is already in a set.");
|
||||||
|
|
||||||
|
node.currentSet = this;
|
||||||
|
size++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge two nodes sets together.
|
||||||
|
*
|
||||||
|
* @param left The first union.
|
||||||
|
* @param right The second union.
|
||||||
|
* @return The union which was subsumed.
|
||||||
|
*/
|
||||||
|
public static NodeSet merge(NodeSet left, NodeSet right) {
|
||||||
|
if (!left.isRoot() || !right.isRoot()) throw new IllegalArgumentException("Cannot union a non-root set.");
|
||||||
|
if (left == right) throw new IllegalArgumentException("Cannot merge a node into itself.");
|
||||||
|
|
||||||
|
return left.size >= right.size ? mergeInto(left, right) : mergeInto(right, left);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static NodeSet mergeInto(NodeSet root, NodeSet child) {
|
||||||
|
assert root.size > child.size;
|
||||||
|
child.parent = root;
|
||||||
|
root.size += child.size;
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setNetwork(WiredNetworkImpl network) {
|
||||||
|
if (!isRoot()) throw new IllegalStateException("Set is not the root.");
|
||||||
|
if (this.network != null) throw new IllegalStateException("Set already has a network.");
|
||||||
|
this.network = network;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the associated network.
|
||||||
|
*
|
||||||
|
* @return The associated network.
|
||||||
|
*/
|
||||||
|
WiredNetworkImpl network() {
|
||||||
|
return Objects.requireNonNull(find().network);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,13 +6,14 @@ package dan200.computercraft.impl.network.wired;
|
|||||||
|
|
||||||
import dan200.computercraft.api.network.wired.WiredNetworkChange;
|
import dan200.computercraft.api.network.wired.WiredNetworkChange;
|
||||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||||
|
import dan200.computercraft.core.util.PeripheralHelpers;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
final class WiredNetworkChangeImpl implements WiredNetworkChange {
|
final class WiredNetworkChangeImpl implements WiredNetworkChange {
|
||||||
private static final WiredNetworkChangeImpl EMPTY = new WiredNetworkChangeImpl(Collections.emptyMap(), Collections.emptyMap());
|
private static final WiredNetworkChangeImpl EMPTY = new WiredNetworkChangeImpl(Map.of(), Map.of());
|
||||||
|
|
||||||
private final Map<String, IPeripheral> removed;
|
private final Map<String, IPeripheral> removed;
|
||||||
private final Map<String, IPeripheral> added;
|
private final Map<String, IPeripheral> added;
|
||||||
@@ -27,11 +28,11 @@ final class WiredNetworkChangeImpl implements WiredNetworkChange {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static WiredNetworkChangeImpl added(Map<String, IPeripheral> added) {
|
static WiredNetworkChangeImpl added(Map<String, IPeripheral> added) {
|
||||||
return added.isEmpty() ? EMPTY : new WiredNetworkChangeImpl(Collections.emptyMap(), Collections.unmodifiableMap(added));
|
return added.isEmpty() ? EMPTY : new WiredNetworkChangeImpl(Map.of(), Collections.unmodifiableMap(added));
|
||||||
}
|
}
|
||||||
|
|
||||||
static WiredNetworkChangeImpl removed(Map<String, IPeripheral> removed) {
|
static WiredNetworkChangeImpl removed(Map<String, IPeripheral> removed) {
|
||||||
return removed.isEmpty() ? EMPTY : new WiredNetworkChangeImpl(Collections.unmodifiableMap(removed), Collections.emptyMap());
|
return removed.isEmpty() ? EMPTY : new WiredNetworkChangeImpl(Collections.unmodifiableMap(removed), Map.of());
|
||||||
}
|
}
|
||||||
|
|
||||||
static WiredNetworkChangeImpl changeOf(Map<String, IPeripheral> oldPeripherals, Map<String, IPeripheral> newPeripherals) {
|
static WiredNetworkChangeImpl changeOf(Map<String, IPeripheral> oldPeripherals, Map<String, IPeripheral> newPeripherals) {
|
||||||
@@ -39,9 +40,9 @@ final class WiredNetworkChangeImpl implements WiredNetworkChange {
|
|||||||
if (oldPeripherals.isEmpty() && newPeripherals.isEmpty()) {
|
if (oldPeripherals.isEmpty() && newPeripherals.isEmpty()) {
|
||||||
return EMPTY;
|
return EMPTY;
|
||||||
} else if (oldPeripherals.isEmpty()) {
|
} else if (oldPeripherals.isEmpty()) {
|
||||||
return new WiredNetworkChangeImpl(Collections.emptyMap(), newPeripherals);
|
return new WiredNetworkChangeImpl(Map.of(), newPeripherals);
|
||||||
} else if (newPeripherals.isEmpty()) {
|
} else if (newPeripherals.isEmpty()) {
|
||||||
return new WiredNetworkChangeImpl(oldPeripherals, Collections.emptyMap());
|
return new WiredNetworkChangeImpl(oldPeripherals, Map.of());
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, IPeripheral> added = new HashMap<>(newPeripherals);
|
Map<String, IPeripheral> added = new HashMap<>(newPeripherals);
|
||||||
@@ -52,7 +53,7 @@ final class WiredNetworkChangeImpl implements WiredNetworkChange {
|
|||||||
var oldValue = entry.getValue();
|
var oldValue = entry.getValue();
|
||||||
if (newPeripherals.containsKey(oldKey)) {
|
if (newPeripherals.containsKey(oldKey)) {
|
||||||
var rightValue = added.get(oldKey);
|
var rightValue = added.get(oldKey);
|
||||||
if (oldValue.equals(rightValue)) {
|
if (PeripheralHelpers.equals(oldValue, rightValue)) {
|
||||||
added.remove(oldKey);
|
added.remove(oldKey);
|
||||||
} else {
|
} else {
|
||||||
removed.put(oldKey, oldValue);
|
removed.put(oldKey, oldValue);
|
||||||
|
|||||||
@@ -4,11 +4,11 @@
|
|||||||
|
|
||||||
package dan200.computercraft.impl.network.wired;
|
package dan200.computercraft.impl.network.wired;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import dan200.computercraft.api.network.Packet;
|
import dan200.computercraft.api.network.Packet;
|
||||||
import dan200.computercraft.api.network.wired.WiredNetwork;
|
import dan200.computercraft.api.network.wired.WiredNetwork;
|
||||||
import dan200.computercraft.api.network.wired.WiredNode;
|
import dan200.computercraft.api.network.wired.WiredNode;
|
||||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||||
|
import dan200.computercraft.core.util.Nullability;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.locks.ReadWriteLock;
|
import java.util.concurrent.locks.ReadWriteLock;
|
||||||
@@ -57,10 +57,10 @@ final class WiredNetworkImpl implements WiredNetwork {
|
|||||||
// Move all nodes across into this network, destroying the original nodes.
|
// Move all nodes across into this network, destroying the original nodes.
|
||||||
nodes.addAll(otherNodes);
|
nodes.addAll(otherNodes);
|
||||||
for (var node : otherNodes) node.network = this;
|
for (var node : otherNodes) node.network = this;
|
||||||
other.nodes = Collections.emptySet();
|
other.nodes = Set.of();
|
||||||
|
|
||||||
// Move all peripherals across,
|
// Move all peripherals across,
|
||||||
other.peripherals = Collections.emptyMap();
|
other.peripherals = Map.of();
|
||||||
peripherals.putAll(otherPeripherals);
|
peripherals.putAll(otherPeripherals);
|
||||||
|
|
||||||
if (!thisPeripherals.isEmpty()) {
|
if (!thisPeripherals.isEmpty()) {
|
||||||
@@ -188,10 +188,76 @@ final class WiredNetworkImpl implements WiredNetwork {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var reachable = reachableNodes(neighbours.iterator().next());
|
assert neighbours.size() >= 2 : "Must have more than one neighbour.";
|
||||||
|
|
||||||
|
/*
|
||||||
|
Otherwise we need to find all sets of connected nodes within the graph, and split them off into their own
|
||||||
|
networks.
|
||||||
|
|
||||||
|
With our current graph representation[^1], this requires a traversal of the graph, taking O(|V| + |E))
|
||||||
|
time, which can get quite expensive for large graphs. We try to avoid this traversal where possible, by
|
||||||
|
optimising for the case where the graph remains fully connected after removing this node, for instance,
|
||||||
|
removing "A" here:
|
||||||
|
|
||||||
|
A---B B
|
||||||
|
| | => |
|
||||||
|
C---D C---D
|
||||||
|
|
||||||
|
We observe that these sorts of loops tend to be local, and so try to identify them as quickly as possible.
|
||||||
|
To do this, we do a standard breadth-first traversal of the graph starting at the neighbours of the
|
||||||
|
removed node, building sets of connected nodes.
|
||||||
|
|
||||||
|
If, at any point, all nodes visited so far are connected to each other, then we know all remaining nodes
|
||||||
|
will also be connected. This allows us to abort our traversal of the graph, and just remove the node (much
|
||||||
|
like we do in the single neighbour case above).
|
||||||
|
|
||||||
|
Otherwise, we then just create a new network for each disjoint set of connected nodes.
|
||||||
|
|
||||||
|
{^1]:
|
||||||
|
There are efficient (near-logarithmic) algorithms for this (e.g. https://arxiv.org/pdf/1609.05867.pdf),
|
||||||
|
but they are significantly more complex to implement.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Create a new set of nodes for each neighbour, and add them to our queue of nodes to visit.
|
||||||
|
List<WiredNodeImpl> queue = new ArrayList<>();
|
||||||
|
Set<NodeSet> nodeSets = new HashSet<>(neighbours.size());
|
||||||
|
for (var neighbour : neighbours) {
|
||||||
|
nodeSets.add(neighbour.currentSet = new NodeSet());
|
||||||
|
queue.add(neighbour);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform a breadth-first search of the graph, starting from the neighbours.
|
||||||
|
graphSearch:
|
||||||
|
for (var i = 0; i < queue.size(); i++) {
|
||||||
|
var enqueuedNode = queue.get(i);
|
||||||
|
for (var neighbour : enqueuedNode.neighbours) {
|
||||||
|
var nodeSet = Nullability.assertNonNull(enqueuedNode.currentSet).find();
|
||||||
|
|
||||||
|
// The neighbour has no set and so has not been visited yet. Add it to the current set and enqueue
|
||||||
|
// it to be visited.
|
||||||
|
if (neighbour.currentSet == null) {
|
||||||
|
nodeSet.addNode(neighbour);
|
||||||
|
queue.add(neighbour);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, take the union of the two nodes' sets if needed. If we've only got a single node set
|
||||||
|
// left, then we know the whole graph is network is connected (even if not all nodes have been
|
||||||
|
// visited) and so can abort early.
|
||||||
|
var neighbourSet = neighbour.currentSet.find();
|
||||||
|
if (nodeSet != neighbourSet) {
|
||||||
|
var removed = nodeSets.remove(NodeSet.merge(nodeSet, neighbourSet));
|
||||||
|
assert removed : "Merged set should have been ";
|
||||||
|
if (nodeSets.size() == 1) break graphSearch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have a single subset, then all nodes are reachable - just clear the set and exit.
|
||||||
|
if (nodeSets.size() == 1) {
|
||||||
|
assert nodeSets.iterator().next().size() == queue.size();
|
||||||
|
for (var neighbour : queue) neighbour.currentSet = null;
|
||||||
|
|
||||||
// If all nodes are reachable then exit.
|
|
||||||
if (reachable.size() == nodes.size()) {
|
|
||||||
// Broadcast our simple peripheral changes
|
// Broadcast our simple peripheral changes
|
||||||
removeSingleNode(wired, wiredNetwork);
|
removeSingleNode(wired, wiredNetwork);
|
||||||
InvariantChecker.checkNode(wired);
|
InvariantChecker.checkNode(wired);
|
||||||
@@ -199,43 +265,46 @@ final class WiredNetworkImpl implements WiredNetwork {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A split may cause 2..neighbours.size() separate networks, so we
|
assert queue.size() == nodes.size() : "Expected queue to contain all nodes.";
|
||||||
// iterate through our neighbour list, generating child networks.
|
|
||||||
neighbours.removeAll(reachable);
|
|
||||||
var maximals = new ArrayList<WiredNetworkImpl>(neighbours.size() + 1);
|
|
||||||
maximals.add(wiredNetwork);
|
|
||||||
maximals.add(new WiredNetworkImpl(reachable));
|
|
||||||
|
|
||||||
while (!neighbours.isEmpty()) {
|
// Otherwise we need to create our new networks.
|
||||||
reachable = reachableNodes(neighbours.iterator().next());
|
var networks = new ArrayList<WiredNetworkImpl>(1 + nodeSets.size());
|
||||||
neighbours.removeAll(reachable);
|
// Add the network we've created for the removed node.
|
||||||
maximals.add(new WiredNetworkImpl(reachable));
|
networks.add(wiredNetwork);
|
||||||
|
// And then create a new network for each disjoint subset.
|
||||||
|
for (var set : nodeSets) {
|
||||||
|
var network = new WiredNetworkImpl(new HashSet<>(set.size()));
|
||||||
|
set.setNetwork(network);
|
||||||
|
networks.add(network);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var network : maximals) network.lock.writeLock().lock();
|
for (var network : networks) network.lock.writeLock().lock();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// We special case the original node: detaching all peripherals when needed.
|
// We special case the original node: detaching all peripherals when needed.
|
||||||
wired.network = wiredNetwork;
|
wired.network = wiredNetwork;
|
||||||
wired.peripherals = Collections.emptyMap();
|
wired.peripherals = Map.of();
|
||||||
|
wired.neighbours.clear();
|
||||||
|
|
||||||
// Ensure every network is finalised
|
// Add all nodes to their appropriate network.
|
||||||
for (var network : maximals) {
|
for (var child : queue) {
|
||||||
for (var child : network.nodes) {
|
var network = Nullability.assertNonNull(child.currentSet).network();
|
||||||
child.network = network;
|
child.currentSet = null;
|
||||||
network.peripherals.putAll(child.peripherals);
|
|
||||||
}
|
child.network = network;
|
||||||
|
network.nodes.add(child);
|
||||||
|
network.peripherals.putAll(child.peripherals);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var network : maximals) InvariantChecker.checkNetwork(network);
|
for (var network : networks) InvariantChecker.checkNetwork(network);
|
||||||
InvariantChecker.checkNode(wired);
|
InvariantChecker.checkNode(wired);
|
||||||
|
|
||||||
// Then broadcast network changes once all nodes are finalised
|
// Then broadcast network changes once all nodes are finalised
|
||||||
for (var network : maximals) {
|
for (var network : networks) {
|
||||||
WiredNetworkChangeImpl.changeOf(peripherals, network.peripherals).broadcast(network.nodes);
|
WiredNetworkChangeImpl.changeOf(peripherals, network.peripherals).broadcast(network.nodes);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
for (var network : maximals) network.lock.writeLock().unlock();
|
for (var network : networks) network.lock.writeLock().unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
nodes.clear();
|
nodes.clear();
|
||||||
@@ -260,7 +329,7 @@ final class WiredNetworkImpl implements WiredNetwork {
|
|||||||
var change = WiredNetworkChangeImpl.changeOf(oldPeripherals, newPeripherals);
|
var change = WiredNetworkChangeImpl.changeOf(oldPeripherals, newPeripherals);
|
||||||
if (change.isEmpty()) return;
|
if (change.isEmpty()) return;
|
||||||
|
|
||||||
wired.peripherals = ImmutableMap.copyOf(newPeripherals);
|
wired.peripherals = Map.copyOf(newPeripherals);
|
||||||
|
|
||||||
// Detach the old peripherals then remove them.
|
// Detach the old peripherals then remove them.
|
||||||
peripherals.keySet().removeAll(change.peripheralsRemoved().keySet());
|
peripherals.keySet().removeAll(change.peripheralsRemoved().keySet());
|
||||||
@@ -333,7 +402,7 @@ final class WiredNetworkImpl implements WiredNetwork {
|
|||||||
// Detach the old peripherals then remove them from the old network
|
// Detach the old peripherals then remove them from the old network
|
||||||
wired.network = wiredNetwork;
|
wired.network = wiredNetwork;
|
||||||
wired.neighbours.clear();
|
wired.neighbours.clear();
|
||||||
wired.peripherals = Collections.emptyMap();
|
wired.peripherals = Map.of();
|
||||||
|
|
||||||
// Broadcast the change
|
// Broadcast the change
|
||||||
if (!peripherals.isEmpty()) WiredNetworkChangeImpl.removed(peripherals).broadcast(wired);
|
if (!peripherals.isEmpty()) WiredNetworkChangeImpl.removed(peripherals).broadcast(wired);
|
||||||
@@ -374,22 +443,4 @@ final class WiredNetworkImpl implements WiredNetwork {
|
|||||||
throw new IllegalArgumentException("Unknown implementation of IWiredNode: " + node);
|
throw new IllegalArgumentException("Unknown implementation of IWiredNode: " + node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Set<WiredNodeImpl> reachableNodes(WiredNodeImpl start) {
|
|
||||||
Queue<WiredNodeImpl> enqueued = new ArrayDeque<>();
|
|
||||||
var reachable = new HashSet<WiredNodeImpl>();
|
|
||||||
|
|
||||||
reachable.add(start);
|
|
||||||
enqueued.add(start);
|
|
||||||
|
|
||||||
WiredNodeImpl node;
|
|
||||||
while ((node = enqueued.poll()) != null) {
|
|
||||||
for (var neighbour : node.neighbours) {
|
|
||||||
// Otherwise attempt to enqueue this neighbour as well.
|
|
||||||
if (reachable.add(neighbour)) enqueued.add(neighbour);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return reachable;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,22 +13,53 @@ import dan200.computercraft.api.network.wired.WiredSender;
|
|||||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.util.*;
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public final class WiredNodeImpl implements WiredNode {
|
public final class WiredNodeImpl implements WiredNode {
|
||||||
private @Nullable Set<PacketReceiver> receivers;
|
private @Nullable Set<PacketReceiver> receivers;
|
||||||
|
|
||||||
final WiredElement element;
|
final WiredElement element;
|
||||||
Map<String, IPeripheral> peripherals = Collections.emptyMap();
|
Map<String, IPeripheral> peripherals = Map.of();
|
||||||
|
|
||||||
final HashSet<WiredNodeImpl> neighbours = new HashSet<>();
|
final HashSet<WiredNodeImpl> neighbours = new HashSet<>();
|
||||||
volatile WiredNetworkImpl network;
|
volatile WiredNetworkImpl network;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A temporary field used when checking network connectivity.
|
||||||
|
*
|
||||||
|
* @see WiredNetworkImpl#remove(WiredNode)
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
NodeSet currentSet;
|
||||||
|
|
||||||
public WiredNodeImpl(WiredElement element) {
|
public WiredNodeImpl(WiredElement element) {
|
||||||
this.element = element;
|
this.element = element;
|
||||||
network = new WiredNetworkImpl(this);
|
network = new WiredNetworkImpl(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean connectTo(WiredNode node) {
|
||||||
|
return network.connect(this, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean disconnectFrom(WiredNode node) {
|
||||||
|
return network == ((WiredNodeImpl) node).network && network.disconnect(this, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean remove() {
|
||||||
|
return network.remove(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updatePeripherals(Map<String, IPeripheral> peripherals) {
|
||||||
|
network.updatePeripherals(this, peripherals);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void addReceiver(PacketReceiver receiver) {
|
public synchronized void addReceiver(PacketReceiver receiver) {
|
||||||
if (receivers == null) receivers = new HashSet<>();
|
if (receivers == null) receivers = new HashSet<>();
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
package dan200.computercraft.mixin;
|
|
||||||
|
|
||||||
import dan200.computercraft.data.PrettyJsonWriter;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
|
||||||
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
|
||||||
|
|
||||||
@Mixin(targets = "net/minecraft/data/HashCache$CacheUpdater")
|
|
||||||
public class CacheUpdaterMixin {
|
|
||||||
@SuppressWarnings("UnusedMethod")
|
|
||||||
@ModifyArg(
|
|
||||||
method = "writeIfNeeded",
|
|
||||||
at = @At(value = "INVOKE", target = "Ljava/nio/file/Files;write(Ljava/nio/file/Path;[B[Ljava/nio/file/OpenOption;)Ljava/nio/file/Path;"),
|
|
||||||
require = 0
|
|
||||||
)
|
|
||||||
private byte[] reformatJson(byte[] contents) {
|
|
||||||
// It would be cleaner to do this inside DataProvider.saveStable, but Forge's version of Mixin doesn't allow us
|
|
||||||
// to inject into interfaces.
|
|
||||||
return PrettyJsonWriter.reformat(contents);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -14,12 +14,16 @@ import dan200.computercraft.shared.computer.metrics.ComputerMBean;
|
|||||||
import dan200.computercraft.shared.peripheral.monitor.MonitorWatcher;
|
import dan200.computercraft.shared.peripheral.monitor.MonitorWatcher;
|
||||||
import dan200.computercraft.shared.util.DropConsumer;
|
import dan200.computercraft.shared.util.DropConsumer;
|
||||||
import dan200.computercraft.shared.util.TickScheduler;
|
import dan200.computercraft.shared.util.TickScheduler;
|
||||||
|
import net.minecraft.resources.ResourceKey;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.server.MinecraftServer;
|
import net.minecraft.server.MinecraftServer;
|
||||||
import net.minecraft.server.dedicated.DedicatedServer;
|
import net.minecraft.server.dedicated.DedicatedServer;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
import net.minecraft.server.level.ServerPlayer;
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
import net.minecraft.server.packs.resources.PreparableReloadListener;
|
import net.minecraft.server.packs.resources.PreparableReloadListener;
|
||||||
import net.minecraft.world.entity.Entity;
|
import net.minecraft.world.entity.Entity;
|
||||||
|
import net.minecraft.world.item.CreativeModeTab;
|
||||||
|
import net.minecraft.world.item.CreativeModeTabs;
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
import net.minecraft.world.level.chunk.LevelChunk;
|
import net.minecraft.world.level.chunk.LevelChunk;
|
||||||
import net.minecraft.world.level.storage.loot.BuiltInLootTables;
|
import net.minecraft.world.level.storage.loot.BuiltInLootTables;
|
||||||
@@ -69,10 +73,19 @@ public final class CommonHooks {
|
|||||||
NetworkUtils.reset();
|
NetworkUtils.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void onServerChunkUnload(LevelChunk chunk) {
|
||||||
|
if (!(chunk.getLevel() instanceof ServerLevel)) throw new IllegalArgumentException("Not a server chunk.");
|
||||||
|
TickScheduler.onChunkUnload(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
public static void onChunkWatch(LevelChunk chunk, ServerPlayer player) {
|
public static void onChunkWatch(LevelChunk chunk, ServerPlayer player) {
|
||||||
MonitorWatcher.onWatch(chunk, player);
|
MonitorWatcher.onWatch(chunk, player);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void onChunkTicketLevelChanged(ServerLevel level, long chunkPos, int oldLevel, int newLevel) {
|
||||||
|
TickScheduler.onChunkTicketChanged(level, chunkPos, oldLevel, newLevel);
|
||||||
|
}
|
||||||
|
|
||||||
public static final ResourceLocation TREASURE_DISK_LOOT = new ResourceLocation(ComputerCraftAPI.MOD_ID, "treasure_disk");
|
public static final ResourceLocation TREASURE_DISK_LOOT = new ResourceLocation(ComputerCraftAPI.MOD_ID, "treasure_disk");
|
||||||
|
|
||||||
private static final Set<ResourceLocation> TREASURE_DISK_LOOT_TABLES = Set.of(
|
private static final Set<ResourceLocation> TREASURE_DISK_LOOT_TABLES = Set.of(
|
||||||
@@ -111,4 +124,17 @@ public final class CommonHooks {
|
|||||||
public static boolean onLivingDrop(Entity entity, ItemStack stack) {
|
public static boolean onLivingDrop(Entity entity, ItemStack stack) {
|
||||||
return DropConsumer.onLivingDrop(entity, stack);
|
return DropConsumer.onLivingDrop(entity, stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add items to an existing creative tab.
|
||||||
|
*
|
||||||
|
* @param key The {@link ResourceKey} for this creative tab.
|
||||||
|
* @param context Additional parameters used for building the contents.
|
||||||
|
* @param out The creative tab output to append items to.
|
||||||
|
*/
|
||||||
|
public static void onBuildCreativeTab(ResourceKey<CreativeModeTab> key, CreativeModeTab.ItemDisplayParameters context, CreativeModeTab.Output out) {
|
||||||
|
if (key == CreativeModeTabs.OP_BLOCKS && context.hasPermissions()) {
|
||||||
|
out.accept(ModRegistry.Items.COMPUTER_COMMAND.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import dan200.computercraft.impl.PocketUpgrades;
|
|||||||
import dan200.computercraft.impl.TurtleUpgrades;
|
import dan200.computercraft.impl.TurtleUpgrades;
|
||||||
import dan200.computercraft.shared.command.UserLevel;
|
import dan200.computercraft.shared.command.UserLevel;
|
||||||
import dan200.computercraft.shared.command.arguments.ComputerArgumentType;
|
import dan200.computercraft.shared.command.arguments.ComputerArgumentType;
|
||||||
import dan200.computercraft.shared.command.arguments.ComputersArgumentType;
|
|
||||||
import dan200.computercraft.shared.command.arguments.RepeatArgumentType;
|
import dan200.computercraft.shared.command.arguments.RepeatArgumentType;
|
||||||
import dan200.computercraft.shared.command.arguments.TrackingFieldArgumentType;
|
import dan200.computercraft.shared.command.arguments.TrackingFieldArgumentType;
|
||||||
import dan200.computercraft.shared.common.ClearColourRecipe;
|
import dan200.computercraft.shared.common.ClearColourRecipe;
|
||||||
@@ -34,6 +33,7 @@ import dan200.computercraft.shared.computer.inventory.ViewComputerMenu;
|
|||||||
import dan200.computercraft.shared.computer.items.CommandComputerItem;
|
import dan200.computercraft.shared.computer.items.CommandComputerItem;
|
||||||
import dan200.computercraft.shared.computer.items.ComputerItem;
|
import dan200.computercraft.shared.computer.items.ComputerItem;
|
||||||
import dan200.computercraft.shared.computer.recipe.ComputerUpgradeRecipe;
|
import dan200.computercraft.shared.computer.recipe.ComputerUpgradeRecipe;
|
||||||
|
import dan200.computercraft.shared.config.Config;
|
||||||
import dan200.computercraft.shared.data.BlockNamedEntityLootCondition;
|
import dan200.computercraft.shared.data.BlockNamedEntityLootCondition;
|
||||||
import dan200.computercraft.shared.data.ConstantLootConditionSerializer;
|
import dan200.computercraft.shared.data.ConstantLootConditionSerializer;
|
||||||
import dan200.computercraft.shared.data.HasComputerIdLootCondition;
|
import dan200.computercraft.shared.data.HasComputerIdLootCondition;
|
||||||
@@ -70,6 +70,10 @@ import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
|||||||
import dan200.computercraft.shared.pocket.peripherals.PocketModem;
|
import dan200.computercraft.shared.pocket.peripherals.PocketModem;
|
||||||
import dan200.computercraft.shared.pocket.peripherals.PocketSpeaker;
|
import dan200.computercraft.shared.pocket.peripherals.PocketSpeaker;
|
||||||
import dan200.computercraft.shared.pocket.recipes.PocketComputerUpgradeRecipe;
|
import dan200.computercraft.shared.pocket.recipes.PocketComputerUpgradeRecipe;
|
||||||
|
import dan200.computercraft.shared.recipe.CustomShapedRecipe;
|
||||||
|
import dan200.computercraft.shared.recipe.CustomShapelessRecipe;
|
||||||
|
import dan200.computercraft.shared.recipe.ImpostorShapedRecipe;
|
||||||
|
import dan200.computercraft.shared.recipe.ImpostorShapelessRecipe;
|
||||||
import dan200.computercraft.shared.turtle.FurnaceRefuelHandler;
|
import dan200.computercraft.shared.turtle.FurnaceRefuelHandler;
|
||||||
import dan200.computercraft.shared.turtle.blocks.TurtleBlock;
|
import dan200.computercraft.shared.turtle.blocks.TurtleBlock;
|
||||||
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
|
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
|
||||||
@@ -79,8 +83,6 @@ import dan200.computercraft.shared.turtle.recipes.TurtleOverlayRecipe;
|
|||||||
import dan200.computercraft.shared.turtle.recipes.TurtleRecipe;
|
import dan200.computercraft.shared.turtle.recipes.TurtleRecipe;
|
||||||
import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe;
|
import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe;
|
||||||
import dan200.computercraft.shared.turtle.upgrades.*;
|
import dan200.computercraft.shared.turtle.upgrades.*;
|
||||||
import dan200.computercraft.shared.util.ImpostorRecipe;
|
|
||||||
import dan200.computercraft.shared.util.ImpostorShapelessRecipe;
|
|
||||||
import net.minecraft.commands.CommandSourceStack;
|
import net.minecraft.commands.CommandSourceStack;
|
||||||
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
|
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
|
||||||
import net.minecraft.commands.synchronization.SingletonArgumentInfo;
|
import net.minecraft.commands.synchronization.SingletonArgumentInfo;
|
||||||
@@ -137,19 +139,17 @@ public final class ModRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static final RegistryEntry<ComputerBlock<ComputerBlockEntity>> COMPUTER_NORMAL = REGISTRY.register("computer_normal",
|
public static final RegistryEntry<ComputerBlock<ComputerBlockEntity>> COMPUTER_NORMAL = REGISTRY.register("computer_normal",
|
||||||
() -> new ComputerBlock<>(computerProperties().mapColor(MapColor.STONE), ComputerFamily.NORMAL, BlockEntities.COMPUTER_NORMAL));
|
() -> new ComputerBlock<>(computerProperties().mapColor(MapColor.STONE), BlockEntities.COMPUTER_NORMAL));
|
||||||
public static final RegistryEntry<ComputerBlock<ComputerBlockEntity>> COMPUTER_ADVANCED = REGISTRY.register("computer_advanced",
|
public static final RegistryEntry<ComputerBlock<ComputerBlockEntity>> COMPUTER_ADVANCED = REGISTRY.register("computer_advanced",
|
||||||
() -> new ComputerBlock<>(computerProperties().mapColor(MapColor.GOLD), ComputerFamily.ADVANCED, BlockEntities.COMPUTER_ADVANCED));
|
() -> new ComputerBlock<>(computerProperties().mapColor(MapColor.GOLD), BlockEntities.COMPUTER_ADVANCED));
|
||||||
|
|
||||||
public static final RegistryEntry<ComputerBlock<CommandComputerBlockEntity>> COMPUTER_COMMAND = REGISTRY.register("computer_command", () -> new CommandComputerBlock<>(
|
public static final RegistryEntry<ComputerBlock<CommandComputerBlockEntity>> COMPUTER_COMMAND = REGISTRY.register("computer_command",
|
||||||
computerProperties().strength(-1, 6000000.0F),
|
() -> new CommandComputerBlock<>(computerProperties().strength(-1, 6000000.0F), BlockEntities.COMPUTER_COMMAND));
|
||||||
ComputerFamily.COMMAND, BlockEntities.COMPUTER_COMMAND
|
|
||||||
));
|
|
||||||
|
|
||||||
public static final RegistryEntry<TurtleBlock> TURTLE_NORMAL = REGISTRY.register("turtle_normal",
|
public static final RegistryEntry<TurtleBlock> TURTLE_NORMAL = REGISTRY.register("turtle_normal",
|
||||||
() -> new TurtleBlock(turtleProperties().mapColor(MapColor.STONE), ComputerFamily.NORMAL, BlockEntities.TURTLE_NORMAL));
|
() -> new TurtleBlock(turtleProperties().mapColor(MapColor.STONE), BlockEntities.TURTLE_NORMAL));
|
||||||
public static final RegistryEntry<TurtleBlock> TURTLE_ADVANCED = REGISTRY.register("turtle_advanced",
|
public static final RegistryEntry<TurtleBlock> TURTLE_ADVANCED = REGISTRY.register("turtle_advanced",
|
||||||
() -> new TurtleBlock(turtleProperties().mapColor(MapColor.GOLD), ComputerFamily.ADVANCED, BlockEntities.TURTLE_ADVANCED));
|
() -> new TurtleBlock(turtleProperties().mapColor(MapColor.GOLD).explosionResistance(TurtleBlock.IMMUNE_EXPLOSION_RESISTANCE), BlockEntities.TURTLE_ADVANCED));
|
||||||
|
|
||||||
public static final RegistryEntry<SpeakerBlock> SPEAKER = REGISTRY.register("speaker", () -> new SpeakerBlock(properties().mapColor(MapColor.STONE)));
|
public static final RegistryEntry<SpeakerBlock> SPEAKER = REGISTRY.register("speaker", () -> new SpeakerBlock(properties().mapColor(MapColor.STONE)));
|
||||||
public static final RegistryEntry<DiskDriveBlock> DISK_DRIVE = REGISTRY.register("disk_drive", () -> new DiskDriveBlock(properties().mapColor(MapColor.STONE)));
|
public static final RegistryEntry<DiskDriveBlock> DISK_DRIVE = REGISTRY.register("disk_drive", () -> new DiskDriveBlock(properties().mapColor(MapColor.STONE)));
|
||||||
@@ -190,9 +190,9 @@ public final class ModRegistry {
|
|||||||
ofBlock(Blocks.COMPUTER_COMMAND, (p, s) -> new CommandComputerBlockEntity(BlockEntities.COMPUTER_COMMAND.get(), p, s));
|
ofBlock(Blocks.COMPUTER_COMMAND, (p, s) -> new CommandComputerBlockEntity(BlockEntities.COMPUTER_COMMAND.get(), p, s));
|
||||||
|
|
||||||
public static final RegistryEntry<BlockEntityType<TurtleBlockEntity>> TURTLE_NORMAL =
|
public static final RegistryEntry<BlockEntityType<TurtleBlockEntity>> TURTLE_NORMAL =
|
||||||
ofBlock(Blocks.TURTLE_NORMAL, (p, s) -> new TurtleBlockEntity(BlockEntities.TURTLE_NORMAL.get(), p, s, ComputerFamily.NORMAL));
|
ofBlock(Blocks.TURTLE_NORMAL, (p, s) -> new TurtleBlockEntity(BlockEntities.TURTLE_NORMAL.get(), p, s, () -> Config.turtleFuelLimit, ComputerFamily.NORMAL));
|
||||||
public static final RegistryEntry<BlockEntityType<TurtleBlockEntity>> TURTLE_ADVANCED =
|
public static final RegistryEntry<BlockEntityType<TurtleBlockEntity>> TURTLE_ADVANCED =
|
||||||
ofBlock(Blocks.TURTLE_ADVANCED, (p, s) -> new TurtleBlockEntity(BlockEntities.TURTLE_ADVANCED.get(), p, s, ComputerFamily.ADVANCED));
|
ofBlock(Blocks.TURTLE_ADVANCED, (p, s) -> new TurtleBlockEntity(BlockEntities.TURTLE_ADVANCED.get(), p, s, () -> Config.advancedTurtleFuelLimit, ComputerFamily.ADVANCED));
|
||||||
|
|
||||||
public static final RegistryEntry<BlockEntityType<SpeakerBlockEntity>> SPEAKER =
|
public static final RegistryEntry<BlockEntityType<SpeakerBlockEntity>> SPEAKER =
|
||||||
ofBlock(Blocks.SPEAKER, (p, s) -> new SpeakerBlockEntity(BlockEntities.SPEAKER.get(), p, s));
|
ofBlock(Blocks.SPEAKER, (p, s) -> new SpeakerBlockEntity(BlockEntities.SPEAKER.get(), p, s));
|
||||||
@@ -309,7 +309,10 @@ public final class ModRegistry {
|
|||||||
() -> new MenuType<>(PrinterMenu::new, FeatureFlags.VANILLA_SET));
|
() -> new MenuType<>(PrinterMenu::new, FeatureFlags.VANILLA_SET));
|
||||||
|
|
||||||
public static final RegistryEntry<MenuType<HeldItemMenu>> PRINTOUT = REGISTRY.register("printout",
|
public static final RegistryEntry<MenuType<HeldItemMenu>> PRINTOUT = REGISTRY.register("printout",
|
||||||
() -> ContainerData.toType(HeldItemContainerData::new, HeldItemMenu::createPrintout));
|
() -> ContainerData.toType(
|
||||||
|
HeldItemContainerData::new,
|
||||||
|
(id, inventory, data) -> new HeldItemMenu(Menus.PRINTOUT.get(), id, inventory.player, data.getHand())
|
||||||
|
));
|
||||||
|
|
||||||
public static final RegistryEntry<MenuType<ViewComputerMenu>> VIEW_COMPUTER = REGISTRY.register("view_computer",
|
public static final RegistryEntry<MenuType<ViewComputerMenu>> VIEW_COMPUTER = REGISTRY.register("view_computer",
|
||||||
() -> ContainerData.toType(ComputerContainerData::new, ViewComputerMenu::new));
|
() -> ContainerData.toType(ComputerContainerData::new, ViewComputerMenu::new));
|
||||||
@@ -333,8 +336,7 @@ public final class ModRegistry {
|
|||||||
|
|
||||||
static {
|
static {
|
||||||
register("tracking_field", TrackingFieldArgumentType.class, TrackingFieldArgumentType.metric());
|
register("tracking_field", TrackingFieldArgumentType.class, TrackingFieldArgumentType.metric());
|
||||||
register("computer", ComputerArgumentType.class, ComputerArgumentType.oneComputer());
|
register("computer", ComputerArgumentType.class, ComputerArgumentType.get());
|
||||||
register("computers", ComputersArgumentType.class, new ComputersArgumentType.Info());
|
|
||||||
registerUnsafe("repeat", RepeatArgumentType.class, new RepeatArgumentType.Info());
|
registerUnsafe("repeat", RepeatArgumentType.class, new RepeatArgumentType.Info());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -359,17 +361,21 @@ public final class ModRegistry {
|
|||||||
return REGISTRY.register(name, () -> new SimpleCraftingRecipeSerializer<>(factory));
|
return REGISTRY.register(name, () -> new SimpleCraftingRecipeSerializer<>(factory));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final RegistryEntry<RecipeSerializer<CustomShapedRecipe>> SHAPED = REGISTRY.register("shaped", () -> CustomShapedRecipe.serialiser(CustomShapedRecipe::new));
|
||||||
|
public static final RegistryEntry<RecipeSerializer<CustomShapelessRecipe>> SHAPELESS = REGISTRY.register("shapeless", () -> CustomShapelessRecipe.serialiser(CustomShapelessRecipe::new));
|
||||||
|
|
||||||
|
public static final RegistryEntry<RecipeSerializer<ImpostorShapedRecipe>> IMPOSTOR_SHAPED = REGISTRY.register("impostor_shaped", () -> CustomShapedRecipe.serialiser(ImpostorShapedRecipe::new));
|
||||||
|
public static final RegistryEntry<RecipeSerializer<ImpostorShapelessRecipe>> IMPOSTOR_SHAPELESS = REGISTRY.register("impostor_shapeless", () -> CustomShapelessRecipe.serialiser(ImpostorShapelessRecipe::new));
|
||||||
|
|
||||||
public static final RegistryEntry<SimpleCraftingRecipeSerializer<ColourableRecipe>> DYEABLE_ITEM = simple("colour", ColourableRecipe::new);
|
public static final RegistryEntry<SimpleCraftingRecipeSerializer<ColourableRecipe>> DYEABLE_ITEM = simple("colour", ColourableRecipe::new);
|
||||||
public static final RegistryEntry<SimpleCraftingRecipeSerializer<ClearColourRecipe>> DYEABLE_ITEM_CLEAR = simple("clear_colour", ClearColourRecipe::new);
|
public static final RegistryEntry<SimpleCraftingRecipeSerializer<ClearColourRecipe>> DYEABLE_ITEM_CLEAR = simple("clear_colour", ClearColourRecipe::new);
|
||||||
public static final RegistryEntry<TurtleRecipe.Serializer> TURTLE = REGISTRY.register("turtle", TurtleRecipe.Serializer::new);
|
public static final RegistryEntry<RecipeSerializer<TurtleRecipe>> TURTLE = REGISTRY.register("turtle", () -> TurtleRecipe.validatingSerialiser(TurtleRecipe::of));
|
||||||
public static final RegistryEntry<SimpleCraftingRecipeSerializer<TurtleUpgradeRecipe>> TURTLE_UPGRADE = simple("turtle_upgrade", TurtleUpgradeRecipe::new);
|
public static final RegistryEntry<SimpleCraftingRecipeSerializer<TurtleUpgradeRecipe>> TURTLE_UPGRADE = simple("turtle_upgrade", TurtleUpgradeRecipe::new);
|
||||||
public static final RegistryEntry<TurtleOverlayRecipe.Serializer> TURTLE_OVERLAY = REGISTRY.register("turtle_overlay", TurtleOverlayRecipe.Serializer::new);
|
public static final RegistryEntry<RecipeSerializer<TurtleOverlayRecipe>> TURTLE_OVERLAY = REGISTRY.register("turtle_overlay", TurtleOverlayRecipe.Serialiser::new);
|
||||||
public static final RegistryEntry<SimpleCraftingRecipeSerializer<PocketComputerUpgradeRecipe>> POCKET_COMPUTER_UPGRADE = simple("pocket_computer_upgrade", PocketComputerUpgradeRecipe::new);
|
public static final RegistryEntry<SimpleCraftingRecipeSerializer<PocketComputerUpgradeRecipe>> POCKET_COMPUTER_UPGRADE = simple("pocket_computer_upgrade", PocketComputerUpgradeRecipe::new);
|
||||||
public static final RegistryEntry<SimpleCraftingRecipeSerializer<PrintoutRecipe>> PRINTOUT = simple("printout", PrintoutRecipe::new);
|
public static final RegistryEntry<SimpleCraftingRecipeSerializer<PrintoutRecipe>> PRINTOUT = simple("printout", PrintoutRecipe::new);
|
||||||
public static final RegistryEntry<SimpleCraftingRecipeSerializer<DiskRecipe>> DISK = simple("disk", DiskRecipe::new);
|
public static final RegistryEntry<SimpleCraftingRecipeSerializer<DiskRecipe>> DISK = simple("disk", DiskRecipe::new);
|
||||||
public static final RegistryEntry<ComputerUpgradeRecipe.Serializer> COMPUTER_UPGRADE = REGISTRY.register("computer_upgrade", ComputerUpgradeRecipe.Serializer::new);
|
public static final RegistryEntry<RecipeSerializer<ComputerUpgradeRecipe>> COMPUTER_UPGRADE = REGISTRY.register("computer_upgrade", () -> CustomShapedRecipe.validatingSerialiser(ComputerUpgradeRecipe::of));
|
||||||
public static final RegistryEntry<ImpostorRecipe.Serializer> IMPOSTOR_SHAPED = REGISTRY.register("impostor_shaped", ImpostorRecipe.Serializer::new);
|
|
||||||
public static final RegistryEntry<ImpostorShapelessRecipe.Serializer> IMPOSTOR_SHAPELESS = REGISTRY.register("impostor_shapeless", ImpostorShapelessRecipe.Serializer::new);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Permissions {
|
public static class Permissions {
|
||||||
@@ -458,8 +464,8 @@ public final class ModRegistry {
|
|||||||
* Register any objects which must be done on the main thread.
|
* Register any objects which must be done on the main thread.
|
||||||
*/
|
*/
|
||||||
public static void registerMainThread() {
|
public static void registerMainThread() {
|
||||||
CauldronInteraction.WATER.put(ModRegistry.Items.TURTLE_NORMAL.get(), TurtleItem.CAULDRON_INTERACTION);
|
CauldronInteraction.WATER.put(Items.TURTLE_NORMAL.get(), TurtleItem.CAULDRON_INTERACTION);
|
||||||
CauldronInteraction.WATER.put(ModRegistry.Items.TURTLE_ADVANCED.get(), TurtleItem.CAULDRON_INTERACTION);
|
CauldronInteraction.WATER.put(Items.TURTLE_ADVANCED.get(), TurtleItem.CAULDRON_INTERACTION);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addTurtle(CreativeModeTab.Output out, TurtleItem turtle) {
|
private static void addTurtle(CreativeModeTab.Output out, TurtleItem turtle) {
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ import com.mojang.brigadier.suggestion.Suggestions;
|
|||||||
import dan200.computercraft.core.computer.ComputerSide;
|
import dan200.computercraft.core.computer.ComputerSide;
|
||||||
import dan200.computercraft.core.metrics.Metrics;
|
import dan200.computercraft.core.metrics.Metrics;
|
||||||
import dan200.computercraft.shared.ModRegistry;
|
import dan200.computercraft.shared.ModRegistry;
|
||||||
import dan200.computercraft.shared.command.arguments.ComputersArgumentType;
|
import dan200.computercraft.shared.command.arguments.ComputerArgumentType;
|
||||||
|
import dan200.computercraft.shared.command.arguments.ComputerSelector;
|
||||||
import dan200.computercraft.shared.command.text.TableBuilder;
|
import dan200.computercraft.shared.command.text.TableBuilder;
|
||||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||||
@@ -23,6 +24,7 @@ import dan200.computercraft.shared.computer.metrics.basic.AggregatedMetric;
|
|||||||
import dan200.computercraft.shared.computer.metrics.basic.BasicComputerMetricsObserver;
|
import dan200.computercraft.shared.computer.metrics.basic.BasicComputerMetricsObserver;
|
||||||
import dan200.computercraft.shared.computer.metrics.basic.ComputerMetrics;
|
import dan200.computercraft.shared.computer.metrics.basic.ComputerMetrics;
|
||||||
import dan200.computercraft.shared.network.container.ComputerContainerData;
|
import dan200.computercraft.shared.network.container.ComputerContainerData;
|
||||||
|
import net.minecraft.ChatFormatting;
|
||||||
import net.minecraft.commands.CommandSourceStack;
|
import net.minecraft.commands.CommandSourceStack;
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
@@ -42,9 +44,6 @@ import java.util.*;
|
|||||||
import static dan200.computercraft.shared.command.CommandUtils.isPlayer;
|
import static dan200.computercraft.shared.command.CommandUtils.isPlayer;
|
||||||
import static dan200.computercraft.shared.command.Exceptions.NOT_TRACKING_EXCEPTION;
|
import static dan200.computercraft.shared.command.Exceptions.NOT_TRACKING_EXCEPTION;
|
||||||
import static dan200.computercraft.shared.command.Exceptions.NO_TIMINGS_EXCEPTION;
|
import static dan200.computercraft.shared.command.Exceptions.NO_TIMINGS_EXCEPTION;
|
||||||
import static dan200.computercraft.shared.command.arguments.ComputerArgumentType.getComputerArgument;
|
|
||||||
import static dan200.computercraft.shared.command.arguments.ComputerArgumentType.oneComputer;
|
|
||||||
import static dan200.computercraft.shared.command.arguments.ComputersArgumentType.*;
|
|
||||||
import static dan200.computercraft.shared.command.arguments.TrackingFieldArgumentType.metric;
|
import static dan200.computercraft.shared.command.arguments.TrackingFieldArgumentType.metric;
|
||||||
import static dan200.computercraft.shared.command.builder.CommandBuilder.args;
|
import static dan200.computercraft.shared.command.builder.CommandBuilder.args;
|
||||||
import static dan200.computercraft.shared.command.builder.CommandBuilder.command;
|
import static dan200.computercraft.shared.command.builder.CommandBuilder.command;
|
||||||
@@ -54,7 +53,12 @@ import static net.minecraft.commands.Commands.literal;
|
|||||||
|
|
||||||
public final class CommandComputerCraft {
|
public final class CommandComputerCraft {
|
||||||
public static final UUID SYSTEM_UUID = new UUID(0, 0);
|
public static final UUID SYSTEM_UUID = new UUID(0, 0);
|
||||||
public static final String OPEN_COMPUTER = "computercraft open-computer ";
|
|
||||||
|
/**
|
||||||
|
* The client-side command to open the folder. Ideally this would live under the main {@code computercraft}
|
||||||
|
* namespace, but unfortunately that overrides commands, rather than merging them.
|
||||||
|
*/
|
||||||
|
public static final String CLIENT_OPEN_FOLDER = "computercraft-computer-folder";
|
||||||
|
|
||||||
private CommandComputerCraft() {
|
private CommandComputerCraft() {
|
||||||
}
|
}
|
||||||
@@ -65,37 +69,37 @@ public final class CommandComputerCraft {
|
|||||||
.requires(ModRegistry.Permissions.PERMISSION_DUMP)
|
.requires(ModRegistry.Permissions.PERMISSION_DUMP)
|
||||||
.executes(c -> dump(c.getSource()))
|
.executes(c -> dump(c.getSource()))
|
||||||
.then(args()
|
.then(args()
|
||||||
.arg("computer", oneComputer())
|
.arg("computer", ComputerArgumentType.get())
|
||||||
.executes(c -> dumpComputer(c.getSource(), getComputerArgument(c, "computer")))))
|
.executes(c -> dumpComputer(c.getSource(), ComputerArgumentType.getOne(c, "computer")))))
|
||||||
|
|
||||||
.then(command("shutdown")
|
.then(command("shutdown")
|
||||||
.requires(ModRegistry.Permissions.PERMISSION_SHUTDOWN)
|
.requires(ModRegistry.Permissions.PERMISSION_SHUTDOWN)
|
||||||
.argManyValue("computers", manyComputers(), s -> ServerContext.get(s.getServer()).registry().getComputers())
|
.argManyValue("computers", ComputerArgumentType.get(), ComputerSelector.all())
|
||||||
.executes((c, a) -> shutdown(c.getSource(), unwrap(c.getSource(), a))))
|
.executes((c, a) -> shutdown(c.getSource(), unwrap(c.getSource(), a))))
|
||||||
|
|
||||||
.then(command("turn-on")
|
.then(command("turn-on")
|
||||||
.requires(ModRegistry.Permissions.PERMISSION_TURN_ON)
|
.requires(ModRegistry.Permissions.PERMISSION_TURN_ON)
|
||||||
.argManyValue("computers", manyComputers(), s -> ServerContext.get(s.getServer()).registry().getComputers())
|
.argManyValue("computers", ComputerArgumentType.get(), ComputerSelector.all())
|
||||||
.executes((c, a) -> turnOn(c.getSource(), unwrap(c.getSource(), a))))
|
.executes((c, a) -> turnOn(c.getSource(), unwrap(c.getSource(), a))))
|
||||||
|
|
||||||
.then(command("tp")
|
.then(command("tp")
|
||||||
.requires(ModRegistry.Permissions.PERMISSION_TP)
|
.requires(ModRegistry.Permissions.PERMISSION_TP)
|
||||||
.arg("computer", oneComputer())
|
.arg("computer", ComputerArgumentType.get())
|
||||||
.executes(c -> teleport(c.getSource(), getComputerArgument(c, "computer"))))
|
.executes(c -> teleport(c.getSource(), ComputerArgumentType.getOne(c, "computer"))))
|
||||||
|
|
||||||
.then(command("queue")
|
.then(command("queue")
|
||||||
.requires(ModRegistry.Permissions.PERMISSION_QUEUE)
|
.requires(ModRegistry.Permissions.PERMISSION_QUEUE)
|
||||||
.arg(
|
.arg(
|
||||||
RequiredArgumentBuilder.<CommandSourceStack, ComputersArgumentType.ComputersSupplier>argument("computer", manyComputers())
|
RequiredArgumentBuilder.<CommandSourceStack, ComputerSelector>argument("computer", ComputerArgumentType.get())
|
||||||
.suggests((context, builder) -> Suggestions.empty())
|
.suggests((context, builder) -> Suggestions.empty())
|
||||||
)
|
)
|
||||||
.argManyValue("args", StringArgumentType.string(), Collections.emptyList())
|
.argManyValue("args", StringArgumentType.string(), List.of())
|
||||||
.executes((c, a) -> queue(getComputersArgument(c, "computer"), a)))
|
.executes((c, a) -> queue(ComputerArgumentType.getMany(c, "computer"), a)))
|
||||||
|
|
||||||
.then(command("view")
|
.then(command("view")
|
||||||
.requires(ModRegistry.Permissions.PERMISSION_VIEW)
|
.requires(ModRegistry.Permissions.PERMISSION_VIEW)
|
||||||
.arg("computer", oneComputer())
|
.arg("computer", ComputerArgumentType.get())
|
||||||
.executes(c -> view(c.getSource(), getComputerArgument(c, "computer"))))
|
.executes(c -> view(c.getSource(), ComputerArgumentType.getOne(c, "computer"))))
|
||||||
|
|
||||||
.then(choice("track")
|
.then(choice("track")
|
||||||
.requires(ModRegistry.Permissions.PERMISSION_TRACK)
|
.requires(ModRegistry.Permissions.PERMISSION_TRACK)
|
||||||
@@ -130,7 +134,7 @@ public final class CommandComputerCraft {
|
|||||||
} else if (b.getLevel() == world) {
|
} else if (b.getLevel() == world) {
|
||||||
return 1;
|
return 1;
|
||||||
} else {
|
} else {
|
||||||
return Integer.compare(a.getInstanceID(), b.getInstanceID());
|
return a.getInstanceUUID().compareTo(b.getInstanceUUID());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -155,7 +159,8 @@ public final class CommandComputerCraft {
|
|||||||
*/
|
*/
|
||||||
private static int dumpComputer(CommandSourceStack source, ServerComputer computer) {
|
private static int dumpComputer(CommandSourceStack source, ServerComputer computer) {
|
||||||
var table = new TableBuilder("Dump");
|
var table = new TableBuilder("Dump");
|
||||||
table.row(header("Instance"), text(Integer.toString(computer.getInstanceID())));
|
table.row(header("Instance ID"), text(Integer.toString(computer.getInstanceID())));
|
||||||
|
table.row(header("Instance UUID"), text(computer.getInstanceUUID().toString()));
|
||||||
table.row(header("Id"), text(Integer.toString(computer.getID())));
|
table.row(header("Id"), text(Integer.toString(computer.getID())));
|
||||||
table.row(header("Label"), text(computer.getLabel()));
|
table.row(header("Label"), text(computer.getLabel()));
|
||||||
table.row(header("On"), bool(computer.isOn()));
|
table.row(header("On"), bool(computer.isOn()));
|
||||||
@@ -300,7 +305,7 @@ public final class CommandComputerCraft {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final List<AggregatedMetric> DEFAULT_FIELDS = Arrays.asList(
|
private static final List<AggregatedMetric> DEFAULT_FIELDS = List.of(
|
||||||
new AggregatedMetric(Metrics.COMPUTER_TASKS, Aggregate.COUNT),
|
new AggregatedMetric(Metrics.COMPUTER_TASKS, Aggregate.COUNT),
|
||||||
new AggregatedMetric(Metrics.COMPUTER_TASKS, Aggregate.NONE),
|
new AggregatedMetric(Metrics.COMPUTER_TASKS, Aggregate.NONE),
|
||||||
new AggregatedMetric(Metrics.COMPUTER_TASKS, Aggregate.AVG)
|
new AggregatedMetric(Metrics.COMPUTER_TASKS, Aggregate.AVG)
|
||||||
@@ -327,29 +332,22 @@ public final class CommandComputerCraft {
|
|||||||
|
|
||||||
// Additional helper functions.
|
// Additional helper functions.
|
||||||
|
|
||||||
private static Component linkComputer(CommandSourceStack source, @Nullable ServerComputer serverComputer, int computerId) {
|
private static Component linkComputer(CommandSourceStack source, @Nullable ServerComputer computer, int computerId) {
|
||||||
var out = Component.literal("");
|
var out = Component.literal("");
|
||||||
|
|
||||||
// Append the computer instance
|
// And instance
|
||||||
if (serverComputer == null) {
|
if (computer == null) {
|
||||||
out.append(text("?"));
|
out.append("#" + computerId + " ").append(coloured("(unloaded)", ChatFormatting.GRAY));
|
||||||
} else {
|
} else {
|
||||||
out.append(link(
|
out.append(makeComputerDumpCommand(computer));
|
||||||
text(Integer.toString(serverComputer.getInstanceID())),
|
|
||||||
"/computercraft dump " + serverComputer.getInstanceID(),
|
|
||||||
Component.translatable("commands.computercraft.dump.action")
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// And ID
|
|
||||||
out.append(" (id " + computerId + ")");
|
|
||||||
|
|
||||||
// And, if we're a player, some useful links
|
// And, if we're a player, some useful links
|
||||||
if (serverComputer != null && isPlayer(source)) {
|
if (computer != null && isPlayer(source)) {
|
||||||
if (ModRegistry.Permissions.PERMISSION_TP.test(source)) {
|
if (ModRegistry.Permissions.PERMISSION_TP.test(source)) {
|
||||||
out.append(" ").append(link(
|
out.append(" ").append(link(
|
||||||
text("\u261b"),
|
text("\u261b"),
|
||||||
"/computercraft tp " + serverComputer.getInstanceID(),
|
makeComputerCommand("tp", computer),
|
||||||
Component.translatable("commands.computercraft.tp.action")
|
Component.translatable("commands.computercraft.tp.action")
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -357,7 +355,7 @@ public final class CommandComputerCraft {
|
|||||||
if (ModRegistry.Permissions.PERMISSION_VIEW.test(source)) {
|
if (ModRegistry.Permissions.PERMISSION_VIEW.test(source)) {
|
||||||
out.append(" ").append(link(
|
out.append(" ").append(link(
|
||||||
text("\u20e2"),
|
text("\u20e2"),
|
||||||
"/computercraft view " + serverComputer.getInstanceID(),
|
makeComputerCommand("view", computer),
|
||||||
Component.translatable("commands.computercraft.view.action")
|
Component.translatable("commands.computercraft.view.action")
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -375,7 +373,7 @@ public final class CommandComputerCraft {
|
|||||||
if (ModRegistry.Permissions.PERMISSION_TP.test(context)) {
|
if (ModRegistry.Permissions.PERMISSION_TP.test(context)) {
|
||||||
return link(
|
return link(
|
||||||
position(computer.getPosition()),
|
position(computer.getPosition()),
|
||||||
"/computercraft tp " + computer.getInstanceID(),
|
makeComputerCommand("tp", computer),
|
||||||
Component.translatable("commands.computercraft.tp.action")
|
Component.translatable("commands.computercraft.tp.action")
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -387,9 +385,9 @@ public final class CommandComputerCraft {
|
|||||||
var file = new File(ServerContext.get(source.getServer()).storageDir().toFile(), "computer/" + id);
|
var file = new File(ServerContext.get(source.getServer()).storageDir().toFile(), "computer/" + id);
|
||||||
if (!file.isDirectory()) return null;
|
if (!file.isDirectory()) return null;
|
||||||
|
|
||||||
return link(
|
return clientLink(
|
||||||
text("\u270E"),
|
text("\u270E"),
|
||||||
"/" + OPEN_COMPUTER + id,
|
"/" + CLIENT_OPEN_FOLDER + " " + id,
|
||||||
Component.translatable("commands.computercraft.dump.open_path")
|
Component.translatable("commands.computercraft.dump.open_path")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -426,4 +424,10 @@ public final class CommandComputerCraft {
|
|||||||
table.display(source);
|
table.display(source);
|
||||||
return timings.size();
|
return timings.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Set<ServerComputer> unwrap(CommandSourceStack source, Collection<ComputerSelector> suppliers) {
|
||||||
|
Set<ServerComputer> computers = new HashSet<>();
|
||||||
|
for (var supplier : suppliers) supplier.find(source).forEach(computers::add);
|
||||||
|
return computers;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,12 +28,12 @@ public final class CommandUtils {
|
|||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public static CompletableFuture<Suggestions> suggestOnServer(CommandContext<?> context, Function<CommandContext<CommandSourceStack>, CompletableFuture<Suggestions>> supplier) {
|
public static CompletableFuture<Suggestions> suggestOnServer(CommandContext<?> context, Function<CommandContext<CommandSourceStack>, CompletableFuture<Suggestions>> supplier) {
|
||||||
var source = context.getSource();
|
var source = context.getSource();
|
||||||
if (!(source instanceof SharedSuggestionProvider)) {
|
if (!(source instanceof SharedSuggestionProvider shared)) {
|
||||||
return Suggestions.empty();
|
return Suggestions.empty();
|
||||||
} else if (source instanceof CommandSourceStack) {
|
} else if (source instanceof CommandSourceStack) {
|
||||||
return supplier.apply((CommandContext<CommandSourceStack>) context);
|
return supplier.apply((CommandContext<CommandSourceStack>) context);
|
||||||
} else {
|
} else {
|
||||||
return ((SharedSuggestionProvider) source).customSuggestion(context);
|
return shared.customSuggestion(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ package dan200.computercraft.shared.command;
|
|||||||
import com.mojang.brigadier.exceptions.Dynamic2CommandExceptionType;
|
import com.mojang.brigadier.exceptions.Dynamic2CommandExceptionType;
|
||||||
import com.mojang.brigadier.exceptions.DynamicCommandExceptionType;
|
import com.mojang.brigadier.exceptions.DynamicCommandExceptionType;
|
||||||
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
|
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
|
||||||
|
import net.minecraft.commands.arguments.selector.EntitySelectorParser;
|
||||||
|
import net.minecraft.commands.arguments.selector.options.EntitySelectorOptions;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
|
|
||||||
public final class Exceptions {
|
public final class Exceptions {
|
||||||
@@ -20,6 +22,13 @@ public final class Exceptions {
|
|||||||
|
|
||||||
public static final SimpleCommandExceptionType ARGUMENT_EXPECTED = translated("argument.computercraft.argument_expected");
|
public static final SimpleCommandExceptionType ARGUMENT_EXPECTED = translated("argument.computercraft.argument_expected");
|
||||||
|
|
||||||
|
public static final DynamicCommandExceptionType UNKNOWN_FAMILY = translated1("argument.computercraft.unknown_computer_family");
|
||||||
|
|
||||||
|
public static final DynamicCommandExceptionType ERROR_EXPECTED_OPTION_VALUE = EntitySelectorParser.ERROR_EXPECTED_OPTION_VALUE;
|
||||||
|
public static final SimpleCommandExceptionType ERROR_EXPECTED_END_OF_OPTIONS = EntitySelectorParser.ERROR_EXPECTED_END_OF_OPTIONS;
|
||||||
|
public static final DynamicCommandExceptionType ERROR_UNKNOWN_OPTION = EntitySelectorOptions.ERROR_UNKNOWN_OPTION;
|
||||||
|
public static final DynamicCommandExceptionType ERROR_INAPPLICABLE_OPTION = EntitySelectorOptions.ERROR_INAPPLICABLE_OPTION;
|
||||||
|
|
||||||
private static SimpleCommandExceptionType translated(String key) {
|
private static SimpleCommandExceptionType translated(String key) {
|
||||||
return new SimpleCommandExceptionType(Component.translatable(key));
|
return new SimpleCommandExceptionType(Component.translatable(key));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.shared.command.arguments;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.StringReader;
|
||||||
|
|
||||||
|
final class ArgumentParserUtils {
|
||||||
|
private ArgumentParserUtils() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean consume(StringReader reader, char lookahead) {
|
||||||
|
if (!reader.canRead() || reader.peek() != lookahead) return false;
|
||||||
|
|
||||||
|
reader.skip();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean consume(StringReader reader, String lookahead) {
|
||||||
|
if (!reader.canRead(lookahead.length())) return false;
|
||||||
|
for (var i = 0; i < lookahead.length(); i++) {
|
||||||
|
if (reader.peek(i) != lookahead.charAt(i)) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.setCursor(reader.getCursor() + lookahead.length());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,66 +14,58 @@ import dan200.computercraft.shared.computer.core.ServerComputer;
|
|||||||
import net.minecraft.commands.CommandSourceStack;
|
import net.minecraft.commands.CommandSourceStack;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
import static dan200.computercraft.shared.command.Exceptions.COMPUTER_ARG_MANY;
|
public final class ComputerArgumentType implements ArgumentType<ComputerSelector> {
|
||||||
|
|
||||||
public final class ComputerArgumentType implements ArgumentType<ComputerArgumentType.ComputerSupplier> {
|
|
||||||
private static final ComputerArgumentType INSTANCE = new ComputerArgumentType();
|
private static final ComputerArgumentType INSTANCE = new ComputerArgumentType();
|
||||||
|
|
||||||
public static ComputerArgumentType oneComputer() {
|
private static final List<String> EXAMPLES = List.of(
|
||||||
return INSTANCE;
|
"0", "123", "@c[instance_id=123]"
|
||||||
}
|
);
|
||||||
|
|
||||||
public static ServerComputer getComputerArgument(CommandContext<CommandSourceStack> context, String name) throws CommandSyntaxException {
|
public static ComputerArgumentType get() {
|
||||||
return context.getArgument(name, ComputerSupplier.class).unwrap(context.getSource());
|
return INSTANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ComputerArgumentType() {
|
private ComputerArgumentType() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract a list of computers from a {@link CommandContext} argument.
|
||||||
|
*
|
||||||
|
* @param context The current command context.
|
||||||
|
* @param name The name of the argument.
|
||||||
|
* @return The found computer(s).
|
||||||
|
*/
|
||||||
|
public static List<ServerComputer> getMany(CommandContext<CommandSourceStack> context, String name) {
|
||||||
|
return context.getArgument(name, ComputerSelector.class).find(context.getSource()).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract a single computer from a {@link CommandContext} argument.
|
||||||
|
*
|
||||||
|
* @param context The current command context.
|
||||||
|
* @param name The name of the argument.
|
||||||
|
* @return The found computer.
|
||||||
|
* @throws CommandSyntaxException If exactly one computer could not be found.
|
||||||
|
*/
|
||||||
|
public static ServerComputer getOne(CommandContext<CommandSourceStack> context, String name) throws CommandSyntaxException {
|
||||||
|
return context.getArgument(name, ComputerSelector.class).findOne(context.getSource());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ComputerSupplier parse(StringReader reader) throws CommandSyntaxException {
|
public ComputerSelector parse(StringReader reader) throws CommandSyntaxException {
|
||||||
var start = reader.getCursor();
|
return ComputerSelector.parse(reader);
|
||||||
var supplier = ComputersArgumentType.someComputers().parse(reader);
|
|
||||||
var selector = reader.getString().substring(start, reader.getCursor());
|
|
||||||
|
|
||||||
return s -> {
|
|
||||||
var computers = supplier.unwrap(s);
|
|
||||||
|
|
||||||
if (computers.size() == 1) return computers.iterator().next();
|
|
||||||
|
|
||||||
var builder = new StringBuilder();
|
|
||||||
var first = true;
|
|
||||||
for (var computer : computers) {
|
|
||||||
if (first) {
|
|
||||||
first = false;
|
|
||||||
} else {
|
|
||||||
builder.append(", ");
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.append(computer.getInstanceID());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// We have an incorrect number of computers: reset and throw an error
|
|
||||||
reader.setCursor(start);
|
|
||||||
throw COMPUTER_ARG_MANY.createWithContext(reader, selector, builder.toString());
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <S> CompletableFuture<Suggestions> listSuggestions(CommandContext<S> context, SuggestionsBuilder builder) {
|
public <S> CompletableFuture<Suggestions> listSuggestions(CommandContext<S> context, SuggestionsBuilder builder) {
|
||||||
return ComputersArgumentType.someComputers().listSuggestions(context, builder);
|
return ComputerSelector.suggest(context, builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<String> getExamples() {
|
public Collection<String> getExamples() {
|
||||||
return ComputersArgumentType.someComputers().getExamples();
|
return EXAMPLES;
|
||||||
}
|
|
||||||
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface ComputerSupplier {
|
|
||||||
ServerComputer unwrap(CommandSourceStack source) throws CommandSyntaxException;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,389 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.shared.command.arguments;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.Message;
|
||||||
|
import com.mojang.brigadier.StringReader;
|
||||||
|
import com.mojang.brigadier.context.CommandContext;
|
||||||
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||||
|
import com.mojang.brigadier.suggestion.Suggestions;
|
||||||
|
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
||||||
|
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||||
|
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||||
|
import dan200.computercraft.shared.computer.core.ServerContext;
|
||||||
|
import net.minecraft.advancements.critereon.MinMaxBounds;
|
||||||
|
import net.minecraft.commands.CommandSourceStack;
|
||||||
|
import net.minecraft.commands.SharedSuggestionProvider;
|
||||||
|
import net.minecraft.commands.arguments.UuidArgument;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.network.chat.ComponentContents;
|
||||||
|
import net.minecraft.network.chat.MutableComponent;
|
||||||
|
import net.minecraft.world.phys.AABB;
|
||||||
|
import net.minecraft.world.phys.Vec3;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static dan200.computercraft.shared.command.CommandUtils.suggestOnServer;
|
||||||
|
import static dan200.computercraft.shared.command.Exceptions.*;
|
||||||
|
import static dan200.computercraft.shared.command.arguments.ArgumentParserUtils.consume;
|
||||||
|
import static dan200.computercraft.shared.command.text.ChatHelpers.makeComputerDumpCommand;
|
||||||
|
|
||||||
|
public record ComputerSelector(
|
||||||
|
String selector,
|
||||||
|
OptionalInt instanceId,
|
||||||
|
@Nullable UUID instanceUuid,
|
||||||
|
OptionalInt computerId,
|
||||||
|
@Nullable String label,
|
||||||
|
@Nullable ComputerFamily family,
|
||||||
|
@Nullable AABB bounds,
|
||||||
|
@Nullable MinMaxBounds.Doubles range
|
||||||
|
) {
|
||||||
|
private static final ComputerSelector all = new ComputerSelector("@c[]", OptionalInt.empty(), null, OptionalInt.empty(), null, null, null, null);
|
||||||
|
|
||||||
|
private static UuidArgument uuidArgument = UuidArgument.uuid();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link ComputerSelector} which matches all computers.
|
||||||
|
*
|
||||||
|
* @return A {@link ComputerSelector} instance.
|
||||||
|
*/
|
||||||
|
public static ComputerSelector all() {
|
||||||
|
return all;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find all computers matching this selector.
|
||||||
|
*
|
||||||
|
* @param source The source requesting these computers.
|
||||||
|
* @return The stream of matching computers.
|
||||||
|
*/
|
||||||
|
public Stream<ServerComputer> find(CommandSourceStack source) {
|
||||||
|
var context = ServerContext.get(source.getServer());
|
||||||
|
if (instanceId().isPresent()) {
|
||||||
|
var computer = context.registry().get(instanceId().getAsInt());
|
||||||
|
return computer != null && matches(source, computer) ? Stream.of(computer) : Stream.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (instanceUuid() != null) {
|
||||||
|
var computer = context.registry().get(instanceUuid());
|
||||||
|
return computer != null && matches(source, computer) ? Stream.of(computer) : Stream.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
return context.registry().getComputers().stream().filter(c -> matches(source, c));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find exactly one computer which matches this selector.
|
||||||
|
*
|
||||||
|
* @param source The source requesting this computer.
|
||||||
|
* @return The computer.
|
||||||
|
* @throws CommandSyntaxException If no or multiple computers could be found.
|
||||||
|
*/
|
||||||
|
public ServerComputer findOne(CommandSourceStack source) throws CommandSyntaxException {
|
||||||
|
var computers = find(source).toList();
|
||||||
|
if (computers.isEmpty()) throw COMPUTER_ARG_NONE.create(selector);
|
||||||
|
if (computers.size() == 1) return computers.iterator().next();
|
||||||
|
|
||||||
|
var builder = MutableComponent.create(ComponentContents.EMPTY);
|
||||||
|
var first = true;
|
||||||
|
for (var computer : computers) {
|
||||||
|
if (first) {
|
||||||
|
first = false;
|
||||||
|
} else {
|
||||||
|
builder.append(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.append(makeComputerDumpCommand(computer));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// We have an incorrect number of computers: throw an error
|
||||||
|
throw COMPUTER_ARG_MANY.create(selector, builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if this selector matches a given computer.
|
||||||
|
*
|
||||||
|
* @param source The command source, used for distance comparisons.
|
||||||
|
* @param computer The computer to check.
|
||||||
|
* @return If this computer is matched by the selector.
|
||||||
|
*/
|
||||||
|
public boolean matches(CommandSourceStack source, ServerComputer computer) {
|
||||||
|
return (instanceId().isEmpty() || computer.getInstanceID() == instanceId().getAsInt())
|
||||||
|
&& (instanceUuid() == null || computer.getInstanceUUID().equals(instanceUuid()))
|
||||||
|
&& (computerId().isEmpty() || computer.getID() == computerId().getAsInt())
|
||||||
|
&& (label == null || Objects.equals(computer.getLabel(), label))
|
||||||
|
&& (family == null || computer.getFamily() == family)
|
||||||
|
&& (bounds == null || (source.getLevel() == computer.getLevel() && bounds.contains(Vec3.atCenterOf(computer.getPosition()))))
|
||||||
|
&& (range == null || (source.getLevel() == computer.getLevel() && range.matchesSqr(source.getPosition().distanceToSqr(Vec3.atCenterOf(computer.getPosition())))));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse an input string.
|
||||||
|
*
|
||||||
|
* @param reader The reader to parse from.
|
||||||
|
* @return The parsed selector.
|
||||||
|
* @throws CommandSyntaxException If the selector was incomplete or malformed.
|
||||||
|
*/
|
||||||
|
public static ComputerSelector parse(StringReader reader) throws CommandSyntaxException {
|
||||||
|
var start = reader.getCursor();
|
||||||
|
|
||||||
|
var builder = new Builder();
|
||||||
|
|
||||||
|
if (consume(reader, "@c[")) {
|
||||||
|
parseSelector(builder, reader);
|
||||||
|
} else {
|
||||||
|
// TODO(1.20.5): Only parse computer ids here.
|
||||||
|
var kind = reader.peek();
|
||||||
|
if (kind == '@') {
|
||||||
|
reader.skip();
|
||||||
|
builder.label = reader.readString();
|
||||||
|
} else if (kind == '~') {
|
||||||
|
reader.skip();
|
||||||
|
builder.family = parseFamily(reader);
|
||||||
|
} else if (kind == '#') {
|
||||||
|
reader.skip();
|
||||||
|
builder.computerId = OptionalInt.of(reader.readInt());
|
||||||
|
} else {
|
||||||
|
builder.instanceId = OptionalInt.of(reader.readInt());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var selector = reader.getString().substring(start, reader.getCursor());
|
||||||
|
return new ComputerSelector(selector, builder.instanceId, builder.instanceUuid, builder.computerId, builder.label, builder.family, builder.bounds, builder.range);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void parseSelector(Builder builder, StringReader reader) throws CommandSyntaxException {
|
||||||
|
Set<Option> seenOptions = new HashSet<>();
|
||||||
|
while (true) {
|
||||||
|
reader.skipWhitespace();
|
||||||
|
|
||||||
|
if (!reader.canRead()) throw ERROR_EXPECTED_END_OF_OPTIONS.createWithContext(reader);
|
||||||
|
if (consume(reader, ']')) break;
|
||||||
|
|
||||||
|
// Read the option and validate it.
|
||||||
|
var option = parseOption(reader, seenOptions);
|
||||||
|
reader.skipWhitespace();
|
||||||
|
if (!consume(reader, '=')) throw ERROR_EXPECTED_OPTION_VALUE.createWithContext(reader, option.name());
|
||||||
|
reader.skipWhitespace();
|
||||||
|
option.parser.parse(reader, builder);
|
||||||
|
reader.skipWhitespace();
|
||||||
|
|
||||||
|
if (consume(reader, ']')) break;
|
||||||
|
if (!consume(reader, ',')) throw ERROR_EXPECTED_END_OF_OPTIONS.createWithContext(reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Option parseOption(StringReader reader, Set<Option> seen) throws CommandSyntaxException {
|
||||||
|
var start = reader.getCursor();
|
||||||
|
var name = reader.readUnquotedString();
|
||||||
|
var option = options.get(name);
|
||||||
|
if (option == null) {
|
||||||
|
reader.setCursor(start);
|
||||||
|
throw ERROR_UNKNOWN_OPTION.createWithContext(reader, name);
|
||||||
|
} else if (!seen.add(option)) {
|
||||||
|
throw ERROR_INAPPLICABLE_OPTION.createWithContext(reader, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return option;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ComputerFamily parseFamily(StringReader reader) throws CommandSyntaxException {
|
||||||
|
var start = reader.getCursor();
|
||||||
|
var name = reader.readUnquotedString();
|
||||||
|
var family = Arrays.stream(ComputerFamily.values()).filter(x -> x.name().equalsIgnoreCase(name)).findFirst().orElse(null);
|
||||||
|
if (family == null) {
|
||||||
|
reader.setCursor(start);
|
||||||
|
throw UNKNOWN_FAMILY.createWithContext(reader, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return family;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suggest completions for a selector argument.
|
||||||
|
*
|
||||||
|
* @param context The current command context.
|
||||||
|
* @param builder The builder containing the current input.
|
||||||
|
* @return The possible suggestions.
|
||||||
|
*/
|
||||||
|
public static CompletableFuture<Suggestions> suggest(CommandContext<?> context, SuggestionsBuilder builder) {
|
||||||
|
var remaining = builder.getRemaining();
|
||||||
|
|
||||||
|
if (remaining.startsWith("@")) {
|
||||||
|
var reader = new StringReader(builder.getInput());
|
||||||
|
reader.setCursor(builder.getStart());
|
||||||
|
return suggestSelector(context, reader);
|
||||||
|
} else if (remaining.startsWith("#")) {
|
||||||
|
return suggestComputers(c -> "#" + c.getID()).suggest(context, builder);
|
||||||
|
} else {
|
||||||
|
return suggestComputers(c -> Integer.toString(c.getInstanceID())).suggest(context, builder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CompletableFuture<Suggestions> suggestSelector(CommandContext<?> context, StringReader reader) {
|
||||||
|
Set<Option> seenOptions = new HashSet<>();
|
||||||
|
var builder = new Builder();
|
||||||
|
|
||||||
|
if (!consume(reader, "@c[")) return suggestions(reader).suggest("@c[").buildFuture();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
reader.skipWhitespace();
|
||||||
|
|
||||||
|
if (!reader.canRead()) return suggestOptions(reader);
|
||||||
|
if (consume(reader, ']')) break;
|
||||||
|
|
||||||
|
// Read the option and validate it.
|
||||||
|
Option option;
|
||||||
|
try {
|
||||||
|
option = parseOption(reader, seenOptions);
|
||||||
|
} catch (CommandSyntaxException e) {
|
||||||
|
return suggestOptions(reader);
|
||||||
|
}
|
||||||
|
reader.skipWhitespace();
|
||||||
|
if (!consume(reader, '=')) return suggestions(reader).suggest("=").buildFuture();
|
||||||
|
reader.skipWhitespace();
|
||||||
|
try {
|
||||||
|
option.parser.parse(reader, builder);
|
||||||
|
} catch (CommandSyntaxException e) {
|
||||||
|
return option.suggest.suggest(context, suggestions(reader));
|
||||||
|
}
|
||||||
|
reader.skipWhitespace();
|
||||||
|
|
||||||
|
if (consume(reader, ']')) break;
|
||||||
|
if (!consume(reader, ',')) return suggestions(reader).suggest(",").buildFuture();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Suggestions.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CompletableFuture<Suggestions> suggestOptions(StringReader reader) {
|
||||||
|
return SharedSuggestionProvider.suggest(options().values(), suggestions(reader), Option::name, Option::tooltip);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SuggestionsBuilder suggestions(StringReader reader) {
|
||||||
|
return new SuggestionsBuilder(reader.getString(), reader.getCursor());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class Builder {
|
||||||
|
private OptionalInt instanceId = OptionalInt.empty();
|
||||||
|
private @Nullable UUID instanceUuid = null;
|
||||||
|
private OptionalInt computerId = OptionalInt.empty();
|
||||||
|
private @Nullable String label;
|
||||||
|
private @Nullable ComputerFamily family;
|
||||||
|
private @Nullable AABB bounds;
|
||||||
|
private @Nullable MinMaxBounds.Doubles range;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Map<String, Option> options;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a map of individual selector options.
|
||||||
|
*
|
||||||
|
* @return The available options.
|
||||||
|
*/
|
||||||
|
public static Map<String, Option> options() {
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
var optionList = new Option[]{
|
||||||
|
new Option(
|
||||||
|
"instance",
|
||||||
|
(reader, builder) -> builder.instanceUuid = uuidArgument.parse(reader),
|
||||||
|
suggestComputers(c -> c.getInstanceUUID().toString())
|
||||||
|
),
|
||||||
|
new Option(
|
||||||
|
"id",
|
||||||
|
(reader, builder) -> builder.computerId = OptionalInt.of(reader.readInt()),
|
||||||
|
suggestComputers(c -> Integer.toString(c.getID()))
|
||||||
|
),
|
||||||
|
new Option(
|
||||||
|
"label",
|
||||||
|
(reader, builder) -> builder.label = reader.readQuotedString(),
|
||||||
|
suggestComputers(ServerComputer::getLabel)
|
||||||
|
),
|
||||||
|
new Option(
|
||||||
|
"family",
|
||||||
|
(reader, builder) -> builder.family = parseFamily(reader),
|
||||||
|
(source, builder) -> SharedSuggestionProvider.suggest(Arrays.stream(ComputerFamily.values()).map(x -> x.name().toLowerCase(Locale.ROOT)), builder)
|
||||||
|
),
|
||||||
|
new Option(
|
||||||
|
"distance",
|
||||||
|
(reader, builder) -> builder.range = MinMaxBounds.Doubles.fromReader(reader),
|
||||||
|
(source, builder) -> Suggestions.empty()
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
options = Arrays.stream(optionList).collect(Collectors.toUnmodifiableMap(Option::name, x -> x));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A single option to filter a computer by.
|
||||||
|
*/
|
||||||
|
public static final class Option {
|
||||||
|
private final String name;
|
||||||
|
private final Parser parser;
|
||||||
|
private final SuggestionProvider suggest;
|
||||||
|
private final String translationKey;
|
||||||
|
private final Message tooltip;
|
||||||
|
|
||||||
|
|
||||||
|
Option(String name, Parser parser, SuggestionProvider suggest) {
|
||||||
|
this.name = name;
|
||||||
|
this.parser = parser;
|
||||||
|
this.suggest = suggest;
|
||||||
|
tooltip = Component.translatable(translationKey = "argument.computercraft.computer." + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of this selector.
|
||||||
|
*
|
||||||
|
* @return The selector's name.
|
||||||
|
*/
|
||||||
|
public String name() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Message tooltip() {
|
||||||
|
return tooltip;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The translation key for this selector.
|
||||||
|
*
|
||||||
|
* @return The selector's translation key.
|
||||||
|
*/
|
||||||
|
public String translationKey() {
|
||||||
|
return translationKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface Parser {
|
||||||
|
void parse(StringReader reader, Builder builder) throws CommandSyntaxException;
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface SuggestionProvider {
|
||||||
|
CompletableFuture<Suggestions> suggest(CommandContext<?> source, SuggestionsBuilder builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SuggestionProvider suggestComputers(Function<ServerComputer, String> renderer) {
|
||||||
|
return (anyContext, builder) -> suggestOnServer(anyContext, context -> {
|
||||||
|
var remaining = builder.getRemaining();
|
||||||
|
for (var computer : ServerContext.get(context.getSource().getServer()).registry().getComputers()) {
|
||||||
|
var converted = renderer.apply(computer);
|
||||||
|
if (converted != null && converted.startsWith(remaining)) {
|
||||||
|
builder.suggest(converted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder.buildFuture();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,188 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2019 The CC: Tweaked Developers
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
package dan200.computercraft.shared.command.arguments;
|
|
||||||
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import com.mojang.brigadier.StringReader;
|
|
||||||
import com.mojang.brigadier.arguments.ArgumentType;
|
|
||||||
import com.mojang.brigadier.context.CommandContext;
|
|
||||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
|
||||||
import com.mojang.brigadier.suggestion.Suggestions;
|
|
||||||
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
|
||||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
|
||||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
|
||||||
import dan200.computercraft.shared.computer.core.ServerContext;
|
|
||||||
import net.minecraft.commands.CommandBuildContext;
|
|
||||||
import net.minecraft.commands.CommandSourceStack;
|
|
||||||
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
|
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
|
|
||||||
import static dan200.computercraft.shared.command.CommandUtils.suggest;
|
|
||||||
import static dan200.computercraft.shared.command.CommandUtils.suggestOnServer;
|
|
||||||
import static dan200.computercraft.shared.command.Exceptions.COMPUTER_ARG_NONE;
|
|
||||||
|
|
||||||
public final class ComputersArgumentType implements ArgumentType<ComputersArgumentType.ComputersSupplier> {
|
|
||||||
private static final ComputersArgumentType MANY = new ComputersArgumentType(false);
|
|
||||||
private static final ComputersArgumentType SOME = new ComputersArgumentType(true);
|
|
||||||
|
|
||||||
private static final List<String> EXAMPLES = Arrays.asList(
|
|
||||||
"0", "#0", "@Label", "~Advanced"
|
|
||||||
);
|
|
||||||
|
|
||||||
public static ComputersArgumentType manyComputers() {
|
|
||||||
return MANY;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ComputersArgumentType someComputers() {
|
|
||||||
return SOME;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Collection<ServerComputer> getComputersArgument(CommandContext<CommandSourceStack> context, String name) throws CommandSyntaxException {
|
|
||||||
return context.getArgument(name, ComputersSupplier.class).unwrap(context.getSource());
|
|
||||||
}
|
|
||||||
|
|
||||||
private final boolean requireSome;
|
|
||||||
|
|
||||||
private ComputersArgumentType(boolean requireSome) {
|
|
||||||
this.requireSome = requireSome;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ComputersSupplier parse(StringReader reader) throws CommandSyntaxException {
|
|
||||||
var start = reader.getCursor();
|
|
||||||
var kind = reader.peek();
|
|
||||||
ComputersSupplier computers;
|
|
||||||
if (kind == '@') {
|
|
||||||
reader.skip();
|
|
||||||
var label = reader.readUnquotedString();
|
|
||||||
computers = getComputers(x -> Objects.equals(label, x.getLabel()));
|
|
||||||
} else if (kind == '~') {
|
|
||||||
reader.skip();
|
|
||||||
var family = reader.readUnquotedString();
|
|
||||||
computers = getComputers(x -> x.getFamily().name().equalsIgnoreCase(family));
|
|
||||||
} else if (kind == '#') {
|
|
||||||
reader.skip();
|
|
||||||
var id = reader.readInt();
|
|
||||||
computers = getComputers(x -> x.getID() == id);
|
|
||||||
} else {
|
|
||||||
var instance = reader.readInt();
|
|
||||||
computers = s -> {
|
|
||||||
var computer = ServerContext.get(s.getServer()).registry().get(instance);
|
|
||||||
return computer == null ? Collections.emptyList() : Collections.singletonList(computer);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (requireSome) {
|
|
||||||
var selector = reader.getString().substring(start, reader.getCursor());
|
|
||||||
return source -> {
|
|
||||||
var matched = computers.unwrap(source);
|
|
||||||
if (matched.isEmpty()) throw COMPUTER_ARG_NONE.create(selector);
|
|
||||||
return matched;
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return computers;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <S> CompletableFuture<Suggestions> listSuggestions(CommandContext<S> context, SuggestionsBuilder builder) {
|
|
||||||
var remaining = builder.getRemaining();
|
|
||||||
|
|
||||||
// We can run this one on the client, for obvious reasons.
|
|
||||||
if (remaining.startsWith("~")) {
|
|
||||||
return suggest(builder, ComputerFamily.values(), x -> "~" + x.name());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify we've a command source and we're running on the server
|
|
||||||
return suggestOnServer(context, s -> {
|
|
||||||
if (remaining.startsWith("@")) {
|
|
||||||
suggestComputers(s.getSource(), builder, remaining, x -> {
|
|
||||||
var label = x.getLabel();
|
|
||||||
return label == null ? null : "@" + label;
|
|
||||||
});
|
|
||||||
} else if (remaining.startsWith("#")) {
|
|
||||||
suggestComputers(s.getSource(), builder, remaining, c -> "#" + c.getID());
|
|
||||||
} else {
|
|
||||||
suggestComputers(s.getSource(), builder, remaining, c -> Integer.toString(c.getInstanceID()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.buildFuture();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<String> getExamples() {
|
|
||||||
return EXAMPLES;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void suggestComputers(CommandSourceStack source, SuggestionsBuilder builder, String remaining, Function<ServerComputer, String> renderer) {
|
|
||||||
remaining = remaining.toLowerCase(Locale.ROOT);
|
|
||||||
for (var computer : ServerContext.get(source.getServer()).registry().getComputers()) {
|
|
||||||
var converted = renderer.apply(computer);
|
|
||||||
if (converted != null && converted.toLowerCase(Locale.ROOT).startsWith(remaining)) {
|
|
||||||
builder.suggest(converted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ComputersSupplier getComputers(Predicate<ServerComputer> predicate) {
|
|
||||||
return s -> ServerContext.get(s.getServer()).registry()
|
|
||||||
.getComputers()
|
|
||||||
.stream()
|
|
||||||
.filter(predicate)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Info implements ArgumentTypeInfo<ComputersArgumentType, Template> {
|
|
||||||
@Override
|
|
||||||
public void serializeToNetwork(ComputersArgumentType.Template arg, FriendlyByteBuf buf) {
|
|
||||||
buf.writeBoolean(arg.requireSome());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ComputersArgumentType.Template deserializeFromNetwork(FriendlyByteBuf buf) {
|
|
||||||
var requiresSome = buf.readBoolean();
|
|
||||||
return new ComputersArgumentType.Template(this, requiresSome);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void serializeToJson(ComputersArgumentType.Template arg, JsonObject json) {
|
|
||||||
json.addProperty("requireSome", arg.requireSome);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ComputersArgumentType.Template unpack(ComputersArgumentType argumentType) {
|
|
||||||
return new ComputersArgumentType.Template(this, argumentType.requireSome);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public record Template(Info info, boolean requireSome) implements ArgumentTypeInfo.Template<ComputersArgumentType> {
|
|
||||||
@Override
|
|
||||||
public ComputersArgumentType instantiate(CommandBuildContext context) {
|
|
||||||
return requireSome ? SOME : MANY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Info type() {
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface ComputersSupplier {
|
|
||||||
Collection<ServerComputer> unwrap(CommandSourceStack source) throws CommandSyntaxException;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Set<ServerComputer> unwrap(CommandSourceStack source, Collection<ComputersSupplier> suppliers) throws CommandSyntaxException {
|
|
||||||
Set<ServerComputer> computers = new HashSet<>();
|
|
||||||
for (var supplier : suppliers) computers.addAll(supplier.unwrap(source));
|
|
||||||
return computers;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -15,7 +15,6 @@ import net.minecraft.commands.CommandSourceStack;
|
|||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
@@ -63,7 +62,7 @@ public class CommandBuilder<S> implements CommandNodeBuilder<S, Command<S>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public <T> CommandNodeBuilder<S, ArgCommand<S, List<T>>> argManyValue(String name, ArgumentType<T> type, T defaultValue) {
|
public <T> CommandNodeBuilder<S, ArgCommand<S, List<T>>> argManyValue(String name, ArgumentType<T> type, T defaultValue) {
|
||||||
return argManyValue(name, type, Collections.singletonList(defaultValue));
|
return argManyValue(name, type, List.of(defaultValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> CommandNodeBuilder<S, ArgCommand<S, List<T>>> argMany(String name, ArgumentType<T> type, Supplier<List<T>> empty) {
|
public <T> CommandNodeBuilder<S, ArgCommand<S, List<T>>> argMany(String name, ArgumentType<T> type, Supplier<List<T>> empty) {
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
package dan200.computercraft.shared.command.text;
|
package dan200.computercraft.shared.command.text;
|
||||||
|
|
||||||
|
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||||
|
import dan200.computercraft.shared.platform.PlatformHelper;
|
||||||
import net.minecraft.ChatFormatting;
|
import net.minecraft.ChatFormatting;
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.network.chat.ClickEvent;
|
import net.minecraft.network.chat.ClickEvent;
|
||||||
@@ -53,6 +55,13 @@ public final class ChatHelpers {
|
|||||||
return link(component, new ClickEvent(ClickEvent.Action.RUN_COMMAND, command), toolTip);
|
return link(component, new ClickEvent(ClickEvent.Action.RUN_COMMAND, command), toolTip);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Component clientLink(MutableComponent component, String command, Component toolTip) {
|
||||||
|
var event = PlatformHelper.get().canClickRunClientCommand()
|
||||||
|
? new ClickEvent(ClickEvent.Action.RUN_COMMAND, command)
|
||||||
|
: new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, command);
|
||||||
|
return link(component, event, toolTip);
|
||||||
|
}
|
||||||
|
|
||||||
public static Component link(Component component, ClickEvent click, Component toolTip) {
|
public static Component link(Component component, ClickEvent click, Component toolTip) {
|
||||||
var style = component.getStyle();
|
var style = component.getStyle();
|
||||||
|
|
||||||
@@ -73,4 +82,16 @@ public final class ChatHelpers {
|
|||||||
.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.translatable("gui.computercraft.tooltip.copy")))
|
.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.translatable("gui.computercraft.tooltip.copy")))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String makeComputerCommand(String command, ServerComputer computer) {
|
||||||
|
return String.format("/computercraft %s @c[instance=%s]", command, computer.getInstanceUUID());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Component makeComputerDumpCommand(ServerComputer computer) {
|
||||||
|
return link(
|
||||||
|
text("#" + computer.getID()),
|
||||||
|
makeComputerCommand("dump", computer),
|
||||||
|
Component.translatable("commands.computercraft.dump.action")
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user