mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-10-23 09:57:39 +00:00
Compare commits
29 Commits
v1.20.1-1.
...
v1.20.1-1.
Author | SHA1 | Date | |
---|---|---|---|
![]() |
8be6b1b772 | ||
![]() |
104d5e70de | ||
![]() |
e3bda2f763 | ||
![]() |
234f69e8e5 | ||
![]() |
ed3a17f9b9 | ||
![]() |
0349c2b1f9 | ||
![]() |
03f9e6bd6d | ||
![]() |
9d8c933a14 | ||
![]() |
78bb3da58c | ||
![]() |
39a5e40c92 | ||
![]() |
763ba51919 | ||
![]() |
cf6ec8c28f | ||
![]() |
95d3b646b2 | ||
![]() |
488f66eead | ||
![]() |
1f7d245876 | ||
![]() |
af12b3a0ea | ||
![]() |
eb3e8ba677 | ||
![]() |
2043939531 | ||
![]() |
84a799d27a | ||
![]() |
fe826f5c9c | ||
![]() |
f8b7422294 | ||
![]() |
b343c01216 | ||
![]() |
76968f2f28 | ||
![]() |
1d365f5a0b | ||
![]() |
7b240cbf7e | ||
![]() |
d272a327c7 | ||
![]() |
0c0556a5bc | ||
![]() |
87345c6b2e | ||
![]() |
784e623776 |
@@ -42,7 +42,6 @@ repositories {
|
||||
url "https://squiddev.cc/maven/"
|
||||
content {
|
||||
includeGroup("cc.tweaked")
|
||||
includeModule("org.squiddev", "Cobalt")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -13,6 +13,8 @@ plugins {
|
||||
publishing
|
||||
alias(libs.plugins.taskTree)
|
||||
alias(libs.plugins.githubRelease)
|
||||
alias(libs.plugins.gradleVersions)
|
||||
alias(libs.plugins.versionCatalogUpdate)
|
||||
id("org.jetbrains.gradle.plugin.idea-ext")
|
||||
id("cc-tweaked")
|
||||
}
|
||||
@@ -102,3 +104,9 @@ idea.project.settings.compiler.javac {
|
||||
}
|
||||
.toMap()
|
||||
}
|
||||
|
||||
versionCatalogUpdate {
|
||||
sortByKey.set(false)
|
||||
pin { versions.addAll("fastutil", "guava", "netty", "slf4j") }
|
||||
keep { keepUnusedLibraries.set(true) }
|
||||
}
|
||||
|
@@ -5,6 +5,8 @@
|
||||
plugins {
|
||||
`java-gradle-plugin`
|
||||
`kotlin-dsl`
|
||||
alias(libs.plugins.gradleVersions)
|
||||
alias(libs.plugins.versionCatalogUpdate)
|
||||
}
|
||||
|
||||
// Duplicated in settings.gradle.kts
|
||||
@@ -75,3 +77,9 @@ gradlePlugin {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
versionCatalogUpdate {
|
||||
sortByKey.set(false)
|
||||
keep { keepUnusedLibraries.set(true) }
|
||||
catalogFile.set(file("../gradle/libs.versions.toml"))
|
||||
}
|
||||
|
@@ -57,7 +57,6 @@ repositories {
|
||||
|
||||
filter {
|
||||
includeGroup("cc.tweaked")
|
||||
includeModule("org.squiddev", "Cobalt")
|
||||
// Things we mirror
|
||||
includeGroup("commoble.morered")
|
||||
includeGroup("dev.architectury")
|
||||
@@ -77,6 +76,12 @@ dependencies {
|
||||
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
|
||||
checkstyle(libs.findLibrary("checkstyle").get())
|
||||
|
||||
constraints {
|
||||
checkstyle("org.codehaus.plexus:plexus-container-default:2.1.1") {
|
||||
because("2.1.0 depends on deprecated Google collections module")
|
||||
}
|
||||
}
|
||||
|
||||
errorprone(libs.findLibrary("errorProne-core").get())
|
||||
errorprone(libs.findLibrary("nullAway").get())
|
||||
}
|
||||
@@ -202,6 +207,7 @@ spotless {
|
||||
val ktlintConfig = mapOf(
|
||||
"ktlint_standard_no-wildcard-imports" to "disabled",
|
||||
"ktlint_standard_class-naming" to "disabled",
|
||||
"ktlint_standard_function-naming" to "disabled",
|
||||
"ij_kotlin_allow_trailing_comma" to "true",
|
||||
"ij_kotlin_allow_trailing_comma_on_call_site" to "true",
|
||||
)
|
||||
|
@@ -10,9 +10,11 @@ import org.gradle.api.GradleException
|
||||
import org.gradle.api.NamedDomainObjectProvider
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.Task
|
||||
import org.gradle.api.artifacts.Dependency
|
||||
import org.gradle.api.attributes.TestSuiteType
|
||||
import org.gradle.api.file.FileSystemOperations
|
||||
import org.gradle.api.plugins.JavaPluginExtension
|
||||
import org.gradle.api.provider.ListProperty
|
||||
import org.gradle.api.provider.Provider
|
||||
import org.gradle.api.provider.SetProperty
|
||||
import org.gradle.api.reporting.ReportingExtension
|
||||
@@ -73,11 +75,17 @@ abstract class CCTweakedExtension(
|
||||
*/
|
||||
val sourceDirectories: SetProperty<SourceSetReference> = project.objects.setProperty(SourceSetReference::class.java)
|
||||
|
||||
/**
|
||||
* Dependencies excluded from published artifacts.
|
||||
*/
|
||||
private val excludedDeps: ListProperty<Dependency> = project.objects.listProperty(Dependency::class.java)
|
||||
|
||||
/** All source sets referenced by this project. */
|
||||
val sourceSets = sourceDirectories.map { x -> x.map { it.sourceSet } }
|
||||
|
||||
init {
|
||||
sourceDirectories.finalizeValueOnRead()
|
||||
excludedDeps.finalizeValueOnRead()
|
||||
project.afterEvaluate { sourceDirectories.disallowChanges() }
|
||||
}
|
||||
|
||||
@@ -246,6 +254,20 @@ abstract class CCTweakedExtension(
|
||||
).resolve().single()
|
||||
}
|
||||
|
||||
/**
|
||||
* Exclude a dependency from being publisehd in Maven.
|
||||
*/
|
||||
fun exclude(dep: Dependency) {
|
||||
excludedDeps.add(dep)
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a [MavenDependencySpec].
|
||||
*/
|
||||
fun configureExcludes(spec: MavenDependencySpec) {
|
||||
for (dep in excludedDeps.get()) spec.exclude(dep)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val COMMIT_COUNTS = Pattern.compile("""^\s*[0-9]+\s+(.*)$""")
|
||||
private val IGNORED_USERS = setOf(
|
||||
|
@@ -0,0 +1,72 @@
|
||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package cc.tweaked.gradle
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.GradleException
|
||||
import org.gradle.api.artifacts.Configuration
|
||||
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
|
||||
import org.gradle.api.artifacts.component.ModuleComponentSelector
|
||||
import org.gradle.api.artifacts.component.ProjectComponentIdentifier
|
||||
import org.gradle.api.artifacts.result.DependencyResult
|
||||
import org.gradle.api.artifacts.result.ResolvedDependencyResult
|
||||
import org.gradle.api.provider.ListProperty
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.gradle.language.base.plugins.LifecycleBasePlugin
|
||||
|
||||
abstract class DependencyCheck : DefaultTask() {
|
||||
@get:Input
|
||||
abstract val configuration: ListProperty<Configuration>
|
||||
|
||||
init {
|
||||
description = "Check :core's dependencies are consistent with Minecraft's."
|
||||
group = LifecycleBasePlugin.VERIFICATION_GROUP
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
fun run() {
|
||||
var ok = true
|
||||
for (configuration in configuration.get()) {
|
||||
configuration.incoming.resolutionResult.allDependencies {
|
||||
if (!check(this@allDependencies)) ok = false
|
||||
}
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
throw GradleException("Mismatched versions in Minecraft dependencies. gradle/libs.versions.toml may need updating.")
|
||||
}
|
||||
}
|
||||
|
||||
private fun check(dependency: DependencyResult): Boolean {
|
||||
if (dependency !is ResolvedDependencyResult) {
|
||||
logger.warn("Found unexpected dependency result {}", dependency)
|
||||
return false
|
||||
}
|
||||
|
||||
// Skip dependencies on non-modules.
|
||||
val requested = dependency.requested
|
||||
if (requested !is ModuleComponentSelector) return true
|
||||
|
||||
// If this dependency is specified within some project (so is non-transitive), or is pulled in via Minecraft,
|
||||
// then check for consistency.
|
||||
// It would be nice to be smarter about transitive dependencies, but avoiding false positives is hard.
|
||||
val from = dependency.from.id
|
||||
if (
|
||||
from is ProjectComponentIdentifier ||
|
||||
from is ModuleComponentIdentifier && (from.group == "net.minecraft" || from.group == "io.netty")
|
||||
) {
|
||||
// If the version is different between the requested and selected version, report an error.
|
||||
val selected = dependency.selected.moduleVersion!!.version
|
||||
if (requested.version != selected) {
|
||||
logger.error("Requested dependency {} (via {}) but got version {}", requested, from, selected)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
@@ -6,7 +6,6 @@ package cc.tweaked.gradle
|
||||
|
||||
import org.gradle.api.artifacts.dsl.DependencyHandler
|
||||
import org.gradle.api.file.FileSystemLocation
|
||||
import org.gradle.api.file.FileSystemLocationProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.provider.Provider
|
||||
import org.gradle.api.tasks.JavaExec
|
||||
@@ -129,3 +128,30 @@ fun <T> Property<T>.setProvider(provider: Provider<out T>) = set(provider)
|
||||
|
||||
/** Short-cut method to get the absolute path of a [FileSystemLocation] provider. */
|
||||
fun Provider<out FileSystemLocation>.getAbsolutePath(): String = get().asFile.absolutePath
|
||||
|
||||
/**
|
||||
* Get the version immediately after the provided version.
|
||||
*
|
||||
* For example, given "1.2.3", this will return "1.2.4".
|
||||
*/
|
||||
fun getNextVersion(version: String): String {
|
||||
// Split a version like x.y.z-SNAPSHOT into x.y.z and -SNAPSHOT
|
||||
val dashIndex = version.indexOf('-')
|
||||
val mainVersion = if (dashIndex < 0) version else version.substring(0, dashIndex)
|
||||
|
||||
// Find the last component in x.y.z and increment it.
|
||||
val lastIndex = mainVersion.lastIndexOf('.')
|
||||
if (lastIndex < 0) throw IllegalArgumentException("Cannot parse version format \"$version\"")
|
||||
val lastVersion = try {
|
||||
version.substring(lastIndex + 1).toInt()
|
||||
} catch (e: NumberFormatException) {
|
||||
throw IllegalArgumentException("Cannot parse version format \"$version\"", e)
|
||||
}
|
||||
|
||||
// Then append all components together.
|
||||
val out = StringBuilder()
|
||||
out.append(version, 0, lastIndex + 1)
|
||||
out.append(lastVersion + 1)
|
||||
if (dashIndex >= 0) out.append(version, dashIndex, version.length)
|
||||
return out.toString()
|
||||
}
|
||||
|
@@ -6,6 +6,8 @@ package cc.tweaked.gradle
|
||||
|
||||
import org.gradle.api.artifacts.Dependency
|
||||
import org.gradle.api.artifacts.MinimalExternalModuleDependency
|
||||
import org.gradle.api.artifacts.ProjectDependency
|
||||
import org.gradle.api.plugins.BasePluginExtension
|
||||
import org.gradle.api.publish.maven.MavenPublication
|
||||
import org.gradle.api.specs.Spec
|
||||
|
||||
@@ -26,8 +28,13 @@ class MavenDependencySpec {
|
||||
|
||||
fun exclude(dep: Dependency) {
|
||||
exclude {
|
||||
// We have to cheat a little for project dependencies, as the project name doesn't match the artifact group.
|
||||
val name = when (dep) {
|
||||
is ProjectDependency -> dep.dependencyProject.extensions.getByType(BasePluginExtension::class.java).archivesName.get()
|
||||
else -> dep.name
|
||||
}
|
||||
(dep.group.isNullOrEmpty() || dep.group == it.groupId) &&
|
||||
(dep.name.isNullOrEmpty() || dep.name == it.artifactId) &&
|
||||
(name.isNullOrEmpty() || name == it.artifactId) &&
|
||||
(dep.version.isNullOrEmpty() || dep.version == it.version)
|
||||
}
|
||||
}
|
||||
|
@@ -109,6 +109,15 @@ class MinecraftConfigurations private constructor(private val project: Project)
|
||||
project.extensions.configure(CCTweakedExtension::class.java) {
|
||||
sourceDirectories.add(SourceSetReference.internal(client))
|
||||
}
|
||||
|
||||
// Register a task to check there are no conflicts with the core project.
|
||||
val checkDependencyConsistency =
|
||||
project.tasks.register("checkDependencyConsistency", DependencyCheck::class.java) {
|
||||
// We need to check both the main and client classpath *configurations*, as the actual configuration
|
||||
configuration.add(configurations.named(main.runtimeClasspathConfigurationName))
|
||||
configuration.add(configurations.named(client.runtimeClasspathConfigurationName))
|
||||
}
|
||||
project.tasks.named("check") { dependsOn(checkDependencyConsistency) }
|
||||
}
|
||||
|
||||
private fun setupOutgoing(sourceSet: SourceSet, suffix: String = "") {
|
||||
|
@@ -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
|
||||
|
@@ -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.
|
||||
|
@@ -21,6 +21,20 @@ of the mod should run fine on later versions.
|
||||
However, some changes to the underlying game, or CC: Tweaked's own internals may break some programs. This page serves
|
||||
as documentation for breaking changes and "gotchas" one should look out for between versions.
|
||||
|
||||
## CC: Tweaked 1.109.0 to 1.109.2 {#cct-1.109}
|
||||
|
||||
- Update to Lua 5.2:
|
||||
- Support for Lua 5.0's pseudo-argument `arg` has been removed. You should always use `...` for varargs.
|
||||
- Environments are no longer baked into the runtime, and instead use the `_ENV` local or upvalue. `getfenv`/`setfenv`
|
||||
now only work on Lua functions with an `_ENV` upvalue. `getfenv` will return the global environment when called
|
||||
with other functions, and `setfenv` will have no effect.
|
||||
- `load`/`loadstring` defaults to using the global environment (`_G`) rather than the current coroutine's
|
||||
environment.
|
||||
- Support for dumping functions (`string.dump`) and loading binary chunks has been removed.
|
||||
- `math.random` now uses Lua 5.4's random number generator.
|
||||
|
||||
- File handles, HTTP requests and websockets now always use the original bytes rather than encoding/decoding to UTF-8.
|
||||
|
||||
## Minecraft 1.13 {#mc-1.13}
|
||||
- The "key code" for [`key`] and [`key_up`] events has changed, due to Minecraft updating to LWJGL 3. Make sure you're
|
||||
using the constants provided by the [`keys`] API, rather than hard-coding numerical values.
|
||||
|
@@ -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()
|
||||
|
@@ -10,7 +10,7 @@ kotlin.jvm.target.validation.mode=error
|
||||
|
||||
# Mod properties
|
||||
isUnstable=false
|
||||
modVersion=1.108.4
|
||||
modVersion=1.109.3
|
||||
|
||||
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
|
||||
mcVersion=1.20.1
|
||||
|
@@ -10,28 +10,29 @@
|
||||
fabric-api = "0.86.1+1.20.1"
|
||||
fabric-loader = "0.14.21"
|
||||
forge = "47.1.0"
|
||||
forgeSpi = "6.0.0"
|
||||
forgeSpi = "7.0.1"
|
||||
mixin = "0.8.5"
|
||||
parchment = "2023.08.20"
|
||||
parchmentMc = "1.20.1"
|
||||
|
||||
# Normal dependencies
|
||||
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.
|
||||
commonsCli = "1.3.1"
|
||||
# Core dependencies (these versions are tied to the version Minecraft uses)
|
||||
fastutil = "8.5.9"
|
||||
guava = "31.1-jre"
|
||||
jetbrainsAnnotations = "24.0.1"
|
||||
netty = "4.1.82.Final"
|
||||
slf4j = "2.0.1"
|
||||
|
||||
# Core dependencies (independent of Minecraft)
|
||||
asm = "9.6"
|
||||
autoService = "1.1.1"
|
||||
checkerFramework = "3.42.0"
|
||||
cobalt = "0.8.2"
|
||||
commonsCli = "1.6.0"
|
||||
jetbrainsAnnotations = "24.1.0"
|
||||
jsr305 = "3.0.2"
|
||||
jzlib = "1.1.3"
|
||||
kotlin = "1.8.10"
|
||||
kotlin-coroutines = "1.6.4"
|
||||
netty = "4.1.82.Final"
|
||||
kotlin = "1.9.21"
|
||||
kotlin-coroutines = "1.7.3"
|
||||
nightConfig = "3.6.7"
|
||||
slf4j = "2.0.1"
|
||||
|
||||
# Minecraft mods
|
||||
emi = "1.0.8+1.20.1"
|
||||
@@ -47,29 +48,31 @@ sodium = "mc1.20-0.4.10"
|
||||
|
||||
# Testing
|
||||
hamcrest = "2.2"
|
||||
jqwik = "1.7.4"
|
||||
junit = "5.10.0"
|
||||
jqwik = "1.8.2"
|
||||
junit = "5.10.1"
|
||||
|
||||
# Build tools
|
||||
cctJavadoc = "1.8.0"
|
||||
checkstyle = "10.12.3"
|
||||
cctJavadoc = "1.8.2"
|
||||
checkstyle = "10.12.6"
|
||||
curseForgeGradle = "1.0.14"
|
||||
errorProne-core = "2.21.1"
|
||||
errorProne-core = "2.23.0"
|
||||
errorProne-plugin = "3.1.0"
|
||||
fabric-loom = "1.3.7"
|
||||
fabric-loom = "1.3.9"
|
||||
forgeGradle = "6.0.8"
|
||||
githubRelease = "2.4.1"
|
||||
githubRelease = "2.5.2"
|
||||
gradleVersions = "0.50.0"
|
||||
ideaExt = "1.1.7"
|
||||
illuaminate = "0.1.0-44-g9ee0055"
|
||||
librarian = "1.+"
|
||||
lwjgl = "3.3.1"
|
||||
lwjgl = "3.3.3"
|
||||
minotaur = "2.+"
|
||||
mixinGradle = "0.7.+"
|
||||
mixinGradle = "0.7.38"
|
||||
nullAway = "0.9.9"
|
||||
spotless = "6.21.0"
|
||||
spotless = "6.23.3"
|
||||
taskTree = "2.1.1"
|
||||
teavm = "0.9.0-SQUID.1"
|
||||
teavm = "0.10.0-SQUID.2"
|
||||
vanillaGradle = "0.2.1-SNAPSHOT"
|
||||
versionCatalogUpdate = "0.8.1"
|
||||
vineflower = "1.11.0"
|
||||
|
||||
[libraries]
|
||||
@@ -78,7 +81,7 @@ 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" }
|
||||
@@ -162,10 +165,12 @@ 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" }
|
||||
gradleVersions = { id = "com.github.ben-manes.versions", version.ref = "gradleVersions" }
|
||||
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
||||
librarian = { id = "org.parchmentmc.librarian.forgegradle", version.ref = "librarian" }
|
||||
mixinGradle = { id = "org.spongepowered.mixin", version.ref = "mixinGradle" }
|
||||
taskTree = { id = "com.dorongold.task-tree", version.ref = "taskTree" }
|
||||
versionCatalogUpdate = { id = "nl.littlerobots.version-catalog-update", version.ref = "versionCatalogUpdate" }
|
||||
|
||||
[bundles]
|
||||
annotations = ["jsr305", "checkerFramework", "jetbrainsAnnotations"]
|
||||
@@ -184,5 +189,5 @@ test = ["junit-jupiter-api", "junit-jupiter-params", "hamcrest", "jqwik-api"]
|
||||
testRuntime = ["junit-jupiter-engine", "jqwik-engine"]
|
||||
|
||||
# Build tools
|
||||
teavm-api = [ "teavm-jso", "teavm-jso-apis", "teavm-platform", "teavm-classlib", "teavm-metaprogramming-api" ]
|
||||
teavm-tooling = [ "teavm-tooling", "teavm-metaprogramming-impl", "teavm-jso-impl" ]
|
||||
teavm-api = ["teavm-jso", "teavm-jso-apis", "teavm-platform", "teavm-classlib", "teavm-metaprogramming-api"]
|
||||
teavm-tooling = ["teavm-tooling", "teavm-metaprogramming-impl", "teavm-jso-impl"]
|
||||
|
@@ -6,7 +6,7 @@
|
||||
|
||||
(sources
|
||||
/doc/
|
||||
/projects/forge/build/docs/luaJavadoc/
|
||||
/projects/common/build/docs/luaJavadoc/
|
||||
/projects/core/src/main/resources/data/computercraft/lua/bios.lua
|
||||
/projects/core/src/main/resources/data/computercraft/lua/rom/
|
||||
/projects/core/src/test/resources/test-rom
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
(library-path
|
||||
/doc/stub/
|
||||
/projects/forge/build/docs/luaJavadoc/
|
||||
/projects/common/build/docs/luaJavadoc/
|
||||
|
||||
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/
|
||||
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/command/
|
||||
@@ -88,7 +88,7 @@
|
||||
(/doc/stub/
|
||||
/projects/core/src/main/resources/data/computercraft/lua/bios.lua
|
||||
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/
|
||||
/projects/forge/build/docs/luaJavadoc/)
|
||||
/projects/common/build/docs/luaJavadoc/)
|
||||
(linters -var:unused-global)
|
||||
(lint (allow-toplevel-global true)))
|
||||
|
||||
|
@@ -2,13 +2,12 @@
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
import cc.tweaked.gradle.annotationProcessorEverywhere
|
||||
import cc.tweaked.gradle.clientClasses
|
||||
import cc.tweaked.gradle.commonClasses
|
||||
import cc.tweaked.gradle.*
|
||||
|
||||
plugins {
|
||||
id("cc-tweaked.vanilla")
|
||||
id("cc-tweaked.gametest")
|
||||
id("cc-tweaked.illuaminate")
|
||||
id("cc-tweaked.publishing")
|
||||
}
|
||||
|
||||
@@ -19,6 +18,10 @@ minecraft {
|
||||
)
|
||||
}
|
||||
|
||||
configurations {
|
||||
register("cctJavadoc")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Pull in our other projects. See comments in MinecraftConfigurations on this nastiness.
|
||||
implementation(project(":core"))
|
||||
@@ -41,4 +44,53 @@ dependencies {
|
||||
testModImplementation(libs.bundles.kotlin)
|
||||
|
||||
testFixturesImplementation(testFixtures(project(":core")))
|
||||
|
||||
"cctJavadoc"(libs.cctJavadoc)
|
||||
}
|
||||
|
||||
illuaminate {
|
||||
version.set(libs.versions.illuaminate)
|
||||
}
|
||||
|
||||
val luaJavadoc by tasks.registering(Javadoc::class) {
|
||||
description = "Generates documentation for Java-side Lua functions."
|
||||
group = JavaBasePlugin.DOCUMENTATION_GROUP
|
||||
|
||||
val sourceSets = listOf(sourceSets.main.get(), project(":core").sourceSets.main.get())
|
||||
for (sourceSet in sourceSets) {
|
||||
source(sourceSet.java)
|
||||
classpath += sourceSet.compileClasspath
|
||||
}
|
||||
|
||||
destinationDir = layout.buildDirectory.dir("docs/luaJavadoc").get().asFile
|
||||
|
||||
val options = options as StandardJavadocDocletOptions
|
||||
options.docletpath = configurations["cctJavadoc"].files.toList()
|
||||
options.doclet = "cc.tweaked.javadoc.LuaDoclet"
|
||||
options.addStringOption("project-root", rootProject.file(".").absolutePath)
|
||||
options.noTimestamp(false)
|
||||
|
||||
javadocTool.set(
|
||||
javaToolchains.javadocToolFor {
|
||||
languageVersion.set(cc.tweaked.gradle.CCTweakedPlugin.JAVA_VERSION)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
val lintLua by tasks.registering(IlluaminateExec::class) {
|
||||
group = JavaBasePlugin.VERIFICATION_GROUP
|
||||
description = "Lint Lua (and Lua docs) with illuaminate"
|
||||
|
||||
// Config files
|
||||
inputs.file(rootProject.file("illuaminate.sexp")).withPropertyName("illuaminate.sexp")
|
||||
// Sources
|
||||
inputs.files(rootProject.fileTree("doc")).withPropertyName("docs")
|
||||
inputs.files(project(":core").fileTree("src/main/resources/data/computercraft/lua")).withPropertyName("lua rom")
|
||||
inputs.files(luaJavadoc)
|
||||
|
||||
args = listOf("lint")
|
||||
workingDir = rootProject.projectDir
|
||||
|
||||
doFirst { if (System.getenv("GITHUB_ACTIONS") != null) println("::add-matcher::.github/matchers/illuaminate.json") }
|
||||
doLast { if (System.getenv("GITHUB_ACTIONS") != null) println("::remove-matcher owner=illuaminate::") }
|
||||
}
|
||||
|
@@ -19,6 +19,7 @@ import dan200.computercraft.shared.network.server.UploadFileMessage;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.components.events.GuiEventListener;
|
||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
@@ -144,6 +145,11 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
|
||||
|| super.mouseDragged(x, y, button, deltaX, deltaY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFocused(@Nullable GuiEventListener listener) {
|
||||
// Don't clear and re-focus if we're already focused.
|
||||
if (listener != getFocused()) super.setFocused(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void renderLabels(GuiGraphics graphics, int mouseX, int mouseY) {
|
||||
|
@@ -8,8 +8,8 @@ import com.mojang.blaze3d.vertex.Tesselator;
|
||||
import dan200.computercraft.client.render.RenderTypes;
|
||||
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.core.util.StringUtil;
|
||||
import dan200.computercraft.shared.computer.core.InputHandler;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.components.AbstractWidget;
|
||||
@@ -112,26 +112,8 @@ public class TerminalWidget extends AbstractWidget {
|
||||
}
|
||||
|
||||
private void paste() {
|
||||
var clipboard = Minecraft.getInstance().keyboardHandler.getClipboard();
|
||||
|
||||
// Clip to the first occurrence of \r or \n
|
||||
var newLineIndex1 = clipboard.indexOf('\r');
|
||||
var newLineIndex2 = clipboard.indexOf('\n');
|
||||
if (newLineIndex1 >= 0 && newLineIndex2 >= 0) {
|
||||
clipboard = clipboard.substring(0, Math.min(newLineIndex1, newLineIndex2));
|
||||
} else if (newLineIndex1 >= 0) {
|
||||
clipboard = clipboard.substring(0, newLineIndex1);
|
||||
} else if (newLineIndex2 >= 0) {
|
||||
clipboard = clipboard.substring(0, newLineIndex2);
|
||||
}
|
||||
|
||||
// Filter the string
|
||||
clipboard = SharedConstants.filterText(clipboard);
|
||||
if (!clipboard.isEmpty()) {
|
||||
// Clip to 512 characters and queue the event
|
||||
if (clipboard.length() > 512) clipboard = clipboard.substring(0, 512);
|
||||
computer.queueEvent("paste", new Object[]{ clipboard });
|
||||
}
|
||||
var clipboard = StringUtil.normaliseClipboardString(Minecraft.getInstance().keyboardHandler.getClipboard());
|
||||
if (!clipboard.isEmpty()) computer.queueEvent("paste", new Object[]{ clipboard });
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -264,7 +246,7 @@ public class TerminalWidget extends AbstractWidget {
|
||||
keysDown.clear();
|
||||
|
||||
// When blurring, we should make the last mouse button go up
|
||||
if (lastMouseButton > 0) {
|
||||
if (lastMouseButton >= 0) {
|
||||
computer.mouseUp(lastMouseButton + 1, lastMouseX + 1, lastMouseY + 1);
|
||||
lastMouseButton = -1;
|
||||
}
|
||||
|
@@ -11,7 +11,6 @@ import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.client.platform.ClientPlatformHelper;
|
||||
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
|
||||
import dan200.computercraft.shared.util.Holiday;
|
||||
import net.minecraft.client.Minecraft;
|
||||
@@ -21,7 +20,6 @@ import net.minecraft.client.renderer.blockentity.BlockEntityRenderDispatcher;
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.HitResult;
|
||||
@@ -29,8 +27,6 @@ import net.minecraft.world.phys.HitResult;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBlockEntity> {
|
||||
private static final ModelResourceLocation NORMAL_TURTLE_MODEL = new ModelResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_normal", "inventory");
|
||||
private static final ModelResourceLocation ADVANCED_TURTLE_MODEL = new ModelResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_advanced", "inventory");
|
||||
private static final ResourceLocation COLOUR_TURTLE_MODEL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_colour");
|
||||
private static final ResourceLocation ELF_OVERLAY_MODEL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_elf_overlay");
|
||||
|
||||
@@ -42,13 +38,6 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
|
||||
font = context.getFont();
|
||||
}
|
||||
|
||||
public static ResourceLocation getTurtleModel(ComputerFamily family, boolean coloured) {
|
||||
return switch (family) {
|
||||
default -> coloured ? COLOUR_TURTLE_MODEL : NORMAL_TURTLE_MODEL;
|
||||
case ADVANCED -> coloured ? COLOUR_TURTLE_MODEL : ADVANCED_TURTLE_MODEL;
|
||||
};
|
||||
}
|
||||
|
||||
public static @Nullable ResourceLocation getTurtleOverlayModel(@Nullable ResourceLocation overlay, boolean christmas) {
|
||||
if (overlay != null) return overlay;
|
||||
if (christmas) return ELF_OVERLAY_MODEL;
|
||||
@@ -78,7 +67,6 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
|
||||
var matrix = transform.last().pose();
|
||||
var opacity = (int) (mc.options.getBackgroundOpacity(0.25f) * 255) << 24;
|
||||
var width = -font.width(label) / 2.0f;
|
||||
// TODO: Check this looks okay
|
||||
font.drawInBatch(label, width, (float) 0, 0x20ffffff, false, matrix, buffers, Font.DisplayMode.SEE_THROUGH, opacity, lightmapCoord);
|
||||
font.drawInBatch(label, width, (float) 0, 0xffffffff, false, matrix, buffers, Font.DisplayMode.NORMAL, 0, lightmapCoord);
|
||||
|
||||
@@ -96,10 +84,18 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
|
||||
|
||||
// Render the turtle
|
||||
var colour = turtle.getColour();
|
||||
var family = turtle.getFamily();
|
||||
var overlay = turtle.getOverlay();
|
||||
|
||||
renderModel(transform, buffers, lightmapCoord, overlayLight, getTurtleModel(family, colour != -1), colour == -1 ? null : new int[]{ colour });
|
||||
if (colour == -1) {
|
||||
// Render the turtle using its item model.
|
||||
var modelManager = Minecraft.getInstance().getItemRenderer().getItemModelShaper();
|
||||
var model = modelManager.getItemModel(turtle.getBlockState().getBlock().asItem());
|
||||
if (model == null) model = modelManager.getModelManager().getMissingModel();
|
||||
renderModel(transform, buffers, lightmapCoord, overlayLight, model, null);
|
||||
} else {
|
||||
// Otherwise render it using the colour item.
|
||||
renderModel(transform, buffers, lightmapCoord, overlayLight, COLOUR_TURTLE_MODEL, new int[]{ colour });
|
||||
}
|
||||
|
||||
// Render the overlay
|
||||
var overlayModel = getTurtleOverlayModel(overlay, Holiday.getCurrent() == Holiday.CHRISTMAS);
|
||||
|
@@ -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");
|
||||
|
@@ -13,7 +13,6 @@ import dan200.computercraft.api.upgrades.UpgradeData;
|
||||
import dan200.computercraft.core.util.Colour;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.common.IColouredItem;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.platform.PlatformHelper;
|
||||
import dan200.computercraft.shared.platform.RecipeIngredients;
|
||||
import dan200.computercraft.shared.platform.RegistryWrappers;
|
||||
@@ -38,7 +37,6 @@ import net.minecraft.world.level.ItemLike;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -106,14 +104,13 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
||||
private void turtleUpgrades(Consumer<FinishedRecipe> add) {
|
||||
for (var turtleItem : turtleItems()) {
|
||||
var base = turtleItem.create(-1, null, -1, null, null, 0, null);
|
||||
|
||||
var nameId = turtleItem.getFamily().name().toLowerCase(Locale.ROOT);
|
||||
var name = RegistryWrappers.ITEMS.getKey(turtleItem);
|
||||
|
||||
for (var upgrade : turtleUpgrades.getGeneratedUpgrades()) {
|
||||
var result = turtleItem.create(-1, null, -1, null, UpgradeData.ofDefault(upgrade), -1, null);
|
||||
ShapedRecipeBuilder
|
||||
.shaped(RecipeCategory.REDSTONE, result.getItem())
|
||||
.group(String.format("%s:turtle_%s", ComputerCraftAPI.MOD_ID, nameId))
|
||||
.group(name.toString())
|
||||
.pattern("#T")
|
||||
.define('T', base.getItem())
|
||||
.define('#', upgrade.getCraftingItem().getItem())
|
||||
@@ -121,9 +118,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
||||
inventoryChange(base.getItem(), upgrade.getCraftingItem().getItem()))
|
||||
.save(
|
||||
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.IMPOSTOR_SHAPED.get(), add).withResultTag(result.getTag()),
|
||||
new ResourceLocation(ComputerCraftAPI.MOD_ID, String.format("turtle_%s/%s/%s",
|
||||
nameId, upgrade.getUpgradeID().getNamespace(), upgrade.getUpgradeID().getPath()
|
||||
))
|
||||
name.withSuffix(String.format("/%s/%s", upgrade.getUpgradeID().getNamespace(), upgrade.getUpgradeID().getPath()))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -141,15 +136,13 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
||||
private void pocketUpgrades(Consumer<FinishedRecipe> add) {
|
||||
for (var pocket : pocketComputerItems()) {
|
||||
var base = pocket.create(-1, null, -1, null);
|
||||
if (base.isEmpty()) continue;
|
||||
|
||||
var nameId = pocket.getFamily().name().toLowerCase(Locale.ROOT);
|
||||
var name = RegistryWrappers.ITEMS.getKey(pocket).withPath(x -> x.replace("pocket_computer_", "pocket_"));
|
||||
|
||||
for (var upgrade : pocketUpgrades.getGeneratedUpgrades()) {
|
||||
var result = pocket.create(-1, null, -1, UpgradeData.ofDefault(upgrade));
|
||||
ShapedRecipeBuilder
|
||||
.shaped(RecipeCategory.REDSTONE, result.getItem())
|
||||
.group(String.format("%s:pocket_%s", ComputerCraftAPI.MOD_ID, nameId))
|
||||
.group(name.toString())
|
||||
.pattern("#")
|
||||
.pattern("P")
|
||||
.define('P', base.getItem())
|
||||
@@ -158,9 +151,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
||||
inventoryChange(base.getItem(), upgrade.getCraftingItem().getItem()))
|
||||
.save(
|
||||
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.IMPOSTOR_SHAPED.get(), add).withResultTag(result.getTag()),
|
||||
new ResourceLocation(ComputerCraftAPI.MOD_ID, String.format("pocket_%s/%s/%s",
|
||||
nameId, upgrade.getUpgradeID().getNamespace(), upgrade.getUpgradeID().getPath()
|
||||
))
|
||||
name.withSuffix(String.format("/%s/%s", upgrade.getUpgradeID().getNamespace(), upgrade.getUpgradeID().getPath()))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -190,12 +181,10 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
||||
private void turtleOverlay(Consumer<FinishedRecipe> add, String overlay, Consumer<ShapelessRecipeBuilder> build) {
|
||||
for (var turtleItem : turtleItems()) {
|
||||
var base = turtleItem.create(-1, null, -1, null, null, 0, null);
|
||||
|
||||
var nameId = turtleItem.getFamily().name().toLowerCase(Locale.ROOT);
|
||||
var group = "%s:turtle_%s_overlay".formatted(ComputerCraftAPI.MOD_ID, nameId);
|
||||
var name = RegistryWrappers.ITEMS.getKey(turtleItem);
|
||||
|
||||
var builder = ShapelessRecipeBuilder.shapeless(RecipeCategory.REDSTONE, base.getItem())
|
||||
.group(group)
|
||||
.group(name.withSuffix("_overlay").toString())
|
||||
.unlockedBy("has_turtle", inventoryChange(base.getItem()));
|
||||
build.accept(builder);
|
||||
builder
|
||||
@@ -204,7 +193,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
||||
RecipeWrapper
|
||||
.wrap(ModRegistry.RecipeSerializers.TURTLE_OVERLAY.get(), add)
|
||||
.withExtraData(x -> x.addProperty("overlay", new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/" + overlay).toString())),
|
||||
new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_%s_overlays/%s".formatted(nameId, overlay))
|
||||
name.withSuffix("_overlays/" + overlay)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -253,7 +242,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
||||
.define('C', ModRegistry.Items.COMPUTER_NORMAL.get())
|
||||
.unlockedBy("has_components", inventoryChange(itemPredicate(ModRegistry.Items.COMPUTER_NORMAL.get()), itemPredicate(ingredients.goldIngot())))
|
||||
.save(
|
||||
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get(), add).withExtraData(family(ComputerFamily.ADVANCED)),
|
||||
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get(), add),
|
||||
new ResourceLocation(ComputerCraftAPI.MOD_ID, "computer_advanced_upgrade")
|
||||
);
|
||||
|
||||
@@ -277,7 +266,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
||||
.define('C', ModRegistry.Items.COMPUTER_NORMAL.get())
|
||||
.define('I', ingredients.woodenChest())
|
||||
.unlockedBy("has_computer", inventoryChange(ModRegistry.Items.COMPUTER_NORMAL.get()))
|
||||
.save(RecipeWrapper.wrap(ModRegistry.RecipeSerializers.TURTLE.get(), add).withExtraData(family(ComputerFamily.NORMAL)));
|
||||
.save(RecipeWrapper.wrap(ModRegistry.RecipeSerializers.TURTLE.get(), add));
|
||||
|
||||
ShapedRecipeBuilder
|
||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Blocks.TURTLE_ADVANCED.get())
|
||||
@@ -288,7 +277,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
||||
.define('C', ModRegistry.Items.COMPUTER_ADVANCED.get())
|
||||
.define('I', ingredients.woodenChest())
|
||||
.unlockedBy("has_computer", inventoryChange(ModRegistry.Items.COMPUTER_NORMAL.get()))
|
||||
.save(RecipeWrapper.wrap(ModRegistry.RecipeSerializers.TURTLE.get(), add).withExtraData(family(ComputerFamily.ADVANCED)));
|
||||
.save(RecipeWrapper.wrap(ModRegistry.RecipeSerializers.TURTLE.get(), add));
|
||||
|
||||
ShapedRecipeBuilder
|
||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Blocks.TURTLE_ADVANCED.get())
|
||||
@@ -300,7 +289,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
||||
.define('B', ingredients.goldBlock())
|
||||
.unlockedBy("has_components", inventoryChange(itemPredicate(ModRegistry.Items.TURTLE_NORMAL.get()), itemPredicate(ingredients.goldIngot())))
|
||||
.save(
|
||||
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get(), add).withExtraData(family(ComputerFamily.ADVANCED)),
|
||||
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get(), add),
|
||||
new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_advanced_upgrade")
|
||||
);
|
||||
|
||||
@@ -367,7 +356,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
||||
.define('C', ModRegistry.Items.POCKET_COMPUTER_NORMAL.get())
|
||||
.unlockedBy("has_components", inventoryChange(itemPredicate(ModRegistry.Items.POCKET_COMPUTER_NORMAL.get()), itemPredicate(ingredients.goldIngot())))
|
||||
.save(
|
||||
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get(), add).withExtraData(family(ComputerFamily.ADVANCED)),
|
||||
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get(), add),
|
||||
new ResourceLocation(ComputerCraftAPI.MOD_ID, "pocket_computer_advanced_upgrade")
|
||||
);
|
||||
|
||||
@@ -519,10 +508,6 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
||||
return tag;
|
||||
}
|
||||
|
||||
private static Consumer<JsonObject> family(ComputerFamily family) {
|
||||
return json -> json.addProperty("family", family.getSerializedName());
|
||||
}
|
||||
|
||||
private static void addSpecial(Consumer<FinishedRecipe> add, SimpleCraftingRecipeSerializer<?> special) {
|
||||
SpecialRecipeBuilder.special(special).save(add, RegistryWrappers.RECIPE_SERIALIZERS.getKey(special).toString());
|
||||
}
|
||||
|
@@ -58,7 +58,10 @@ class TagProvider {
|
||||
|
||||
tags.tag(ComputerCraftTags.Blocks.TURTLE_SWORD_BREAKABLE).addTag(BlockTags.WOOL).add(Blocks.COBWEB);
|
||||
|
||||
tags.tag(ComputerCraftTags.Blocks.TURTLE_CAN_USE).addTag(BlockTags.CAULDRONS).addTag(BlockTags.BEEHIVES);
|
||||
tags.tag(ComputerCraftTags.Blocks.TURTLE_CAN_USE)
|
||||
.addTag(BlockTags.BEEHIVES)
|
||||
.addTag(BlockTags.CAULDRONS)
|
||||
.add(Blocks.COMPOSTER);
|
||||
|
||||
// Make all blocks aside from command computer mineable.
|
||||
tags.tag(BlockTags.MINEABLE_WITH_PICKAXE).add(
|
||||
@@ -76,6 +79,8 @@ class TagProvider {
|
||||
ModRegistry.Blocks.WIRED_MODEM_FULL.get(),
|
||||
ModRegistry.Blocks.CABLE.get()
|
||||
);
|
||||
|
||||
tags.tag(BlockTags.WITHER_IMMUNE).add(ModRegistry.Blocks.COMPUTER_COMMAND.get());
|
||||
}
|
||||
|
||||
public static void itemTags(ItemTagConsumer tags) {
|
||||
|
@@ -14,12 +14,15 @@ import dan200.computercraft.shared.computer.metrics.ComputerMBean;
|
||||
import dan200.computercraft.shared.peripheral.monitor.MonitorWatcher;
|
||||
import dan200.computercraft.shared.util.DropConsumer;
|
||||
import dan200.computercraft.shared.util.TickScheduler;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.dedicated.DedicatedServer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.server.packs.resources.PreparableReloadListener;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.item.CreativeModeTab;
|
||||
import net.minecraft.world.item.CreativeModeTabs;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.level.storage.loot.BuiltInLootTables;
|
||||
@@ -111,4 +114,17 @@ public final class CommonHooks {
|
||||
public static boolean onLivingDrop(Entity entity, ItemStack stack) {
|
||||
return DropConsumer.onLivingDrop(entity, stack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add items to an existing creative tab.
|
||||
*
|
||||
* @param key The {@link ResourceKey} for this creative tab.
|
||||
* @param context Additional parameters used for building the contents.
|
||||
* @param out The creative tab output to append items to.
|
||||
*/
|
||||
public static void onBuildCreativeTab(ResourceKey<CreativeModeTab> key, CreativeModeTab.ItemDisplayParameters context, CreativeModeTab.Output out) {
|
||||
if (key == CreativeModeTabs.OP_BLOCKS && context.hasPermissions()) {
|
||||
out.accept(ModRegistry.Items.COMPUTER_COMMAND.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -34,6 +34,7 @@ import dan200.computercraft.shared.computer.inventory.ViewComputerMenu;
|
||||
import dan200.computercraft.shared.computer.items.CommandComputerItem;
|
||||
import dan200.computercraft.shared.computer.items.ComputerItem;
|
||||
import dan200.computercraft.shared.computer.recipe.ComputerUpgradeRecipe;
|
||||
import dan200.computercraft.shared.config.Config;
|
||||
import dan200.computercraft.shared.data.BlockNamedEntityLootCondition;
|
||||
import dan200.computercraft.shared.data.ConstantLootConditionSerializer;
|
||||
import dan200.computercraft.shared.data.HasComputerIdLootCondition;
|
||||
@@ -139,19 +140,17 @@ public final class ModRegistry {
|
||||
}
|
||||
|
||||
public static final RegistryEntry<ComputerBlock<ComputerBlockEntity>> COMPUTER_NORMAL = REGISTRY.register("computer_normal",
|
||||
() -> new ComputerBlock<>(computerProperties().mapColor(MapColor.STONE), ComputerFamily.NORMAL, BlockEntities.COMPUTER_NORMAL));
|
||||
() -> new ComputerBlock<>(computerProperties().mapColor(MapColor.STONE), BlockEntities.COMPUTER_NORMAL));
|
||||
public static final RegistryEntry<ComputerBlock<ComputerBlockEntity>> COMPUTER_ADVANCED = REGISTRY.register("computer_advanced",
|
||||
() -> new ComputerBlock<>(computerProperties().mapColor(MapColor.GOLD), ComputerFamily.ADVANCED, BlockEntities.COMPUTER_ADVANCED));
|
||||
() -> new ComputerBlock<>(computerProperties().mapColor(MapColor.GOLD), BlockEntities.COMPUTER_ADVANCED));
|
||||
|
||||
public static final RegistryEntry<ComputerBlock<CommandComputerBlockEntity>> COMPUTER_COMMAND = REGISTRY.register("computer_command", () -> new CommandComputerBlock<>(
|
||||
computerProperties().strength(-1, 6000000.0F),
|
||||
ComputerFamily.COMMAND, BlockEntities.COMPUTER_COMMAND
|
||||
));
|
||||
public static final RegistryEntry<ComputerBlock<CommandComputerBlockEntity>> COMPUTER_COMMAND = REGISTRY.register("computer_command",
|
||||
() -> new CommandComputerBlock<>(computerProperties().strength(-1, 6000000.0F), BlockEntities.COMPUTER_COMMAND));
|
||||
|
||||
public static final RegistryEntry<TurtleBlock> TURTLE_NORMAL = REGISTRY.register("turtle_normal",
|
||||
() -> new TurtleBlock(turtleProperties().mapColor(MapColor.STONE), ComputerFamily.NORMAL, BlockEntities.TURTLE_NORMAL));
|
||||
() -> new TurtleBlock(turtleProperties().mapColor(MapColor.STONE), BlockEntities.TURTLE_NORMAL));
|
||||
public static final RegistryEntry<TurtleBlock> TURTLE_ADVANCED = REGISTRY.register("turtle_advanced",
|
||||
() -> new TurtleBlock(turtleProperties().mapColor(MapColor.GOLD), ComputerFamily.ADVANCED, BlockEntities.TURTLE_ADVANCED));
|
||||
() -> new TurtleBlock(turtleProperties().mapColor(MapColor.GOLD).explosionResistance(TurtleBlock.IMMUNE_EXPLOSION_RESISTANCE), BlockEntities.TURTLE_ADVANCED));
|
||||
|
||||
public static final RegistryEntry<SpeakerBlock> SPEAKER = REGISTRY.register("speaker", () -> new SpeakerBlock(properties().mapColor(MapColor.STONE)));
|
||||
public static final RegistryEntry<DiskDriveBlock> DISK_DRIVE = REGISTRY.register("disk_drive", () -> new DiskDriveBlock(properties().mapColor(MapColor.STONE)));
|
||||
@@ -192,9 +191,9 @@ public final class ModRegistry {
|
||||
ofBlock(Blocks.COMPUTER_COMMAND, (p, s) -> new CommandComputerBlockEntity(BlockEntities.COMPUTER_COMMAND.get(), p, s));
|
||||
|
||||
public static final RegistryEntry<BlockEntityType<TurtleBlockEntity>> TURTLE_NORMAL =
|
||||
ofBlock(Blocks.TURTLE_NORMAL, (p, s) -> new TurtleBlockEntity(BlockEntities.TURTLE_NORMAL.get(), p, s, ComputerFamily.NORMAL));
|
||||
ofBlock(Blocks.TURTLE_NORMAL, (p, s) -> new TurtleBlockEntity(BlockEntities.TURTLE_NORMAL.get(), p, s, () -> Config.turtleFuelLimit, ComputerFamily.NORMAL));
|
||||
public static final RegistryEntry<BlockEntityType<TurtleBlockEntity>> TURTLE_ADVANCED =
|
||||
ofBlock(Blocks.TURTLE_ADVANCED, (p, s) -> new TurtleBlockEntity(BlockEntities.TURTLE_ADVANCED.get(), p, s, ComputerFamily.ADVANCED));
|
||||
ofBlock(Blocks.TURTLE_ADVANCED, (p, s) -> new TurtleBlockEntity(BlockEntities.TURTLE_ADVANCED.get(), p, s, () -> Config.advancedTurtleFuelLimit, ComputerFamily.ADVANCED));
|
||||
|
||||
public static final RegistryEntry<BlockEntityType<SpeakerBlockEntity>> SPEAKER =
|
||||
ofBlock(Blocks.SPEAKER, (p, s) -> new SpeakerBlockEntity(BlockEntities.SPEAKER.get(), p, s));
|
||||
@@ -311,7 +310,10 @@ public final class ModRegistry {
|
||||
() -> new MenuType<>(PrinterMenu::new, FeatureFlags.VANILLA_SET));
|
||||
|
||||
public static final RegistryEntry<MenuType<HeldItemMenu>> PRINTOUT = REGISTRY.register("printout",
|
||||
() -> ContainerData.toType(HeldItemContainerData::new, HeldItemMenu::createPrintout));
|
||||
() -> ContainerData.toType(
|
||||
HeldItemContainerData::new,
|
||||
(id, inventory, data) -> new HeldItemMenu(Menus.PRINTOUT.get(), id, inventory.player, data.getHand())
|
||||
));
|
||||
|
||||
public static final RegistryEntry<MenuType<ViewComputerMenu>> VIEW_COMPUTER = REGISTRY.register("view_computer",
|
||||
() -> ContainerData.toType(ComputerContainerData::new, ViewComputerMenu::new));
|
||||
@@ -371,11 +373,11 @@ public final class ModRegistry {
|
||||
public static final RegistryEntry<SimpleCraftingRecipeSerializer<ClearColourRecipe>> DYEABLE_ITEM_CLEAR = simple("clear_colour", ClearColourRecipe::new);
|
||||
public static final RegistryEntry<RecipeSerializer<TurtleRecipe>> TURTLE = REGISTRY.register("turtle", () -> TurtleRecipe.validatingSerialiser(TurtleRecipe::of));
|
||||
public static final RegistryEntry<SimpleCraftingRecipeSerializer<TurtleUpgradeRecipe>> TURTLE_UPGRADE = simple("turtle_upgrade", TurtleUpgradeRecipe::new);
|
||||
public static final RegistryEntry<RecipeSerializer<TurtleOverlayRecipe>> TURTLE_OVERLAY = REGISTRY.register("turtle_overlay", TurtleOverlayRecipe.Serializer::new);
|
||||
public static final RegistryEntry<RecipeSerializer<TurtleOverlayRecipe>> TURTLE_OVERLAY = REGISTRY.register("turtle_overlay", TurtleOverlayRecipe.Serialiser::new);
|
||||
public static final RegistryEntry<SimpleCraftingRecipeSerializer<PocketComputerUpgradeRecipe>> POCKET_COMPUTER_UPGRADE = simple("pocket_computer_upgrade", PocketComputerUpgradeRecipe::new);
|
||||
public static final RegistryEntry<SimpleCraftingRecipeSerializer<PrintoutRecipe>> PRINTOUT = simple("printout", PrintoutRecipe::new);
|
||||
public static final RegistryEntry<SimpleCraftingRecipeSerializer<DiskRecipe>> DISK = simple("disk", DiskRecipe::new);
|
||||
public static final RegistryEntry<RecipeSerializer<ComputerUpgradeRecipe>> COMPUTER_UPGRADE = REGISTRY.register("computer_upgrade", ComputerUpgradeRecipe.Serializer::new);
|
||||
public static final RegistryEntry<RecipeSerializer<ComputerUpgradeRecipe>> COMPUTER_UPGRADE = REGISTRY.register("computer_upgrade", () -> CustomShapedRecipe.validatingSerialiser(ComputerUpgradeRecipe::of));
|
||||
}
|
||||
|
||||
public static class Permissions {
|
||||
@@ -464,8 +466,8 @@ public final class ModRegistry {
|
||||
* Register any objects which must be done on the main thread.
|
||||
*/
|
||||
public static void registerMainThread() {
|
||||
CauldronInteraction.WATER.put(ModRegistry.Items.TURTLE_NORMAL.get(), TurtleItem.CAULDRON_INTERACTION);
|
||||
CauldronInteraction.WATER.put(ModRegistry.Items.TURTLE_ADVANCED.get(), TurtleItem.CAULDRON_INTERACTION);
|
||||
CauldronInteraction.WATER.put(Items.TURTLE_NORMAL.get(), TurtleItem.CAULDRON_INTERACTION);
|
||||
CauldronInteraction.WATER.put(Items.TURTLE_ADVANCED.get(), TurtleItem.CAULDRON_INTERACTION);
|
||||
}
|
||||
|
||||
private static void addTurtle(CreativeModeTab.Output out, TurtleItem turtle) {
|
||||
|
@@ -4,8 +4,6 @@
|
||||
|
||||
package dan200.computercraft.shared.common;
|
||||
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.network.container.HeldItemContainerData;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.MenuProvider;
|
||||
@@ -28,10 +26,6 @@ public class HeldItemMenu extends AbstractContainerMenu {
|
||||
stack = player.getItemInHand(hand).copy();
|
||||
}
|
||||
|
||||
public static HeldItemMenu createPrintout(int id, Inventory inventory, HeldItemContainerData data) {
|
||||
return new HeldItemMenu(ModRegistry.Menus.PRINTOUT.get(), id, inventory.player, data.getHand());
|
||||
}
|
||||
|
||||
public ItemStack getStack() {
|
||||
return stack;
|
||||
}
|
||||
|
@@ -7,7 +7,6 @@ package dan200.computercraft.shared.computer.blocks;
|
||||
import dan200.computercraft.annotations.ForgeOverride;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.shared.common.IBundledRedstoneBlock;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.items.IComputerItem;
|
||||
import dan200.computercraft.shared.platform.RegistryEntry;
|
||||
import dan200.computercraft.shared.util.BlockEntityHelpers;
|
||||
@@ -42,13 +41,11 @@ import javax.annotation.Nullable;
|
||||
public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntity> extends HorizontalDirectionalBlock implements IBundledRedstoneBlock, EntityBlock {
|
||||
private static final ResourceLocation DROP = new ResourceLocation(ComputerCraftAPI.MOD_ID, "computer");
|
||||
|
||||
private final ComputerFamily family;
|
||||
protected final RegistryEntry<BlockEntityType<T>> type;
|
||||
private final BlockEntityTicker<T> serverTicker = (level, pos, state, computer) -> computer.serverTick();
|
||||
|
||||
protected AbstractComputerBlock(Properties settings, ComputerFamily family, RegistryEntry<BlockEntityType<T>> type) {
|
||||
protected AbstractComputerBlock(Properties settings, RegistryEntry<BlockEntityType<T>> type) {
|
||||
super(settings);
|
||||
this.family = family;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@@ -82,10 +79,6 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit
|
||||
|
||||
protected abstract ItemStack getItem(AbstractComputerBlockEntity tile);
|
||||
|
||||
public ComputerFamily getFamily() {
|
||||
return family;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public int getSignal(BlockState state, BlockGetter world, BlockPos pos, Direction incomingSide) {
|
||||
|
@@ -4,7 +4,6 @@
|
||||
|
||||
package dan200.computercraft.shared.computer.blocks;
|
||||
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.platform.RegistryEntry;
|
||||
import net.minecraft.world.level.block.GameMasterBlock;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
@@ -17,7 +16,7 @@ import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
* @see dan200.computercraft.shared.computer.items.CommandComputerItem
|
||||
*/
|
||||
public class CommandComputerBlock<T extends CommandComputerBlockEntity> extends ComputerBlock<T> implements GameMasterBlock {
|
||||
public CommandComputerBlock(Properties settings, ComputerFamily family, RegistryEntry<BlockEntityType<T>> type) {
|
||||
super(settings, family, type);
|
||||
public CommandComputerBlock(Properties settings, RegistryEntry<BlockEntityType<T>> type) {
|
||||
super(settings, type);
|
||||
}
|
||||
}
|
||||
|
@@ -4,9 +4,8 @@
|
||||
|
||||
package dan200.computercraft.shared.computer.blocks;
|
||||
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.core.ComputerState;
|
||||
import dan200.computercraft.shared.computer.items.ComputerItemFactory;
|
||||
import dan200.computercraft.shared.computer.items.ComputerItem;
|
||||
import dan200.computercraft.shared.platform.RegistryEntry;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
@@ -25,8 +24,8 @@ public class ComputerBlock<T extends ComputerBlockEntity> extends AbstractComput
|
||||
public static final EnumProperty<ComputerState> STATE = EnumProperty.create("state", ComputerState.class);
|
||||
public static final DirectionProperty FACING = BlockStateProperties.HORIZONTAL_FACING;
|
||||
|
||||
public ComputerBlock(Properties settings, ComputerFamily family, RegistryEntry<BlockEntityType<T>> type) {
|
||||
super(settings, family, type);
|
||||
public ComputerBlock(Properties settings, RegistryEntry<BlockEntityType<T>> type) {
|
||||
super(settings, type);
|
||||
registerDefaultState(defaultBlockState()
|
||||
.setValue(FACING, Direction.NORTH)
|
||||
.setValue(STATE, ComputerState.OFF)
|
||||
@@ -46,6 +45,9 @@ public class ComputerBlock<T extends ComputerBlockEntity> extends AbstractComput
|
||||
|
||||
@Override
|
||||
protected ItemStack getItem(AbstractComputerBlockEntity tile) {
|
||||
return tile instanceof ComputerBlockEntity ? ComputerItemFactory.create((ComputerBlockEntity) tile) : ItemStack.EMPTY;
|
||||
if (!(tile instanceof ComputerBlockEntity computer)) return ItemStack.EMPTY;
|
||||
if (!(asItem() instanceof ComputerItem item)) return ItemStack.EMPTY;
|
||||
|
||||
return item.create(computer.getComputerID(), computer.getLabel());
|
||||
}
|
||||
}
|
||||
|
@@ -32,11 +32,9 @@ public class ComputerBlockEntity extends AbstractComputerBlockEntity {
|
||||
|
||||
@Override
|
||||
protected ServerComputer createComputer(int id) {
|
||||
var family = getFamily();
|
||||
return new ServerComputer(
|
||||
(ServerLevel) getLevel(), getBlockPos(), id, label,
|
||||
family, Config.computerTermWidth,
|
||||
Config.computerTermHeight
|
||||
getFamily(), Config.computerTermWidth, Config.computerTermHeight
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -4,33 +4,8 @@
|
||||
|
||||
package dan200.computercraft.shared.computer.core;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import net.minecraft.util.StringRepresentable;
|
||||
|
||||
public enum ComputerFamily implements StringRepresentable {
|
||||
NORMAL("normal"),
|
||||
ADVANCED("advanced"),
|
||||
COMMAND("command");
|
||||
|
||||
private final String name;
|
||||
|
||||
ComputerFamily(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public static ComputerFamily getFamily(JsonObject json, String name) {
|
||||
var familyName = GsonHelper.getAsString(json, name);
|
||||
for (var family : values()) {
|
||||
if (family.getSerializedName().equalsIgnoreCase(familyName)) return family;
|
||||
}
|
||||
|
||||
throw new JsonSyntaxException("Unknown computer family '" + familyName + "' for field " + name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSerializedName() {
|
||||
return name;
|
||||
}
|
||||
public enum ComputerFamily {
|
||||
NORMAL,
|
||||
ADVANCED,
|
||||
COMMAND,
|
||||
}
|
||||
|
@@ -21,7 +21,7 @@ import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static dan200.computercraft.core.filesystem.MountHelpers.NO_SUCH_FILE;
|
||||
import static dan200.computercraft.api.filesystem.MountConstants.NO_SUCH_FILE;
|
||||
|
||||
/**
|
||||
* A mount backed by Minecraft's {@link ResourceManager}.
|
||||
|
@@ -8,7 +8,6 @@ import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.filesystem.Mount;
|
||||
import dan200.computercraft.api.media.IMedia;
|
||||
import dan200.computercraft.shared.computer.blocks.AbstractComputerBlock;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.config.Config;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.network.chat.Component;
|
||||
@@ -22,11 +21,8 @@ import javax.annotation.Nullable;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class AbstractComputerItem extends BlockItem implements IComputerItem, IMedia {
|
||||
private final ComputerFamily family;
|
||||
|
||||
public AbstractComputerItem(AbstractComputerBlock<?> block, Properties settings) {
|
||||
super(block, settings);
|
||||
family = block.getFamily();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -45,13 +41,6 @@ public abstract class AbstractComputerItem extends BlockItem implements ICompute
|
||||
return IComputerItem.super.getLabel(stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ComputerFamily getFamily() {
|
||||
return family;
|
||||
}
|
||||
|
||||
// IMedia implementation
|
||||
|
||||
@Override
|
||||
public boolean setLabel(ItemStack stack, @Nullable String label) {
|
||||
if (label != null) {
|
||||
|
@@ -5,8 +5,8 @@
|
||||
package dan200.computercraft.shared.computer.items;
|
||||
|
||||
import dan200.computercraft.shared.computer.blocks.ComputerBlock;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
@@ -24,9 +24,9 @@ public class ComputerItem extends AbstractComputerItem {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withFamily(ItemStack stack, ComputerFamily family) {
|
||||
var result = ComputerItemFactory.create(getComputerID(stack), null, family);
|
||||
if (stack.hasCustomHoverName()) result.setHoverName(stack.getHoverName());
|
||||
return result;
|
||||
public ItemStack changeItem(ItemStack stack, Item newItem) {
|
||||
return newItem instanceof ComputerItem computer
|
||||
? computer.create(getComputerID(stack), getLabel(stack))
|
||||
: ItemStack.EMPTY;
|
||||
}
|
||||
}
|
||||
|
@@ -1,29 +0,0 @@
|
||||
// Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
|
||||
//
|
||||
// SPDX-License-Identifier: LicenseRef-CCPL
|
||||
|
||||
package dan200.computercraft.shared.computer.items;
|
||||
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.computer.blocks.ComputerBlockEntity;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public final class ComputerItemFactory {
|
||||
private ComputerItemFactory() {
|
||||
}
|
||||
|
||||
public static ItemStack create(ComputerBlockEntity tile) {
|
||||
return create(tile.getComputerID(), tile.getLabel(), tile.getFamily());
|
||||
}
|
||||
|
||||
public static ItemStack create(int id, @Nullable String label, ComputerFamily family) {
|
||||
return switch (family) {
|
||||
case NORMAL -> ModRegistry.Items.COMPUTER_NORMAL.get().create(id, label);
|
||||
case ADVANCED -> ModRegistry.Items.COMPUTER_ADVANCED.get().create(id, label);
|
||||
case COMMAND -> ModRegistry.Items.COMPUTER_COMMAND.get().create(id, label);
|
||||
};
|
||||
}
|
||||
}
|
@@ -4,7 +4,7 @@
|
||||
|
||||
package dan200.computercraft.shared.computer.items;
|
||||
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
@@ -21,7 +21,15 @@ public interface IComputerItem {
|
||||
return stack.hasCustomHoverName() ? stack.getHoverName().getString() : null;
|
||||
}
|
||||
|
||||
ComputerFamily getFamily();
|
||||
|
||||
ItemStack withFamily(ItemStack stack, ComputerFamily family);
|
||||
/**
|
||||
* Create a new stack, changing the underlying item.
|
||||
* <p>
|
||||
* This should copy the computer's data to a different item of the same type (for instance, converting a normal
|
||||
* computer to an advanced one).
|
||||
*
|
||||
* @param stack The current computer stack.
|
||||
* @param newItem The new item.
|
||||
* @return The new stack, possibly {@linkplain ItemStack#EMPTY empty} if {@code newItem} is of the same type.
|
||||
*/
|
||||
ItemStack changeItem(ItemStack stack, Item newItem);
|
||||
}
|
||||
|
@@ -46,12 +46,14 @@ public final class GlobalMetrics {
|
||||
* Add a new global metrics observer. This will receive metrics data for all computers.
|
||||
*
|
||||
* @param tracker The observer to add.
|
||||
* @return Whether the observer was added. {@code false} if the observer was already registered.
|
||||
*/
|
||||
public void addObserver(ComputerMetricsObserver tracker) {
|
||||
public boolean addObserver(ComputerMetricsObserver tracker) {
|
||||
synchronized (lock) {
|
||||
if (trackers.contains(tracker)) return;
|
||||
if (trackers.contains(tracker)) return false;
|
||||
trackers.add(tracker);
|
||||
enabled = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,11 +61,13 @@ public final class GlobalMetrics {
|
||||
* Remove a previously-registered global metrics observer.
|
||||
*
|
||||
* @param tracker The observer to add.
|
||||
* @return Whether the observer was removed. {@code false} if the observer was not registered.
|
||||
*/
|
||||
public void removeObserver(ComputerMetricsObserver tracker) {
|
||||
public boolean removeObserver(ComputerMetricsObserver tracker) {
|
||||
synchronized (lock) {
|
||||
trackers.remove(tracker);
|
||||
var changed = trackers.remove(tracker);
|
||||
enabled = !trackers.isEmpty();
|
||||
return changed;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -10,6 +10,7 @@ import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import dan200.computercraft.shared.computer.metrics.ComputerMetricsObserver;
|
||||
import dan200.computercraft.shared.computer.metrics.GlobalMetrics;
|
||||
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -21,29 +22,31 @@ import java.util.Map;
|
||||
*/
|
||||
public class BasicComputerMetricsObserver implements ComputerMetricsObserver {
|
||||
private final GlobalMetrics owner;
|
||||
private boolean tracking = false;
|
||||
|
||||
@GuardedBy("this")
|
||||
private final List<ComputerMetrics> timings = new ArrayList<>();
|
||||
|
||||
@GuardedBy("this")
|
||||
private final Map<ServerComputer, ComputerMetrics> timingLookup = new MapMaker().weakKeys().makeMap();
|
||||
|
||||
public BasicComputerMetricsObserver(GlobalMetrics owner) {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public synchronized void start() {
|
||||
if (!tracking) owner.addObserver(this);
|
||||
tracking = true;
|
||||
public void start() {
|
||||
if (!owner.addObserver(this)) return;
|
||||
|
||||
timings.clear();
|
||||
timingLookup.clear();
|
||||
synchronized (this) {
|
||||
timings.clear();
|
||||
timingLookup.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized boolean stop() {
|
||||
if (!tracking) return false;
|
||||
|
||||
owner.removeObserver(this);
|
||||
tracking = false;
|
||||
timingLookup.clear();
|
||||
public boolean stop() {
|
||||
if (!owner.removeObserver(this)) return false;
|
||||
synchronized (this) {
|
||||
timingLookup.clear();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -57,6 +60,7 @@ public class BasicComputerMetricsObserver implements ComputerMetricsObserver {
|
||||
return new ArrayList<>(timings);
|
||||
}
|
||||
|
||||
@GuardedBy("this")
|
||||
private ComputerMetrics getMetrics(ServerComputer computer) {
|
||||
var existing = timingLookup.get(computer);
|
||||
if (existing != null) return existing;
|
||||
|
@@ -4,57 +4,44 @@
|
||||
|
||||
package dan200.computercraft.shared.computer.recipe;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.mojang.serialization.DataResult;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.items.IComputerItem;
|
||||
import dan200.computercraft.shared.recipe.ShapedRecipeSpec;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.crafting.RecipeSerializer;
|
||||
|
||||
/**
|
||||
* A recipe which "upgrades" a {@linkplain IComputerItem computer}, converting it from one {@linkplain ComputerFamily
|
||||
* family} to another.
|
||||
* A recipe which "upgrades" a {@linkplain IComputerItem computer}, converting to it a new item (for instance a normal
|
||||
* turtle to an advanced one).
|
||||
*
|
||||
* @see IComputerItem#changeItem(ItemStack, Item)
|
||||
*/
|
||||
public final class ComputerUpgradeRecipe extends ComputerConvertRecipe {
|
||||
private final ComputerFamily family;
|
||||
private final Item result;
|
||||
|
||||
private ComputerUpgradeRecipe(ResourceLocation identifier, ShapedRecipeSpec recipe, ComputerFamily family) {
|
||||
private ComputerUpgradeRecipe(ResourceLocation identifier, ShapedRecipeSpec recipe) {
|
||||
super(identifier, recipe);
|
||||
this.family = family;
|
||||
this.result = recipe.result().getItem();
|
||||
}
|
||||
|
||||
public static DataResult<ComputerUpgradeRecipe> of(ResourceLocation id, ShapedRecipeSpec recipe) {
|
||||
if (!(recipe.result().getItem() instanceof IComputerItem)) {
|
||||
return DataResult.error(() -> recipe.result().getItem() + " is not a computer item");
|
||||
}
|
||||
|
||||
return DataResult.success(new ComputerUpgradeRecipe(id, recipe));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ItemStack convert(IComputerItem item, ItemStack stack) {
|
||||
return item.withFamily(stack, family);
|
||||
return item.changeItem(stack, result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecipeSerializer<ComputerUpgradeRecipe> getSerializer() {
|
||||
return ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get();
|
||||
}
|
||||
|
||||
public static class Serializer implements RecipeSerializer<ComputerUpgradeRecipe> {
|
||||
@Override
|
||||
public ComputerUpgradeRecipe fromJson(ResourceLocation identifier, JsonObject json) {
|
||||
var recipe = ShapedRecipeSpec.fromJson(json);
|
||||
var family = ComputerFamily.getFamily(json, "family");
|
||||
return new ComputerUpgradeRecipe(identifier, recipe, family);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComputerUpgradeRecipe fromNetwork(ResourceLocation identifier, FriendlyByteBuf buf) {
|
||||
var recipe = ShapedRecipeSpec.fromNetwork(buf);
|
||||
var family = buf.readEnum(ComputerFamily.class);
|
||||
return new ComputerUpgradeRecipe(identifier, recipe, family);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toNetwork(FriendlyByteBuf buf, ComputerUpgradeRecipe recipe) {
|
||||
recipe.toSpec().toNetwork(buf);
|
||||
buf.writeEnum(recipe.family);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,18 @@
|
||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.network;
|
||||
|
||||
/**
|
||||
* A type of message to send over the network.
|
||||
* <p>
|
||||
* Much like recipe or argument serialisers, each type of {@link NetworkMessage} should have a unique type associated
|
||||
* with it. This holds platform-specific information about how the packet should be sent over the network.
|
||||
*
|
||||
* @param <T> The type of message to send
|
||||
* @see NetworkMessages
|
||||
* @see NetworkMessage#type()
|
||||
*/
|
||||
public interface MessageType<T extends NetworkMessage<?>> {
|
||||
}
|
@@ -17,6 +17,13 @@ import net.minecraft.network.FriendlyByteBuf;
|
||||
* @see ServerNetworkContext
|
||||
*/
|
||||
public interface NetworkMessage<T> {
|
||||
/**
|
||||
* Get the type of this message.
|
||||
*
|
||||
* @return The type of this message.
|
||||
*/
|
||||
MessageType<?> type();
|
||||
|
||||
/**
|
||||
* Write this packet to a buffer.
|
||||
* <p>
|
||||
@@ -24,7 +31,7 @@ public interface NetworkMessage<T> {
|
||||
*
|
||||
* @param buf The buffer to write data to.
|
||||
*/
|
||||
void toBytes(FriendlyByteBuf buf);
|
||||
void write(FriendlyByteBuf buf);
|
||||
|
||||
/**
|
||||
* Handle this {@link NetworkMessage}.
|
||||
|
@@ -4,48 +4,84 @@
|
||||
|
||||
package dan200.computercraft.shared.network;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.shared.network.client.*;
|
||||
import dan200.computercraft.shared.network.server.*;
|
||||
import dan200.computercraft.shared.platform.PlatformHelper;
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Registry for all packets provided by CC: Tweaked.
|
||||
* List of all {@link MessageType}s provided by CC: Tweaked.
|
||||
*
|
||||
* @see PlatformHelper The platform helper is used to send packets.
|
||||
*/
|
||||
public final class NetworkMessages {
|
||||
private static final IntSet seenIds = new IntOpenHashSet();
|
||||
private static final Set<String> seenChannel = new HashSet<>();
|
||||
private static final List<MessageType<? extends NetworkMessage<ServerNetworkContext>>> serverMessages = new ArrayList<>();
|
||||
private static final List<MessageType<? extends NetworkMessage<ClientNetworkContext>>> clientMessages = new ArrayList<>();
|
||||
|
||||
public static final MessageType<ComputerActionServerMessage> COMPUTER_ACTION = registerServerbound(0, "computer_action", ComputerActionServerMessage.class, ComputerActionServerMessage::new);
|
||||
public static final MessageType<QueueEventServerMessage> QUEUE_EVENT = registerServerbound(1, "queue_event", QueueEventServerMessage.class, QueueEventServerMessage::new);
|
||||
public static final MessageType<KeyEventServerMessage> KEY_EVENT = registerServerbound(2, "key_event", KeyEventServerMessage.class, KeyEventServerMessage::new);
|
||||
public static final MessageType<MouseEventServerMessage> MOUSE_EVENT = registerServerbound(3, "mouse_event", MouseEventServerMessage.class, MouseEventServerMessage::new);
|
||||
public static final MessageType<UploadFileMessage> UPLOAD_FILE = registerServerbound(4, "upload_file", UploadFileMessage.class, UploadFileMessage::new);
|
||||
|
||||
public static final MessageType<ChatTableClientMessage> CHAT_TABLE = registerClientbound(10, "chat_table", ChatTableClientMessage.class, ChatTableClientMessage::new);
|
||||
public static final MessageType<PocketComputerDataMessage> POCKET_COMPUTER_DATA = registerClientbound(11, "pocket_computer_data", PocketComputerDataMessage.class, PocketComputerDataMessage::new);
|
||||
public static final MessageType<PocketComputerDeletedClientMessage> POCKET_COMPUTER_DELETED = registerClientbound(12, "pocket_computer_deleted", PocketComputerDeletedClientMessage.class, PocketComputerDeletedClientMessage::new);
|
||||
public static final MessageType<ComputerTerminalClientMessage> COMPUTER_TERMINAL = registerClientbound(13, "computer_terminal", ComputerTerminalClientMessage.class, ComputerTerminalClientMessage::new);
|
||||
public static final MessageType<PlayRecordClientMessage> PLAY_RECORD = registerClientbound(14, "play_record", PlayRecordClientMessage.class, PlayRecordClientMessage::new);
|
||||
public static final MessageType<MonitorClientMessage> MONITOR_CLIENT = registerClientbound(15, "monitor_client", MonitorClientMessage.class, MonitorClientMessage::new);
|
||||
public static final MessageType<SpeakerAudioClientMessage> SPEAKER_AUDIO = registerClientbound(16, "speaker_audio", SpeakerAudioClientMessage.class, SpeakerAudioClientMessage::new);
|
||||
public static final MessageType<SpeakerMoveClientMessage> SPEAKER_MOVE = registerClientbound(17, "speaker_move", SpeakerMoveClientMessage.class, SpeakerMoveClientMessage::new);
|
||||
public static final MessageType<SpeakerPlayClientMessage> SPEAKER_PLAY = registerClientbound(18, "speaker_play", SpeakerPlayClientMessage.class, SpeakerPlayClientMessage::new);
|
||||
public static final MessageType<SpeakerStopClientMessage> SPEAKER_STOP = registerClientbound(19, "speaker_stop", SpeakerStopClientMessage.class, SpeakerStopClientMessage::new);
|
||||
public static final MessageType<UploadResultMessage> UPLOAD_RESULT = registerClientbound(20, "upload_result", UploadResultMessage.class, UploadResultMessage::new);
|
||||
public static final MessageType<UpgradesLoadedMessage> UPGRADES_LOADED = registerClientbound(21, "upgrades_loaded", UpgradesLoadedMessage.class, UpgradesLoadedMessage::new);
|
||||
|
||||
private NetworkMessages() {
|
||||
}
|
||||
|
||||
public interface PacketRegistry {
|
||||
<T extends NetworkMessage<ClientNetworkContext>> void registerClientbound(int id, Class<T> type, Function<FriendlyByteBuf, T> decoder);
|
||||
|
||||
<T extends NetworkMessage<ServerNetworkContext>> void registerServerbound(int id, Class<T> type, Function<FriendlyByteBuf, T> decoder);
|
||||
private static <C, T extends NetworkMessage<C>> MessageType<T> register(
|
||||
List<MessageType<? extends NetworkMessage<C>>> messages,
|
||||
int id, String channel, Class<T> klass, FriendlyByteBuf.Reader<T> reader
|
||||
) {
|
||||
if (!seenIds.add(id)) throw new IllegalArgumentException("Duplicate id " + id);
|
||||
if (!seenChannel.add(channel)) throw new IllegalArgumentException("Duplicate channel " + channel);
|
||||
var type = PlatformHelper.get().createMessageType(id, new ResourceLocation(ComputerCraftAPI.MOD_ID, channel), klass, reader);
|
||||
messages.add(type);
|
||||
return type;
|
||||
}
|
||||
|
||||
public static void register(PacketRegistry registry) {
|
||||
// Server messages
|
||||
registry.registerServerbound(0, ComputerActionServerMessage.class, ComputerActionServerMessage::new);
|
||||
registry.registerServerbound(1, QueueEventServerMessage.class, QueueEventServerMessage::new);
|
||||
registry.registerServerbound(2, KeyEventServerMessage.class, KeyEventServerMessage::new);
|
||||
registry.registerServerbound(3, MouseEventServerMessage.class, MouseEventServerMessage::new);
|
||||
registry.registerServerbound(4, UploadFileMessage.class, UploadFileMessage::new);
|
||||
private static <T extends NetworkMessage<ServerNetworkContext>> MessageType<T> registerServerbound(int id, String channel, Class<T> klass, FriendlyByteBuf.Reader<T> reader) {
|
||||
return register(serverMessages, id, channel, klass, reader);
|
||||
}
|
||||
|
||||
// Client messages
|
||||
registry.registerClientbound(10, ChatTableClientMessage.class, ChatTableClientMessage::new);
|
||||
registry.registerClientbound(11, PocketComputerDataMessage.class, PocketComputerDataMessage::new);
|
||||
registry.registerClientbound(12, PocketComputerDeletedClientMessage.class, PocketComputerDeletedClientMessage::new);
|
||||
registry.registerClientbound(13, ComputerTerminalClientMessage.class, ComputerTerminalClientMessage::new);
|
||||
registry.registerClientbound(14, PlayRecordClientMessage.class, PlayRecordClientMessage::new);
|
||||
registry.registerClientbound(15, MonitorClientMessage.class, MonitorClientMessage::new);
|
||||
registry.registerClientbound(16, SpeakerAudioClientMessage.class, SpeakerAudioClientMessage::new);
|
||||
registry.registerClientbound(17, SpeakerMoveClientMessage.class, SpeakerMoveClientMessage::new);
|
||||
registry.registerClientbound(18, SpeakerPlayClientMessage.class, SpeakerPlayClientMessage::new);
|
||||
registry.registerClientbound(19, SpeakerStopClientMessage.class, SpeakerStopClientMessage::new);
|
||||
registry.registerClientbound(20, UploadResultMessage.class, UploadResultMessage::new);
|
||||
registry.registerClientbound(21, UpgradesLoadedMessage.class, UpgradesLoadedMessage::new);
|
||||
private static <T extends NetworkMessage<ClientNetworkContext>> MessageType<T> registerClientbound(int id, String channel, Class<T> klass, FriendlyByteBuf.Reader<T> reader) {
|
||||
return register(clientMessages, id, channel, klass, reader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all serverbound message types.
|
||||
*
|
||||
* @return An unmodifiable sequence of all serverbound message types.
|
||||
*/
|
||||
public static Collection<MessageType<? extends NetworkMessage<ServerNetworkContext>>> getServerbound() {
|
||||
return Collections.unmodifiableCollection(serverMessages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all clientbound message types.
|
||||
*
|
||||
* @return An unmodifiable sequence of all clientbound message types.
|
||||
*/
|
||||
public static Collection<MessageType<? extends NetworkMessage<ClientNetworkContext>>> getClientbound() {
|
||||
return Collections.unmodifiableCollection(clientMessages);
|
||||
}
|
||||
}
|
||||
|
@@ -5,7 +5,9 @@
|
||||
package dan200.computercraft.shared.network.client;
|
||||
|
||||
import dan200.computercraft.shared.command.text.TableBuilder;
|
||||
import dan200.computercraft.shared.network.MessageType;
|
||||
import dan200.computercraft.shared.network.NetworkMessage;
|
||||
import dan200.computercraft.shared.network.NetworkMessages;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
@@ -43,7 +45,7 @@ public class ChatTableClientMessage implements NetworkMessage<ClientNetworkConte
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBytes(FriendlyByteBuf buf) {
|
||||
public void write(FriendlyByteBuf buf) {
|
||||
buf.writeUtf(table.getId(), MAX_LEN);
|
||||
buf.writeVarInt(table.getColumns());
|
||||
buf.writeBoolean(table.getHeaders() != null);
|
||||
@@ -63,4 +65,9 @@ public class ChatTableClientMessage implements NetworkMessage<ClientNetworkConte
|
||||
public void handle(ClientNetworkContext context) {
|
||||
context.handleChatTable(table);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageType<ChatTableClientMessage> type() {
|
||||
return NetworkMessages.CHAT_TABLE;
|
||||
}
|
||||
}
|
||||
|
@@ -5,7 +5,9 @@
|
||||
package dan200.computercraft.shared.network.client;
|
||||
|
||||
import dan200.computercraft.shared.computer.terminal.TerminalState;
|
||||
import dan200.computercraft.shared.network.MessageType;
|
||||
import dan200.computercraft.shared.network.NetworkMessage;
|
||||
import dan200.computercraft.shared.network.NetworkMessages;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
|
||||
@@ -25,7 +27,7 @@ public class ComputerTerminalClientMessage implements NetworkMessage<ClientNetwo
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBytes(FriendlyByteBuf buf) {
|
||||
public void write(FriendlyByteBuf buf) {
|
||||
buf.writeVarInt(containerId);
|
||||
terminal.write(buf);
|
||||
}
|
||||
@@ -34,4 +36,9 @@ public class ComputerTerminalClientMessage implements NetworkMessage<ClientNetwo
|
||||
public void handle(ClientNetworkContext context) {
|
||||
context.handleComputerTerminal(containerId, terminal);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageType<ComputerTerminalClientMessage> type() {
|
||||
return NetworkMessages.COMPUTER_TERMINAL;
|
||||
}
|
||||
}
|
||||
|
@@ -5,7 +5,9 @@
|
||||
package dan200.computercraft.shared.network.client;
|
||||
|
||||
import dan200.computercraft.shared.computer.terminal.TerminalState;
|
||||
import dan200.computercraft.shared.network.MessageType;
|
||||
import dan200.computercraft.shared.network.NetworkMessage;
|
||||
import dan200.computercraft.shared.network.NetworkMessages;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
|
||||
@@ -25,7 +27,7 @@ public class MonitorClientMessage implements NetworkMessage<ClientNetworkContext
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBytes(FriendlyByteBuf buf) {
|
||||
public void write(FriendlyByteBuf buf) {
|
||||
buf.writeBlockPos(pos);
|
||||
state.write(buf);
|
||||
}
|
||||
@@ -34,4 +36,9 @@ public class MonitorClientMessage implements NetworkMessage<ClientNetworkContext
|
||||
public void handle(ClientNetworkContext context) {
|
||||
context.handleMonitorData(pos, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageType<MonitorClientMessage> type() {
|
||||
return NetworkMessages.MONITOR_CLIENT;
|
||||
}
|
||||
}
|
||||
|
@@ -4,7 +4,9 @@
|
||||
|
||||
package dan200.computercraft.shared.network.client;
|
||||
|
||||
import dan200.computercraft.shared.network.MessageType;
|
||||
import dan200.computercraft.shared.network.NetworkMessage;
|
||||
import dan200.computercraft.shared.network.NetworkMessages;
|
||||
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveBlockEntity;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
@@ -43,7 +45,7 @@ public class PlayRecordClientMessage implements NetworkMessage<ClientNetworkCont
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBytes(FriendlyByteBuf buf) {
|
||||
public void write(FriendlyByteBuf buf) {
|
||||
buf.writeBlockPos(pos);
|
||||
buf.writeNullable(soundEvent, (b, e) -> e.writeToNetwork(b));
|
||||
buf.writeNullable(name, FriendlyByteBuf::writeUtf);
|
||||
@@ -53,4 +55,9 @@ public class PlayRecordClientMessage implements NetworkMessage<ClientNetworkCont
|
||||
public void handle(ClientNetworkContext context) {
|
||||
context.handlePlayRecord(pos, soundEvent, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageType<PlayRecordClientMessage> type() {
|
||||
return NetworkMessages.PLAY_RECORD;
|
||||
}
|
||||
}
|
||||
|
@@ -7,7 +7,9 @@ package dan200.computercraft.shared.network.client;
|
||||
import dan200.computercraft.shared.computer.core.ComputerState;
|
||||
import dan200.computercraft.shared.computer.terminal.NetworkedTerminal;
|
||||
import dan200.computercraft.shared.computer.terminal.TerminalState;
|
||||
import dan200.computercraft.shared.network.MessageType;
|
||||
import dan200.computercraft.shared.network.NetworkMessage;
|
||||
import dan200.computercraft.shared.network.NetworkMessages;
|
||||
import dan200.computercraft.shared.pocket.core.PocketServerComputer;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
|
||||
@@ -35,7 +37,7 @@ public class PocketComputerDataMessage implements NetworkMessage<ClientNetworkCo
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBytes(FriendlyByteBuf buf) {
|
||||
public void write(FriendlyByteBuf buf) {
|
||||
buf.writeVarInt(instanceId);
|
||||
buf.writeEnum(state);
|
||||
buf.writeVarInt(lightState);
|
||||
@@ -46,4 +48,9 @@ public class PocketComputerDataMessage implements NetworkMessage<ClientNetworkCo
|
||||
public void handle(ClientNetworkContext context) {
|
||||
context.handlePocketComputerData(instanceId, state, lightState, terminal);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageType<PocketComputerDataMessage> type() {
|
||||
return NetworkMessages.POCKET_COMPUTER_DATA;
|
||||
}
|
||||
}
|
||||
|
@@ -4,7 +4,9 @@
|
||||
|
||||
package dan200.computercraft.shared.network.client;
|
||||
|
||||
import dan200.computercraft.shared.network.MessageType;
|
||||
import dan200.computercraft.shared.network.NetworkMessage;
|
||||
import dan200.computercraft.shared.network.NetworkMessages;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
|
||||
|
||||
@@ -20,7 +22,7 @@ public class PocketComputerDeletedClientMessage implements NetworkMessage<Client
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBytes(FriendlyByteBuf buf) {
|
||||
public void write(FriendlyByteBuf buf) {
|
||||
buf.writeVarInt(instanceId);
|
||||
}
|
||||
|
||||
@@ -28,4 +30,9 @@ public class PocketComputerDeletedClientMessage implements NetworkMessage<Client
|
||||
public void handle(ClientNetworkContext context) {
|
||||
context.handlePocketComputerDeleted(instanceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageType<PocketComputerDeletedClientMessage> type() {
|
||||
return NetworkMessages.POCKET_COMPUTER_DELETED;
|
||||
}
|
||||
}
|
||||
|
@@ -4,7 +4,9 @@
|
||||
|
||||
package dan200.computercraft.shared.network.client;
|
||||
|
||||
import dan200.computercraft.shared.network.MessageType;
|
||||
import dan200.computercraft.shared.network.NetworkMessage;
|
||||
import dan200.computercraft.shared.network.NetworkMessages;
|
||||
import dan200.computercraft.shared.peripheral.speaker.SpeakerBlockEntity;
|
||||
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
@@ -47,7 +49,7 @@ public class SpeakerAudioClientMessage implements NetworkMessage<ClientNetworkCo
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBytes(FriendlyByteBuf buf) {
|
||||
public void write(FriendlyByteBuf buf) {
|
||||
buf.writeUUID(source);
|
||||
pos.write(buf);
|
||||
buf.writeFloat(volume);
|
||||
@@ -58,4 +60,9 @@ public class SpeakerAudioClientMessage implements NetworkMessage<ClientNetworkCo
|
||||
public void handle(ClientNetworkContext context) {
|
||||
context.handleSpeakerAudio(source, pos, volume);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageType<SpeakerAudioClientMessage> type() {
|
||||
return NetworkMessages.SPEAKER_AUDIO;
|
||||
}
|
||||
}
|
||||
|
@@ -4,7 +4,9 @@
|
||||
|
||||
package dan200.computercraft.shared.network.client;
|
||||
|
||||
import dan200.computercraft.shared.network.MessageType;
|
||||
import dan200.computercraft.shared.network.NetworkMessage;
|
||||
import dan200.computercraft.shared.network.NetworkMessages;
|
||||
import dan200.computercraft.shared.peripheral.speaker.SpeakerBlockEntity;
|
||||
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
@@ -33,7 +35,7 @@ public class SpeakerMoveClientMessage implements NetworkMessage<ClientNetworkCon
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBytes(FriendlyByteBuf buf) {
|
||||
public void write(FriendlyByteBuf buf) {
|
||||
buf.writeUUID(source);
|
||||
pos.write(buf);
|
||||
}
|
||||
@@ -42,4 +44,9 @@ public class SpeakerMoveClientMessage implements NetworkMessage<ClientNetworkCon
|
||||
public void handle(ClientNetworkContext context) {
|
||||
context.handleSpeakerMove(source, pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageType<SpeakerMoveClientMessage> type() {
|
||||
return NetworkMessages.SPEAKER_MOVE;
|
||||
}
|
||||
}
|
||||
|
@@ -4,7 +4,9 @@
|
||||
|
||||
package dan200.computercraft.shared.network.client;
|
||||
|
||||
import dan200.computercraft.shared.network.MessageType;
|
||||
import dan200.computercraft.shared.network.NetworkMessage;
|
||||
import dan200.computercraft.shared.network.NetworkMessages;
|
||||
import dan200.computercraft.shared.peripheral.speaker.SpeakerBlockEntity;
|
||||
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
@@ -43,7 +45,7 @@ public class SpeakerPlayClientMessage implements NetworkMessage<ClientNetworkCon
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBytes(FriendlyByteBuf buf) {
|
||||
public void write(FriendlyByteBuf buf) {
|
||||
buf.writeUUID(source);
|
||||
pos.write(buf);
|
||||
buf.writeResourceLocation(sound);
|
||||
@@ -55,4 +57,9 @@ public class SpeakerPlayClientMessage implements NetworkMessage<ClientNetworkCon
|
||||
public void handle(ClientNetworkContext context) {
|
||||
context.handleSpeakerPlay(source, pos, sound, volume, pitch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageType<SpeakerPlayClientMessage> type() {
|
||||
return NetworkMessages.SPEAKER_PLAY;
|
||||
}
|
||||
}
|
||||
|
@@ -4,7 +4,9 @@
|
||||
|
||||
package dan200.computercraft.shared.network.client;
|
||||
|
||||
import dan200.computercraft.shared.network.MessageType;
|
||||
import dan200.computercraft.shared.network.NetworkMessage;
|
||||
import dan200.computercraft.shared.network.NetworkMessages;
|
||||
import dan200.computercraft.shared.peripheral.speaker.SpeakerBlockEntity;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
|
||||
@@ -29,7 +31,7 @@ public class SpeakerStopClientMessage implements NetworkMessage<ClientNetworkCon
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBytes(FriendlyByteBuf buf) {
|
||||
public void write(FriendlyByteBuf buf) {
|
||||
buf.writeUUID(source);
|
||||
}
|
||||
|
||||
@@ -37,4 +39,9 @@ public class SpeakerStopClientMessage implements NetworkMessage<ClientNetworkCon
|
||||
public void handle(ClientNetworkContext context) {
|
||||
context.handleSpeakerStop(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageType<SpeakerStopClientMessage> type() {
|
||||
return NetworkMessages.SPEAKER_STOP;
|
||||
}
|
||||
}
|
||||
|
@@ -13,7 +13,9 @@ import dan200.computercraft.api.upgrades.UpgradeSerialiser;
|
||||
import dan200.computercraft.impl.PocketUpgrades;
|
||||
import dan200.computercraft.impl.TurtleUpgrades;
|
||||
import dan200.computercraft.impl.UpgradeManager;
|
||||
import dan200.computercraft.shared.network.MessageType;
|
||||
import dan200.computercraft.shared.network.NetworkMessage;
|
||||
import dan200.computercraft.shared.network.NetworkMessages;
|
||||
import dan200.computercraft.shared.platform.PlatformHelper;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
@@ -27,7 +29,7 @@ import java.util.Objects;
|
||||
/**
|
||||
* Syncs turtle and pocket upgrades to the client.
|
||||
*/
|
||||
public class UpgradesLoadedMessage implements NetworkMessage<ClientNetworkContext> {
|
||||
public final class UpgradesLoadedMessage implements NetworkMessage<ClientNetworkContext> {
|
||||
private final Map<String, UpgradeManager.UpgradeWrapper<TurtleUpgradeSerialiser<?>, ITurtleUpgrade>> turtleUpgrades;
|
||||
private final Map<String, UpgradeManager.UpgradeWrapper<PocketUpgradeSerialiser<?>, IPocketUpgrade>> pocketUpgrades;
|
||||
|
||||
@@ -65,7 +67,7 @@ public class UpgradesLoadedMessage implements NetworkMessage<ClientNetworkContex
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBytes(FriendlyByteBuf buf) {
|
||||
public void write(FriendlyByteBuf buf) {
|
||||
toBytes(buf, TurtleUpgradeSerialiser.registryId(), turtleUpgrades);
|
||||
toBytes(buf, PocketUpgradeSerialiser.registryId(), pocketUpgrades);
|
||||
}
|
||||
@@ -95,4 +97,9 @@ public class UpgradesLoadedMessage implements NetworkMessage<ClientNetworkContex
|
||||
TurtleUpgrades.instance().loadFromNetwork(turtleUpgrades);
|
||||
PocketUpgrades.instance().loadFromNetwork(pocketUpgrades);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageType<UpgradesLoadedMessage> type() {
|
||||
return NetworkMessages.UPGRADES_LOADED;
|
||||
}
|
||||
}
|
||||
|
@@ -6,7 +6,9 @@ package dan200.computercraft.shared.network.client;
|
||||
|
||||
import dan200.computercraft.core.util.Nullability;
|
||||
import dan200.computercraft.shared.computer.upload.UploadResult;
|
||||
import dan200.computercraft.shared.network.MessageType;
|
||||
import dan200.computercraft.shared.network.NetworkMessage;
|
||||
import dan200.computercraft.shared.network.NetworkMessages;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
@@ -43,7 +45,7 @@ public class UploadResultMessage implements NetworkMessage<ClientNetworkContext>
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBytes(FriendlyByteBuf buf) {
|
||||
public void write(FriendlyByteBuf buf) {
|
||||
buf.writeVarInt(containerId);
|
||||
buf.writeEnum(result);
|
||||
if (result == UploadResult.ERROR) buf.writeComponent(Nullability.assertNonNull(errorMessage));
|
||||
@@ -53,4 +55,9 @@ public class UploadResultMessage implements NetworkMessage<ClientNetworkContext>
|
||||
public void handle(ClientNetworkContext context) {
|
||||
context.handleUploadResult(containerId, result, errorMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageType<UploadResultMessage> type() {
|
||||
return NetworkMessages.UPLOAD_RESULT;
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,8 @@
|
||||
package dan200.computercraft.shared.network.server;
|
||||
|
||||
import dan200.computercraft.shared.computer.menu.ComputerMenu;
|
||||
import dan200.computercraft.shared.network.MessageType;
|
||||
import dan200.computercraft.shared.network.NetworkMessages;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
|
||||
@@ -23,8 +25,8 @@ public class ComputerActionServerMessage extends ComputerServerMessage {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBytes(FriendlyByteBuf buf) {
|
||||
super.toBytes(buf);
|
||||
public void write(FriendlyByteBuf buf) {
|
||||
super.write(buf);
|
||||
buf.writeEnum(action);
|
||||
}
|
||||
|
||||
@@ -37,6 +39,11 @@ public class ComputerActionServerMessage extends ComputerServerMessage {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageType<ComputerActionServerMessage> type() {
|
||||
return NetworkMessages.COMPUTER_ACTION;
|
||||
}
|
||||
|
||||
public enum Action {
|
||||
TURN_ON,
|
||||
SHUTDOWN,
|
||||
|
@@ -28,7 +28,7 @@ public abstract class ComputerServerMessage implements NetworkMessage<ServerNetw
|
||||
|
||||
@Override
|
||||
@OverridingMethodsMustInvokeSuper
|
||||
public void toBytes(FriendlyByteBuf buf) {
|
||||
public void write(FriendlyByteBuf buf) {
|
||||
buf.writeVarInt(containerId);
|
||||
}
|
||||
|
||||
|
@@ -5,6 +5,8 @@
|
||||
package dan200.computercraft.shared.network.server;
|
||||
|
||||
import dan200.computercraft.shared.computer.menu.ComputerMenu;
|
||||
import dan200.computercraft.shared.network.MessageType;
|
||||
import dan200.computercraft.shared.network.NetworkMessages;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
|
||||
@@ -30,8 +32,8 @@ public class KeyEventServerMessage extends ComputerServerMessage {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBytes(FriendlyByteBuf buf) {
|
||||
super.toBytes(buf);
|
||||
public void write(FriendlyByteBuf buf) {
|
||||
super.write(buf);
|
||||
buf.writeByte(type);
|
||||
buf.writeVarInt(key);
|
||||
}
|
||||
@@ -45,4 +47,9 @@ public class KeyEventServerMessage extends ComputerServerMessage {
|
||||
input.keyDown(key, type == TYPE_REPEAT);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageType<KeyEventServerMessage> type() {
|
||||
return NetworkMessages.KEY_EVENT;
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,8 @@
|
||||
package dan200.computercraft.shared.network.server;
|
||||
|
||||
import dan200.computercraft.shared.computer.menu.ComputerMenu;
|
||||
import dan200.computercraft.shared.network.MessageType;
|
||||
import dan200.computercraft.shared.network.NetworkMessages;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
|
||||
@@ -37,8 +39,8 @@ public class MouseEventServerMessage extends ComputerServerMessage {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBytes(FriendlyByteBuf buf) {
|
||||
super.toBytes(buf);
|
||||
public void write(FriendlyByteBuf buf) {
|
||||
super.write(buf);
|
||||
buf.writeByte(type);
|
||||
buf.writeVarInt(arg);
|
||||
buf.writeVarInt(x);
|
||||
@@ -55,4 +57,9 @@ public class MouseEventServerMessage extends ComputerServerMessage {
|
||||
case TYPE_SCROLL -> input.mouseScroll(arg, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageType<MouseEventServerMessage> type() {
|
||||
return NetworkMessages.MOUSE_EVENT;
|
||||
}
|
||||
}
|
||||
|
@@ -7,6 +7,8 @@ package dan200.computercraft.shared.network.server;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import dan200.computercraft.shared.computer.menu.ComputerMenu;
|
||||
import dan200.computercraft.shared.computer.menu.ServerInputHandler;
|
||||
import dan200.computercraft.shared.network.MessageType;
|
||||
import dan200.computercraft.shared.network.NetworkMessages;
|
||||
import dan200.computercraft.shared.util.NBTUtil;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
@@ -37,8 +39,8 @@ public class QueueEventServerMessage extends ComputerServerMessage {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBytes(FriendlyByteBuf buf) {
|
||||
super.toBytes(buf);
|
||||
public void write(FriendlyByteBuf buf) {
|
||||
super.write(buf);
|
||||
buf.writeUtf(event);
|
||||
buf.writeNbt(args == null ? null : NBTUtil.encodeObjects(args));
|
||||
}
|
||||
@@ -47,4 +49,9 @@ public class QueueEventServerMessage extends ComputerServerMessage {
|
||||
protected void handle(ServerNetworkContext context, ComputerMenu container) {
|
||||
container.getInput().queueEvent(event, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageType<QueueEventServerMessage> type() {
|
||||
return NetworkMessages.QUEUE_EVENT;
|
||||
}
|
||||
}
|
||||
|
@@ -9,6 +9,8 @@ import dan200.computercraft.shared.computer.menu.ComputerMenu;
|
||||
import dan200.computercraft.shared.computer.upload.FileSlice;
|
||||
import dan200.computercraft.shared.computer.upload.FileUpload;
|
||||
import dan200.computercraft.shared.config.Config;
|
||||
import dan200.computercraft.shared.network.MessageType;
|
||||
import dan200.computercraft.shared.network.NetworkMessages;
|
||||
import io.netty.handler.codec.DecoderException;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
@@ -91,8 +93,8 @@ public class UploadFileMessage extends ComputerServerMessage {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBytes(FriendlyByteBuf buf) {
|
||||
super.toBytes(buf);
|
||||
public void write(FriendlyByteBuf buf) {
|
||||
super.write(buf);
|
||||
buf.writeUUID(uuid);
|
||||
buf.writeByte(flag);
|
||||
|
||||
@@ -166,4 +168,9 @@ public class UploadFileMessage extends ComputerServerMessage {
|
||||
input.continueUpload(uuid, slices);
|
||||
if ((flag & FLAG_LAST) != 0) input.finishUpload(player, uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageType<UploadFileMessage> type() {
|
||||
return NetworkMessages.UPLOAD_FILE;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,53 @@
|
||||
// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.peripheral.generic.methods;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.api.peripheral.GenericPeripheral;
|
||||
import dan200.computercraft.api.peripheral.PeripheralType;
|
||||
|
||||
/**
|
||||
* Methods for interacting with blocks which store energy.
|
||||
* <p>
|
||||
* This works with energy storage blocks, as well as generators and machines which consume energy.
|
||||
* <p>
|
||||
* > [!NOTE]
|
||||
* > Due to limitations with Forge's energy API, it is not possible to measure throughput (i.e. FE used/generated per
|
||||
* > tick).
|
||||
*
|
||||
* @param <T> The type for energy storage.
|
||||
* @cc.module energy_storage
|
||||
* @cc.since 1.94.0
|
||||
*/
|
||||
public abstract class AbstractEnergyMethods<T> implements GenericPeripheral {
|
||||
@Override
|
||||
public final PeripheralType getType() {
|
||||
return PeripheralType.ofAdditional("energy_storage");
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String id() {
|
||||
return ComputerCraftAPI.MOD_ID + ":energy";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the energy of this block.
|
||||
*
|
||||
* @param energy The current energy storage.
|
||||
* @return The energy stored in this block, in FE.
|
||||
*/
|
||||
@LuaFunction(mainThread = true)
|
||||
public abstract int getEnergy(T energy);
|
||||
|
||||
/**
|
||||
* Get the maximum amount of energy this block can store.
|
||||
*
|
||||
* @param energy The current energy storage.
|
||||
* @return The energy capacity of this block.
|
||||
*/
|
||||
@LuaFunction(mainThread = true)
|
||||
public abstract int getEnergyCapacity(T energy);
|
||||
}
|
@@ -0,0 +1,92 @@
|
||||
// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.peripheral.generic.methods;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.api.peripheral.GenericPeripheral;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.api.peripheral.PeripheralType;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Methods for interacting with tanks and other fluid storage blocks.
|
||||
*
|
||||
* @param <T> The type for fluid inventories.
|
||||
* @cc.module fluid_storage
|
||||
* @cc.since 1.94.0
|
||||
*/
|
||||
public abstract class AbstractFluidMethods<T> implements GenericPeripheral {
|
||||
@Override
|
||||
public final PeripheralType getType() {
|
||||
return PeripheralType.ofAdditional("fluid_storage");
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String id() {
|
||||
return ComputerCraftAPI.MOD_ID + ":fluid";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all "tanks" in this fluid storage.
|
||||
* <p>
|
||||
* Each tank either contains some amount of fluid or is empty. Tanks with fluids inside will return some basic
|
||||
* information about the fluid, including its name and amount.
|
||||
* <p>
|
||||
* The returned table is sparse, and so empty tanks will be `nil` - it is recommended to loop over using [`pairs`]
|
||||
* rather than [`ipairs`].
|
||||
*
|
||||
* @param fluids The current fluid handler.
|
||||
* @return All tanks.
|
||||
* @cc.treturn { (table|nil)... } All tanks in this fluid storage.
|
||||
*/
|
||||
@LuaFunction(mainThread = true)
|
||||
public abstract Map<Integer, Map<String, ?>> tanks(T fluids);
|
||||
|
||||
/**
|
||||
* Move a fluid from one fluid container to another connected one.
|
||||
* <p>
|
||||
* This allows you to pull fluid in the current fluid container to another container <em>on the same wired
|
||||
* network</em>. Both containers must attached to wired modems which are connected via a cable.
|
||||
*
|
||||
* @param from Container to move fluid from.
|
||||
* @param computer The current computer.
|
||||
* @param toName The name of the peripheral/container to push to. This is the string given to [`peripheral.wrap`],
|
||||
* and displayed by the wired modem.
|
||||
* @param limit The maximum amount of fluid to move.
|
||||
* @param fluidName The fluid to move. If not given, an arbitrary fluid will be chosen.
|
||||
* @return The amount of moved fluid.
|
||||
* @throws LuaException If the peripheral to transfer to doesn't exist or isn't an fluid container.
|
||||
* @cc.see peripheral.getName Allows you to get the name of a [wrapped][`peripheral.wrap`] peripheral.
|
||||
*/
|
||||
@LuaFunction(mainThread = true)
|
||||
public abstract int pushFluid(
|
||||
T from, IComputerAccess computer, String toName, Optional<Integer> limit, Optional<String> fluidName
|
||||
) throws LuaException;
|
||||
|
||||
/**
|
||||
* Move a fluid from a connected fluid container into this oneone.
|
||||
* <p>
|
||||
* This allows you to pull fluid in the current fluid container from another container <em>on the same wired
|
||||
* network</em>. Both containers must attached to wired modems which are connected via a cable.
|
||||
*
|
||||
* @param to Container to move fluid to.
|
||||
* @param computer The current computer.
|
||||
* @param fromName The name of the peripheral/container to push to. This is the string given to [`peripheral.wrap`],
|
||||
* and displayed by the wired modem.
|
||||
* @param limit The maximum amount of fluid to move.
|
||||
* @param fluidName The fluid to move. If not given, an arbitrary fluid will be chosen.
|
||||
* @return The amount of moved fluid.
|
||||
* @throws LuaException If the peripheral to transfer to doesn't exist or isn't an fluid container.
|
||||
* @cc.see peripheral.getName Allows you to get the name of a [wrapped][`peripheral.wrap`] peripheral.
|
||||
*/
|
||||
@LuaFunction(mainThread = true)
|
||||
public abstract int pullFluid(
|
||||
T to, IComputerAccess computer, String fromName, Optional<Integer> limit, Optional<String> fluidName
|
||||
) throws LuaException;
|
||||
}
|
@@ -0,0 +1,197 @@
|
||||
// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: LicenseRef-CCPL
|
||||
|
||||
package dan200.computercraft.shared.peripheral.generic.methods;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.api.peripheral.GenericPeripheral;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.api.peripheral.PeripheralType;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Methods for interacting with inventories.
|
||||
*
|
||||
* @param <T> The type for inventories.
|
||||
* @cc.module inventory
|
||||
* @cc.since 1.94.0
|
||||
*/
|
||||
public abstract class AbstractInventoryMethods<T> implements GenericPeripheral {
|
||||
@Override
|
||||
public final PeripheralType getType() {
|
||||
return PeripheralType.ofAdditional("inventory");
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String id() {
|
||||
return ComputerCraftAPI.MOD_ID + ":inventory";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of this inventory.
|
||||
*
|
||||
* @param inventory The current inventory.
|
||||
* @return The number of slots in this inventory.
|
||||
*/
|
||||
@LuaFunction(mainThread = true)
|
||||
public abstract int size(T inventory);
|
||||
|
||||
/**
|
||||
* List all items in this inventory. This returns a table, with an entry for each slot.
|
||||
* <p>
|
||||
* Each item in the inventory is represented by a table containing some basic information, much like
|
||||
* {@link dan200.computercraft.shared.turtle.apis.TurtleAPI#getItemDetail(ILuaContext, Optional, Optional)}
|
||||
* includes. More information can be fetched with {@link #getItemDetail}. The table contains the item `name`, the
|
||||
* `count` and an a (potentially nil) hash of the item's `nbt.` This NBT data doesn't contain anything useful, but
|
||||
* allows you to distinguish identical items.
|
||||
* <p>
|
||||
* The returned table is sparse, and so empty slots will be `nil` - it is recommended to loop over using [`pairs`]
|
||||
* rather than [`ipairs`].
|
||||
*
|
||||
* @param inventory The current inventory.
|
||||
* @return All items in this inventory.
|
||||
* @cc.treturn { (table|nil)... } All items in this inventory.
|
||||
* @cc.usage Find an adjacent chest and print all items in it.
|
||||
*
|
||||
* <pre>{@code
|
||||
* local chest = peripheral.find("minecraft:chest")
|
||||
* for slot, item in pairs(chest.list()) do
|
||||
* print(("%d x %s in slot %d"):format(item.count, item.name, slot))
|
||||
* end
|
||||
* }</pre>
|
||||
*/
|
||||
@LuaFunction(mainThread = true)
|
||||
public abstract Map<Integer, Map<String, ?>> list(T inventory);
|
||||
|
||||
/**
|
||||
* Get detailed information about an item.
|
||||
* <p>
|
||||
* The returned information contains the same information as each item in
|
||||
* {@link #list}, as well as additional details like the display name
|
||||
* (`displayName`), and item and item durability (`damage`, `maxDamage`, `durability`).
|
||||
* <p>
|
||||
* Some items include more information (such as enchantments) - it is
|
||||
* recommended to print it out using [`textutils.serialize`] or in the Lua
|
||||
* REPL, to explore what is available.
|
||||
* <p>
|
||||
* > [Deprecated fields][!INFO]
|
||||
* > Older versions of CC: Tweaked exposed an {@code itemGroups} field, listing the
|
||||
* > creative tabs an item was available under. This information is no longer available on
|
||||
* > more recent versions of the game, and so this field will always be empty. Do not use this
|
||||
* > field in new code!
|
||||
*
|
||||
* @param inventory The current inventory.
|
||||
* @param slot The slot to get information about.
|
||||
* @return Information about the item in this slot, or {@code nil} if not present.
|
||||
* @throws LuaException If the slot is out of range.
|
||||
* @cc.treturn table Information about the item in this slot, or {@code nil} if not present.
|
||||
* @cc.usage Print some information about the first in a chest.
|
||||
*
|
||||
* <pre>{@code
|
||||
* local chest = peripheral.find("minecraft:chest")
|
||||
* local item = chest.getItemDetail(1)
|
||||
* if not item then print("No item") return end
|
||||
*
|
||||
* print(("%s (%s)"):format(item.displayName, item.name))
|
||||
* print(("Count: %d/%d"):format(item.count, item.maxCount))
|
||||
*
|
||||
* if item.damage then
|
||||
* print(("Damage: %d/%d"):format(item.damage, item.maxDamage))
|
||||
* end
|
||||
* }</pre>
|
||||
*/
|
||||
@Nullable
|
||||
@LuaFunction(mainThread = true)
|
||||
public abstract Map<String, ?> getItemDetail(T inventory, int slot) throws LuaException;
|
||||
|
||||
/**
|
||||
* Get the maximum number of items which can be stored in this slot.
|
||||
* <p>
|
||||
* Typically this will be limited to 64 items. However, some inventories (such as barrels or caches) can store
|
||||
* hundreds or thousands of items in one slot.
|
||||
*
|
||||
* @param inventory Inventory to probe.
|
||||
* @param slot The slot
|
||||
* @return The maximum number of items in this slot.
|
||||
* @throws LuaException If the slot is out of range.
|
||||
* @cc.usage Count the maximum number of items an adjacent chest can hold.
|
||||
* <pre>{@code
|
||||
* local chest = peripheral.find("minecraft:chest")
|
||||
* local total = 0
|
||||
* for i = 1, chest.size() do
|
||||
* total = total + chest.getItemLimit(i)
|
||||
* end
|
||||
* print(total)
|
||||
* }</pre>
|
||||
* @cc.since 1.96.0
|
||||
*/
|
||||
@LuaFunction(mainThread = true)
|
||||
public abstract long getItemLimit(T inventory, int slot) throws LuaException;
|
||||
|
||||
/**
|
||||
* Push items from one inventory to another connected one.
|
||||
* <p>
|
||||
* This allows you to push an item in an inventory to another inventory <em>on the same wired network</em>. Both
|
||||
* inventories must attached to wired modems which are connected via a cable.
|
||||
*
|
||||
* @param from Inventory to move items from.
|
||||
* @param computer The current computer.
|
||||
* @param toName The name of the peripheral/inventory to push to. This is the string given to [`peripheral.wrap`],
|
||||
* and displayed by the wired modem.
|
||||
* @param fromSlot The slot in the current inventory to move items to.
|
||||
* @param limit The maximum number of items to move. Defaults to the current stack limit.
|
||||
* @param toSlot The slot in the target inventory to move to. If not given, the item will be inserted into any slot.
|
||||
* @return The number of transferred items.
|
||||
* @throws LuaException If the peripheral to transfer to doesn't exist or isn't an inventory.
|
||||
* @throws LuaException If either source or destination slot is out of range.
|
||||
* @cc.see peripheral.getName Allows you to get the name of a [wrapped][`peripheral.wrap`] peripheral.
|
||||
* @cc.usage Wrap two chests, and push an item from one to another.
|
||||
* <pre>{@code
|
||||
* local chest_a = peripheral.wrap("minecraft:chest_0")
|
||||
* local chest_b = peripheral.wrap("minecraft:chest_1")
|
||||
*
|
||||
* chest_a.pushItems(peripheral.getName(chest_b), 1)
|
||||
* }</pre>
|
||||
*/
|
||||
@LuaFunction(mainThread = true)
|
||||
public abstract int pushItems(
|
||||
T from, IComputerAccess computer, String toName, int fromSlot, Optional<Integer> limit, Optional<Integer> toSlot
|
||||
) throws LuaException;
|
||||
|
||||
/**
|
||||
* Pull items from a connected inventory into this one.
|
||||
* <p>
|
||||
* This allows you to transfer items between inventories <em>on the same wired network</em>. Both this and the source
|
||||
* inventory must attached to wired modems which are connected via a cable.
|
||||
*
|
||||
* @param to Inventory to move items to.
|
||||
* @param computer The current computer.
|
||||
* @param fromName The name of the peripheral/inventory to pull from. This is the string given to [`peripheral.wrap`],
|
||||
* and displayed by the wired modem.
|
||||
* @param fromSlot The slot in the source inventory to move items from.
|
||||
* @param limit The maximum number of items to move. Defaults to the current stack limit.
|
||||
* @param toSlot The slot in current inventory to move to. If not given, the item will be inserted into any slot.
|
||||
* @return The number of transferred items.
|
||||
* @throws LuaException If the peripheral to transfer to doesn't exist or isn't an inventory.
|
||||
* @throws LuaException If either source or destination slot is out of range.
|
||||
* @cc.see peripheral.getName Allows you to get the name of a [wrapped][`peripheral.wrap`] peripheral.
|
||||
* @cc.usage Wrap two chests, and push an item from one to another.
|
||||
* <pre>{@code
|
||||
* local chest_a = peripheral.wrap("minecraft:chest_0")
|
||||
* local chest_b = peripheral.wrap("minecraft:chest_1")
|
||||
*
|
||||
* chest_a.pullItems(peripheral.getName(chest_b), 1)
|
||||
* }</pre>
|
||||
*/
|
||||
@LuaFunction(mainThread = true)
|
||||
public abstract int pullItems(
|
||||
T to, IComputerAccess computer, String fromName, int fromSlot, Optional<Integer> limit, Optional<Integer> toSlot
|
||||
) throws LuaException;
|
||||
}
|
@@ -10,6 +10,7 @@ import com.mojang.brigadier.arguments.ArgumentType;
|
||||
import dan200.computercraft.api.network.wired.WiredElement;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.shared.config.ConfigFile;
|
||||
import dan200.computercraft.shared.network.MessageType;
|
||||
import dan200.computercraft.shared.network.NetworkMessage;
|
||||
import dan200.computercraft.shared.network.client.ClientNetworkContext;
|
||||
import dan200.computercraft.shared.network.container.ContainerData;
|
||||
@@ -164,6 +165,18 @@ public interface PlatformHelper extends dan200.computercraft.impl.PlatformHelper
|
||||
*/
|
||||
void openMenu(Player player, MenuProvider owner, ContainerData menu);
|
||||
|
||||
/**
|
||||
* Create a new {@link MessageType}.
|
||||
*
|
||||
* @param id The descriminator for this message type.
|
||||
* @param channel The channel name for this message type.
|
||||
* @param klass The type of this message.
|
||||
* @param reader The function which reads the packet from a buffer. Should be the inverse to {@link NetworkMessage#write(FriendlyByteBuf)}.
|
||||
* @param <T> The type of this message.
|
||||
* @return The new {@link MessageType} instance.
|
||||
*/
|
||||
<T extends NetworkMessage<?>> MessageType<T> createMessageType(int id, ResourceLocation channel, Class<T> klass, FriendlyByteBuf.Reader<T> reader);
|
||||
|
||||
/**
|
||||
* Send a message to a specific player.
|
||||
*
|
||||
|
@@ -12,7 +12,6 @@ import dan200.computercraft.api.pocket.IPocketUpgrade;
|
||||
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||
import dan200.computercraft.core.computer.ComputerSide;
|
||||
import dan200.computercraft.impl.PocketUpgrades;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.common.IColouredItem;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.core.ServerContext;
|
||||
@@ -60,14 +59,6 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I
|
||||
this.family = family;
|
||||
}
|
||||
|
||||
public static ItemStack create(int id, @Nullable String label, int colour, ComputerFamily family, @Nullable UpgradeData<IPocketUpgrade> upgrade) {
|
||||
return switch (family) {
|
||||
case NORMAL -> ModRegistry.Items.POCKET_COMPUTER_NORMAL.get().create(id, label, colour, upgrade);
|
||||
case ADVANCED -> ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get().create(id, label, colour, upgrade);
|
||||
default -> ItemStack.EMPTY;
|
||||
};
|
||||
}
|
||||
|
||||
public ItemStack create(int id, @Nullable String label, int colour, @Nullable UpgradeData<IPocketUpgrade> upgrade) {
|
||||
var result = new ItemStack(this);
|
||||
if (id >= 0) result.getOrCreateTag().putInt(NBT_ID, id);
|
||||
@@ -243,17 +234,16 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I
|
||||
return IComputerItem.super.getLabel(stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComputerFamily getFamily() {
|
||||
return family;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withFamily(ItemStack stack, ComputerFamily family) {
|
||||
return create(
|
||||
public ItemStack changeItem(ItemStack stack, Item newItem) {
|
||||
return newItem instanceof PocketComputerItem pocket ? pocket.create(
|
||||
getComputerID(stack), getLabel(stack), getColour(stack),
|
||||
family, getUpgradeWithData(stack)
|
||||
);
|
||||
getUpgradeWithData(stack)
|
||||
) : ItemStack.EMPTY;
|
||||
}
|
||||
|
||||
// IMedia
|
||||
|
@@ -81,7 +81,6 @@ public final class PocketComputerUpgradeRecipe extends CustomRecipe {
|
||||
if (upgrade == null) return ItemStack.EMPTY;
|
||||
|
||||
// Construct the new stack
|
||||
var family = itemComputer.getFamily();
|
||||
var computerID = itemComputer.getComputerID(computer);
|
||||
var label = itemComputer.getLabel(computer);
|
||||
var colour = itemComputer.getColour(computer);
|
||||
|
@@ -11,6 +11,7 @@ import dan200.computercraft.api.turtle.TurtleCommandResult;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.core.apis.IAPIEnvironment;
|
||||
import dan200.computercraft.core.metrics.Metrics;
|
||||
import dan200.computercraft.shared.peripheral.generic.methods.AbstractInventoryMethods;
|
||||
import dan200.computercraft.shared.turtle.core.*;
|
||||
|
||||
import java.util.Optional;
|
||||
@@ -749,7 +750,7 @@ public class TurtleAPI implements ILuaAPI {
|
||||
* -- count = 13,
|
||||
* -- }
|
||||
* }</pre>
|
||||
* @see dan200.computercraft.shared.peripheral.generic.methods.InventoryMethods#getItemDetail Describes the information returned by a detailed query.
|
||||
* @see AbstractInventoryMethods#getItemDetail Describes the information returned by a detailed query.
|
||||
*/
|
||||
@LuaFunction
|
||||
public final MethodResult getItemDetail(ILuaContext context, Optional<Integer> slot, Optional<Boolean> detailed) throws LuaException {
|
||||
|
@@ -10,7 +10,6 @@ import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||
import dan200.computercraft.shared.computer.blocks.AbstractComputerBlock;
|
||||
import dan200.computercraft.shared.computer.blocks.AbstractComputerBlockEntity;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.platform.RegistryEntry;
|
||||
import dan200.computercraft.shared.turtle.core.TurtleBrain;
|
||||
import dan200.computercraft.shared.turtle.items.TurtleItem;
|
||||
@@ -52,6 +51,16 @@ import static dan200.computercraft.shared.util.WaterloggableHelpers.getFluidStat
|
||||
public class TurtleBlock extends AbstractComputerBlock<TurtleBlockEntity> implements SimpleWaterloggedBlock {
|
||||
public static final DirectionProperty FACING = BlockStateProperties.HORIZONTAL_FACING;
|
||||
|
||||
/**
|
||||
* The explosion resistance to use when a turtle is "immune" to explosions.
|
||||
* <p>
|
||||
* This is used as the default explosion resistance for advanced turtles, and the resistance for entity-based
|
||||
* explosions (e.g. creepers).
|
||||
*
|
||||
* @see #getExplosionResistance(BlockState, BlockGetter, BlockPos, Explosion)
|
||||
*/
|
||||
public static final float IMMUNE_EXPLOSION_RESISTANCE = 2000f;
|
||||
|
||||
private static final VoxelShape DEFAULT_SHAPE = Shapes.box(
|
||||
0.125, 0.125, 0.125,
|
||||
0.875, 0.875, 0.875
|
||||
@@ -59,8 +68,8 @@ public class TurtleBlock extends AbstractComputerBlock<TurtleBlockEntity> implem
|
||||
|
||||
private final BlockEntityTicker<TurtleBlockEntity> clientTicker = (level, pos, state, computer) -> computer.clientTick();
|
||||
|
||||
public TurtleBlock(Properties settings, ComputerFamily family, RegistryEntry<BlockEntityType<TurtleBlockEntity>> type) {
|
||||
super(settings, family, type);
|
||||
public TurtleBlock(Properties settings, RegistryEntry<BlockEntityType<TurtleBlockEntity>> type) {
|
||||
super(settings, type);
|
||||
registerDefaultState(getStateDefinition().any()
|
||||
.setValue(FACING, Direction.NORTH)
|
||||
.setValue(WATERLOGGED, false)
|
||||
@@ -149,20 +158,21 @@ public class TurtleBlock extends AbstractComputerBlock<TurtleBlockEntity> implem
|
||||
@ForgeOverride
|
||||
public float getExplosionResistance(BlockState state, BlockGetter world, BlockPos pos, Explosion explosion) {
|
||||
var exploder = explosion.getDirectSourceEntity();
|
||||
if (getFamily() == ComputerFamily.ADVANCED || exploder instanceof LivingEntity || exploder instanceof AbstractHurtingProjectile) {
|
||||
return 2000;
|
||||
if (exploder instanceof LivingEntity || exploder instanceof AbstractHurtingProjectile) {
|
||||
return IMMUNE_EXPLOSION_RESISTANCE;
|
||||
}
|
||||
|
||||
return explosionResistance;
|
||||
return getExplosionResistance();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ItemStack getItem(AbstractComputerBlockEntity tile) {
|
||||
if (!(tile instanceof TurtleBlockEntity turtle)) return ItemStack.EMPTY;
|
||||
if (!(asItem() instanceof TurtleItem item)) return ItemStack.EMPTY;
|
||||
|
||||
var access = turtle.getAccess();
|
||||
return TurtleItem.create(
|
||||
turtle.getComputerID(), turtle.getLabel(), access.getColour(), turtle.getFamily(),
|
||||
return item.create(
|
||||
turtle.getComputerID(), turtle.getLabel(), access.getColour(),
|
||||
withPersistedData(access.getUpgradeWithData(TurtleSide.LEFT)),
|
||||
withPersistedData(access.getUpgradeWithData(TurtleSide.RIGHT)),
|
||||
access.getFuelLevel(), turtle.getOverlay()
|
||||
|
@@ -38,6 +38,7 @@ import net.minecraft.world.phys.Vec3;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Collections;
|
||||
import java.util.function.IntSupplier;
|
||||
|
||||
public class TurtleBlockEntity extends AbstractComputerBlockEntity implements BasicContainer {
|
||||
public static final int INVENTORY_SIZE = 16;
|
||||
@@ -53,13 +54,17 @@ public class TurtleBlockEntity extends AbstractComputerBlockEntity implements Ba
|
||||
private final NonNullList<ItemStack> inventory = NonNullList.withSize(INVENTORY_SIZE, ItemStack.EMPTY);
|
||||
private final NonNullList<ItemStack> inventorySnapshot = NonNullList.withSize(INVENTORY_SIZE, ItemStack.EMPTY);
|
||||
private boolean inventoryChanged = false;
|
||||
|
||||
private final IntSupplier fuelLimit;
|
||||
|
||||
private TurtleBrain brain = new TurtleBrain(this);
|
||||
private MoveState moveState = MoveState.NOT_MOVED;
|
||||
private @Nullable IPeripheral peripheral;
|
||||
private @Nullable Runnable onMoved;
|
||||
|
||||
public TurtleBlockEntity(BlockEntityType<? extends TurtleBlockEntity> type, BlockPos pos, BlockState state, ComputerFamily family) {
|
||||
public TurtleBlockEntity(BlockEntityType<? extends TurtleBlockEntity> type, BlockPos pos, BlockState state, IntSupplier fuelLimit, ComputerFamily family) {
|
||||
super(type, pos, state, family);
|
||||
this.fuelLimit = fuelLimit;
|
||||
}
|
||||
|
||||
boolean hasMoved() {
|
||||
@@ -172,8 +177,6 @@ public class TurtleBlockEntity extends AbstractComputerBlockEntity implements Ba
|
||||
return hasPeripheralUpgradeOnSide(localSide);
|
||||
}
|
||||
|
||||
// IDirectionalTile
|
||||
|
||||
@Override
|
||||
public Direction getDirection() {
|
||||
return getBlockState().getValue(TurtleBlock.FACING);
|
||||
@@ -272,6 +275,10 @@ public class TurtleBlockEntity extends AbstractComputerBlockEntity implements Ba
|
||||
|
||||
// Privates
|
||||
|
||||
public int getFuelLimit() {
|
||||
return fuelLimit.getAsInt();
|
||||
}
|
||||
|
||||
private boolean hasPeripheralUpgradeOnSide(ComputerSide side) {
|
||||
ITurtleUpgrade upgrade;
|
||||
switch (side) {
|
||||
|
@@ -395,11 +395,7 @@ public class TurtleBrain implements TurtleAccessInternal {
|
||||
|
||||
@Override
|
||||
public int getFuelLimit() {
|
||||
if (owner.getFamily() == ComputerFamily.ADVANCED) {
|
||||
return Config.advancedTurtleFuelLimit;
|
||||
} else {
|
||||
return Config.turtleFuelLimit;
|
||||
}
|
||||
return owner.getFuelLimit();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -10,9 +10,7 @@ import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||
import dan200.computercraft.impl.TurtleUpgrades;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.common.IColouredItem;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.items.AbstractComputerItem;
|
||||
import dan200.computercraft.shared.turtle.blocks.TurtleBlock;
|
||||
import dan200.computercraft.shared.util.NBTUtil;
|
||||
@@ -20,6 +18,7 @@ import net.minecraft.core.cauldron.CauldronInteraction;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.LayeredCauldronBlock;
|
||||
|
||||
@@ -32,20 +31,6 @@ public class TurtleItem extends AbstractComputerItem implements IColouredItem {
|
||||
super(block, settings);
|
||||
}
|
||||
|
||||
public static ItemStack create(
|
||||
int id, @Nullable String label, int colour, ComputerFamily family,
|
||||
@Nullable UpgradeData<ITurtleUpgrade> leftUpgrade, @Nullable UpgradeData<ITurtleUpgrade> rightUpgrade,
|
||||
int fuelLevel, @Nullable ResourceLocation overlay
|
||||
) {
|
||||
return switch (family) {
|
||||
case NORMAL ->
|
||||
ModRegistry.Items.TURTLE_NORMAL.get().create(id, label, colour, leftUpgrade, rightUpgrade, fuelLevel, overlay);
|
||||
case ADVANCED ->
|
||||
ModRegistry.Items.TURTLE_ADVANCED.get().create(id, label, colour, leftUpgrade, rightUpgrade, fuelLevel, overlay);
|
||||
default -> ItemStack.EMPTY;
|
||||
};
|
||||
}
|
||||
|
||||
public ItemStack create(
|
||||
int id, @Nullable String label, int colour,
|
||||
@Nullable UpgradeData<ITurtleUpgrade> leftUpgrade, @Nullable UpgradeData<ITurtleUpgrade> rightUpgrade,
|
||||
@@ -119,13 +104,13 @@ public class TurtleItem extends AbstractComputerItem implements IColouredItem {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withFamily(ItemStack stack, ComputerFamily family) {
|
||||
return create(
|
||||
public ItemStack changeItem(ItemStack stack, Item newItem) {
|
||||
return newItem instanceof TurtleItem turtle ? turtle.create(
|
||||
getComputerID(stack), getLabel(stack),
|
||||
getColour(stack), family,
|
||||
getColour(stack),
|
||||
getUpgradeWithData(stack, TurtleSide.LEFT), getUpgradeWithData(stack, TurtleSide.RIGHT),
|
||||
getFuelLevel(stack), getOverlay(stack)
|
||||
);
|
||||
) : ItemStack.EMPTY;
|
||||
}
|
||||
|
||||
public @Nullable ITurtleUpgrade getUpgrade(ItemStack stack, TurtleSide side) {
|
||||
|
@@ -58,7 +58,7 @@ public class TurtleOverlayRecipe extends CustomShapelessRecipe {
|
||||
return ModRegistry.RecipeSerializers.TURTLE_OVERLAY.get();
|
||||
}
|
||||
|
||||
public static class Serializer implements RecipeSerializer<TurtleOverlayRecipe> {
|
||||
public static class Serialiser implements RecipeSerializer<TurtleOverlayRecipe> {
|
||||
@Override
|
||||
public TurtleOverlayRecipe fromJson(ResourceLocation id, JsonObject json) {
|
||||
var recipe = ShapelessRecipeSpec.fromJson(json);
|
||||
|
@@ -10,6 +10,7 @@ accessWidener v1 named
|
||||
accessible method net/minecraft/client/renderer/item/ItemProperties register (Lnet/minecraft/world/item/Item;Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/client/renderer/item/ClampedItemPropertyFunction;)V
|
||||
accessible method net/minecraft/client/renderer/blockentity/BlockEntityRenderers register (Lnet/minecraft/world/level/block/entity/BlockEntityType;Lnet/minecraft/client/renderer/blockentity/BlockEntityRendererProvider;)V
|
||||
accessible class net/minecraft/world/item/CreativeModeTab$Output
|
||||
accessible field net/minecraft/world/item/CreativeModeTabs OP_BLOCKS Lnet/minecraft/resources/ResourceKey;
|
||||
|
||||
# Containers
|
||||
accessible class net/minecraft/world/inventory/MenuType$MenuSupplier
|
||||
|
@@ -13,6 +13,7 @@ import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.impl.AbstractComputerCraftAPI;
|
||||
import dan200.computercraft.impl.ComputerCraftAPIService;
|
||||
import dan200.computercraft.shared.config.ConfigFile;
|
||||
import dan200.computercraft.shared.network.MessageType;
|
||||
import dan200.computercraft.shared.network.NetworkMessage;
|
||||
import dan200.computercraft.shared.network.client.ClientNetworkContext;
|
||||
import dan200.computercraft.shared.network.container.ContainerData;
|
||||
@@ -168,6 +169,13 @@ public class TestPlatformHelper extends AbstractComputerCraftAPI implements Plat
|
||||
throw new UnsupportedOperationException("Cannot open menu inside tests");
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends NetworkMessage<?>> MessageType<T> createMessageType(int id, ResourceLocation channel, Class<T> klass, FriendlyByteBuf.Reader<T> reader) {
|
||||
record TypeImpl<T extends NetworkMessage<?>>(Function<FriendlyByteBuf, T> reader) implements MessageType<T> {
|
||||
}
|
||||
return new TypeImpl<>(reader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComponentAccess<IPeripheral> createPeripheralAccess(Consumer<Direction> invalidate) {
|
||||
throw new UnsupportedOperationException("Cannot interact with the world inside tests");
|
||||
|
@@ -29,7 +29,7 @@ class PlayRecordClientMessageTest {
|
||||
@Property
|
||||
public void testRoundTrip(@ForAll("message") PlayRecordClientMessage message) {
|
||||
var buffer = new FriendlyByteBuf(Unpooled.directBuffer());
|
||||
message.toBytes(buffer);
|
||||
message.write(buffer);
|
||||
|
||||
var converted = new PlayRecordClientMessage(buffer);
|
||||
assertEquals(buffer.readableBytes(), 0, "Whole packet was read");
|
||||
|
@@ -63,7 +63,7 @@ public class UploadFileMessageTest {
|
||||
private static List<UploadFileMessage> roundtripPackets(List<UploadFileMessage> packets) {
|
||||
return packets.stream().map(packet -> {
|
||||
var buffer = new FriendlyByteBuf(Unpooled.directBuffer());
|
||||
packet.toBytes(buffer);
|
||||
packet.write(buffer);
|
||||
// We include things like file size in the packet, but not in the count, so grant a slightly larger threshold.
|
||||
assertThat("Packet is too large", buffer.writerIndex(), lessThanOrEqualTo(MAX_PACKET_SIZE + 128));
|
||||
if ((packet.flag & FLAG_LAST) == 0) {
|
||||
|
@@ -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
|
||||
|
@@ -11,11 +11,10 @@ import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
* A generic source of {@link LuaFunction} functions.
|
||||
* <p>
|
||||
* Unlike normal objects ({@link IDynamicLuaObject} or {@link IPeripheral}), methods do not target this object but
|
||||
* instead are defined as {@code static} and accept their target as the first parameter. This allows you to inject
|
||||
* methods onto objects you do not own, as well as declaring methods for a specific "trait" (for instance, a Forge
|
||||
* capability or Fabric block lookup interface).
|
||||
* accept their target as the first parameter. This allows you to inject methods onto objects you do not own, as well as
|
||||
* declaring methods for a specific "trait" (for instance, a Forge capability or Fabric block lookup interface).
|
||||
* <p>
|
||||
* Currently the "generic peripheral" system is incompatible with normal peripherals. Peripherals explicitly provided
|
||||
* Currently, the "generic peripheral" system is incompatible with normal peripherals. Peripherals explicitly provided
|
||||
* by capabilities/the block lookup API take priority. Block entities which use this system are given a peripheral name
|
||||
* determined by their id, rather than any peripheral provider, though additional types may be provided by overriding
|
||||
* {@link GenericPeripheral#getType()}.
|
||||
@@ -25,7 +24,7 @@ import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
* <pre>{@code
|
||||
* public class InventoryMethods implements GenericSource {
|
||||
* \@LuaFunction( mainThread = true )
|
||||
* public static int size(IItemHandler inventory) {
|
||||
* public int size(IItemHandler inventory) {
|
||||
* return inventory.getSlots();
|
||||
* }
|
||||
*
|
||||
|
@@ -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.
|
||||
*
|
||||
|
@@ -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,6 +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.nio.ByteBuffer;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -73,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;
|
||||
@@ -81,7 +80,8 @@ 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);
|
||||
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);
|
||||
@@ -90,7 +90,7 @@ public class HTTPAPI implements ILuaAPI {
|
||||
} else {
|
||||
// Get URL and post information
|
||||
address = args.getString(0);
|
||||
postString = args.optString(1, null);
|
||||
postBody = args.optBytes(1).orElse(null);
|
||||
headerTable = args.optTable(2, Map.of());
|
||||
binary = args.optBoolean(3, false);
|
||||
requestMethod = null;
|
||||
@@ -103,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")) {
|
||||
@@ -113,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))) {
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,68 @@
|
||||
// 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.nio.channels.SeekableByteChannel;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* A file handle opened for reading with {@link dan200.computercraft.core.apis.FSAPI#open(String, String)}.
|
||||
*
|
||||
* @cc.module fs.ReadHandle
|
||||
*/
|
||||
public class ReadHandle extends AbstractHandle {
|
||||
public ReadHandle(SeekableByteChannel channel, TrackingCloseable closeable, boolean binary) {
|
||||
super(channel, closeable, binary);
|
||||
}
|
||||
|
||||
public ReadHandle(SeekableByteChannel channel, boolean binary) {
|
||||
this(channel, new TrackingCloseable.Impl(channel), binary);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
@LuaFunction
|
||||
public final Object[] read(Optional<Integer> countArg) throws LuaException {
|
||||
return super.read(countArg);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
@LuaFunction
|
||||
public final Object[] readAll() throws LuaException {
|
||||
return super.readAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
@LuaFunction
|
||||
public final Object[] readLine(Optional<Boolean> withTrailingArg) throws LuaException {
|
||||
return super.readLine(withTrailingArg);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
@LuaFunction
|
||||
public final Object[] seek(Optional<String> whence, Optional<Long> offset) throws LuaException {
|
||||
return super.seek(whence, offset);
|
||||
}
|
||||
}
|
@@ -0,0 +1,96 @@
|
||||
// SPDX-FileCopyrightText: 2018 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 javax.annotation.Nullable;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* A file handle opened for reading and writing with {@link dan200.computercraft.core.apis.FSAPI#open(String, String)}.
|
||||
*
|
||||
* @cc.module fs.ReadWriteHandle
|
||||
*/
|
||||
public class ReadWriteHandle extends AbstractHandle {
|
||||
public ReadWriteHandle(SeekableByteChannel channel, TrackingCloseable closeable, boolean binary) {
|
||||
super(channel, closeable, binary);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
@LuaFunction
|
||||
public final Object[] read(Optional<Integer> countArg) throws LuaException {
|
||||
return super.read(countArg);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
@LuaFunction
|
||||
public final Object[] readAll() throws LuaException {
|
||||
return super.readAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
@LuaFunction
|
||||
public final Object[] readLine(Optional<Boolean> withTrailingArg) throws LuaException {
|
||||
return super.readLine(withTrailingArg);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
@LuaFunction
|
||||
public final Object[] seek(Optional<String> whence, Optional<Long> offset) throws LuaException {
|
||||
return super.seek(whence, offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
@LuaFunction
|
||||
public final void write(IArguments arguments) throws LuaException {
|
||||
super.write(arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
@LuaFunction
|
||||
public final void writeLine(Coerced<ByteBuffer> text) throws LuaException {
|
||||
super.writeLine(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
@LuaFunction
|
||||
public final void flush() throws LuaException {
|
||||
super.flush();
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,74 @@
|
||||
// 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 javax.annotation.Nullable;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* A file handle opened for writing by {@link dan200.computercraft.core.apis.FSAPI#open}.
|
||||
*
|
||||
* @cc.module fs.WriteHandle
|
||||
*/
|
||||
public class WriteHandle extends AbstractHandle {
|
||||
protected WriteHandle(SeekableByteChannel channel, TrackingCloseable closeable, boolean binary) {
|
||||
super(channel, closeable, binary);
|
||||
}
|
||||
|
||||
public static WriteHandle of(SeekableByteChannel channel, TrackingCloseable closeable, boolean binary, boolean canSeek) {
|
||||
return canSeek ? new Seekable(channel, closeable, binary) : new WriteHandle(channel, closeable, binary);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
@LuaFunction
|
||||
public final void write(IArguments arguments) throws LuaException {
|
||||
super.write(arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
@LuaFunction
|
||||
public final void writeLine(Coerced<ByteBuffer> text) throws LuaException {
|
||||
super.writeLine(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
@LuaFunction
|
||||
public final void flush() throws LuaException {
|
||||
super.flush();
|
||||
}
|
||||
|
||||
public static class Seekable extends WriteHandle {
|
||||
Seekable(SeekableByteChannel channel, TrackingCloseable closeable, boolean binary) {
|
||||
super(channel, closeable, binary);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
@LuaFunction
|
||||
public final Object[] seek(Optional<String> whence, Optional<Long> offset) throws LuaException {
|
||||
return super.seek(whence, offset);
|
||||
}
|
||||
}
|
||||
}
|
@@ -26,7 +26,7 @@ import org.slf4j.LoggerFactory;
|
||||
import javax.annotation.Nullable;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -57,21 +57,21 @@ public class HttpRequest extends Resource<HttpRequest> {
|
||||
final AtomicInteger redirects;
|
||||
|
||||
public HttpRequest(
|
||||
ResourceGroup<HttpRequest> limiter, IAPIEnvironment environment, String address, @Nullable String postText,
|
||||
ResourceGroup<HttpRequest> limiter, IAPIEnvironment environment, String address, @Nullable ByteBuffer postBody,
|
||||
HttpHeaders headers, boolean binary, boolean followRedirects, int timeout
|
||||
) {
|
||||
super(limiter);
|
||||
this.environment = environment;
|
||||
this.address = address;
|
||||
postBuffer = postText != null
|
||||
? Unpooled.wrappedBuffer(postText.getBytes(StandardCharsets.UTF_8))
|
||||
postBuffer = postBody != null
|
||||
? Unpooled.wrappedBuffer(postBody)
|
||||
: Unpooled.buffer(0);
|
||||
this.headers = headers;
|
||||
this.binary = binary;
|
||||
redirects = new AtomicInteger(followRedirects ? MAX_REDIRECTS : 0);
|
||||
this.timeout = timeout;
|
||||
|
||||
if (postText != null) {
|
||||
if (postBody != null) {
|
||||
if (!headers.contains(HttpHeaderNames.CONTENT_TYPE)) {
|
||||
headers.set(HttpHeaderNames.CONTENT_TYPE, "application/x-www-form-urlencoded; charset=utf-8");
|
||||
}
|
||||
|
@@ -6,8 +6,7 @@ package dan200.computercraft.core.apis.http.request;
|
||||
|
||||
import dan200.computercraft.core.Logging;
|
||||
import dan200.computercraft.core.apis.handles.ArrayByteChannel;
|
||||
import dan200.computercraft.core.apis.handles.BinaryReadableHandle;
|
||||
import dan200.computercraft.core.apis.handles.EncodedReadableHandle;
|
||||
import dan200.computercraft.core.apis.handles.ReadHandle;
|
||||
import dan200.computercraft.core.apis.http.HTTPRequestException;
|
||||
import dan200.computercraft.core.apis.http.NetworkUtils;
|
||||
import dan200.computercraft.core.apis.http.options.Options;
|
||||
@@ -188,9 +187,7 @@ public final class HttpRequestHandler extends SimpleChannelInboundHandler<HttpOb
|
||||
|
||||
// Prepare to queue an event
|
||||
var contents = new ArrayByteChannel(bytes);
|
||||
var reader = request.isBinary()
|
||||
? BinaryReadableHandle.of(contents)
|
||||
: new EncodedReadableHandle(EncodedReadableHandle.open(contents, responseCharset));
|
||||
var reader = new ReadHandle(contents, request.isBinary());
|
||||
var stream = new HttpResponseHandle(reader, status.code(), status.reasonPhrase(), headers);
|
||||
|
||||
if (status.code() >= 200 && status.code() < 400) {
|
||||
|
@@ -7,18 +7,16 @@ package dan200.computercraft.core.apis.http.request;
|
||||
import dan200.computercraft.api.lua.IArguments;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.core.apis.HTTPAPI;
|
||||
import dan200.computercraft.core.apis.handles.BinaryReadableHandle;
|
||||
import dan200.computercraft.core.apis.handles.EncodedReadableHandle;
|
||||
import dan200.computercraft.core.apis.handles.HandleGeneric;
|
||||
import dan200.computercraft.core.apis.handles.AbstractHandle;
|
||||
import dan200.computercraft.core.apis.handles.ReadHandle;
|
||||
import dan200.computercraft.core.methods.ObjectSource;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A http response. This provides the same methods as a {@link EncodedReadableHandle file} (or
|
||||
* {@link BinaryReadableHandle binary file} if the request used binary mode), though provides several request specific
|
||||
* methods.
|
||||
* A http response. This provides the same methods as a {@link ReadHandle file}, though provides several request
|
||||
* specific methods.
|
||||
*
|
||||
* @cc.module http.Response
|
||||
* @see HTTPAPI#request(IArguments) On how to make a http request.
|
||||
@@ -29,7 +27,7 @@ public class HttpResponseHandle implements ObjectSource {
|
||||
private final String responseStatus;
|
||||
private final Map<String, String> responseHeaders;
|
||||
|
||||
public HttpResponseHandle(HandleGeneric reader, int responseCode, String responseStatus, Map<String, String> responseHeaders) {
|
||||
public HttpResponseHandle(AbstractHandle reader, int responseCode, String responseStatus, Map<String, String> responseHeaders) {
|
||||
this.reader = reader;
|
||||
this.responseCode = responseCode;
|
||||
this.responseStatus = responseStatus;
|
||||
|
@@ -5,14 +5,13 @@
|
||||
package dan200.computercraft.core.apis.http.websocket;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.core.Logging;
|
||||
import dan200.computercraft.core.apis.IAPIEnvironment;
|
||||
import dan200.computercraft.core.apis.http.HTTPRequestException;
|
||||
import dan200.computercraft.core.apis.http.NetworkUtils;
|
||||
import dan200.computercraft.core.apis.http.Resource;
|
||||
import dan200.computercraft.core.apis.http.ResourceGroup;
|
||||
import dan200.computercraft.core.apis.http.*;
|
||||
import dan200.computercraft.core.apis.http.options.Options;
|
||||
import dan200.computercraft.core.metrics.Metrics;
|
||||
import dan200.computercraft.core.util.AtomicHelpers;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
@@ -24,10 +23,8 @@ import io.netty.handler.codec.http.HttpClientCodec;
|
||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import io.netty.handler.codec.http.HttpHeaders;
|
||||
import io.netty.handler.codec.http.HttpObjectAggregator;
|
||||
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
|
||||
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
||||
import io.netty.handler.codec.http.websocketx.WebSocketClientProtocolHandler;
|
||||
import io.netty.handler.codec.http.websocketx.WebSocketVersion;
|
||||
import io.netty.handler.codec.http.websocketx.*;
|
||||
import io.netty.util.concurrent.GenericFutureListener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -35,6 +32,7 @@ import javax.annotation.Nullable;
|
||||
import java.net.URI;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Provides functionality to verify and connect to a remote websocket.
|
||||
@@ -57,6 +55,9 @@ public class Websocket extends Resource<Websocket> implements WebsocketClient {
|
||||
private final HttpHeaders headers;
|
||||
private final int timeout;
|
||||
|
||||
private final AtomicInteger inFlight = new AtomicInteger(0);
|
||||
private final GenericFutureListener<? extends io.netty.util.concurrent.Future<? super Void>> onSend = f -> inFlight.decrementAndGet();
|
||||
|
||||
public Websocket(ResourceGroup<Websocket> limiter, IAPIEnvironment environment, URI uri, String address, HttpHeaders headers, int timeout) {
|
||||
super(limiter);
|
||||
this.environment = environment;
|
||||
@@ -170,18 +171,27 @@ public class Websocket extends Resource<Websocket> implements WebsocketClient {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendText(String message) {
|
||||
environment.observe(Metrics.WEBSOCKET_OUTGOING, message.length());
|
||||
|
||||
var channel = channel();
|
||||
if (channel != null) channel.writeAndFlush(new TextWebSocketFrame(message));
|
||||
public void sendText(String message) throws LuaException {
|
||||
sendMessage(new TextWebSocketFrame(message), message.length());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendBinary(ByteBuffer message) {
|
||||
environment.observe(Metrics.WEBSOCKET_OUTGOING, message.remaining());
|
||||
public void sendBinary(ByteBuffer message) throws LuaException {
|
||||
long size = message.remaining();
|
||||
sendMessage(new BinaryWebSocketFrame(Unpooled.wrappedBuffer(message)), size);
|
||||
}
|
||||
|
||||
private void sendMessage(WebSocketFrame frame, long size) throws LuaException {
|
||||
var channel = channel();
|
||||
if (channel != null) channel.writeAndFlush(new BinaryWebSocketFrame(Unpooled.wrappedBuffer(message)));
|
||||
if (channel == null) return;
|
||||
|
||||
// Grow the number of in-flight requests, aborting if we've hit the limit. This is then decremented when the
|
||||
// promise finishes.
|
||||
if (!AtomicHelpers.incrementToLimit(inFlight, ResourceQueue.DEFAULT_LIMIT)) {
|
||||
throw new LuaException("Too many ongoing websocket messages");
|
||||
}
|
||||
|
||||
environment.observe(Metrics.WEBSOCKET_OUTGOING, size);
|
||||
channel.writeAndFlush(frame).addListener(onSend);
|
||||
}
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@
|
||||
|
||||
package dan200.computercraft.core.apis.http.websocket;
|
||||
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.core.apis.http.HTTPRequestException;
|
||||
|
||||
import java.io.Closeable;
|
||||
@@ -39,15 +40,17 @@ public interface WebsocketClient extends Closeable {
|
||||
* Send a text websocket frame.
|
||||
*
|
||||
* @param message The message to send.
|
||||
* @throws LuaException If the message could not be sent.
|
||||
*/
|
||||
void sendText(String message);
|
||||
void sendText(String message) throws LuaException;
|
||||
|
||||
/**
|
||||
* Send a binary websocket frame.
|
||||
*
|
||||
* @param message The message to send.
|
||||
* @throws LuaException If the message could not be sent.
|
||||
*/
|
||||
void sendBinary(ByteBuffer message);
|
||||
void sendBinary(ByteBuffer message) throws LuaException;
|
||||
|
||||
/**
|
||||
* Parse an address, ensuring it is a valid websocket URI.
|
||||
|
@@ -8,6 +8,11 @@ import dan200.computercraft.api.lua.*;
|
||||
import dan200.computercraft.core.apis.IAPIEnvironment;
|
||||
import dan200.computercraft.core.apis.http.options.Options;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.CharacterCodingException;
|
||||
import java.nio.charset.CharsetDecoder;
|
||||
import java.nio.charset.CodingErrorAction;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
@@ -24,6 +29,8 @@ import static dan200.computercraft.core.apis.http.websocket.WebsocketClient.MESS
|
||||
* @see dan200.computercraft.core.apis.HTTPAPI#websocket On how to open a websocket.
|
||||
*/
|
||||
public class WebsocketHandle {
|
||||
private static final CharsetDecoder DECODER = StandardCharsets.UTF_8.newDecoder().onMalformedInput(CodingErrorAction.REPLACE);
|
||||
|
||||
private final IAPIEnvironment environment;
|
||||
private final String address;
|
||||
private final WebsocketClient websocket;
|
||||
@@ -68,18 +75,23 @@ public class WebsocketHandle {
|
||||
* @cc.changed 1.81.0 Added argument for binary mode.
|
||||
*/
|
||||
@LuaFunction
|
||||
public final void send(Coerced<String> message, Optional<Boolean> binary) throws LuaException {
|
||||
public final void send(Coerced<ByteBuffer> message, Optional<Boolean> binary) throws LuaException {
|
||||
checkOpen();
|
||||
|
||||
var text = message.value();
|
||||
if (options.websocketMessage() != 0 && text.length() > options.websocketMessage()) {
|
||||
if (options.websocketMessage() != 0 && text.remaining() > options.websocketMessage()) {
|
||||
throw new LuaException("Message is too large");
|
||||
}
|
||||
|
||||
if (binary.orElse(false)) {
|
||||
websocket.sendBinary(LuaValues.encode(text));
|
||||
websocket.sendBinary(text);
|
||||
} else {
|
||||
websocket.sendText(text);
|
||||
try {
|
||||
websocket.sendText(DECODER.decode(text).toString());
|
||||
} catch (CharacterCodingException e) {
|
||||
// This shouldn't happen, but worth mentioning.
|
||||
throw new LuaException("Message is not valid UTF8");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -51,15 +51,15 @@ class WebsocketHandler extends SimpleChannelInboundHandler<Object> {
|
||||
|
||||
var frame = (WebSocketFrame) msg;
|
||||
if (frame instanceof TextWebSocketFrame textFrame) {
|
||||
var data = textFrame.text();
|
||||
var data = NetworkUtils.toBytes(textFrame.content());
|
||||
|
||||
websocket.environment().observe(Metrics.WEBSOCKET_INCOMING, data.length());
|
||||
websocket.environment().observe(Metrics.WEBSOCKET_INCOMING, data.length);
|
||||
websocket.environment().queueEvent(MESSAGE_EVENT, websocket.address(), data, false);
|
||||
} else if (frame instanceof BinaryWebSocketFrame) {
|
||||
var converted = NetworkUtils.toBytes(frame.content());
|
||||
var data = NetworkUtils.toBytes(frame.content());
|
||||
|
||||
websocket.environment().observe(Metrics.WEBSOCKET_INCOMING, converted.length);
|
||||
websocket.environment().queueEvent(MESSAGE_EVENT, websocket.address(), converted, true);
|
||||
websocket.environment().observe(Metrics.WEBSOCKET_INCOMING, data.length);
|
||||
websocket.environment().queueEvent(MESSAGE_EVENT, websocket.address(), data, true);
|
||||
} else if (frame instanceof CloseWebSocketFrame closeFrame) {
|
||||
websocket.close(closeFrame.statusCode(), closeFrame.reasonText());
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@
|
||||
package dan200.computercraft.core.apis.transfer;
|
||||
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.core.apis.handles.BinaryReadableHandle;
|
||||
import dan200.computercraft.core.apis.handles.ReadHandle;
|
||||
import dan200.computercraft.core.methods.ObjectSource;
|
||||
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
@@ -15,19 +15,19 @@ import java.util.Optional;
|
||||
/**
|
||||
* A binary file handle that has been transferred to this computer.
|
||||
* <p>
|
||||
* This inherits all methods of {@link BinaryReadableHandle binary file handles}, meaning you can use the standard
|
||||
* {@link BinaryReadableHandle#read(Optional) read functions} to access the contents of the file.
|
||||
* This inherits all methods of {@link ReadHandle binary file handles}, meaning you can use the standard
|
||||
* {@link ReadHandle#read(Optional) read functions} to access the contents of the file.
|
||||
*
|
||||
* @cc.module [kind=event] file_transfer.TransferredFile
|
||||
* @see BinaryReadableHandle
|
||||
* @see ReadHandle
|
||||
*/
|
||||
public class TransferredFile implements ObjectSource {
|
||||
private final String name;
|
||||
private final BinaryReadableHandle handle;
|
||||
private final ReadHandle handle;
|
||||
|
||||
public TransferredFile(String name, SeekableByteChannel contents) {
|
||||
this.name = name;
|
||||
handle = BinaryReadableHandle.of(contents);
|
||||
handle = new ReadHandle(contents, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user