mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-10-15 14:07:38 +00:00
Compare commits
31 Commits
mc-1.20.y
...
v1.20.1-1.
Author | SHA1 | Date | |
---|---|---|---|
![]() |
08d4f91c8b | ||
![]() |
b9eac4e509 | ||
![]() |
dc3d8ea198 | ||
![]() |
cbe075b001 | ||
![]() |
ed0b156e05 | ||
![]() |
4dd0735066 | ||
![]() |
38e516d7c7 | ||
![]() |
70a31855ac | ||
![]() |
6c8e64ffcd | ||
![]() |
7285c32d58 | ||
![]() |
99c60ac54b | ||
![]() |
63e40cf3cb | ||
![]() |
1d45935a25 | ||
![]() |
f80373e7a2 | ||
![]() |
63185629b7 | ||
![]() |
4bfb9ac323 | ||
![]() |
5926b6c994 | ||
![]() |
f5ed43584d | ||
![]() |
d77f5f135f | ||
![]() |
7744d2663b | ||
![]() |
4566cb8273 | ||
![]() |
052e7a7ae5 | ||
![]() |
0895200681 | ||
![]() |
09d0f563b7 | ||
![]() |
e188f1d3fa | ||
![]() |
819a4f7231 | ||
![]() |
898cb2a95d | ||
![]() |
03a8f83191 | ||
![]() |
aef92c8ebc | ||
![]() |
571ea794a8 | ||
![]() |
e81af93043 |
6
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
6
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@@ -8,10 +8,8 @@ body:
|
||||
label: Minecraft Version
|
||||
description: What version of Minecraft are you using?
|
||||
options:
|
||||
- 1.16.x
|
||||
- 1.18.x
|
||||
- 1.19.x
|
||||
- 1.20.x
|
||||
- 1.20.1
|
||||
- 1.21.x
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
|
@@ -27,7 +27,7 @@ repos:
|
||||
exclude: "^(.*\\.(bat)|LICENSE)$"
|
||||
|
||||
- repo: https://github.com/fsfe/reuse-tool
|
||||
rev: v2.1.0
|
||||
rev: v4.0.3
|
||||
hooks:
|
||||
- id: reuse
|
||||
|
||||
|
99
.reuse/dep5
99
.reuse/dep5
@@ -1,99 +0,0 @@
|
||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Source: https://github.com/cc-tweaked/cc-tweaked
|
||||
Upstream-Name: CC: Tweaked
|
||||
Upstream-Contact: Jonathan Coates <git@squiddev.cc>
|
||||
|
||||
Files:
|
||||
projects/common/src/main/resources/assets/computercraft/sounds.json
|
||||
projects/common/src/main/resources/assets/computercraft/sounds/empty.ogg
|
||||
projects/common/src/testMod/resources/data/cctest/computercraft/turtle_upgrades/*
|
||||
projects/common/src/testMod/resources/data/cctest/structures/*
|
||||
projects/*/src/generated/*
|
||||
projects/web/src/htmlTransform/export/index.json
|
||||
projects/web/src/htmlTransform/export/items/minecraft/*
|
||||
Comment: Generated/data files are CC0.
|
||||
Copyright: The CC: Tweaked Developers
|
||||
License: CC0-1.0
|
||||
|
||||
Files:
|
||||
doc/images/*
|
||||
package.json
|
||||
package-lock.json
|
||||
projects/common/src/client/resources/computercraft-client.mixins.json
|
||||
projects/common/src/main/resources/assets/minecraft/shaders/core/computercraft/monitor_tbo.json
|
||||
projects/common/src/main/resources/computercraft.mixins.json
|
||||
projects/common/src/testMod/resources/computercraft-gametest.mixins.json
|
||||
projects/common/src/testMod/resources/data/computercraft/loot_tables/treasure_disk.json
|
||||
projects/common/src/testMod/resources/pack.mcmeta
|
||||
projects/core/src/main/resources/data/computercraft/lua/rom/modules/command/.ignoreme
|
||||
projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/.ignoreme
|
||||
projects/core/src/main/resources/data/computercraft/lua/rom/modules/turtle/.ignoreme
|
||||
projects/core/src/main/resources/data/computercraft/lua/rom/motd.txt
|
||||
projects/fabric-api/src/main/modJson/fabric.mod.json
|
||||
projects/fabric/src/client/resources/computercraft-client.fabric.mixins.json
|
||||
projects/fabric/src/main/resources/computercraft.fabric.mixins.json
|
||||
projects/fabric/src/main/resources/fabric.mod.json
|
||||
projects/fabric/src/testMod/resources/computercraft-gametest.fabric.mixins.json
|
||||
projects/fabric/src/testMod/resources/fabric.mod.json
|
||||
projects/forge/src/client/resources/computercraft-client.forge.mixins.json
|
||||
projects/web/src/frontend/mount/.settings
|
||||
projects/web/src/frontend/mount/example.nfp
|
||||
projects/web/src/frontend/mount/example.nft
|
||||
projects/web/src/frontend/mount/expr_template.lua
|
||||
projects/web/tsconfig.json
|
||||
Comment: Several assets where it's inconvenient to create a .license file.
|
||||
Copyright: The CC: Tweaked Developers
|
||||
License: MPL-2.0
|
||||
|
||||
Files:
|
||||
doc/logo.png
|
||||
doc/logo-darkmode.png
|
||||
projects/common/src/main/resources/assets/computercraft/models/*
|
||||
projects/common/src/main/resources/assets/computercraft/textures/*
|
||||
projects/common/src/main/resources/pack.mcmeta
|
||||
projects/common/src/main/resources/pack.png
|
||||
projects/core/src/main/resources/assets/computercraft/textures/gui/term_font.png
|
||||
projects/core/src/main/resources/data/computercraft/lua/rom/autorun/.ignoreme
|
||||
projects/core/src/main/resources/data/computercraft/lua/rom/help/*
|
||||
projects/core/src/main/resources/data/computercraft/lua/rom/programs/fun/advanced/levels/*
|
||||
projects/web/src/htmlTransform/export/items/computercraft/*
|
||||
Comment: Bulk-license original assets as CCPL.
|
||||
Copyright: 2011 Daniel Ratcliffe
|
||||
License: LicenseRef-CCPL
|
||||
|
||||
Files:
|
||||
projects/common/src/main/resources/assets/computercraft/lang/cs_cz.json
|
||||
projects/common/src/main/resources/assets/computercraft/lang/ko_kr.json
|
||||
projects/common/src/main/resources/assets/computercraft/lang/pl_pl.json
|
||||
projects/common/src/main/resources/assets/computercraft/lang/pt_br.json
|
||||
projects/common/src/main/resources/assets/computercraft/lang/ru_ru.json
|
||||
projects/common/src/main/resources/assets/computercraft/lang/uk_ua.json
|
||||
projects/common/src/main/resources/assets/computercraft/lang/zh_cn.json
|
||||
Comment: Community-contributed license files
|
||||
Copyright: 2017 The CC: Tweaked Developers
|
||||
License: LicenseRef-CCPL
|
||||
|
||||
Files:
|
||||
projects/common/src/main/resources/assets/computercraft/lang/*
|
||||
Comment: Community-contributed license files
|
||||
Copyright: 2017 The CC: Tweaked Developers
|
||||
License: MPL-2.0
|
||||
|
||||
Files:
|
||||
.github/*
|
||||
Comment:
|
||||
GitHub build scripts are CC0. While we could add a header to each file,
|
||||
it's unclear if it will break actions or issue templates in some way.
|
||||
Copyright: Jonathan Coates <git@squiddev.cc>
|
||||
License: CC0-1.0
|
||||
|
||||
Files:
|
||||
gradle/wrapper/*
|
||||
gradlew
|
||||
gradlew.bat
|
||||
Copyright: Gradle Inc
|
||||
License: Apache-2.0
|
||||
|
||||
Files: projects/core/src/test/resources/test-rom/data/json-parsing/*
|
||||
Copyright: 2016 Nicolas Seriot
|
||||
License: MIT
|
@@ -12,7 +12,6 @@ If you've any other questions, [just ask the community][community] or [open an i
|
||||
|
||||
## Table of Contents
|
||||
- [Reporting issues](#reporting-issues)
|
||||
- [Translations](#translations)
|
||||
- [Setting up a development environment](#setting-up-a-development-environment)
|
||||
- [Developing CC: Tweaked](#developing-cc-tweaked)
|
||||
- [Writing documentation](#writing-documentation)
|
||||
@@ -21,17 +20,13 @@ If you've any other questions, [just ask the community][community] or [open an i
|
||||
If you have a bug, suggestion, or other feedback, the best thing to do is [file an issue][new-issue]. When doing so, do
|
||||
use the issue templates - they provide a useful hint on what information to provide.
|
||||
|
||||
## Translations
|
||||
Translations are managed through [Weblate], an online interface for managing language strings. This is synced
|
||||
automatically with GitHub, so please don't submit PRs adding/changing translations!
|
||||
|
||||
## Setting up a development environment
|
||||
In order to develop CC: Tweaked, you'll need to download the source code and then run it.
|
||||
|
||||
- Make sure you've got the following software installed:
|
||||
- Java Development Kit (JDK). This can be downloaded from [Adoptium].
|
||||
- Java Development Kit 17 (JDK). This can be downloaded from [Adoptium].
|
||||
- [Git](https://git-scm.com/).
|
||||
- [NodeJS][node].
|
||||
- [NodeJS 20 or later][node].
|
||||
|
||||
- Download CC: Tweaked's source code:
|
||||
```
|
||||
@@ -101,7 +96,6 @@ about how you can build on that until you've covered everything!
|
||||
[community]: README.md#community "Get in touch with the community."
|
||||
[Adoptium]: https://adoptium.net/temurin/releases?version=17 "Download OpenJDK 17"
|
||||
[illuaminate]: https://github.com/SquidDev/illuaminate/ "Illuaminate on GitHub"
|
||||
[weblate]: https://i18n.tweaked.cc/projects/cc-tweaked/minecraft/ "CC: Tweaked weblate instance"
|
||||
[docs]: https://tweaked.cc/ "CC: Tweaked documentation"
|
||||
[ldoc]: http://stevedonovan.github.io/ldoc/ "ldoc, a Lua documentation generator."
|
||||
[mc-test]: https://www.youtube.com/watch?v=vXaWOJTCYNg
|
||||
|
11
README.md
11
README.md
@@ -26,8 +26,9 @@ developing the mod, [check out the instructions here](CONTRIBUTING.md#developing
|
||||
|
||||
## Community
|
||||
If you need help getting started with CC: Tweaked, want to show off your latest project, or just want to chat about
|
||||
ComputerCraft, do check out our [forum] and [GitHub discussions page][GitHub discussions]! There's also a fairly
|
||||
populated, albeit quiet [IRC channel][irc], if that's more your cup of tea.
|
||||
ComputerCraft, do check out our [GitHub discussions page][GitHub discussions]! There's also a fairly populated,
|
||||
albeit quiet IRC channel on [EsperNet], if that's more your cup of tea. You can join `#computercraft` through your
|
||||
desktop client, or online using [KiwiIRC].
|
||||
|
||||
We also host fairly comprehensive documentation at [tweaked.cc](https://tweaked.cc/ "The CC: Tweaked website").
|
||||
|
||||
@@ -39,7 +40,7 @@ on is present.
|
||||
```groovy
|
||||
repositories {
|
||||
maven {
|
||||
url "https://squiddev.cc/maven/"
|
||||
url "https://maven.squiddev.cc"
|
||||
content {
|
||||
includeGroup("cc.tweaked")
|
||||
}
|
||||
@@ -86,6 +87,6 @@ the generated documentation [can be browsed online](https://tweaked.cc/javadoc/)
|
||||
[modrinth]: https://modrinth.com/mod/gu7yAYhd "Download CC: Tweaked from Modrinth"
|
||||
[Minecraft Forge]: https://files.minecraftforge.net/ "Download Minecraft Forge."
|
||||
[Fabric]: https://fabricmc.net/use/installer/ "Download Fabric."
|
||||
[forum]: https://forums.computercraft.cc/
|
||||
[GitHub Discussions]: https://github.com/cc-tweaked/CC-Tweaked/discussions
|
||||
[IRC]: https://webchat.esper.net/?channels=computercraft "#computercraft on EsperNet"
|
||||
[EsperNet]: https://www.esper.net/
|
||||
[KiwiIRC]: https://kiwiirc.com/nextclient/#irc://irc.esper.net:+6697/#computercraft "#computercraft on EsperNet"
|
||||
|
110
REUSE.toml
Normal file
110
REUSE.toml
Normal file
@@ -0,0 +1,110 @@
|
||||
# SPDX-FileCopyrightText: 2017 The CC: Tweaked Developers
|
||||
#
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
version = 1
|
||||
SPDX-PackageName = "CC: Tweaked"
|
||||
SPDX-PackageSupplier = "Jonathan Coates <git@squiddev.cc>"
|
||||
SPDX-PackageDownloadLocation = "https://github.com/cc-tweaked/cc-tweaked"
|
||||
|
||||
[[annotations]]
|
||||
# Generated/data files are CC0.
|
||||
SPDX-FileCopyrightText = "The CC: Tweaked Developers"
|
||||
SPDX-License-Identifier = "CC0-1.0"
|
||||
path = [
|
||||
"gradle/gradle-daemon-jvm.properties",
|
||||
"projects/common/src/main/resources/assets/computercraft/sounds.json",
|
||||
"projects/common/src/main/resources/assets/computercraft/sounds/empty.ogg",
|
||||
"projects/common/src/testMod/resources/data/cctest/computercraft/turtle_upgrades/**",
|
||||
"projects/common/src/testMod/resources/data/cctest/structures/**",
|
||||
"projects/**/src/generated/**",
|
||||
"projects/web/src/htmlTransform/export/index.json",
|
||||
"projects/web/src/htmlTransform/export/items/minecraft/**",
|
||||
]
|
||||
|
||||
[[annotations]]
|
||||
# Several assets where it's inconvenient to create a .license file.
|
||||
SPDX-FileCopyrightText = "The CC: Tweaked Developers"
|
||||
SPDX-License-Identifier = "MPL-2.0"
|
||||
path = [
|
||||
"doc/images/**",
|
||||
"package.json",
|
||||
"package-lock.json",
|
||||
"projects/common/src/client/resources/computercraft-client.mixins.json",
|
||||
"projects/common/src/main/resources/assets/minecraft/shaders/core/computercraft/monitor_tbo.json",
|
||||
"projects/common/src/main/resources/computercraft.mixins.json",
|
||||
"projects/common/src/testMod/resources/computercraft-gametest.mixins.json",
|
||||
"projects/common/src/testMod/resources/data/computercraft/loot_tables/treasure_disk.json",
|
||||
"projects/common/src/testMod/resources/pack.mcmeta",
|
||||
"projects/core/src/main/resources/data/computercraft/lua/rom/modules/command/.ignoreme",
|
||||
"projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/.ignoreme",
|
||||
"projects/core/src/main/resources/data/computercraft/lua/rom/modules/turtle/.ignoreme",
|
||||
"projects/core/src/main/resources/data/computercraft/lua/rom/motd.txt",
|
||||
"projects/fabric-api/src/main/modJson/fabric.mod.json",
|
||||
"projects/fabric/src/client/resources/computercraft-client.fabric.mixins.json",
|
||||
"projects/fabric/src/main/resources/computercraft.fabric.mixins.json",
|
||||
"projects/fabric/src/main/resources/fabric.mod.json",
|
||||
"projects/fabric/src/testMod/resources/computercraft-gametest.fabric.mixins.json",
|
||||
"projects/fabric/src/testMod/resources/fabric.mod.json",
|
||||
"projects/forge/src/client/resources/computercraft-client.forge.mixins.json",
|
||||
"projects/web/src/frontend/mount/.settings",
|
||||
"projects/web/src/frontend/mount/example.nfp",
|
||||
"projects/web/src/frontend/mount/example.nft",
|
||||
"projects/web/src/frontend/mount/expr_template.lua",
|
||||
"projects/web/tsconfig.json",
|
||||
]
|
||||
|
||||
[[annotations]]
|
||||
# Bulk-license original assets as CCPL.
|
||||
SPDX-FileCopyrightText = "2011 Daniel Ratcliffe"
|
||||
SPDX-License-Identifier = "LicenseRef-CCPL"
|
||||
path = [
|
||||
"doc/logo.png",
|
||||
"doc/logo-darkmode.png",
|
||||
"projects/common/src/main/resources/assets/computercraft/models/**",
|
||||
"projects/common/src/main/resources/assets/computercraft/textures/**",
|
||||
"projects/common/src/main/resources/pack.mcmeta",
|
||||
"projects/common/src/main/resources/pack.png",
|
||||
"projects/core/src/main/resources/assets/computercraft/textures/gui/term_font.png",
|
||||
"projects/core/src/main/resources/data/computercraft/lua/rom/autorun/.ignoreme",
|
||||
"projects/core/src/main/resources/data/computercraft/lua/rom/help/**",
|
||||
"projects/core/src/main/resources/data/computercraft/lua/rom/programs/fun/advanced/levels/**",
|
||||
"projects/web/src/htmlTransform/export/items/computercraft/**",
|
||||
]
|
||||
|
||||
[[annotations]]
|
||||
# Community-contributed license files
|
||||
SPDX-FileCopyrightText = "2017 The CC: Tweaked Developers"
|
||||
SPDX-License-Identifier = "LicenseRef-CCPL"
|
||||
path = [
|
||||
"projects/common/src/main/resources/assets/computercraft/lang/cs_cz.json",
|
||||
"projects/common/src/main/resources/assets/computercraft/lang/ko_kr.json",
|
||||
"projects/common/src/main/resources/assets/computercraft/lang/pl_pl.json",
|
||||
"projects/common/src/main/resources/assets/computercraft/lang/pt_br.json",
|
||||
"projects/common/src/main/resources/assets/computercraft/lang/ru_ru.json",
|
||||
"projects/common/src/main/resources/assets/computercraft/lang/uk_ua.json",
|
||||
"projects/common/src/main/resources/assets/computercraft/lang/zh_cn.json",
|
||||
]
|
||||
|
||||
[[annotations]]
|
||||
# Community-contributed license files
|
||||
SPDX-FileCopyrightText = "2017 The CC: Tweaked Developers"
|
||||
SPDX-License-Identifier = "MPL-2.0"
|
||||
path = "projects/common/src/main/resources/assets/computercraft/lang/**"
|
||||
|
||||
[[annotations]]
|
||||
# GitHub build scripts are CC0. While we could add a header to each file,
|
||||
# it's unclear if it will break actions or issue templates in some way.
|
||||
SPDX-FileCopyrightText = "Jonathan Coates <git@squiddev.cc>"
|
||||
SPDX-License-Identifier = "CC0-1.0"
|
||||
path = ".github/**"
|
||||
|
||||
[[annotations]]
|
||||
path = ["gradle/wrapper/**", "gradlew", "gradlew.bat"]
|
||||
SPDX-FileCopyrightText = "Gradle Inc"
|
||||
SPDX-License-Identifier = "Apache-2.0"
|
||||
|
||||
[[annotations]]
|
||||
path = "projects/core/src/test/resources/test-rom/data/json-parsing/**"
|
||||
SPDX-FileCopyrightText = "2016 Nicolas Seriot"
|
||||
SPDX-License-Identifier = "MIT"
|
@@ -36,7 +36,7 @@ repositories {
|
||||
}
|
||||
}
|
||||
|
||||
maven("https://squiddev.cc/maven") {
|
||||
maven("https://maven.squiddev.cc") {
|
||||
name = "SquidDev"
|
||||
content {
|
||||
includeGroup("cc.tweaked.vanilla-extract")
|
||||
|
@@ -38,7 +38,7 @@ java {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
||||
val mainMaven = maven("https://squiddev.cc/maven") {
|
||||
val mainMaven = maven("https://maven.squiddev.cc/mirror") {
|
||||
name = "SquidDev"
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ repositories {
|
||||
filter {
|
||||
includeGroup("cc.tweaked")
|
||||
// Things we mirror
|
||||
includeGroup("com.simibubi.create")
|
||||
includeGroup("commoble.morered")
|
||||
includeGroup("dev.architectury")
|
||||
includeGroup("dev.emi")
|
||||
@@ -133,8 +134,8 @@ tasks.processResources {
|
||||
tasks.withType(AbstractArchiveTask::class.java).configureEach {
|
||||
isPreserveFileTimestamps = false
|
||||
isReproducibleFileOrder = true
|
||||
dirMode = Integer.valueOf("755", 8)
|
||||
fileMode = Integer.valueOf("664", 8)
|
||||
filePermissions {}
|
||||
dirPermissions {}
|
||||
}
|
||||
|
||||
tasks.jar {
|
||||
|
@@ -38,7 +38,7 @@ publishing {
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven("https://squiddev.cc/maven") {
|
||||
maven("https://maven.squiddev.cc") {
|
||||
name = "SquidDev"
|
||||
|
||||
credentials(PasswordCredentials::class)
|
||||
|
@@ -22,7 +22,6 @@ import org.gradle.api.tasks.SourceSet
|
||||
import org.gradle.api.tasks.bundling.Jar
|
||||
import org.gradle.api.tasks.compile.JavaCompile
|
||||
import org.gradle.api.tasks.javadoc.Javadoc
|
||||
import org.gradle.configurationcache.extensions.capitalized
|
||||
import org.gradle.language.base.plugins.LifecycleBasePlugin
|
||||
import org.gradle.language.jvm.tasks.ProcessResources
|
||||
import org.gradle.process.JavaForkOptions
|
||||
@@ -181,7 +180,7 @@ abstract class CCTweakedExtension(
|
||||
|
||||
fun <T> jacoco(task: NamedDomainObjectProvider<T>) where T : Task, T : JavaForkOptions {
|
||||
val classDump = project.layout.buildDirectory.dir("jacocoClassDump/${task.name}")
|
||||
val reportTaskName = "jacoco${task.name.capitalized()}Report"
|
||||
val reportTaskName = "jacoco${task.name.capitalise()}Report"
|
||||
|
||||
val jacoco = project.extensions.getByType(JacocoPluginExtension::class.java)
|
||||
task.configure {
|
||||
|
@@ -155,3 +155,15 @@ fun getNextVersion(version: String): String {
|
||||
if (dashIndex >= 0) out.append(version, dashIndex, version.length)
|
||||
return out.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Capitalise the first letter of the string.
|
||||
*
|
||||
* This is a replacement for the now deprecated [String.capitalize].
|
||||
*/
|
||||
fun String.capitalise(): String {
|
||||
if (isEmpty()) return this
|
||||
val first = this[0]
|
||||
val firstTitle = first.titlecaseChar()
|
||||
return if (first == firstTitle) this else firstTitle + substring(1)
|
||||
}
|
||||
|
@@ -191,7 +191,7 @@ end
|
||||
|
||||
> [Confused?][!NOTE]
|
||||
> Don't worry if you don't understand this example. It's quite advanced, and does use some ideas that this guide doesn't
|
||||
> cover. That said, don't be afraid to ask on [GitHub Discussions] or [IRC] either!
|
||||
> cover. That said, don't be afraid to ask [the community for help][community].
|
||||
|
||||
It's worth noting that the examples of audio processing we've mentioned here are about manipulating the _amplitude_ of
|
||||
the wave. If you wanted to modify the _frequency_ (for instance, shifting the pitch), things get rather more complex.
|
||||
@@ -205,5 +205,4 @@ This is, I'm afraid, left as an exercise to the reader.
|
||||
[PCM]: https://en.wikipedia.org/wiki/Pulse-code_modulation "Pulse-code Modulation - Wikipedia"
|
||||
[Ring Buffer]: https://en.wikipedia.org/wiki/Circular_buffer "Circular buffer - Wikipedia"
|
||||
[Sine Wave]: https://en.wikipedia.org/wiki/Sine_wave "Sine wave - Wikipedia"
|
||||
[GitHub Discussions]: https://github.com/cc-tweaked/CC-Tweaked/discussions
|
||||
[IRC]: https://webchat.esper.net/?channels=computercraft "#computercraft on EsperNet"
|
||||
[Community]: /#community
|
||||
|
@@ -50,7 +50,11 @@ little daunting getting started. Thankfully, there's several fantastic tutorials
|
||||
Once you're a little more familiar with the mod, the sidebar and links below provide more detailed documentation on the
|
||||
various APIs and peripherals provided by the mod.
|
||||
|
||||
If you get stuck, do [ask a question on GitHub][GitHub Discussions] or pop in to the ComputerCraft's [IRC channel][IRC].
|
||||
<h2 id="community">Community</h2>
|
||||
If you need help getting started with CC: Tweaked, want to show off your latest project, or just want to chat about
|
||||
ComputerCraft, do check out our [GitHub discussions page][GitHub discussions]! There's also a fairly populated,
|
||||
albeit quiet IRC channel on [EsperNet], if that's more your cup of tea. You can join `#computercraft` through your
|
||||
desktop client, or online using [KiwiIRC].
|
||||
|
||||
## Get Involved
|
||||
CC: Tweaked lives on [GitHub]. If you've got any ideas, feedback or bugs please do [create an issue][bug].
|
||||
@@ -65,4 +69,5 @@ CC: Tweaked lives on [GitHub]. If you've got any ideas, feedback or bugs please
|
||||
[Fabric]: https://fabricmc.net/use/installer/ "Download Fabric."
|
||||
[lua]: https://www.lua.org/ "Lua's main website"
|
||||
[GitHub Discussions]: https://github.com/cc-tweaked/CC-Tweaked/discussions
|
||||
[IRC]: https://webchat.esper.net/?channels=computercraft "#computercraft on EsperNet"
|
||||
[EsperNet]: https://www.esper.net/
|
||||
[KiwiIRC]: https://kiwiirc.com/nextclient/#irc://irc.esper.net:+6697/#computercraft "#computercraft on EsperNet"
|
||||
|
@@ -50,7 +50,11 @@ little daunting getting started. Thankfully, there's several fantastic tutorials
|
||||
Once you're a little more familiar with the mod, the [wiki](https://tweaked.cc/) provides more detailed documentation on the
|
||||
various APIs and peripherals provided by the mod.
|
||||
|
||||
If you get stuck, do [ask a question on GitHub][GitHub Discussions] or pop in to the ComputerCraft's [IRC channel][IRC].
|
||||
## Community
|
||||
If you need help getting started with CC: Tweaked, want to show off your latest project, or just want to chat about
|
||||
ComputerCraft, do check out our [GitHub discussions page][GitHub discussions]! There's also a fairly populated,
|
||||
albeit quiet IRC channel on [EsperNet], if that's more your cup of tea. You can join `#computercraft` through your
|
||||
desktop client, or online using [KiwiIRC].
|
||||
|
||||
## Get Involved
|
||||
CC: Tweaked lives on [GitHub]. If you've got any ideas, feedback or bugs please do [create an issue][bug].
|
||||
@@ -60,4 +64,5 @@ CC: Tweaked lives on [GitHub]. If you've got any ideas, feedback or bugs please
|
||||
[computercraft]: https://github.com/dan200/ComputerCraft "ComputerCraft on GitHub"
|
||||
[lua]: https://www.lua.org/ "Lua's main website"
|
||||
[GitHub Discussions]: https://github.com/cc-tweaked/CC-Tweaked/discussions
|
||||
[IRC]: http://webchat.esper.net/?channels=computercraft "#computercraft on EsperNet"
|
||||
[EsperNet]: https://www.esper.net/
|
||||
[KiwiIRC]: https://kiwiirc.com/nextclient/#irc://irc.esper.net:+6697/#computercraft "#computercraft on EsperNet"
|
||||
|
@@ -25,13 +25,13 @@ as documentation for breaking changes and "gotchas" one should look out for betw
|
||||
|
||||
- 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
|
||||
- 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.
|
||||
- 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.
|
||||
|
||||
@@ -44,7 +44,7 @@ as documentation for breaking changes and "gotchas" one should look out for betw
|
||||
`keys.enter` constant was queued when the key was pressed)
|
||||
|
||||
- Minecraft 1.13 removed the concept of item damage and block metadata (see ["The Flattening"][flattening]). As a
|
||||
result `turtle.inspect` no longer provides block metadata, and `turtle.getItemDetail` no longer provides damage.
|
||||
result [`turtle.inspect`] no longer provides block metadata, and [`turtle.getItemDetail`] no longer provides damage.
|
||||
|
||||
- Block states (`turtle.inspect().state`) should provide all the same information as block metadata, but in a much
|
||||
more understandable format.
|
||||
@@ -70,7 +70,7 @@ as documentation for breaking changes and "gotchas" one should look out for betw
|
||||
- Unlabelled computers and turtles now keep their ID when broken, meaning that unlabelled computers/items do not stack.
|
||||
|
||||
## ComputerCraft 1.80pr1 {#cc-1.80}
|
||||
- Programs run via `shell.run` are now started in their own isolated environment. This means globals set by programs
|
||||
- Programs run via [`shell.run`] are now started in their own isolated environment. This means globals set by programs
|
||||
will not be accessible outside of this program.
|
||||
|
||||
- Programs containing `/` are looked up in the current directory and are no longer looked up on the path. For instance,
|
||||
|
@@ -10,7 +10,7 @@ kotlin.jvm.target.validation.mode=error
|
||||
|
||||
# Mod properties
|
||||
isUnstable=false
|
||||
modVersion=1.111.0
|
||||
modVersion=1.112.0
|
||||
|
||||
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
|
||||
mcVersion=1.20.1
|
||||
|
2
gradle/gradle-daemon-jvm.properties
Normal file
2
gradle/gradle-daemon-jvm.properties
Normal file
@@ -0,0 +1,2 @@
|
||||
#This file is generated by updateDaemonJvm
|
||||
toolchainVersion=17
|
@@ -46,6 +46,8 @@ oculus = "1.2.5"
|
||||
rei = "12.0.626"
|
||||
rubidium = "0.6.1"
|
||||
sodium = "mc1.20-0.4.10"
|
||||
create-forge = "0.5.1.f-33"
|
||||
create-fabric = "0.5.1-f-build.1467+mc1.20.1"
|
||||
|
||||
# Testing
|
||||
hamcrest = "2.2"
|
||||
@@ -59,7 +61,7 @@ checkstyle = "10.14.1"
|
||||
curseForgeGradle = "1.0.14"
|
||||
errorProne-core = "2.27.0"
|
||||
errorProne-plugin = "3.1.0"
|
||||
fabric-loom = "1.6.7"
|
||||
fabric-loom = "1.7.1"
|
||||
forgeGradle = "6.0.21"
|
||||
githubRelease = "2.5.2"
|
||||
gradleVersions = "0.50.0"
|
||||
@@ -71,7 +73,7 @@ minotaur = "2.+"
|
||||
nullAway = "0.10.25"
|
||||
spotless = "6.23.3"
|
||||
taskTree = "2.1.1"
|
||||
teavm = "0.10.0-SQUID.4"
|
||||
teavm = "0.11.0-SQUID.1"
|
||||
vanillaExtract = "0.1.3"
|
||||
versionCatalogUpdate = "0.8.1"
|
||||
|
||||
@@ -100,11 +102,13 @@ nightConfig-toml = { module = "com.electronwill.night-config:toml", version.ref
|
||||
slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
|
||||
|
||||
# Minecraft mods
|
||||
fabric-api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabric-api" }
|
||||
fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" }
|
||||
fabric-junit = { module = "net.fabricmc:fabric-loader-junit", version.ref = "fabric-loader" }
|
||||
fabricPermissions = { module = "me.lucko:fabric-permissions-api", version.ref = "fabricPermissions" }
|
||||
create-fabric = { module = "com.simibubi.create:create-fabric-1.20.1", version.ref = "create-fabric" }
|
||||
create-forge = { module = "com.simibubi.create:create-1.20.1", version.ref = "create-forge" }
|
||||
emi = { module = "dev.emi:emi-xplat-mojmap", version.ref = "emi" }
|
||||
fabric-api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabric-api" }
|
||||
fabric-junit = { module = "net.fabricmc:fabric-loader-junit", version.ref = "fabric-loader" }
|
||||
fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" }
|
||||
fabricPermissions = { module = "me.lucko:fabric-permissions-api", version.ref = "fabricPermissions" }
|
||||
iris = { module = "maven.modrinth:iris", version.ref = "iris" }
|
||||
jei-api = { module = "mezz.jei:jei-1.20.1-common-api", version.ref = "jei" }
|
||||
jei-fabric = { module = "mezz.jei:jei-1.20.1-fabric", version.ref = "jei" }
|
||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
2
gradlew
vendored
2
gradlew
vendored
@@ -55,7 +55,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
|
@@ -4,9 +4,11 @@
|
||||
|
||||
package dan200.computercraft.api;
|
||||
|
||||
import dan200.computercraft.api.component.ComputerComponent;
|
||||
import dan200.computercraft.api.filesystem.Mount;
|
||||
import dan200.computercraft.api.filesystem.WritableMount;
|
||||
import dan200.computercraft.api.lua.GenericSource;
|
||||
import dan200.computercraft.api.lua.IComputerSystem;
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.lua.ILuaAPIFactory;
|
||||
import dan200.computercraft.api.media.IMedia;
|
||||
@@ -165,7 +167,20 @@ public final class ComputerCraftAPI {
|
||||
* Register a custom {@link ILuaAPI}, which may be added onto all computers without requiring a peripheral.
|
||||
* <p>
|
||||
* Before implementing this interface, consider alternative methods of providing methods. It is generally preferred
|
||||
* to use peripherals to provide functionality to users.
|
||||
* to use peripherals to provide functionality to users. If an API is <em>required</em>, you may want to consider
|
||||
* using {@link ILuaAPI#getModuleName()} to expose this library as a module instead of as a global.
|
||||
* <p>
|
||||
* This may be used with {@link IComputerSystem#getComponent(ComputerComponent)} to only attach APIs to specific
|
||||
* computers. For example, one can add an additional API just to turtles with the following code:
|
||||
*
|
||||
* <pre>{@code
|
||||
* ComputerCraftAPI.registerAPIFactory(computer -> {
|
||||
* // Read the turtle component.
|
||||
* var turtle = computer.getComponent(ComputerComponents.TURTLE);
|
||||
* // If present then add our API.
|
||||
* return turtle == null ? null : new MyCustomTurtleApi(turtle);
|
||||
* });
|
||||
* }</pre>
|
||||
*
|
||||
* @param factory The factory for your API subclass.
|
||||
* @see ILuaAPIFactory
|
||||
|
@@ -0,0 +1,24 @@
|
||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.api.component;
|
||||
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
/**
|
||||
* A computer which has permission to perform administrative/op commands, such as the command computer.
|
||||
*/
|
||||
@ApiStatus.NonExtendable
|
||||
public interface AdminComputer {
|
||||
/**
|
||||
* The permission level that this computer can operate at.
|
||||
*
|
||||
* @return The permission level for this computer.
|
||||
* @see CommandSourceStack#hasPermission(int)
|
||||
*/
|
||||
default int permissionLevel() {
|
||||
return 2;
|
||||
}
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.api.component;
|
||||
|
||||
import dan200.computercraft.api.lua.IComputerSystem;
|
||||
import dan200.computercraft.api.lua.ILuaAPIFactory;
|
||||
|
||||
/**
|
||||
* A component attached to a computer.
|
||||
* <p>
|
||||
* Components provide a mechanism to attach additional data to a computer, that can then be queried with
|
||||
* {@link IComputerSystem#getComponent(ComputerComponent)}.
|
||||
* <p>
|
||||
* This is largely designed for {@linkplain ILuaAPIFactory custom APIs}, allowing APIs to read additional properties
|
||||
* of the computer, such as its position.
|
||||
*
|
||||
* @param <T> The type of this component.
|
||||
* @see ComputerComponents The built-in components.
|
||||
*/
|
||||
@SuppressWarnings("UnusedTypeParameter")
|
||||
public final class ComputerComponent<T> {
|
||||
private final String id;
|
||||
|
||||
private ComputerComponent(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new computer component.
|
||||
* <p>
|
||||
* Mods typically will not need to create their own components.
|
||||
*
|
||||
* @param namespace The namespace of this component. This should be the mod id.
|
||||
* @param id The unique id of this component.
|
||||
* @param <T> The component
|
||||
* @return The newly created component.
|
||||
*/
|
||||
public static <T> ComputerComponent<T> create(String namespace, String id) {
|
||||
return new ComputerComponent<>(namespace + ":" + id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ComputerComponent(" + id + ")";
|
||||
}
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.api.component;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.pocket.IPocketAccess;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
|
||||
/**
|
||||
* The {@link ComputerComponent}s provided by ComputerCraft.
|
||||
*/
|
||||
public class ComputerComponents {
|
||||
/**
|
||||
* The {@link ITurtleAccess} associated with a turtle.
|
||||
*/
|
||||
public static final ComputerComponent<ITurtleAccess> TURTLE = ComputerComponent.create(ComputerCraftAPI.MOD_ID, "turtle");
|
||||
|
||||
/**
|
||||
* The {@link IPocketAccess} associated with a pocket computer.
|
||||
*/
|
||||
public static final ComputerComponent<IPocketAccess> POCKET = ComputerComponent.create(ComputerCraftAPI.MOD_ID, "pocket");
|
||||
|
||||
/**
|
||||
* This component is only present on "command computers", and other computers with admin capabilities.
|
||||
*/
|
||||
public static final ComputerComponent<AdminComputer> ADMIN_COMPUTER = ComputerComponent.create(ComputerCraftAPI.MOD_ID, "admin_computer");
|
||||
}
|
@@ -0,0 +1,59 @@
|
||||
// SPDX-FileCopyrightText: 2017 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.api.lua;
|
||||
|
||||
import dan200.computercraft.api.component.ComputerComponent;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* An interface passed to {@link ILuaAPIFactory} in order to provide additional information
|
||||
* about a computer.
|
||||
*/
|
||||
@ApiStatus.NonExtendable
|
||||
public interface IComputerSystem extends IComputerAccess {
|
||||
/**
|
||||
* Get the level this computer is currently in.
|
||||
* <p>
|
||||
* This method is not guaranteed to remain the same (even for stationary computers).
|
||||
*
|
||||
* @return The computer's current level.
|
||||
*/
|
||||
ServerLevel getLevel();
|
||||
|
||||
/**
|
||||
* Get the position this computer is currently at.
|
||||
* <p>
|
||||
* This method is not guaranteed to remain the same (even for stationary computers).
|
||||
*
|
||||
* @return The computer's current position.
|
||||
*/
|
||||
BlockPos getPosition();
|
||||
|
||||
/**
|
||||
* Get the label for this computer.
|
||||
*
|
||||
* @return This computer's label, or {@code null} if it is not set.
|
||||
*/
|
||||
@Nullable
|
||||
String getLabel();
|
||||
|
||||
/**
|
||||
* Get a component attached to this computer.
|
||||
* <p>
|
||||
* No component is guaranteed to be on a computer, and so this method should always be guarded with a null check.
|
||||
* <p>
|
||||
* This method will always return the same value for a given component, and so may be cached.
|
||||
*
|
||||
* @param component The component to query.
|
||||
* @param <T> The type of the component.
|
||||
* @return The component, if present.
|
||||
*/
|
||||
<T> @Nullable T getComponent(ComputerComponent<T> component);
|
||||
}
|
@@ -4,13 +4,15 @@
|
||||
|
||||
package dan200.computercraft.api.lua;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Construct an {@link ILuaAPI} for a specific computer.
|
||||
* Construct an {@link ILuaAPI} for a computer.
|
||||
*
|
||||
* @see ILuaAPI
|
||||
* @see dan200.computercraft.api.ComputerCraftAPI#registerAPIFactory(ILuaAPIFactory)
|
||||
* @see ComputerCraftAPI#registerAPIFactory(ILuaAPIFactory)
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ILuaAPIFactory {
|
@@ -6,10 +6,14 @@ package dan200.computercraft.api.pocket;
|
||||
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.api.upgrades.UpgradeBase;
|
||||
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Map;
|
||||
@@ -17,7 +21,22 @@ import java.util.Map;
|
||||
/**
|
||||
* Wrapper class for pocket computers.
|
||||
*/
|
||||
@ApiStatus.NonExtendable
|
||||
public interface IPocketAccess {
|
||||
/**
|
||||
* Get the level in which the pocket computer exists.
|
||||
*
|
||||
* @return The pocket computer's level.
|
||||
*/
|
||||
ServerLevel getLevel();
|
||||
|
||||
/**
|
||||
* Get the position of the pocket computer.
|
||||
*
|
||||
* @return The pocket computer's position.
|
||||
*/
|
||||
Vec3 getPosition();
|
||||
|
||||
/**
|
||||
* Gets the entity holding this item.
|
||||
* <p>
|
||||
@@ -64,6 +83,26 @@ public interface IPocketAccess {
|
||||
*/
|
||||
void setLight(int colour);
|
||||
|
||||
/**
|
||||
* Get the currently equipped upgrade.
|
||||
*
|
||||
* @return The currently equipped upgrade.
|
||||
* @see #getUpgradeNBTData()
|
||||
* @see #setUpgrade(UpgradeData)
|
||||
*/
|
||||
@Nullable
|
||||
UpgradeData<IPocketUpgrade> getUpgrade();
|
||||
|
||||
/**
|
||||
* Set the upgrade for this pocket computer, also updating the item stack.
|
||||
* <p>
|
||||
* Note this method is not thread safe - it must be called from the server thread.
|
||||
*
|
||||
* @param upgrade The new upgrade to set it to, may be {@code null}.
|
||||
* @see #getUpgrade()
|
||||
*/
|
||||
void setUpgrade(@Nullable UpgradeData<IPocketUpgrade> upgrade);
|
||||
|
||||
/**
|
||||
* Get the upgrade-specific NBT.
|
||||
* <p>
|
||||
@@ -73,6 +112,7 @@ public interface IPocketAccess {
|
||||
* @see #updateUpgradeNBTData()
|
||||
* @see UpgradeBase#getUpgradeItem(CompoundTag)
|
||||
* @see UpgradeBase#getUpgradeData(ItemStack)
|
||||
* @see #getUpgrade()
|
||||
*/
|
||||
CompoundTag getUpgradeNBTData();
|
||||
|
||||
|
@@ -43,6 +43,7 @@ dependencies {
|
||||
clientImplementation(clientClasses(project(":common-api")))
|
||||
|
||||
compileOnly(libs.bundles.externalMods.common)
|
||||
compileOnly(variantOf(libs.create.forge) { classifier("slim") }) { isTransitive = false }
|
||||
clientCompileOnly(variantOf(libs.emi) { classifier("api") })
|
||||
|
||||
annotationProcessorEverywhere(libs.autoService)
|
||||
|
@@ -25,7 +25,6 @@ import dan200.computercraft.shared.common.IColouredItem;
|
||||
import dan200.computercraft.shared.computer.core.ComputerState;
|
||||
import dan200.computercraft.shared.computer.core.ServerContext;
|
||||
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
|
||||
import dan200.computercraft.shared.computer.inventory.ViewComputerMenu;
|
||||
import dan200.computercraft.shared.media.items.DiskItem;
|
||||
import dan200.computercraft.shared.media.items.TreasureDiskItem;
|
||||
import net.minecraft.Util;
|
||||
@@ -83,7 +82,6 @@ public final class ClientRegistry {
|
||||
*/
|
||||
public static void registerMainThread(RegisterItemProperty itemProperties) {
|
||||
MenuScreens.<AbstractComputerMenu, ComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.COMPUTER.get(), ComputerScreen::new);
|
||||
MenuScreens.<AbstractComputerMenu, ComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.POCKET_COMPUTER.get(), ComputerScreen::new);
|
||||
MenuScreens.<AbstractComputerMenu, NoTermComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.POCKET_COMPUTER_NO_TERM.get(), NoTermComputerScreen::new);
|
||||
MenuScreens.register(ModRegistry.Menus.TURTLE.get(), TurtleScreen::new);
|
||||
|
||||
@@ -91,8 +89,6 @@ public final class ClientRegistry {
|
||||
MenuScreens.register(ModRegistry.Menus.DISK_DRIVE.get(), DiskDriveScreen::new);
|
||||
MenuScreens.register(ModRegistry.Menus.PRINTOUT.get(), PrintoutScreen::new);
|
||||
|
||||
MenuScreens.<ViewComputerMenu, ComputerScreen<ViewComputerMenu>>register(ModRegistry.Menus.VIEW_COMPUTER.get(), ComputerScreen::new);
|
||||
|
||||
registerItemProperty(itemProperties, "state",
|
||||
new UnclampedPropertyFunction((stack, world, player, random) -> {
|
||||
var computer = ClientPocketComputers.get(stack);
|
||||
|
@@ -2,7 +2,7 @@
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.integration.jei;
|
||||
package dan200.computercraft.client.integration.jei;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
@@ -2,7 +2,7 @@
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.integration.jei;
|
||||
package dan200.computercraft.client.integration.jei;
|
||||
|
||||
import dan200.computercraft.shared.integration.UpgradeRecipeGenerator;
|
||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
@@ -14,7 +14,6 @@ import java.util.Objects;
|
||||
/**
|
||||
* The global factory for {@link ILuaAPIFactory}s.
|
||||
*
|
||||
* @see dan200.computercraft.core.ComputerContext.Builder#apiFactories(Collection)
|
||||
* @see dan200.computercraft.api.ComputerCraftAPI#registerAPIFactory(ILuaAPIFactory)
|
||||
*/
|
||||
public final class ApiFactories {
|
||||
|
@@ -6,6 +6,7 @@ package dan200.computercraft.shared;
|
||||
|
||||
import com.mojang.brigadier.arguments.ArgumentType;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.component.ComputerComponents;
|
||||
import dan200.computercraft.api.detail.DetailProvider;
|
||||
import dan200.computercraft.api.detail.VanillaDetailRegistries;
|
||||
import dan200.computercraft.api.media.IMedia;
|
||||
@@ -23,13 +24,12 @@ import dan200.computercraft.shared.common.ClearColourRecipe;
|
||||
import dan200.computercraft.shared.common.ColourableRecipe;
|
||||
import dan200.computercraft.shared.common.DefaultBundledRedstoneProvider;
|
||||
import dan200.computercraft.shared.common.HeldItemMenu;
|
||||
import dan200.computercraft.shared.computer.apis.CommandAPI;
|
||||
import dan200.computercraft.shared.computer.blocks.CommandComputerBlock;
|
||||
import dan200.computercraft.shared.computer.blocks.CommandComputerBlockEntity;
|
||||
import dan200.computercraft.shared.computer.blocks.ComputerBlock;
|
||||
import dan200.computercraft.shared.computer.blocks.ComputerBlockEntity;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.inventory.ComputerMenuWithoutInventory;
|
||||
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;
|
||||
@@ -66,6 +66,7 @@ import dan200.computercraft.shared.peripheral.speaker.SpeakerBlockEntity;
|
||||
import dan200.computercraft.shared.platform.PlatformHelper;
|
||||
import dan200.computercraft.shared.platform.RegistrationHelper;
|
||||
import dan200.computercraft.shared.platform.RegistryEntry;
|
||||
import dan200.computercraft.shared.pocket.apis.PocketAPI;
|
||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
||||
import dan200.computercraft.shared.pocket.peripherals.PocketModem;
|
||||
import dan200.computercraft.shared.pocket.peripherals.PocketSpeaker;
|
||||
@@ -75,14 +76,17 @@ import dan200.computercraft.shared.recipe.CustomShapelessRecipe;
|
||||
import dan200.computercraft.shared.recipe.ImpostorShapedRecipe;
|
||||
import dan200.computercraft.shared.recipe.ImpostorShapelessRecipe;
|
||||
import dan200.computercraft.shared.turtle.FurnaceRefuelHandler;
|
||||
import dan200.computercraft.shared.turtle.apis.TurtleAPI;
|
||||
import dan200.computercraft.shared.turtle.blocks.TurtleBlock;
|
||||
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
|
||||
import dan200.computercraft.shared.turtle.core.TurtleAccessInternal;
|
||||
import dan200.computercraft.shared.turtle.inventory.TurtleMenu;
|
||||
import dan200.computercraft.shared.turtle.items.TurtleItem;
|
||||
import dan200.computercraft.shared.turtle.recipes.TurtleOverlayRecipe;
|
||||
import dan200.computercraft.shared.turtle.recipes.TurtleRecipe;
|
||||
import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe;
|
||||
import dan200.computercraft.shared.turtle.upgrades.*;
|
||||
import dan200.computercraft.shared.util.ComponentMap;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
|
||||
import net.minecraft.commands.synchronization.SingletonArgumentInfo;
|
||||
@@ -104,6 +108,7 @@ import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.material.MapColor;
|
||||
import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@@ -142,8 +147,7 @@ public final class ModRegistry {
|
||||
() -> 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), BlockEntities.COMPUTER_ADVANCED));
|
||||
|
||||
public static final RegistryEntry<ComputerBlock<CommandComputerBlockEntity>> COMPUTER_COMMAND = REGISTRY.register("computer_command",
|
||||
public static final RegistryEntry<ComputerBlock<ComputerBlockEntity>> 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",
|
||||
@@ -186,8 +190,8 @@ public final class ModRegistry {
|
||||
ofBlock(Blocks.COMPUTER_NORMAL, (p, s) -> new ComputerBlockEntity(BlockEntities.COMPUTER_NORMAL.get(), p, s, ComputerFamily.NORMAL));
|
||||
public static final RegistryEntry<BlockEntityType<ComputerBlockEntity>> COMPUTER_ADVANCED =
|
||||
ofBlock(Blocks.COMPUTER_ADVANCED, (p, s) -> new ComputerBlockEntity(BlockEntities.COMPUTER_ADVANCED.get(), p, s, ComputerFamily.ADVANCED));
|
||||
public static final RegistryEntry<BlockEntityType<CommandComputerBlockEntity>> COMPUTER_COMMAND =
|
||||
ofBlock(Blocks.COMPUTER_COMMAND, (p, s) -> new CommandComputerBlockEntity(BlockEntities.COMPUTER_COMMAND.get(), p, s));
|
||||
public static final RegistryEntry<BlockEntityType<ComputerBlockEntity>> COMPUTER_COMMAND =
|
||||
ofBlock(Blocks.COMPUTER_COMMAND, (p, s) -> new ComputerBlockEntity(BlockEntities.COMPUTER_COMMAND.get(), p, s, ComputerFamily.COMMAND));
|
||||
|
||||
public static final RegistryEntry<BlockEntityType<TurtleBlockEntity>> TURTLE_NORMAL =
|
||||
ofBlock(Blocks.TURTLE_NORMAL, (p, s) -> new TurtleBlockEntity(BlockEntities.TURTLE_NORMAL.get(), p, s, () -> Config.turtleFuelLimit, ComputerFamily.NORMAL));
|
||||
@@ -293,9 +297,6 @@ public final class ModRegistry {
|
||||
public static final RegistryEntry<MenuType<ComputerMenuWithoutInventory>> COMPUTER = REGISTRY.register("computer",
|
||||
() -> ContainerData.toType(ComputerContainerData::new, (id, inv, data) -> new ComputerMenuWithoutInventory(Menus.COMPUTER.get(), id, inv, data)));
|
||||
|
||||
public static final RegistryEntry<MenuType<ComputerMenuWithoutInventory>> POCKET_COMPUTER = REGISTRY.register("pocket_computer",
|
||||
() -> ContainerData.toType(ComputerContainerData::new, (id, inv, data) -> new ComputerMenuWithoutInventory(Menus.POCKET_COMPUTER.get(), id, inv, data)));
|
||||
|
||||
public static final RegistryEntry<MenuType<ComputerMenuWithoutInventory>> POCKET_COMPUTER_NO_TERM = REGISTRY.register("pocket_computer_no_term",
|
||||
() -> ContainerData.toType(ComputerContainerData::new, (id, inv, data) -> new ComputerMenuWithoutInventory(Menus.POCKET_COMPUTER_NO_TERM.get(), id, inv, data)));
|
||||
|
||||
@@ -313,9 +314,6 @@ public final class ModRegistry {
|
||||
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));
|
||||
}
|
||||
|
||||
static class ArgumentTypes {
|
||||
@@ -456,6 +454,22 @@ public final class ModRegistry {
|
||||
return null;
|
||||
});
|
||||
|
||||
ComputerCraftAPI.registerAPIFactory(computer -> {
|
||||
var turtle = computer.getComponent(ComputerComponents.TURTLE);
|
||||
var metrics = Objects.requireNonNull(computer.getComponent(ComponentMap.METRICS));
|
||||
return turtle == null ? null : new TurtleAPI(metrics, (TurtleAccessInternal) turtle);
|
||||
});
|
||||
|
||||
ComputerCraftAPI.registerAPIFactory(computer -> {
|
||||
var pocket = computer.getComponent(ComputerComponents.POCKET);
|
||||
return pocket == null ? null : new PocketAPI(pocket);
|
||||
});
|
||||
|
||||
ComputerCraftAPI.registerAPIFactory(computer -> {
|
||||
var admin = computer.getComponent(ComputerComponents.ADMIN_COMPUTER);
|
||||
return admin == null ? null : new CommandAPI(computer, admin);
|
||||
});
|
||||
|
||||
VanillaDetailRegistries.ITEM_STACK.addProvider(ItemDetails::fill);
|
||||
VanillaDetailRegistries.BLOCK_IN_WORLD.addProvider(BlockDetails::fill);
|
||||
}
|
||||
|
@@ -18,7 +18,7 @@ import dan200.computercraft.shared.command.text.TableBuilder;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import dan200.computercraft.shared.computer.core.ServerContext;
|
||||
import dan200.computercraft.shared.computer.inventory.ViewComputerMenu;
|
||||
import dan200.computercraft.shared.computer.inventory.ComputerMenuWithoutInventory;
|
||||
import dan200.computercraft.shared.computer.metrics.basic.Aggregate;
|
||||
import dan200.computercraft.shared.computer.metrics.basic.AggregatedMetric;
|
||||
import dan200.computercraft.shared.computer.metrics.basic.BasicComputerMetricsObserver;
|
||||
@@ -269,7 +269,7 @@ public final class CommandComputerCraft {
|
||||
|
||||
@Override
|
||||
public AbstractContainerMenu createMenu(int id, Inventory player, Player entity) {
|
||||
return new ViewComputerMenu(id, player, computer);
|
||||
return new ComputerMenuWithoutInventory(ModRegistry.Menus.COMPUTER.get(), id, player, p -> true, computer);
|
||||
}
|
||||
});
|
||||
return 1;
|
||||
|
@@ -6,19 +6,23 @@ package dan200.computercraft.shared.computer.apis;
|
||||
|
||||
import com.mojang.brigadier.tree.CommandNode;
|
||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||
import dan200.computercraft.api.component.AdminComputer;
|
||||
import dan200.computercraft.api.detail.BlockReference;
|
||||
import dan200.computercraft.api.detail.VanillaDetailRegistries;
|
||||
import dan200.computercraft.api.lua.*;
|
||||
import dan200.computercraft.core.Logging;
|
||||
import dan200.computercraft.shared.computer.blocks.CommandComputerBlockEntity;
|
||||
import dan200.computercraft.shared.util.NBTUtil;
|
||||
import net.minecraft.commands.CommandSource;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.GameRules;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.Vec2;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -31,10 +35,13 @@ import java.util.*;
|
||||
public class CommandAPI implements ILuaAPI {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CommandAPI.class);
|
||||
|
||||
private final CommandComputerBlockEntity computer;
|
||||
private final IComputerSystem computer;
|
||||
private final AdminComputer admin;
|
||||
private final OutputReceiver receiver = new OutputReceiver();
|
||||
|
||||
public CommandAPI(CommandComputerBlockEntity computer) {
|
||||
public CommandAPI(IComputerSystem computer, AdminComputer admin) {
|
||||
this.computer = computer;
|
||||
this.admin = admin;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -48,15 +55,14 @@ public class CommandAPI implements ILuaAPI {
|
||||
|
||||
private Object[] doCommand(String command) {
|
||||
var server = computer.getLevel().getServer();
|
||||
if (server == null || !server.isCommandBlockEnabled()) {
|
||||
if (!server.isCommandBlockEnabled()) {
|
||||
return new Object[]{ false, createOutput("Command blocks disabled by server") };
|
||||
}
|
||||
|
||||
var commandManager = server.getCommands();
|
||||
var receiver = computer.getReceiver();
|
||||
try {
|
||||
receiver.clearOutput();
|
||||
var result = commandManager.performPrefixedCommand(computer.getSource(), command);
|
||||
var result = commandManager.performPrefixedCommand(getSource(), command);
|
||||
return new Object[]{ result > 0, receiver.copyOutput(), result };
|
||||
} catch (Throwable t) {
|
||||
LOG.error(Logging.JAVA_ERROR, "Error running command.", t);
|
||||
@@ -134,7 +140,6 @@ public class CommandAPI implements ILuaAPI {
|
||||
public final List<String> list(IArguments args) throws LuaException {
|
||||
var server = computer.getLevel().getServer();
|
||||
|
||||
if (server == null) return List.of();
|
||||
CommandNode<CommandSourceStack> node = server.getCommands().getDispatcher().getRoot();
|
||||
for (var j = 0; j < args.count(); j++) {
|
||||
var name = args.getString(j);
|
||||
@@ -161,7 +166,7 @@ public class CommandAPI implements ILuaAPI {
|
||||
@LuaFunction
|
||||
public final Object[] getBlockPosition() {
|
||||
// This is probably safe to do on the Lua thread. Probably.
|
||||
var pos = computer.getBlockPos();
|
||||
var pos = computer.getPosition();
|
||||
return new Object[]{ pos.getX(), pos.getY(), pos.getZ() };
|
||||
}
|
||||
|
||||
@@ -186,7 +191,6 @@ public class CommandAPI implements ILuaAPI {
|
||||
* @throws LuaException If trying to get information about more than 4096 blocks.
|
||||
* @cc.since 1.76
|
||||
* @cc.changed 1.99 Added {@code dimension} argument.
|
||||
*
|
||||
* @cc.usage Print out all blocks in a cube around the computer.
|
||||
*
|
||||
* <pre>{@code
|
||||
@@ -220,7 +224,7 @@ public class CommandAPI implements ILuaAPI {
|
||||
Math.max(minY, maxY),
|
||||
Math.max(minZ, maxZ)
|
||||
);
|
||||
if (world == null || !world.isInWorldBounds(min) || !world.isInWorldBounds(max)) {
|
||||
if (!world.isInWorldBounds(min) || !world.isInWorldBounds(max)) {
|
||||
throw new LuaException("Co-ordinates out of range");
|
||||
}
|
||||
|
||||
@@ -265,10 +269,9 @@ public class CommandAPI implements ILuaAPI {
|
||||
}
|
||||
|
||||
private Level getLevel(Optional<String> id) throws LuaException {
|
||||
var currentLevel = (ServerLevel) computer.getLevel();
|
||||
if (currentLevel == null) throw new LuaException("No world exists");
|
||||
var currentLevel = computer.getLevel();
|
||||
|
||||
if (!id.isPresent()) return currentLevel;
|
||||
if (id.isEmpty()) return currentLevel;
|
||||
|
||||
var dimensionId = ResourceLocation.tryParse(id.get());
|
||||
if (dimensionId == null) throw new LuaException("Invalid dimension name");
|
||||
@@ -278,4 +281,52 @@ public class CommandAPI implements ILuaAPI {
|
||||
|
||||
return level;
|
||||
}
|
||||
|
||||
private CommandSourceStack getSource() {
|
||||
var name = "@";
|
||||
var label = computer.getLabel();
|
||||
if (label != null) name = label;
|
||||
|
||||
return new CommandSourceStack(receiver,
|
||||
Vec3.atCenterOf(computer.getPosition()), Vec2.ZERO,
|
||||
computer.getLevel(), admin.permissionLevel(),
|
||||
name, Component.literal(name),
|
||||
computer.getLevel().getServer(), null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link CommandSource} that consumes output messages and stores them to a list.
|
||||
*/
|
||||
private final class OutputReceiver implements CommandSource {
|
||||
private final List<String> output = new ArrayList<>();
|
||||
|
||||
void clearOutput() {
|
||||
output.clear();
|
||||
}
|
||||
|
||||
List<String> copyOutput() {
|
||||
return List.copyOf(output);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendSystemMessage(Component textComponent) {
|
||||
output.add(textComponent.getString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptsSuccess() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptsFailure() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldInformAdmins() {
|
||||
return computer.getLevel().getGameRules().getBoolean(GameRules.RULE_COMMANDBLOCKOUTPUT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -146,7 +146,7 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit
|
||||
super.setPlacedBy(world, pos, state, placer, stack);
|
||||
|
||||
var tile = world.getBlockEntity(pos);
|
||||
if (!world.isClientSide && tile instanceof IComputerBlockEntity computer && stack.getItem() instanceof IComputerItem item) {
|
||||
if (!world.isClientSide && tile instanceof AbstractComputerBlockEntity computer && stack.getItem() instanceof IComputerItem item) {
|
||||
|
||||
var id = item.getComputerID(stack);
|
||||
if (id != -1) computer.setComputerID(id);
|
||||
|
@@ -38,7 +38,7 @@ import javax.annotation.Nullable;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
public abstract class AbstractComputerBlockEntity extends BlockEntity implements IComputerBlockEntity, Nameable, MenuProvider {
|
||||
public abstract class AbstractComputerBlockEntity extends BlockEntity implements Nameable, MenuProvider {
|
||||
private static final String NBT_ID = "ComputerId";
|
||||
private static final String NBT_LABEL = "Label";
|
||||
private static final String NBT_ON = "On";
|
||||
@@ -81,7 +81,8 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
|
||||
}
|
||||
|
||||
public boolean isUsable(Player player) {
|
||||
return BaseContainerBlockEntity.canUnlock(player, lockCode, getDisplayName())
|
||||
return getFamily().checkUsable(player)
|
||||
&& BaseContainerBlockEntity.canUnlock(player, lockCode, getDisplayName())
|
||||
&& Container.stillValidBlockEntity(this, player, getInteractRange());
|
||||
}
|
||||
|
||||
@@ -299,17 +300,14 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
|
||||
for (var dir : DirectionUtil.FACINGS) updateRedstoneTo(dir);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getComputerID() {
|
||||
return computerID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final @Nullable String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void setComputerID(int id) {
|
||||
if (getLevel().isClientSide || computerID == id) return;
|
||||
|
||||
@@ -317,7 +315,6 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
|
||||
BlockEntityHelpers.updateBlock(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void setLabel(@Nullable String label) {
|
||||
if (getLevel().isClientSide || Objects.equals(this.label, label)) return;
|
||||
|
||||
@@ -327,7 +324,6 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
|
||||
BlockEntityHelpers.updateBlock(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComputerFamily getFamily() {
|
||||
return family;
|
||||
}
|
||||
|
@@ -15,7 +15,7 @@ import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
* @param <T> The type of the computer block entity.
|
||||
* @see dan200.computercraft.shared.computer.items.CommandComputerItem
|
||||
*/
|
||||
public class CommandComputerBlock<T extends CommandComputerBlockEntity> extends ComputerBlock<T> implements GameMasterBlock {
|
||||
public class CommandComputerBlock<T extends ComputerBlockEntity> extends ComputerBlock<T> implements GameMasterBlock {
|
||||
public CommandComputerBlock(Properties settings, RegistryEntry<BlockEntityType<T>> type) {
|
||||
super(settings, type);
|
||||
}
|
||||
|
@@ -1,114 +0,0 @@
|
||||
// Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
|
||||
//
|
||||
// SPDX-License-Identifier: LicenseRef-CCPL
|
||||
|
||||
package dan200.computercraft.shared.computer.blocks;
|
||||
|
||||
import dan200.computercraft.shared.computer.apis.CommandAPI;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import dan200.computercraft.shared.config.Config;
|
||||
import net.minecraft.commands.CommandSource;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.level.GameRules;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.Vec2;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class CommandComputerBlockEntity extends ComputerBlockEntity {
|
||||
public class CommandReceiver implements CommandSource {
|
||||
private final List<String> output = new ArrayList<>();
|
||||
|
||||
public void clearOutput() {
|
||||
output.clear();
|
||||
}
|
||||
|
||||
public List<String> copyOutput() {
|
||||
return new ArrayList<>(output);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendSystemMessage(Component textComponent) {
|
||||
output.add(textComponent.getString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptsSuccess() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptsFailure() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldInformAdmins() {
|
||||
return getLevel().getGameRules().getBoolean(GameRules.RULE_COMMANDBLOCKOUTPUT);
|
||||
}
|
||||
}
|
||||
|
||||
private final CommandReceiver receiver;
|
||||
|
||||
public CommandComputerBlockEntity(BlockEntityType<? extends ComputerBlockEntity> type, BlockPos pos, BlockState state) {
|
||||
super(type, pos, state, ComputerFamily.COMMAND);
|
||||
receiver = new CommandReceiver();
|
||||
}
|
||||
|
||||
public CommandReceiver getReceiver() {
|
||||
return receiver;
|
||||
}
|
||||
|
||||
public CommandSourceStack getSource() {
|
||||
var computer = getServerComputer();
|
||||
var name = "@";
|
||||
if (computer != null) {
|
||||
var label = computer.getLabel();
|
||||
if (label != null) name = label;
|
||||
}
|
||||
|
||||
return new CommandSourceStack(receiver,
|
||||
Vec3.atCenterOf(worldPosition), Vec2.ZERO,
|
||||
(ServerLevel) getLevel(), 2,
|
||||
name, Component.literal(name),
|
||||
getLevel().getServer(), null
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ServerComputer createComputer(int id) {
|
||||
var computer = super.createComputer(id);
|
||||
computer.addAPI(new CommandAPI(this));
|
||||
return computer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUsable(Player player) {
|
||||
return isCommandUsable(player) && super.isUsable(player);
|
||||
}
|
||||
|
||||
public static boolean isCommandUsable(Player player) {
|
||||
var server = player.getServer();
|
||||
if (server == null || !server.isCommandBlockEnabled()) {
|
||||
player.displayClientMessage(Component.translatable("advMode.notEnabled"), true);
|
||||
return false;
|
||||
} else if (!canUseCommandBlock(player)) {
|
||||
player.displayClientMessage(Component.translatable("advMode.notAllowed"), true);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean canUseCommandBlock(Player player) {
|
||||
return Config.commandRequireCreative ? player.canUseGameMasterBlocks() : player.hasPermissions(2);
|
||||
}
|
||||
}
|
@@ -12,6 +12,7 @@ import dan200.computercraft.shared.computer.core.ComputerState;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import dan200.computercraft.shared.computer.inventory.ComputerMenuWithoutInventory;
|
||||
import dan200.computercraft.shared.config.Config;
|
||||
import dan200.computercraft.shared.util.ComponentMap;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
@@ -34,7 +35,8 @@ public class ComputerBlockEntity extends AbstractComputerBlockEntity {
|
||||
protected ServerComputer createComputer(int id) {
|
||||
return new ServerComputer(
|
||||
(ServerLevel) getLevel(), getBlockPos(), id, label,
|
||||
getFamily(), Config.computerTermWidth, Config.computerTermHeight
|
||||
getFamily(), Config.computerTermWidth, Config.computerTermHeight,
|
||||
ComponentMap.empty()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -67,7 +69,7 @@ public class ComputerBlockEntity extends AbstractComputerBlockEntity {
|
||||
@Nullable
|
||||
@Override
|
||||
public AbstractContainerMenu createMenu(int id, Inventory inventory, Player player) {
|
||||
return new ComputerMenuWithoutInventory(ModRegistry.Menus.COMPUTER.get(), id, inventory, this::isUsableByPlayer, createServerComputer(), getFamily());
|
||||
return new ComputerMenuWithoutInventory(ModRegistry.Menus.COMPUTER.get(), id, inventory, this::isUsableByPlayer, createServerComputer());
|
||||
}
|
||||
|
||||
public IPeripheral peripheral() {
|
||||
|
@@ -1,22 +0,0 @@
|
||||
// Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
|
||||
//
|
||||
// SPDX-License-Identifier: LicenseRef-CCPL
|
||||
|
||||
package dan200.computercraft.shared.computer.blocks;
|
||||
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public interface IComputerBlockEntity {
|
||||
int getComputerID();
|
||||
|
||||
void setComputerID(int id);
|
||||
|
||||
@Nullable
|
||||
String getLabel();
|
||||
|
||||
void setLabel(@Nullable String label);
|
||||
|
||||
ComputerFamily getFamily();
|
||||
}
|
@@ -4,8 +4,45 @@
|
||||
|
||||
package dan200.computercraft.shared.computer.core;
|
||||
|
||||
import dan200.computercraft.shared.config.Config;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
|
||||
public enum ComputerFamily {
|
||||
NORMAL,
|
||||
ADVANCED,
|
||||
COMMAND,
|
||||
COMMAND;
|
||||
|
||||
/**
|
||||
* Check whether computers with this family can be used by the provided player.
|
||||
* <p>
|
||||
* This method is not pure. On failure, the method may send a message to the player telling them why they cannot
|
||||
* interact with the computer.
|
||||
*
|
||||
* @param player The player trying to use a computer.
|
||||
* @return Whether this computer family can be used.
|
||||
*/
|
||||
public boolean checkUsable(Player player) {
|
||||
return switch (this) {
|
||||
case NORMAL, ADVANCED -> true;
|
||||
case COMMAND -> checkCommandUsable(player);
|
||||
};
|
||||
}
|
||||
|
||||
private static boolean checkCommandUsable(Player player) {
|
||||
var server = player.getServer();
|
||||
if (server == null || !server.isCommandBlockEnabled()) {
|
||||
player.displayClientMessage(Component.translatable("advMode.notEnabled"), true);
|
||||
return false;
|
||||
} else if (!canUseCommandBlock(player)) {
|
||||
player.displayClientMessage(Component.translatable("advMode.notAllowed"), true);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean canUseCommandBlock(Player player) {
|
||||
return Config.commandRequireCreative ? player.canUseGameMasterBlocks() : player.hasPermissions(2);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,101 @@
|
||||
// SPDX-FileCopyrightText: 2019 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.computer.core;
|
||||
|
||||
import dan200.computercraft.api.component.ComputerComponent;
|
||||
import dan200.computercraft.api.lua.IComputerSystem;
|
||||
import dan200.computercraft.api.lua.ILuaAPIFactory;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.core.apis.ComputerAccess;
|
||||
import dan200.computercraft.core.apis.IAPIEnvironment;
|
||||
import dan200.computercraft.core.computer.ApiLifecycle;
|
||||
import dan200.computercraft.shared.util.ComponentMap;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Implementation of {@link IComputerSystem} for usage by externally registered APIs.
|
||||
*
|
||||
* @see ILuaAPIFactory
|
||||
*/
|
||||
final class ComputerSystem extends ComputerAccess implements IComputerSystem, ApiLifecycle {
|
||||
private final ServerComputer computer;
|
||||
private final IAPIEnvironment environment;
|
||||
private final ComponentMap components;
|
||||
|
||||
private boolean active;
|
||||
|
||||
ComputerSystem(ServerComputer computer, IAPIEnvironment environment, ComponentMap components) {
|
||||
super(environment);
|
||||
this.computer = computer;
|
||||
this.environment = environment;
|
||||
this.components = components;
|
||||
}
|
||||
|
||||
void activate() {
|
||||
active = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
unmountAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAttachmentName() {
|
||||
return "computer";
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerLevel getLevel() {
|
||||
if (!active) {
|
||||
throw new IllegalStateException("""
|
||||
Cannot access level when constructing the API. Computers are not guaranteed to stay in one place and
|
||||
APIs should not rely on the level remaining constant. Instead, call this method when needed.
|
||||
""".replace('\n', ' ').strip()
|
||||
);
|
||||
}
|
||||
return computer.getLevel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockPos getPosition() {
|
||||
if (!active) {
|
||||
throw new IllegalStateException("""
|
||||
Cannot access computer position when constructing the API. Computers are not guaranteed to stay in one
|
||||
place and APIs should not rely on the position remaining constant. Instead, call this method when
|
||||
needed.
|
||||
""".replace('\n', ' ').strip()
|
||||
);
|
||||
}
|
||||
return computer.getPosition();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getLabel() {
|
||||
return environment.getLabel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, IPeripheral> getAvailablePeripherals() {
|
||||
// TODO: Should this return peripherals on the current computer?
|
||||
return Map.of();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IPeripheral getAvailablePeripheral(String name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> @Nullable T getComponent(ComputerComponent<T> component) {
|
||||
return components.get(component);
|
||||
}
|
||||
}
|
@@ -7,7 +7,6 @@ package dan200.computercraft.shared.computer.core;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import dan200.computercraft.api.filesystem.FileOperationException;
|
||||
import dan200.computercraft.core.filesystem.ArchiveMount;
|
||||
import dan200.computercraft.core.filesystem.FileSystem;
|
||||
import net.minecraft.ResourceLocationException;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
@@ -65,9 +64,10 @@ public final class ResourceMount extends ArchiveMount<ResourceMount.FileEntry> {
|
||||
existingNamespace = file.getNamespace();
|
||||
|
||||
if (!file.getNamespace().equals(namespace)) continue;
|
||||
if (!FileSystem.contains(subPath, file.getPath())) continue; // Some packs seem to include the parent?
|
||||
|
||||
var localPath = FileSystem.toLocal(file.getPath(), subPath);
|
||||
var localPath = getLocalPath(file.getPath(), subPath);
|
||||
if (localPath == null) continue;
|
||||
|
||||
try {
|
||||
getOrCreateChild(newRoot, localPath, this::createEntry);
|
||||
} catch (ResourceLocationException e) {
|
||||
|
@@ -5,14 +5,16 @@
|
||||
package dan200.computercraft.shared.computer.core;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.component.AdminComputer;
|
||||
import dan200.computercraft.api.component.ComputerComponents;
|
||||
import dan200.computercraft.api.filesystem.WritableMount;
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.core.apis.IAPIEnvironment;
|
||||
import dan200.computercraft.api.peripheral.WorkMonitor;
|
||||
import dan200.computercraft.core.computer.Computer;
|
||||
import dan200.computercraft.core.computer.ComputerEnvironment;
|
||||
import dan200.computercraft.core.computer.ComputerSide;
|
||||
import dan200.computercraft.core.metrics.MetricsObserver;
|
||||
import dan200.computercraft.impl.ApiFactories;
|
||||
import dan200.computercraft.shared.computer.menu.ComputerMenu;
|
||||
import dan200.computercraft.shared.computer.terminal.NetworkedTerminal;
|
||||
import dan200.computercraft.shared.computer.terminal.TerminalState;
|
||||
@@ -21,8 +23,10 @@ import dan200.computercraft.shared.network.NetworkMessage;
|
||||
import dan200.computercraft.shared.network.client.ClientNetworkContext;
|
||||
import dan200.computercraft.shared.network.client.ComputerTerminalClientMessage;
|
||||
import dan200.computercraft.shared.network.server.ServerNetworking;
|
||||
import dan200.computercraft.shared.util.ComponentMap;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
@@ -47,7 +51,8 @@ public class ServerComputer implements InputHandler, ComputerEnvironment {
|
||||
private int ticksSincePing;
|
||||
|
||||
public ServerComputer(
|
||||
ServerLevel level, BlockPos position, int computerID, @Nullable String label, ComputerFamily family, int terminalWidth, int terminalHeight
|
||||
ServerLevel level, BlockPos position, int computerID, @Nullable String label, ComputerFamily family, int terminalWidth, int terminalHeight,
|
||||
ComponentMap baseComponents
|
||||
) {
|
||||
this.level = level;
|
||||
this.position = position;
|
||||
@@ -58,8 +63,27 @@ public class ServerComputer implements InputHandler, ComputerEnvironment {
|
||||
terminal = new NetworkedTerminal(terminalWidth, terminalHeight, family != ComputerFamily.NORMAL, this::markTerminalChanged);
|
||||
metrics = context.metrics().createMetricObserver(this);
|
||||
|
||||
var componentBuilder = ComponentMap.builder();
|
||||
componentBuilder.add(ComponentMap.METRICS, metrics);
|
||||
if (family == ComputerFamily.COMMAND) {
|
||||
componentBuilder.add(ComputerComponents.ADMIN_COMPUTER, new AdminComputer() {
|
||||
});
|
||||
}
|
||||
componentBuilder.add(baseComponents);
|
||||
var components = componentBuilder.build();
|
||||
|
||||
computer = new Computer(context.computerContext(), this, terminal, computerID);
|
||||
computer.setLabel(label);
|
||||
|
||||
// Load in the externally registered APIs.
|
||||
for (var factory : ApiFactories.getAll()) {
|
||||
var system = new ComputerSystem(this, computer.getAPIEnvironment(), components);
|
||||
var api = factory.create(system);
|
||||
if (api == null) continue;
|
||||
|
||||
system.activate();
|
||||
computer.addApi(api, system);
|
||||
}
|
||||
}
|
||||
|
||||
public ComputerFamily getFamily() {
|
||||
@@ -70,32 +94,20 @@ public class ServerComputer implements InputHandler, ComputerEnvironment {
|
||||
return level;
|
||||
}
|
||||
|
||||
public void setLevel(ServerLevel level) {
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
public BlockPos getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public void setPosition(BlockPos pos) {
|
||||
position = new BlockPos(pos);
|
||||
}
|
||||
|
||||
public IAPIEnvironment getAPIEnvironment() {
|
||||
return computer.getAPIEnvironment();
|
||||
}
|
||||
|
||||
public Computer getComputer() {
|
||||
return computer;
|
||||
public void setPosition(ServerLevel level, BlockPos pos) {
|
||||
this.level = level;
|
||||
position = pos.immutable();
|
||||
}
|
||||
|
||||
protected void markTerminalChanged() {
|
||||
terminalChanged.set(true);
|
||||
}
|
||||
|
||||
|
||||
public void tickServer() {
|
||||
protected void tickServer() {
|
||||
ticksSincePing++;
|
||||
computer.tick();
|
||||
if (terminalChanged.getAndSet(false)) onTerminalChanged();
|
||||
@@ -113,10 +125,15 @@ public class ServerComputer implements InputHandler, ComputerEnvironment {
|
||||
ticksSincePing = 0;
|
||||
}
|
||||
|
||||
public boolean hasTimedOut() {
|
||||
boolean hasTimedOut() {
|
||||
return ticksSincePing > 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a bitmask returning which sides on the computer have changed, resetting the internal state.
|
||||
*
|
||||
* @return What sides on the computer have changed.
|
||||
*/
|
||||
public int pollAndResetChanges() {
|
||||
return computer.pollAndResetChanges();
|
||||
}
|
||||
@@ -135,6 +152,17 @@ public class ServerComputer implements InputHandler, ComputerEnvironment {
|
||||
ServerContext.get(level.getServer()).registry().remove(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this computer is usable by a player.
|
||||
*
|
||||
* @param player The player trying to use this computer.
|
||||
* @return Whether this computer can be used.
|
||||
*/
|
||||
public final boolean checkUsable(Player player) {
|
||||
return ServerContext.get(level.getServer()).registry().get(instanceUUID) == this
|
||||
&& getFamily().checkUsable(player);
|
||||
}
|
||||
|
||||
private void sendToAllInteracting(Function<AbstractContainerMenu, NetworkMessage<ClientNetworkContext>> createPacket) {
|
||||
var server = level.getServer();
|
||||
|
||||
@@ -175,25 +203,21 @@ public class ServerComputer implements InputHandler, ComputerEnvironment {
|
||||
|
||||
@Override
|
||||
public void turnOn() {
|
||||
// Turn on
|
||||
computer.turnOn();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
// Shutdown
|
||||
computer.shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reboot() {
|
||||
// Reboot
|
||||
computer.reboot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queueEvent(String event, @Nullable Object[] arguments) {
|
||||
// Queue event
|
||||
computer.queueEvent(event, arguments);
|
||||
}
|
||||
|
||||
@@ -213,10 +237,6 @@ public class ServerComputer implements InputHandler, ComputerEnvironment {
|
||||
computer.getEnvironment().setBundledRedstoneInput(side, combination);
|
||||
}
|
||||
|
||||
public void addAPI(ILuaAPI api) {
|
||||
computer.addApi(api);
|
||||
}
|
||||
|
||||
public void setPeripheral(ComputerSide side, @Nullable IPeripheral peripheral) {
|
||||
computer.getEnvironment().setPeripheral(side, peripheral);
|
||||
}
|
||||
@@ -245,6 +265,10 @@ public class ServerComputer implements InputHandler, ComputerEnvironment {
|
||||
return metrics;
|
||||
}
|
||||
|
||||
public WorkMonitor getMainThreadMonitor() {
|
||||
return computer.getMainThreadMonitor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable WritableMount createRootMount() {
|
||||
return ComputerCraftAPI.createSaveDirMount(level.getServer(), "computer/" + computer.getID(), Config.computerSpaceLimit);
|
||||
|
@@ -17,7 +17,6 @@ import dan200.computercraft.core.lua.ILuaMachine;
|
||||
import dan200.computercraft.core.methods.MethodSupplier;
|
||||
import dan200.computercraft.core.methods.PeripheralMethod;
|
||||
import dan200.computercraft.impl.AbstractComputerCraftAPI;
|
||||
import dan200.computercraft.impl.ApiFactories;
|
||||
import dan200.computercraft.impl.GenericSources;
|
||||
import dan200.computercraft.shared.CommonHooks;
|
||||
import dan200.computercraft.shared.computer.metrics.GlobalMetrics;
|
||||
@@ -74,7 +73,6 @@ public final class ServerContext {
|
||||
.computerThreads(ConfigSpec.computerThreads.get())
|
||||
.mainThreadScheduler(mainThread)
|
||||
.luaFactory(luaMachine)
|
||||
.apiFactories(ApiFactories.getAll())
|
||||
.genericMethods(GenericSources.getAllMethods())
|
||||
.build();
|
||||
idAssigner = new IDAssigner(storageDir.resolve("ids.json"));
|
||||
|
@@ -59,7 +59,7 @@ public abstract class AbstractComputerMenu extends AbstractContainerMenu impleme
|
||||
|
||||
@Override
|
||||
public boolean stillValid(Player player) {
|
||||
return canUse.test(player);
|
||||
return (computer == null || computer.checkUsable(player)) && canUse.test(player);
|
||||
}
|
||||
|
||||
public ComputerFamily getFamily() {
|
||||
|
@@ -4,7 +4,6 @@
|
||||
|
||||
package dan200.computercraft.shared.computer.inventory;
|
||||
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import dan200.computercraft.shared.container.InvisibleSlot;
|
||||
import dan200.computercraft.shared.network.container.ComputerContainerData;
|
||||
@@ -23,9 +22,9 @@ import java.util.function.Predicate;
|
||||
public class ComputerMenuWithoutInventory extends AbstractComputerMenu {
|
||||
public ComputerMenuWithoutInventory(
|
||||
MenuType<? extends AbstractComputerMenu> type, int id, Inventory player, Predicate<Player> canUse,
|
||||
ServerComputer computer, ComputerFamily family
|
||||
ServerComputer computer
|
||||
) {
|
||||
super(type, id, canUse, family, computer, null);
|
||||
super(type, id, canUse, computer.getFamily(), computer, null);
|
||||
addSlots(player);
|
||||
}
|
||||
|
||||
|
@@ -1,39 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2017 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.computer.inventory;
|
||||
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.computer.blocks.CommandComputerBlockEntity;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import dan200.computercraft.shared.computer.core.ServerContext;
|
||||
import dan200.computercraft.shared.network.container.ComputerContainerData;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
|
||||
|
||||
public class ViewComputerMenu extends ComputerMenuWithoutInventory {
|
||||
public ViewComputerMenu(int id, Inventory player, ServerComputer computer) {
|
||||
super(ModRegistry.Menus.VIEW_COMPUTER.get(), id, player, p -> canInteractWith(computer, p), computer, computer.getFamily());
|
||||
}
|
||||
|
||||
public ViewComputerMenu(int id, Inventory player, ComputerContainerData data) {
|
||||
super(ModRegistry.Menus.VIEW_COMPUTER.get(), id, player, data);
|
||||
}
|
||||
|
||||
private static boolean canInteractWith(ServerComputer computer, Player player) {
|
||||
// If this computer no longer exists then discard it.
|
||||
if (ServerContext.get(computer.getLevel().getServer()).registry().get(computer.getInstanceUUID()) != computer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we're a command computer then ensure we're in creative
|
||||
if (computer.getFamily() == ComputerFamily.COMMAND && !CommandComputerBlockEntity.isCommandUsable(player)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -5,7 +5,7 @@
|
||||
package dan200.computercraft.shared.data;
|
||||
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.computer.blocks.IComputerBlockEntity;
|
||||
import dan200.computercraft.shared.computer.blocks.AbstractComputerBlockEntity;
|
||||
import net.minecraft.world.level.storage.loot.LootContext;
|
||||
import net.minecraft.world.level.storage.loot.parameters.LootContextParam;
|
||||
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
|
||||
@@ -27,7 +27,7 @@ public final class HasComputerIdLootCondition implements LootItemCondition {
|
||||
@Override
|
||||
public boolean test(LootContext lootContext) {
|
||||
var tile = lootContext.getParamOrNull(LootContextParams.BLOCK_ENTITY);
|
||||
return tile instanceof IComputerBlockEntity computer && computer.getComputerID() >= 0;
|
||||
return tile instanceof AbstractComputerBlockEntity computer && computer.getComputerID() >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -0,0 +1,34 @@
|
||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.integration;
|
||||
|
||||
import com.simibubi.create.content.contraptions.BlockMovementChecks;
|
||||
import com.simibubi.create.content.contraptions.BlockMovementChecks.CheckResult;
|
||||
import dan200.computercraft.shared.peripheral.modem.wired.CableBlock;
|
||||
import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemBlock;
|
||||
|
||||
/**
|
||||
* Integration with Create.
|
||||
*/
|
||||
public final class CreateIntegration {
|
||||
public static final String ID = "create";
|
||||
|
||||
private CreateIntegration() {
|
||||
}
|
||||
|
||||
public static void setup() {
|
||||
// Allow modems to be treated as "attached" to their adjacent block.
|
||||
BlockMovementChecks.registerAttachedCheck((state, world, pos, direction) -> {
|
||||
var block = state.getBlock();
|
||||
if (block instanceof WirelessModemBlock) {
|
||||
return CheckResult.of(state.getValue(WirelessModemBlock.FACING) == direction);
|
||||
} else if (block instanceof CableBlock) {
|
||||
return CheckResult.of(state.getValue(CableBlock.MODEM).getFacing() == direction);
|
||||
} else {
|
||||
return CheckResult.PASS;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@@ -8,6 +8,7 @@ import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
/**
|
||||
* Tags defined by external mods.
|
||||
@@ -26,9 +27,9 @@ public final class ExternalModTags {
|
||||
/**
|
||||
* Create's "brittle" tag, used to determine if this block needs to be moved before its neighbours.
|
||||
*
|
||||
* @see <a href="https://github.com/Creators-of-Create/Create/blob/mc1.20.1/dev/src/main/java/com/simibubi/create/content/contraptions/BlockMovementChecks.java">{@code BlockMovementChecks}</a>
|
||||
* @see com.simibubi.create.content.contraptions.BlockMovementChecks#isBrittle(BlockState)
|
||||
*/
|
||||
public static final TagKey<Block> CREATE_BRITTLE = make("create", "brittle");
|
||||
public static final TagKey<Block> CREATE_BRITTLE = make(CreateIntegration.ID, "brittle");
|
||||
|
||||
private static TagKey<Block> make(String mod, String name) {
|
||||
return TagKey.create(Registries.BLOCK, new ResourceLocation(mod, name));
|
||||
|
@@ -27,7 +27,7 @@ public class PocketComputerDataMessage implements NetworkMessage<ClientNetworkCo
|
||||
public PocketComputerDataMessage(PocketServerComputer computer, boolean sendTerminal) {
|
||||
clientId = computer.getInstanceUUID();
|
||||
state = computer.getState();
|
||||
lightState = computer.getLight();
|
||||
lightState = computer.getBrain().getLight();
|
||||
terminal = sendTerminal ? computer.getTerminalState() : null;
|
||||
}
|
||||
|
||||
|
@@ -273,16 +273,18 @@ public abstract class SpeakerPeripheral implements IPeripheral {
|
||||
* Attempt to stream some audio data to the speaker.
|
||||
* <p>
|
||||
* This accepts a list of audio samples as amplitudes between -128 and 127. These are stored in an internal buffer
|
||||
* and played back at 48kHz. If this buffer is full, this function will return {@literal false}. You should wait for
|
||||
* a [`speaker_audio_empty`] event before trying again.
|
||||
* and played back at 48kHz. If this buffer is full, this function will return {@literal false}. Programs should
|
||||
* wait for a [`speaker_audio_empty`] event before trying to play audio again.
|
||||
* <p>
|
||||
* > [!NOTE]
|
||||
* > The speaker only buffers a single call to {@link #playAudio} at once. This means if you try to play a small
|
||||
* > number of samples, you'll have a lot of stutter. You should try to play as many samples in one call as possible
|
||||
* > (up to 128×1024), as this reduces the chances of audio stuttering or halting, especially when the server or
|
||||
* > computer is lagging.
|
||||
* The speaker only buffers a single call to {@link #playAudio} at once. This means if you try to play a small
|
||||
* number of samples, you'll have a lot of stutter. You should try to play as many samples in one call as possible
|
||||
* (up to 128×1024), as this reduces the chances of audio stuttering or halting, especially when the server or
|
||||
* computer is lagging.
|
||||
* <p>
|
||||
* [`speaker_audio`] provides a more complete guide to using speakers
|
||||
* While the speaker accepts 8-bit PCM audio, the audio stream is re-encoded before being played. This means that
|
||||
* the supplied samples may not be played out exactly.
|
||||
* <p>
|
||||
* [`speaker_audio`] provides a more complete guide to using speakers.
|
||||
*
|
||||
* @param context The Lua context.
|
||||
* @param audio The audio data to play.
|
||||
|
@@ -6,10 +6,10 @@ package dan200.computercraft.shared.pocket.apis;
|
||||
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.api.pocket.IPocketAccess;
|
||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
|
||||
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||
import dan200.computercraft.impl.PocketUpgrades;
|
||||
import dan200.computercraft.shared.pocket.core.PocketServerComputer;
|
||||
import net.minecraft.core.NonNullList;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
@@ -34,10 +34,10 @@ import java.util.Objects;
|
||||
* @cc.module pocket
|
||||
*/
|
||||
public class PocketAPI implements ILuaAPI {
|
||||
private final PocketServerComputer computer;
|
||||
private final IPocketAccess pocket;
|
||||
|
||||
public PocketAPI(PocketServerComputer computer) {
|
||||
this.computer = computer;
|
||||
public PocketAPI(IPocketAccess pocket) {
|
||||
this.pocket = pocket;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -56,10 +56,10 @@ public class PocketAPI implements ILuaAPI {
|
||||
*/
|
||||
@LuaFunction(mainThread = true)
|
||||
public final Object[] equipBack() {
|
||||
var entity = computer.getEntity();
|
||||
var entity = pocket.getEntity();
|
||||
if (!(entity instanceof Player player)) return new Object[]{ false, "Cannot find player" };
|
||||
var inventory = player.getInventory();
|
||||
var previousUpgrade = computer.getUpgrade();
|
||||
var previousUpgrade = pocket.getUpgrade();
|
||||
|
||||
// Attempt to find the upgrade, starting in the main segment, and then looking in the opposite
|
||||
// one. We start from the position the item is currently in and loop round to the start.
|
||||
@@ -73,7 +73,7 @@ public class PocketAPI implements ILuaAPI {
|
||||
if (previousUpgrade != null) storeItem(player, previousUpgrade.getUpgradeItem());
|
||||
|
||||
// Set the new upgrade
|
||||
computer.setUpgrade(newUpgrade);
|
||||
pocket.setUpgrade(newUpgrade);
|
||||
|
||||
return new Object[]{ true };
|
||||
}
|
||||
@@ -87,13 +87,13 @@ public class PocketAPI implements ILuaAPI {
|
||||
*/
|
||||
@LuaFunction(mainThread = true)
|
||||
public final Object[] unequipBack() {
|
||||
var entity = computer.getEntity();
|
||||
var entity = pocket.getEntity();
|
||||
if (!(entity instanceof Player player)) return new Object[]{ false, "Cannot find player" };
|
||||
var previousUpgrade = computer.getUpgrade();
|
||||
var previousUpgrade = pocket.getUpgrade();
|
||||
|
||||
if (previousUpgrade == null) return new Object[]{ false, "Nothing to unequip" };
|
||||
|
||||
computer.setUpgrade(null);
|
||||
pocket.setUpgrade(null);
|
||||
|
||||
storeItem(player, previousUpgrade.getUpgradeItem());
|
||||
|
||||
|
@@ -0,0 +1,184 @@
|
||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.pocket.core;
|
||||
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.api.pocket.IPocketAccess;
|
||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
|
||||
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||
import dan200.computercraft.core.computer.ComputerSide;
|
||||
import dan200.computercraft.shared.common.IColouredItem;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.network.client.PocketComputerDataMessage;
|
||||
import dan200.computercraft.shared.network.server.ServerNetworking;
|
||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Holds additional state for a pocket computer. This includes pocket computer upgrade,
|
||||
* {@linkplain IPocketAccess#getLight() light colour} and {@linkplain IPocketAccess#getColour() colour}.
|
||||
* <p>
|
||||
* This state is read when the brain is created, and written back to the holding item stack when the holding entity is
|
||||
* ticked (see {@link #updateItem(ItemStack)}).
|
||||
*/
|
||||
public final class PocketBrain implements IPocketAccess {
|
||||
private final PocketServerComputer computer;
|
||||
|
||||
private PocketHolder holder;
|
||||
private Vec3 position;
|
||||
|
||||
private boolean dirty = false;
|
||||
private @Nullable UpgradeData<IPocketUpgrade> upgrade;
|
||||
private int colour = -1;
|
||||
private int lightColour = -1;
|
||||
|
||||
public PocketBrain(PocketHolder holder, int computerID, @Nullable String label, ComputerFamily family, @Nullable UpgradeData<IPocketUpgrade> upgrade) {
|
||||
this.computer = new PocketServerComputer(this, holder, computerID, label, family);
|
||||
this.holder = holder;
|
||||
this.position = holder.pos();
|
||||
this.upgrade = UpgradeData.copyOf(upgrade);
|
||||
invalidatePeripheral();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the corresponding pocket computer for this brain.
|
||||
*
|
||||
* @return The pocket computer.
|
||||
*/
|
||||
public PocketServerComputer computer() {
|
||||
return computer;
|
||||
}
|
||||
|
||||
PocketHolder holder() {
|
||||
return holder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the position and holder for this computer.
|
||||
*
|
||||
* @param newHolder The new holder
|
||||
*/
|
||||
public void updateHolder(PocketHolder newHolder) {
|
||||
position = newHolder.pos();
|
||||
computer.setPosition(newHolder.level(), newHolder.blockPos());
|
||||
|
||||
var oldHolder = this.holder;
|
||||
if (holder.equals(newHolder)) return;
|
||||
holder = newHolder;
|
||||
|
||||
// If a new player has picked it up then rebroadcast the terminal to them
|
||||
var oldPlayer = oldHolder instanceof PocketHolder.PlayerHolder p ? p.entity() : null;
|
||||
if (newHolder instanceof PocketHolder.PlayerHolder player && player.entity() != oldPlayer) {
|
||||
ServerNetworking.sendToPlayer(new PocketComputerDataMessage(computer, true), player.entity());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write back properties of the pocket brain to the item.
|
||||
*
|
||||
* @param stack The pocket computer stack to update.
|
||||
* @return Whether the item was changed.
|
||||
*/
|
||||
public boolean updateItem(ItemStack stack) {
|
||||
if (!dirty) return false;
|
||||
this.dirty = false;
|
||||
|
||||
IColouredItem.setColourBasic(stack, colour);
|
||||
PocketComputerItem.setUpgrade(stack, UpgradeData.copyOf(upgrade));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerLevel getLevel() {
|
||||
return computer.getLevel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec3 getPosition() {
|
||||
// This method can be called from off-thread, and so we must use the cached position rather than rereading
|
||||
// from the holder.
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Entity getEntity() {
|
||||
return holder instanceof PocketHolder.EntityHolder entity && holder.isValid(computer) ? entity.entity() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColour() {
|
||||
return colour;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColour(int colour) {
|
||||
if (this.colour == colour) return;
|
||||
dirty = true;
|
||||
this.colour = colour;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLight() {
|
||||
return lightColour;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLight(int colour) {
|
||||
if (colour < 0 || colour > 0xFFFFFF) colour = -1;
|
||||
lightColour = colour;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag getUpgradeNBTData() {
|
||||
var upgrade = this.upgrade;
|
||||
return upgrade == null ? new CompoundTag() : upgrade.data();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateUpgradeNBTData() {
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidatePeripheral() {
|
||||
var peripheral = upgrade == null ? null : upgrade.upgrade().createPeripheral(this);
|
||||
computer.setPeripheral(ComputerSide.BACK, peripheral);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated(forRemoval = true)
|
||||
public Map<ResourceLocation, IPeripheral> getUpgrades() {
|
||||
var upgrade = this.upgrade;
|
||||
return upgrade == null ? Map.of() : Collections.singletonMap(upgrade.upgrade().getUpgradeID(), computer.getPeripheral(ComputerSide.BACK));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable UpgradeData<IPocketUpgrade> getUpgrade() {
|
||||
return upgrade;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the upgrade for this pocket computer, also updating the item stack.
|
||||
* <p>
|
||||
* Note this method is not thread safe - it must be called from the server thread.
|
||||
*
|
||||
* @param upgrade The new upgrade to set it to, may be {@code null}.
|
||||
*/
|
||||
@Override
|
||||
public void setUpgrade(@Nullable UpgradeData<IPocketUpgrade> upgrade) {
|
||||
this.upgrade = upgrade;
|
||||
dirty = true;
|
||||
invalidatePeripheral();
|
||||
}
|
||||
}
|
@@ -0,0 +1,115 @@
|
||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.pocket.core;
|
||||
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.item.ItemEntity;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
/**
|
||||
* An object that holds a pocket computer item.
|
||||
*/
|
||||
public sealed interface PocketHolder permits PocketHolder.EntityHolder {
|
||||
/**
|
||||
* The level this holder is in.
|
||||
*
|
||||
* @return The holder's level.
|
||||
*/
|
||||
ServerLevel level();
|
||||
|
||||
/**
|
||||
* The position of this holder.
|
||||
*
|
||||
* @return The position of this holder.
|
||||
*/
|
||||
Vec3 pos();
|
||||
|
||||
/**
|
||||
* The block position of this holder.
|
||||
*
|
||||
* @return The position of this holder.
|
||||
*/
|
||||
BlockPos blockPos();
|
||||
|
||||
/**
|
||||
* Determine if this holder is still valid for a particular computer.
|
||||
*
|
||||
* @param computer The current computer.
|
||||
* @return Whether this holder is valid.
|
||||
*/
|
||||
boolean isValid(ServerComputer computer);
|
||||
|
||||
/**
|
||||
* Mark the pocket computer item as having changed.
|
||||
*/
|
||||
void setChanged();
|
||||
|
||||
/**
|
||||
* An {@link Entity} holding a pocket computer.
|
||||
*/
|
||||
sealed interface EntityHolder extends PocketHolder permits PocketHolder.PlayerHolder, PocketHolder.ItemEntityHolder {
|
||||
/**
|
||||
* Get the entity holding this pocket computer.
|
||||
*
|
||||
* @return The holding entity.
|
||||
*/
|
||||
Entity entity();
|
||||
|
||||
@Override
|
||||
default ServerLevel level() {
|
||||
return (ServerLevel) entity().level();
|
||||
}
|
||||
|
||||
@Override
|
||||
default Vec3 pos() {
|
||||
return entity().getEyePosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
default BlockPos blockPos() {
|
||||
return entity().blockPosition();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A pocket computer in a player's slot.
|
||||
*
|
||||
* @param entity The current player.
|
||||
* @param slot The slot the pocket computer is in.
|
||||
*/
|
||||
record PlayerHolder(ServerPlayer entity, int slot) implements EntityHolder {
|
||||
@Override
|
||||
public boolean isValid(ServerComputer computer) {
|
||||
return entity().isAlive() && PocketComputerItem.isServerComputer(computer, entity().getInventory().getItem(this.slot()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChanged() {
|
||||
entity.getInventory().setChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A pocket computer in an {@link ItemEntity}.
|
||||
*
|
||||
* @param entity The item entity.
|
||||
*/
|
||||
record ItemEntityHolder(ItemEntity entity) implements EntityHolder {
|
||||
@Override
|
||||
public boolean isValid(ServerComputer computer) {
|
||||
return entity().isAlive() && PocketComputerItem.isServerComputer(computer, this.entity().getItem());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChanged() {
|
||||
entity.setItem(entity.getItem().copy());
|
||||
}
|
||||
}
|
||||
}
|
@@ -4,12 +4,7 @@
|
||||
|
||||
package dan200.computercraft.shared.pocket.core;
|
||||
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.api.pocket.IPocketAccess;
|
||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
|
||||
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||
import dan200.computercraft.core.computer.ComputerSide;
|
||||
import dan200.computercraft.shared.common.IColouredItem;
|
||||
import dan200.computercraft.api.component.ComputerComponents;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.core.ComputerState;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
@@ -18,172 +13,81 @@ import dan200.computercraft.shared.network.client.PocketComputerDataMessage;
|
||||
import dan200.computercraft.shared.network.client.PocketComputerDeletedClientMessage;
|
||||
import dan200.computercraft.shared.network.server.ServerNetworking;
|
||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import dan200.computercraft.shared.util.ComponentMap;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.item.ItemEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.*;
|
||||
import java.util.Set;
|
||||
|
||||
public class PocketServerComputer extends ServerComputer implements IPocketAccess {
|
||||
private @Nullable IPocketUpgrade upgrade;
|
||||
private @Nullable Entity entity;
|
||||
private ItemStack stack = ItemStack.EMPTY;
|
||||
|
||||
private int lightColour = -1;
|
||||
/**
|
||||
* A {@link ServerComputer}-subclass for {@linkplain PocketComputerItem pocket computers}.
|
||||
* <p>
|
||||
* This extends default {@link ServerComputer} behaviour by also syncing pocket computer state to nearby players, and
|
||||
* syncing the terminal to the current player.
|
||||
* <p>
|
||||
* The actual pocket computer state (upgrade, light) is maintained in {@link PocketBrain}. The two classes are tightly
|
||||
* coupled, and maintain a reference to each other.
|
||||
*
|
||||
* @see PocketComputerDataMessage
|
||||
* @see PocketComputerDeletedClientMessage
|
||||
*/
|
||||
public final class PocketServerComputer extends ServerComputer {
|
||||
private final PocketBrain brain;
|
||||
|
||||
// The state the previous tick, used to determine if the state needs to be sent to the client.
|
||||
private int oldLightColour = -1;
|
||||
private @Nullable ComputerState oldComputerState;
|
||||
|
||||
private final Set<ServerPlayer> tracking = new HashSet<>();
|
||||
private Set<ServerPlayer> tracking = Set.of();
|
||||
|
||||
public PocketServerComputer(ServerLevel world, BlockPos position, int computerID, @Nullable String label, ComputerFamily family) {
|
||||
super(world, position, computerID, label, family, Config.pocketTermWidth, Config.pocketTermHeight);
|
||||
PocketServerComputer(PocketBrain brain, PocketHolder holder, int computerID, @Nullable String label, ComputerFamily family) {
|
||||
super(
|
||||
holder.level(), holder.blockPos(), computerID, label, family, Config.pocketTermWidth, Config.pocketTermHeight,
|
||||
ComponentMap.builder().add(ComputerComponents.POCKET, brain).build()
|
||||
);
|
||||
this.brain = brain;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Entity getEntity() {
|
||||
var entity = this.entity;
|
||||
if (entity == null || stack.isEmpty() || !entity.isAlive()) return null;
|
||||
|
||||
if (entity instanceof Player) {
|
||||
var inventory = ((Player) entity).getInventory();
|
||||
return inventory.items.contains(stack) || inventory.offhand.contains(stack) ? entity : null;
|
||||
} else if (entity instanceof LivingEntity living) {
|
||||
return living.getMainHandItem() == stack || living.getOffhandItem() == stack ? entity : null;
|
||||
} else if (entity instanceof ItemEntity itemEntity) {
|
||||
return itemEntity.getItem() == stack ? entity : null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
public PocketBrain getBrain() {
|
||||
return brain;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColour() {
|
||||
return IColouredItem.getColourBasic(stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColour(int colour) {
|
||||
IColouredItem.setColourBasic(stack, colour);
|
||||
updateUpgradeNBTData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLight() {
|
||||
return lightColour;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLight(int colour) {
|
||||
if (colour < 0 || colour > 0xFFFFFF) colour = -1;
|
||||
lightColour = colour;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag getUpgradeNBTData() {
|
||||
return PocketComputerItem.getUpgradeInfo(stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateUpgradeNBTData() {
|
||||
if (entity instanceof Player player) player.getInventory().setChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidatePeripheral() {
|
||||
var peripheral = upgrade == null ? null : upgrade.createPeripheral(this);
|
||||
setPeripheral(ComputerSide.BACK, peripheral);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated(forRemoval = true)
|
||||
public Map<ResourceLocation, IPeripheral> getUpgrades() {
|
||||
return upgrade == null ? Map.of() : Collections.singletonMap(upgrade.getUpgradeID(), getPeripheral(ComputerSide.BACK));
|
||||
}
|
||||
|
||||
public @Nullable UpgradeData<IPocketUpgrade> getUpgrade() {
|
||||
return upgrade == null ? null : UpgradeData.of(upgrade, getUpgradeNBTData());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the upgrade for this pocket computer, also updating the item stack.
|
||||
* <p>
|
||||
* Note this method is not thread safe - it must be called from the server thread.
|
||||
*
|
||||
* @param upgrade The new upgrade to set it to, may be {@code null}.
|
||||
*/
|
||||
public void setUpgrade(@Nullable UpgradeData<IPocketUpgrade> upgrade) {
|
||||
synchronized (this) {
|
||||
PocketComputerItem.setUpgrade(stack, upgrade);
|
||||
updateUpgradeNBTData();
|
||||
this.upgrade = upgrade == null ? null : upgrade.upgrade();
|
||||
invalidatePeripheral();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void updateValues(@Nullable Entity entity, ItemStack stack, @Nullable IPocketUpgrade upgrade) {
|
||||
if (entity != null) {
|
||||
setLevel((ServerLevel) entity.getCommandSenderWorld());
|
||||
setPosition(entity.blockPosition());
|
||||
}
|
||||
|
||||
// If a new entity has picked it up then rebroadcast the terminal to them
|
||||
if (entity != this.entity && entity instanceof ServerPlayer) markTerminalChanged();
|
||||
|
||||
this.entity = entity;
|
||||
this.stack = stack;
|
||||
|
||||
if (this.upgrade != upgrade) {
|
||||
this.upgrade = upgrade;
|
||||
invalidatePeripheral();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tickServer() {
|
||||
protected void tickServer() {
|
||||
super.tickServer();
|
||||
|
||||
// Find any players which have gone missing and remove them from the tracking list.
|
||||
tracking.removeIf(player -> !player.isAlive() || player.level() != getLevel());
|
||||
// Get the new set of players tracking the current position.
|
||||
var newTracking = getLevel().getChunkSource().chunkMap.getPlayers(new ChunkPos(getPosition()), false);
|
||||
var trackingChanged = tracking.size() != newTracking.size() || !tracking.containsAll(newTracking);
|
||||
|
||||
// And now find any new players, add them to the tracking list, and broadcast state where appropriate.
|
||||
var state = getState();
|
||||
if (oldLightColour != lightColour || oldComputerState != state) {
|
||||
var light = brain.getLight();
|
||||
if (oldLightColour != light || oldComputerState != state) {
|
||||
oldComputerState = state;
|
||||
oldLightColour = lightColour;
|
||||
oldLightColour = light;
|
||||
|
||||
// Broadcast the state to all players
|
||||
tracking.addAll(getLevel().players());
|
||||
ServerNetworking.sendToPlayers(new PocketComputerDataMessage(this, false), tracking);
|
||||
} else {
|
||||
ServerNetworking.sendToPlayers(new PocketComputerDataMessage(this, false), newTracking);
|
||||
} else if (trackingChanged) {
|
||||
// Broadcast the state to new players.
|
||||
List<ServerPlayer> added = new ArrayList<>();
|
||||
for (var player : getLevel().players()) {
|
||||
if (tracking.add(player)) added.add(player);
|
||||
}
|
||||
var added = newTracking.stream().filter(x -> !tracking.contains(x)).toList();
|
||||
if (!added.isEmpty()) {
|
||||
ServerNetworking.sendToPlayers(new PocketComputerDataMessage(this, false), added);
|
||||
}
|
||||
}
|
||||
|
||||
if (trackingChanged) tracking = Set.copyOf(newTracking);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onTerminalChanged() {
|
||||
super.onTerminalChanged();
|
||||
|
||||
if (entity instanceof ServerPlayer player && entity.isAlive()) {
|
||||
if (brain.holder() instanceof PocketHolder.PlayerHolder holder && holder.isValid(this)) {
|
||||
// Broadcast the terminal to the current player.
|
||||
ServerNetworking.sendToPlayer(new PocketComputerDataMessage(this, true), player);
|
||||
ServerNetworking.sendToPlayer(new PocketComputerDataMessage(this, true), holder.entity());
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -45,12 +45,12 @@ public class PocketComputerMenuProvider implements MenuProvider {
|
||||
@Override
|
||||
public AbstractContainerMenu createMenu(int id, Inventory inventory, Player entity) {
|
||||
return new ComputerMenuWithoutInventory(
|
||||
isTypingOnly ? ModRegistry.Menus.POCKET_COMPUTER_NO_TERM.get() : ModRegistry.Menus.POCKET_COMPUTER.get(), id, inventory,
|
||||
isTypingOnly ? ModRegistry.Menus.POCKET_COMPUTER_NO_TERM.get() : ModRegistry.Menus.COMPUTER.get(), id, inventory,
|
||||
p -> {
|
||||
var stack = p.getItemInHand(hand);
|
||||
return stack.getItem() == item && PocketComputerItem.getServerComputer(assertNonNull(entity.level().getServer()), stack) == computer;
|
||||
},
|
||||
computer, item.getFamily()
|
||||
computer
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -14,21 +14,25 @@ import dan200.computercraft.core.computer.ComputerSide;
|
||||
import dan200.computercraft.impl.PocketUpgrades;
|
||||
import dan200.computercraft.shared.common.IColouredItem;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputerRegistry;
|
||||
import dan200.computercraft.shared.computer.core.ServerContext;
|
||||
import dan200.computercraft.shared.computer.items.IComputerItem;
|
||||
import dan200.computercraft.shared.config.Config;
|
||||
import dan200.computercraft.shared.network.container.ComputerContainerData;
|
||||
import dan200.computercraft.shared.pocket.apis.PocketAPI;
|
||||
import dan200.computercraft.shared.pocket.core.PocketBrain;
|
||||
import dan200.computercraft.shared.pocket.core.PocketHolder;
|
||||
import dan200.computercraft.shared.pocket.core.PocketServerComputer;
|
||||
import dan200.computercraft.shared.pocket.inventory.PocketComputerMenuProvider;
|
||||
import dan200.computercraft.shared.util.IDAssigner;
|
||||
import dan200.computercraft.shared.util.InventoryUtil;
|
||||
import dan200.computercraft.shared.util.NBTUtil;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.InteractionResultHolder;
|
||||
@@ -72,13 +76,33 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean tick(ItemStack stack, Level world, Entity entity, PocketServerComputer computer) {
|
||||
var upgrade = getUpgrade(stack);
|
||||
/**
|
||||
* Tick a pocket computer.
|
||||
*
|
||||
* @param stack The current pocket computer stack.
|
||||
* @param holder The entity holding the pocket item.
|
||||
* @param brain The pocket computer brain.
|
||||
*/
|
||||
private void tick(ItemStack stack, PocketHolder holder, PocketBrain brain) {
|
||||
brain.updateHolder(holder);
|
||||
|
||||
computer.setLevel((ServerLevel) world);
|
||||
computer.updateValues(entity, stack, upgrade);
|
||||
// Update pocket upgrade
|
||||
var upgrade = brain.getUpgrade();
|
||||
if (upgrade != null) upgrade.upgrade().update(brain, brain.computer().getPeripheral(ComputerSide.BACK));
|
||||
|
||||
var changed = false;
|
||||
if (updateItem(stack, brain)) holder.setChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy properties from the brain back to the item stack.
|
||||
*
|
||||
* @param stack The current pocket computer stack.
|
||||
* @param brain The current pocket brain.
|
||||
* @return Whether the item was changed.
|
||||
*/
|
||||
private boolean updateItem(ItemStack stack, PocketBrain brain) {
|
||||
var changed = brain.updateItem(stack);
|
||||
var computer = brain.computer();
|
||||
|
||||
// Sync ID
|
||||
var id = computer.getID();
|
||||
@@ -100,21 +124,20 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I
|
||||
stack.getOrCreateTag().putBoolean(NBT_ON, on);
|
||||
}
|
||||
|
||||
// Update pocket upgrade
|
||||
if (upgrade != null) upgrade.update(computer, computer.getPeripheral(ComputerSide.BACK));
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inventoryTick(ItemStack stack, Level world, Entity entity, int slotNum, boolean selected) {
|
||||
if (world.isClientSide) return;
|
||||
Container inventory = entity instanceof Player player ? player.getInventory() : null;
|
||||
var computer = createServerComputer((ServerLevel) world, entity, inventory, stack);
|
||||
computer.keepAlive();
|
||||
// This (in vanilla at least) is only called for players. Don't bother to handle other entities.
|
||||
if (world.isClientSide || !(entity instanceof ServerPlayer player)) return;
|
||||
|
||||
var changed = tick(stack, world, entity, computer);
|
||||
if (changed && inventory != null) inventory.setChanged();
|
||||
// If we're in the inventory, create a computer and keep it alive.
|
||||
var holder = new PocketHolder.PlayerHolder(player, slotNum);
|
||||
var brain = getOrCreateBrain((ServerLevel) world, holder, stack);
|
||||
brain.computer().keepAlive();
|
||||
|
||||
tick(stack, holder, brain);
|
||||
}
|
||||
|
||||
@ForgeOverride
|
||||
@@ -122,8 +145,11 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I
|
||||
var level = entity.level();
|
||||
if (level.isClientSide || level.getServer() == null) return false;
|
||||
|
||||
// If we're an item entity, tick an already existing computer (as to update the position), but do not keep the
|
||||
// computer alive.
|
||||
var computer = getServerComputer(level.getServer(), stack);
|
||||
if (computer != null && tick(stack, entity.level(), entity, computer)) entity.setItem(stack.copy());
|
||||
if (computer != null) tick(stack, new PocketHolder.ItemEntityHolder(entity), computer.getBrain());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -131,14 +157,18 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I
|
||||
public InteractionResultHolder<ItemStack> use(Level world, Player player, InteractionHand hand) {
|
||||
var stack = player.getItemInHand(hand);
|
||||
if (!world.isClientSide) {
|
||||
var computer = createServerComputer((ServerLevel) world, player, player.getInventory(), stack);
|
||||
var holder = new PocketHolder.PlayerHolder((ServerPlayer) player, InventoryUtil.getHandSlot(player, hand));
|
||||
var brain = getOrCreateBrain((ServerLevel) world, holder, stack);
|
||||
var computer = brain.computer();
|
||||
computer.turnOn();
|
||||
|
||||
var stop = false;
|
||||
var upgrade = getUpgrade(stack);
|
||||
if (upgrade != null) {
|
||||
computer.updateValues(player, stack, upgrade);
|
||||
stop = upgrade.onRightClick(world, computer, computer.getPeripheral(ComputerSide.BACK));
|
||||
brain.updateHolder(holder);
|
||||
stop = upgrade.onRightClick(world, brain, computer.getPeripheral(ComputerSide.BACK));
|
||||
// Sync back just in case. We don't need to setChanged, as we'll return the item anyway.
|
||||
updateItem(stack, brain);
|
||||
}
|
||||
|
||||
if (!stop) {
|
||||
@@ -188,40 +218,49 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I
|
||||
return ComputerCraftAPI.MOD_ID;
|
||||
}
|
||||
|
||||
public PocketServerComputer createServerComputer(ServerLevel level, Entity entity, @Nullable Container inventory, ItemStack stack) {
|
||||
|
||||
private PocketBrain getOrCreateBrain(ServerLevel level, PocketHolder holder, ItemStack stack) {
|
||||
var registry = ServerContext.get(level.getServer()).registry();
|
||||
var computer = (PocketServerComputer) registry.get(getSessionID(stack), getInstanceID(stack));
|
||||
if (computer == null) {
|
||||
var computerID = getComputerID(stack);
|
||||
if (computerID < 0) {
|
||||
computerID = ComputerCraftAPI.createUniqueNumberedSaveDir(level.getServer(), IDAssigner.COMPUTER);
|
||||
setComputerID(stack, computerID);
|
||||
}
|
||||
|
||||
computer = new PocketServerComputer(level, entity.blockPosition(), getComputerID(stack), getLabel(stack), getFamily());
|
||||
|
||||
var tag = stack.getOrCreateTag();
|
||||
tag.putInt(NBT_SESSION, registry.getSessionID());
|
||||
tag.putUUID(NBT_INSTANCE, computer.register());
|
||||
|
||||
var upgrade = getUpgrade(stack);
|
||||
|
||||
computer.updateValues(entity, stack, upgrade);
|
||||
computer.addAPI(new PocketAPI(computer));
|
||||
|
||||
// Only turn on when initially creating the computer, rather than each tick.
|
||||
if (isMarkedOn(stack) && entity instanceof Player) computer.turnOn();
|
||||
|
||||
if (inventory != null) inventory.setChanged();
|
||||
{
|
||||
var computer = getServerComputer(registry, stack);
|
||||
if (computer != null) return computer.getBrain();
|
||||
}
|
||||
computer.setLevel(level);
|
||||
return computer;
|
||||
|
||||
var computerID = getComputerID(stack);
|
||||
if (computerID < 0) {
|
||||
computerID = ComputerCraftAPI.createUniqueNumberedSaveDir(level.getServer(), IDAssigner.COMPUTER);
|
||||
setComputerID(stack, computerID);
|
||||
}
|
||||
|
||||
var brain = new PocketBrain(holder, getComputerID(stack), getLabel(stack), getFamily(), getUpgradeWithData(stack));
|
||||
var computer = brain.computer();
|
||||
|
||||
var tag = stack.getOrCreateTag();
|
||||
tag.putInt(NBT_SESSION, registry.getSessionID());
|
||||
tag.putUUID(NBT_INSTANCE, computer.register());
|
||||
|
||||
// Only turn on when initially creating the computer, rather than each tick.
|
||||
if (isMarkedOn(stack) && holder instanceof PocketHolder.PlayerHolder) computer.turnOn();
|
||||
|
||||
updateItem(stack, brain);
|
||||
|
||||
holder.setChanged();
|
||||
|
||||
return brain;
|
||||
}
|
||||
|
||||
public static boolean isServerComputer(ServerComputer computer, ItemStack stack) {
|
||||
return stack.getItem() instanceof PocketComputerItem
|
||||
&& getServerComputer(computer.getLevel().getServer(), stack) == computer;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static PocketServerComputer getServerComputer(ServerComputerRegistry registry, ItemStack stack) {
|
||||
return (PocketServerComputer) registry.get(getSessionID(stack), getInstanceID(stack));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static PocketServerComputer getServerComputer(MinecraftServer server, ItemStack stack) {
|
||||
return (PocketServerComputer) ServerContext.get(server).registry().get(getSessionID(stack), getInstanceID(stack));
|
||||
return getServerComputer(ServerContext.get(server).registry(), stack);
|
||||
}
|
||||
|
||||
// IComputerItem implementation
|
||||
|
@@ -31,8 +31,6 @@ public class PocketModem extends AbstractPocketUpgrade {
|
||||
public void update(IPocketAccess access, @Nullable IPeripheral peripheral) {
|
||||
if (!(peripheral instanceof PocketModemPeripheral modem)) return;
|
||||
|
||||
modem.setLocation(access);
|
||||
|
||||
var state = modem.getModemState();
|
||||
if (state.pollChanged()) access.setLight(state.isOpen() ? 0xBA0000 : -1);
|
||||
}
|
||||
|
@@ -14,31 +14,21 @@ import net.minecraft.world.phys.Vec3;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class PocketModemPeripheral extends WirelessModemPeripheral {
|
||||
private @Nullable Level level = null;
|
||||
private Vec3 position = Vec3.ZERO;
|
||||
private final IPocketAccess access;
|
||||
|
||||
public PocketModemPeripheral(boolean advanced, IPocketAccess access) {
|
||||
super(new ModemState(), advanced);
|
||||
setLocation(access);
|
||||
}
|
||||
|
||||
void setLocation(IPocketAccess access) {
|
||||
var entity = access.getEntity();
|
||||
if (entity != null) {
|
||||
level = entity.getCommandSenderWorld();
|
||||
position = entity.getEyePosition(1);
|
||||
}
|
||||
this.access = access;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Level getLevel() {
|
||||
if (level == null) throw new IllegalStateException("Using modem before position has been defined");
|
||||
return level;
|
||||
return access.getLevel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec3 getPosition() {
|
||||
return position;
|
||||
return access.getPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -8,15 +8,11 @@ import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.api.pocket.IPocketAccess;
|
||||
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
|
||||
import dan200.computercraft.shared.peripheral.speaker.UpgradeSpeakerPeripheral;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class PocketSpeakerPeripheral extends UpgradeSpeakerPeripheral {
|
||||
private final IPocketAccess access;
|
||||
private @Nullable Level level;
|
||||
private Vec3 position = Vec3.ZERO;
|
||||
|
||||
public PocketSpeakerPeripheral(IPocketAccess access) {
|
||||
this.access = access;
|
||||
@@ -25,7 +21,7 @@ public class PocketSpeakerPeripheral extends UpgradeSpeakerPeripheral {
|
||||
@Override
|
||||
public SpeakerPosition getPosition() {
|
||||
var entity = access.getEntity();
|
||||
return entity == null ? SpeakerPosition.of(level, position) : SpeakerPosition.of(entity);
|
||||
return entity == null ? SpeakerPosition.of(access.getLevel(), access.getPosition()) : SpeakerPosition.of(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -35,12 +31,6 @@ public class PocketSpeakerPeripheral extends UpgradeSpeakerPeripheral {
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
var entity = access.getEntity();
|
||||
if (entity != null) {
|
||||
level = entity.level();
|
||||
position = entity.position();
|
||||
}
|
||||
|
||||
super.update();
|
||||
|
||||
access.setLight(madeSound() ? 0x3320fc : -1);
|
||||
|
@@ -59,7 +59,6 @@ public final class PocketComputerUpgradeRecipe extends CustomRecipe {
|
||||
|
||||
if (computer.isEmpty()) return ItemStack.EMPTY;
|
||||
|
||||
var itemComputer = (PocketComputerItem) computer.getItem();
|
||||
if (PocketComputerItem.getUpgrade(computer) != null) return ItemStack.EMPTY;
|
||||
|
||||
// Check for upgrades around the item
|
||||
@@ -81,10 +80,9 @@ public final class PocketComputerUpgradeRecipe extends CustomRecipe {
|
||||
if (upgrade == null) return ItemStack.EMPTY;
|
||||
|
||||
// Construct the new stack
|
||||
var computerID = itemComputer.getComputerID(computer);
|
||||
var label = itemComputer.getLabel(computer);
|
||||
var colour = itemComputer.getColour(computer);
|
||||
return itemComputer.create(computerID, label, colour, upgrade);
|
||||
var newStack = computer.copyWithCount(1);
|
||||
PocketComputerItem.setUpgrade(newStack, upgrade);
|
||||
return newStack;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -9,8 +9,8 @@ import dan200.computercraft.api.lua.*;
|
||||
import dan200.computercraft.api.turtle.TurtleCommand;
|
||||
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.core.metrics.MetricsObserver;
|
||||
import dan200.computercraft.shared.peripheral.generic.methods.AbstractInventoryMethods;
|
||||
import dan200.computercraft.shared.turtle.core.*;
|
||||
|
||||
@@ -64,11 +64,11 @@ import java.util.Optional;
|
||||
* @cc.since 1.3
|
||||
*/
|
||||
public class TurtleAPI implements ILuaAPI {
|
||||
private final IAPIEnvironment environment;
|
||||
private final MetricsObserver metrics;
|
||||
private final TurtleAccessInternal turtle;
|
||||
|
||||
public TurtleAPI(IAPIEnvironment environment, TurtleAccessInternal turtle) {
|
||||
this.environment = environment;
|
||||
public TurtleAPI(MetricsObserver metrics, TurtleAccessInternal turtle) {
|
||||
this.metrics = metrics;
|
||||
this.turtle = turtle;
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ public class TurtleAPI implements ILuaAPI {
|
||||
}
|
||||
|
||||
private MethodResult trackCommand(TurtleCommand command) {
|
||||
environment.observe(Metrics.TURTLE_OPS);
|
||||
metrics.observe(Metrics.TURTLE_OPS);
|
||||
return turtle.executeCommand(command);
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ public class TurtleAPI implements ILuaAPI {
|
||||
*/
|
||||
@LuaFunction
|
||||
public final MethodResult dig(Optional<TurtleSide> side) {
|
||||
environment.observe(Metrics.TURTLE_OPS);
|
||||
metrics.observe(Metrics.TURTLE_OPS);
|
||||
return trackCommand(TurtleToolCommand.dig(InteractDirection.FORWARD, side.orElse(null)));
|
||||
}
|
||||
|
||||
@@ -184,7 +184,7 @@ public class TurtleAPI implements ILuaAPI {
|
||||
*/
|
||||
@LuaFunction
|
||||
public final MethodResult digUp(Optional<TurtleSide> side) {
|
||||
environment.observe(Metrics.TURTLE_OPS);
|
||||
metrics.observe(Metrics.TURTLE_OPS);
|
||||
return trackCommand(TurtleToolCommand.dig(InteractDirection.UP, side.orElse(null)));
|
||||
}
|
||||
|
||||
@@ -199,7 +199,7 @@ public class TurtleAPI implements ILuaAPI {
|
||||
*/
|
||||
@LuaFunction
|
||||
public final MethodResult digDown(Optional<TurtleSide> side) {
|
||||
environment.observe(Metrics.TURTLE_OPS);
|
||||
metrics.observe(Metrics.TURTLE_OPS);
|
||||
return trackCommand(TurtleToolCommand.dig(InteractDirection.DOWN, side.orElse(null)));
|
||||
}
|
||||
|
||||
|
@@ -5,6 +5,7 @@
|
||||
package dan200.computercraft.shared.turtle.blocks;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import dan200.computercraft.api.component.ComputerComponents;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
@@ -17,9 +18,9 @@ import dan200.computercraft.shared.computer.core.ComputerState;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import dan200.computercraft.shared.config.Config;
|
||||
import dan200.computercraft.shared.container.BasicContainer;
|
||||
import dan200.computercraft.shared.turtle.apis.TurtleAPI;
|
||||
import dan200.computercraft.shared.turtle.core.TurtleBrain;
|
||||
import dan200.computercraft.shared.turtle.inventory.TurtleMenu;
|
||||
import dan200.computercraft.shared.util.ComponentMap;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.NonNullList;
|
||||
@@ -75,10 +76,9 @@ public class TurtleBlockEntity extends AbstractComputerBlockEntity implements Ba
|
||||
protected ServerComputer createComputer(int id) {
|
||||
var computer = new ServerComputer(
|
||||
(ServerLevel) getLevel(), getBlockPos(), id, label,
|
||||
getFamily(), Config.turtleTermWidth,
|
||||
Config.turtleTermHeight
|
||||
getFamily(), Config.turtleTermWidth, Config.turtleTermHeight,
|
||||
ComponentMap.builder().add(ComputerComponents.TURTLE, brain).build()
|
||||
);
|
||||
computer.addAPI(new TurtleAPI(computer.getAPIEnvironment(), brain));
|
||||
brain.setupComputer(computer);
|
||||
return computer;
|
||||
}
|
||||
|
@@ -290,8 +290,7 @@ public class TurtleBrain implements TurtleAccessInternal {
|
||||
newTurtle.transferStateFrom(oldOwner);
|
||||
|
||||
var computer = newTurtle.createServerComputer();
|
||||
computer.setLevel((ServerLevel) world);
|
||||
computer.setPosition(pos);
|
||||
computer.setPosition((ServerLevel) world, pos);
|
||||
|
||||
// Remove the old turtle
|
||||
oldWorld.removeBlock(oldPos, false);
|
||||
@@ -608,7 +607,7 @@ public class TurtleBrain implements TurtleAccessInternal {
|
||||
|
||||
// If we've got a computer, ensure that we're allowed to perform work.
|
||||
var computer = owner.getServerComputer();
|
||||
if (computer != null && !computer.getComputer().getMainThreadMonitor().canWork()) return;
|
||||
if (computer != null && !computer.getMainThreadMonitor().canWork()) return;
|
||||
|
||||
// Pull a new command
|
||||
var nextCommand = commandQueue.poll();
|
||||
@@ -621,7 +620,7 @@ public class TurtleBrain implements TurtleAccessInternal {
|
||||
|
||||
// Dispatch the callback
|
||||
if (computer == null) return;
|
||||
computer.getComputer().getMainThreadMonitor().trackWork(end - start, TimeUnit.NANOSECONDS);
|
||||
computer.getMainThreadMonitor().trackWork(end - start, TimeUnit.NANOSECONDS);
|
||||
var callbackID = nextCommand.callbackID();
|
||||
if (callbackID < 0) return;
|
||||
|
||||
|
@@ -52,7 +52,7 @@ public final class TurtlePlayer {
|
||||
|
||||
var player = brain.cachedPlayer;
|
||||
if (player == null || player.player.getGameProfile() != getProfile(access.getOwningPlayer())
|
||||
|| player.player.getCommandSenderWorld() != access.getLevel()) {
|
||||
|| player.player.level() != access.getLevel()) {
|
||||
player = brain.cachedPlayer = create(brain);
|
||||
} else {
|
||||
player.setState(access);
|
||||
|
@@ -0,0 +1,66 @@
|
||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.util;
|
||||
|
||||
import dan200.computercraft.api.component.ComputerComponent;
|
||||
import dan200.computercraft.core.metrics.MetricsObserver;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An immutable map of components.
|
||||
*/
|
||||
public final class ComponentMap {
|
||||
public static final ComputerComponent<MetricsObserver> METRICS = ComputerComponent.create("computercraft", "metrics");
|
||||
|
||||
private static final ComponentMap EMPTY = new ComponentMap(Map.of());
|
||||
|
||||
private final Map<ComputerComponent<?>, Object> components;
|
||||
|
||||
private ComponentMap(Map<ComputerComponent<?>, Object> components) {
|
||||
this.components = components;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> @Nullable T get(ComputerComponent<T> component) {
|
||||
return (T) components.get(component);
|
||||
}
|
||||
|
||||
public static ComponentMap empty() {
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private final Map<ComputerComponent<?>, Object> components = new HashMap<>();
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
public <T> Builder add(ComputerComponent<T> component, T value) {
|
||||
addImpl(component, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder add(ComponentMap components) {
|
||||
for (var component : components.components.entrySet()) addImpl(component.getKey(), component.getValue());
|
||||
return this;
|
||||
}
|
||||
|
||||
private void addImpl(ComputerComponent<?> component, Object value) {
|
||||
if (components.containsKey(component)) throw new IllegalArgumentException(component + " is already set");
|
||||
components.put(component, value);
|
||||
}
|
||||
|
||||
public ComponentMap build() {
|
||||
return new ComponentMap(Map.copyOf(components));
|
||||
}
|
||||
}
|
||||
}
|
@@ -8,6 +8,9 @@ import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.phys.EntityHitResult;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
@@ -18,6 +21,20 @@ public final class InventoryUtil {
|
||||
private InventoryUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the inventory slot for a given hand.
|
||||
*
|
||||
* @param player The player to get the slot from.
|
||||
* @param hand The hand to get.
|
||||
* @return The current slot.
|
||||
*/
|
||||
public static int getHandSlot(Player player, InteractionHand hand) {
|
||||
return switch (hand) {
|
||||
case MAIN_HAND -> player.getInventory().selected;
|
||||
case OFF_HAND -> Inventory.SLOT_OFFHAND;
|
||||
};
|
||||
}
|
||||
|
||||
public static @Nullable Container getEntityContainer(ServerLevel level, BlockPos pos, Direction side) {
|
||||
var vecStart = new Vec3(
|
||||
pos.getX() + 0.5 + 0.6 * side.getStepX(),
|
||||
|
@@ -1,38 +1,37 @@
|
||||
{
|
||||
"parent": "block/block",
|
||||
"textures": {
|
||||
"particle": "computercraft:block/turtle_rainbow_overlay",
|
||||
"texture": "computercraft:block/turtle_rainbow_overlay"
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"name": "Flag",
|
||||
"from": [1.5, 13, 10.5],
|
||||
"to": [2, 16.5, 15.5],
|
||||
"rotation": {"angle": 22.5, "axis": "x", "origin": [2, 11, 10.75]},
|
||||
"faces": {
|
||||
"north": {"uv": [0, 0, 1, 6], "texture": "#texture"},
|
||||
"east": {"uv": [0, 0, 7, 6], "texture": "#texture"},
|
||||
"south": {"uv": [0, 0, 1, 6], "texture": "#texture"},
|
||||
"west": {"uv": [0, 0, 7, 6], "texture": "#texture"},
|
||||
"up": {"uv": [15, 0, 16, 6], "texture": "#texture"},
|
||||
"down": {"uv": [8, 0, 9, 6], "texture": "#texture"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Stick",
|
||||
"from": [1.5, 10.5, 10.5],
|
||||
"to": [2, 13, 11],
|
||||
"rotation": {"angle": 22.5, "axis": "x", "origin": [2, 11, 10.75]},
|
||||
"faces": {
|
||||
"north": {"uv": [12, 0, 13, 6], "texture": "#texture"},
|
||||
"east": {"uv": [13, 0, 14, 6], "texture": "#texture"},
|
||||
"south": {"uv": [12, 0, 13, 6], "texture": "#texture"},
|
||||
"west": {"uv": [13, 0, 14, 6], "texture": "#texture"},
|
||||
"up": {"uv": [12, 6, 13, 7], "texture": "#texture"},
|
||||
"down": {"uv": [13, 6, 14, 7], "texture": "#texture"}
|
||||
}
|
||||
}
|
||||
],
|
||||
"display": {}
|
||||
"parent": "block/block",
|
||||
"textures": {
|
||||
"particle": "computercraft:block/turtle_rainbow_overlay",
|
||||
"texture": "computercraft:block/turtle_rainbow_overlay"
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"name": "Flag",
|
||||
"from": [1.5, 13.5, 10.5],
|
||||
"to": [2, 16.5, 15.5],
|
||||
"rotation": {"angle": 22.5, "axis": "x", "origin": [2, 11, 10.75]},
|
||||
"faces": {
|
||||
"north": {"uv": [0, 0, 1, 6], "texture": "#texture"},
|
||||
"east": {"uv": [0, 0, 7, 6], "texture": "#texture"},
|
||||
"south": {"uv": [0, 0, 1, 6], "texture": "#texture"},
|
||||
"west": {"uv": [0, 0, 7, 6], "texture": "#texture"},
|
||||
"up": {"uv": [15, 0, 16, 6], "texture": "#texture"},
|
||||
"down": {"uv": [8, 0, 9, 6], "texture": "#texture"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Stick",
|
||||
"from": [1.5, 10.5, 10.5],
|
||||
"to": [2, 13.5, 11],
|
||||
"rotation": {"angle": 22.5, "axis": "x", "origin": [2, 11, 10.75]},
|
||||
"faces": {
|
||||
"north": {"uv": [12, 0, 13, 6], "texture": "#texture"},
|
||||
"east": {"uv": [13, 0, 14, 6], "texture": "#texture"},
|
||||
"south": {"uv": [12, 0, 13, 6], "texture": "#texture"},
|
||||
"west": {"uv": [13, 0, 14, 6], "texture": "#texture"},
|
||||
"up": {"uv": [12, 6, 13, 7], "texture": "#texture"},
|
||||
"down": {"uv": [13, 6, 14, 7], "texture": "#texture"}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@@ -1,38 +1,37 @@
|
||||
{
|
||||
"parent": "block/block",
|
||||
"textures": {
|
||||
"particle": "computercraft:block/turtle_trans_overlay",
|
||||
"texture": "computercraft:block/turtle_trans_overlay"
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"name": "Flag",
|
||||
"from": [1.5, 13.5, 10.5],
|
||||
"to": [2, 16.5, 15.5],
|
||||
"rotation": {"angle": 22.5, "axis": "x", "origin": [2, 11, 10.75]},
|
||||
"faces": {
|
||||
"north": {"uv": [0, 0, 1, 5], "texture": "#texture"},
|
||||
"east": {"uv": [0, 0, 7, 5], "texture": "#texture"},
|
||||
"south": {"uv": [0, 0, 1, 5], "texture": "#texture"},
|
||||
"west": {"uv": [0, 0, 7, 5], "texture": "#texture"},
|
||||
"up": {"uv": [15, 0, 16, 5], "texture": "#texture"},
|
||||
"down": {"uv": [15, 0, 16, 5], "texture": "#texture"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Stick",
|
||||
"from": [1.5, 10.5, 10.5],
|
||||
"to": [2, 13.5, 11],
|
||||
"rotation": {"angle": 22.5, "axis": "x", "origin": [2, 11, 10.75]},
|
||||
"faces": {
|
||||
"north": {"uv": [12, 0, 13, 6], "texture": "#texture"},
|
||||
"east": {"uv": [13, 0, 14, 6], "texture": "#texture"},
|
||||
"south": {"uv": [12, 0, 13, 6], "texture": "#texture"},
|
||||
"west": {"uv": [13, 0, 14, 6], "texture": "#texture"},
|
||||
"up": {"uv": [12, 6, 13, 7], "texture": "#texture"},
|
||||
"down": {"uv": [13, 6, 14, 7], "texture": "#texture"}
|
||||
}
|
||||
}
|
||||
],
|
||||
"display": {}
|
||||
"parent": "block/block",
|
||||
"textures": {
|
||||
"particle": "computercraft:block/turtle_trans_overlay",
|
||||
"texture": "computercraft:block/turtle_trans_overlay"
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"name": "Flag",
|
||||
"from": [1.5, 13.5, 10.5],
|
||||
"to": [2, 16, 15.5],
|
||||
"rotation": {"angle": 22.5, "axis": "x", "origin": [2, 11, 10.75]},
|
||||
"faces": {
|
||||
"north": {"uv": [0, 0, 1, 5], "texture": "#texture"},
|
||||
"east": {"uv": [0, 0, 7, 5], "texture": "#texture"},
|
||||
"south": {"uv": [0, 0, 1, 5], "texture": "#texture"},
|
||||
"west": {"uv": [0, 0, 7, 5], "texture": "#texture"},
|
||||
"up": {"uv": [15, 0, 16, 5], "texture": "#texture"},
|
||||
"down": {"uv": [15, 0, 16, 5], "texture": "#texture"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Stick",
|
||||
"from": [1.5, 10.5, 10.5],
|
||||
"to": [2, 13.5, 11],
|
||||
"rotation": {"angle": 22.5, "axis": "x", "origin": [2, 11, 10.75]},
|
||||
"faces": {
|
||||
"north": {"uv": [12, 0, 13, 6], "texture": "#texture"},
|
||||
"east": {"uv": [13, 0, 14, 6], "texture": "#texture"},
|
||||
"south": {"uv": [12, 0, 13, 6], "texture": "#texture"},
|
||||
"west": {"uv": [13, 0, 14, 6], "texture": "#texture"},
|
||||
"up": {"uv": [12, 6, 13, 7], "texture": "#texture"},
|
||||
"down": {"uv": [13, 6, 14, 7], "texture": "#texture"}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@@ -27,6 +27,7 @@ import dan200.computercraft.shared.util.WaterloggableHelpers
|
||||
import dan200.computercraft.test.core.assertArrayEquals
|
||||
import dan200.computercraft.test.core.computer.LuaTaskContext
|
||||
import dan200.computercraft.test.core.computer.getApi
|
||||
import dan200.computercraft.test.shared.ItemStackMatcher.isStack
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.gametest.framework.GameTest
|
||||
import net.minecraft.gametest.framework.GameTestHelper
|
||||
@@ -40,8 +41,7 @@ import net.minecraft.world.level.block.FenceBlock
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.Matchers.array
|
||||
import org.hamcrest.Matchers.instanceOf
|
||||
import org.hamcrest.Matchers.*
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNotEquals
|
||||
import java.util.*
|
||||
@@ -656,6 +656,43 @@ class Turtle_Test {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `turtle.craft` works as expected
|
||||
*/
|
||||
@GameTest
|
||||
fun Craft(helper: GameTestHelper) = helper.sequence {
|
||||
thenOnComputer {
|
||||
callPeripheral("left", "craft", 1).assertArrayEquals(true)
|
||||
}
|
||||
thenExecute {
|
||||
val turtle = helper.getBlockEntity(BlockPos(2, 2, 2), ModRegistry.BlockEntities.TURTLE_NORMAL.get())
|
||||
assertThat(
|
||||
"Inventory is as expected.",
|
||||
turtle.contents,
|
||||
contains(
|
||||
isStack(Items.DIAMOND, 1), isStack(Items.DIAMOND, 1), isStack(Items.DIAMOND, 1), isStack(Items.DIAMOND_PICKAXE, 1),
|
||||
isStack(ItemStack.EMPTY), isStack(Items.STICK, 1), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY),
|
||||
isStack(ItemStack.EMPTY), isStack(Items.STICK, 1), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY),
|
||||
isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `turtle.equipLeft` equips a tool.
|
||||
*/
|
||||
@GameTest
|
||||
fun Equip_tool(helper: GameTestHelper) = helper.sequence {
|
||||
thenOnComputer {
|
||||
turtle.equipLeft().await().assertArrayEquals(true)
|
||||
}
|
||||
thenExecute {
|
||||
val turtle = helper.getBlockEntity(BlockPos(2, 2, 2), ModRegistry.BlockEntities.TURTLE_NORMAL.get())
|
||||
assertEquals(TurtleUpgrades.instance().get("minecraft:diamond_pickaxe"), turtle.getUpgrade(TurtleSide.LEFT))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render turtles as an item.
|
||||
*/
|
||||
|
137
projects/common/src/testMod/resources/data/cctest/structures/turtle_test.craft.snbt
generated
Normal file
137
projects/common/src/testMod/resources/data/cctest/structures/turtle_test.craft.snbt
generated
Normal file
@@ -0,0 +1,137 @@
|
||||
{
|
||||
DataVersion: 3465,
|
||||
size: [5, 5, 5],
|
||||
data: [
|
||||
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 1, 0], state: "minecraft:air"},
|
||||
{pos: [0, 1, 1], state: "minecraft:air"},
|
||||
{pos: [0, 1, 2], state: "minecraft:air"},
|
||||
{pos: [0, 1, 3], state: "minecraft:air"},
|
||||
{pos: [0, 1, 4], state: "minecraft:air"},
|
||||
{pos: [1, 1, 0], state: "minecraft:air"},
|
||||
{pos: [1, 1, 1], state: "minecraft:air"},
|
||||
{pos: [1, 1, 2], state: "minecraft:air"},
|
||||
{pos: [1, 1, 3], state: "minecraft:air"},
|
||||
{pos: [1, 1, 4], state: "minecraft:air"},
|
||||
{pos: [2, 1, 0], state: "minecraft:air"},
|
||||
{pos: [2, 1, 1], state: "minecraft:air"},
|
||||
{pos: [2, 1, 2], state: "computercraft:turtle_normal{facing:north,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 0, Items: [{Count: 2b, Slot: 0b, id: "minecraft:diamond"}, {Count: 2b, Slot: 1b, id: "minecraft:diamond"}, {Count: 2b, Slot: 2b, id: "minecraft:diamond"}, {Count: 2b, Slot: 5b, id: "minecraft:stick"}, {Count: 2b, Slot: 9b, id: "minecraft:stick"}], Label: "turtle_test.craft", LeftUpgrade: "minecraft:crafting_table", LeftUpgradeNbt: {}, On: 1b, Slot: 0, id: "computercraft:turtle_normal"}},
|
||||
{pos: [2, 1, 3], state: "minecraft:air"},
|
||||
{pos: [2, 1, 4], state: "minecraft:air"},
|
||||
{pos: [3, 1, 0], state: "minecraft:air"},
|
||||
{pos: [3, 1, 1], state: "minecraft:air"},
|
||||
{pos: [3, 1, 2], state: "minecraft:air"},
|
||||
{pos: [3, 1, 3], state: "minecraft:air"},
|
||||
{pos: [3, 1, 4], state: "minecraft:air"},
|
||||
{pos: [4, 1, 0], state: "minecraft:air"},
|
||||
{pos: [4, 1, 1], state: "minecraft:air"},
|
||||
{pos: [4, 1, 2], state: "minecraft:air"},
|
||||
{pos: [4, 1, 3], state: "minecraft:air"},
|
||||
{pos: [4, 1, 4], state: "minecraft:air"},
|
||||
{pos: [0, 2, 0], state: "minecraft:air"},
|
||||
{pos: [0, 2, 1], state: "minecraft:air"},
|
||||
{pos: [0, 2, 2], state: "minecraft:air"},
|
||||
{pos: [0, 2, 3], state: "minecraft:air"},
|
||||
{pos: [0, 2, 4], state: "minecraft:air"},
|
||||
{pos: [1, 2, 0], state: "minecraft:air"},
|
||||
{pos: [1, 2, 1], state: "minecraft:air"},
|
||||
{pos: [1, 2, 2], state: "minecraft:air"},
|
||||
{pos: [1, 2, 3], state: "minecraft:air"},
|
||||
{pos: [1, 2, 4], state: "minecraft:air"},
|
||||
{pos: [2, 2, 0], state: "minecraft:air"},
|
||||
{pos: [2, 2, 1], state: "minecraft:air"},
|
||||
{pos: [2, 2, 2], state: "minecraft:air"},
|
||||
{pos: [2, 2, 3], state: "minecraft:air"},
|
||||
{pos: [2, 2, 4], state: "minecraft:air"},
|
||||
{pos: [3, 2, 0], state: "minecraft:air"},
|
||||
{pos: [3, 2, 1], state: "minecraft:air"},
|
||||
{pos: [3, 2, 2], state: "minecraft:air"},
|
||||
{pos: [3, 2, 3], state: "minecraft:air"},
|
||||
{pos: [3, 2, 4], state: "minecraft:air"},
|
||||
{pos: [4, 2, 0], state: "minecraft:air"},
|
||||
{pos: [4, 2, 1], state: "minecraft:air"},
|
||||
{pos: [4, 2, 2], state: "minecraft:air"},
|
||||
{pos: [4, 2, 3], state: "minecraft:air"},
|
||||
{pos: [4, 2, 4], state: "minecraft:air"},
|
||||
{pos: [0, 3, 0], state: "minecraft:air"},
|
||||
{pos: [0, 3, 1], state: "minecraft:air"},
|
||||
{pos: [0, 3, 2], state: "minecraft:air"},
|
||||
{pos: [0, 3, 3], state: "minecraft:air"},
|
||||
{pos: [0, 3, 4], state: "minecraft:air"},
|
||||
{pos: [1, 3, 0], state: "minecraft:air"},
|
||||
{pos: [1, 3, 1], state: "minecraft:air"},
|
||||
{pos: [1, 3, 2], state: "minecraft:air"},
|
||||
{pos: [1, 3, 3], state: "minecraft:air"},
|
||||
{pos: [1, 3, 4], state: "minecraft:air"},
|
||||
{pos: [2, 3, 0], state: "minecraft:air"},
|
||||
{pos: [2, 3, 1], state: "minecraft:air"},
|
||||
{pos: [2, 3, 2], state: "minecraft:air"},
|
||||
{pos: [2, 3, 3], state: "minecraft:air"},
|
||||
{pos: [2, 3, 4], state: "minecraft:air"},
|
||||
{pos: [3, 3, 0], state: "minecraft:air"},
|
||||
{pos: [3, 3, 1], state: "minecraft:air"},
|
||||
{pos: [3, 3, 2], state: "minecraft:air"},
|
||||
{pos: [3, 3, 3], state: "minecraft:air"},
|
||||
{pos: [3, 3, 4], state: "minecraft:air"},
|
||||
{pos: [4, 3, 0], state: "minecraft:air"},
|
||||
{pos: [4, 3, 1], state: "minecraft:air"},
|
||||
{pos: [4, 3, 2], state: "minecraft:air"},
|
||||
{pos: [4, 3, 3], state: "minecraft:air"},
|
||||
{pos: [4, 3, 4], state: "minecraft:air"},
|
||||
{pos: [0, 4, 0], state: "minecraft:air"},
|
||||
{pos: [0, 4, 1], state: "minecraft:air"},
|
||||
{pos: [0, 4, 2], state: "minecraft:air"},
|
||||
{pos: [0, 4, 3], state: "minecraft:air"},
|
||||
{pos: [0, 4, 4], state: "minecraft:air"},
|
||||
{pos: [1, 4, 0], state: "minecraft:air"},
|
||||
{pos: [1, 4, 1], state: "minecraft:air"},
|
||||
{pos: [1, 4, 2], state: "minecraft:air"},
|
||||
{pos: [1, 4, 3], state: "minecraft:air"},
|
||||
{pos: [1, 4, 4], state: "minecraft:air"},
|
||||
{pos: [2, 4, 0], state: "minecraft:air"},
|
||||
{pos: [2, 4, 1], state: "minecraft:air"},
|
||||
{pos: [2, 4, 2], state: "minecraft:air"},
|
||||
{pos: [2, 4, 3], state: "minecraft:air"},
|
||||
{pos: [2, 4, 4], state: "minecraft:air"},
|
||||
{pos: [3, 4, 0], state: "minecraft:air"},
|
||||
{pos: [3, 4, 1], state: "minecraft:air"},
|
||||
{pos: [3, 4, 2], state: "minecraft:air"},
|
||||
{pos: [3, 4, 3], state: "minecraft:air"},
|
||||
{pos: [3, 4, 4], state: "minecraft:air"},
|
||||
{pos: [4, 4, 0], state: "minecraft:air"},
|
||||
{pos: [4, 4, 1], state: "minecraft:air"},
|
||||
{pos: [4, 4, 2], state: "minecraft:air"},
|
||||
{pos: [4, 4, 3], state: "minecraft:air"},
|
||||
{pos: [4, 4, 4], state: "minecraft:air"}
|
||||
],
|
||||
entities: [],
|
||||
palette: [
|
||||
"minecraft:polished_andesite",
|
||||
"minecraft:air",
|
||||
"computercraft:turtle_normal{facing:north,waterlogged:false}"
|
||||
]
|
||||
}
|
137
projects/common/src/testMod/resources/data/cctest/structures/turtle_test.equip_tool.snbt
generated
Normal file
137
projects/common/src/testMod/resources/data/cctest/structures/turtle_test.equip_tool.snbt
generated
Normal file
@@ -0,0 +1,137 @@
|
||||
{
|
||||
DataVersion: 3465,
|
||||
size: [5, 5, 5],
|
||||
data: [
|
||||
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 1, 0], state: "minecraft:air"},
|
||||
{pos: [0, 1, 1], state: "minecraft:air"},
|
||||
{pos: [0, 1, 2], state: "minecraft:air"},
|
||||
{pos: [0, 1, 3], state: "minecraft:air"},
|
||||
{pos: [0, 1, 4], state: "minecraft:air"},
|
||||
{pos: [1, 1, 0], state: "minecraft:air"},
|
||||
{pos: [1, 1, 1], state: "minecraft:air"},
|
||||
{pos: [1, 1, 2], state: "minecraft:air"},
|
||||
{pos: [1, 1, 3], state: "minecraft:air"},
|
||||
{pos: [1, 1, 4], state: "minecraft:air"},
|
||||
{pos: [2, 1, 0], state: "minecraft:air"},
|
||||
{pos: [2, 1, 1], state: "minecraft:air"},
|
||||
{pos: [2, 1, 2], state: "computercraft:turtle_normal{facing:north,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 0, Items: [{Count: 1b, Slot: 0b, id: "minecraft:diamond_pickaxe"}], Label: "turtle_test.equip_tool", On: 1b, Slot: 0, id: "computercraft:turtle_normal"}},
|
||||
{pos: [2, 1, 3], state: "minecraft:air"},
|
||||
{pos: [2, 1, 4], state: "minecraft:air"},
|
||||
{pos: [3, 1, 0], state: "minecraft:air"},
|
||||
{pos: [3, 1, 1], state: "minecraft:air"},
|
||||
{pos: [3, 1, 2], state: "minecraft:air"},
|
||||
{pos: [3, 1, 3], state: "minecraft:air"},
|
||||
{pos: [3, 1, 4], state: "minecraft:air"},
|
||||
{pos: [4, 1, 0], state: "minecraft:air"},
|
||||
{pos: [4, 1, 1], state: "minecraft:air"},
|
||||
{pos: [4, 1, 2], state: "minecraft:air"},
|
||||
{pos: [4, 1, 3], state: "minecraft:air"},
|
||||
{pos: [4, 1, 4], state: "minecraft:air"},
|
||||
{pos: [0, 2, 0], state: "minecraft:air"},
|
||||
{pos: [0, 2, 1], state: "minecraft:air"},
|
||||
{pos: [0, 2, 2], state: "minecraft:air"},
|
||||
{pos: [0, 2, 3], state: "minecraft:air"},
|
||||
{pos: [0, 2, 4], state: "minecraft:air"},
|
||||
{pos: [1, 2, 0], state: "minecraft:air"},
|
||||
{pos: [1, 2, 1], state: "minecraft:air"},
|
||||
{pos: [1, 2, 2], state: "minecraft:air"},
|
||||
{pos: [1, 2, 3], state: "minecraft:air"},
|
||||
{pos: [1, 2, 4], state: "minecraft:air"},
|
||||
{pos: [2, 2, 0], state: "minecraft:air"},
|
||||
{pos: [2, 2, 1], state: "minecraft:air"},
|
||||
{pos: [2, 2, 2], state: "minecraft:air"},
|
||||
{pos: [2, 2, 3], state: "minecraft:air"},
|
||||
{pos: [2, 2, 4], state: "minecraft:air"},
|
||||
{pos: [3, 2, 0], state: "minecraft:air"},
|
||||
{pos: [3, 2, 1], state: "minecraft:air"},
|
||||
{pos: [3, 2, 2], state: "minecraft:air"},
|
||||
{pos: [3, 2, 3], state: "minecraft:air"},
|
||||
{pos: [3, 2, 4], state: "minecraft:air"},
|
||||
{pos: [4, 2, 0], state: "minecraft:air"},
|
||||
{pos: [4, 2, 1], state: "minecraft:air"},
|
||||
{pos: [4, 2, 2], state: "minecraft:air"},
|
||||
{pos: [4, 2, 3], state: "minecraft:air"},
|
||||
{pos: [4, 2, 4], state: "minecraft:air"},
|
||||
{pos: [0, 3, 0], state: "minecraft:air"},
|
||||
{pos: [0, 3, 1], state: "minecraft:air"},
|
||||
{pos: [0, 3, 2], state: "minecraft:air"},
|
||||
{pos: [0, 3, 3], state: "minecraft:air"},
|
||||
{pos: [0, 3, 4], state: "minecraft:air"},
|
||||
{pos: [1, 3, 0], state: "minecraft:air"},
|
||||
{pos: [1, 3, 1], state: "minecraft:air"},
|
||||
{pos: [1, 3, 2], state: "minecraft:air"},
|
||||
{pos: [1, 3, 3], state: "minecraft:air"},
|
||||
{pos: [1, 3, 4], state: "minecraft:air"},
|
||||
{pos: [2, 3, 0], state: "minecraft:air"},
|
||||
{pos: [2, 3, 1], state: "minecraft:air"},
|
||||
{pos: [2, 3, 2], state: "minecraft:air"},
|
||||
{pos: [2, 3, 3], state: "minecraft:air"},
|
||||
{pos: [2, 3, 4], state: "minecraft:air"},
|
||||
{pos: [3, 3, 0], state: "minecraft:air"},
|
||||
{pos: [3, 3, 1], state: "minecraft:air"},
|
||||
{pos: [3, 3, 2], state: "minecraft:air"},
|
||||
{pos: [3, 3, 3], state: "minecraft:air"},
|
||||
{pos: [3, 3, 4], state: "minecraft:air"},
|
||||
{pos: [4, 3, 0], state: "minecraft:air"},
|
||||
{pos: [4, 3, 1], state: "minecraft:air"},
|
||||
{pos: [4, 3, 2], state: "minecraft:air"},
|
||||
{pos: [4, 3, 3], state: "minecraft:air"},
|
||||
{pos: [4, 3, 4], state: "minecraft:air"},
|
||||
{pos: [0, 4, 0], state: "minecraft:air"},
|
||||
{pos: [0, 4, 1], state: "minecraft:air"},
|
||||
{pos: [0, 4, 2], state: "minecraft:air"},
|
||||
{pos: [0, 4, 3], state: "minecraft:air"},
|
||||
{pos: [0, 4, 4], state: "minecraft:air"},
|
||||
{pos: [1, 4, 0], state: "minecraft:air"},
|
||||
{pos: [1, 4, 1], state: "minecraft:air"},
|
||||
{pos: [1, 4, 2], state: "minecraft:air"},
|
||||
{pos: [1, 4, 3], state: "minecraft:air"},
|
||||
{pos: [1, 4, 4], state: "minecraft:air"},
|
||||
{pos: [2, 4, 0], state: "minecraft:air"},
|
||||
{pos: [2, 4, 1], state: "minecraft:air"},
|
||||
{pos: [2, 4, 2], state: "minecraft:air"},
|
||||
{pos: [2, 4, 3], state: "minecraft:air"},
|
||||
{pos: [2, 4, 4], state: "minecraft:air"},
|
||||
{pos: [3, 4, 0], state: "minecraft:air"},
|
||||
{pos: [3, 4, 1], state: "minecraft:air"},
|
||||
{pos: [3, 4, 2], state: "minecraft:air"},
|
||||
{pos: [3, 4, 3], state: "minecraft:air"},
|
||||
{pos: [3, 4, 4], state: "minecraft:air"},
|
||||
{pos: [4, 4, 0], state: "minecraft:air"},
|
||||
{pos: [4, 4, 1], state: "minecraft:air"},
|
||||
{pos: [4, 4, 2], state: "minecraft:air"},
|
||||
{pos: [4, 4, 3], state: "minecraft:air"},
|
||||
{pos: [4, 4, 4], state: "minecraft:air"}
|
||||
],
|
||||
entities: [],
|
||||
palette: [
|
||||
"minecraft:polished_andesite",
|
||||
"minecraft:air",
|
||||
"computercraft:turtle_normal{facing:north,waterlogged:false}"
|
||||
]
|
||||
}
|
@@ -1,23 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2017 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.api.lua;
|
||||
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* An interface passed to {@link ILuaAPIFactory} in order to provide additional information
|
||||
* about a computer.
|
||||
*/
|
||||
public interface IComputerSystem extends IComputerAccess {
|
||||
/**
|
||||
* Get the label for this computer.
|
||||
*
|
||||
* @return This computer's label, or {@code null} if it is not set.
|
||||
*/
|
||||
@Nullable
|
||||
String getLabel();
|
||||
}
|
@@ -4,7 +4,6 @@
|
||||
|
||||
package dan200.computercraft.core;
|
||||
|
||||
import dan200.computercraft.api.lua.ILuaAPIFactory;
|
||||
import dan200.computercraft.core.asm.GenericMethod;
|
||||
import dan200.computercraft.core.asm.LuaMethodSupplier;
|
||||
import dan200.computercraft.core.asm.PeripheralMethodSupplier;
|
||||
@@ -35,21 +34,19 @@ public final class ComputerContext {
|
||||
private final ComputerScheduler computerScheduler;
|
||||
private final MainThreadScheduler mainThreadScheduler;
|
||||
private final ILuaMachine.Factory luaFactory;
|
||||
private final List<ILuaAPIFactory> apiFactories;
|
||||
private final MethodSupplier<LuaMethod> luaMethods;
|
||||
private final MethodSupplier<PeripheralMethod> peripheralMethods;
|
||||
|
||||
ComputerContext(
|
||||
private ComputerContext(
|
||||
GlobalEnvironment globalEnvironment, ComputerScheduler computerScheduler,
|
||||
MainThreadScheduler mainThreadScheduler, ILuaMachine.Factory luaFactory,
|
||||
List<ILuaAPIFactory> apiFactories, MethodSupplier<LuaMethod> luaMethods,
|
||||
MethodSupplier<LuaMethod> luaMethods,
|
||||
MethodSupplier<PeripheralMethod> peripheralMethods
|
||||
) {
|
||||
this.globalEnvironment = globalEnvironment;
|
||||
this.computerScheduler = computerScheduler;
|
||||
this.mainThreadScheduler = mainThreadScheduler;
|
||||
this.luaFactory = luaFactory;
|
||||
this.apiFactories = apiFactories;
|
||||
this.luaMethods = luaMethods;
|
||||
this.peripheralMethods = peripheralMethods;
|
||||
}
|
||||
@@ -91,15 +88,6 @@ public final class ComputerContext {
|
||||
return luaFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional APIs to inject into each computer.
|
||||
*
|
||||
* @return All available API factories.
|
||||
*/
|
||||
public List<ILuaAPIFactory> apiFactories() {
|
||||
return apiFactories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link MethodSupplier} used to find methods on Lua values.
|
||||
*
|
||||
@@ -166,7 +154,6 @@ public final class ComputerContext {
|
||||
private @Nullable ComputerScheduler computerScheduler = null;
|
||||
private @Nullable MainThreadScheduler mainThreadScheduler;
|
||||
private @Nullable ILuaMachine.Factory luaFactory;
|
||||
private @Nullable List<ILuaAPIFactory> apiFactories;
|
||||
private @Nullable List<GenericMethod> genericMethods;
|
||||
|
||||
Builder(GlobalEnvironment environment) {
|
||||
@@ -227,20 +214,6 @@ public final class ComputerContext {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the additional {@linkplain ILuaAPIFactory APIs} to add to each computer.
|
||||
*
|
||||
* @param apis A list of API factories.
|
||||
* @return {@code this}, for chaining
|
||||
* @see ComputerContext#apiFactories()
|
||||
*/
|
||||
public Builder apiFactories(Collection<ILuaAPIFactory> apis) {
|
||||
Objects.requireNonNull(apis);
|
||||
if (apiFactories != null) throw new IllegalStateException("Main-thread scheduler already specified");
|
||||
apiFactories = List.copyOf(apis);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the set of {@link GenericMethod}s used by the {@linkplain MethodSupplier method suppliers}.
|
||||
*
|
||||
@@ -267,7 +240,6 @@ public final class ComputerContext {
|
||||
computerScheduler == null ? new ComputerThread(1) : computerScheduler,
|
||||
mainThreadScheduler == null ? new NoWorkMainThreadScheduler() : mainThreadScheduler,
|
||||
luaFactory == null ? CobaltLuaMachine::new : luaFactory,
|
||||
apiFactories == null ? List.of() : apiFactories,
|
||||
LuaMethodSupplier.create(genericMethods == null ? List.of() : genericMethods),
|
||||
PeripheralMethodSupplier.create(genericMethods == null ? List.of() : genericMethods)
|
||||
);
|
||||
|
@@ -21,7 +21,7 @@ public abstract class ComputerAccess implements IComputerAccess {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ComputerAccess.class);
|
||||
|
||||
private final IAPIEnvironment environment;
|
||||
private final Set<String> mounts = new HashSet<>();
|
||||
private final Set<String> mounts = new HashSet<>(0);
|
||||
|
||||
protected ComputerAccess(IAPIEnvironment environment) {
|
||||
this.environment = environment;
|
||||
|
@@ -175,9 +175,9 @@ public class OSAPI implements ILuaAPI {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an alarm that will fire at the specified in-game time. When it
|
||||
* fires, * an {@code alarm} event will be added to the event queue with the
|
||||
* ID * returned from this function as the first parameter.
|
||||
* Sets an alarm that will fire at the specified {@linkplain #time(IArguments) in-game time}.
|
||||
* When it fires, an {@code alarm} event will be added to the event queue with the
|
||||
* ID returned from this function as the first parameter.
|
||||
*
|
||||
* @param time The time at which to fire the alarm, in the range [0.0, 24.0).
|
||||
* @return The ID of the new alarm. This can be used to filter the
|
||||
|
@@ -0,0 +1,28 @@
|
||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.core.computer;
|
||||
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
|
||||
/**
|
||||
* Hooks for managing the lifecycle of an API. This allows adding additional logic to an API's {@link ILuaAPI#startup()}
|
||||
* and {@link ILuaAPI#shutdown()} methods.
|
||||
*
|
||||
* @see ILuaAPI
|
||||
* @see Computer#addApi(ILuaAPI, ApiLifecycle)
|
||||
*/
|
||||
public interface ApiLifecycle {
|
||||
/**
|
||||
* Called before the API's {@link ILuaAPI#startup()} method, may be used to set up resources.
|
||||
*/
|
||||
default void startup() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after the API's {@link ILuaAPI#shutdown()} method, may be used to tear down resources.
|
||||
*/
|
||||
default void shutdown() {
|
||||
}
|
||||
}
|
@@ -9,18 +9,14 @@ import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A wrapper for {@link ILuaAPI}s which optionally manages the lifecycle of a {@link ComputerSystem}.
|
||||
* A wrapper for {@link ILuaAPI}s which provides an optional shutdown hook to clean up resources.
|
||||
*
|
||||
* @param api The original API.
|
||||
* @param lifecycle The optional lifecycle hooks for this API.
|
||||
*/
|
||||
final class ApiWrapper {
|
||||
private final ILuaAPI api;
|
||||
private final @Nullable ComputerSystem system;
|
||||
|
||||
ApiWrapper(ILuaAPI api, @Nullable ComputerSystem system) {
|
||||
this.api = api;
|
||||
this.system = system;
|
||||
}
|
||||
|
||||
record ApiWrapper(ILuaAPI api, @Nullable ApiLifecycle lifecycle) {
|
||||
public void startup() {
|
||||
if (lifecycle != null) lifecycle.startup();
|
||||
api.startup();
|
||||
}
|
||||
|
||||
@@ -30,7 +26,7 @@ final class ApiWrapper {
|
||||
|
||||
public void shutdown() {
|
||||
api.shutdown();
|
||||
if (system != null) system.unmountAll();
|
||||
if (lifecycle != null) lifecycle.shutdown();
|
||||
}
|
||||
|
||||
public ILuaAPI api() {
|
||||
|
@@ -184,6 +184,10 @@ public class Computer {
|
||||
executor.addApi(api);
|
||||
}
|
||||
|
||||
public void addApi(ILuaAPI api, ApiLifecycle lifecycleHooks) {
|
||||
executor.addApi(api, lifecycleHooks);
|
||||
}
|
||||
|
||||
long getUniqueTaskId() {
|
||||
return lastTaskId.incrementAndGet();
|
||||
}
|
||||
|
@@ -162,13 +162,6 @@ final class ComputerExecutor implements ComputerScheduler.Worker {
|
||||
addApi(new PeripheralAPI(environment, context.peripheralMethods()));
|
||||
addApi(new OSAPI(environment));
|
||||
if (CoreConfig.httpEnabled) addApi(new HTTPAPI(environment));
|
||||
|
||||
// Load in the externally registered APIs.
|
||||
for (var factory : context.apiFactories()) {
|
||||
var system = new ComputerSystem(environment);
|
||||
var api = factory.create(system);
|
||||
if (api != null) apis.add(new ApiWrapper(api, system));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -190,6 +183,10 @@ final class ComputerExecutor implements ComputerScheduler.Worker {
|
||||
apis.add(new ApiWrapper(api, null));
|
||||
}
|
||||
|
||||
void addApi(ILuaAPI api, ApiLifecycle lifecycleHooks) {
|
||||
apis.add(new ApiWrapper(api, lifecycleHooks));
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule this computer to be started if not already on.
|
||||
*/
|
||||
|
@@ -1,53 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2019 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.core.computer;
|
||||
|
||||
import dan200.computercraft.api.lua.IComputerSystem;
|
||||
import dan200.computercraft.api.lua.ILuaAPIFactory;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.core.apis.ComputerAccess;
|
||||
import dan200.computercraft.core.apis.IAPIEnvironment;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Implementation of {@link IComputerAccess}/{@link IComputerSystem} for usage by externally registered APIs.
|
||||
*
|
||||
* @see ILuaAPIFactory
|
||||
* @see ApiWrapper
|
||||
*/
|
||||
public class ComputerSystem extends ComputerAccess implements IComputerSystem {
|
||||
private final IAPIEnvironment environment;
|
||||
|
||||
ComputerSystem(IAPIEnvironment environment) {
|
||||
super(environment);
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAttachmentName() {
|
||||
return "computer";
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getLabel() {
|
||||
return environment.getLabel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, IPeripheral> getAvailablePeripherals() {
|
||||
// TODO: Should this return peripherals on the current computer?
|
||||
return Map.of();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IPeripheral getAvailablePeripheral(String name) {
|
||||
return null;
|
||||
}
|
||||
}
|
@@ -5,6 +5,7 @@
|
||||
package dan200.computercraft.core.computer.computerthread;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.errorprone.annotations.Keep;
|
||||
import dan200.computercraft.core.ComputerContext;
|
||||
import dan200.computercraft.core.Logging;
|
||||
import dan200.computercraft.core.computer.TimeoutState;
|
||||
@@ -774,6 +775,7 @@ public final class ComputerThread implements ComputerScheduler {
|
||||
/**
|
||||
* The current state of this worker.
|
||||
*/
|
||||
@Keep
|
||||
private volatile ExecutorState $state = ExecutorState.IDLE;
|
||||
|
||||
/**
|
||||
@@ -784,6 +786,7 @@ public final class ComputerThread implements ComputerScheduler {
|
||||
* {@linkplain #afterWork()} finishes executing, we set this back to null and compute the difference between the
|
||||
* two, updating the {@link Metrics#JAVA_ALLOCATION} metric.
|
||||
*/
|
||||
@Keep
|
||||
private volatile @Nullable ThreadAllocation $threadAllocation = null;
|
||||
|
||||
/**
|
||||
|
@@ -8,6 +8,7 @@ import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import dan200.computercraft.core.apis.handles.ArrayByteChannel;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -82,6 +83,24 @@ public abstract class ArchiveMount<T extends ArchiveMount.FileEntry<T>> extends
|
||||
*/
|
||||
protected abstract byte[] getFileContents(String path, T file) throws IOException;
|
||||
|
||||
/**
|
||||
* Convert an absolute path to one relative to {@code root}. If this path is not a child of {@code root}, return
|
||||
* {@code null}.
|
||||
*
|
||||
* @param path The full path.
|
||||
* @param root The root directory to be relative to.
|
||||
* @return The relativised path, or {@code null}.
|
||||
*/
|
||||
protected static @Nullable String getLocalPath(String path, String root) {
|
||||
// Some packs seem to include files not under the root, so drop them immediately.
|
||||
if (!path.startsWith(root)) return null;
|
||||
|
||||
if (path.length() == root.length()) return "";
|
||||
|
||||
if (path.charAt(root.length()) != '/') return null;
|
||||
return path.substring(root.length() + 1);
|
||||
}
|
||||
|
||||
protected static class FileEntry<T extends FileEntry<T>> extends AbstractInMemoryMount.FileEntry<T> {
|
||||
long size = -1;
|
||||
}
|
||||
|
@@ -224,11 +224,11 @@ public class FileSystem {
|
||||
public synchronized void copy(String sourcePath, String destPath) throws FileSystemException {
|
||||
sourcePath = sanitizePath(sourcePath);
|
||||
destPath = sanitizePath(destPath);
|
||||
if (isReadOnly(destPath)) throw new FileSystemException("/" + destPath + ": " + ACCESS_DENIED);
|
||||
if (!exists(sourcePath)) throw new FileSystemException("/" + sourcePath + ": " + NO_SUCH_FILE);
|
||||
if (exists(destPath)) throw new FileSystemException("/" + destPath + ": " + FILE_EXISTS);
|
||||
if (isReadOnly(destPath)) throw new FileSystemException(destPath, ACCESS_DENIED);
|
||||
if (!exists(sourcePath)) throw new FileSystemException(sourcePath, NO_SUCH_FILE);
|
||||
if (exists(destPath)) throw new FileSystemException(destPath, FILE_EXISTS);
|
||||
if (contains(sourcePath, destPath)) {
|
||||
throw new FileSystemException("/" + sourcePath + ": Can't copy a directory inside itself");
|
||||
throw new FileSystemException(sourcePath, "Can't copy a directory inside itself");
|
||||
}
|
||||
copyRecursive(sourcePath, getMount(sourcePath), destPath, getMount(destPath), 0);
|
||||
}
|
||||
@@ -341,7 +341,7 @@ public class FileSystem {
|
||||
}
|
||||
}
|
||||
if (match == null) {
|
||||
throw new FileSystemException("/" + path + ": Invalid Path");
|
||||
throw new FileSystemException(path, "Invalid Path");
|
||||
}
|
||||
return match;
|
||||
}
|
||||
@@ -404,7 +404,7 @@ public class FileSystem {
|
||||
return String.join("/", outputParts);
|
||||
}
|
||||
|
||||
public static boolean contains(String pathA, String pathB) {
|
||||
private static boolean contains(String pathA, String pathB) {
|
||||
pathA = sanitizePath(pathA).toLowerCase(Locale.ROOT);
|
||||
pathB = sanitizePath(pathB).toLowerCase(Locale.ROOT);
|
||||
|
||||
@@ -421,7 +421,7 @@ public class FileSystem {
|
||||
}
|
||||
}
|
||||
|
||||
public static String toLocal(String path, String location) {
|
||||
static String toLocal(String path, String location) {
|
||||
path = sanitizePath(path);
|
||||
location = sanitizePath(location);
|
||||
|
||||
|
@@ -13,8 +13,12 @@ public class FileSystemException extends Exception {
|
||||
@Serial
|
||||
private static final long serialVersionUID = -2500631644868104029L;
|
||||
|
||||
FileSystemException(String s) {
|
||||
super(s);
|
||||
FileSystemException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
FileSystemException(String path, String message) {
|
||||
this("/" + path + ": " + message);
|
||||
}
|
||||
|
||||
public static FileSystemException of(IOException e) {
|
||||
|
@@ -49,10 +49,9 @@ public final class JarMount extends ArchiveMount<JarMount.FileEntry> implements
|
||||
while (zipEntries.hasMoreElements()) {
|
||||
var entry = zipEntries.nextElement();
|
||||
|
||||
var entryPath = entry.getName();
|
||||
if (!entryPath.startsWith(subPath)) continue;
|
||||
var localPath = getLocalPath(entry.getName(), subPath);
|
||||
if (localPath == null) continue;
|
||||
|
||||
var localPath = FileSystem.toLocal(entryPath, subPath);
|
||||
getOrCreateChild(root, localPath, x -> new FileEntry()).setup(entry);
|
||||
}
|
||||
}
|
||||
|
@@ -129,7 +129,7 @@ class MountWrapper {
|
||||
}
|
||||
|
||||
public void makeDirectory(String path) throws FileSystemException {
|
||||
if (writableMount == null) throw exceptionOf(path, ACCESS_DENIED);
|
||||
if (writableMount == null) throw new FileSystemException(path, ACCESS_DENIED);
|
||||
|
||||
path = toLocal(path);
|
||||
try {
|
||||
@@ -140,7 +140,7 @@ class MountWrapper {
|
||||
}
|
||||
|
||||
public void delete(String path) throws FileSystemException {
|
||||
if (writableMount == null) throw exceptionOf(path, ACCESS_DENIED);
|
||||
if (writableMount == null) throw new FileSystemException(path, ACCESS_DENIED);
|
||||
|
||||
path = toLocal(path);
|
||||
try {
|
||||
@@ -151,7 +151,7 @@ class MountWrapper {
|
||||
}
|
||||
|
||||
public void rename(String source, String dest) throws FileSystemException {
|
||||
if (writableMount == null) throw exceptionOf(source, ACCESS_DENIED);
|
||||
if (writableMount == null) throw new FileSystemException(source, ACCESS_DENIED);
|
||||
|
||||
source = toLocal(source);
|
||||
dest = toLocal(dest);
|
||||
@@ -168,7 +168,7 @@ class MountWrapper {
|
||||
}
|
||||
|
||||
public SeekableByteChannel openForWrite(String path, Set<OpenOption> options) throws FileSystemException {
|
||||
if (writableMount == null) throw exceptionOf(path, ACCESS_DENIED);
|
||||
if (writableMount == null) throw new FileSystemException(path, ACCESS_DENIED);
|
||||
|
||||
path = toLocal(path);
|
||||
try {
|
||||
@@ -206,10 +206,6 @@ class MountWrapper {
|
||||
|
||||
private FileSystemException localExceptionOf(String path, String message) {
|
||||
if (!location.isEmpty()) path = path.isEmpty() ? location : location + "/" + path;
|
||||
return exceptionOf(path, message);
|
||||
}
|
||||
|
||||
private static FileSystemException exceptionOf(String path, String message) {
|
||||
return new FileSystemException("/" + path + ": " + message);
|
||||
return new FileSystemException(path, message);
|
||||
}
|
||||
}
|
||||
|
@@ -7,9 +7,13 @@
|
||||
-- @module textutils
|
||||
-- @since 1.2
|
||||
|
||||
local expect = dofile("rom/modules/main/cc/expect.lua")
|
||||
local pgk_env = setmetatable({}, { __index = _ENV })
|
||||
pgk_env.require = dofile("rom/modules/main/cc/require.lua").make(pgk_env, "rom/modules/main")
|
||||
local require = pgk_env.require
|
||||
|
||||
local expect = require("cc.expect")
|
||||
local expect, field = expect.expect, expect.field
|
||||
local wrap = dofile("rom/modules/main/cc/strings.lua").wrap
|
||||
local wrap = require("cc.strings").wrap
|
||||
|
||||
--- Slowly writes string text at current cursor position,
|
||||
-- character-by-character.
|
||||
|
@@ -1,3 +1,15 @@
|
||||
# New features in CC: Tweaked 1.112.0
|
||||
|
||||
* Report a custom error when using `!` instead of `not`.
|
||||
* Update several translations (zyxkad, MineKID-LP).
|
||||
* Add `cc.strings.split` function.
|
||||
|
||||
Several bug fixes:
|
||||
* Fix `drive.getAudioTitle` returning `nil` when no disk is inserted.
|
||||
* Preserve item data when upgrading pocket computers.
|
||||
* Add missing bounds check to `cc.strings.wrap` (Lupus950).
|
||||
* Fix modems not moving with Create contraptions.
|
||||
|
||||
# New features in CC: Tweaked 1.111.0
|
||||
|
||||
* Update several translations (Ale32bit).
|
||||
|
@@ -1,13 +1,13 @@
|
||||
New features in CC: Tweaked 1.111.0
|
||||
New features in CC: Tweaked 1.112.0
|
||||
|
||||
* Update several translations (Ale32bit).
|
||||
* Split up turtle textures into individual textures.
|
||||
* Add `r+`/`w+` support to the `io` library.
|
||||
* Warn when capabilities are not registered and Optifine is installed.
|
||||
* Report a custom error when using `!` instead of `not`.
|
||||
* Update several translations (zyxkad, MineKID-LP).
|
||||
* Add `cc.strings.split` function.
|
||||
|
||||
Several bug fixes:
|
||||
* Allow planks to be used for building in "adventure" (dan200).
|
||||
* Fix `disk.getAudioTitle()` returning untranslated strings for some modded discs.
|
||||
* Fix crash when right clicking turtles in spectator.
|
||||
* Fix `drive.getAudioTitle` returning `nil` when no disk is inserted.
|
||||
* Preserve item data when upgrading pocket computers.
|
||||
* Add missing bounds check to `cc.strings.wrap` (Lupus950).
|
||||
* Fix modems not moving with Create contraptions.
|
||||
|
||||
Type "help changelog" to see the full version history.
|
||||
|
@@ -118,8 +118,8 @@ end
|
||||
--- Expect a number to be within a specific range.
|
||||
--
|
||||
-- @tparam number num The value to check.
|
||||
-- @tparam number min The minimum value, if nil then `-math.huge` is used.
|
||||
-- @tparam number max The maximum value, if nil then `math.huge` is used.
|
||||
-- @tparam[opt=-math.huge] number min The minimum value.
|
||||
-- @tparam[opt=math.huge] number max The maximum value.
|
||||
-- @return The given `value`.
|
||||
-- @throws If the value is outside of the allowed range.
|
||||
-- @since 1.96.0
|
||||
|
@@ -8,7 +8,8 @@
|
||||
-- @since 1.95.0
|
||||
-- @see textutils For additional string related utilities.
|
||||
|
||||
local expect = (require and require("cc.expect") or dofile("rom/modules/main/cc/expect.lua")).expect
|
||||
local expect = require("cc.expect")
|
||||
local expect, range = expect.expect, expect.range
|
||||
|
||||
--[[- Wraps a block of text, so that each line fits within the given width.
|
||||
|
||||
@@ -32,7 +33,7 @@ local function wrap(text, width)
|
||||
expect(1, text, "string")
|
||||
expect(2, width, "number", "nil")
|
||||
width = width or term.getSize()
|
||||
|
||||
range(width, 1)
|
||||
|
||||
local lines, lines_n, current_line = {}, 0, ""
|
||||
local function push_line()
|
||||
@@ -109,7 +110,63 @@ local function ensure_width(line, width)
|
||||
return line
|
||||
end
|
||||
|
||||
--[[- Split a string into parts, each separated by a deliminator.
|
||||
|
||||
For instance, splitting the string `"a b c"` with the deliminator `" "`, would
|
||||
return a table with three strings: `"a"`, `"b"`, and `"c"`.
|
||||
|
||||
By default, the deliminator is given as a [Lua pattern][pattern]. Passing `true`
|
||||
to the `plain` argument will cause the deliminator to be treated as a litteral
|
||||
string.
|
||||
|
||||
[pattern]: https://www.lua.org/manual/5.3/manual.html#6.4.1
|
||||
|
||||
@tparam string str The string to split.
|
||||
@tparam string deliminator The pattern to split this string on.
|
||||
@tparam[opt=false] boolean plain Treat the deliminator as a plain string, rather than a pattern.
|
||||
@tparam[opt] number limit The maximum number of elements in the returned list.
|
||||
@treturn { string... } The list of split strings.
|
||||
|
||||
@usage Split a string into words.
|
||||
|
||||
require "cc.strings".split("This is a sentence.", "%s+")
|
||||
|
||||
@usage Split a string by "-" into at most 3 elements.
|
||||
|
||||
require "cc.strings".split("a-separated-string-of-sorts", "-", true, 3)
|
||||
|
||||
@see table.concat To join strings together.
|
||||
|
||||
@since 1.112.0
|
||||
]]
|
||||
local function split(str, deliminator, plain, limit)
|
||||
expect(1, str, "string")
|
||||
expect(2, deliminator, "string")
|
||||
expect(3, plain, "boolean", "nil")
|
||||
expect(4, limit, "number", "nil")
|
||||
|
||||
local out, out_n, pos = {}, 0, 1
|
||||
while not limit or out_n < limit - 1 do
|
||||
local start, finish = str:find(deliminator, pos, plain)
|
||||
if not start then break end
|
||||
|
||||
out_n = out_n + 1
|
||||
out[out_n] = str:sub(pos, start - 1)
|
||||
|
||||
-- Require us to advance by at least one character.
|
||||
if finish < start then error("separator is empty", 2) end
|
||||
|
||||
pos = finish + 1
|
||||
end
|
||||
|
||||
if pos == 1 then return { str } end
|
||||
|
||||
out[out_n + 1] = str:sub(pos)
|
||||
return out
|
||||
end
|
||||
|
||||
return {
|
||||
wrap = wrap,
|
||||
ensure_width = ensure_width,
|
||||
split = split,
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@
|
||||
--
|
||||
-- SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
describe("cc.pretty", function()
|
||||
describe("cc.strings", function()
|
||||
local str = require("cc.strings")
|
||||
|
||||
describe("wrap", function()
|
||||
@@ -11,6 +11,8 @@ describe("cc.pretty", function()
|
||||
str.wrap("test string is long", 11)
|
||||
expect.error(str.wrap, nil):eq("bad argument #1 (string expected, got nil)")
|
||||
expect.error(str.wrap, "", false):eq("bad argument #2 (number expected, got boolean)")
|
||||
|
||||
expect.error(str.wrap, "", 0):eq("number outside of range (expected 0 to be within 1 and inf)")
|
||||
end)
|
||||
|
||||
it("wraps lines", function()
|
||||
@@ -42,4 +44,33 @@ describe("cc.pretty", function()
|
||||
expect(str.ensure_width("test string is long", 15)):eq("test string is ")
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("split", function()
|
||||
it("splits with empty segments", function()
|
||||
expect(str.split("", "%-")):same { "" }
|
||||
expect(str.split("-", "%-")):same { "", "" }
|
||||
expect(str.split("---", "%-")):same { "", "", "", "" }
|
||||
expect(str.split("-a", "%-")):same { "", "a" }
|
||||
expect(str.split("a-", "%-")):same { "a", "" }
|
||||
end)
|
||||
|
||||
it("cannot split with an empty separator", function()
|
||||
expect.error(str.split, "abc", ""):eq("separator is empty")
|
||||
end)
|
||||
|
||||
it("splits on patterns", function()
|
||||
expect(str.split("a.bcd ef", "%W+")):same { "a", "bcd", "ef" }
|
||||
end)
|
||||
|
||||
it("splits on literal strings", function()
|
||||
expect(str.split("a-bcd-ef", "-", true)):same { "a", "bcd", "ef" }
|
||||
end)
|
||||
|
||||
it("accepts a limit", function()
|
||||
expect(str.split("foo-bar-baz-qux-quyux", "-", true, 3)):same { "foo", "bar", "baz-qux-quyux" }
|
||||
expect(str.split("foo-bar-baz", "-", true, 5)):same { "foo", "bar", "baz" }
|
||||
expect(str.split("foo-bar-baz", "-", true, 1)):same { "foo-bar-baz" }
|
||||
expect(str.split("foo-bar-baz", "-", true, 1)):same { "foo-bar-baz" }
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
@@ -69,6 +69,7 @@ dependencies {
|
||||
exclude("net.fabricmc", "fabric-loader")
|
||||
exclude("net.fabricmc.fabric-api")
|
||||
}
|
||||
modCompileOnly(libs.create.fabric) { isTransitive = false }
|
||||
|
||||
modClientRuntimeOnly(libs.bundles.externalMods.fabric.runtime) {
|
||||
exclude("net.fabricmc", "fabric-loader")
|
||||
|
@@ -13,6 +13,7 @@ import dan200.computercraft.shared.command.CommandComputerCraft;
|
||||
import dan200.computercraft.shared.config.Config;
|
||||
import dan200.computercraft.shared.config.ConfigSpec;
|
||||
import dan200.computercraft.shared.details.FluidDetails;
|
||||
import dan200.computercraft.shared.integration.CreateIntegration;
|
||||
import dan200.computercraft.shared.network.NetworkMessages;
|
||||
import dan200.computercraft.shared.network.client.UpgradesLoadedMessage;
|
||||
import dan200.computercraft.shared.network.server.ServerNetworking;
|
||||
@@ -34,6 +35,7 @@ import net.fabricmc.fabric.api.loot.v2.LootTableEvents;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
|
||||
import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener;
|
||||
import net.fabricmc.fabric.api.resource.ResourceManagerHelper;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.PackType;
|
||||
@@ -119,6 +121,8 @@ public class ComputerCraft {
|
||||
ComputerCraftAPI.registerGenericSource(new InventoryMethods());
|
||||
|
||||
Peripherals.addGenericLookup((world, pos, state, blockEntity, side, invalidate) -> InventoryMethods.extractContainer(world, pos, state, blockEntity, side));
|
||||
|
||||
if (FabricLoader.getInstance().isModLoaded(CreateIntegration.ID)) CreateIntegration.setup();
|
||||
}
|
||||
|
||||
private record ReloadListener(String name, PreparableReloadListener listener)
|
||||
|
@@ -27,7 +27,7 @@
|
||||
"dan200.computercraft.data.FabricDataGenerators"
|
||||
],
|
||||
"jei_mod_plugin": [
|
||||
"dan200.computercraft.shared.integration.jei.JEIComputerCraft"
|
||||
"dan200.computercraft.client.integration.jei.JEIComputerCraft"
|
||||
],
|
||||
"rei_client": [
|
||||
"dan200.computercraft.client.integration.rei.REIComputerCraft"
|
||||
|
@@ -121,6 +121,11 @@ dependencies {
|
||||
libs.bundles.externalMods.forge.compile.get().map { compileOnly(fg.deobf(it)) }
|
||||
libs.bundles.externalMods.forge.runtime.get().map { runtimeOnly(fg.deobf(it)) }
|
||||
|
||||
// fg.debof only accepts a closure to configure the dependency, so doesn't work with Kotlin. We create and configure
|
||||
// the dep first, and then pass it off to ForgeGradle.
|
||||
(create(variantOf(libs.create.forge) { classifier("slim") }.get()) as ExternalModuleDependency)
|
||||
.apply { isTransitive = false }.let { compileOnly(fg.deobf(it)) }
|
||||
|
||||
// Depend on our other projects.
|
||||
api(commonClasses(project(":forge-api"))) { cct.exclude(this) }
|
||||
clientApi(clientClasses(project(":forge-api"))) { cct.exclude(this) }
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user