mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-10-18 15:37:38 +00:00
Compare commits
59 Commits
v1.20.1-1.
...
v1.20.1-1.
Author | SHA1 | Date | |
---|---|---|---|
![]() |
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 |
@@ -44,7 +44,7 @@ repos:
|
||||
name: Check Java codestyle
|
||||
files: ".*\\.java$"
|
||||
language: system
|
||||
entry: ./gradlew checkstyleMain checkstyleTest
|
||||
entry: ./gradlew checkstyle
|
||||
pass_filenames: false
|
||||
require_serial: true
|
||||
- id: illuaminate
|
||||
|
29
.reuse/dep5
29
.reuse/dep5
@@ -10,8 +10,8 @@ Files:
|
||||
projects/common/src/testMod/resources/data/cctest/structures/*
|
||||
projects/fabric/src/generated/*
|
||||
projects/forge/src/generated/*
|
||||
projects/web/src/export/index.json
|
||||
projects/web/src/export/items/minecraft/*
|
||||
projects/web/src/htmlTransform/export/index.json
|
||||
projects/web/src/htmlTransform/export/items/minecraft/*
|
||||
Comment: Generated/data files are CC0.
|
||||
Copyright: The CC: Tweaked Developers
|
||||
License: CC0-1.0
|
||||
@@ -37,10 +37,10 @@ Files:
|
||||
projects/fabric/src/testMod/resources/computercraft-gametest.fabric.mixins.json
|
||||
projects/fabric/src/testMod/resources/fabric.mod.json
|
||||
projects/forge/src/client/resources/computercraft-client.forge.mixins.json
|
||||
projects/web/src/mount/.settings
|
||||
projects/web/src/mount/example.nfp
|
||||
projects/web/src/mount/example.nft
|
||||
projects/web/src/mount/expr_template.lua
|
||||
projects/web/src/frontend/mount/.settings
|
||||
projects/web/src/frontend/mount/example.nfp
|
||||
projects/web/src/frontend/mount/example.nft
|
||||
projects/web/src/frontend/mount/expr_template.lua
|
||||
projects/web/tsconfig.json
|
||||
Comment: Several assets where it's inconvenient to create a .license file.
|
||||
Copyright: The CC: Tweaked Developers
|
||||
@@ -53,19 +53,32 @@ Files:
|
||||
projects/common/src/main/resources/assets/computercraft/textures/*
|
||||
projects/common/src/main/resources/pack.mcmeta
|
||||
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/help/*
|
||||
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.
|
||||
Copyright: 2011 Daniel Ratcliffe
|
||||
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:
|
||||
projects/common/src/main/resources/assets/computercraft/lang/*
|
||||
Comment: Community-contributed license files
|
||||
Copyright: 2017 The CC: Tweaked Developers
|
||||
License: LicenseRef-CCPL
|
||||
License: MPL-2.0
|
||||
|
||||
Files:
|
||||
.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"
|
||||
[community]: README.md#community "Get in touch with the community."
|
||||
[Adoptium]: https://adoptium.net/temurin/releases?version=17 "Download OpenJDK 17"
|
||||
[checkstyle]: https://checkstyle.org/
|
||||
[illuaminate]: https://github.com/SquidDev/illuaminate/ "Illuaminate on GitHub"
|
||||
[weblate]: https://i18n.tweaked.cc/projects/cc-tweaked/minecraft/ "CC: Tweaked weblate instance"
|
||||
[docs]: https://tweaked.cc/ "CC: Tweaked documentation"
|
||||
|
@@ -42,7 +42,6 @@ repositories {
|
||||
url "https://squiddev.cc/maven/"
|
||||
content {
|
||||
includeGroup("cc.tweaked")
|
||||
includeModule("org.squiddev", "Cobalt")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -2,7 +2,11 @@
|
||||
//
|
||||
// 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.runConfigurations
|
||||
import org.jetbrains.gradle.ext.settings
|
||||
|
||||
plugins {
|
||||
@@ -38,6 +42,50 @@ 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 {
|
||||
// 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.
|
||||
|
@@ -50,10 +50,11 @@ dependencies {
|
||||
implementation(libs.curseForgeGradle)
|
||||
implementation(libs.fabric.loom)
|
||||
implementation(libs.forgeGradle)
|
||||
implementation(libs.ideaExt)
|
||||
implementation(libs.librarian)
|
||||
implementation(libs.minotaur)
|
||||
implementation(libs.vineflower)
|
||||
implementation(libs.vanillaGradle)
|
||||
implementation(libs.vineflower)
|
||||
}
|
||||
|
||||
gradlePlugin {
|
||||
|
@@ -57,7 +57,6 @@ repositories {
|
||||
|
||||
filter {
|
||||
includeGroup("cc.tweaked")
|
||||
includeModule("org.squiddev", "Cobalt")
|
||||
// Things we mirror
|
||||
includeGroup("commoble.morered")
|
||||
includeGroup("dev.architectury")
|
||||
@@ -66,6 +65,7 @@ repositories {
|
||||
includeGroup("me.shedaniel.cloth")
|
||||
includeGroup("me.shedaniel")
|
||||
includeGroup("mezz.jei")
|
||||
includeGroup("org.teavm")
|
||||
includeModule("com.terraformersmc", "modmenu")
|
||||
includeModule("me.lucko", "fabric-permissions-api")
|
||||
}
|
||||
@@ -99,7 +99,10 @@ sourceSets.all {
|
||||
check("FutureReturnValueIgnored", CheckSeverity.OFF) // Too many false positives with Netty
|
||||
|
||||
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:CastToNonNullMethod", "dan200.computercraft.core.util.Nullability.assertNonNull")
|
||||
option("NullAway:CheckOptionalEmptiness")
|
||||
@@ -174,6 +177,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 {
|
||||
encoding = StandardCharsets.UTF_8
|
||||
lineEndings = LineEnding.UNIX
|
||||
|
@@ -173,7 +173,7 @@ abstract class CCTweakedExtension(
|
||||
}
|
||||
|
||||
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 jacoco = project.extensions.getByType(JacocoPluginExtension::class.java)
|
||||
@@ -185,7 +185,7 @@ abstract class CCTweakedExtension(
|
||||
jacoco.applyTo(this)
|
||||
extensions.configure(JacocoTaskExtension::class.java) {
|
||||
includes = listOf("dan200.computercraft.*")
|
||||
classDumpDir = classDump
|
||||
classDumpDir = classDump.get().asFile
|
||||
|
||||
// Older versions of modlauncher don't include a protection domain (and thus no code
|
||||
// source). Jacoco skips such classes by default, so we need to explicitly include them.
|
||||
|
@@ -9,6 +9,10 @@ import org.gradle.api.Project
|
||||
import org.gradle.api.plugins.JavaPlugin
|
||||
import org.gradle.api.plugins.JavaPluginExtension
|
||||
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.
|
||||
@@ -21,6 +25,20 @@ class CCTweakedPlugin : Plugin<Project> {
|
||||
val sourceSets = project.extensions.getByType(JavaPluginExtension::class.java).sourceSets
|
||||
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 {
|
||||
|
@@ -4,6 +4,7 @@
|
||||
|
||||
package cc.tweaked.gradle
|
||||
|
||||
import org.gradle.api.file.DirectoryProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.tasks.AbstractExecTask
|
||||
import org.gradle.api.tasks.OutputDirectory
|
||||
@@ -11,5 +12,5 @@ import java.io.File
|
||||
|
||||
abstract class ExecToDir : AbstractExecTask<ExecToDir>(ExecToDir::class.java) {
|
||||
@get:OutputDirectory
|
||||
abstract val output: Property<File>
|
||||
abstract val output: DirectoryProperty
|
||||
}
|
||||
|
@@ -5,6 +5,8 @@
|
||||
package cc.tweaked.gradle
|
||||
|
||||
import org.gradle.api.artifacts.dsl.DependencyHandler
|
||||
import org.gradle.api.file.FileSystemLocation
|
||||
import org.gradle.api.file.FileSystemLocationProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.provider.Provider
|
||||
import org.gradle.api.tasks.JavaExec
|
||||
@@ -124,3 +126,6 @@ class CloseScope : AutoCloseable {
|
||||
|
||||
/** Proxy method to avoid overload ambiguity. */
|
||||
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
|
||||
|
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
|
||||
}
|
||||
}
|
@@ -58,7 +58,7 @@ abstract class ClientJavaExec : JavaExec() {
|
||||
if (!clientDebug) systemProperty("cctest.client", "")
|
||||
if (renderdoc) environment("LD_PRELOAD", "/usr/lib/librenderdoc.so")
|
||||
systemProperty("cctest.gametest-report", testResults.get().asFile.absoluteFile)
|
||||
workingDir(project.buildDir.resolve("gametest").resolve(name))
|
||||
workingDir(project.layout.buildDirectory.dir("gametest/$name"))
|
||||
}
|
||||
|
||||
init {
|
||||
|
@@ -112,7 +112,9 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<module name="LambdaParameterName" />
|
||||
<module name="LocalFinalVariableName" />
|
||||
<module name="LocalVariableName" />
|
||||
<module name="MemberName" />
|
||||
<module name="MemberName">
|
||||
<property name="format" value="^\$?[a-z][a-zA-Z0-9]*$" />
|
||||
</module>
|
||||
<module name="MethodName">
|
||||
<property name="format" value="^(computercraft\$)?[a-z][a-zA-Z0-9]*$" />
|
||||
</module>
|
||||
@@ -122,7 +124,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
</module>
|
||||
<module name="ParameterName" />
|
||||
<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 name="TypeName" />
|
||||
|
||||
|
@@ -16,4 +16,10 @@ SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
<!-- The commands API is documented in Lua. -->
|
||||
<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.java" />
|
||||
</suppressions>
|
||||
|
@@ -6,7 +6,7 @@ see: key To listen to any key press.
|
||||
<!--
|
||||
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.
|
||||
|
@@ -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.
|
||||
|
||||
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.
|
||||
|
||||
## Return values
|
||||
@@ -29,7 +29,7 @@ for _, file in ipairs(files.getFiles()) do
|
||||
local size = file.seek("end")
|
||||
file.seek("set", 0)
|
||||
|
||||
print(file.getName() .. " " .. file.getSize())
|
||||
print(file.getName() .. " " .. size)
|
||||
end
|
||||
```
|
||||
|
||||
|
@@ -5,7 +5,7 @@ module: [kind=event] key
|
||||
<!--
|
||||
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.
|
||||
|
@@ -6,7 +6,7 @@ see: keys For a lookup table of the given keys.
|
||||
<!--
|
||||
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).
|
||||
|
@@ -5,7 +5,7 @@ module: [kind=event] mouse_click
|
||||
<!--
|
||||
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
|
||||
|
@@ -6,7 +6,7 @@ see: mouse_click For when a mouse button is initially pressed.
|
||||
<!--
|
||||
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.
|
||||
|
@@ -5,7 +5,7 @@ module: [kind=event] mouse_scroll
|
||||
<!--
|
||||
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.
|
||||
|
@@ -5,7 +5,7 @@ module: [kind=event] mouse_up
|
||||
<!--
|
||||
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.
|
||||
|
@@ -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
|
||||
sample, which means we want to process our audio in chunks of 16×1024 bytes (16KiB). In order to do this, we use
|
||||
[`io.lines`], which provides a nice way to loop over chunks of a file. You can of course just use [`fs.open`] and
|
||||
[`fs.BinaryReadHandle.read`] if you prefer.
|
||||
[`fs.ReadHandle.read`] if you prefer.
|
||||
|
||||
## 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.
|
||||
|
80
doc/reference/breaking_changes.md
Normal file
80
doc/reference/breaking_changes.md
Normal file
@@ -0,0 +1,80 @@
|
||||
---
|
||||
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 {#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.
|
||||
|
||||
- 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.com/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"
|
@@ -9,17 +9,19 @@ SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
# 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
|
||||
| Feature | Supported? | Notes |
|
||||
|---------------------------------------------------------------|------------|-------------------------------------------------------------------|
|
||||
| `goto`/labels | ❌ | |
|
||||
| `_ENV` | 🔶 | The `_ENV` global points to `getfenv()`, but it cannot be set. |
|
||||
| `goto`/labels | ✔ | |
|
||||
| `_ENV` | ✔ | |
|
||||
| `\z` escape | ✔ | |
|
||||
| `\xNN` escape | ✔ | |
|
||||
| Hex literal fractional/exponent parts | ✔ | |
|
||||
| Empty statements | ❌ | |
|
||||
| Empty statements | ✔ | |
|
||||
| `__len` metamethod | ✔ | |
|
||||
| `__ipairs` metamethod | ❌ | Deprecated in Lua 5.3. `ipairs` uses `__len`/`__index` instead. |
|
||||
| `__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. |
|
||||
| New `load` 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 `getfenv`, `setfenv` | 🔶 | Only if `disable_lua51_features` is enabled in the configuration. |
|
||||
| Removed `loadstring` | ❌ | |
|
||||
| Removed `getfenv`, `setfenv` | 🔶 | Only supports closures with an `_ENV` upvalue. |
|
||||
| `rawlen` function | ✔ | |
|
||||
| Negative index to `select` | ✔ | |
|
||||
| Removed `unpack` | 🔶 | Only if `disable_lua51_features` is enabled in the configuration. |
|
||||
| Arguments to `xpcall` | ✔ | |
|
||||
| Removed `unpack` | ❌ | |
|
||||
| Arguments to `xpcall` | ✔ | |
|
||||
| Second return value from `coroutine.running` | ✔ | |
|
||||
| Removed `module` | ✔ | |
|
||||
| `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.searchpath` | ✔ | |
|
||||
| Removed `package.seeall` | ✔ | |
|
||||
| `string.dump` on functions with upvalues (blanks them out) | ✔ | |
|
||||
| `string.rep` separator | ✔ | |
|
||||
| `string.dump` on functions with upvalues (blanks them out) | ❌ | `string.dump` is not supported |
|
||||
| `string.rep` separator | ✔ | |
|
||||
| `%g` 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` | ✔ | |
|
||||
| `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` | ✔ | |
|
||||
| `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. |
|
||||
@@ -61,7 +63,7 @@ CC: Tweaked is based off of the Cobalt Lua runtime, which uses Lua 5.1. However,
|
||||
| Tail call hooks | ❌ | |
|
||||
| `=` prefix for chunks | ✔ | |
|
||||
| Yield across C boundary | ✔ | |
|
||||
| Removal of ambiguity error | ❌ | |
|
||||
| Removal of ambiguity error | ✔ | |
|
||||
| Identifiers may no longer use locale-dependent letters | ✔ | |
|
||||
| Ephemeron tables | ❌ | |
|
||||
| Identical functions may be reused | ❌ | Removed in Lua 5.4 |
|
||||
|
@@ -95,10 +95,10 @@ function pullEventRaw(filter) end
|
||||
-- nearest multiple of 0.05.
|
||||
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
|
||||
-- should return `CraftOS 1.8`.
|
||||
-- should return `CraftOS 1.9`.
|
||||
--
|
||||
-- @treturn string The current CraftOS version.
|
||||
-- @usage os.version()
|
||||
|
@@ -9,8 +9,8 @@ kotlin.stdlib.default.dependency=false
|
||||
kotlin.jvm.target.validation.mode=error
|
||||
|
||||
# Mod properties
|
||||
isUnstable=false
|
||||
modVersion=1.108.1
|
||||
isUnstable=true
|
||||
modVersion=1.109.1
|
||||
|
||||
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
|
||||
mcVersion=1.20.1
|
||||
|
@@ -19,8 +19,9 @@ parchmentMc = "1.20.1"
|
||||
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.
|
||||
cobalt = "0.8.0"
|
||||
cobalt-next = "0.8.1" # Not a real version, used to constrain the version we accept.
|
||||
commonsCli = "1.3.1"
|
||||
fastutil = "8.5.9"
|
||||
guava = "31.1-jre"
|
||||
jetbrainsAnnotations = "24.0.1"
|
||||
@@ -30,7 +31,7 @@ kotlin = "1.8.10"
|
||||
kotlin-coroutines = "1.6.4"
|
||||
netty = "4.1.82.Final"
|
||||
nightConfig = "3.6.7"
|
||||
slf4j = "1.7.36"
|
||||
slf4j = "2.0.1"
|
||||
|
||||
# Minecraft mods
|
||||
emi = "1.0.8+1.20.1"
|
||||
@@ -45,13 +46,12 @@ rubidium = "0.6.1"
|
||||
sodium = "mc1.20-0.4.10"
|
||||
|
||||
# Testing
|
||||
byteBuddy = "1.14.7"
|
||||
hamcrest = "2.2"
|
||||
jqwik = "1.7.4"
|
||||
junit = "5.10.0"
|
||||
|
||||
# Build tools
|
||||
cctJavadoc = "1.8.0"
|
||||
cctJavadoc = "1.8.2"
|
||||
checkstyle = "10.12.3"
|
||||
curseForgeGradle = "1.0.14"
|
||||
errorProne-core = "2.21.1"
|
||||
@@ -60,22 +60,26 @@ fabric-loom = "1.3.7"
|
||||
forgeGradle = "6.0.8"
|
||||
githubRelease = "2.4.1"
|
||||
ideaExt = "1.1.7"
|
||||
illuaminate = "0.1.0-40-g975cbc3"
|
||||
illuaminate = "0.1.0-44-g9ee0055"
|
||||
librarian = "1.+"
|
||||
lwjgl = "3.3.1"
|
||||
minotaur = "2.+"
|
||||
mixinGradle = "0.7.+"
|
||||
nullAway = "0.9.9"
|
||||
spotless = "6.21.0"
|
||||
taskTree = "2.1.1"
|
||||
teavm = "0.10.0-SQUID.2"
|
||||
vanillaGradle = "0.2.1-SNAPSHOT"
|
||||
vineflower = "1.11.0"
|
||||
|
||||
[libraries]
|
||||
# Normal dependencies
|
||||
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" }
|
||||
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" }
|
||||
forgeSpi = { module = "net.minecraftforge:forgespi", version.ref = "forgeSpi" }
|
||||
guava = { module = "com.google.guava:guava", version.ref = "guava" }
|
||||
@@ -95,6 +99,7 @@ slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
|
||||
# Minecraft mods
|
||||
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-junit = { module = "net.fabricmc:fabric-loader-junit", version.ref = "fabric-loader" }
|
||||
fabricPermissions = { module = "me.lucko:fabric-permissions-api", version.ref = "fabricPermissions" }
|
||||
emi = { module = "dev.emi:emi-xplat-mojmap", version.ref = "emi" }
|
||||
iris = { module = "maven.modrinth:iris", version.ref = "iris" }
|
||||
@@ -112,8 +117,6 @@ rubidium = { module = "maven.modrinth:rubidium", version.ref = "rubidium" }
|
||||
sodium = { module = "maven.modrinth:sodium", version.ref = "sodium" }
|
||||
|
||||
# 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" }
|
||||
jqwik-api = { module = "net.jqwik:jqwik-api", version.ref = "jqwik" }
|
||||
jqwik-engine = { module = "net.jqwik:jqwik-engine", version.ref = "jqwik" }
|
||||
@@ -122,6 +125,12 @@ junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", vers
|
||||
junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit" }
|
||||
slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" }
|
||||
|
||||
# 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
|
||||
cctJavadoc = { module = "cc.tweaked:cct-javadoc", version.ref = "cctJavadoc" }
|
||||
checkstyle = { module = "com.puppycrawl.tools:checkstyle", version.ref = "checkstyle" }
|
||||
@@ -133,24 +142,33 @@ errorProne-plugin = { module = "net.ltgt.gradle:gradle-errorprone-plugin", versi
|
||||
errorProne-testHelpers = { module = "com.google.errorprone:error_prone_test_helpers", version.ref = "errorProne-core" }
|
||||
fabric-loom = { module = "net.fabricmc:fabric-loom", version.ref = "fabric-loom" }
|
||||
forgeGradle = { module = "net.minecraftforge.gradle:ForgeGradle", version.ref = "forgeGradle" }
|
||||
ideaExt = { module = "gradle.plugin.org.jetbrains.gradle.plugin.idea-ext:gradle-idea-ext", version.ref = "ideaExt" }
|
||||
kotlin-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
|
||||
librarian = { module = "org.parchmentmc:librarian", version.ref = "librarian" }
|
||||
minotaur = { module = "com.modrinth.minotaur:Minotaur", version.ref = "minotaur" }
|
||||
nullAway = { module = "com.uber.nullaway:nullaway", version.ref = "nullAway" }
|
||||
spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" }
|
||||
teavm-classlib = { module = "org.teavm:teavm-classlib", version.ref = "teavm" }
|
||||
teavm-jso = { module = "org.teavm:teavm-jso", version.ref = "teavm" }
|
||||
teavm-jso-apis = { module = "org.teavm:teavm-jso-apis", version.ref = "teavm" }
|
||||
teavm-jso-impl = { module = "org.teavm:teavm-jso-impl", version.ref = "teavm" }
|
||||
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" }
|
||||
vanillaGradle = { module = "org.spongepowered:vanillagradle", version.ref = "vanillaGradle" }
|
||||
vineflower = { module = "io.github.juuxel:loom-vineflower", version.ref = "vineflower" }
|
||||
|
||||
[plugins]
|
||||
forgeGradle = { id = "net.minecraftforge.gradle", version.ref = "forgeGradle" }
|
||||
githubRelease = { id = "com.github.breadmoirai.github-release", version.ref = "githubRelease" }
|
||||
ideaExt = { id = "org.jetbrains.gradle.plugin.idea-ext", version.ref = "ideaExt" }
|
||||
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
||||
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" }
|
||||
|
||||
[bundles]
|
||||
annotations = ["jsr305", "checkerFramework", "jetbrainsAnnotations"]
|
||||
kotlin = ["kotlin-stdlib", "kotlin-coroutines"]
|
||||
|
||||
# Minecraft
|
||||
@@ -164,3 +182,7 @@ externalMods-fabric-runtime = ["jei-fabric", "modmenu"]
|
||||
# Testing
|
||||
test = ["junit-jupiter-api", "junit-jupiter-params", "hamcrest", "jqwik-api"]
|
||||
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" ]
|
||||
|
@@ -2,15 +2,15 @@
|
||||
|
||||
; SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
||||
;
|
||||
; SPDX-License-Identifier: LicenseRef-CCPL
|
||||
; SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
(sources
|
||||
/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/rom/
|
||||
/projects/core/src/test/resources/test-rom
|
||||
/projects/web/src/mount)
|
||||
/projects/web/src/frontend/mount)
|
||||
|
||||
(doc
|
||||
; Also defined in projects/web/build.gradle.kts
|
||||
@@ -23,7 +23,7 @@
|
||||
(url https://tweaked.cc/)
|
||||
(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)
|
||||
(head doc/head.html))
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
(library-path
|
||||
/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/command/
|
||||
@@ -77,7 +77,6 @@
|
||||
(globals
|
||||
:max
|
||||
_CC_DEFAULT_SETTINGS
|
||||
_CC_DISABLE_LUA51_FEATURES
|
||||
_HOST
|
||||
;; Ideally we'd pick these up from bios.lua, but illuaminate currently
|
||||
;; isn't smart enough.
|
||||
@@ -89,7 +88,7 @@
|
||||
(/doc/stub/
|
||||
/projects/core/src/main/resources/data/computercraft/lua/bios.lua
|
||||
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/
|
||||
/projects/forge/build/docs/luaJavadoc/)
|
||||
/projects/common/build/docs/luaJavadoc/)
|
||||
(linters -var:unused-global)
|
||||
(lint (allow-toplevel-global true)))
|
||||
|
||||
@@ -115,4 +114,4 @@
|
||||
:max sleep write
|
||||
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__)))
|
||||
|
3957
package-lock.json
generated
3957
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",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@squid-dev/cc-web-term": "^2.0.0",
|
||||
"preact": "^10.5.5",
|
||||
"setimmediate": "^1.0.5",
|
||||
"tslib": "^2.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-terser": "^0.4.0",
|
||||
"@rollup/plugin-node-resolve": "^15.2.1",
|
||||
"@rollup/plugin-typescript": "^11.0.0",
|
||||
"@rollup/plugin-url": "^8.0.1",
|
||||
"@types/glob": "^8.1.0",
|
||||
"@types/react-dom": "^18.0.5",
|
||||
"glob": "^10.3.4",
|
||||
"react-dom": "^18.1.0",
|
||||
"react": "^18.1.0",
|
||||
"rehype-highlight": "^6.0.0",
|
||||
"rehype-react": "^7.1.1",
|
||||
"rehype": "^12.0.0",
|
||||
"requirejs": "^2.3.6",
|
||||
"rollup": "^3.19.1",
|
||||
"ts-node": "^10.8.0",
|
||||
"@swc/core": "^1.3.92",
|
||||
"@types/node": "^20.8.3",
|
||||
"lightningcss": "^1.22.0",
|
||||
"preact-render-to-string": "^6.2.1",
|
||||
"rehype": "^13.0.0",
|
||||
"rehype-highlight": "^7.0.0",
|
||||
"rehype-react": "^8.0.0",
|
||||
"rollup": "^4.0.0",
|
||||
"tsx": "^3.12.10",
|
||||
"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
|
||||
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
|
||||
rendering recipes
|
||||
|
||||
|
@@ -46,6 +46,14 @@ public class ComputerCraftTags {
|
||||
public static final TagKey<Block> WIRED_MODEM = make("wired_modem");
|
||||
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.
|
||||
*/
|
||||
|
@@ -19,8 +19,6 @@ import net.minecraft.data.PackOutput;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.Item;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.*;
|
||||
@@ -36,8 +34,6 @@ import java.util.function.Function;
|
||||
* @param <R> The upgrade serialiser to register for.
|
||||
*/
|
||||
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 String name;
|
||||
private final String folder;
|
||||
|
@@ -2,14 +2,13 @@
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
import cc.tweaked.gradle.annotationProcessorEverywhere
|
||||
import cc.tweaked.gradle.clientClasses
|
||||
import cc.tweaked.gradle.commonClasses
|
||||
import cc.tweaked.gradle.*
|
||||
|
||||
plugins {
|
||||
id("cc-tweaked.publishing")
|
||||
id("cc-tweaked.vanilla")
|
||||
id("cc-tweaked.gametest")
|
||||
id("cc-tweaked.illuaminate")
|
||||
id("cc-tweaked.publishing")
|
||||
}
|
||||
|
||||
minecraft {
|
||||
@@ -19,6 +18,10 @@ minecraft {
|
||||
)
|
||||
}
|
||||
|
||||
configurations {
|
||||
register("cctJavadoc")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Pull in our other projects. See comments in MinecraftConfigurations on this nastiness.
|
||||
implementation(project(":core"))
|
||||
@@ -39,4 +42,55 @@ dependencies {
|
||||
testModImplementation(testFixtures(project(":core")))
|
||||
testModImplementation(testFixtures(project(":common")))
|
||||
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(cc.tweaked.gradle.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::") }
|
||||
}
|
||||
|
@@ -19,6 +19,7 @@ import dan200.computercraft.shared.network.server.UploadFileMessage;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.Util;
|
||||
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.network.chat.Component;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
@@ -33,7 +34,6 @@ import java.nio.ByteBuffer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@@ -145,6 +145,11 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
|
||||
|| 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
|
||||
protected void renderLabels(GuiGraphics graphics, int mouseX, int mouseY) {
|
||||
@@ -219,7 +224,7 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
|
||||
|
||||
private void alert(Component title, Component 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)
|
||||
);
|
||||
}
|
||||
|
@@ -8,8 +8,8 @@ import com.mojang.blaze3d.vertex.Tesselator;
|
||||
import dan200.computercraft.client.render.RenderTypes;
|
||||
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.core.util.StringUtil;
|
||||
import dan200.computercraft.shared.computer.core.InputHandler;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.components.AbstractWidget;
|
||||
@@ -112,26 +112,8 @@ public class TerminalWidget extends AbstractWidget {
|
||||
}
|
||||
|
||||
private void paste() {
|
||||
var clipboard = Minecraft.getInstance().keyboardHandler.getClipboard();
|
||||
|
||||
// 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 });
|
||||
}
|
||||
var clipboard = StringUtil.normaliseClipboardString(Minecraft.getInstance().keyboardHandler.getClipboard());
|
||||
if (!clipboard.isEmpty()) computer.queueEvent("paste", new Object[]{ clipboard });
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -264,7 +246,7 @@ public class TerminalWidget extends AbstractWidget {
|
||||
keysDown.clear();
|
||||
|
||||
// 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);
|
||||
lastMouseButton = -1;
|
||||
}
|
||||
|
@@ -14,10 +14,10 @@ import dan200.computercraft.core.terminal.TextBuffer;
|
||||
import dan200.computercraft.core.util.Colour;
|
||||
import net.minecraft.client.renderer.ShaderInstance;
|
||||
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.GL31;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
@@ -36,12 +36,12 @@ import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.get
|
||||
* @see RenderTypes#getMonitorTextureBufferShader()
|
||||
*/
|
||||
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;
|
||||
|
||||
static final int TEXTURE_INDEX = GL13.GL_TEXTURE3;
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
|
||||
private final int monitorData;
|
||||
private int uniformBuffer = 0;
|
||||
|
||||
@@ -75,7 +75,7 @@ public class MonitorTextureBufferShader extends ShaderInstance {
|
||||
private Uniform getUniformChecked(String name) {
|
||||
var uniform = getUniform(name);
|
||||
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;
|
||||
|
@@ -174,6 +174,7 @@ public final class LanguageProvider implements DataProvider {
|
||||
// Metrics
|
||||
add(Metrics.COMPUTER_TASKS, "Tasks");
|
||||
add(Metrics.SERVER_TASKS, "Server tasks");
|
||||
add(Metrics.JAVA_ALLOCATION, "Java Allocations");
|
||||
add(Metrics.PERIPHERAL_OPS, "Peripheral calls");
|
||||
add(Metrics.FS_OPS, "Filesystem operations");
|
||||
add(Metrics.HTTP_REQUESTS, "HTTP requests");
|
||||
@@ -213,7 +214,6 @@ public final class LanguageProvider implements DataProvider {
|
||||
addConfigEntry(ConfigSpec.floppySpaceLimit, "Floppy Disk space limit (bytes)");
|
||||
addConfigEntry(ConfigSpec.uploadMaxSize, "File upload size limit (bytes)");
|
||||
addConfigEntry(ConfigSpec.maximumFilesOpen, "Maximum files open per computer");
|
||||
addConfigEntry(ConfigSpec.disableLua51Features, "Disable Lua 5.1 features");
|
||||
addConfigEntry(ConfigSpec.defaultComputerSettings, "Default Computer settings");
|
||||
addConfigEntry(ConfigSpec.logComputerErrors, "Log computer errors");
|
||||
addConfigEntry(ConfigSpec.commandRequireCreative, "Command computers require creative");
|
||||
|
@@ -5,6 +5,7 @@
|
||||
package dan200.computercraft.data;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.pocket.PocketUpgradeDataProvider;
|
||||
import dan200.computercraft.api.turtle.TurtleUpgradeDataProvider;
|
||||
@@ -25,15 +26,12 @@ import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.data.PackOutput;
|
||||
import net.minecraft.data.recipes.*;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.NbtUtils;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import net.minecraft.world.item.DyeColor;
|
||||
import net.minecraft.world.item.DyeItem;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.item.*;
|
||||
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.SimpleCraftingRecipeSerializer;
|
||||
import net.minecraft.world.level.ItemLike;
|
||||
@@ -41,6 +39,7 @@ import net.minecraft.world.level.block.Blocks;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static dan200.computercraft.api.ComputerCraftTags.Items.COMPUTER;
|
||||
@@ -443,7 +442,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
||||
.requires(ModRegistry.Items.MONITOR_NORMAL.get())
|
||||
.unlockedBy("has_monitor", inventoryChange(ModRegistry.Items.MONITOR_NORMAL.get()))
|
||||
.save(
|
||||
RecipeWrapper.wrap(RecipeSerializer.SHAPELESS_RECIPE, add)
|
||||
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.SHAPELESS.get(), add)
|
||||
.withResultTag(playerHead("Cloudhunter", "6d074736-b1e9-4378-a99b-bd8777821c9c")),
|
||||
new ResourceLocation(ComputerCraftAPI.MOD_ID, "skull_cloudy")
|
||||
);
|
||||
@@ -454,7 +453,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
||||
.requires(ModRegistry.Items.COMPUTER_ADVANCED.get())
|
||||
.unlockedBy("has_computer", inventoryChange(ModRegistry.Items.COMPUTER_ADVANCED.get()))
|
||||
.save(
|
||||
RecipeWrapper.wrap(RecipeSerializer.SHAPELESS_RECIPE, add)
|
||||
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.SHAPELESS.get(), add)
|
||||
.withResultTag(playerHead("dan200", "f3c8d69b-0776-4512-8434-d1b2165909eb")),
|
||||
new ResourceLocation(ComputerCraftAPI.MOD_ID, "skull_dan200")
|
||||
);
|
||||
@@ -513,17 +512,15 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
||||
}
|
||||
|
||||
private static CompoundTag playerHead(String name, String uuid) {
|
||||
var owner = new CompoundTag();
|
||||
owner.putString("Name", name);
|
||||
owner.putString("Id", uuid);
|
||||
var owner = NbtUtils.writeGameProfile(new CompoundTag(), new GameProfile(UUID.fromString(uuid), name));
|
||||
|
||||
var tag = new CompoundTag();
|
||||
tag.put("SkullOwner", owner);
|
||||
tag.put(PlayerHeadItem.TAG_SKULL_OWNER, owner);
|
||||
return tag;
|
||||
}
|
||||
|
||||
private static Consumer<JsonObject> family(ComputerFamily family) {
|
||||
return json -> json.addProperty("family", family.toString());
|
||||
return json -> json.addProperty("family", family.getSerializedName());
|
||||
}
|
||||
|
||||
private static void addSpecial(Consumer<FinishedRecipe> add, SimpleCraftingRecipeSerializer<?> special) {
|
||||
|
@@ -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.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(
|
||||
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_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.
|
||||
tags.tag(BlockTags.MINEABLE_WITH_PICKAXE).add(
|
||||
|
@@ -18,8 +18,8 @@ import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import net.minecraft.util.profiling.ProfilerFiller;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Collection;
|
||||
@@ -37,7 +37,7 @@ import java.util.stream.Collectors;
|
||||
* @see PocketUpgrades
|
||||
*/
|
||||
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();
|
||||
|
||||
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 ResourceKey<Registry<R>> registry;
|
||||
|
||||
private Map<String, UpgradeWrapper<R, T>> current = Collections.emptyMap();
|
||||
private Map<T, UpgradeWrapper<R, T>> currentWrappers = Collections.emptyMap();
|
||||
private Map<String, UpgradeWrapper<R, T>> current = Map.of();
|
||||
private Map<T, UpgradeWrapper<R, T>> currentWrappers = Map.of();
|
||||
|
||||
public UpgradeManager(String kind, String path, ResourceKey<Registry<R>> registry) {
|
||||
super(GSON, path);
|
||||
@@ -103,13 +103,13 @@ public class UpgradeManager<R extends UpgradeSerialiser<? extends T>, T extends
|
||||
try {
|
||||
loadUpgrade(newUpgrades, element.getKey(), element.getValue());
|
||||
} 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);
|
||||
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) {
|
||||
|
@@ -12,7 +12,7 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
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> added;
|
||||
@@ -27,11 +27,11 @@ final class WiredNetworkChangeImpl implements WiredNetworkChange {
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
@@ -39,9 +39,9 @@ final class WiredNetworkChangeImpl implements WiredNetworkChange {
|
||||
if (oldPeripherals.isEmpty() && newPeripherals.isEmpty()) {
|
||||
return EMPTY;
|
||||
} else if (oldPeripherals.isEmpty()) {
|
||||
return new WiredNetworkChangeImpl(Collections.emptyMap(), newPeripherals);
|
||||
return new WiredNetworkChangeImpl(Map.of(), newPeripherals);
|
||||
} else if (newPeripherals.isEmpty()) {
|
||||
return new WiredNetworkChangeImpl(oldPeripherals, Collections.emptyMap());
|
||||
return new WiredNetworkChangeImpl(oldPeripherals, Map.of());
|
||||
}
|
||||
|
||||
Map<String, IPeripheral> added = new HashMap<>(newPeripherals);
|
||||
|
@@ -4,7 +4,6 @@
|
||||
|
||||
package dan200.computercraft.impl.network.wired;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import dan200.computercraft.api.network.Packet;
|
||||
import dan200.computercraft.api.network.wired.WiredNetwork;
|
||||
import dan200.computercraft.api.network.wired.WiredNode;
|
||||
@@ -57,10 +56,10 @@ final class WiredNetworkImpl implements WiredNetwork {
|
||||
// Move all nodes across into this network, destroying the original nodes.
|
||||
nodes.addAll(otherNodes);
|
||||
for (var node : otherNodes) node.network = this;
|
||||
other.nodes = Collections.emptySet();
|
||||
other.nodes = Set.of();
|
||||
|
||||
// Move all peripherals across,
|
||||
other.peripherals = Collections.emptyMap();
|
||||
other.peripherals = Map.of();
|
||||
peripherals.putAll(otherPeripherals);
|
||||
|
||||
if (!thisPeripherals.isEmpty()) {
|
||||
@@ -217,7 +216,7 @@ final class WiredNetworkImpl implements WiredNetwork {
|
||||
try {
|
||||
// We special case the original node: detaching all peripherals when needed.
|
||||
wired.network = wiredNetwork;
|
||||
wired.peripherals = Collections.emptyMap();
|
||||
wired.peripherals = Map.of();
|
||||
|
||||
// Ensure every network is finalised
|
||||
for (var network : maximals) {
|
||||
@@ -260,7 +259,7 @@ final class WiredNetworkImpl implements WiredNetwork {
|
||||
var change = WiredNetworkChangeImpl.changeOf(oldPeripherals, newPeripherals);
|
||||
if (change.isEmpty()) return;
|
||||
|
||||
wired.peripherals = ImmutableMap.copyOf(newPeripherals);
|
||||
wired.peripherals = Map.copyOf(newPeripherals);
|
||||
|
||||
// Detach the old peripherals then remove them.
|
||||
peripherals.keySet().removeAll(change.peripheralsRemoved().keySet());
|
||||
@@ -333,7 +332,7 @@ final class WiredNetworkImpl implements WiredNetwork {
|
||||
// Detach the old peripherals then remove them from the old network
|
||||
wired.network = wiredNetwork;
|
||||
wired.neighbours.clear();
|
||||
wired.peripherals = Collections.emptyMap();
|
||||
wired.peripherals = Map.of();
|
||||
|
||||
// Broadcast the change
|
||||
if (!peripherals.isEmpty()) WiredNetworkChangeImpl.removed(peripherals).broadcast(wired);
|
||||
|
@@ -13,13 +13,16 @@ import dan200.computercraft.api.network.wired.WiredSender;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
|
||||
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 {
|
||||
private @Nullable Set<PacketReceiver> receivers;
|
||||
|
||||
final WiredElement element;
|
||||
Map<String, IPeripheral> peripherals = Collections.emptyMap();
|
||||
Map<String, IPeripheral> peripherals = Map.of();
|
||||
|
||||
final HashSet<WiredNodeImpl> neighbours = new HashSet<>();
|
||||
volatile WiredNetworkImpl network;
|
||||
|
@@ -70,6 +70,10 @@ import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
||||
import dan200.computercraft.shared.pocket.peripherals.PocketModem;
|
||||
import dan200.computercraft.shared.pocket.peripherals.PocketSpeaker;
|
||||
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.blocks.TurtleBlock;
|
||||
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.TurtleUpgradeRecipe;
|
||||
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.synchronization.ArgumentTypeInfo;
|
||||
import net.minecraft.commands.synchronization.SingletonArgumentInfo;
|
||||
@@ -359,17 +361,21 @@ public final class ModRegistry {
|
||||
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<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<TurtleOverlayRecipe.Serializer> TURTLE_OVERLAY = REGISTRY.register("turtle_overlay", TurtleOverlayRecipe.Serializer::new);
|
||||
public static final RegistryEntry<RecipeSerializer<TurtleOverlayRecipe>> TURTLE_OVERLAY = REGISTRY.register("turtle_overlay", TurtleOverlayRecipe.Serializer::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<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<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 final RegistryEntry<RecipeSerializer<ComputerUpgradeRecipe>> COMPUTER_UPGRADE = REGISTRY.register("computer_upgrade", ComputerUpgradeRecipe.Serializer::new);
|
||||
}
|
||||
|
||||
public static class Permissions {
|
||||
|
@@ -89,7 +89,7 @@ public final class CommandComputerCraft {
|
||||
RequiredArgumentBuilder.<CommandSourceStack, ComputersArgumentType.ComputersSupplier>argument("computer", manyComputers())
|
||||
.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)))
|
||||
|
||||
.then(command("view")
|
||||
@@ -300,7 +300,7 @@ public final class CommandComputerCraft {
|
||||
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.NONE),
|
||||
new AggregatedMetric(Metrics.COMPUTER_TASKS, Aggregate.AVG)
|
||||
|
@@ -32,7 +32,7 @@ public final class ComputersArgumentType implements ArgumentType<ComputersArgume
|
||||
private static final ComputersArgumentType MANY = new ComputersArgumentType(false);
|
||||
private static final ComputersArgumentType SOME = new ComputersArgumentType(true);
|
||||
|
||||
private static final List<String> EXAMPLES = Arrays.asList(
|
||||
private static final List<String> EXAMPLES = List.of(
|
||||
"0", "#0", "@Label", "~Advanced"
|
||||
);
|
||||
|
||||
@@ -75,7 +75,7 @@ public final class ComputersArgumentType implements ArgumentType<ComputersArgume
|
||||
var instance = reader.readInt();
|
||||
computers = s -> {
|
||||
var computer = ServerContext.get(s.getServer()).registry().get(instance);
|
||||
return computer == null ? Collections.emptyList() : Collections.singletonList(computer);
|
||||
return computer == null ? List.of() : List.of(computer);
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -15,7 +15,6 @@ import net.minecraft.commands.CommandSourceStack;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
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) {
|
||||
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) {
|
||||
|
@@ -134,12 +134,12 @@ public class CommandAPI implements ILuaAPI {
|
||||
public final List<String> list(IArguments args) throws LuaException {
|
||||
var server = computer.getLevel().getServer();
|
||||
|
||||
if (server == null) return Collections.emptyList();
|
||||
if (server == null) return List.of();
|
||||
CommandNode<CommandSourceStack> node = server.getCommands().getDispatcher().getRoot();
|
||||
for (var j = 0; j < args.count(); j++) {
|
||||
var name = args.getString(j);
|
||||
node = node.getChild(name);
|
||||
if (!(node instanceof LiteralCommandNode)) return Collections.emptyList();
|
||||
if (!(node instanceof LiteralCommandNode)) return List.of();
|
||||
}
|
||||
|
||||
List<String> result = new ArrayList<>();
|
||||
|
@@ -4,8 +4,33 @@
|
||||
|
||||
package dan200.computercraft.shared.computer.core;
|
||||
|
||||
public enum ComputerFamily {
|
||||
NORMAL,
|
||||
ADVANCED,
|
||||
COMMAND
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import net.minecraft.util.StringRepresentable;
|
||||
|
||||
public enum ComputerFamily implements StringRepresentable {
|
||||
NORMAL("normal"),
|
||||
ADVANCED("advanced"),
|
||||
COMMAND("command");
|
||||
|
||||
private final String name;
|
||||
|
||||
ComputerFamily(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public static ComputerFamily getFamily(JsonObject json, String name) {
|
||||
var familyName = GsonHelper.getAsString(json, name);
|
||||
for (var family : values()) {
|
||||
if (family.getSerializedName().equalsIgnoreCase(familyName)) return family;
|
||||
}
|
||||
|
||||
throw new JsonSyntaxException("Unknown computer family '" + familyName + "' for field " + name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSerializedName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
@@ -21,6 +21,8 @@ import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static dan200.computercraft.api.filesystem.MountConstants.NO_SUCH_FILE;
|
||||
|
||||
/**
|
||||
* A mount backed by Minecraft's {@link ResourceManager}.
|
||||
*
|
||||
@@ -29,8 +31,6 @@ import java.util.Map;
|
||||
public final class ResourceMount extends ArchiveMount<ResourceMount.FileEntry> {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ResourceMount.class);
|
||||
|
||||
private static final byte[] TEMP_BUFFER = new byte[8192];
|
||||
|
||||
/**
|
||||
* Maintain a cache of currently loaded resource mounts. This cache is invalidated when currentManager changes.
|
||||
*/
|
||||
@@ -60,7 +60,7 @@ public final class ResourceMount extends ArchiveMount<ResourceMount.FileEntry> {
|
||||
var hasAny = false;
|
||||
String existingNamespace = null;
|
||||
|
||||
var newRoot = new FileEntry("", new ResourceLocation(namespace, subPath));
|
||||
var newRoot = new FileEntry(new ResourceLocation(namespace, subPath));
|
||||
for (var file : manager.listResources(subPath, s -> true).keySet()) {
|
||||
existingNamespace = file.getNamespace();
|
||||
|
||||
@@ -68,7 +68,11 @@ public final class ResourceMount extends ArchiveMount<ResourceMount.FileEntry> {
|
||||
if (!FileSystem.contains(subPath, file.getPath())) continue; // Some packs seem to include the parent?
|
||||
|
||||
var localPath = FileSystem.toLocal(file.getPath(), subPath);
|
||||
create(newRoot, localPath);
|
||||
try {
|
||||
getOrCreateChild(newRoot, localPath, this::createEntry);
|
||||
} catch (ResourceLocationException e) {
|
||||
LOG.warn("Cannot create resource location for {} ({})", localPath, e.getMessage());
|
||||
}
|
||||
hasAny = true;
|
||||
}
|
||||
|
||||
@@ -83,65 +87,24 @@ public final class ResourceMount extends ArchiveMount<ResourceMount.FileEntry> {
|
||||
}
|
||||
}
|
||||
|
||||
private void create(FileEntry lastEntry, String path) {
|
||||
var lastIndex = 0;
|
||||
while (lastIndex < path.length()) {
|
||||
var nextIndex = path.indexOf('/', lastIndex);
|
||||
if (nextIndex < 0) nextIndex = path.length();
|
||||
|
||||
var part = path.substring(lastIndex, nextIndex);
|
||||
if (lastEntry.children == null) lastEntry.children = new HashMap<>();
|
||||
|
||||
var nextEntry = lastEntry.children.get(part);
|
||||
if (nextEntry == null) {
|
||||
ResourceLocation childPath;
|
||||
try {
|
||||
childPath = new ResourceLocation(namespace, subPath + "/" + path);
|
||||
} catch (ResourceLocationException e) {
|
||||
LOG.warn("Cannot create resource location for {} ({})", part, e.getMessage());
|
||||
return;
|
||||
}
|
||||
lastEntry.children.put(part, nextEntry = new FileEntry(path, childPath));
|
||||
}
|
||||
|
||||
lastEntry = nextEntry;
|
||||
lastIndex = nextIndex + 1;
|
||||
}
|
||||
private FileEntry createEntry(String path) {
|
||||
return new FileEntry(new ResourceLocation(namespace, subPath + "/" + path));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSize(FileEntry file) {
|
||||
protected byte[] getFileContents(String path, FileEntry file) throws IOException {
|
||||
var resource = manager.getResource(file.identifier).orElse(null);
|
||||
if (resource == null) return 0;
|
||||
|
||||
try (var stream = resource.open()) {
|
||||
int total = 0, read = 0;
|
||||
do {
|
||||
total += read;
|
||||
read = stream.read(TEMP_BUFFER);
|
||||
} while (read > 0);
|
||||
|
||||
return total;
|
||||
} catch (IOException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getContents(FileEntry file) throws IOException {
|
||||
var resource = manager.getResource(file.identifier).orElse(null);
|
||||
if (resource == null) throw new FileOperationException(file.path, NO_SUCH_FILE);
|
||||
if (resource == null) throw new FileOperationException(path, NO_SUCH_FILE);
|
||||
|
||||
try (var stream = resource.open()) {
|
||||
return stream.readAllBytes();
|
||||
}
|
||||
}
|
||||
|
||||
protected static class FileEntry extends ArchiveMount.FileEntry<FileEntry> {
|
||||
protected static final class FileEntry extends ArchiveMount.FileEntry<FileEntry> {
|
||||
final ResourceLocation identifier;
|
||||
|
||||
FileEntry(String path, ResourceLocation identifier) {
|
||||
super(path);
|
||||
FileEntry(ResourceLocation identifier) {
|
||||
this.identifier = identifier;
|
||||
}
|
||||
}
|
||||
|
@@ -4,7 +4,12 @@
|
||||
|
||||
package dan200.computercraft.shared.computer.menu;
|
||||
|
||||
import dan200.computercraft.shared.computer.upload.*;
|
||||
import dan200.computercraft.core.apis.handles.ByteBufferChannel;
|
||||
import dan200.computercraft.core.apis.transfer.TransferredFile;
|
||||
import dan200.computercraft.core.apis.transfer.TransferredFiles;
|
||||
import dan200.computercraft.shared.computer.upload.FileSlice;
|
||||
import dan200.computercraft.shared.computer.upload.FileUpload;
|
||||
import dan200.computercraft.shared.computer.upload.UploadResult;
|
||||
import dan200.computercraft.shared.network.client.UploadResultMessage;
|
||||
import dan200.computercraft.shared.platform.PlatformHelper;
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
@@ -18,7 +23,6 @@ import org.slf4j.LoggerFactory;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* The default concrete implementation of {@link ServerInputHandler}.
|
||||
@@ -150,8 +154,14 @@ public class ServerInputState<T extends AbstractContainerMenu & ComputerMenu> im
|
||||
}
|
||||
}
|
||||
|
||||
computer.queueEvent("file_transfer", new Object[]{
|
||||
new TransferredFiles(player, owner, toUpload.stream().map(x -> new TransferredFile(x.getName(), x.getBytes())).collect(Collectors.toList())),
|
||||
computer.queueEvent(TransferredFiles.EVENT, new Object[]{
|
||||
new TransferredFiles(
|
||||
toUpload.stream().map(x -> new TransferredFile(x.getName(), new ByteBufferChannel(x.getBytes()))).toList(),
|
||||
() -> {
|
||||
if (player.isAlive() && player.containerMenu == owner) {
|
||||
PlatformHelper.get().sendToPlayer(UploadResultMessage.consumed(owner), player);
|
||||
}
|
||||
}),
|
||||
});
|
||||
return UploadResultMessage.queued(owner);
|
||||
}
|
||||
|
@@ -46,12 +46,14 @@ public final class GlobalMetrics {
|
||||
* Add a new global metrics observer. This will receive metrics data for all computers.
|
||||
*
|
||||
* @param tracker The observer to add.
|
||||
* @return Whether the observer was added. {@code false} if the observer was already registered.
|
||||
*/
|
||||
public void addObserver(ComputerMetricsObserver tracker) {
|
||||
public boolean addObserver(ComputerMetricsObserver tracker) {
|
||||
synchronized (lock) {
|
||||
if (trackers.contains(tracker)) return;
|
||||
if (trackers.contains(tracker)) return false;
|
||||
trackers.add(tracker);
|
||||
enabled = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,11 +61,13 @@ public final class GlobalMetrics {
|
||||
* Remove a previously-registered global metrics observer.
|
||||
*
|
||||
* @param tracker The observer to add.
|
||||
* @return Whether the observer was removed. {@code false} if the observer was not registered.
|
||||
*/
|
||||
public void removeObserver(ComputerMetricsObserver tracker) {
|
||||
public boolean removeObserver(ComputerMetricsObserver tracker) {
|
||||
synchronized (lock) {
|
||||
trackers.remove(tracker);
|
||||
var changed = trackers.remove(tracker);
|
||||
enabled = !trackers.isEmpty();
|
||||
return changed;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -10,6 +10,7 @@ import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import dan200.computercraft.shared.computer.metrics.ComputerMetricsObserver;
|
||||
import dan200.computercraft.shared.computer.metrics.GlobalMetrics;
|
||||
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -21,29 +22,31 @@ import java.util.Map;
|
||||
*/
|
||||
public class BasicComputerMetricsObserver implements ComputerMetricsObserver {
|
||||
private final GlobalMetrics owner;
|
||||
private boolean tracking = false;
|
||||
|
||||
@GuardedBy("this")
|
||||
private final List<ComputerMetrics> timings = new ArrayList<>();
|
||||
|
||||
@GuardedBy("this")
|
||||
private final Map<ServerComputer, ComputerMetrics> timingLookup = new MapMaker().weakKeys().makeMap();
|
||||
|
||||
public BasicComputerMetricsObserver(GlobalMetrics owner) {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public synchronized void start() {
|
||||
if (!tracking) owner.addObserver(this);
|
||||
tracking = true;
|
||||
public void start() {
|
||||
if (!owner.addObserver(this)) return;
|
||||
|
||||
timings.clear();
|
||||
timingLookup.clear();
|
||||
synchronized (this) {
|
||||
timings.clear();
|
||||
timingLookup.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized boolean stop() {
|
||||
if (!tracking) return false;
|
||||
|
||||
owner.removeObserver(this);
|
||||
tracking = false;
|
||||
timingLookup.clear();
|
||||
public boolean stop() {
|
||||
if (!owner.removeObserver(this)) return false;
|
||||
synchronized (this) {
|
||||
timingLookup.clear();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -57,6 +60,7 @@ public class BasicComputerMetricsObserver implements ComputerMetricsObserver {
|
||||
return new ArrayList<>(timings);
|
||||
}
|
||||
|
||||
@GuardedBy("this")
|
||||
private ComputerMetrics getMetrics(ServerComputer computer) {
|
||||
var existing = timingLookup.get(computer);
|
||||
if (existing != null) return existing;
|
||||
|
@@ -5,31 +5,20 @@
|
||||
package dan200.computercraft.shared.computer.recipe;
|
||||
|
||||
import dan200.computercraft.shared.computer.items.IComputerItem;
|
||||
import net.minecraft.core.NonNullList;
|
||||
import dan200.computercraft.shared.recipe.CustomShapedRecipe;
|
||||
import dan200.computercraft.shared.recipe.ShapedRecipeSpec;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.inventory.CraftingContainer;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.crafting.CraftingBookCategory;
|
||||
import net.minecraft.world.item.crafting.Ingredient;
|
||||
import net.minecraft.world.item.crafting.ShapedRecipe;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
/**
|
||||
* Represents a recipe which converts a computer from one form into another.
|
||||
* A recipe which converts a computer from one form into another.
|
||||
*/
|
||||
public abstract class ComputerConvertRecipe extends ShapedRecipe {
|
||||
private final String group;
|
||||
private final ItemStack result;
|
||||
|
||||
public ComputerConvertRecipe(ResourceLocation identifier, String group, CraftingBookCategory category, int width, int height, NonNullList<Ingredient> ingredients, ItemStack result) {
|
||||
super(identifier, group, category, width, height, ingredients, result);
|
||||
this.group = group;
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public ItemStack getResultItem() {
|
||||
return result;
|
||||
public abstract class ComputerConvertRecipe extends CustomShapedRecipe {
|
||||
public ComputerConvertRecipe(ResourceLocation identifier, ShapedRecipeSpec recipe) {
|
||||
super(identifier, recipe);
|
||||
}
|
||||
|
||||
protected abstract ItemStack convert(IComputerItem item, ItemStack stack);
|
||||
@@ -55,9 +44,4 @@ public abstract class ComputerConvertRecipe extends ShapedRecipe {
|
||||
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getGroup() {
|
||||
return group;
|
||||
}
|
||||
}
|
||||
|
@@ -1,72 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2018 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.computer.recipe;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.util.RecipeUtil;
|
||||
import net.minecraft.core.NonNullList;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.crafting.CraftingBookCategory;
|
||||
import net.minecraft.world.item.crafting.Ingredient;
|
||||
import net.minecraft.world.item.crafting.RecipeSerializer;
|
||||
|
||||
public abstract class ComputerFamilyRecipe extends ComputerConvertRecipe {
|
||||
private final ComputerFamily family;
|
||||
|
||||
public ComputerFamilyRecipe(ResourceLocation identifier, String group, CraftingBookCategory category, int width, int height, NonNullList<Ingredient> ingredients, ItemStack result, ComputerFamily family) {
|
||||
super(identifier, group, category, width, height, ingredients, result);
|
||||
this.family = family;
|
||||
}
|
||||
|
||||
public ComputerFamily getFamily() {
|
||||
return family;
|
||||
}
|
||||
|
||||
public abstract static class Serializer<T extends ComputerFamilyRecipe> implements RecipeSerializer<T> {
|
||||
protected abstract T create(ResourceLocation identifier, String group, CraftingBookCategory category, int width, int height, NonNullList<Ingredient> ingredients, ItemStack result, ComputerFamily family);
|
||||
|
||||
@Override
|
||||
public T fromJson(ResourceLocation identifier, JsonObject json) {
|
||||
var group = GsonHelper.getAsString(json, "group", "");
|
||||
var category = CraftingBookCategory.CODEC.byName(GsonHelper.getAsString(json, "category", null), CraftingBookCategory.MISC);
|
||||
var family = RecipeUtil.getFamily(json, "family");
|
||||
|
||||
var template = RecipeUtil.getTemplate(json);
|
||||
var result = itemStackFromJson(GsonHelper.getAsJsonObject(json, "result"));
|
||||
|
||||
return create(identifier, group, category, template.width(), template.height(), template.ingredients(), result, family);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T fromNetwork(ResourceLocation identifier, FriendlyByteBuf buf) {
|
||||
var width = buf.readVarInt();
|
||||
var height = buf.readVarInt();
|
||||
var group = buf.readUtf();
|
||||
var category = buf.readEnum(CraftingBookCategory.class);
|
||||
|
||||
var ingredients = NonNullList.withSize(width * height, Ingredient.EMPTY);
|
||||
for (var i = 0; i < ingredients.size(); i++) ingredients.set(i, Ingredient.fromNetwork(buf));
|
||||
|
||||
var result = buf.readItem();
|
||||
var family = buf.readEnum(ComputerFamily.class);
|
||||
return create(identifier, group, category, width, height, ingredients, result, family);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toNetwork(FriendlyByteBuf buf, T recipe) {
|
||||
buf.writeVarInt(recipe.getWidth());
|
||||
buf.writeVarInt(recipe.getHeight());
|
||||
buf.writeUtf(recipe.getGroup());
|
||||
buf.writeEnum(recipe.category());
|
||||
for (var ingredient : recipe.getIngredients()) ingredient.toNetwork(buf);
|
||||
buf.writeItem(recipe.getResultItem());
|
||||
buf.writeEnum(recipe.getFamily());
|
||||
}
|
||||
}
|
||||
}
|
@@ -4,35 +4,57 @@
|
||||
|
||||
package dan200.computercraft.shared.computer.recipe;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.items.IComputerItem;
|
||||
import net.minecraft.core.NonNullList;
|
||||
import dan200.computercraft.shared.recipe.ShapedRecipeSpec;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.crafting.CraftingBookCategory;
|
||||
import net.minecraft.world.item.crafting.Ingredient;
|
||||
import net.minecraft.world.item.crafting.RecipeSerializer;
|
||||
|
||||
public final class ComputerUpgradeRecipe extends ComputerFamilyRecipe {
|
||||
private ComputerUpgradeRecipe(ResourceLocation identifier, String group, CraftingBookCategory category, int width, int height, NonNullList<Ingredient> ingredients, ItemStack result, ComputerFamily family) {
|
||||
super(identifier, group, category, width, height, ingredients, result, family);
|
||||
/**
|
||||
* A recipe which "upgrades" a {@linkplain IComputerItem computer}, converting it from one {@linkplain ComputerFamily
|
||||
* family} to another.
|
||||
*/
|
||||
public final class ComputerUpgradeRecipe extends ComputerConvertRecipe {
|
||||
private final ComputerFamily family;
|
||||
|
||||
private ComputerUpgradeRecipe(ResourceLocation identifier, ShapedRecipeSpec recipe, ComputerFamily family) {
|
||||
super(identifier, recipe);
|
||||
this.family = family;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ItemStack convert(IComputerItem item, ItemStack stack) {
|
||||
return item.withFamily(stack, getFamily());
|
||||
return item.withFamily(stack, family);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecipeSerializer<?> getSerializer() {
|
||||
public RecipeSerializer<ComputerUpgradeRecipe> getSerializer() {
|
||||
return ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get();
|
||||
}
|
||||
|
||||
public static class Serializer extends ComputerFamilyRecipe.Serializer<ComputerUpgradeRecipe> {
|
||||
public static class Serializer implements RecipeSerializer<ComputerUpgradeRecipe> {
|
||||
@Override
|
||||
protected ComputerUpgradeRecipe create(ResourceLocation identifier, String group, CraftingBookCategory category, int width, int height, NonNullList<Ingredient> ingredients, ItemStack result, ComputerFamily family) {
|
||||
return new ComputerUpgradeRecipe(identifier, group, category, width, height, ingredients, result, family);
|
||||
public ComputerUpgradeRecipe fromJson(ResourceLocation identifier, JsonObject json) {
|
||||
var recipe = ShapedRecipeSpec.fromJson(json);
|
||||
var family = ComputerFamily.getFamily(json, "family");
|
||||
return new ComputerUpgradeRecipe(identifier, recipe, family);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComputerUpgradeRecipe fromNetwork(ResourceLocation identifier, FriendlyByteBuf buf) {
|
||||
var recipe = ShapedRecipeSpec.fromNetwork(buf);
|
||||
var family = buf.readEnum(ComputerFamily.class);
|
||||
return new ComputerUpgradeRecipe(identifier, recipe, family);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toNetwork(FriendlyByteBuf buf, ComputerUpgradeRecipe recipe) {
|
||||
recipe.toSpec().toNetwork(buf);
|
||||
buf.writeEnum(recipe.family);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -30,7 +30,6 @@ public final class ConfigSpec {
|
||||
public static final ConfigFile.Value<Integer> computerSpaceLimit;
|
||||
public static final ConfigFile.Value<Integer> floppySpaceLimit;
|
||||
public static final ConfigFile.Value<Integer> maximumFilesOpen;
|
||||
public static final ConfigFile.Value<Boolean> disableLua51Features;
|
||||
public static final ConfigFile.Value<String> defaultComputerSettings;
|
||||
public static final ConfigFile.Value<Boolean> logComputerErrors;
|
||||
public static final ConfigFile.Value<Boolean> commandRequireCreative;
|
||||
@@ -115,12 +114,6 @@ public final class ConfigSpec {
|
||||
.comment("Set how many files a computer can have open at the same time. Set to 0 for unlimited.")
|
||||
.defineInRange("maximum_open_files", CoreConfig.maximumFilesOpen, 0, Integer.MAX_VALUE);
|
||||
|
||||
disableLua51Features = builder
|
||||
.comment("""
|
||||
Set this to true to disable Lua 5.1 functions that will be removed in a future
|
||||
update. Useful for ensuring forward compatibility of your programs now.""")
|
||||
.define("disable_lua51_features", CoreConfig.disableLua51Features);
|
||||
|
||||
defaultComputerSettings = builder
|
||||
.comment("""
|
||||
A comma separated list of default system settings to set on new computers.
|
||||
@@ -395,7 +388,6 @@ public final class ConfigSpec {
|
||||
Config.floppySpaceLimit = floppySpaceLimit.get();
|
||||
Config.uploadMaxSize = uploadMaxSize.get();
|
||||
CoreConfig.maximumFilesOpen = maximumFilesOpen.get();
|
||||
CoreConfig.disableLua51Features = disableLua51Features.get();
|
||||
CoreConfig.defaultComputerSettings = defaultComputerSettings.get();
|
||||
Config.commandRequireCreative = commandRequireCreative.get();
|
||||
|
||||
|
@@ -12,7 +12,6 @@ import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
|
||||
import net.minecraft.world.level.storage.loot.predicates.LootItemCondition;
|
||||
import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -33,7 +32,7 @@ public final class BlockNamedEntityLootCondition implements LootItemCondition {
|
||||
|
||||
@Override
|
||||
public Set<LootContextParam<?>> getReferencedContextParams() {
|
||||
return Collections.singleton(LootContextParams.BLOCK_ENTITY);
|
||||
return Set.of(LootContextParams.BLOCK_ENTITY);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -12,7 +12,6 @@ import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
|
||||
import net.minecraft.world.level.storage.loot.predicates.LootItemCondition;
|
||||
import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -33,7 +32,7 @@ public final class HasComputerIdLootCondition implements LootItemCondition {
|
||||
|
||||
@Override
|
||||
public Set<LootContextParam<?>> getReferencedContextParams() {
|
||||
return Collections.singleton(LootContextParams.BLOCK_ENTITY);
|
||||
return Set.of(LootContextParams.BLOCK_ENTITY);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -12,7 +12,6 @@ import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
|
||||
import net.minecraft.world.level.storage.loot.predicates.LootItemCondition;
|
||||
import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -33,7 +32,7 @@ public final class PlayerCreativeLootCondition implements LootItemCondition {
|
||||
|
||||
@Override
|
||||
public Set<LootContextParam<?>> getReferencedContextParams() {
|
||||
return Collections.singleton(LootContextParams.THIS_ENTITY);
|
||||
return Set.of(LootContextParams.THIS_ENTITY);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -61,7 +61,7 @@ public class ItemDetails {
|
||||
|
||||
/*
|
||||
* Used to hide some data from ItemStack tooltip.
|
||||
* @see https://minecraft.gamepedia.com/Tutorials/Command_NBT_tags
|
||||
* @see https://minecraft.wiki/w/Tutorials/Command_NBT_tags
|
||||
* @see ItemStack#getTooltip
|
||||
*/
|
||||
var hideFlags = tag != null ? tag.getInt("HideFlags") : 0;
|
||||
|
@@ -9,14 +9,12 @@ import dan200.computercraft.api.upgrades.UpgradeData;
|
||||
import dan200.computercraft.impl.PocketUpgrades;
|
||||
import dan200.computercraft.impl.TurtleUpgrades;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
||||
import dan200.computercraft.shared.turtle.items.TurtleItem;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@@ -24,7 +22,6 @@ import java.util.function.Supplier;
|
||||
* Utilities for recipe mod plugins (such as JEI).
|
||||
*/
|
||||
public final class RecipeModHelpers {
|
||||
static final List<ComputerFamily> MAIN_FAMILIES = Arrays.asList(ComputerFamily.NORMAL, ComputerFamily.ADVANCED);
|
||||
static final List<Supplier<TurtleItem>> TURTLES = List.of(ModRegistry.Items.TURTLE_NORMAL, ModRegistry.Items.TURTLE_ADVANCED);
|
||||
static final List<Supplier<PocketComputerItem>> POCKET_COMPUTERS = List.of(ModRegistry.Items.POCKET_COMPUTER_NORMAL, ModRegistry.Items.POCKET_COMPUTER_ADVANCED);
|
||||
|
||||
|
@@ -114,7 +114,7 @@ public class UpgradeRecipeGenerator<T> {
|
||||
// Suggest possible upgrades which can be applied to this turtle
|
||||
var left = item.getUpgradeWithData(stack, TurtleSide.LEFT);
|
||||
var right = item.getUpgradeWithData(stack, TurtleSide.RIGHT);
|
||||
if (left != null && right != null) return Collections.emptyList();
|
||||
if (left != null && right != null) return List.of();
|
||||
|
||||
List<T> recipes = new ArrayList<>();
|
||||
var ingredient = Ingredient.of(stack);
|
||||
@@ -135,7 +135,7 @@ public class UpgradeRecipeGenerator<T> {
|
||||
} else if (stack.getItem() instanceof PocketComputerItem) {
|
||||
// Suggest possible upgrades which can be applied to this turtle
|
||||
var back = PocketComputerItem.getUpgrade(stack);
|
||||
if (back != null) return Collections.emptyList();
|
||||
if (back != null) return List.of();
|
||||
|
||||
List<T> recipes = new ArrayList<>();
|
||||
var ingredient = Ingredient.of(stack);
|
||||
@@ -148,7 +148,7 @@ public class UpgradeRecipeGenerator<T> {
|
||||
} else {
|
||||
// If this item is usable as an upgrade, find all possible recipes.
|
||||
var upgrades = upgradeItemLookup.get(stack.getItem());
|
||||
if (upgrades == null) return Collections.emptyList();
|
||||
if (upgrades == null) return List.of();
|
||||
|
||||
List<T> recipes = null;
|
||||
var multiple = false;
|
||||
@@ -169,7 +169,7 @@ public class UpgradeRecipeGenerator<T> {
|
||||
}
|
||||
}
|
||||
|
||||
return recipes == null ? Collections.emptyList() : Collections.unmodifiableList(recipes);
|
||||
return recipes == null ? List.of() : Collections.unmodifiableList(recipes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,7 +215,7 @@ public class UpgradeRecipeGenerator<T> {
|
||||
|
||||
return Collections.unmodifiableList(recipes);
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -22,7 +22,7 @@ import mezz.jei.api.runtime.IJeiRuntime;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@JeiPlugin
|
||||
public class JEIComputerCraft implements IModPlugin {
|
||||
@@ -61,7 +61,7 @@ public class JEIComputerCraft implements IModPlugin {
|
||||
var category = registry.createRecipeLookup(RecipeTypes.CRAFTING);
|
||||
category.get().forEach(wrapper -> {
|
||||
if (RecipeModHelpers.shouldRemoveRecipe(wrapper.getId())) {
|
||||
registry.hideRecipes(RecipeTypes.CRAFTING, Collections.singleton(wrapper));
|
||||
registry.hideRecipes(RecipeTypes.CRAFTING, List.of(wrapper));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@@ -15,7 +15,6 @@ import mezz.jei.api.recipe.category.IRecipeCategory;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.crafting.CraftingRecipe;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
class RecipeResolver implements IRecipeManagerPlugin {
|
||||
@@ -24,36 +23,36 @@ class RecipeResolver implements IRecipeManagerPlugin {
|
||||
@Override
|
||||
public <V> List<RecipeType<?>> getRecipeTypes(IFocus<V> focus) {
|
||||
var value = focus.getTypedValue().getIngredient();
|
||||
if (!(value instanceof ItemStack stack)) return Collections.emptyList();
|
||||
if (!(value instanceof ItemStack stack)) return List.of();
|
||||
|
||||
return switch (focus.getRole()) {
|
||||
case INPUT ->
|
||||
stack.getItem() instanceof TurtleItem || stack.getItem() instanceof PocketComputerItem || resolver.isUpgrade(stack)
|
||||
? Collections.singletonList(RecipeTypes.CRAFTING)
|
||||
: Collections.emptyList();
|
||||
? List.of(RecipeTypes.CRAFTING)
|
||||
: List.of();
|
||||
case OUTPUT -> stack.getItem() instanceof TurtleItem || stack.getItem() instanceof PocketComputerItem
|
||||
? Collections.singletonList(RecipeTypes.CRAFTING)
|
||||
: Collections.emptyList();
|
||||
default -> Collections.emptyList();
|
||||
? List.of(RecipeTypes.CRAFTING)
|
||||
: List.of();
|
||||
default -> List.of();
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T, V> List<T> getRecipes(IRecipeCategory<T> recipeCategory, IFocus<V> focus) {
|
||||
if (!(focus.getTypedValue().getIngredient() instanceof ItemStack stack) || recipeCategory.getRecipeType() != RecipeTypes.CRAFTING) {
|
||||
return Collections.emptyList();
|
||||
return List.of();
|
||||
}
|
||||
|
||||
return switch (focus.getRole()) {
|
||||
case INPUT -> cast(resolver.findRecipesWithInput(stack));
|
||||
case OUTPUT -> cast(resolver.findRecipesWithOutput(stack));
|
||||
default -> Collections.emptyList();
|
||||
default -> List.of();
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<T> getRecipes(IRecipeCategory<T> recipeCategory) {
|
||||
return Collections.emptyList();
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
|
@@ -0,0 +1,36 @@
|
||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.peripheral.generic;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Extract some component (for instance a capability on Forge, or a {@code BlockApiLookup} on Fabric) from a block and
|
||||
* block entity.
|
||||
*
|
||||
* @param <C> A platform-specific type, used for the invalidation callback.
|
||||
*/
|
||||
public interface ComponentLookup<C extends Runnable> {
|
||||
/**
|
||||
* Extract some component from a block in the world.
|
||||
*
|
||||
* @param level The current level.
|
||||
* @param pos The position of the block in the level.
|
||||
* @param state The block state at that position.
|
||||
* @param blockEntity The block entity at that position.
|
||||
* @param side The side of the block to extract the component from. Implementations should try to use a
|
||||
* sideless lookup first, but may fall back to a sided lookup if needed.
|
||||
* @param invalidate An invalidation function to call if this component changes.
|
||||
* @return The found component, or {@code null} if not present.
|
||||
*/
|
||||
@Nullable
|
||||
Object find(Level level, BlockPos pos, BlockState state, BlockEntity blockEntity, Direction side, C invalidate);
|
||||
}
|
@@ -6,12 +6,9 @@ package dan200.computercraft.shared.peripheral.generic;
|
||||
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.api.peripheral.PeripheralType;
|
||||
import dan200.computercraft.core.methods.MethodSupplier;
|
||||
import dan200.computercraft.core.methods.NamedMethod;
|
||||
import dan200.computercraft.core.methods.PeripheralMethod;
|
||||
import dan200.computercraft.shared.computer.core.ServerContext;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
@@ -28,16 +25,10 @@ import java.util.Set;
|
||||
* See the platform-specific peripheral providers for the usage of this.
|
||||
*/
|
||||
final class GenericPeripheralBuilder {
|
||||
private final MethodSupplier<PeripheralMethod> peripheralMethods;
|
||||
|
||||
private @Nullable String name;
|
||||
private final Set<String> additionalTypes = new HashSet<>(0);
|
||||
private final ArrayList<SaturatedMethod> methods = new ArrayList<>();
|
||||
|
||||
GenericPeripheralBuilder(MinecraftServer server) {
|
||||
peripheralMethods = ServerContext.get(server).peripheralMethods();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
IPeripheral toPeripheral(BlockEntity blockEntity, Direction side) {
|
||||
if (methods.isEmpty()) return null;
|
||||
@@ -46,18 +37,16 @@ final class GenericPeripheralBuilder {
|
||||
return new GenericPeripheral(blockEntity, side, name, additionalTypes, methods);
|
||||
}
|
||||
|
||||
boolean addMethods(Object target) {
|
||||
return peripheralMethods.forEachSelfMethod(target, (name, method, info) -> {
|
||||
methods.add(new SaturatedMethod(target, name, method));
|
||||
void addMethod(Object target, String name, PeripheralMethod method, @Nullable NamedMethod<PeripheralMethod> info) {
|
||||
methods.add(new SaturatedMethod(target, name, method));
|
||||
|
||||
// If we have a peripheral type, use it. Always pick the smallest one, so it's consistent (assuming mods
|
||||
// don't change).
|
||||
var type = info == null ? null : info.genericType();
|
||||
if (type != null && type.getPrimaryType() != null) {
|
||||
var primaryType = type.getPrimaryType();
|
||||
if (this.name == null || this.name.compareTo(primaryType) > 0) this.name = primaryType;
|
||||
}
|
||||
if (type != null) additionalTypes.addAll(type.getAdditionalTypes());
|
||||
});
|
||||
// If we have a peripheral type, use it. Always pick the smallest one, so it's consistent (assuming mods
|
||||
// don't change).
|
||||
var type = info == null ? null : info.genericType();
|
||||
if (type != null && type.getPrimaryType() != null) {
|
||||
var primaryType = type.getPrimaryType();
|
||||
if (this.name == null || this.name.compareTo(primaryType) > 0) this.name = primaryType;
|
||||
}
|
||||
if (type != null) additionalTypes.addAll(type.getAdditionalTypes());
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,69 @@
|
||||
// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.peripheral.generic;
|
||||
|
||||
import dan200.computercraft.api.lua.GenericSource;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.core.methods.MethodSupplier;
|
||||
import dan200.computercraft.core.methods.PeripheralMethod;
|
||||
import dan200.computercraft.shared.computer.core.ServerContext;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A peripheral provider which finds methods from various {@linkplain GenericSource generic sources}.
|
||||
* <p>
|
||||
* Methods are found using the original block entity itself and a registered list of {@link ComponentLookup}s.
|
||||
*
|
||||
* @param <C> A platform-specific type, used for the invalidation callback.
|
||||
*/
|
||||
public final class GenericPeripheralProvider<C extends Runnable> {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(GenericPeripheralProvider.class);
|
||||
|
||||
private final List<ComponentLookup<? super C>> lookups = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Register a component lookup function.
|
||||
*
|
||||
* @param lookup The component lookup function.
|
||||
*/
|
||||
public synchronized void registerLookup(ComponentLookup<? super C> lookup) {
|
||||
Objects.requireNonNull(lookup);
|
||||
if (!lookups.contains(lookup)) lookups.add(lookup);
|
||||
}
|
||||
|
||||
public void forEachMethod(MethodSupplier<PeripheralMethod> methods, Level level, BlockPos pos, Direction side, BlockEntity blockEntity, C invalidate, MethodSupplier.TargetedConsumer<PeripheralMethod> consumer) {
|
||||
methods.forEachMethod(blockEntity, consumer);
|
||||
|
||||
for (var lookup : lookups) {
|
||||
var contents = lookup.find(level, pos, blockEntity.getBlockState(), blockEntity, side, invalidate);
|
||||
if (contents != null) methods.forEachMethod(contents, consumer);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public IPeripheral getPeripheral(Level level, BlockPos pos, Direction side, @Nullable BlockEntity blockEntity, C invalidate) {
|
||||
if (blockEntity == null) return null;
|
||||
|
||||
var server = level.getServer();
|
||||
if (server == null) {
|
||||
LOG.warn("Fetching peripherals on a non-server level {}.", level, new IllegalStateException("Fetching peripherals on a non-server level."));
|
||||
return null;
|
||||
}
|
||||
|
||||
var builder = new GenericPeripheralBuilder();
|
||||
forEachMethod(ServerContext.get(server).peripheralMethods(), level, pos, side, blockEntity, invalidate, builder::addMethod);
|
||||
return builder.toPeripheral(blockEntity, side);
|
||||
}
|
||||
}
|
@@ -0,0 +1,53 @@
|
||||
// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.peripheral.generic.methods;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.api.peripheral.GenericPeripheral;
|
||||
import dan200.computercraft.api.peripheral.PeripheralType;
|
||||
|
||||
/**
|
||||
* Methods for interacting with blocks which store energy.
|
||||
* <p>
|
||||
* This works with energy storage blocks, as well as generators and machines which consume energy.
|
||||
* <p>
|
||||
* > [!NOTE]
|
||||
* > Due to limitations with Forge's energy API, it is not possible to measure throughput (i.e. FE used/generated per
|
||||
* > tick).
|
||||
*
|
||||
* @param <T> The type for energy storage.
|
||||
* @cc.module energy_storage
|
||||
* @cc.since 1.94.0
|
||||
*/
|
||||
public abstract class AbstractEnergyMethods<T> implements GenericPeripheral {
|
||||
@Override
|
||||
public final PeripheralType getType() {
|
||||
return PeripheralType.ofAdditional("energy_storage");
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String id() {
|
||||
return ComputerCraftAPI.MOD_ID + ":energy";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the energy of this block.
|
||||
*
|
||||
* @param energy The current energy storage.
|
||||
* @return The energy stored in this block, in FE.
|
||||
*/
|
||||
@LuaFunction(mainThread = true)
|
||||
public abstract int getEnergy(T energy);
|
||||
|
||||
/**
|
||||
* Get the maximum amount of energy this block can store.
|
||||
*
|
||||
* @param energy The current energy storage.
|
||||
* @return The energy capacity of this block.
|
||||
*/
|
||||
@LuaFunction(mainThread = true)
|
||||
public abstract int getEnergyCapacity(T energy);
|
||||
}
|
@@ -0,0 +1,92 @@
|
||||
// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.peripheral.generic.methods;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.api.peripheral.GenericPeripheral;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.api.peripheral.PeripheralType;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Methods for interacting with tanks and other fluid storage blocks.
|
||||
*
|
||||
* @param <T> The type for fluid inventories.
|
||||
* @cc.module fluid_storage
|
||||
* @cc.since 1.94.0
|
||||
*/
|
||||
public abstract class AbstractFluidMethods<T> implements GenericPeripheral {
|
||||
@Override
|
||||
public final PeripheralType getType() {
|
||||
return PeripheralType.ofAdditional("fluid_storage");
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String id() {
|
||||
return ComputerCraftAPI.MOD_ID + ":fluid";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all "tanks" in this fluid storage.
|
||||
* <p>
|
||||
* Each tank either contains some amount of fluid or is empty. Tanks with fluids inside will return some basic
|
||||
* information about the fluid, including its name and amount.
|
||||
* <p>
|
||||
* The returned table is sparse, and so empty tanks will be `nil` - it is recommended to loop over using [`pairs`]
|
||||
* rather than [`ipairs`].
|
||||
*
|
||||
* @param fluids The current fluid handler.
|
||||
* @return All tanks.
|
||||
* @cc.treturn { (table|nil)... } All tanks in this fluid storage.
|
||||
*/
|
||||
@LuaFunction(mainThread = true)
|
||||
public abstract Map<Integer, Map<String, ?>> tanks(T fluids);
|
||||
|
||||
/**
|
||||
* Move a fluid from one fluid container to another connected one.
|
||||
* <p>
|
||||
* This allows you to pull fluid in the current fluid container to another container <em>on the same wired
|
||||
* network</em>. Both containers must attached to wired modems which are connected via a cable.
|
||||
*
|
||||
* @param from Container to move fluid from.
|
||||
* @param computer The current computer.
|
||||
* @param toName The name of the peripheral/container to push to. This is the string given to [`peripheral.wrap`],
|
||||
* and displayed by the wired modem.
|
||||
* @param limit The maximum amount of fluid to move.
|
||||
* @param fluidName The fluid to move. If not given, an arbitrary fluid will be chosen.
|
||||
* @return The amount of moved fluid.
|
||||
* @throws LuaException If the peripheral to transfer to doesn't exist or isn't an fluid container.
|
||||
* @cc.see peripheral.getName Allows you to get the name of a [wrapped][`peripheral.wrap`] peripheral.
|
||||
*/
|
||||
@LuaFunction(mainThread = true)
|
||||
public abstract int pushFluid(
|
||||
T from, IComputerAccess computer, String toName, Optional<Integer> limit, Optional<String> fluidName
|
||||
) throws LuaException;
|
||||
|
||||
/**
|
||||
* Move a fluid from a connected fluid container into this oneone.
|
||||
* <p>
|
||||
* This allows you to pull fluid in the current fluid container from another container <em>on the same wired
|
||||
* network</em>. Both containers must attached to wired modems which are connected via a cable.
|
||||
*
|
||||
* @param to Container to move fluid to.
|
||||
* @param computer The current computer.
|
||||
* @param fromName The name of the peripheral/container to push to. This is the string given to [`peripheral.wrap`],
|
||||
* and displayed by the wired modem.
|
||||
* @param limit The maximum amount of fluid to move.
|
||||
* @param fluidName The fluid to move. If not given, an arbitrary fluid will be chosen.
|
||||
* @return The amount of moved fluid.
|
||||
* @throws LuaException If the peripheral to transfer to doesn't exist or isn't an fluid container.
|
||||
* @cc.see peripheral.getName Allows you to get the name of a [wrapped][`peripheral.wrap`] peripheral.
|
||||
*/
|
||||
@LuaFunction(mainThread = true)
|
||||
public abstract int pullFluid(
|
||||
T to, IComputerAccess computer, String fromName, Optional<Integer> limit, Optional<String> fluidName
|
||||
) throws LuaException;
|
||||
}
|
@@ -0,0 +1,197 @@
|
||||
// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: LicenseRef-CCPL
|
||||
|
||||
package dan200.computercraft.shared.peripheral.generic.methods;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.api.peripheral.GenericPeripheral;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.api.peripheral.PeripheralType;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Methods for interacting with inventories.
|
||||
*
|
||||
* @param <T> The type for inventories.
|
||||
* @cc.module inventory
|
||||
* @cc.since 1.94.0
|
||||
*/
|
||||
public abstract class AbstractInventoryMethods<T> implements GenericPeripheral {
|
||||
@Override
|
||||
public final PeripheralType getType() {
|
||||
return PeripheralType.ofAdditional("inventory");
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String id() {
|
||||
return ComputerCraftAPI.MOD_ID + ":inventory";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of this inventory.
|
||||
*
|
||||
* @param inventory The current inventory.
|
||||
* @return The number of slots in this inventory.
|
||||
*/
|
||||
@LuaFunction(mainThread = true)
|
||||
public abstract int size(T inventory);
|
||||
|
||||
/**
|
||||
* List all items in this inventory. This returns a table, with an entry for each slot.
|
||||
* <p>
|
||||
* Each item in the inventory is represented by a table containing some basic information, much like
|
||||
* {@link dan200.computercraft.shared.turtle.apis.TurtleAPI#getItemDetail(ILuaContext, Optional, Optional)}
|
||||
* includes. More information can be fetched with {@link #getItemDetail}. The table contains the item `name`, the
|
||||
* `count` and an a (potentially nil) hash of the item's `nbt.` This NBT data doesn't contain anything useful, but
|
||||
* allows you to distinguish identical items.
|
||||
* <p>
|
||||
* The returned table is sparse, and so empty slots will be `nil` - it is recommended to loop over using [`pairs`]
|
||||
* rather than [`ipairs`].
|
||||
*
|
||||
* @param inventory The current inventory.
|
||||
* @return All items in this inventory.
|
||||
* @cc.treturn { (table|nil)... } All items in this inventory.
|
||||
* @cc.usage Find an adjacent chest and print all items in it.
|
||||
*
|
||||
* <pre>{@code
|
||||
* local chest = peripheral.find("minecraft:chest")
|
||||
* for slot, item in pairs(chest.list()) do
|
||||
* print(("%d x %s in slot %d"):format(item.count, item.name, slot))
|
||||
* end
|
||||
* }</pre>
|
||||
*/
|
||||
@LuaFunction(mainThread = true)
|
||||
public abstract Map<Integer, Map<String, ?>> list(T inventory);
|
||||
|
||||
/**
|
||||
* Get detailed information about an item.
|
||||
* <p>
|
||||
* The returned information contains the same information as each item in
|
||||
* {@link #list}, as well as additional details like the display name
|
||||
* (`displayName`), and item and item durability (`damage`, `maxDamage`, `durability`).
|
||||
* <p>
|
||||
* Some items include more information (such as enchantments) - it is
|
||||
* recommended to print it out using [`textutils.serialize`] or in the Lua
|
||||
* REPL, to explore what is available.
|
||||
* <p>
|
||||
* > [Deprecated fields][!INFO]
|
||||
* > Older versions of CC: Tweaked exposed an {@code itemGroups} field, listing the
|
||||
* > creative tabs an item was available under. This information is no longer available on
|
||||
* > more recent versions of the game, and so this field will always be empty. Do not use this
|
||||
* > field in new code!
|
||||
*
|
||||
* @param inventory The current inventory.
|
||||
* @param slot The slot to get information about.
|
||||
* @return Information about the item in this slot, or {@code nil} if not present.
|
||||
* @throws LuaException If the slot is out of range.
|
||||
* @cc.treturn table Information about the item in this slot, or {@code nil} if not present.
|
||||
* @cc.usage Print some information about the first in a chest.
|
||||
*
|
||||
* <pre>{@code
|
||||
* local chest = peripheral.find("minecraft:chest")
|
||||
* local item = chest.getItemDetail(1)
|
||||
* if not item then print("No item") return end
|
||||
*
|
||||
* print(("%s (%s)"):format(item.displayName, item.name))
|
||||
* print(("Count: %d/%d"):format(item.count, item.maxCount))
|
||||
*
|
||||
* if item.damage then
|
||||
* print(("Damage: %d/%d"):format(item.damage, item.maxDamage))
|
||||
* end
|
||||
* }</pre>
|
||||
*/
|
||||
@Nullable
|
||||
@LuaFunction(mainThread = true)
|
||||
public abstract Map<String, ?> getItemDetail(T inventory, int slot) throws LuaException;
|
||||
|
||||
/**
|
||||
* Get the maximum number of items which can be stored in this slot.
|
||||
* <p>
|
||||
* Typically this will be limited to 64 items. However, some inventories (such as barrels or caches) can store
|
||||
* hundreds or thousands of items in one slot.
|
||||
*
|
||||
* @param inventory Inventory to probe.
|
||||
* @param slot The slot
|
||||
* @return The maximum number of items in this slot.
|
||||
* @throws LuaException If the slot is out of range.
|
||||
* @cc.usage Count the maximum number of items an adjacent chest can hold.
|
||||
* <pre>{@code
|
||||
* local chest = peripheral.find("minecraft:chest")
|
||||
* local total = 0
|
||||
* for i = 1, chest.size() do
|
||||
* total = total + chest.getItemLimit(i)
|
||||
* end
|
||||
* print(total)
|
||||
* }</pre>
|
||||
* @cc.since 1.96.0
|
||||
*/
|
||||
@LuaFunction(mainThread = true)
|
||||
public abstract long getItemLimit(T inventory, int slot) throws LuaException;
|
||||
|
||||
/**
|
||||
* Push items from one inventory to another connected one.
|
||||
* <p>
|
||||
* This allows you to push an item in an inventory to another inventory <em>on the same wired network</em>. Both
|
||||
* inventories must attached to wired modems which are connected via a cable.
|
||||
*
|
||||
* @param from Inventory to move items from.
|
||||
* @param computer The current computer.
|
||||
* @param toName The name of the peripheral/inventory to push to. This is the string given to [`peripheral.wrap`],
|
||||
* and displayed by the wired modem.
|
||||
* @param fromSlot The slot in the current inventory to move items to.
|
||||
* @param limit The maximum number of items to move. Defaults to the current stack limit.
|
||||
* @param toSlot The slot in the target inventory to move to. If not given, the item will be inserted into any slot.
|
||||
* @return The number of transferred items.
|
||||
* @throws LuaException If the peripheral to transfer to doesn't exist or isn't an inventory.
|
||||
* @throws LuaException If either source or destination slot is out of range.
|
||||
* @cc.see peripheral.getName Allows you to get the name of a [wrapped][`peripheral.wrap`] peripheral.
|
||||
* @cc.usage Wrap two chests, and push an item from one to another.
|
||||
* <pre>{@code
|
||||
* local chest_a = peripheral.wrap("minecraft:chest_0")
|
||||
* local chest_b = peripheral.wrap("minecraft:chest_1")
|
||||
*
|
||||
* chest_a.pushItems(peripheral.getName(chest_b), 1)
|
||||
* }</pre>
|
||||
*/
|
||||
@LuaFunction(mainThread = true)
|
||||
public abstract int pushItems(
|
||||
T from, IComputerAccess computer, String toName, int fromSlot, Optional<Integer> limit, Optional<Integer> toSlot
|
||||
) throws LuaException;
|
||||
|
||||
/**
|
||||
* Pull items from a connected inventory into this one.
|
||||
* <p>
|
||||
* This allows you to transfer items between inventories <em>on the same wired network</em>. Both this and the source
|
||||
* inventory must attached to wired modems which are connected via a cable.
|
||||
*
|
||||
* @param to Inventory to move items to.
|
||||
* @param computer The current computer.
|
||||
* @param fromName The name of the peripheral/inventory to pull from. This is the string given to [`peripheral.wrap`],
|
||||
* and displayed by the wired modem.
|
||||
* @param fromSlot The slot in the source inventory to move items from.
|
||||
* @param limit The maximum number of items to move. Defaults to the current stack limit.
|
||||
* @param toSlot The slot in current inventory to move to. If not given, the item will be inserted into any slot.
|
||||
* @return The number of transferred items.
|
||||
* @throws LuaException If the peripheral to transfer to doesn't exist or isn't an inventory.
|
||||
* @throws LuaException If either source or destination slot is out of range.
|
||||
* @cc.see peripheral.getName Allows you to get the name of a [wrapped][`peripheral.wrap`] peripheral.
|
||||
* @cc.usage Wrap two chests, and push an item from one to another.
|
||||
* <pre>{@code
|
||||
* local chest_a = peripheral.wrap("minecraft:chest_0")
|
||||
* local chest_b = peripheral.wrap("minecraft:chest_1")
|
||||
*
|
||||
* chest_a.pullItems(peripheral.getName(chest_b), 1)
|
||||
* }</pre>
|
||||
*/
|
||||
@LuaFunction(mainThread = true)
|
||||
public abstract int pullItems(
|
||||
T to, IComputerAccess computer, String fromName, int fromSlot, Optional<Integer> limit, Optional<Integer> toSlot
|
||||
) throws LuaException;
|
||||
}
|
@@ -4,12 +4,12 @@
|
||||
|
||||
package dan200.computercraft.shared.peripheral.modem.wired;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import dan200.computercraft.annotations.ForgeOverride;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.platform.PlatformHelper;
|
||||
import dan200.computercraft.shared.util.WaterloggableHelpers;
|
||||
import dan200.computercraft.shared.util.WorldUtil;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
@@ -52,12 +52,14 @@ public class CableBlock extends Block implements SimpleWaterloggedBlock, EntityB
|
||||
public static final BooleanProperty UP = BooleanProperty.create("up");
|
||||
public static final BooleanProperty DOWN = BooleanProperty.create("down");
|
||||
|
||||
static final EnumMap<Direction, BooleanProperty> CONNECTIONS =
|
||||
new EnumMap<>(new ImmutableMap.Builder<Direction, BooleanProperty>()
|
||||
.put(Direction.DOWN, DOWN).put(Direction.UP, UP)
|
||||
.put(Direction.NORTH, NORTH).put(Direction.SOUTH, SOUTH)
|
||||
.put(Direction.WEST, WEST).put(Direction.EAST, EAST)
|
||||
.build());
|
||||
static final EnumMap<Direction, BooleanProperty> CONNECTIONS = Util.make(new EnumMap<>(Direction.class), m -> {
|
||||
m.put(Direction.DOWN, DOWN);
|
||||
m.put(Direction.UP, UP);
|
||||
m.put(Direction.NORTH, NORTH);
|
||||
m.put(Direction.SOUTH, SOUTH);
|
||||
m.put(Direction.WEST, WEST);
|
||||
m.put(Direction.EAST, EAST);
|
||||
});
|
||||
|
||||
public CableBlock(Properties settings) {
|
||||
super(settings);
|
||||
|
@@ -4,7 +4,6 @@
|
||||
|
||||
package dan200.computercraft.shared.peripheral.modem.wired;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import dan200.computercraft.api.network.wired.WiredElement;
|
||||
import dan200.computercraft.api.network.wired.WiredNode;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
@@ -31,7 +30,8 @@ import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
public class CableBlockEntity extends BlockEntity {
|
||||
private static final String NBT_PERIPHERAL_ENABLED = "PeripheralAccess";
|
||||
@@ -181,7 +181,7 @@ public class CableBlockEntity extends BlockEntity {
|
||||
var oldName = peripheral.getConnectedName();
|
||||
togglePeripheralAccess();
|
||||
var newName = peripheral.getConnectedName();
|
||||
if (!Objects.equal(newName, oldName)) {
|
||||
if (!Objects.equals(newName, oldName)) {
|
||||
if (oldName != null) {
|
||||
player.displayClientMessage(Component.translatable("chat.computercraft.wired_modem.peripheral_disconnected",
|
||||
ChatHelpers.copy(oldName)), false);
|
||||
@@ -274,7 +274,7 @@ public class CableBlockEntity extends BlockEntity {
|
||||
if (!canAttachPeripheral() && peripheralAccessAllowed) {
|
||||
peripheralAccessAllowed = false;
|
||||
peripheral.detach();
|
||||
node.updatePeripherals(Collections.emptyMap());
|
||||
node.updatePeripherals(Map.of());
|
||||
setChanged();
|
||||
updateBlockState();
|
||||
}
|
||||
@@ -291,7 +291,7 @@ public class CableBlockEntity extends BlockEntity {
|
||||
peripheral.detach();
|
||||
|
||||
peripheralAccessAllowed = false;
|
||||
node.updatePeripherals(Collections.emptyMap());
|
||||
node.updatePeripherals(Map.of());
|
||||
}
|
||||
|
||||
updateBlockState();
|
||||
|
@@ -4,9 +4,9 @@
|
||||
|
||||
package dan200.computercraft.shared.peripheral.modem.wired;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import dan200.computercraft.shared.peripheral.modem.ModemShapes;
|
||||
import dan200.computercraft.shared.util.DirectionUtil;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.shapes.Shapes;
|
||||
@@ -21,16 +21,14 @@ public final class CableShapes {
|
||||
private static final double MAX = 1 - MIN;
|
||||
|
||||
private static final VoxelShape SHAPE_CABLE_CORE = Shapes.box(MIN, MIN, MIN, MAX, MAX, MAX);
|
||||
private static final EnumMap<Direction, VoxelShape> SHAPE_CABLE_ARM =
|
||||
new EnumMap<>(new ImmutableMap.Builder<Direction, VoxelShape>()
|
||||
.put(Direction.DOWN, Shapes.box(MIN, 0, MIN, MAX, MIN, MAX))
|
||||
.put(Direction.UP, Shapes.box(MIN, MAX, MIN, MAX, 1, MAX))
|
||||
.put(Direction.NORTH, Shapes.box(MIN, MIN, 0, MAX, MAX, MIN))
|
||||
.put(Direction.SOUTH, Shapes.box(MIN, MIN, MAX, MAX, MAX, 1))
|
||||
.put(Direction.WEST, Shapes.box(0, MIN, MIN, MIN, MAX, MAX))
|
||||
.put(Direction.EAST, Shapes.box(MAX, MIN, MIN, 1, MAX, MAX))
|
||||
.build()
|
||||
);
|
||||
private static final EnumMap<Direction, VoxelShape> SHAPE_CABLE_ARM = Util.make(new EnumMap<>(Direction.class), m -> {
|
||||
m.put(Direction.DOWN, Shapes.box(MIN, 0, MIN, MAX, MIN, MAX));
|
||||
m.put(Direction.UP, Shapes.box(MIN, MAX, MIN, MAX, 1, MAX));
|
||||
m.put(Direction.NORTH, Shapes.box(MIN, MIN, 0, MAX, MAX, MIN));
|
||||
m.put(Direction.SOUTH, Shapes.box(MIN, MIN, MAX, MAX, MAX, 1));
|
||||
m.put(Direction.WEST, Shapes.box(0, MIN, MIN, MIN, MAX, MAX));
|
||||
m.put(Direction.EAST, Shapes.box(MAX, MIN, MIN, 1, MAX, MAX));
|
||||
});
|
||||
|
||||
private static final VoxelShape[] SHAPES = new VoxelShape[(1 << 6) * 7];
|
||||
private static final VoxelShape[] CABLE_SHAPES = new VoxelShape[1 << 6];
|
||||
|
@@ -4,7 +4,6 @@
|
||||
|
||||
package dan200.computercraft.shared.peripheral.modem.wired;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import dan200.computercraft.api.network.wired.WiredElement;
|
||||
import dan200.computercraft.api.network.wired.WiredNode;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
@@ -133,7 +132,7 @@ public class WiredModemFullBlockEntity extends BlockEntity {
|
||||
togglePeripheralAccess();
|
||||
var periphNames = getConnectedPeripheralNames();
|
||||
|
||||
if (!Objects.equal(periphNames, oldPeriphNames)) {
|
||||
if (!Objects.equals(periphNames, oldPeriphNames)) {
|
||||
sendPeripheralChanges(player, "chat.computercraft.wired_modem.peripheral_disconnected", oldPeriphNames);
|
||||
sendPeripheralChanges(player, "chat.computercraft.wired_modem.peripheral_connected", periphNames);
|
||||
}
|
||||
@@ -241,14 +240,14 @@ public class WiredModemFullBlockEntity extends BlockEntity {
|
||||
peripheralAccessAllowed = false;
|
||||
|
||||
for (var peripheral : peripherals) peripheral.detach();
|
||||
node.updatePeripherals(Collections.emptyMap());
|
||||
node.updatePeripherals(Map.of());
|
||||
}
|
||||
|
||||
updateBlockState();
|
||||
}
|
||||
|
||||
private Set<String> getConnectedPeripheralNames() {
|
||||
if (!peripheralAccessAllowed) return Collections.emptySet();
|
||||
if (!peripheralAccessAllowed) return Set.of();
|
||||
|
||||
Set<String> peripherals = new HashSet<>(6);
|
||||
for (var peripheral : this.peripherals) {
|
||||
@@ -259,7 +258,7 @@ public class WiredModemFullBlockEntity extends BlockEntity {
|
||||
}
|
||||
|
||||
private Map<String, IPeripheral> getConnectedPeripherals() {
|
||||
if (!peripheralAccessAllowed) return Collections.emptyMap();
|
||||
if (!peripheralAccessAllowed) return Map.of();
|
||||
|
||||
Map<String, IPeripheral> peripherals = new HashMap<>(6);
|
||||
for (var peripheral : this.peripherals) peripheral.extendMap(peripherals);
|
||||
|
@@ -4,8 +4,8 @@
|
||||
|
||||
package dan200.computercraft.shared.peripheral.modem.wired;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftTags;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.computer.core.ServerContext;
|
||||
import dan200.computercraft.shared.platform.ComponentAccess;
|
||||
import dan200.computercraft.shared.platform.PlatformHelper;
|
||||
@@ -103,7 +103,7 @@ public final class WiredModemLocalPeripheral {
|
||||
|
||||
public Map<String, IPeripheral> toMap() {
|
||||
return peripheral == null
|
||||
? Collections.emptyMap()
|
||||
? Map.of()
|
||||
: Collections.singletonMap(type + "_" + id, peripheral);
|
||||
}
|
||||
|
||||
@@ -124,8 +124,7 @@ public final class WiredModemLocalPeripheral {
|
||||
private IPeripheral getPeripheralFrom(Level world, BlockPos pos, Direction direction) {
|
||||
var offset = pos.relative(direction);
|
||||
|
||||
var block = world.getBlockState(offset).getBlock();
|
||||
if (block == ModRegistry.Blocks.WIRED_MODEM_FULL.get() || block == ModRegistry.Blocks.CABLE.get()) return null;
|
||||
if (world.getBlockState(offset).is(ComputerCraftTags.Blocks.PERIPHERAL_HUB_IGNORE)) return null;
|
||||
|
||||
var peripheral = peripherals.get((ServerLevel) world, pos, direction);
|
||||
return peripheral instanceof WiredModemPeripheral ? null : peripheral;
|
||||
|
@@ -4,7 +4,6 @@
|
||||
|
||||
package dan200.computercraft.shared.peripheral.modem.wired;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import dan200.computercraft.api.filesystem.Mount;
|
||||
import dan200.computercraft.api.filesystem.WritableMount;
|
||||
import dan200.computercraft.api.lua.*;
|
||||
@@ -69,7 +68,7 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements Wi
|
||||
|
||||
@Override
|
||||
public Set<String> getAdditionalTypes() {
|
||||
return Collections.singleton("peripheral_hub");
|
||||
return Set.of("peripheral_hub");
|
||||
}
|
||||
|
||||
//region Peripheral methods
|
||||
@@ -89,7 +88,7 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements Wi
|
||||
@LuaFunction
|
||||
public final Collection<String> getNamesRemote(IComputerAccess computer) {
|
||||
var wrappers = getWrappers(computer);
|
||||
return wrappers == null ? Collections.emptySet() : wrappers.keySet();
|
||||
return wrappers == null ? Set.of() : wrappers.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -429,7 +428,7 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements Wi
|
||||
public Map<String, IPeripheral> getAvailablePeripherals() {
|
||||
if (!attached) throw new NotAttachedException();
|
||||
synchronized (element.getRemotePeripherals()) {
|
||||
return ImmutableMap.copyOf(element.getRemotePeripherals());
|
||||
return Map.copyOf(element.getRemotePeripherals());
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -157,7 +157,6 @@ public class MonitorBlockEntity extends BlockEntity {
|
||||
if (xIndex == 0 && yIndex == 0) {
|
||||
// If we're the origin, set up the new monitor
|
||||
serverMonitor = new ServerMonitor(advanced, this);
|
||||
serverMonitor.rebuild();
|
||||
|
||||
// And propagate it to child monitors
|
||||
for (var x = 0; x < width; x++) {
|
||||
@@ -178,6 +177,11 @@ public class MonitorBlockEntity extends BlockEntity {
|
||||
}
|
||||
}
|
||||
|
||||
private void createServerTerminal() {
|
||||
var monitor = createServerMonitor();
|
||||
if (monitor != null && monitor.getTerminal() == null) monitor.rebuild();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ClientMonitor getClientMonitor() {
|
||||
if (clientMonitor != null) return clientMonitor;
|
||||
@@ -349,13 +353,15 @@ public class MonitorBlockEntity extends BlockEntity {
|
||||
// Either delete the current monitor or sync a new one.
|
||||
if (needsTerminal) {
|
||||
if (serverMonitor == null) serverMonitor = new ServerMonitor(advanced, this);
|
||||
} else {
|
||||
serverMonitor = null;
|
||||
}
|
||||
|
||||
// Update the terminal's width and height and rebuild it. This ensures the monitor
|
||||
// is consistent when syncing it to other monitors.
|
||||
if (serverMonitor != null) serverMonitor.rebuild();
|
||||
// Update the terminal's width and height and rebuild it. This ensures the monitor
|
||||
// is consistent when syncing it to other monitors.
|
||||
serverMonitor.rebuild();
|
||||
} else {
|
||||
// Remove the terminal from the serverMonitor, but keep it around - this ensures that we sync
|
||||
// the (now blank) monitor to the client.
|
||||
if (serverMonitor != null) serverMonitor.reset();
|
||||
}
|
||||
|
||||
// Update the other monitors, setting coordinates, dimensions and the server terminal
|
||||
var pos = getBlockPos();
|
||||
@@ -375,6 +381,8 @@ public class MonitorBlockEntity extends BlockEntity {
|
||||
BlockEntityHelpers.updateBlock(monitor);
|
||||
}
|
||||
}
|
||||
|
||||
assertInvariant();
|
||||
}
|
||||
|
||||
void updateNeighborsDeferred() {
|
||||
@@ -485,9 +493,10 @@ public class MonitorBlockEntity extends BlockEntity {
|
||||
}
|
||||
|
||||
public IPeripheral peripheral() {
|
||||
createServerMonitor();
|
||||
if (peripheral != null) return peripheral;
|
||||
return peripheral = new MonitorPeripheral(this);
|
||||
createServerTerminal();
|
||||
var peripheral = this.peripheral != null ? this.peripheral : (this.peripheral = new MonitorPeripheral(this));
|
||||
assertInvariant();
|
||||
return peripheral;
|
||||
}
|
||||
|
||||
void addComputer(IComputerAccess computer) {
|
||||
@@ -526,4 +535,85 @@ public class MonitorBlockEntity extends BlockEntity {
|
||||
Math.max(startPos.getZ(), endPos.getZ()) + 1
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert all {@linkplain #checkInvariants() monitor invariants} hold.
|
||||
*/
|
||||
private void assertInvariant() {
|
||||
assert checkInvariants() : "Monitor invariants failed. See logs.";
|
||||
}
|
||||
|
||||
/**
|
||||
* Check various invariants about this monitor multiblock. This is only called when assertions are enabled, so
|
||||
* will be skipped outside of tests.
|
||||
*
|
||||
* @return Whether all invariants passed.
|
||||
*/
|
||||
private boolean checkInvariants() {
|
||||
LOG.debug("Checking monitor invariants at {}", getBlockPos());
|
||||
|
||||
var okay = true;
|
||||
|
||||
if (width <= 0 || height <= 0) {
|
||||
okay = false;
|
||||
LOG.error("Monitor {} has non-positive of {}x{}", getBlockPos(), width, height);
|
||||
}
|
||||
|
||||
var hasPeripheral = false;
|
||||
var origin = getOrigin().getMonitor();
|
||||
var serverMonitor = origin != null ? origin.serverMonitor : this.serverMonitor;
|
||||
for (var x = 0; x < width; x++) {
|
||||
for (var y = 0; y < height; y++) {
|
||||
var monitor = getLoadedMonitor(x, y).getMonitor();
|
||||
if (monitor == null) continue;
|
||||
|
||||
hasPeripheral |= monitor.peripheral != null;
|
||||
|
||||
if (monitor.serverMonitor != null && monitor.serverMonitor != serverMonitor) {
|
||||
okay = false;
|
||||
LOG.error(
|
||||
"Monitor {} expected to be have serverMonitor={}, but was {}",
|
||||
monitor.getBlockPos(), serverMonitor, monitor.serverMonitor
|
||||
);
|
||||
}
|
||||
|
||||
if (monitor.xIndex != x || monitor.yIndex != y) {
|
||||
okay = false;
|
||||
LOG.error(
|
||||
"Monitor {} expected to be at {},{}, but believes it is {},{}",
|
||||
monitor.getBlockPos(), x, y, monitor.xIndex, monitor.yIndex
|
||||
);
|
||||
}
|
||||
|
||||
if (monitor.width != width || monitor.height != height) {
|
||||
okay = false;
|
||||
LOG.error(
|
||||
"Monitor {} expected to be size {},{}, but believes it is {},{}",
|
||||
monitor.getBlockPos(), width, height, monitor.width, monitor.height
|
||||
);
|
||||
}
|
||||
|
||||
var expectedState = getBlockState().setValue(MonitorBlock.STATE, MonitorEdgeState.fromConnections(
|
||||
y < height - 1, y > 0, x > 0, x < width - 1
|
||||
));
|
||||
if (monitor.getBlockState() != expectedState) {
|
||||
okay = false;
|
||||
LOG.error(
|
||||
"Monitor {} expected to have state {}, but has state {}",
|
||||
monitor.getBlockState(), expectedState, monitor.getBlockState()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasPeripheral != (serverMonitor != null && serverMonitor.getTerminal() != null)) {
|
||||
okay = false;
|
||||
LOG.error(
|
||||
"Peripheral is {}, but serverMonitor={} and serverMonitor.terminal={}",
|
||||
hasPeripheral, serverMonitor, serverMonitor == null ? null : serverMonitor.getTerminal()
|
||||
);
|
||||
}
|
||||
|
||||
return okay;
|
||||
}
|
||||
}
|
||||
|
@@ -55,6 +55,12 @@ public class ServerMonitor {
|
||||
}
|
||||
}
|
||||
|
||||
synchronized void reset() {
|
||||
if (terminal == null) return;
|
||||
terminal = null;
|
||||
markChanged();
|
||||
}
|
||||
|
||||
private void markChanged() {
|
||||
if (!changed.getAndSet(true)) TickScheduler.schedule(origin.tickToken);
|
||||
}
|
||||
|
@@ -188,7 +188,7 @@ public abstract class SpeakerPeripheral implements IPeripheral {
|
||||
* {@literal false}.
|
||||
* <p>
|
||||
* ### Valid instruments
|
||||
* The speaker supports [all of Minecraft's noteblock instruments](https://minecraft.fandom.com/wiki/Note_Block#Instruments).
|
||||
* The speaker supports [all of Minecraft's noteblock instruments](https://minecraft.wiki/w/Note_Block#Instruments).
|
||||
* These are:
|
||||
* <p>
|
||||
* {@code "harp"}, {@code "basedrum"}, {@code "snare"}, {@code "hat"}, {@code "bass"}, {@code "flute"},
|
||||
@@ -228,7 +228,7 @@ public abstract class SpeakerPeripheral implements IPeripheral {
|
||||
/**
|
||||
* Plays a Minecraft sound through the speaker.
|
||||
* <p>
|
||||
* This takes the [name of a Minecraft sound](https://minecraft.fandom.com/wiki/Sounds.json), such as
|
||||
* This takes the [name of a Minecraft sound](https://minecraft.wiki/w/Sounds.json), such as
|
||||
* {@code "minecraft:block.note_block.harp"}, as well as an optional volume and pitch.
|
||||
* <p>
|
||||
* Only one sound can be played at once. This function will return {@literal false} if another sound was started
|
||||
|
@@ -107,7 +107,7 @@ public class PocketServerComputer extends ServerComputer implements IPocketAcces
|
||||
@Override
|
||||
@Deprecated(forRemoval = true)
|
||||
public Map<ResourceLocation, IPeripheral> getUpgrades() {
|
||||
return upgrade == null ? Collections.emptyMap() : Collections.singletonMap(upgrade.getUpgradeID(), getPeripheral(ComputerSide.BACK));
|
||||
return upgrade == null ? Map.of() : Collections.singletonMap(upgrade.getUpgradeID(), getPeripheral(ComputerSide.BACK));
|
||||
}
|
||||
|
||||
public @Nullable UpgradeData<IPocketUpgrade> getUpgrade() {
|
||||
|
@@ -4,7 +4,6 @@
|
||||
|
||||
package dan200.computercraft.shared.pocket.items;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import dan200.computercraft.annotations.ForgeOverride;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.filesystem.Mount;
|
||||
@@ -44,6 +43,7 @@ import net.minecraft.world.level.Level;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class PocketComputerItem extends Item implements IComputerItem, IMedia, IColouredItem {
|
||||
private static final String NBT_UPGRADE = "Upgrade";
|
||||
@@ -97,7 +97,7 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I
|
||||
|
||||
// Sync label
|
||||
var label = computer.getLabel();
|
||||
if (!Objects.equal(label, getLabel(stack))) {
|
||||
if (!Objects.equals(label, getLabel(stack))) {
|
||||
changed = true;
|
||||
setLabel(stack, label);
|
||||
}
|
||||
|
@@ -0,0 +1,86 @@
|
||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.recipe;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.mojang.serialization.DataResult;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.crafting.RecipeSerializer;
|
||||
import net.minecraft.world.item.crafting.ShapedRecipe;
|
||||
|
||||
/**
|
||||
* A custom version of {@link ShapedRecipe}, which can be converted to and from a {@link ShapedRecipeSpec}.
|
||||
* <p>
|
||||
* This recipe may both be used as a normal recipe (behaving mostly the same as {@link ShapedRecipe}, with
|
||||
* {@linkplain RecipeUtil#itemStackFromJson(JsonObject) support for putting nbt on the result}), or subclassed to
|
||||
* customise the crafting behaviour.
|
||||
*/
|
||||
public class CustomShapedRecipe extends ShapedRecipe {
|
||||
private final ItemStack result;
|
||||
|
||||
public CustomShapedRecipe(ResourceLocation id, ShapedRecipeSpec recipe) {
|
||||
super(
|
||||
id,
|
||||
recipe.properties().group(), recipe.properties().category(),
|
||||
recipe.template().width(), recipe.template().height(), recipe.template().ingredients(),
|
||||
recipe.result()
|
||||
);
|
||||
this.result = recipe.result();
|
||||
}
|
||||
|
||||
public final ShapedRecipeSpec toSpec() {
|
||||
return new ShapedRecipeSpec(RecipeProperties.of(this), ShapedTemplate.of(this), result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecipeSerializer<? extends CustomShapedRecipe> getSerializer() {
|
||||
return ModRegistry.RecipeSerializers.SHAPED.get();
|
||||
}
|
||||
|
||||
public interface Factory<R> {
|
||||
R create(ResourceLocation id, ShapedRecipeSpec recipe);
|
||||
}
|
||||
|
||||
public static <T extends CustomShapedRecipe> RecipeSerializer<T> serialiser(CustomShapedRecipe.Factory<T> factory) {
|
||||
return new Serialiser<>((id, r) -> DataResult.success(factory.create(id, r)));
|
||||
}
|
||||
|
||||
public static <T extends CustomShapedRecipe> RecipeSerializer<T> validatingSerialiser(CustomShapedRecipe.Factory<DataResult<T>> factory) {
|
||||
return new Serialiser<>(factory);
|
||||
}
|
||||
|
||||
private record Serialiser<T extends CustomShapedRecipe>(
|
||||
Factory<DataResult<T>> factory
|
||||
) implements RecipeSerializer<T> {
|
||||
private Serialiser(Factory<DataResult<T>> factory) {
|
||||
this.factory = (id, r) -> factory.create(id, r).flatMap(x -> {
|
||||
if (x.getSerializer() != this) {
|
||||
return DataResult.error(() -> "Expected serialiser to be " + this + ", but was " + x.getSerializer());
|
||||
}
|
||||
return DataResult.success(x);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public T fromJson(ResourceLocation id, JsonObject json) {
|
||||
return Util.getOrThrow(factory.create(id, ShapedRecipeSpec.fromJson(json)), JsonParseException::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) {
|
||||
return Util.getOrThrow(factory.create(id, ShapedRecipeSpec.fromNetwork(buffer)), IllegalStateException::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toNetwork(FriendlyByteBuf buffer, T recipe) {
|
||||
recipe.toSpec().toNetwork(buffer);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,81 @@
|
||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.recipe;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.mojang.serialization.DataResult;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.crafting.RecipeSerializer;
|
||||
import net.minecraft.world.item.crafting.ShapelessRecipe;
|
||||
|
||||
/**
|
||||
* A custom version of {@link ShapelessRecipe}, which can be converted to and from a {@link ShapelessRecipeSpec}.
|
||||
* <p>
|
||||
* This recipe may both be used as a normal recipe (behaving mostly the same as {@link ShapelessRecipe}, with
|
||||
* {@linkplain RecipeUtil#itemStackFromJson(JsonObject) support for putting nbt on the result}), or subclassed to
|
||||
* customise the crafting behaviour.
|
||||
*/
|
||||
public class CustomShapelessRecipe extends ShapelessRecipe {
|
||||
private final ItemStack result;
|
||||
|
||||
public CustomShapelessRecipe(ResourceLocation id, ShapelessRecipeSpec recipe) {
|
||||
super(id, recipe.properties().group(), recipe.properties().category(), recipe.result(), recipe.ingredients());
|
||||
this.result = recipe.result();
|
||||
}
|
||||
|
||||
public final ShapelessRecipeSpec toSpec() {
|
||||
return new ShapelessRecipeSpec(RecipeProperties.of(this), getIngredients(), result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecipeSerializer<? extends CustomShapelessRecipe> getSerializer() {
|
||||
return ModRegistry.RecipeSerializers.SHAPELESS.get();
|
||||
}
|
||||
|
||||
public interface Factory<R> {
|
||||
R create(ResourceLocation id, ShapelessRecipeSpec recipe);
|
||||
}
|
||||
|
||||
public static <T extends CustomShapelessRecipe> RecipeSerializer<T> serialiser(Factory<T> factory) {
|
||||
return new CustomShapelessRecipe.Serialiser<>((id, r) -> DataResult.success(factory.create(id, r)));
|
||||
}
|
||||
|
||||
public static <T extends CustomShapelessRecipe> RecipeSerializer<T> validatingSerialiser(Factory<DataResult<T>> factory) {
|
||||
return new CustomShapelessRecipe.Serialiser<>(factory);
|
||||
}
|
||||
|
||||
private record Serialiser<T extends CustomShapelessRecipe>(
|
||||
Factory<DataResult<T>> factory
|
||||
) implements RecipeSerializer<T> {
|
||||
private Serialiser(Factory<DataResult<T>> factory) {
|
||||
this.factory = (id, r) -> factory.create(id, r).flatMap(x -> {
|
||||
if (x.getSerializer() != this) {
|
||||
return DataResult.error(() -> "Expected serialiser to be " + this + ", but was " + x.getSerializer());
|
||||
}
|
||||
return DataResult.success(x);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public T fromJson(ResourceLocation id, JsonObject json) {
|
||||
return Util.getOrThrow(factory.create(id, ShapelessRecipeSpec.fromJson(json)), JsonParseException::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) {
|
||||
return Util.getOrThrow(factory.create(id, ShapelessRecipeSpec.fromNetwork(buffer)), IllegalStateException::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toNetwork(FriendlyByteBuf buffer, T recipe) {
|
||||
recipe.toSpec().toNetwork(buffer);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
// Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
|
||||
//
|
||||
// SPDX-License-Identifier: LicenseRef-CCPL
|
||||
|
||||
package dan200.computercraft.shared.recipe;
|
||||
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.inventory.CraftingContainer;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.crafting.CustomRecipe;
|
||||
import net.minecraft.world.item.crafting.RecipeSerializer;
|
||||
import net.minecraft.world.item.crafting.ShapedRecipe;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
/**
|
||||
* A fake {@link ShapedRecipe}, which appears in the recipe book (and other recipe mods), but cannot be crafted.
|
||||
* <p>
|
||||
* This is used to represent examples for our {@link CustomRecipe}s.
|
||||
*/
|
||||
public final class ImpostorShapedRecipe extends CustomShapedRecipe {
|
||||
public ImpostorShapedRecipe(ResourceLocation id, ShapedRecipeSpec recipe) {
|
||||
super(id, recipe);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(CraftingContainer inv, Level world) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack assemble(CraftingContainer inventory, RegistryAccess registryAccess) {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecipeSerializer<ImpostorShapedRecipe> getSerializer() {
|
||||
return ModRegistry.RecipeSerializers.IMPOSTOR_SHAPED.get();
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
// Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
|
||||
//
|
||||
// SPDX-License-Identifier: LicenseRef-CCPL
|
||||
|
||||
package dan200.computercraft.shared.recipe;
|
||||
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.inventory.CraftingContainer;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.crafting.CustomRecipe;
|
||||
import net.minecraft.world.item.crafting.RecipeSerializer;
|
||||
import net.minecraft.world.item.crafting.ShapelessRecipe;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
/**
|
||||
* A fake {@link ShapelessRecipe}, which appears in the recipe book (and other recipe mods), but cannot be crafted.
|
||||
* <p>
|
||||
* This is used to represent examples for our {@link CustomRecipe}s.
|
||||
*/
|
||||
public final class ImpostorShapelessRecipe extends CustomShapelessRecipe {
|
||||
public ImpostorShapelessRecipe(ResourceLocation id, ShapelessRecipeSpec recipe) {
|
||||
super(id, recipe);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(CraftingContainer inv, Level world) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack assemble(CraftingContainer inventory, RegistryAccess access) {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecipeSerializer<ImpostorShapelessRecipe> getSerializer() {
|
||||
return ModRegistry.RecipeSerializers.IMPOSTOR_SHAPELESS.get();
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.recipe;
|
||||
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.mojang.datafixers.util.Either;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.DataResult;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.TagParser;
|
||||
|
||||
/**
|
||||
* Additional codecs for working with recipes.
|
||||
*/
|
||||
public class MoreCodecs {
|
||||
/**
|
||||
* A codec for {@link CompoundTag}s, which either accepts a NBT-string or a JSON object.
|
||||
*/
|
||||
public static final Codec<CompoundTag> TAG = Codec.either(Codec.STRING, CompoundTag.CODEC).flatXmap(
|
||||
either -> either.map(MoreCodecs::parseTag, DataResult::success),
|
||||
nbtCompound -> DataResult.success(Either.left(nbtCompound.getAsString()))
|
||||
);
|
||||
|
||||
private static DataResult<CompoundTag> parseTag(String contents) {
|
||||
try {
|
||||
return DataResult.success(TagParser.parseTag(contents));
|
||||
} catch (CommandSyntaxException e) {
|
||||
return DataResult.error(e::getMessage);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.recipe;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import net.minecraft.world.item.crafting.CraftingBookCategory;
|
||||
import net.minecraft.world.item.crafting.CraftingRecipe;
|
||||
|
||||
/**
|
||||
* Common properties that appear in all {@link CraftingRecipe}s.
|
||||
*
|
||||
* @param group The (optional) group of the recipe, see {@link CraftingRecipe#getGroup()}.
|
||||
* @param category The category the recipe appears in, see {@link CraftingRecipe#category()}.
|
||||
*/
|
||||
public record RecipeProperties(String group, CraftingBookCategory category) {
|
||||
public static RecipeProperties of(CraftingRecipe recipe) {
|
||||
return new RecipeProperties(recipe.getGroup(), recipe.category());
|
||||
}
|
||||
|
||||
public static RecipeProperties fromJson(JsonObject json) {
|
||||
var group = GsonHelper.getAsString(json, "group", "");
|
||||
var category = CraftingBookCategory.CODEC.byName(GsonHelper.getAsString(json, "category", null), CraftingBookCategory.MISC);
|
||||
return new RecipeProperties(group, category);
|
||||
}
|
||||
|
||||
public static RecipeProperties fromNetwork(FriendlyByteBuf buffer) {
|
||||
var group = buffer.readUtf();
|
||||
var category = buffer.readEnum(CraftingBookCategory.class);
|
||||
return new RecipeProperties(group, category);
|
||||
}
|
||||
|
||||
public void toNetwork(FriendlyByteBuf buffer) {
|
||||
buffer.writeUtf(group());
|
||||
buffer.writeEnum(category());
|
||||
}
|
||||
}
|
@@ -0,0 +1,73 @@
|
||||
// SPDX-FileCopyrightText: 2017 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.recipe;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import com.mojang.serialization.JsonOps;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.core.NonNullList;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.crafting.Ingredient;
|
||||
import net.minecraft.world.item.crafting.ShapedRecipe;
|
||||
|
||||
public final class RecipeUtil {
|
||||
private RecipeUtil() {
|
||||
}
|
||||
|
||||
public static NonNullList<Ingredient> readIngredients(FriendlyByteBuf buffer) {
|
||||
var count = buffer.readVarInt();
|
||||
var ingredients = NonNullList.withSize(count, Ingredient.EMPTY);
|
||||
for (var i = 0; i < ingredients.size(); i++) ingredients.set(i, Ingredient.fromNetwork(buffer));
|
||||
return ingredients;
|
||||
}
|
||||
|
||||
public static void writeIngredients(FriendlyByteBuf buffer, NonNullList<Ingredient> ingredients) {
|
||||
buffer.writeCollection(ingredients, (a, b) -> b.toNetwork(a));
|
||||
}
|
||||
|
||||
public static NonNullList<Ingredient> readShapelessIngredients(JsonObject json) {
|
||||
NonNullList<Ingredient> ingredients = NonNullList.create();
|
||||
|
||||
var ingredientsList = GsonHelper.getAsJsonArray(json, "ingredients");
|
||||
for (var i = 0; i < ingredientsList.size(); ++i) {
|
||||
var ingredient = Ingredient.fromJson(ingredientsList.get(i));
|
||||
if (!ingredient.isEmpty()) ingredients.add(ingredient);
|
||||
}
|
||||
|
||||
if (ingredients.isEmpty()) throw new JsonParseException("No ingredients for shapeless recipe");
|
||||
if (ingredients.size() > 9) {
|
||||
throw new JsonParseException("Too many ingredients for shapeless recipe the max is 9");
|
||||
}
|
||||
|
||||
return ingredients;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends {@link ShapedRecipe#itemStackFromJson(JsonObject)} with support for the {@code nbt} field.
|
||||
*
|
||||
* @param json The json to extract the item from.
|
||||
* @return The parsed item stack.
|
||||
*/
|
||||
public static ItemStack itemStackFromJson(JsonObject json) {
|
||||
var item = ShapedRecipe.itemFromJson(json);
|
||||
if (json.has("data")) throw new JsonParseException("Disallowed data tag found");
|
||||
|
||||
var count = GsonHelper.getAsInt(json, "count", 1);
|
||||
if (count < 1) throw new JsonSyntaxException("Invalid output count: " + count);
|
||||
|
||||
var stack = new ItemStack(item, count);
|
||||
|
||||
var nbt = json.get("nbt");
|
||||
if (nbt != null) {
|
||||
stack.setTag(Util.getOrThrow(MoreCodecs.TAG.parse(JsonOps.INSTANCE, nbt), JsonParseException::new));
|
||||
}
|
||||
return stack;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.recipe;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.crafting.ShapedRecipe;
|
||||
|
||||
/**
|
||||
* A description of a {@link ShapedRecipe}.
|
||||
* <p>
|
||||
* This is meant to be used in conjunction with {@link CustomShapedRecipe} for more reusable serialisation and
|
||||
* deserialisation of {@link ShapedRecipe}-like recipes.
|
||||
*
|
||||
* @param properties The common properties of this recipe.
|
||||
* @param template The shaped template of the recipe.
|
||||
* @param result The result of the recipe.
|
||||
*/
|
||||
public record ShapedRecipeSpec(RecipeProperties properties, ShapedTemplate template, ItemStack result) {
|
||||
public static ShapedRecipeSpec fromJson(JsonObject json) {
|
||||
var properties = RecipeProperties.fromJson(json);
|
||||
var template = ShapedTemplate.fromJson(json);
|
||||
var result = RecipeUtil.itemStackFromJson(GsonHelper.getAsJsonObject(json, "result"));
|
||||
return new ShapedRecipeSpec(properties, template, result);
|
||||
}
|
||||
|
||||
public static ShapedRecipeSpec fromNetwork(FriendlyByteBuf buffer) {
|
||||
var properties = RecipeProperties.fromNetwork(buffer);
|
||||
var template = ShapedTemplate.fromNetwork(buffer);
|
||||
var result = buffer.readItem();
|
||||
return new ShapedRecipeSpec(properties, template, result);
|
||||
}
|
||||
|
||||
public void toNetwork(FriendlyByteBuf buffer) {
|
||||
properties().toNetwork(buffer);
|
||||
template().toNetwork(buffer);
|
||||
buffer.writeItem(result());
|
||||
}
|
||||
}
|
@@ -0,0 +1,99 @@
|
||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.recipe;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import net.minecraft.core.NonNullList;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import net.minecraft.world.item.crafting.Ingredient;
|
||||
import net.minecraft.world.item.crafting.ShapedRecipe;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* The template for {@linkplain ShapedRecipe shaped recipes}. This largely exists for parsing shaped recipes from JSON.
|
||||
*
|
||||
* @param width The width of the recipe, see {@link ShapedRecipe#getWidth()}.
|
||||
* @param height The height of the recipe, see {@link ShapedRecipe#getHeight()}.
|
||||
* @param ingredients The ingredients in the recipe, see {@link ShapedRecipe#getIngredients()}
|
||||
*/
|
||||
public record ShapedTemplate(int width, int height, NonNullList<Ingredient> ingredients) {
|
||||
public static ShapedTemplate of(ShapedRecipe recipe) {
|
||||
return new ShapedTemplate(recipe.getWidth(), recipe.getHeight(), recipe.getIngredients());
|
||||
}
|
||||
|
||||
public static ShapedTemplate fromJson(JsonObject json) {
|
||||
Map<Character, Ingredient> key = new HashMap<>();
|
||||
for (var entry : GsonHelper.getAsJsonObject(json, "key").entrySet()) {
|
||||
if (entry.getKey().length() != 1) {
|
||||
throw new JsonSyntaxException("Invalid key entry: '" + entry.getKey() + "' is an invalid symbol (must be 1 character only).");
|
||||
}
|
||||
if (" ".equals(entry.getKey())) {
|
||||
throw new JsonSyntaxException("Invalid key entry: ' ' is a reserved symbol.");
|
||||
}
|
||||
|
||||
key.put(entry.getKey().charAt(0), Ingredient.fromJson(entry.getValue()));
|
||||
}
|
||||
|
||||
var patternList = GsonHelper.getAsJsonArray(json, "pattern");
|
||||
if (patternList.size() == 0) {
|
||||
throw new JsonSyntaxException("Invalid pattern: empty pattern not allowed");
|
||||
}
|
||||
|
||||
var pattern = new String[patternList.size()];
|
||||
for (var x = 0; x < pattern.length; x++) {
|
||||
var line = GsonHelper.convertToString(patternList.get(x), "pattern[" + x + "]");
|
||||
if (x > 0 && pattern[0].length() != line.length()) {
|
||||
throw new JsonSyntaxException("Invalid pattern: each row must be the same width");
|
||||
}
|
||||
pattern[x] = line;
|
||||
}
|
||||
|
||||
var width = pattern[0].length();
|
||||
var height = pattern.length;
|
||||
var ingredients = NonNullList.withSize(width * height, Ingredient.EMPTY);
|
||||
|
||||
Set<Character> missingKeys = new HashSet<>(key.keySet());
|
||||
|
||||
var ingredientIdx = 0;
|
||||
for (var line : pattern) {
|
||||
for (var x = 0; x < line.length(); x++) {
|
||||
var chr = line.charAt(x);
|
||||
var ing = chr == ' ' ? Ingredient.EMPTY : key.get(chr);
|
||||
if (ing == null) {
|
||||
throw new JsonSyntaxException("Pattern references symbol '" + chr + "' but it's not defined in the key");
|
||||
}
|
||||
ingredients.set(ingredientIdx++, ing);
|
||||
missingKeys.remove(chr);
|
||||
}
|
||||
}
|
||||
|
||||
if (!missingKeys.isEmpty()) {
|
||||
throw new JsonSyntaxException("Key defines symbols that aren't used in pattern: " + missingKeys);
|
||||
}
|
||||
|
||||
return new ShapedTemplate(width, height, ingredients);
|
||||
}
|
||||
|
||||
public static ShapedTemplate fromNetwork(FriendlyByteBuf buffer) {
|
||||
var width = buffer.readVarInt();
|
||||
var height = buffer.readVarInt();
|
||||
var ingredients = NonNullList.withSize(width * height, Ingredient.EMPTY);
|
||||
for (var i = 0; i < ingredients.size(); ++i) ingredients.set(i, Ingredient.fromNetwork(buffer));
|
||||
return new ShapedTemplate(width, height, ingredients);
|
||||
}
|
||||
|
||||
public void toNetwork(FriendlyByteBuf buffer) {
|
||||
buffer.writeVarInt(width());
|
||||
buffer.writeVarInt(height());
|
||||
for (var ingredient : ingredients) ingredient.toNetwork(buffer);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.recipe;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import net.minecraft.core.NonNullList;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.crafting.Ingredient;
|
||||
import net.minecraft.world.item.crafting.ShapelessRecipe;
|
||||
|
||||
/**
|
||||
* A description of a {@link ShapelessRecipe}.
|
||||
* <p>
|
||||
* This is meant to be used in conjunction with {@link CustomShapelessRecipe} for more reusable serialisation and
|
||||
* deserialisation of {@link ShapelessRecipe}-like recipes.
|
||||
*
|
||||
* @param properties The common properties of this recipe.
|
||||
* @param ingredients The ingredients of the recipe.
|
||||
* @param result The result of the recipe.
|
||||
*/
|
||||
public record ShapelessRecipeSpec(RecipeProperties properties, NonNullList<Ingredient> ingredients, ItemStack result) {
|
||||
public static ShapelessRecipeSpec fromJson(JsonObject json) {
|
||||
var properties = RecipeProperties.fromJson(json);
|
||||
var ingredients = RecipeUtil.readShapelessIngredients(json);
|
||||
var result = RecipeUtil.itemStackFromJson(GsonHelper.getAsJsonObject(json, "result"));
|
||||
return new ShapelessRecipeSpec(properties, ingredients, result);
|
||||
}
|
||||
|
||||
public static ShapelessRecipeSpec fromNetwork(FriendlyByteBuf buffer) {
|
||||
var properties = RecipeProperties.fromNetwork(buffer);
|
||||
var ingredients = RecipeUtil.readIngredients(buffer);
|
||||
var result = buffer.readItem();
|
||||
|
||||
return new ShapelessRecipeSpec(properties, ingredients, result);
|
||||
}
|
||||
|
||||
public void toNetwork(FriendlyByteBuf buffer) {
|
||||
properties().toNetwork(buffer);
|
||||
RecipeUtil.writeIngredients(buffer, ingredients());
|
||||
buffer.writeItem(result());
|
||||
}
|
||||
}
|
@@ -11,6 +11,7 @@ import dan200.computercraft.api.turtle.TurtleCommandResult;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.core.apis.IAPIEnvironment;
|
||||
import dan200.computercraft.core.metrics.Metrics;
|
||||
import dan200.computercraft.shared.peripheral.generic.methods.AbstractInventoryMethods;
|
||||
import dan200.computercraft.shared.turtle.core.*;
|
||||
|
||||
import java.util.Optional;
|
||||
@@ -749,7 +750,7 @@ public class TurtleAPI implements ILuaAPI {
|
||||
* -- count = 13,
|
||||
* -- }
|
||||
* }</pre>
|
||||
* @see dan200.computercraft.shared.peripheral.generic.methods.InventoryMethods#getItemDetail Describes the information returned by a detailed query.
|
||||
* @see AbstractInventoryMethods#getItemDetail Describes the information returned by a detailed query.
|
||||
*/
|
||||
@LuaFunction
|
||||
public final MethodResult getItemDetail(ILuaContext context, Optional<Integer> slot, Optional<Boolean> detailed) throws LuaException {
|
||||
|
@@ -4,7 +4,6 @@
|
||||
|
||||
package dan200.computercraft.shared.turtle.core;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import dan200.computercraft.api.lua.ILuaCallback;
|
||||
import dan200.computercraft.api.lua.MethodResult;
|
||||
@@ -455,7 +454,7 @@ public class TurtleBrain implements TurtleAccessInternal {
|
||||
}
|
||||
|
||||
public void setOverlay(@Nullable ResourceLocation overlay) {
|
||||
if (!Objects.equal(this.overlay, overlay)) {
|
||||
if (!Objects.equals(this.overlay, overlay)) {
|
||||
this.overlay = overlay;
|
||||
BlockEntityHelpers.updateBlock(owner);
|
||||
}
|
||||
@@ -573,7 +572,7 @@ public class TurtleBrain implements TurtleAccessInternal {
|
||||
|
||||
public float getToolRenderAngle(TurtleSide side, float f) {
|
||||
return (side == TurtleSide.LEFT && animation == TurtleAnimation.SWING_LEFT_TOOL) ||
|
||||
(side == TurtleSide.RIGHT && animation == TurtleAnimation.SWING_RIGHT_TOOL)
|
||||
(side == TurtleSide.RIGHT && animation == TurtleAnimation.SWING_RIGHT_TOOL)
|
||||
? 45.0f * (float) Math.sin(getAnimationFraction(f) * Math.PI)
|
||||
: 0.0f;
|
||||
}
|
||||
|
@@ -27,7 +27,6 @@ import net.minecraft.world.item.context.BlockPlaceContext;
|
||||
import net.minecraft.world.item.context.UseOnContext;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.SignBlockEntity;
|
||||
import net.minecraft.world.level.block.entity.SignText;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
@@ -186,7 +185,7 @@ public class TurtlePlaceCommand implements TurtleCommand {
|
||||
tile = world.getBlockEntity(position.relative(side));
|
||||
}
|
||||
|
||||
if (tile instanceof SignBlockEntity) setSignText(world, tile, message);
|
||||
if (tile instanceof SignBlockEntity sign) setSignText(world, sign, message);
|
||||
}
|
||||
|
||||
return placed;
|
||||
@@ -220,23 +219,20 @@ public class TurtlePlaceCommand implements TurtleCommand {
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
|
||||
private static void setSignText(Level world, BlockEntity tile, String message) {
|
||||
var signTile = (SignBlockEntity) tile;
|
||||
var split = Splitter.on('\n').splitToList(message);
|
||||
var firstLine = split.size() <= 2 ? 1 : 0;
|
||||
private static void setSignText(Level world, SignBlockEntity sign, String message) {
|
||||
var lines = Splitter.on('\n').splitToList(message);
|
||||
var firstLine = lines.size() <= 2 ? 1 : 0;
|
||||
|
||||
var signText = new SignText();
|
||||
for (var i = 0; i < 4; i++) {
|
||||
if (i >= firstLine && i < firstLine + split.size()) {
|
||||
var line = split.get(i - firstLine);
|
||||
signText.setMessage(i, line.length() > 15
|
||||
? Component.literal(line.substring(0, 15))
|
||||
: Component.literal(line)
|
||||
);
|
||||
}
|
||||
for (int i = 0, len = Math.min(lines.size(), 4); i < len; i++) {
|
||||
var line = lines.get(i);
|
||||
signText = signText.setMessage(i + firstLine, line.length() > 15
|
||||
? Component.literal(line.substring(0, 15))
|
||||
: Component.literal(line)
|
||||
);
|
||||
}
|
||||
signTile.setText(signText, true);
|
||||
world.sendBlockUpdated(tile.getBlockPos(), tile.getBlockState(), tile.getBlockState(), Block.UPDATE_ALL);
|
||||
sign.setText(signText, true);
|
||||
world.sendBlockUpdated(sign.getBlockPos(), sign.getBlockState(), sign.getBlockState(), Block.UPDATE_ALL);
|
||||
}
|
||||
|
||||
private static final class ErrorMessage {
|
||||
|
@@ -4,32 +4,30 @@
|
||||
|
||||
package dan200.computercraft.shared.turtle.recipes;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.recipe.CustomShapelessRecipe;
|
||||
import dan200.computercraft.shared.recipe.ShapelessRecipeSpec;
|
||||
import dan200.computercraft.shared.turtle.items.TurtleItem;
|
||||
import net.minecraft.core.NonNullList;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import net.minecraft.world.inventory.CraftingContainer;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.crafting.*;
|
||||
import net.minecraft.world.item.crafting.RecipeSerializer;
|
||||
import net.minecraft.world.item.crafting.ShapelessRecipe;
|
||||
|
||||
/**
|
||||
* A {@link ShapelessRecipe} which sets the {@linkplain TurtleItem#getOverlay(ItemStack)} turtle's overlay} instead.
|
||||
*/
|
||||
public class TurtleOverlayRecipe extends ShapelessRecipe {
|
||||
public class TurtleOverlayRecipe extends CustomShapelessRecipe {
|
||||
private final ResourceLocation overlay;
|
||||
private final ItemStack result;
|
||||
|
||||
public TurtleOverlayRecipe(ResourceLocation id, String group, CraftingBookCategory category, ItemStack result, NonNullList<Ingredient> ingredients, ResourceLocation overlay) {
|
||||
super(id, group, category, result, ingredients);
|
||||
public TurtleOverlayRecipe(ResourceLocation id, ShapelessRecipeSpec spec, ResourceLocation overlay) {
|
||||
super(id, spec);
|
||||
this.overlay = overlay;
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
private static ItemStack make(ItemStack stack, ResourceLocation overlay) {
|
||||
@@ -56,63 +54,29 @@ public class TurtleOverlayRecipe extends ShapelessRecipe {
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecipeSerializer<?> getSerializer() {
|
||||
public RecipeSerializer<TurtleOverlayRecipe> getSerializer() {
|
||||
return ModRegistry.RecipeSerializers.TURTLE_OVERLAY.get();
|
||||
}
|
||||
|
||||
public static class Serializer implements RecipeSerializer<TurtleOverlayRecipe> {
|
||||
@Override
|
||||
public TurtleOverlayRecipe fromJson(ResourceLocation id, JsonObject json) {
|
||||
var group = GsonHelper.getAsString(json, "group", "");
|
||||
var category = CraftingBookCategory.CODEC.byName(GsonHelper.getAsString(json, "category", null), CraftingBookCategory.MISC);
|
||||
var ingredients = readIngredients(GsonHelper.getAsJsonArray(json, "ingredients"));
|
||||
|
||||
if (ingredients.isEmpty()) throw new JsonParseException("No ingredients for shapeless recipe");
|
||||
if (ingredients.size() > 9) {
|
||||
throw new JsonParseException("Too many ingredients for shapeless recipe the max is 9");
|
||||
}
|
||||
|
||||
var recipe = ShapelessRecipeSpec.fromJson(json);
|
||||
var overlay = new ResourceLocation(GsonHelper.getAsString(json, "overlay"));
|
||||
|
||||
// We could derive this from the ingredients, but we want to avoid evaluating the ingredients too early, so
|
||||
// it's easier to do this.
|
||||
var result = make(ShapedRecipe.itemStackFromJson(GsonHelper.getAsJsonObject(json, "result")), overlay);
|
||||
|
||||
return new TurtleOverlayRecipe(id, group, category, result, ingredients, overlay);
|
||||
}
|
||||
|
||||
private NonNullList<Ingredient> readIngredients(JsonArray arrays) {
|
||||
NonNullList<Ingredient> items = NonNullList.create();
|
||||
for (var i = 0; i < arrays.size(); ++i) {
|
||||
var ingredient = Ingredient.fromJson(arrays.get(i));
|
||||
if (!ingredient.isEmpty()) items.add(ingredient);
|
||||
}
|
||||
|
||||
return items;
|
||||
return new TurtleOverlayRecipe(id, recipe, overlay);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TurtleOverlayRecipe fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) {
|
||||
var group = buffer.readUtf();
|
||||
var category = buffer.readEnum(CraftingBookCategory.class);
|
||||
var count = buffer.readVarInt();
|
||||
var items = NonNullList.withSize(count, Ingredient.EMPTY);
|
||||
|
||||
for (var j = 0; j < items.size(); j++) items.set(j, Ingredient.fromNetwork(buffer));
|
||||
var result = buffer.readItem();
|
||||
var recipe = ShapelessRecipeSpec.fromNetwork(buffer);
|
||||
var overlay = buffer.readResourceLocation();
|
||||
|
||||
return new TurtleOverlayRecipe(id, group, category, result, items, overlay);
|
||||
return new TurtleOverlayRecipe(id, recipe, overlay);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toNetwork(FriendlyByteBuf buffer, TurtleOverlayRecipe recipe) {
|
||||
buffer.writeUtf(recipe.getGroup());
|
||||
buffer.writeEnum(recipe.category());
|
||||
buffer.writeVarInt(recipe.getIngredients().size());
|
||||
|
||||
for (var ingredient : recipe.getIngredients()) ingredient.toNetwork(buffer);
|
||||
buffer.writeItem(recipe.result);
|
||||
recipe.toSpec().toNetwork(buffer);
|
||||
buffer.writeResourceLocation(recipe.overlay);
|
||||
}
|
||||
}
|
||||
|
@@ -4,26 +4,33 @@
|
||||
|
||||
package dan200.computercraft.shared.turtle.recipes;
|
||||
|
||||
import com.mojang.serialization.DataResult;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.items.IComputerItem;
|
||||
import dan200.computercraft.shared.computer.recipe.ComputerFamilyRecipe;
|
||||
import dan200.computercraft.shared.computer.recipe.ComputerConvertRecipe;
|
||||
import dan200.computercraft.shared.recipe.ShapedRecipeSpec;
|
||||
import dan200.computercraft.shared.turtle.items.TurtleItem;
|
||||
import net.minecraft.core.NonNullList;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.crafting.CraftingBookCategory;
|
||||
import net.minecraft.world.item.crafting.Ingredient;
|
||||
import net.minecraft.world.item.crafting.RecipeSerializer;
|
||||
|
||||
public final class TurtleRecipe extends ComputerFamilyRecipe {
|
||||
public TurtleRecipe(ResourceLocation identifier, String group, CraftingBookCategory category, int width, int height, NonNullList<Ingredient> ingredients, ItemStack result, ComputerFamily family) {
|
||||
super(identifier, group, category, width, height, ingredients, result, family);
|
||||
/**
|
||||
* The recipe which crafts a turtle from an existing computer item.
|
||||
*/
|
||||
public final class TurtleRecipe extends ComputerConvertRecipe {
|
||||
private final TurtleItem turtle;
|
||||
|
||||
private TurtleRecipe(ResourceLocation id, ShapedRecipeSpec recipe, TurtleItem turtle) {
|
||||
super(id, recipe);
|
||||
this.turtle = turtle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecipeSerializer<?> getSerializer() {
|
||||
return ModRegistry.RecipeSerializers.TURTLE.get();
|
||||
public static DataResult<TurtleRecipe> of(ResourceLocation id, ShapedRecipeSpec recipe) {
|
||||
if (!(recipe.result().getItem() instanceof TurtleItem turtle)) {
|
||||
return DataResult.error(() -> recipe.result().getItem() + " is not a turtle item");
|
||||
}
|
||||
|
||||
return DataResult.success(new TurtleRecipe(id, recipe, turtle));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -31,13 +38,11 @@ public final class TurtleRecipe extends ComputerFamilyRecipe {
|
||||
var computerID = item.getComputerID(stack);
|
||||
var label = item.getLabel(stack);
|
||||
|
||||
return TurtleItem.create(computerID, label, -1, getFamily(), null, null, 0, null);
|
||||
return turtle.create(computerID, label, -1, null, null, 0, null);
|
||||
}
|
||||
|
||||
public static class Serializer extends ComputerFamilyRecipe.Serializer<TurtleRecipe> {
|
||||
@Override
|
||||
protected TurtleRecipe create(ResourceLocation identifier, String group, CraftingBookCategory category, int width, int height, NonNullList<Ingredient> ingredients, ItemStack result, ComputerFamily family) {
|
||||
return new TurtleRecipe(identifier, group, category, width, height, ingredients, result, family);
|
||||
}
|
||||
@Override
|
||||
public RecipeSerializer<TurtleRecipe> getSerializer() {
|
||||
return ModRegistry.RecipeSerializers.TURTLE.get();
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user