mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-10-22 17:37:38 +00:00
Compare commits
32 Commits
v1.20.1-1.
...
v1.20.1-1.
Author | SHA1 | Date | |
---|---|---|---|
![]() |
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 |
15
.reuse/dep5
15
.reuse/dep5
@@ -53,6 +53,7 @@ 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/*
|
||||
@@ -62,11 +63,23 @@ Copyright: 2011 Daniel Ratcliffe
|
||||
License: LicenseRef-CCPL
|
||||
|
||||
Files:
|
||||
projects/common/src/main/resources/assets/computercraft/lang/*
|
||||
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: MPL-2.0
|
||||
|
||||
Files:
|
||||
.github/*
|
||||
Comment:
|
||||
|
@@ -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")
|
||||
|
@@ -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 {
|
||||
|
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" />
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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.3
|
||||
isUnstable=true
|
||||
modVersion=1.109.0
|
||||
|
||||
# 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"
|
||||
@@ -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.1"
|
||||
checkstyle = "10.12.3"
|
||||
curseForgeGradle = "1.0.14"
|
||||
errorProne-core = "2.21.1"
|
||||
@@ -62,14 +62,15 @@ githubRelease = "2.4.1"
|
||||
ideaExt = "1.1.7"
|
||||
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.1"
|
||||
vanillaGradle = "0.2.1-SNAPSHOT"
|
||||
vineflower = "1.11.0"
|
||||
teavm = "0.9.0-SQUID.1"
|
||||
|
||||
[libraries]
|
||||
# Normal dependencies
|
||||
@@ -77,7 +78,8 @@ 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" }
|
||||
@@ -97,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" }
|
||||
@@ -114,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" }
|
||||
@@ -124,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" }
|
||||
@@ -135,6 +142,7 @@ 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" }
|
||||
@@ -154,7 +162,6 @@ vineflower = { module = "io.github.juuxel:loom-vineflower", version.ref = "vinef
|
||||
[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" }
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
; SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
||||
;
|
||||
; SPDX-License-Identifier: LicenseRef-CCPL
|
||||
; SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
(sources
|
||||
/doc/
|
||||
@@ -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.
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -7,9 +7,9 @@ import cc.tweaked.gradle.clientClasses
|
||||
import cc.tweaked.gradle.commonClasses
|
||||
|
||||
plugins {
|
||||
id("cc-tweaked.publishing")
|
||||
id("cc-tweaked.vanilla")
|
||||
id("cc-tweaked.gametest")
|
||||
id("cc-tweaked.publishing")
|
||||
}
|
||||
|
||||
minecraft {
|
||||
|
@@ -33,7 +33,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;
|
||||
|
||||
@@ -219,7 +218,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
|
||||
|
@@ -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");
|
||||
|
@@ -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;
|
||||
|
@@ -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<>();
|
||||
|
@@ -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}.
|
||||
*
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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);
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
@@ -377,6 +381,8 @@ public class MonitorBlockEntity extends BlockEntity {
|
||||
BlockEntityHelpers.updateBlock(monitor);
|
||||
}
|
||||
}
|
||||
|
||||
assertInvariant();
|
||||
}
|
||||
|
||||
void updateNeighborsDeferred() {
|
||||
@@ -487,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) {
|
||||
@@ -528,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;
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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 {
|
||||
|
@@ -70,7 +70,7 @@ public class TurtleInventoryCrafting implements CraftingContainer {
|
||||
if (recipe == null) return null;
|
||||
|
||||
// Special case: craft(0) just returns an empty list if crafting was possible
|
||||
if (maxCount == 0) return Collections.emptyList();
|
||||
if (maxCount == 0) return List.of();
|
||||
|
||||
var player = TurtlePlayer.get(turtle).player();
|
||||
|
||||
|
@@ -20,7 +20,6 @@ import java.io.OutputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -38,7 +37,7 @@ public final class NBTUtil {
|
||||
try {
|
||||
var ctor = CompoundTag.class.getDeclaredConstructor(Map.class);
|
||||
ctor.setAccessible(true);
|
||||
EMPTY_TAG = ctor.newInstance(Collections.emptyMap());
|
||||
EMPTY_TAG = ctor.newInstance(Map.of());
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
@@ -74,8 +74,6 @@
|
||||
"gui.computercraft.config.computer_space_limit.tooltip": "Limit diskového místa pro počítače a roboty, v bytech.",
|
||||
"gui.computercraft.config.default_computer_settings": "Vychozí nastavení počítače",
|
||||
"gui.computercraft.config.default_computer_settings.tooltip": "Čárkami oddělený seznam základních systémových nastavení pro nastavení na nových počítačích.\nPříklad: \"shell.autocomplete=false,lua.autocomplete=false,edit.autocomplete=false\"\nvypne všechno automatické vyplňování.",
|
||||
"gui.computercraft.config.disable_lua51_features": "Vypnout Lua 5.1 funkce",
|
||||
"gui.computercraft.config.disable_lua51_features.tooltip": "Nastav toto na pravdivou hodnotu pro vypnutí Lua 5.1 funkcí které budou odstraněny v budoucí\naktualizaci. Dobré pro ověření kompatibilty tvých programů nyní a předem.",
|
||||
"gui.computercraft.config.disabled_generic_methods": "Vypnuté obecné metody",
|
||||
"gui.computercraft.config.execution": "Spuštení",
|
||||
"gui.computercraft.config.execution.computer_threads": "Počítačová vlákna",
|
||||
|
@@ -27,6 +27,7 @@
|
||||
"commands.computercraft.desc": "Der /computercraft Befehl enthält verschiedene Werkzeuge um Computer zu debuggen, kontrollieren oder mit ihnen zu interagieren.",
|
||||
"commands.computercraft.dump.action": "Zeigt mehr Informationen über einen Computer",
|
||||
"commands.computercraft.dump.desc": "Zeigt den Status aller Computer oder genauere Informationen über einen angegebenen Computer. Der Computer kann entweder über seine Instanz ID (z.B. 123), seine Computer ID (z.B. #123) oder seinen Namen (z.B. \"@Mein Computer\") angegeben werden.",
|
||||
"commands.computercraft.dump.open_path": "Siehe die Dateien dieses Computers.",
|
||||
"commands.computercraft.dump.synopsis": "Zeigt den Status eines Computers.",
|
||||
"commands.computercraft.generic.additional_rows": "%d zusätzliche Zeilen…",
|
||||
"commands.computercraft.generic.exception": "Unbehandelte Ausnahme (%s)",
|
||||
@@ -68,12 +69,11 @@
|
||||
"commands.computercraft.view.not_player": "Konnte Terminal für Nicht-Spieler nicht öffnen",
|
||||
"commands.computercraft.view.synopsis": "Zeigt das Terminal eines Computers.",
|
||||
"gui.computercraft.config.command_require_creative": "Kommando-Computer benötigen den Kreativ-Modus",
|
||||
"gui.computercraft.config.command_require_creative.tooltip": "Benötigt Spieler sowohl im Kreativ-Spielmodus, als auch ein Operator zu sein,\num mit Kommando-Computern zu interagieren. Dies entspricht dem standardmäßigen Verhalten von Befehlsblöcken.",
|
||||
"gui.computercraft.config.computer_space_limit": "Speicherplatz von Computern (Bytes)",
|
||||
"gui.computercraft.config.computer_space_limit.tooltip": "Das Speicherplatzlimit für Computer und Turtles in Bytes.",
|
||||
"gui.computercraft.config.default_computer_settings": "Computer-Standardeinstellungen",
|
||||
"gui.computercraft.config.default_computer_settings.tooltip": "eine mit Komma separierte Liste an standardmäßige Systemeinstellungen für neuen Computern.\nBeispiel: \"shell.autocomplete=false,lua.autocomplete=false,edit.autocomplete=false\"\nwürde jegliche Autovervollständigung deaktivieren.",
|
||||
"gui.computercraft.config.disable_lua51_features": "Lua 5.1-Funktionen deaktivieren",
|
||||
"gui.computercraft.config.disable_lua51_features.tooltip": "Beim aktivieren werden die Lua 5.1 Funktionieren deaktiviert, die in zukünftigen\nUpdates sowieso entfernt werden. Auf dieser Weise kann vorwärts Kompatibilität für Programme sichergestellt werden.",
|
||||
"gui.computercraft.config.disabled_generic_methods": "Generische Methoden deaktiviert.",
|
||||
"gui.computercraft.config.disabled_generic_methods.tooltip": "Eine Liste an generischen Methoden oder Methodenquellen zum deaktivieren.\nGenerische Methoden sind Methoden die zu einem block/block entity hinzugefügt werden, insofern kein expliziter Peripheral Provider\ngefunden wurde. Mitbetroffen sind Inventarmethoden (d.h. inventory.getItemDetail,\ninventory.pushItems) und, wenn in Forge gespielt wird, die fluid_storage und energy_storage\nMethoden.\nMethoden in dieser Liste können entweder Gruppen von Methoden (wie computercraft:inventory)\noder einzelne Methoden (wie computercraft:inventory#pushItems) sein.\n",
|
||||
"gui.computercraft.config.execution": "Ausführung",
|
||||
|
@@ -33,7 +33,6 @@
|
||||
"commands.computercraft.shutdown.synopsis": "Apague las computadoras de forma remota.",
|
||||
"gui.computercraft.config.computer_space_limit": "Límite de memoria de ordenadores (en bytes)",
|
||||
"gui.computercraft.config.default_computer_settings": "Configuración de Ordenador por defecto",
|
||||
"gui.computercraft.config.disable_lua51_features": "Deshabilitar funciones de Lua 5.1",
|
||||
"gui.computercraft.config.floppy_space_limit": "Límite de memoria de disquetes (en bytes)",
|
||||
"gui.computercraft.config.http.enabled": "Habilitar API de HTTP",
|
||||
"gui.computercraft.config.log_computer_errors": "Grabar errores periféricos",
|
||||
|
@@ -74,9 +74,8 @@
|
||||
"gui.computercraft.config.computer_space_limit.tooltip": "La limite d'espace du disque pour les ordinateurs et les tortues, en octets.",
|
||||
"gui.computercraft.config.default_computer_settings": "Configuration d'Ordinateur par défaut",
|
||||
"gui.computercraft.config.default_computer_settings.tooltip": "Une liste séparée par des virgules des paramètres système par défaut à définir sur les nouveaux ordinateurs.\nExemple : \"shell.autocomplete=false,lua.autocomplete=false,edit.autocomplete=false\"\ndésactivera toute saisie semi-automatique.",
|
||||
"gui.computercraft.config.disable_lua51_features": "Désactiver les particularités de Lua 5.1",
|
||||
"gui.computercraft.config.disable_lua51_features.tooltip": "Définir sur true pour désactiver les fonctions Lua 5.1 qui seront supprimées dans une future mise à jour.\nUtile pour assurer la compatibilité ascendante de vos programmes actuels.",
|
||||
"gui.computercraft.config.disabled_generic_methods": "Méthodes génériques désactivées",
|
||||
"gui.computercraft.config.disabled_generic_methods.tooltip": "Une liste de méthodes génériques ou de méthodes source à désactiver.\nLes méthodes génériques sont ajoutées à un bloc ou bloc-entité lorsque aucune implémentation explicite de périphérique n'est disponible. Cela inclut les méthodes liées à l'inventaire (ex. inventory.getItemDetail, inventory.pushItems) et, avec Forge, les méthodes fluid_storage et energy_storage.\nLes méthodes de cette liste peuvent être groupées (computercraft:inventory) ou individuelles (computercraft:inventory#pushItems).\n",
|
||||
"gui.computercraft.config.execution": "Exécution",
|
||||
"gui.computercraft.config.execution.computer_threads": "Threads d'ordinateur",
|
||||
"gui.computercraft.config.execution.computer_threads.tooltip": "Définissez le nombre de threads sur lesquels les ordinateurs peuvent s'exécuter. Un nombre plus élevé signifie\nque plus d'ordinateurs peuvent fonctionner à la fois, mais peut induire un décalage. Veuillez noter que\ncertains mods peuvent ne pas fonctionner avec un nombre de threads supérieur à 1. À utiliser avec prudence.\nPlage : > 1",
|
||||
@@ -95,7 +94,7 @@
|
||||
"gui.computercraft.config.http.bandwidth.global_upload.tooltip": "Le nombre d'octets qui peuvent être téléversé en une seconde. Ceci est partagé sur tous les ordinateurs. (octets/s).\nPlage : > 1",
|
||||
"gui.computercraft.config.http.bandwidth.tooltip": "Limite la bande passante utilisée par les ordinateurs.",
|
||||
"gui.computercraft.config.http.enabled": "Permettre l'API HTTP",
|
||||
"gui.computercraft.config.http.enabled.tooltip": "Active l'API \"http\" sur les ordinateurs. Cela désactive également les programmes \"pastebin\" et \"wget\",\nsur lesquels de nombreux utilisateurs comptent. Il est recommandé de laisser cette option activée et\nd'utiliser l'option de configuration \"rules\" pour imposer un contrôle plus précis.",
|
||||
"gui.computercraft.config.http.enabled.tooltip": "Active l'API \"http\" sur les ordinateurs. La désactiver empêche également l'utilisation des programmes \"pastebin\" et \"wget\",\nsur lesquels de nombreux utilisateurs comptent. Il est recommandé de laisser cette option activée et\nd'utiliser l'option de configuration \"rules\" pour imposer un contrôle plus précis.",
|
||||
"gui.computercraft.config.http.max_requests": "Maximum de requêtes simultanées",
|
||||
"gui.computercraft.config.http.max_requests.tooltip": "Le nombre de requêtes http qu'un ordinateur peut effectuer en même temps.\nLes demandes supplémentaires seront mises en file d'attente et envoyées lorsque\nles demandes en cours seront terminées. Mettre à 0 pour illimité.\nPlage : > 0",
|
||||
"gui.computercraft.config.http.max_websockets": "Maximum de websockets en simultané",
|
||||
@@ -200,6 +199,8 @@
|
||||
"item.computercraft.printed_pages": "Pages imprimées",
|
||||
"item.computercraft.treasure_disk": "Disquette",
|
||||
"itemGroup.computercraft": "ComputerCraft",
|
||||
"tag.item.computercraft.computer": "Ordinateurs",
|
||||
"tag.item.computercraft.wired_modem": "Modems câblés",
|
||||
"tracking_field.computercraft.avg": "%s (moyenne)",
|
||||
"tracking_field.computercraft.computer_tasks.name": "Tâches",
|
||||
"tracking_field.computercraft.count": "%s (compte)",
|
||||
|
@@ -74,8 +74,6 @@
|
||||
"gui.computercraft.config.computer_space_limit.tooltip": "Limite di spazio di archiviazione per i computer e le tartarughe, in byte.",
|
||||
"gui.computercraft.config.default_computer_settings": "Impostazioni Computer predefinite",
|
||||
"gui.computercraft.config.default_computer_settings.tooltip": "Una lista di impostazioni predefinite per i nuovi computer, separate da virgola.\nEsempio: \"shell.autocomplete=false,lua.autocomplete=false,edit.autocomplete=false\"\ndisattiverà tutti gli autocompletamenti.",
|
||||
"gui.computercraft.config.disable_lua51_features": "Disattiva features Lua 5.1",
|
||||
"gui.computercraft.config.disable_lua51_features.tooltip": "Imposta a \"true\" per disattivare le funzionalità di Lua 5.1 che saranno rimosse in un\naggiornamento futuro. Utile per assicurare futura compatibilità con i tuoi programmi.",
|
||||
"gui.computercraft.config.execution": "Esecuzione",
|
||||
"gui.computercraft.config.execution.computer_threads": "Threads computer",
|
||||
"gui.computercraft.config.execution.computer_threads.tooltip": "Imposta la quantità di thread che possono eseguire i computer. Un numero più alto significa\nche più computer possono essere eseguiti alla volta, ma può indurre a lag. Alcune mod potrebbero\nnon funzionare con numeri di thread maggiore a 1. Usare con cautela.\nRange: > 1",
|
||||
|
@@ -70,7 +70,6 @@
|
||||
"commands.computercraft.view.synopsis": "컴퓨터의 터미널을 보기",
|
||||
"gui.computercraft.config.computer_space_limit": "컴퓨터 공간 제한 (바이트)",
|
||||
"gui.computercraft.config.default_computer_settings": "기본 컴퓨터 설정",
|
||||
"gui.computercraft.config.disable_lua51_features": "Lua 5.1 기능 미사용",
|
||||
"gui.computercraft.config.execution": "실행",
|
||||
"gui.computercraft.config.execution.computer_threads": "컴퓨터 쓰레드",
|
||||
"gui.computercraft.config.execution.max_main_computer_time": "컴퓨터 시간 당 서버 제한",
|
||||
|
@@ -38,7 +38,6 @@
|
||||
"commands.computercraft.turn_on.synopsis": "Liga computadores remotamente.",
|
||||
"gui.computercraft.config.computer_space_limit": "Limite de espaço dos Computadores (bytes)",
|
||||
"gui.computercraft.config.default_computer_settings": "Configurações padrão para Computadores",
|
||||
"gui.computercraft.config.disable_lua51_features": "Desabilitar funcionalidade da Lua 5.1",
|
||||
"gui.computercraft.config.execution.computer_threads": "Threads por computador",
|
||||
"gui.computercraft.config.floppy_space_limit": "Limite de espaço dos Disquetes (bytes)",
|
||||
"gui.computercraft.config.http": "HTTP",
|
||||
|
@@ -74,8 +74,6 @@
|
||||
"gui.computercraft.config.computer_space_limit.tooltip": "Лимит места на дисках компьютеров и черепашек, в байтах.",
|
||||
"gui.computercraft.config.default_computer_settings": "Настройки Компьютера по умолчанию",
|
||||
"gui.computercraft.config.default_computer_settings.tooltip": "Разделенный запятыми список системных настроек по умолчанию на новых компьютерах.\nНапример: \"shell.autocomplete=false,lua.autocomplete=false,edit.autocomplete=false\"\nотключит всё автодополнение.",
|
||||
"gui.computercraft.config.disable_lua51_features": "Отключить функции Lua 5.1",
|
||||
"gui.computercraft.config.disable_lua51_features.tooltip": "Поставьте, чтобы отключить функции из Lua 5.1, которые будут убраны в будущих\nобновлениях. Полезно для того, чтобы улучшить совместимость вперед ваших программ.",
|
||||
"gui.computercraft.config.execution": "Выполнение",
|
||||
"gui.computercraft.config.execution.computer_threads": "Потоки компьютера",
|
||||
"gui.computercraft.config.execution.computer_threads.tooltip": "Устанавливает количество потоков, на которых работают компьютеры. Большее число\nозначает, что больше компьютеров сможет работать одновременно, но может привести к лагу.\nОбратите внимание, что некоторые моды могут не работать с более чем одним потоком. Используйте с осторожностью.\nОграничение: > 1",
|
||||
|
@@ -70,7 +70,6 @@
|
||||
"commands.computercraft.view.synopsis": "Titta på datorns terminal.",
|
||||
"gui.computercraft.config.computer_space_limit": "Dator maximalt utrymme (bytes)",
|
||||
"gui.computercraft.config.default_computer_settings": "Standard Datorinställningar",
|
||||
"gui.computercraft.config.disable_lua51_features": "Avaktivera Lua 5.1 funktioner",
|
||||
"gui.computercraft.config.execution.computer_threads": "Dator trådar",
|
||||
"gui.computercraft.config.floppy_space_limit": "Diskett maximalt utrymme (bytes)",
|
||||
"gui.computercraft.config.http": "HTTP",
|
||||
|
@@ -66,7 +66,6 @@
|
||||
"commands.computercraft.view.not_player": "jan ala la, mi ken ala open e sitelen pi ilo sona",
|
||||
"commands.computercraft.view.synopsis": "o lukin e sitelen pi ilo sona.",
|
||||
"gui.computercraft.config.command_require_creative": "ilo sona pi toki wawa li wile e jan sewi",
|
||||
"gui.computercraft.config.disable_lua51_features": "ijo pona pi ilo Lua 5.1 li weka",
|
||||
"gui.computercraft.config.execution": "pali",
|
||||
"gui.computercraft.config.execution.computer_threads": "linja pi ilo sona",
|
||||
"gui.computercraft.config.http": "ijo HTTP",
|
||||
|
@@ -74,8 +74,6 @@
|
||||
"gui.computercraft.config.computer_space_limit.tooltip": "Обмеження на займаєме місце на диску комп'ютерами та черепахами, в байтах.",
|
||||
"gui.computercraft.config.default_computer_settings": "Налаштування комп'ютера за замовчуванням",
|
||||
"gui.computercraft.config.default_computer_settings.tooltip": "Список лашатувань за замовчуванням, розділених комою, що будуть нашалтовані на нових комп'ютерах\nНаприклад: \"shell.autocomplete=false,lua.autocomplete=false,edit.autocomplete=false\"\nвідключить усі автодоповнення.",
|
||||
"gui.computercraft.config.disable_lua51_features": "Відключити підтримку Lua 5.1",
|
||||
"gui.computercraft.config.disable_lua51_features.tooltip": "Включить це налаштування, якщо хочете відключити функціональність, пов'язану з Lua 5.1, яка буде вилучен в наступних оновленнях.\nТаким чином ви можете впевнитись, що ваші програми не зламаються від подальших оновлень.",
|
||||
"gui.computercraft.config.execution": "Виконання",
|
||||
"gui.computercraft.config.execution.computer_threads": "Потоки для комп'ютерів",
|
||||
"gui.computercraft.config.execution.computer_threads.tooltip": "Встановлює кількість потоків, на яких запускаються комп'ютери. Більше число\nозначає більшу кількість комп'ютерів, які працюють паралельно, але може призвести до проблем із продуктивністю.\nЗауважте, що деякі модифікації можуть не працювати із кількістю потоків більше за 1. Використовуйте з обережністю.\nОбмеження: > 1",
|
||||
|
@@ -68,7 +68,6 @@
|
||||
"commands.computercraft.view.synopsis": "查看计算机的终端.",
|
||||
"gui.computercraft.config.computer_space_limit": "计算机空间限制(字节)",
|
||||
"gui.computercraft.config.default_computer_settings": "默认计算机设置",
|
||||
"gui.computercraft.config.disable_lua51_features": "禁用Lua 5.1功能",
|
||||
"gui.computercraft.config.execution": "执行",
|
||||
"gui.computercraft.config.execution.computer_threads": "计算机线程数",
|
||||
"gui.computercraft.config.execution.max_main_computer_time": "服务器计算机tick时间限制",
|
||||
|
@@ -4,8 +4,6 @@
|
||||
|
||||
package dan200.computercraft.impl.network.wired;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import dan200.computercraft.api.network.wired.WiredElement;
|
||||
import dan200.computercraft.api.network.wired.WiredNetwork;
|
||||
import dan200.computercraft.api.network.wired.WiredNetworkChange;
|
||||
@@ -19,6 +17,7 @@ import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
@@ -47,24 +46,24 @@ public class NetworkTest {
|
||||
assertFalse(aN.getNetwork().connect(aN, bN), "Cannot add connection twice");
|
||||
|
||||
assertEquals(aN.getNetwork(), bN.getNetwork(), "A's and B's network must be equal");
|
||||
assertEquals(Sets.newHashSet(aN, bN), nodes(aN.getNetwork()), "A's network should be A and B");
|
||||
assertEquals(Set.of(aN, bN), nodes(aN.getNetwork()), "A's network should be A and B");
|
||||
|
||||
assertEquals(Sets.newHashSet("a", "b"), aE.allPeripherals().keySet(), "A's peripheral set should be A, B");
|
||||
assertEquals(Sets.newHashSet("a", "b"), bE.allPeripherals().keySet(), "B's peripheral set should be A, B");
|
||||
assertEquals(Set.of("a", "b"), aE.allPeripherals().keySet(), "A's peripheral set should be A, B");
|
||||
assertEquals(Set.of("a", "b"), bE.allPeripherals().keySet(), "B's peripheral set should be A, B");
|
||||
|
||||
aN.getNetwork().connect(aN, cN);
|
||||
|
||||
assertEquals(aN.getNetwork(), bN.getNetwork(), "A's and B's network must be equal");
|
||||
assertEquals(aN.getNetwork(), cN.getNetwork(), "A's and C's network must be equal");
|
||||
assertEquals(Sets.newHashSet(aN, bN, cN), nodes(aN.getNetwork()), "A's network should be A, B and C");
|
||||
assertEquals(Set.of(aN, bN, cN), nodes(aN.getNetwork()), "A's network should be A, B and C");
|
||||
|
||||
assertEquals(Sets.newHashSet(bN, cN), neighbours(aN), "A's neighbour set should be B, C");
|
||||
assertEquals(Sets.newHashSet(aN), neighbours(bN), "B's neighbour set should be A");
|
||||
assertEquals(Sets.newHashSet(aN), neighbours(cN), "C's neighbour set should be A");
|
||||
assertEquals(Set.of(bN, cN), neighbours(aN), "A's neighbour set should be B, C");
|
||||
assertEquals(Set.of(aN), neighbours(bN), "B's neighbour set should be A");
|
||||
assertEquals(Set.of(aN), neighbours(cN), "C's neighbour set should be A");
|
||||
|
||||
assertEquals(Sets.newHashSet("a", "b", "c"), aE.allPeripherals().keySet(), "A's peripheral set should be A, B, C");
|
||||
assertEquals(Sets.newHashSet("a", "b", "c"), bE.allPeripherals().keySet(), "B's peripheral set should be A, B, C");
|
||||
assertEquals(Sets.newHashSet("a", "b", "c"), cE.allPeripherals().keySet(), "C's peripheral set should be A, B, C");
|
||||
assertEquals(Set.of("a", "b", "c"), aE.allPeripherals().keySet(), "A's peripheral set should be A, B, C");
|
||||
assertEquals(Set.of("a", "b", "c"), bE.allPeripherals().keySet(), "B's peripheral set should be A, B, C");
|
||||
assertEquals(Set.of("a", "b", "c"), cE.allPeripherals().keySet(), "C's peripheral set should be A, B, C");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -87,11 +86,11 @@ public class NetworkTest {
|
||||
|
||||
assertEquals(aN.getNetwork(), bN.getNetwork(), "A's and B's network must be equal");
|
||||
assertEquals(aN.getNetwork(), cN.getNetwork(), "A's and C's network must be equal");
|
||||
assertEquals(Sets.newHashSet(aN, bN, cN), nodes(aN.getNetwork()), "A's network should be A, B and C");
|
||||
assertEquals(Set.of(aN, bN, cN), nodes(aN.getNetwork()), "A's network should be A, B and C");
|
||||
|
||||
assertEquals(Sets.newHashSet("a", "b", "c"), aE.allPeripherals().keySet(), "A's peripheral set should be A, B, C");
|
||||
assertEquals(Sets.newHashSet("a", "b", "c"), bE.allPeripherals().keySet(), "B's peripheral set should be A, B, C");
|
||||
assertEquals(Sets.newHashSet("a", "b", "c"), cE.allPeripherals().keySet(), "C's peripheral set should be A, B, C");
|
||||
assertEquals(Set.of("a", "b", "c"), aE.allPeripherals().keySet(), "A's peripheral set should be A, B, C");
|
||||
assertEquals(Set.of("a", "b", "c"), bE.allPeripherals().keySet(), "B's peripheral set should be A, B, C");
|
||||
assertEquals(Set.of("a", "b", "c"), cE.allPeripherals().keySet(), "C's peripheral set should be A, B, C");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -113,12 +112,12 @@ public class NetworkTest {
|
||||
|
||||
assertNotEquals(aN.getNetwork(), bN.getNetwork(), "A's and B's network must not be equal");
|
||||
assertEquals(aN.getNetwork(), cN.getNetwork(), "A's and C's network must be equal");
|
||||
assertEquals(Sets.newHashSet(aN, cN), nodes(aN.getNetwork()), "A's network should be A and C");
|
||||
assertEquals(Sets.newHashSet(bN), nodes(bN.getNetwork()), "B's network should be B");
|
||||
assertEquals(Set.of(aN, cN), nodes(aN.getNetwork()), "A's network should be A and C");
|
||||
assertEquals(Set.of(bN), nodes(bN.getNetwork()), "B's network should be B");
|
||||
|
||||
assertEquals(Sets.newHashSet("a", "c"), aE.allPeripherals().keySet(), "A's peripheral set should be A, C");
|
||||
assertEquals(Sets.newHashSet("b"), bE.allPeripherals().keySet(), "B's peripheral set should be B");
|
||||
assertEquals(Sets.newHashSet("a", "c"), cE.allPeripherals().keySet(), "C's peripheral set should be A, C");
|
||||
assertEquals(Set.of("a", "c"), aE.allPeripherals().keySet(), "A's peripheral set should be A, C");
|
||||
assertEquals(Set.of("b"), bE.allPeripherals().keySet(), "B's peripheral set should be B");
|
||||
assertEquals(Set.of("a", "c"), cE.allPeripherals().keySet(), "C's peripheral set should be A, C");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -146,11 +145,11 @@ public class NetworkTest {
|
||||
assertEquals(aN.getNetwork(), aaN.getNetwork(), "A's and A_'s network must be equal");
|
||||
assertEquals(bN.getNetwork(), bbN.getNetwork(), "B's and B_'s network must be equal");
|
||||
|
||||
assertEquals(Sets.newHashSet(aN, aaN), nodes(aN.getNetwork()), "A's network should be A and A_");
|
||||
assertEquals(Sets.newHashSet(bN, bbN), nodes(bN.getNetwork()), "B's network should be B and B_");
|
||||
assertEquals(Set.of(aN, aaN), nodes(aN.getNetwork()), "A's network should be A and A_");
|
||||
assertEquals(Set.of(bN, bbN), nodes(bN.getNetwork()), "B's network should be B and B_");
|
||||
|
||||
assertEquals(Sets.newHashSet("a", "a_"), aE.allPeripherals().keySet(), "A's peripheral set should be A and A_");
|
||||
assertEquals(Sets.newHashSet("b", "b_"), bE.allPeripherals().keySet(), "B's peripheral set should be B and B_");
|
||||
assertEquals(Set.of("a", "a_"), aE.allPeripherals().keySet(), "A's peripheral set should be A and A_");
|
||||
assertEquals(Set.of("b", "b_"), bE.allPeripherals().keySet(), "B's peripheral set should be B and B_");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -184,12 +183,12 @@ public class NetworkTest {
|
||||
assertNotEquals(aN.getNetwork(), bN.getNetwork(), "A's and B's network must not be equal");
|
||||
assertEquals(aN.getNetwork(), cN.getNetwork(), "A's and C's network must be equal");
|
||||
|
||||
assertEquals(Sets.newHashSet(aN, cN), nodes(aN.getNetwork()), "A's network should be A and C");
|
||||
assertEquals(Sets.newHashSet(bN), nodes(bN.getNetwork()), "B's network should be B");
|
||||
assertEquals(Set.of(aN, cN), nodes(aN.getNetwork()), "A's network should be A and C");
|
||||
assertEquals(Set.of(bN), nodes(bN.getNetwork()), "B's network should be B");
|
||||
|
||||
assertEquals(Sets.newHashSet("a", "c"), aE.allPeripherals().keySet(), "A's peripheral set should be A, C");
|
||||
assertEquals(Sets.newHashSet(), bE.allPeripherals().keySet(), "B's peripheral set should be empty");
|
||||
assertEquals(Sets.newHashSet("a", "c"), cE.allPeripherals().keySet(), "C's peripheral set should be A, C");
|
||||
assertEquals(Set.of("a", "c"), aE.allPeripherals().keySet(), "A's peripheral set should be A, C");
|
||||
assertEquals(Set.of(), bE.allPeripherals().keySet(), "B's peripheral set should be empty");
|
||||
assertEquals(Set.of("a", "c"), cE.allPeripherals().keySet(), "C's peripheral set should be A, C");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -220,13 +219,13 @@ public class NetworkTest {
|
||||
assertEquals(aN.getNetwork(), aaN.getNetwork(), "A's and A_'s network must be equal");
|
||||
assertEquals(bN.getNetwork(), bbN.getNetwork(), "B's and B_'s network must be equal");
|
||||
|
||||
assertEquals(Sets.newHashSet(aN, aaN), nodes(aN.getNetwork()), "A's network should be A and A_");
|
||||
assertEquals(Sets.newHashSet(bN, bbN), nodes(bN.getNetwork()), "B's network should be B and B_");
|
||||
assertEquals(Sets.newHashSet(cN), nodes(cN.getNetwork()), "C's network should be C");
|
||||
assertEquals(Set.of(aN, aaN), nodes(aN.getNetwork()), "A's network should be A and A_");
|
||||
assertEquals(Set.of(bN, bbN), nodes(bN.getNetwork()), "B's network should be B and B_");
|
||||
assertEquals(Set.of(cN), nodes(cN.getNetwork()), "C's network should be C");
|
||||
|
||||
assertEquals(Sets.newHashSet("a", "a_"), aE.allPeripherals().keySet(), "A's peripheral set should be A and A_");
|
||||
assertEquals(Sets.newHashSet("b", "b_"), bE.allPeripherals().keySet(), "B's peripheral set should be B and B_");
|
||||
assertEquals(Sets.newHashSet(), cE.allPeripherals().keySet(), "C's peripheral set should be empty");
|
||||
assertEquals(Set.of("a", "a_"), aE.allPeripherals().keySet(), "A's peripheral set should be A and A_");
|
||||
assertEquals(Set.of("b", "b_"), bE.allPeripherals().keySet(), "B's peripheral set should be B and B_");
|
||||
assertEquals(Set.of(), cE.allPeripherals().keySet(), "C's peripheral set should be empty");
|
||||
}
|
||||
|
||||
private static final int BRUTE_SIZE = 16;
|
||||
@@ -300,8 +299,8 @@ public class NetworkTest {
|
||||
private final Vec3 position;
|
||||
private final String id;
|
||||
private final WiredNode node;
|
||||
private final Map<String, IPeripheral> localPeripherals = Maps.newHashMap();
|
||||
private final Map<String, IPeripheral> remotePeripherals = Maps.newHashMap();
|
||||
private final Map<String, IPeripheral> localPeripherals = new HashMap<>();
|
||||
private final Map<String, IPeripheral> remotePeripherals = new HashMap<>();
|
||||
|
||||
private NetworkElement(Level world, Vec3 position, String id) {
|
||||
this.world = world;
|
||||
|
@@ -16,7 +16,6 @@ import org.hamcrest.Matcher;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -110,7 +109,7 @@ public class UploadFileMessageTest {
|
||||
@Provide
|
||||
Arbitrary<FileUpload> fileUpload() {
|
||||
return Combinators.combine(
|
||||
Arbitraries.oneOf(Arrays.asList(
|
||||
Arbitraries.oneOf(List.of(
|
||||
// 1.16 doesn't correctly handle unicode file names. We'll be generous in our tests here.
|
||||
Arbitraries.strings().ofMinLength(1).ascii().ofMaxLength(MAX_FILE_NAME),
|
||||
Arbitraries.strings().ofMinLength(1).ofMaxLength(MAX_FILE_NAME / 4)
|
||||
|
@@ -52,8 +52,8 @@ public class JsonDump {
|
||||
inputs[pos] = itemIds;
|
||||
}
|
||||
|
||||
private static final Set<Item> canonicalItem = new HashSet<>(Arrays.asList(
|
||||
private static final Set<Item> canonicalItem = Set.of(
|
||||
Items.GLASS_PANE, Items.STONE, Items.CHEST
|
||||
));
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -17,7 +17,10 @@ import net.minecraft.gametest.framework.GameTestGenerator
|
||||
import net.minecraft.gametest.framework.GameTestHelper
|
||||
import net.minecraft.gametest.framework.TestFunction
|
||||
import net.minecraft.nbt.CompoundTag
|
||||
import net.minecraft.world.entity.EntityType
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.level.block.Blocks
|
||||
import org.junit.jupiter.api.Assertions.*
|
||||
import java.util.*
|
||||
|
||||
class Monitor_Test {
|
||||
@@ -32,7 +35,7 @@ class Monitor_Test {
|
||||
|
||||
val toSet = BlockInput(
|
||||
ModRegistry.Blocks.MONITOR_ADVANCED.get().defaultBlockState(),
|
||||
Collections.emptySet(),
|
||||
emptySet(),
|
||||
tag,
|
||||
)
|
||||
|
||||
@@ -61,6 +64,45 @@ class Monitor_Test {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When a monitor is destroyed and then replaced, the terminal is recreated.
|
||||
*/
|
||||
@GameTest
|
||||
fun Creates_terminal(helper: GameTestHelper) = helper.sequence {
|
||||
fun monitorAt(x: Int) =
|
||||
helper.getBlockEntity(BlockPos(x, 2, 2), ModRegistry.BlockEntities.MONITOR_ADVANCED.get())
|
||||
|
||||
thenExecute {
|
||||
for (i in 1..3) {
|
||||
assertNull(monitorAt(i).cachedServerMonitor, "Monitor $i starts with no ServerMonitor")
|
||||
}
|
||||
|
||||
monitorAt(2).peripheral()
|
||||
assertNotNull(monitorAt(1).cachedServerMonitor?.terminal, "Creating a peripheral creates a terminal")
|
||||
|
||||
// Then remove the middle monitor and check it splits into two.
|
||||
helper.setBlock(BlockPos(2, 2, 2), Blocks.AIR.defaultBlockState())
|
||||
|
||||
assertNotNull(monitorAt(3).cachedServerMonitor, "Origin retains its monitor")
|
||||
assertNull(monitorAt(3).cachedServerMonitor!!.terminal, "Origin deletes the terminal")
|
||||
assertNotEquals(monitorAt(1).cachedServerMonitor, monitorAt(3).cachedServerMonitor, "Monitors are different")
|
||||
|
||||
// Then set the monitor, check it rejoins and recreates the terminal.
|
||||
val pos = BlockPos(2, 2, 2)
|
||||
helper.setBlock(pos, ModRegistry.Blocks.MONITOR_ADVANCED.get())
|
||||
ModRegistry.Blocks.MONITOR_ADVANCED.get().setPlacedBy(
|
||||
helper.level,
|
||||
helper.absolutePos(pos),
|
||||
helper.getBlockState(pos),
|
||||
EntityType.COW.create(helper.level),
|
||||
ItemStack.EMPTY,
|
||||
)
|
||||
monitorAt(2).peripheral()
|
||||
|
||||
assertNotNull(monitorAt(1).cachedServerMonitor?.terminal, "Recreates the terminal")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test monitors render correctly
|
||||
*/
|
||||
|
@@ -37,6 +37,7 @@ import net.minecraft.world.item.Items
|
||||
import net.minecraft.world.item.enchantment.Enchantments
|
||||
import net.minecraft.world.level.block.Blocks
|
||||
import net.minecraft.world.level.block.FenceBlock
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.Matchers.array
|
||||
@@ -86,6 +87,26 @@ class Turtle_Test {
|
||||
thenExecute { helper.assertBlockPresent(Blocks.LAVA, BlockPos(2, 2, 2)) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks turtles can write to signs.
|
||||
*
|
||||
* @see [#1611](https://github.com/cc-tweaked/CC-Tweaked/issues/1611)
|
||||
*/
|
||||
@GameTest
|
||||
fun Place_sign(helper: GameTestHelper) = helper.sequence {
|
||||
thenOnComputer {
|
||||
turtle.place(ObjectArguments("Test\nmessage")).await()
|
||||
.assertArrayEquals(true, message = "Placed sign")
|
||||
}
|
||||
thenExecute {
|
||||
val sign = helper.getBlockEntity(BlockPos(2, 2, 1), BlockEntityType.SIGN)
|
||||
val lines = listOf("", "Test", "message", "")
|
||||
for ((i, line) in lines.withIndex()) {
|
||||
assertEquals(line, sign.frontText.getMessage(i, false).string, "Line $i")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that calling [net.minecraft.world.item.Item.use] will not place blocks too far away.
|
||||
*
|
||||
|
@@ -17,7 +17,7 @@ import net.minecraft.gametest.framework.GameTestAssertException
|
||||
import net.minecraft.gametest.framework.GameTestAssertPosException
|
||||
import net.minecraft.gametest.framework.GameTestInfo
|
||||
import net.minecraft.gametest.framework.GameTestSequence
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.InputStream
|
||||
import java.util.*
|
||||
import java.util.concurrent.CancellationException
|
||||
@@ -33,7 +33,7 @@ import java.util.concurrent.atomic.AtomicReference
|
||||
* @see GameTestSequence.thenOnComputer
|
||||
*/
|
||||
object ManagedComputers : ILuaMachine.Factory {
|
||||
private val LOGGER = LogManager.getLogger(ManagedComputers::class.java)
|
||||
private val LOGGER = LoggerFactory.getLogger(ManagedComputers::class.java)
|
||||
private val computers: MutableMap<String, Queue<suspend LuaTaskContext.() -> Unit>> = mutableMapOf()
|
||||
|
||||
internal fun enqueue(test: GameTestInfo, label: String, task: suspend LuaTaskContext.() -> Unit): Monitor {
|
||||
|
139
projects/common/src/testMod/resources/data/cctest/structures/monitor_test.creates_terminal.snbt
generated
Normal file
139
projects/common/src/testMod/resources/data/cctest/structures/monitor_test.creates_terminal.snbt
generated
Normal file
@@ -0,0 +1,139 @@
|
||||
{
|
||||
DataVersion: 3465,
|
||||
size: [5, 5, 5],
|
||||
data: [
|
||||
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 1, 0], state: "minecraft:air"},
|
||||
{pos: [0, 1, 1], state: "minecraft:air"},
|
||||
{pos: [0, 1, 2], state: "minecraft:air"},
|
||||
{pos: [0, 1, 3], state: "minecraft:air"},
|
||||
{pos: [0, 1, 4], state: "minecraft:air"},
|
||||
{pos: [1, 1, 0], state: "minecraft:air"},
|
||||
{pos: [1, 1, 1], state: "minecraft:air"},
|
||||
{pos: [1, 1, 2], state: "computercraft:monitor_advanced{facing:north,orientation:north,state:l}", nbt: {Height: 1, Width: 3, XIndex: 2, YIndex: 0, id: "computercraft:monitor_advanced"}},
|
||||
{pos: [1, 1, 3], state: "minecraft:air"},
|
||||
{pos: [1, 1, 4], state: "minecraft:air"},
|
||||
{pos: [2, 1, 0], state: "minecraft:air"},
|
||||
{pos: [2, 1, 1], state: "minecraft:air"},
|
||||
{pos: [2, 1, 2], state: "computercraft:monitor_advanced{facing:north,orientation:north,state:lr}", nbt: {Height: 1, Width: 3, XIndex: 1, YIndex: 0, id: "computercraft:monitor_advanced"}},
|
||||
{pos: [2, 1, 3], state: "minecraft:air"},
|
||||
{pos: [2, 1, 4], state: "minecraft:air"},
|
||||
{pos: [3, 1, 0], state: "minecraft:air"},
|
||||
{pos: [3, 1, 1], state: "minecraft:air"},
|
||||
{pos: [3, 1, 2], state: "computercraft:monitor_advanced{facing:north,orientation:north,state:r}", nbt: {Height: 1, Width: 3, XIndex: 0, YIndex: 0, id: "computercraft:monitor_advanced"}},
|
||||
{pos: [3, 1, 3], state: "minecraft:air"},
|
||||
{pos: [3, 1, 4], state: "minecraft:air"},
|
||||
{pos: [4, 1, 0], state: "minecraft:air"},
|
||||
{pos: [4, 1, 1], state: "minecraft:air"},
|
||||
{pos: [4, 1, 2], state: "minecraft:air"},
|
||||
{pos: [4, 1, 3], state: "minecraft:air"},
|
||||
{pos: [4, 1, 4], state: "minecraft:air"},
|
||||
{pos: [0, 3, 0], state: "minecraft:air"},
|
||||
{pos: [0, 2, 1], state: "minecraft:air"},
|
||||
{pos: [0, 2, 2], state: "minecraft:air"},
|
||||
{pos: [0, 2, 3], state: "minecraft:air"},
|
||||
{pos: [0, 2, 4], state: "minecraft:air"},
|
||||
{pos: [1, 2, 0], state: "minecraft:air"},
|
||||
{pos: [1, 2, 1], state: "minecraft:air"},
|
||||
{pos: [1, 2, 2], state: "minecraft:air"},
|
||||
{pos: [1, 2, 3], state: "minecraft:air"},
|
||||
{pos: [1, 2, 4], state: "minecraft:air"},
|
||||
{pos: [2, 2, 0], state: "minecraft:air"},
|
||||
{pos: [2, 2, 1], state: "minecraft:air"},
|
||||
{pos: [2, 2, 2], state: "minecraft:air"},
|
||||
{pos: [2, 2, 3], state: "minecraft:air"},
|
||||
{pos: [2, 2, 4], state: "minecraft:air"},
|
||||
{pos: [3, 2, 0], state: "minecraft:air"},
|
||||
{pos: [3, 2, 1], state: "minecraft:air"},
|
||||
{pos: [3, 2, 2], state: "minecraft:air"},
|
||||
{pos: [3, 2, 3], state: "minecraft:air"},
|
||||
{pos: [3, 2, 4], state: "minecraft:air"},
|
||||
{pos: [4, 2, 0], state: "minecraft:air"},
|
||||
{pos: [4, 2, 1], state: "minecraft:air"},
|
||||
{pos: [4, 2, 2], state: "minecraft:air"},
|
||||
{pos: [4, 2, 3], state: "minecraft:air"},
|
||||
{pos: [4, 2, 4], state: "minecraft:air"},
|
||||
{pos: [0, 3, 0], state: "minecraft:air"},
|
||||
{pos: [0, 3, 1], state: "minecraft:air"},
|
||||
{pos: [0, 3, 2], state: "minecraft:air"},
|
||||
{pos: [0, 3, 3], state: "minecraft:air"},
|
||||
{pos: [0, 3, 4], state: "minecraft:air"},
|
||||
{pos: [1, 3, 0], state: "minecraft:air"},
|
||||
{pos: [1, 3, 1], state: "minecraft:air"},
|
||||
{pos: [1, 3, 2], state: "minecraft:air"},
|
||||
{pos: [1, 3, 3], state: "minecraft:air"},
|
||||
{pos: [1, 3, 4], state: "minecraft:air"},
|
||||
{pos: [2, 3, 0], state: "minecraft:air"},
|
||||
{pos: [2, 3, 1], state: "minecraft:air"},
|
||||
{pos: [2, 3, 2], state: "minecraft:air"},
|
||||
{pos: [2, 3, 3], state: "minecraft:air"},
|
||||
{pos: [2, 3, 4], state: "minecraft:air"},
|
||||
{pos: [3, 3, 0], state: "minecraft:air"},
|
||||
{pos: [3, 3, 1], state: "minecraft:air"},
|
||||
{pos: [3, 3, 2], state: "minecraft:air"},
|
||||
{pos: [3, 3, 3], state: "minecraft:air"},
|
||||
{pos: [3, 3, 4], state: "minecraft:air"},
|
||||
{pos: [4, 3, 0], state: "minecraft:air"},
|
||||
{pos: [4, 3, 1], state: "minecraft:air"},
|
||||
{pos: [4, 3, 2], state: "minecraft:air"},
|
||||
{pos: [4, 3, 3], state: "minecraft:air"},
|
||||
{pos: [4, 3, 4], state: "minecraft:air"},
|
||||
{pos: [0, 4, 0], state: "minecraft:air"},
|
||||
{pos: [0, 4, 1], state: "minecraft:air"},
|
||||
{pos: [0, 4, 2], state: "minecraft:air"},
|
||||
{pos: [0, 4, 3], state: "minecraft:air"},
|
||||
{pos: [0, 4, 4], state: "minecraft:air"},
|
||||
{pos: [1, 4, 0], state: "minecraft:air"},
|
||||
{pos: [1, 4, 1], state: "minecraft:air"},
|
||||
{pos: [1, 4, 2], state: "minecraft:air"},
|
||||
{pos: [1, 4, 3], state: "minecraft:air"},
|
||||
{pos: [1, 4, 4], state: "minecraft:air"},
|
||||
{pos: [2, 4, 0], state: "minecraft:air"},
|
||||
{pos: [2, 4, 1], state: "minecraft:air"},
|
||||
{pos: [2, 4, 2], state: "minecraft:air"},
|
||||
{pos: [2, 4, 3], state: "minecraft:air"},
|
||||
{pos: [2, 4, 4], state: "minecraft:air"},
|
||||
{pos: [3, 4, 0], state: "minecraft:air"},
|
||||
{pos: [3, 4, 1], state: "minecraft:air"},
|
||||
{pos: [3, 4, 2], state: "minecraft:air"},
|
||||
{pos: [3, 4, 3], state: "minecraft:air"},
|
||||
{pos: [3, 4, 4], state: "minecraft:air"},
|
||||
{pos: [4, 4, 0], state: "minecraft:air"},
|
||||
{pos: [4, 4, 1], state: "minecraft:air"},
|
||||
{pos: [4, 4, 2], state: "minecraft:air"},
|
||||
{pos: [4, 4, 3], state: "minecraft:air"},
|
||||
{pos: [4, 4, 4], state: "minecraft:air"}
|
||||
],
|
||||
entities: [],
|
||||
palette: [
|
||||
"minecraft:polished_andesite",
|
||||
"minecraft:air",
|
||||
"computercraft:monitor_advanced{facing:north,orientation:north,state:l}",
|
||||
"computercraft:monitor_advanced{facing:north,orientation:north,state:lr}",
|
||||
"computercraft:monitor_advanced{facing:north,orientation:north,state:r}"
|
||||
]
|
||||
}
|
137
projects/common/src/testMod/resources/data/cctest/structures/turtle_test.place_sign.snbt
generated
Normal file
137
projects/common/src/testMod/resources/data/cctest/structures/turtle_test.place_sign.snbt
generated
Normal file
@@ -0,0 +1,137 @@
|
||||
{
|
||||
DataVersion: 3465,
|
||||
size: [5, 5, 5],
|
||||
data: [
|
||||
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 1, 0], state: "minecraft:air"},
|
||||
{pos: [0, 1, 1], state: "minecraft:air"},
|
||||
{pos: [0, 1, 2], state: "minecraft:air"},
|
||||
{pos: [0, 1, 3], state: "minecraft:air"},
|
||||
{pos: [0, 1, 4], state: "minecraft:air"},
|
||||
{pos: [1, 1, 0], state: "minecraft:air"},
|
||||
{pos: [1, 1, 1], state: "minecraft:air"},
|
||||
{pos: [1, 1, 2], state: "minecraft:air"},
|
||||
{pos: [1, 1, 3], state: "minecraft:air"},
|
||||
{pos: [1, 1, 4], state: "minecraft:air"},
|
||||
{pos: [2, 1, 0], state: "minecraft:air"},
|
||||
{pos: [2, 1, 1], state: "minecraft:air"},
|
||||
{pos: [2, 1, 2], state: "computercraft:turtle_normal{facing:north,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 0, Items: [{Count: 1b, Slot: 0b, id: "minecraft:oak_sign"}], Label: "turtle_test.place_sign", On: 1b, Owner: {LowerId: -7553144124013011039L, Name: "Player840", UpperId: 8473952144820417152L}, Slot: 0, id: "computercraft:turtle_normal"}},
|
||||
{pos: [2, 1, 3], state: "minecraft:air"},
|
||||
{pos: [2, 1, 4], state: "minecraft:air"},
|
||||
{pos: [3, 1, 0], state: "minecraft:air"},
|
||||
{pos: [3, 1, 1], state: "minecraft:air"},
|
||||
{pos: [3, 1, 2], state: "minecraft:air"},
|
||||
{pos: [3, 1, 3], state: "minecraft:air"},
|
||||
{pos: [3, 1, 4], state: "minecraft:air"},
|
||||
{pos: [4, 1, 0], state: "minecraft:air"},
|
||||
{pos: [4, 1, 1], state: "minecraft:air"},
|
||||
{pos: [4, 1, 2], state: "minecraft:air"},
|
||||
{pos: [4, 1, 3], state: "minecraft:air"},
|
||||
{pos: [4, 1, 4], state: "minecraft:air"},
|
||||
{pos: [0, 2, 0], state: "minecraft:air"},
|
||||
{pos: [0, 2, 1], state: "minecraft:air"},
|
||||
{pos: [0, 2, 2], state: "minecraft:air"},
|
||||
{pos: [0, 2, 3], state: "minecraft:air"},
|
||||
{pos: [0, 2, 4], state: "minecraft:air"},
|
||||
{pos: [1, 2, 0], state: "minecraft:air"},
|
||||
{pos: [1, 2, 1], state: "minecraft:air"},
|
||||
{pos: [1, 2, 2], state: "minecraft:air"},
|
||||
{pos: [1, 2, 3], state: "minecraft:air"},
|
||||
{pos: [1, 2, 4], state: "minecraft:air"},
|
||||
{pos: [2, 2, 0], state: "minecraft:air"},
|
||||
{pos: [2, 2, 1], state: "minecraft:air"},
|
||||
{pos: [2, 2, 2], state: "minecraft:air"},
|
||||
{pos: [2, 2, 3], state: "minecraft:air"},
|
||||
{pos: [2, 2, 4], state: "minecraft:air"},
|
||||
{pos: [3, 2, 0], state: "minecraft:air"},
|
||||
{pos: [3, 2, 1], state: "minecraft:air"},
|
||||
{pos: [3, 2, 2], state: "minecraft:air"},
|
||||
{pos: [3, 2, 3], state: "minecraft:air"},
|
||||
{pos: [3, 2, 4], state: "minecraft:air"},
|
||||
{pos: [4, 2, 0], state: "minecraft:air"},
|
||||
{pos: [4, 2, 1], state: "minecraft:air"},
|
||||
{pos: [4, 2, 2], state: "minecraft:air"},
|
||||
{pos: [4, 2, 3], state: "minecraft:air"},
|
||||
{pos: [4, 2, 4], state: "minecraft:air"},
|
||||
{pos: [0, 3, 0], state: "minecraft:air"},
|
||||
{pos: [0, 3, 1], state: "minecraft:air"},
|
||||
{pos: [0, 3, 2], state: "minecraft:air"},
|
||||
{pos: [0, 3, 3], state: "minecraft:air"},
|
||||
{pos: [0, 3, 4], state: "minecraft:air"},
|
||||
{pos: [1, 3, 0], state: "minecraft:air"},
|
||||
{pos: [1, 3, 1], state: "minecraft:air"},
|
||||
{pos: [1, 3, 2], state: "minecraft:air"},
|
||||
{pos: [1, 3, 3], state: "minecraft:air"},
|
||||
{pos: [1, 3, 4], state: "minecraft:air"},
|
||||
{pos: [2, 3, 0], state: "minecraft:air"},
|
||||
{pos: [2, 3, 1], state: "minecraft:air"},
|
||||
{pos: [2, 3, 2], state: "minecraft:air"},
|
||||
{pos: [2, 3, 3], state: "minecraft:air"},
|
||||
{pos: [2, 3, 4], state: "minecraft:air"},
|
||||
{pos: [3, 3, 0], state: "minecraft:air"},
|
||||
{pos: [3, 3, 1], state: "minecraft:air"},
|
||||
{pos: [3, 3, 2], state: "minecraft:air"},
|
||||
{pos: [3, 3, 3], state: "minecraft:air"},
|
||||
{pos: [3, 3, 4], state: "minecraft:air"},
|
||||
{pos: [4, 3, 0], state: "minecraft:air"},
|
||||
{pos: [4, 3, 1], state: "minecraft:air"},
|
||||
{pos: [4, 3, 2], state: "minecraft:air"},
|
||||
{pos: [4, 3, 3], state: "minecraft:air"},
|
||||
{pos: [4, 3, 4], state: "minecraft:air"},
|
||||
{pos: [0, 4, 0], state: "minecraft:air"},
|
||||
{pos: [0, 4, 1], state: "minecraft:air"},
|
||||
{pos: [0, 4, 2], state: "minecraft:air"},
|
||||
{pos: [0, 4, 3], state: "minecraft:air"},
|
||||
{pos: [0, 4, 4], state: "minecraft:air"},
|
||||
{pos: [1, 4, 0], state: "minecraft:air"},
|
||||
{pos: [1, 4, 1], state: "minecraft:air"},
|
||||
{pos: [1, 4, 2], state: "minecraft:air"},
|
||||
{pos: [1, 4, 3], state: "minecraft:air"},
|
||||
{pos: [1, 4, 4], state: "minecraft:air"},
|
||||
{pos: [2, 4, 0], state: "minecraft:air"},
|
||||
{pos: [2, 4, 1], state: "minecraft:air"},
|
||||
{pos: [2, 4, 2], state: "minecraft:air"},
|
||||
{pos: [2, 4, 3], state: "minecraft:air"},
|
||||
{pos: [2, 4, 4], state: "minecraft:air"},
|
||||
{pos: [3, 4, 0], state: "minecraft:air"},
|
||||
{pos: [3, 4, 1], state: "minecraft:air"},
|
||||
{pos: [3, 4, 2], state: "minecraft:air"},
|
||||
{pos: [3, 4, 3], state: "minecraft:air"},
|
||||
{pos: [3, 4, 4], state: "minecraft:air"},
|
||||
{pos: [4, 4, 0], state: "minecraft:air"},
|
||||
{pos: [4, 4, 1], state: "minecraft:air"},
|
||||
{pos: [4, 4, 2], state: "minecraft:air"},
|
||||
{pos: [4, 4, 3], state: "minecraft:air"},
|
||||
{pos: [4, 4, 4], state: "minecraft:air"}
|
||||
],
|
||||
entities: [],
|
||||
palette: [
|
||||
"minecraft:polished_andesite",
|
||||
"minecraft:air",
|
||||
"computercraft:turtle_normal{facing:north,waterlogged:false}"
|
||||
]
|
||||
}
|
@@ -7,7 +7,8 @@ package dan200.computercraft.api.filesystem;
|
||||
import javax.annotation.Nullable;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.time.Instant;
|
||||
|
||||
import static dan200.computercraft.api.filesystem.MountConstants.EPOCH;
|
||||
|
||||
/**
|
||||
* A simple version of {@link BasicFileAttributes}, which provides what information a {@link Mount} already exposes.
|
||||
@@ -20,8 +21,6 @@ import java.time.Instant;
|
||||
public record FileAttributes(
|
||||
boolean isDirectory, long size, FileTime creationTime, FileTime lastModifiedTime
|
||||
) implements BasicFileAttributes {
|
||||
private static final FileTime EPOCH = FileTime.from(Instant.EPOCH);
|
||||
|
||||
/**
|
||||
* Create a new {@link FileAttributes} instance with the {@linkplain #creationTime() creation time} and
|
||||
* {@linkplain #lastModifiedTime() last modified time} set to the Unix epoch.
|
||||
|
@@ -0,0 +1,85 @@
|
||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.api.filesystem;
|
||||
|
||||
import java.nio.file.OpenOption;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Useful constants functions for working with mounts.
|
||||
*
|
||||
* @see Mount
|
||||
* @see WritableMount
|
||||
*/
|
||||
public final class MountConstants {
|
||||
/**
|
||||
* A {@link FileTime} set to the Unix EPOCH, intended for {@link BasicFileAttributes}'s file times.
|
||||
*/
|
||||
public static final FileTime EPOCH = FileTime.from(Instant.EPOCH);
|
||||
|
||||
/**
|
||||
* The minimum size of a file for file {@linkplain WritableMount#getCapacity() capacity calculations}.
|
||||
*/
|
||||
public static final long MINIMUM_FILE_SIZE = 500;
|
||||
|
||||
/**
|
||||
* The error message used when the file does not exist.
|
||||
*/
|
||||
public static final String NO_SUCH_FILE = "No such file";
|
||||
|
||||
/**
|
||||
* The error message used when trying to use a file as a directory (for instance when
|
||||
* {@linkplain Mount#list(String, List) listing its contents}).
|
||||
*/
|
||||
public static final String NOT_A_DIRECTORY = "Not a directory";
|
||||
|
||||
/**
|
||||
* The error message used when trying to use a directory as a file (for instance when
|
||||
* {@linkplain Mount#openForRead(String) opening for reading}).
|
||||
*/
|
||||
public static final String NOT_A_FILE = "Not a file";
|
||||
|
||||
/**
|
||||
* The error message used when attempting to modify a read-only file or mount.
|
||||
*/
|
||||
public static final String ACCESS_DENIED = "Access denied";
|
||||
|
||||
/**
|
||||
* The error message used when trying to overwrite a file (for instance when
|
||||
* {@linkplain WritableMount#rename(String, String) renaming files} or {@linkplain WritableMount#makeDirectory(String)
|
||||
* creating directories}).
|
||||
*/
|
||||
public static final String FILE_EXISTS = "File exists";
|
||||
|
||||
/**
|
||||
* The error message used when trying to {@linkplain WritableMount#openForWrite(String) opening a directory to read}.
|
||||
*/
|
||||
public static final String CANNOT_WRITE_TO_DIRECTORY = "Cannot write to directory";
|
||||
|
||||
/**
|
||||
* The error message used when the mount runs out of space.
|
||||
*/
|
||||
public static final String OUT_OF_SPACE = "Out of space";
|
||||
|
||||
/**
|
||||
* The error message to throw when an unsupported set of options were passed to
|
||||
* {@link WritableMount#openFile(String, Set)}.
|
||||
*/
|
||||
public static final String UNSUPPORTED_MODE = "Unsupported mode";
|
||||
|
||||
public static final Set<OpenOption> READ_OPTIONS = Set.of(StandardOpenOption.READ);
|
||||
|
||||
public static final Set<OpenOption> WRITE_OPTIONS = Set.of(StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
|
||||
public static final Set<OpenOption> APPEND_OPTIONS = Set.of(StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.APPEND);
|
||||
|
||||
private MountConstants() {
|
||||
}
|
||||
}
|
@@ -7,8 +7,13 @@ package dan200.computercraft.api.filesystem;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.nio.file.OpenOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.nio.file.attribute.FileAttribute;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents a part of a virtual filesystem that can be mounted onto a computer using {@link IComputerAccess#mount(String, Mount)}
|
||||
@@ -51,22 +56,51 @@ public interface WritableMount extends Mount {
|
||||
void rename(String source, String dest) throws IOException;
|
||||
|
||||
/**
|
||||
* Opens a file with a given path, and returns an {@link OutputStream} for writing to it.
|
||||
* Opens a file with a given path, and returns an {@link SeekableByteChannel} for writing to it.
|
||||
*
|
||||
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram".
|
||||
* @return A stream for writing to.
|
||||
* @return A channel for writing to.
|
||||
* @throws IOException If the file could not be opened for writing.
|
||||
* @deprecated Replaced with more the generic {@link #openFile(String, Set)}.
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
SeekableByteChannel openForWrite(String path) throws IOException;
|
||||
|
||||
/**
|
||||
* Opens a file with a given path, and returns an {@link OutputStream} for appending to it.
|
||||
* Opens a file with a given path, and returns an {@link SeekableByteChannel} for appending to it.
|
||||
*
|
||||
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram".
|
||||
* @return A stream for writing to.
|
||||
* @return A channel for writing to.
|
||||
* @throws IOException If the file could not be opened for writing.
|
||||
* @deprecated Replaced with more the generic {@link #openFile(String, Set)}.
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
SeekableByteChannel openForAppend(String path) throws IOException;
|
||||
|
||||
/**
|
||||
* Opens a file with a given path, and returns an {@link SeekableByteChannel}.
|
||||
* <p>
|
||||
* This allows opening a file in a variety of options, much like {@link FileChannel#open(Path, Set, FileAttribute[])}.
|
||||
* <p>
|
||||
* At minimum, the option sets {@link MountConstants#READ_OPTIONS}, {@link MountConstants#WRITE_OPTIONS} and
|
||||
* {@link MountConstants#APPEND_OPTIONS} should be supported. It is recommended any valid combination of
|
||||
* {@link StandardOpenOption#READ}, {@link StandardOpenOption#WRITE}, {@link StandardOpenOption#CREATE},
|
||||
* {@link StandardOpenOption#TRUNCATE_EXISTING} and {@link StandardOpenOption#APPEND} are supported.
|
||||
* <p>
|
||||
* Unsupported modes (or combinations of modes) should throw an exception with the message
|
||||
* {@link MountConstants#UNSUPPORTED_MODE "Unsupported mode"}.
|
||||
*
|
||||
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram".
|
||||
* @param options For options used for opening a file.
|
||||
* @return A channel for writing to.
|
||||
* @throws IOException If the file could not be opened for writing.
|
||||
*/
|
||||
SeekableByteChannel openForAppend(String path) throws IOException;
|
||||
default SeekableByteChannel openFile(String path, Set<OpenOption> options) throws IOException {
|
||||
if (options.equals(MountConstants.READ_OPTIONS)) return openForRead(path);
|
||||
if (options.equals(MountConstants.WRITE_OPTIONS)) return openForWrite(path);
|
||||
if (options.equals(MountConstants.APPEND_OPTIONS)) return openForAppend(path);
|
||||
throw new IOException(MountConstants.UNSUPPORTED_MODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the amount of free space on the mount, in bytes. You should decrease this value as the user writes to the
|
||||
|
@@ -195,6 +195,19 @@ public interface IArguments {
|
||||
return LuaValues.encode(getString(index));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the argument, converting it to the raw-byte representation of its string by following Lua conventions.
|
||||
* <p>
|
||||
* This is equivalent to {@link #getStringCoerced(int)}, but then
|
||||
*
|
||||
* @param index The argument number.
|
||||
* @return The argument's value. This is a <em>read only</em> buffer.
|
||||
* @throws LuaException If the argument cannot be converted to Java.
|
||||
*/
|
||||
default ByteBuffer getBytesCoerced(int index) throws LuaException {
|
||||
return LuaValues.encode(getStringCoerced(index));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a string argument as an enum value.
|
||||
*
|
||||
|
@@ -7,7 +7,6 @@ package dan200.computercraft.api.peripheral;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -35,7 +34,7 @@ public interface IPeripheral {
|
||||
* @see PeripheralType#getAdditionalTypes()
|
||||
*/
|
||||
default Set<String> getAdditionalTypes() {
|
||||
return Collections.emptySet();
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -6,7 +6,6 @@ package dan200.computercraft.api.peripheral;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -16,7 +15,7 @@ import java.util.Set;
|
||||
* lexicographically smallest non-empty name being chosen.
|
||||
*/
|
||||
public final class PeripheralType {
|
||||
private static final PeripheralType UNTYPED = new PeripheralType(null, Collections.emptySet());
|
||||
private static final PeripheralType UNTYPED = new PeripheralType(null, Set.of());
|
||||
|
||||
private final @Nullable String type;
|
||||
private final Set<String> additionalTypes;
|
||||
@@ -46,7 +45,7 @@ public final class PeripheralType {
|
||||
*/
|
||||
public static PeripheralType ofType(String type) {
|
||||
checkTypeName("type cannot be null or empty");
|
||||
return new PeripheralType(type, Collections.emptySet());
|
||||
return new PeripheralType(type, Set.of());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -118,8 +117,8 @@ public final class PeripheralType {
|
||||
}
|
||||
|
||||
private static Set<String> getTypes(Collection<String> types) {
|
||||
if (types.isEmpty()) return Collections.emptySet();
|
||||
if (types.size() == 1) return Collections.singleton(types.iterator().next());
|
||||
if (types.isEmpty()) return Set.of();
|
||||
if (types.size() == 1) return Set.of(types.iterator().next());
|
||||
return Set.copyOf(types);
|
||||
}
|
||||
}
|
||||
|
@@ -2,6 +2,8 @@
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
import cc.tweaked.gradle.getAbsolutePath
|
||||
|
||||
plugins {
|
||||
`java-library`
|
||||
`java-test-fixtures`
|
||||
@@ -24,13 +26,13 @@ dependencies {
|
||||
implementation(libs.netty.socks)
|
||||
implementation(libs.netty.proxy)
|
||||
implementation(libs.slf4j)
|
||||
implementation(libs.asm)
|
||||
|
||||
testFixturesImplementation(libs.slf4j)
|
||||
testFixturesApi(platform(libs.kotlin.platform))
|
||||
testFixturesApi(libs.bundles.test)
|
||||
testFixturesApi(libs.bundles.kotlin)
|
||||
|
||||
testImplementation(libs.asm)
|
||||
testImplementation(libs.bundles.test)
|
||||
testRuntimeOnly(libs.bundles.testRuntime)
|
||||
testRuntimeOnly(libs.slf4j.simple)
|
||||
@@ -45,7 +47,7 @@ tasks.processResources {
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
systemProperty("cct.test-files", buildDir.resolve("tmp/testFiles").absolutePath)
|
||||
systemProperty("cct.test-files", layout.buildDirectory.dir("tmp/testFiles").getAbsolutePath())
|
||||
}
|
||||
|
||||
tasks.testFixturesJar {
|
||||
|
@@ -8,8 +8,9 @@ import dan200.computercraft.api.lua.ILuaAPIFactory;
|
||||
import dan200.computercraft.core.asm.GenericMethod;
|
||||
import dan200.computercraft.core.asm.LuaMethodSupplier;
|
||||
import dan200.computercraft.core.asm.PeripheralMethodSupplier;
|
||||
import dan200.computercraft.core.computer.ComputerThread;
|
||||
import dan200.computercraft.core.computer.GlobalEnvironment;
|
||||
import dan200.computercraft.core.computer.computerthread.ComputerScheduler;
|
||||
import dan200.computercraft.core.computer.computerthread.ComputerThread;
|
||||
import dan200.computercraft.core.computer.mainthread.MainThreadScheduler;
|
||||
import dan200.computercraft.core.computer.mainthread.NoWorkMainThreadScheduler;
|
||||
import dan200.computercraft.core.lua.CobaltLuaMachine;
|
||||
@@ -31,7 +32,7 @@ import java.util.concurrent.TimeUnit;
|
||||
*/
|
||||
public final class ComputerContext {
|
||||
private final GlobalEnvironment globalEnvironment;
|
||||
private final ComputerThread computerScheduler;
|
||||
private final ComputerScheduler computerScheduler;
|
||||
private final MainThreadScheduler mainThreadScheduler;
|
||||
private final ILuaMachine.Factory luaFactory;
|
||||
private final List<ILuaAPIFactory> apiFactories;
|
||||
@@ -39,7 +40,7 @@ public final class ComputerContext {
|
||||
private final MethodSupplier<PeripheralMethod> peripheralMethods;
|
||||
|
||||
ComputerContext(
|
||||
GlobalEnvironment globalEnvironment, ComputerThread computerScheduler,
|
||||
GlobalEnvironment globalEnvironment, ComputerScheduler computerScheduler,
|
||||
MainThreadScheduler mainThreadScheduler, ILuaMachine.Factory luaFactory,
|
||||
List<ILuaAPIFactory> apiFactories, MethodSupplier<LuaMethod> luaMethods,
|
||||
MethodSupplier<PeripheralMethod> peripheralMethods
|
||||
@@ -68,7 +69,7 @@ public final class ComputerContext {
|
||||
*
|
||||
* @return The current computer thread manager.
|
||||
*/
|
||||
public ComputerThread computerScheduler() {
|
||||
public ComputerScheduler computerScheduler() {
|
||||
return computerScheduler;
|
||||
}
|
||||
|
||||
@@ -162,7 +163,7 @@ public final class ComputerContext {
|
||||
*/
|
||||
public static class Builder {
|
||||
private final GlobalEnvironment environment;
|
||||
private int threads = 1;
|
||||
private @Nullable ComputerScheduler computerScheduler = null;
|
||||
private @Nullable MainThreadScheduler mainThreadScheduler;
|
||||
private @Nullable ILuaMachine.Factory luaFactory;
|
||||
private @Nullable List<ILuaAPIFactory> apiFactories;
|
||||
@@ -173,7 +174,7 @@ public final class ComputerContext {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the number of threads the {@link ComputerThread} will use.
|
||||
* Set the {@link #computerScheduler()} to use {@link ComputerThread} with a given number of threads.
|
||||
*
|
||||
* @param threads The number of threads to use.
|
||||
* @return {@code this}, for chaining
|
||||
@@ -181,7 +182,20 @@ public final class ComputerContext {
|
||||
*/
|
||||
public Builder computerThreads(int threads) {
|
||||
if (threads < 1) throw new IllegalArgumentException("Threads must be >= 1");
|
||||
this.threads = threads;
|
||||
return computerScheduler(new ComputerThread(threads));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ComputerScheduler} for this context.
|
||||
*
|
||||
* @param scheduler The computer thread scheduler.
|
||||
* @return {@code this}, for chaining
|
||||
* @see ComputerContext#mainThreadScheduler()
|
||||
*/
|
||||
public Builder computerScheduler(ComputerScheduler scheduler) {
|
||||
Objects.requireNonNull(scheduler);
|
||||
if (computerScheduler != null) throw new IllegalStateException("Computer scheduler already specified");
|
||||
computerScheduler = scheduler;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -250,7 +264,7 @@ public final class ComputerContext {
|
||||
public ComputerContext build() {
|
||||
return new ComputerContext(
|
||||
environment,
|
||||
new ComputerThread(threads),
|
||||
computerScheduler == null ? new ComputerThread(1) : computerScheduler,
|
||||
mainThreadScheduler == null ? new NoWorkMainThreadScheduler() : mainThreadScheduler,
|
||||
luaFactory == null ? CobaltLuaMachine::new : luaFactory,
|
||||
apiFactories == null ? List.of() : apiFactories,
|
||||
|
@@ -22,7 +22,6 @@ public final class CoreConfig {
|
||||
}
|
||||
|
||||
public static int maximumFilesOpen = 128;
|
||||
public static boolean disableLua51Features = false;
|
||||
public static String defaultComputerSettings = "";
|
||||
|
||||
public static boolean httpEnabled = true;
|
||||
|
@@ -4,22 +4,22 @@
|
||||
|
||||
package dan200.computercraft.core.apis;
|
||||
|
||||
import dan200.computercraft.api.filesystem.MountConstants;
|
||||
import dan200.computercraft.api.lua.IArguments;
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.core.apis.handles.BinaryReadableHandle;
|
||||
import dan200.computercraft.core.apis.handles.BinaryWritableHandle;
|
||||
import dan200.computercraft.core.apis.handles.EncodedReadableHandle;
|
||||
import dan200.computercraft.core.apis.handles.EncodedWritableHandle;
|
||||
import dan200.computercraft.core.apis.handles.ReadHandle;
|
||||
import dan200.computercraft.core.apis.handles.ReadWriteHandle;
|
||||
import dan200.computercraft.core.apis.handles.WriteHandle;
|
||||
import dan200.computercraft.core.filesystem.FileSystem;
|
||||
import dan200.computercraft.core.filesystem.FileSystemException;
|
||||
import dan200.computercraft.core.metrics.Metrics;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.nio.file.OpenOption;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Interact with the computer's files and filesystem, allowing you to manipulate files, directories and paths. This
|
||||
@@ -55,6 +55,9 @@ import java.util.function.Function;
|
||||
* @cc.module fs
|
||||
*/
|
||||
public class FSAPI implements ILuaAPI {
|
||||
private static final Set<OpenOption> READ_EXTENDED = Set.of(StandardOpenOption.READ, StandardOpenOption.WRITE);
|
||||
private static final Set<OpenOption> WRITE_EXTENDED = union(Set.of(StandardOpenOption.READ), MountConstants.WRITE_OPTIONS);
|
||||
|
||||
private final IAPIEnvironment environment;
|
||||
private @Nullable FileSystem fileSystem = null;
|
||||
|
||||
@@ -301,8 +304,6 @@ public class FSAPI implements ILuaAPI {
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Add individual handle type documentation
|
||||
|
||||
/**
|
||||
* Opens a file for reading or writing at a path.
|
||||
* <p>
|
||||
@@ -311,10 +312,13 @@ public class FSAPI implements ILuaAPI {
|
||||
* <li><strong>"r"</strong>: Read mode</li>
|
||||
* <li><strong>"w"</strong>: Write mode</li>
|
||||
* <li><strong>"a"</strong>: Append mode</li>
|
||||
* <li><strong>"r+"</strong>: Update mode (allows reading and writing), all data is preserved</li>
|
||||
* <li><strong>"w+"</strong>: Update mode, all data is erased.</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* The mode may also have a "b" at the end, which opens the file in "binary
|
||||
* mode". This allows you to read binary files, as well as seek within a file.
|
||||
* mode". This changes {@link ReadHandle#read(Optional)} and {@link WriteHandle#write(IArguments)}
|
||||
* to read/write single bytes as numbers rather than strings.
|
||||
*
|
||||
* @param path The path to the file to open.
|
||||
* @param mode The mode to open the file with.
|
||||
@@ -354,42 +358,38 @@ public class FSAPI implements ILuaAPI {
|
||||
* file.write("Just testing some code")
|
||||
* file.close() -- Remember to call close, otherwise changes may not be written!
|
||||
* }</pre>
|
||||
* @cc.changed 1.109.0 Add support for update modes ({@code r+} and {@code w+}).
|
||||
* @cc.changed 1.109.0 Opening a file in non-binary mode now uses the raw bytes of the file rather than encoding to
|
||||
* UTF-8.
|
||||
*/
|
||||
@LuaFunction
|
||||
public final Object[] open(String path, String mode) throws LuaException {
|
||||
if (mode.isEmpty()) throw new LuaException(MountConstants.UNSUPPORTED_MODE);
|
||||
|
||||
var binary = mode.indexOf('b') >= 0;
|
||||
try (var ignored = environment.time(Metrics.FS_OPS)) {
|
||||
switch (mode) {
|
||||
case "r" -> {
|
||||
// Open the file for reading, then create a wrapper around the reader
|
||||
var reader = getFileSystem().openForRead(path, EncodedReadableHandle::openUtf8);
|
||||
return new Object[]{ new EncodedReadableHandle(reader.get(), reader) };
|
||||
case "r", "rb" -> {
|
||||
var reader = getFileSystem().openForRead(path);
|
||||
return new Object[]{ new ReadHandle(reader.get(), reader, binary) };
|
||||
}
|
||||
case "w" -> {
|
||||
// Open the file for writing, then create a wrapper around the writer
|
||||
var writer = getFileSystem().openForWrite(path, false, EncodedWritableHandle::openUtf8);
|
||||
return new Object[]{ new EncodedWritableHandle(writer.get(), writer) };
|
||||
case "w", "wb" -> {
|
||||
var writer = getFileSystem().openForWrite(path, MountConstants.WRITE_OPTIONS);
|
||||
return new Object[]{ WriteHandle.of(writer.get(), writer, binary, true) };
|
||||
}
|
||||
case "a" -> {
|
||||
// Open the file for appending, then create a wrapper around the writer
|
||||
var writer = getFileSystem().openForWrite(path, true, EncodedWritableHandle::openUtf8);
|
||||
return new Object[]{ new EncodedWritableHandle(writer.get(), writer) };
|
||||
case "a", "ab" -> {
|
||||
var writer = getFileSystem().openForWrite(path, MountConstants.APPEND_OPTIONS);
|
||||
return new Object[]{ WriteHandle.of(writer.get(), writer, binary, false) };
|
||||
}
|
||||
case "rb" -> {
|
||||
// Open the file for binary reading, then create a wrapper around the reader
|
||||
var reader = getFileSystem().openForRead(path, Function.identity());
|
||||
return new Object[]{ BinaryReadableHandle.of(reader.get(), reader) };
|
||||
case "r+", "r+b" -> {
|
||||
var reader = getFileSystem().openForWrite(path, READ_EXTENDED);
|
||||
return new Object[]{ new ReadWriteHandle(reader.get(), reader, binary) };
|
||||
}
|
||||
case "wb" -> {
|
||||
// Open the file for binary writing, then create a wrapper around the writer
|
||||
var writer = getFileSystem().openForWrite(path, false, Function.identity());
|
||||
return new Object[]{ BinaryWritableHandle.of(writer.get(), writer, true) };
|
||||
case "w+", "w+b" -> {
|
||||
var writer = getFileSystem().openForWrite(path, WRITE_EXTENDED);
|
||||
return new Object[]{ new ReadWriteHandle(writer.get(), writer, binary) };
|
||||
}
|
||||
case "ab" -> {
|
||||
// Open the file for binary appending, then create a wrapper around the reader
|
||||
var writer = getFileSystem().openForWrite(path, true, Function.identity());
|
||||
return new Object[]{ BinaryWritableHandle.of(writer.get(), writer, false) };
|
||||
}
|
||||
default -> throw new LuaException("Unsupported mode");
|
||||
default -> throw new LuaException(MountConstants.UNSUPPORTED_MODE);
|
||||
}
|
||||
} catch (FileSystemException e) {
|
||||
return new Object[]{ null, e.getMessage() };
|
||||
@@ -498,4 +498,11 @@ public class FSAPI implements ILuaAPI {
|
||||
throw new LuaException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static Set<OpenOption> union(Set<OpenOption> a, Set<OpenOption> b) {
|
||||
Set<OpenOption> union = new HashSet<>();
|
||||
union.addAll(a);
|
||||
union.addAll(b);
|
||||
return Set.copyOf(union);
|
||||
}
|
||||
}
|
||||
|
@@ -4,10 +4,7 @@
|
||||
|
||||
package dan200.computercraft.core.apis;
|
||||
|
||||
import dan200.computercraft.api.lua.IArguments;
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.api.lua.*;
|
||||
import dan200.computercraft.core.CoreConfig;
|
||||
import dan200.computercraft.core.apis.http.*;
|
||||
import dan200.computercraft.core.apis.http.request.HttpRequest;
|
||||
@@ -18,7 +15,7 @@ import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import io.netty.handler.codec.http.HttpHeaders;
|
||||
import io.netty.handler.codec.http.HttpMethod;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -74,7 +71,8 @@ public class HTTPAPI implements ILuaAPI {
|
||||
|
||||
@LuaFunction
|
||||
public final Object[] request(IArguments args) throws LuaException {
|
||||
String address, postString, requestMethod;
|
||||
String address, requestMethod;
|
||||
ByteBuffer postBody;
|
||||
Map<?, ?> headerTable;
|
||||
boolean binary, redirect;
|
||||
Optional<Double> timeoutArg;
|
||||
@@ -82,8 +80,9 @@ public class HTTPAPI implements ILuaAPI {
|
||||
if (args.get(0) instanceof Map) {
|
||||
var options = args.getTable(0);
|
||||
address = getStringField(options, "url");
|
||||
postString = optStringField(options, "body", null);
|
||||
headerTable = optTableField(options, "headers", Collections.emptyMap());
|
||||
var postString = optStringField(options, "body", null);
|
||||
postBody = postString == null ? null : LuaValues.encode(postString);
|
||||
headerTable = optTableField(options, "headers", Map.of());
|
||||
binary = optBooleanField(options, "binary", false);
|
||||
requestMethod = optStringField(options, "method", null);
|
||||
redirect = optBooleanField(options, "redirect", true);
|
||||
@@ -91,8 +90,8 @@ public class HTTPAPI implements ILuaAPI {
|
||||
} else {
|
||||
// Get URL and post information
|
||||
address = args.getString(0);
|
||||
postString = args.optString(1, null);
|
||||
headerTable = args.optTable(2, Collections.emptyMap());
|
||||
postBody = args.optBytes(1).orElse(null);
|
||||
headerTable = args.optTable(2, Map.of());
|
||||
binary = args.optBoolean(3, false);
|
||||
requestMethod = null;
|
||||
redirect = true;
|
||||
@@ -104,7 +103,7 @@ public class HTTPAPI implements ILuaAPI {
|
||||
|
||||
HttpMethod httpMethod;
|
||||
if (requestMethod == null) {
|
||||
httpMethod = postString == null ? HttpMethod.GET : HttpMethod.POST;
|
||||
httpMethod = postBody == null ? HttpMethod.GET : HttpMethod.POST;
|
||||
} else {
|
||||
httpMethod = HttpMethod.valueOf(requestMethod.toUpperCase(Locale.ROOT));
|
||||
if (httpMethod == null || requestMethod.equalsIgnoreCase("CONNECT")) {
|
||||
@@ -114,7 +113,7 @@ public class HTTPAPI implements ILuaAPI {
|
||||
|
||||
try {
|
||||
var uri = HttpRequest.checkUri(address);
|
||||
var request = new HttpRequest(requests, apiEnvironment, address, postString, headers, binary, redirect, timeout);
|
||||
var request = new HttpRequest(requests, apiEnvironment, address, postBody, headers, binary, redirect, timeout);
|
||||
|
||||
// Make the request
|
||||
if (!request.queue(r -> r.request(uri, httpMethod))) {
|
||||
@@ -154,11 +153,11 @@ public class HTTPAPI implements ILuaAPI {
|
||||
if (args.get(0) instanceof Map) {
|
||||
var options = args.getTable(0);
|
||||
address = getStringField(options, "url");
|
||||
headerTable = optTableField(options, "headers", Collections.emptyMap());
|
||||
headerTable = optTableField(options, "headers", Map.of());
|
||||
timeoutArg = optRealField(options, "timeout");
|
||||
} else {
|
||||
address = args.getString(0);
|
||||
headerTable = args.optTable(1, Collections.emptyMap());
|
||||
headerTable = args.optTable(1, Map.of());
|
||||
timeoutArg = Optional.empty();
|
||||
}
|
||||
|
||||
|
@@ -1,45 +1,100 @@
|
||||
// SPDX-FileCopyrightText: 2018 The CC: Tweaked Developers
|
||||
// SPDX-FileCopyrightText: 2017 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.core.apis.handles;
|
||||
|
||||
import dan200.computercraft.api.lua.Coerced;
|
||||
import dan200.computercraft.api.lua.IArguments;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.core.filesystem.TrackingCloseable;
|
||||
import dan200.computercraft.core.util.IoUtil;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* A file handle opened with {@link dan200.computercraft.core.apis.FSAPI#open(String, String)} with the {@code "rb"}
|
||||
* mode.
|
||||
*
|
||||
* @cc.module fs.BinaryReadHandle
|
||||
* The base class for all file handle types.
|
||||
*/
|
||||
public class BinaryReadableHandle extends HandleGeneric {
|
||||
public abstract class AbstractHandle {
|
||||
private static final int BUFFER_SIZE = 8192;
|
||||
|
||||
private final SeekableByteChannel channel;
|
||||
private @Nullable TrackingCloseable closeable;
|
||||
protected final boolean binary;
|
||||
|
||||
private final ByteBuffer single = ByteBuffer.allocate(1);
|
||||
|
||||
BinaryReadableHandle(SeekableByteChannel channel, TrackingCloseable closeable) {
|
||||
super(closeable);
|
||||
protected AbstractHandle(SeekableByteChannel channel, TrackingCloseable closeable, boolean binary) {
|
||||
this.channel = channel;
|
||||
this.closeable = closeable;
|
||||
this.binary = binary;
|
||||
}
|
||||
|
||||
public static BinaryReadableHandle of(SeekableByteChannel channel, TrackingCloseable closeable) {
|
||||
return new BinaryReadableHandle(channel, closeable);
|
||||
protected void checkOpen() throws LuaException {
|
||||
var closeable = this.closeable;
|
||||
if (closeable == null || !closeable.isOpen()) throw new LuaException("attempt to use a closed file");
|
||||
}
|
||||
|
||||
public static BinaryReadableHandle of(SeekableByteChannel channel) {
|
||||
return of(channel, new TrackingCloseable.Impl(channel));
|
||||
/**
|
||||
* Close this file, freeing any resources it uses.
|
||||
* <p>
|
||||
* Once a file is closed it may no longer be read or written to.
|
||||
*
|
||||
* @throws LuaException If the file has already been closed.
|
||||
*/
|
||||
@LuaFunction
|
||||
public final void close() throws LuaException {
|
||||
checkOpen();
|
||||
IoUtil.closeQuietly(closeable);
|
||||
closeable = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Seek to a new position within the file, changing where bytes are written to. The new position is an offset
|
||||
* given by {@code offset}, relative to a start position determined by {@code whence}:
|
||||
* <p>
|
||||
* - {@code "set"}: {@code offset} is relative to the beginning of the file.
|
||||
* - {@code "cur"}: Relative to the current position. This is the default.
|
||||
* - {@code "end"}: Relative to the end of the file.
|
||||
* <p>
|
||||
* In case of success, {@code seek} returns the new file position from the beginning of the file.
|
||||
*
|
||||
* @param whence Where the offset is relative to.
|
||||
* @param offset The offset to seek to.
|
||||
* @return The new position.
|
||||
* @throws LuaException If the file has been closed.
|
||||
* @cc.treturn [1] number The new position.
|
||||
* @cc.treturn [2] nil If seeking failed.
|
||||
* @cc.treturn string The reason seeking failed.
|
||||
* @cc.since 1.80pr1.9
|
||||
*/
|
||||
@Nullable
|
||||
public Object[] seek(Optional<String> whence, Optional<Long> offset) throws LuaException {
|
||||
checkOpen();
|
||||
long actualOffset = offset.orElse(0L);
|
||||
try {
|
||||
switch (whence.orElse("cur")) {
|
||||
case "set" -> channel.position(actualOffset);
|
||||
case "cur" -> channel.position(channel.position() + actualOffset);
|
||||
case "end" -> channel.position(channel.size() + actualOffset);
|
||||
default -> throw new LuaException("bad argument #1 to 'seek' (invalid option '" + whence + "'");
|
||||
}
|
||||
|
||||
return new Object[]{ channel.position() };
|
||||
} catch (IllegalArgumentException e) {
|
||||
return new Object[]{ null, "Position is negative" };
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,17 +106,21 @@ public class BinaryReadableHandle extends HandleGeneric {
|
||||
* @throws LuaException When trying to read a negative number of bytes.
|
||||
* @throws LuaException If the file has been closed.
|
||||
* @cc.treturn [1] nil If we are at the end of the file.
|
||||
* @cc.treturn [2] number The value of the byte read. This is returned when the {@code count} is absent.
|
||||
* @cc.treturn [2] number The value of the byte read. This is returned if the file is opened in binary mode and
|
||||
* {@code count} is absent
|
||||
* @cc.treturn [3] string The bytes read as a string. This is returned when the {@code count} is given.
|
||||
* @cc.changed 1.80pr1 Now accepts an integer argument to read multiple bytes, returning a string instead of a number.
|
||||
*/
|
||||
@Nullable
|
||||
@LuaFunction
|
||||
public final Object[] read(Optional<Integer> countArg) throws LuaException {
|
||||
public Object[] read(Optional<Integer> countArg) throws LuaException {
|
||||
checkOpen();
|
||||
try {
|
||||
if (countArg.isPresent()) {
|
||||
int count = countArg.get();
|
||||
if (binary && countArg.isEmpty()) {
|
||||
single.clear();
|
||||
var b = channel.read(single);
|
||||
return b == -1 ? null : new Object[]{ single.get(0) & 0xFF };
|
||||
} else {
|
||||
int count = countArg.orElse(1);
|
||||
if (count < 0) throw new LuaException("Cannot read a negative number of bytes");
|
||||
if (count == 0) return channel.position() >= channel.size() ? null : new Object[]{ "" };
|
||||
|
||||
@@ -109,10 +168,6 @@ public class BinaryReadableHandle extends HandleGeneric {
|
||||
assert pos == totalRead;
|
||||
return new Object[]{ bytes };
|
||||
}
|
||||
} else {
|
||||
single.clear();
|
||||
var b = channel.read(single);
|
||||
return b == -1 ? null : new Object[]{ single.get(0) & 0xFF };
|
||||
}
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
@@ -128,8 +183,7 @@ public class BinaryReadableHandle extends HandleGeneric {
|
||||
* @cc.since 1.80pr1
|
||||
*/
|
||||
@Nullable
|
||||
@LuaFunction
|
||||
public final Object[] readAll() throws LuaException {
|
||||
public Object[] readAll() throws LuaException {
|
||||
checkOpen();
|
||||
try {
|
||||
var expected = 32;
|
||||
@@ -137,16 +191,14 @@ public class BinaryReadableHandle extends HandleGeneric {
|
||||
var stream = new ByteArrayOutputStream(expected);
|
||||
|
||||
var buf = ByteBuffer.allocate(8192);
|
||||
var readAnything = false;
|
||||
while (true) {
|
||||
buf.clear();
|
||||
var r = channel.read(buf);
|
||||
if (r == -1) break;
|
||||
|
||||
readAnything = true;
|
||||
stream.write(buf.array(), 0, r);
|
||||
}
|
||||
return readAnything ? new Object[]{ stream.toByteArray() } : null;
|
||||
return new Object[]{ stream.toByteArray() };
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
@@ -163,8 +215,7 @@ public class BinaryReadableHandle extends HandleGeneric {
|
||||
* @cc.changed 1.81.0 `\r` is now stripped.
|
||||
*/
|
||||
@Nullable
|
||||
@LuaFunction
|
||||
public final Object[] readLine(Optional<Boolean> withTrailingArg) throws LuaException {
|
||||
public Object[] readLine(Optional<Boolean> withTrailingArg) throws LuaException {
|
||||
checkOpen();
|
||||
boolean withTrailing = withTrailingArg.orElse(false);
|
||||
try {
|
||||
@@ -206,28 +257,64 @@ public class BinaryReadableHandle extends HandleGeneric {
|
||||
}
|
||||
|
||||
/**
|
||||
* Seek to a new position within the file, changing where bytes are written to. The new position is an offset
|
||||
* given by {@code offset}, relative to a start position determined by {@code whence}:
|
||||
* <p>
|
||||
* - {@code "set"}: {@code offset} is relative to the beginning of the file.
|
||||
* - {@code "cur"}: Relative to the current position. This is the default.
|
||||
* - {@code "end"}: Relative to the end of the file.
|
||||
* <p>
|
||||
* In case of success, {@code seek} returns the new file position from the beginning of the file.
|
||||
* Write a string or byte to the file.
|
||||
*
|
||||
* @param whence Where the offset is relative to.
|
||||
* @param offset The offset to seek to.
|
||||
* @return The new position.
|
||||
* @param arguments The value to write.
|
||||
* @throws LuaException If the file has been closed.
|
||||
* @cc.treturn [1] number The new position.
|
||||
* @cc.treturn [2] nil If seeking failed.
|
||||
* @cc.treturn string The reason seeking failed.
|
||||
* @cc.since 1.80pr1.9
|
||||
* @cc.tparam [1] string contents The string to write.
|
||||
* @cc.tparam [2] number charcode The byte to write, if the file was opened in binary mode.
|
||||
* @cc.changed 1.80pr1 Now accepts a string to write multiple bytes.
|
||||
*/
|
||||
@Nullable
|
||||
@LuaFunction
|
||||
public final Object[] seek(Optional<String> whence, Optional<Long> offset) throws LuaException {
|
||||
public void write(IArguments arguments) throws LuaException {
|
||||
checkOpen();
|
||||
return handleSeek(channel, whence, offset);
|
||||
try {
|
||||
var arg = arguments.get(0);
|
||||
if (binary && arg instanceof Number) {
|
||||
var number = ((Number) arg).intValue();
|
||||
writeSingle((byte) number);
|
||||
} else {
|
||||
channel.write(arguments.getBytesCoerced(0));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new LuaException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a string of characters to the file, following them with a new line character.
|
||||
*
|
||||
* @param text The text to write to the file.
|
||||
* @throws LuaException If the file has been closed.
|
||||
*/
|
||||
public void writeLine(Coerced<ByteBuffer> text) throws LuaException {
|
||||
checkOpen();
|
||||
try {
|
||||
channel.write(text.value());
|
||||
writeSingle((byte) '\n');
|
||||
} catch (IOException e) {
|
||||
throw new LuaException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void writeSingle(byte value) throws IOException {
|
||||
single.clear();
|
||||
single.put(value);
|
||||
single.flip();
|
||||
channel.write(single);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the current file without closing it.
|
||||
*
|
||||
* @throws LuaException If the file has been closed.
|
||||
*/
|
||||
public void flush() throws LuaException {
|
||||
checkOpen();
|
||||
try {
|
||||
// Technically this is not needed
|
||||
if (channel instanceof FileChannel channel) channel.force(false);
|
||||
} catch (IOException e) {
|
||||
throw new LuaException(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,117 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2017 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.core.apis.handles;
|
||||
|
||||
import dan200.computercraft.api.lua.IArguments;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.api.lua.LuaValues;
|
||||
import dan200.computercraft.core.filesystem.TrackingCloseable;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* A file handle opened by {@link dan200.computercraft.core.apis.FSAPI#open} using the {@code "wb"} or {@code "ab"}
|
||||
* modes.
|
||||
*
|
||||
* @cc.module fs.BinaryWriteHandle
|
||||
*/
|
||||
public class BinaryWritableHandle extends HandleGeneric {
|
||||
final SeekableByteChannel channel;
|
||||
private final ByteBuffer single = ByteBuffer.allocate(1);
|
||||
|
||||
protected BinaryWritableHandle(SeekableByteChannel channel, TrackingCloseable closeable) {
|
||||
super(closeable);
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
public static BinaryWritableHandle of(SeekableByteChannel channel, TrackingCloseable closeable, boolean canSeek) {
|
||||
return canSeek ? new Seekable(channel, closeable) : new BinaryWritableHandle(channel, closeable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a string or byte to the file.
|
||||
*
|
||||
* @param arguments The value to write.
|
||||
* @throws LuaException If the file has been closed.
|
||||
* @cc.tparam [1] number charcode The byte to write.
|
||||
* @cc.tparam [2] string contents The string to write.
|
||||
* @cc.changed 1.80pr1 Now accepts a string to write multiple bytes.
|
||||
*/
|
||||
@LuaFunction
|
||||
public final void write(IArguments arguments) throws LuaException {
|
||||
checkOpen();
|
||||
try {
|
||||
var arg = arguments.get(0);
|
||||
if (arg instanceof Number) {
|
||||
var number = ((Number) arg).intValue();
|
||||
single.clear();
|
||||
single.put((byte) number);
|
||||
single.flip();
|
||||
|
||||
channel.write(single);
|
||||
} else if (arg instanceof String) {
|
||||
channel.write(arguments.getBytes(0));
|
||||
} else {
|
||||
throw LuaValues.badArgumentOf(arguments, 0, "string or number");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new LuaException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the current file without closing it.
|
||||
*
|
||||
* @throws LuaException If the file has been closed.
|
||||
*/
|
||||
@LuaFunction
|
||||
public final void flush() throws LuaException {
|
||||
checkOpen();
|
||||
try {
|
||||
// Technically this is not needed
|
||||
if (channel instanceof FileChannel channel) channel.force(false);
|
||||
} catch (IOException e) {
|
||||
throw new LuaException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static class Seekable extends BinaryWritableHandle {
|
||||
public Seekable(SeekableByteChannel channel, TrackingCloseable closeable) {
|
||||
super(channel, closeable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Seek to a new position within the file, changing where bytes are written to. The new position is an offset
|
||||
* given by {@code offset}, relative to a start position determined by {@code whence}:
|
||||
* <p>
|
||||
* - {@code "set"}: {@code offset} is relative to the beginning of the file.
|
||||
* - {@code "cur"}: Relative to the current position. This is the default.
|
||||
* - {@code "end"}: Relative to the end of the file.
|
||||
* <p>
|
||||
* In case of success, {@code seek} returns the new file position from the beginning of the file.
|
||||
*
|
||||
* @param whence Where the offset is relative to.
|
||||
* @param offset The offset to seek to.
|
||||
* @return The new position.
|
||||
* @throws LuaException If the file has been closed.
|
||||
* @cc.treturn [1] number The new position.
|
||||
* @cc.treturn [2] nil If seeking failed.
|
||||
* @cc.treturn string The reason seeking failed.
|
||||
* @cc.since 1.80pr1.9
|
||||
*/
|
||||
@Nullable
|
||||
@LuaFunction
|
||||
public final Object[] seek(Optional<String> whence, Optional<Long> offset) throws LuaException {
|
||||
checkOpen();
|
||||
return handleSeek(channel, whence, offset);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,163 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2018 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.core.apis.handles;
|
||||
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.core.filesystem.TrackingCloseable;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.CodingErrorAction;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* A file handle opened with {@link dan200.computercraft.core.apis.FSAPI#open(String, String)} with the {@code "r"}
|
||||
* mode.
|
||||
*
|
||||
* @cc.module fs.ReadHandle
|
||||
*/
|
||||
public class EncodedReadableHandle extends HandleGeneric {
|
||||
private static final int BUFFER_SIZE = 8192;
|
||||
|
||||
private final BufferedReader reader;
|
||||
|
||||
public EncodedReadableHandle(BufferedReader reader, TrackingCloseable closable) {
|
||||
super(closable);
|
||||
this.reader = reader;
|
||||
}
|
||||
|
||||
public EncodedReadableHandle(BufferedReader reader) {
|
||||
this(reader, new TrackingCloseable.Impl(reader));
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a line from the file.
|
||||
*
|
||||
* @param withTrailingArg Whether to include the newline characters with the returned string. Defaults to {@code false}.
|
||||
* @return The read string.
|
||||
* @throws LuaException If the file has been closed.
|
||||
* @cc.treturn string|nil The read line or {@code nil} if at the end of the file.
|
||||
* @cc.changed 1.81.0 Added option to return trailing newline.
|
||||
*/
|
||||
@Nullable
|
||||
@LuaFunction
|
||||
public final Object[] readLine(Optional<Boolean> withTrailingArg) throws LuaException {
|
||||
checkOpen();
|
||||
boolean withTrailing = withTrailingArg.orElse(false);
|
||||
try {
|
||||
var line = reader.readLine();
|
||||
if (line != null) {
|
||||
// While this is technically inaccurate, it's better than nothing
|
||||
if (withTrailing) line += "\n";
|
||||
return new Object[]{ line };
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the remainder of the file.
|
||||
*
|
||||
* @return The file, or {@code null} if at the end of it.
|
||||
* @throws LuaException If the file has been closed.
|
||||
* @cc.treturn nil|string The remaining contents of the file, or {@code nil} if we are at the end.
|
||||
*/
|
||||
@Nullable
|
||||
@LuaFunction
|
||||
public final Object[] readAll() throws LuaException {
|
||||
checkOpen();
|
||||
try {
|
||||
var result = new StringBuilder();
|
||||
var line = reader.readLine();
|
||||
while (line != null) {
|
||||
result.append(line);
|
||||
line = reader.readLine();
|
||||
if (line != null) {
|
||||
result.append("\n");
|
||||
}
|
||||
}
|
||||
return new Object[]{ result.toString() };
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a number of characters from this file.
|
||||
*
|
||||
* @param countA The number of characters to read, defaulting to 1.
|
||||
* @return The read characters.
|
||||
* @throws LuaException When trying to read a negative number of characters.
|
||||
* @throws LuaException If the file has been closed.
|
||||
* @cc.treturn string|nil The read characters, or {@code nil} if at the of the file.
|
||||
* @cc.since 1.80pr1.4
|
||||
*/
|
||||
@Nullable
|
||||
@LuaFunction
|
||||
public final Object[] read(Optional<Integer> countA) throws LuaException {
|
||||
checkOpen();
|
||||
try {
|
||||
int count = countA.orElse(1);
|
||||
if (count < 0) {
|
||||
// Whilst this may seem absurd to allow reading 0 characters, PUC Lua it so
|
||||
// it seems best to remain somewhat consistent.
|
||||
throw new LuaException("Cannot read a negative number of characters");
|
||||
} else if (count <= BUFFER_SIZE) {
|
||||
// If we've got a small count, then allocate that and read it.
|
||||
var chars = new char[count];
|
||||
var read = reader.read(chars);
|
||||
|
||||
return read < 0 ? null : new Object[]{ new String(chars, 0, read) };
|
||||
} else {
|
||||
// If we've got a large count, read in bunches of 8192.
|
||||
var buffer = new char[BUFFER_SIZE];
|
||||
|
||||
// Read the initial set of characters, failing if none are read.
|
||||
var read = reader.read(buffer, 0, Math.min(buffer.length, count));
|
||||
if (read < 0) return null;
|
||||
|
||||
var out = new StringBuilder(read);
|
||||
var totalRead = read;
|
||||
out.append(buffer, 0, read);
|
||||
|
||||
// Otherwise read until we either reach the limit or we no longer consume
|
||||
// the full buffer.
|
||||
while (read >= BUFFER_SIZE && totalRead < count) {
|
||||
read = reader.read(buffer, 0, Math.min(BUFFER_SIZE, count - totalRead));
|
||||
if (read < 0) break;
|
||||
|
||||
totalRead += read;
|
||||
out.append(buffer, 0, read);
|
||||
}
|
||||
|
||||
return new Object[]{ out.toString() };
|
||||
}
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static BufferedReader openUtf8(ReadableByteChannel channel) {
|
||||
return open(channel, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public static BufferedReader open(ReadableByteChannel channel, Charset charset) {
|
||||
// Create a charset decoder with the same properties as StreamDecoder does for
|
||||
// InputStreams: namely, replace everything instead of erroring.
|
||||
var decoder = charset.newDecoder()
|
||||
.onMalformedInput(CodingErrorAction.REPLACE)
|
||||
.onUnmappableCharacter(CodingErrorAction.REPLACE);
|
||||
return new BufferedReader(Channels.newReader(channel, decoder, -1));
|
||||
}
|
||||
}
|
@@ -1,95 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2017 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.core.apis.handles;
|
||||
|
||||
import dan200.computercraft.api.lua.Coerced;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.core.filesystem.TrackingCloseable;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.CodingErrorAction;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* A file handle opened by {@link dan200.computercraft.core.apis.FSAPI#open} using the {@code "w"} or {@code "a"} modes.
|
||||
*
|
||||
* @cc.module fs.WriteHandle
|
||||
*/
|
||||
public class EncodedWritableHandle extends HandleGeneric {
|
||||
private final BufferedWriter writer;
|
||||
|
||||
public EncodedWritableHandle(BufferedWriter writer, TrackingCloseable closable) {
|
||||
super(closable);
|
||||
this.writer = writer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a string of characters to the file.
|
||||
*
|
||||
* @param textA The text to write to the file.
|
||||
* @throws LuaException If the file has been closed.
|
||||
*/
|
||||
@LuaFunction
|
||||
public final void write(Coerced<String> textA) throws LuaException {
|
||||
checkOpen();
|
||||
var text = textA.value();
|
||||
try {
|
||||
writer.write(text, 0, text.length());
|
||||
} catch (IOException e) {
|
||||
throw new LuaException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a string of characters to the file, following them with a new line character.
|
||||
*
|
||||
* @param textA The text to write to the file.
|
||||
* @throws LuaException If the file has been closed.
|
||||
*/
|
||||
@LuaFunction
|
||||
public final void writeLine(Coerced<String> textA) throws LuaException {
|
||||
checkOpen();
|
||||
var text = textA.value();
|
||||
try {
|
||||
writer.write(text, 0, text.length());
|
||||
writer.newLine();
|
||||
} catch (IOException e) {
|
||||
throw new LuaException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the current file without closing it.
|
||||
*
|
||||
* @throws LuaException If the file has been closed.
|
||||
*/
|
||||
@LuaFunction
|
||||
public final void flush() throws LuaException {
|
||||
checkOpen();
|
||||
try {
|
||||
writer.flush();
|
||||
} catch (IOException e) {
|
||||
throw new LuaException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static BufferedWriter openUtf8(WritableByteChannel channel) {
|
||||
return open(channel, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public static BufferedWriter open(WritableByteChannel channel, Charset charset) {
|
||||
// Create a charset encoder with the same properties as StreamEncoder does for
|
||||
// OutputStreams: namely, replace everything instead of erroring.
|
||||
var encoder = charset.newEncoder()
|
||||
.onMalformedInput(CodingErrorAction.REPLACE)
|
||||
.onUnmappableCharacter(CodingErrorAction.REPLACE);
|
||||
return new BufferedWriter(Channels.newWriter(channel, encoder, -1));
|
||||
}
|
||||
}
|
@@ -1,76 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2017 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.core.apis.handles;
|
||||
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.core.filesystem.TrackingCloseable;
|
||||
import dan200.computercraft.core.util.IoUtil;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.util.Optional;
|
||||
|
||||
public abstract class HandleGeneric {
|
||||
private @Nullable TrackingCloseable closeable;
|
||||
|
||||
protected HandleGeneric(TrackingCloseable closeable) {
|
||||
this.closeable = closeable;
|
||||
}
|
||||
|
||||
protected void checkOpen() throws LuaException {
|
||||
var closeable = this.closeable;
|
||||
if (closeable == null || !closeable.isOpen()) throw new LuaException("attempt to use a closed file");
|
||||
}
|
||||
|
||||
protected final void close() {
|
||||
IoUtil.closeQuietly(closeable);
|
||||
closeable = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close this file, freeing any resources it uses.
|
||||
* <p>
|
||||
* Once a file is closed it may no longer be read or written to.
|
||||
*
|
||||
* @throws LuaException If the file has already been closed.
|
||||
*/
|
||||
@LuaFunction("close")
|
||||
public final void doClose() throws LuaException {
|
||||
checkOpen();
|
||||
close();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Shared implementation for various file handle types.
|
||||
*
|
||||
* @param channel The channel to seek in
|
||||
* @param whence The seeking mode.
|
||||
* @param offset The offset to seek to.
|
||||
* @return The new position of the file, or null if some error occurred.
|
||||
* @throws LuaException If the arguments were invalid
|
||||
* @see <a href="https://www.lua.org/manual/5.1/manual.html#pdf-file:seek">{@code file:seek} in the Lua manual.</a>
|
||||
*/
|
||||
@Nullable
|
||||
protected static Object[] handleSeek(SeekableByteChannel channel, Optional<String> whence, Optional<Long> offset) throws LuaException {
|
||||
long actualOffset = offset.orElse(0L);
|
||||
try {
|
||||
switch (whence.orElse("cur")) {
|
||||
case "set" -> channel.position(actualOffset);
|
||||
case "cur" -> channel.position(channel.position() + actualOffset);
|
||||
case "end" -> channel.position(channel.size() + actualOffset);
|
||||
default -> throw new LuaException("bad argument #1 to 'seek' (invalid option '" + whence + "'");
|
||||
}
|
||||
|
||||
return new Object[]{ channel.position() };
|
||||
} catch (IllegalArgumentException e) {
|
||||
return new Object[]{ null, "Position is negative" };
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user