mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-10-16 14:37:39 +00:00
Compare commits
71 Commits
v1.20.1-1.
...
v1.20.1-1.
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d24984c1d5 | ||
![]() |
8080dcdd9e | ||
![]() |
d7cea55e2a | ||
![]() |
9b2f974a81 | ||
![]() |
43770fa9bd | ||
![]() |
80c7a54ad4 | ||
![]() |
e57b6fede2 | ||
![]() |
34a2fd039f | ||
![]() |
3299d0e72a | ||
![]() |
b89e2615db | ||
![]() |
cdcd82679c | ||
![]() |
cdfa866760 | ||
![]() |
aa8078ddeb | ||
![]() |
7e53c19d74 | ||
![]() |
b7a8432cfb | ||
![]() |
356c8e8aeb | ||
![]() |
ed283155f7 | ||
![]() |
87dfad026e | ||
![]() |
bb97c465d9 | ||
![]() |
9484315d37 | ||
![]() |
be59f1a875 | ||
![]() |
bfb28b4710 | ||
![]() |
216f0adb3c | ||
![]() |
77af4bc213 | ||
![]() |
5abab982c7 | ||
![]() |
764e1aa332 | ||
![]() |
c47718b09d | ||
![]() |
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 | ||
![]() |
25b8a65c5c | ||
![]() |
e4236824d7 | ||
![]() |
cfd11ffa92 | ||
![]() |
ce133a5e66 | ||
![]() |
038fbc1ed1 | ||
![]() |
c582fb521c | ||
![]() |
af21792844 | ||
![]() |
9fbb1070ef | ||
![]() |
1944995c33 | ||
![]() |
ac851a795b | ||
![]() |
334761788a | ||
![]() |
5af3e15dd5 | ||
![]() |
d9b0cc7075 |
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
|
||||
|
19
.github/workflows/make-doc.sh
vendored
19
.github/workflows/make-doc.sh
vendored
@@ -1,19 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -eu
|
||||
|
||||
DEST="${GITHUB_REF#refs/*/}"
|
||||
echo "Uploading docs to https://tweaked.cc/$DEST"
|
||||
|
||||
# Setup ssh key
|
||||
mkdir -p "$HOME/.ssh/"
|
||||
echo "$SSH_KEY" > "$HOME/.ssh/key"
|
||||
chmod 600 "$HOME/.ssh/key"
|
||||
|
||||
# And upload
|
||||
rsync -avc -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no -p $SSH_PORT" \
|
||||
"$GITHUB_WORKSPACE/projects/web/build/site/" \
|
||||
"$SSH_USER@$SSH_HOST:/$DEST"
|
||||
rsync -avc -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no -p $SSH_PORT" \
|
||||
"$GITHUB_WORKSPACE/projects/common-api/build/docs/javadoc/" \
|
||||
"$SSH_USER@$SSH_HOST:/$DEST/javadoc"
|
25
.github/workflows/make-doc.yml
vendored
25
.github/workflows/make-doc.yml
vendored
@@ -11,30 +11,25 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Clone repository
|
||||
- name: 📥 Clone repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Java
|
||||
- name: 📥 Set up Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Setup Gradle
|
||||
- name: 📥 Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
with:
|
||||
cache-read-only: ${{ !startsWith(github.ref, 'refs/heads/mc-') }}
|
||||
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew compileJava --no-daemon || ./gradlew compileJava --no-daemon
|
||||
- name: ⚒️ Generate documentation
|
||||
run: ./gradlew docWebsite --no-daemon
|
||||
|
||||
- name: Generate documentation
|
||||
run: ./gradlew docWebsite :common-api:javadoc --no-daemon
|
||||
|
||||
- name: Upload documentation
|
||||
run: .github/workflows/make-doc.sh 2> /dev/null
|
||||
env:
|
||||
SSH_KEY: ${{ secrets.SSH_KEY }}
|
||||
SSH_USER: ${{ secrets.SSH_USER }}
|
||||
SSH_HOST: ${{ secrets.SSH_HOST }}
|
||||
SSH_PORT: ${{ secrets.SSH_PORT }}
|
||||
- name: 📤 Upload Jar
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Documentation
|
||||
path: ./projects/web/build/site/
|
||||
|
@@ -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/**"]
|
||||
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 {
|
||||
|
@@ -143,7 +143,7 @@ fun getNextVersion(version: String): String {
|
||||
val lastIndex = mainVersion.lastIndexOf('.')
|
||||
if (lastIndex < 0) throw IllegalArgumentException("Cannot parse version format \"$version\"")
|
||||
val lastVersion = try {
|
||||
version.substring(lastIndex + 1).toInt()
|
||||
mainVersion.substring(lastIndex + 1).toInt()
|
||||
} catch (e: NumberFormatException) {
|
||||
throw IllegalArgumentException("Cannot parse version format \"$version\"", e)
|
||||
}
|
||||
@@ -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
|
||||
|
20
doc/index.md
20
doc/index.md
@@ -4,7 +4,14 @@ SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers
|
||||
SPDX-License-Identifier: MPL-2.0
|
||||
-->
|
||||
|
||||
# 
|
||||
<h1>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="logo-darkmode.png">
|
||||
<source media="(prefers-color-scheme: light)" srcset="logo.png">
|
||||
<img alt="CC: Tweaked" src="logo.png">
|
||||
</picture>
|
||||
</h1>
|
||||
|
||||
CC: Tweaked is a mod for Minecraft which adds programmable computers, turtles and more to the game. A fork of the
|
||||
much-beloved [ComputerCraft], it continues its legacy with improved performance and stability, along with a wealth of
|
||||
new features.
|
||||
@@ -38,12 +45,16 @@ little daunting getting started. Thankfully, there's several fantastic tutorials
|
||||
|
||||
- [Direwolf20's ComputerCraft tutorials](https://www.youtube.com/watch?v=wrUHUhfCY5A "ComputerCraft Tutorial Episode 1 - HELP! and Hello World")
|
||||
- [Sethbling's ComputerCraft series](https://www.youtube.com/watch?v=DSsx4VSe-Uk "Programming Tutorial with Minecraft Turtles -- Ep. 1: Intro to Turtles and If-Then-Else_End")
|
||||
- [Lyqyd's Computer Basics 1](http://www.computercraft.info/forums2/index.php?/topic/15033-computer-basics-i/ "Computer Basics I")
|
||||
- [Lyqyd's Computer Basics 1](https://ccf.squiddev.cc/forums2/index.php?/topic/15033-computer-basics-i/ "Computer Basics I")
|
||||
|
||||
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].
|
||||
@@ -58,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"
|
||||
|
@@ -45,12 +45,16 @@ little daunting getting started. Thankfully, there's several fantastic tutorials
|
||||
|
||||
- [Direwolf20's ComputerCraft tutorials](https://www.youtube.com/watch?v=wrUHUhfCY5A "ComputerCraft Tutorial Episode 1 - HELP! and Hello World")
|
||||
- [Sethbling's ComputerCraft series](https://www.youtube.com/watch?v=DSsx4VSe-Uk "Programming Tutorial with Minecraft Turtles -- Ep. 1: Intro to Turtles and If-Then-Else_End")
|
||||
- [Lyqyd's Computer Basics 1](http://www.computercraft.info/forums2/index.php?/topic/15033-computer-basics-i/ "Computer Basics I")
|
||||
- [Lyqyd's Computer Basics 1](https://ccf.squiddev.cc/forums2/index.php?/topic/15033-computer-basics-i/ "Computer Basics I")
|
||||
|
||||
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,12 +70,12 @@ 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,
|
||||
you can no longer type `turtle/excavate` to run `/rom/programs/turtle/excavate.lua`.
|
||||
|
||||
[flattening]: https://minecraft.wiki/w/Java_Edition_1.13/Flattening
|
||||
[legal_data_pack]: https://minecraft.gamepedia.com/Tutorials/Creating_a_data_pack#Legal_characters
|
||||
[legal_data_pack]: https://minecraft.wiki/w/Tutorials/Creating_a_data_pack#Legal_characters
|
||||
[datapack-example]: https://github.com/cc-tweaked/datapack-example "An example datapack for CC: Tweaked"
|
||||
|
@@ -10,7 +10,7 @@ kotlin.jvm.target.validation.mode=error
|
||||
|
||||
# Mod properties
|
||||
isUnstable=false
|
||||
modVersion=1.111.0
|
||||
modVersion=1.113.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,19 +61,19 @@ 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"
|
||||
ideaExt = "1.1.7"
|
||||
illuaminate = "0.1.0-71-g378d86e"
|
||||
illuaminate = "0.1.0-73-g43ee16c"
|
||||
librarian = "1.+"
|
||||
lwjgl = "3.3.3"
|
||||
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" }
|
||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
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.10-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
7
gradlew
vendored
7
gradlew
vendored
@@ -15,6 +15,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
@@ -55,7 +57,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/.
|
||||
@@ -84,7 +86,8 @@ done
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
||||
' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
2
gradlew.bat
vendored
2
gradlew.bat
vendored
@@ -13,6 +13,8 @@
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
|
@@ -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");
|
||||
}
|
@@ -13,7 +13,7 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* An item detail provider for {@link ItemStack}'s whose {@link Item} has a specific type.
|
||||
* An item detail provider for {@link ItemStack}s whose {@link Item} has a specific type.
|
||||
*
|
||||
* @param <T> The type the stack's item must have.
|
||||
*/
|
||||
@@ -22,7 +22,7 @@ public abstract class BasicItemDetailProvider<T> implements DetailProvider<ItemS
|
||||
private final @Nullable String namespace;
|
||||
|
||||
/**
|
||||
* Create a new item detail provider. Meta will be inserted into a new sub-map named as per {@code namespace}.
|
||||
* Create a new item detail provider. Details will be inserted into a new sub-map named as per {@code namespace}.
|
||||
*
|
||||
* @param itemType The type the stack's item must have.
|
||||
* @param namespace The namespace to use for this provider.
|
||||
@@ -34,7 +34,7 @@ public abstract class BasicItemDetailProvider<T> implements DetailProvider<ItemS
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new item detail provider. Meta will be inserted directly into the results.
|
||||
* Create a new item detail provider. Details will be inserted directly into the results.
|
||||
*
|
||||
* @param itemType The type the stack's item must have.
|
||||
*/
|
||||
@@ -53,21 +53,18 @@ public abstract class BasicItemDetailProvider<T> implements DetailProvider<ItemS
|
||||
* @param stack The item stack to provide details for.
|
||||
* @param item The item to provide details for.
|
||||
*/
|
||||
public abstract void provideDetails(
|
||||
Map<? super String, Object> data, ItemStack stack, T item
|
||||
);
|
||||
public abstract void provideDetails(Map<? super String, Object> data, ItemStack stack, T item);
|
||||
|
||||
@Override
|
||||
public void provideDetails(Map<? super String, Object> data, ItemStack stack) {
|
||||
public final void provideDetails(Map<? super String, Object> data, ItemStack stack) {
|
||||
var item = stack.getItem();
|
||||
if (!itemType.isInstance(item)) return;
|
||||
|
||||
// If `namespace` is specified, insert into a new data map instead of the existing one.
|
||||
Map<? super String, Object> child = namespace == null ? data : new HashMap<>();
|
||||
|
||||
provideDetails(child, stack, itemType.cast(item));
|
||||
|
||||
if (namespace != null) {
|
||||
if (namespace == null) {
|
||||
provideDetails(data, stack, itemType.cast(item));
|
||||
} else {
|
||||
Map<? super String, Object> child = new HashMap<>();
|
||||
provideDetails(child, stack, itemType.cast(item));
|
||||
data.put(namespace, child);
|
||||
}
|
||||
}
|
||||
|
@@ -26,7 +26,7 @@ public interface DetailRegistry<T> {
|
||||
* @param provider The detail provider to register.
|
||||
* @see DetailProvider
|
||||
*/
|
||||
void addProvider(DetailProvider<T> provider);
|
||||
void addProvider(DetailProvider<? super T> provider);
|
||||
|
||||
/**
|
||||
* Compute basic details about an object. This is cheaper than computing all details operation, and so is suitable
|
||||
|
@@ -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();
|
||||
|
||||
|
@@ -5,7 +5,7 @@
|
||||
package dan200.computercraft.api.turtle;
|
||||
|
||||
/**
|
||||
* An enum representing the two sides of the turtle that a turtle turtle might reside.
|
||||
* An enum representing the two sides of the turtle that a turtle upgrade might reside.
|
||||
*/
|
||||
public enum TurtleSide {
|
||||
/**
|
||||
|
@@ -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)
|
||||
|
@@ -13,6 +13,7 @@ import dan200.computercraft.api.client.turtle.RegisterTurtleUpgradeModeller;
|
||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
|
||||
import dan200.computercraft.client.gui.*;
|
||||
import dan200.computercraft.client.pocket.ClientPocketComputers;
|
||||
import dan200.computercraft.client.render.CustomLecternRenderer;
|
||||
import dan200.computercraft.client.render.RenderTypes;
|
||||
import dan200.computercraft.client.render.TurtleBlockEntityRenderer;
|
||||
import dan200.computercraft.client.render.monitor.MonitorBlockEntityRenderer;
|
||||
@@ -25,7 +26,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;
|
||||
@@ -74,6 +74,7 @@ public final class ClientRegistry {
|
||||
BlockEntityRenderers.register(ModRegistry.BlockEntities.MONITOR_ADVANCED.get(), MonitorBlockEntityRenderer::new);
|
||||
BlockEntityRenderers.register(ModRegistry.BlockEntities.TURTLE_NORMAL.get(), TurtleBlockEntityRenderer::new);
|
||||
BlockEntityRenderers.register(ModRegistry.BlockEntities.TURTLE_ADVANCED.get(), TurtleBlockEntityRenderer::new);
|
||||
BlockEntityRenderers.register(ModRegistry.BlockEntities.LECTERN.get(), CustomLecternRenderer::new);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,7 +84,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 +91,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);
|
||||
|
@@ -6,15 +6,22 @@ package dan200.computercraft.client.gui;
|
||||
|
||||
import com.mojang.blaze3d.vertex.Tesselator;
|
||||
import dan200.computercraft.core.terminal.TextBuffer;
|
||||
import dan200.computercraft.shared.common.HeldItemMenu;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.media.PrintoutMenu;
|
||||
import dan200.computercraft.shared.media.items.PrintoutItem;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
import net.minecraft.world.inventory.ContainerListener;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
import static dan200.computercraft.client.render.PrintoutRenderer.*;
|
||||
import static dan200.computercraft.client.render.RenderTypes.FULL_BRIGHT_LIGHTMAP;
|
||||
|
||||
@@ -23,40 +30,75 @@ import static dan200.computercraft.client.render.RenderTypes.FULL_BRIGHT_LIGHTMA
|
||||
*
|
||||
* @see dan200.computercraft.client.render.PrintoutRenderer
|
||||
*/
|
||||
public class PrintoutScreen extends AbstractContainerScreen<HeldItemMenu> {
|
||||
private final boolean book;
|
||||
private final int pages;
|
||||
private final TextBuffer[] text;
|
||||
private final TextBuffer[] colours;
|
||||
private int page;
|
||||
public final class PrintoutScreen extends AbstractContainerScreen<PrintoutMenu> implements ContainerListener {
|
||||
private PrintoutInfo printout = PrintoutInfo.DEFAULT;
|
||||
private int page = 0;
|
||||
|
||||
public PrintoutScreen(HeldItemMenu container, Inventory player, Component title) {
|
||||
public PrintoutScreen(PrintoutMenu container, Inventory player, Component title) {
|
||||
super(container, player, title);
|
||||
|
||||
imageHeight = Y_SIZE;
|
||||
}
|
||||
|
||||
var text = PrintoutItem.getText(container.getStack());
|
||||
this.text = new TextBuffer[text.length];
|
||||
for (var i = 0; i < this.text.length; i++) this.text[i] = new TextBuffer(text[i]);
|
||||
private void setPrintout(ItemStack stack) {
|
||||
var text = PrintoutItem.getText(stack);
|
||||
var textBuffers = new TextBuffer[text.length];
|
||||
for (var i = 0; i < textBuffers.length; i++) textBuffers[i] = new TextBuffer(text[i]);
|
||||
|
||||
var colours = PrintoutItem.getColours(container.getStack());
|
||||
this.colours = new TextBuffer[colours.length];
|
||||
for (var i = 0; i < this.colours.length; i++) this.colours[i] = new TextBuffer(colours[i]);
|
||||
var colours = PrintoutItem.getColours(stack);
|
||||
var colourBuffers = new TextBuffer[colours.length];
|
||||
for (var i = 0; i < colours.length; i++) colourBuffers[i] = new TextBuffer(colours[i]);
|
||||
|
||||
page = 0;
|
||||
pages = Math.max(this.text.length / PrintoutItem.LINES_PER_PAGE, 1);
|
||||
book = ((PrintoutItem) container.getStack().getItem()).getType() == PrintoutItem.Type.BOOK;
|
||||
var pages = Math.max(text.length / PrintoutItem.LINES_PER_PAGE, 1);
|
||||
var book = stack.is(ModRegistry.Items.PRINTED_BOOK.get());
|
||||
|
||||
printout = new PrintoutInfo(pages, book, textBuffers, colourBuffers);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
super.init();
|
||||
menu.addSlotListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removed() {
|
||||
menu.removeSlotListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void slotChanged(AbstractContainerMenu menu, int slot, ItemStack stack) {
|
||||
if (slot == 0) setPrintout(stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dataChanged(AbstractContainerMenu menu, int slot, int data) {
|
||||
if (slot == PrintoutMenu.DATA_CURRENT_PAGE) page = data;
|
||||
}
|
||||
|
||||
private void setPage(int page) {
|
||||
this.page = page;
|
||||
|
||||
var gameMode = Objects.requireNonNull(Objects.requireNonNull(minecraft).gameMode);
|
||||
gameMode.handleInventoryButtonClick(menu.containerId, PrintoutMenu.PAGE_BUTTON_OFFSET + page);
|
||||
}
|
||||
|
||||
private void previousPage() {
|
||||
if (page > 0) setPage(page - 1);
|
||||
}
|
||||
|
||||
private void nextPage() {
|
||||
if (page < printout.pages() - 1) setPage(page + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean keyPressed(int key, int scancode, int modifiers) {
|
||||
if (key == GLFW.GLFW_KEY_RIGHT) {
|
||||
if (page < pages - 1) page++;
|
||||
nextPage();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key == GLFW.GLFW_KEY_LEFT) {
|
||||
if (page > 0) page--;
|
||||
previousPage();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -68,13 +110,13 @@ public class PrintoutScreen extends AbstractContainerScreen<HeldItemMenu> {
|
||||
if (super.mouseScrolled(x, y, delta)) return true;
|
||||
if (delta < 0) {
|
||||
// Scroll up goes to the next page
|
||||
if (page < pages - 1) page++;
|
||||
nextPage();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (delta > 0) {
|
||||
// Scroll down goes to the previous page
|
||||
if (page > 0) page--;
|
||||
previousPage();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -85,8 +127,9 @@ public class PrintoutScreen extends AbstractContainerScreen<HeldItemMenu> {
|
||||
protected void renderBg(GuiGraphics graphics, float partialTicks, int mouseX, int mouseY) {
|
||||
// Draw the printout
|
||||
var renderer = MultiBufferSource.immediate(Tesselator.getInstance().getBuilder());
|
||||
drawBorder(graphics.pose(), renderer, leftPos, topPos, 0, page, pages, book, FULL_BRIGHT_LIGHTMAP);
|
||||
drawText(graphics.pose(), renderer, leftPos + X_TEXT_MARGIN, topPos + Y_TEXT_MARGIN, PrintoutItem.LINES_PER_PAGE * page, FULL_BRIGHT_LIGHTMAP, text, colours);
|
||||
|
||||
drawBorder(graphics.pose(), renderer, leftPos, topPos, 0, page, printout.pages(), printout.book(), FULL_BRIGHT_LIGHTMAP);
|
||||
drawText(graphics.pose(), renderer, leftPos + X_TEXT_MARGIN, topPos + Y_TEXT_MARGIN, PrintoutItem.LINES_PER_PAGE * page, FULL_BRIGHT_LIGHTMAP, printout.text(), printout.colour());
|
||||
renderer.endBatch();
|
||||
}
|
||||
|
||||
@@ -105,4 +148,18 @@ public class PrintoutScreen extends AbstractContainerScreen<HeldItemMenu> {
|
||||
protected void renderLabels(GuiGraphics graphics, int mouseX, int mouseY) {
|
||||
// Skip rendering labels.
|
||||
}
|
||||
|
||||
record PrintoutInfo(int pages, boolean book, TextBuffer[] text, TextBuffer[] colour) {
|
||||
public static final PrintoutInfo DEFAULT;
|
||||
|
||||
static {
|
||||
var textLines = new TextBuffer[PrintoutItem.LINES_PER_PAGE];
|
||||
Arrays.fill(textLines, new TextBuffer(" ".repeat(PrintoutItem.LINE_MAX_LENGTH)));
|
||||
|
||||
var colourLines = new TextBuffer[PrintoutItem.LINES_PER_PAGE];
|
||||
Arrays.fill(colourLines, new TextBuffer("f".repeat(PrintoutItem.LINE_MAX_LENGTH)));
|
||||
|
||||
DEFAULT = new PrintoutInfo(1, false, textLines, colourLines);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
@@ -0,0 +1,117 @@
|
||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.client.model;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.client.render.CustomLecternRenderer;
|
||||
import dan200.computercraft.shared.media.items.PrintoutItem;
|
||||
import net.minecraft.client.model.geom.ModelPart;
|
||||
import net.minecraft.client.model.geom.PartPose;
|
||||
import net.minecraft.client.model.geom.builders.CubeListBuilder;
|
||||
import net.minecraft.client.model.geom.builders.MeshDefinition;
|
||||
import net.minecraft.client.resources.model.Material;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.inventory.InventoryMenu;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A model for {@linkplain PrintoutItem printouts} placed on a lectern.
|
||||
* <p>
|
||||
* This provides two models, {@linkplain #renderPages(PoseStack, VertexConsumer, int, int, int) one for a variable
|
||||
* number of pages}, and {@linkplain #renderBook(PoseStack, VertexConsumer, int, int) one for books}.
|
||||
*
|
||||
* @see CustomLecternRenderer
|
||||
*/
|
||||
public class LecternPrintoutModel {
|
||||
public static final ResourceLocation TEXTURE = new ResourceLocation(ComputerCraftAPI.MOD_ID, "entity/printout");
|
||||
public static final Material MATERIAL = new Material(InventoryMenu.BLOCK_ATLAS, TEXTURE);
|
||||
|
||||
private static final int TEXTURE_WIDTH = 32;
|
||||
private static final int TEXTURE_HEIGHT = 32;
|
||||
|
||||
private static final String PAGE_1 = "page_1";
|
||||
private static final String PAGE_2 = "page_2";
|
||||
private static final String PAGE_3 = "page_3";
|
||||
private static final List<String> PAGES = List.of(PAGE_1, PAGE_2, PAGE_3);
|
||||
|
||||
private final ModelPart pagesRoot;
|
||||
private final ModelPart bookRoot;
|
||||
private final ModelPart[] pages;
|
||||
|
||||
public LecternPrintoutModel() {
|
||||
pagesRoot = buildPages();
|
||||
bookRoot = buildBook();
|
||||
pages = PAGES.stream().map(pagesRoot::getChild).toArray(ModelPart[]::new);
|
||||
}
|
||||
|
||||
private static ModelPart buildPages() {
|
||||
var mesh = new MeshDefinition();
|
||||
var parts = mesh.getRoot();
|
||||
parts.addOrReplaceChild(
|
||||
PAGE_1,
|
||||
CubeListBuilder.create().texOffs(0, 0).addBox(-0.005f, -4.0f, -2.5f, 1f, 8.0f, 5.0f),
|
||||
PartPose.ZERO
|
||||
);
|
||||
|
||||
parts.addOrReplaceChild(
|
||||
PAGE_2,
|
||||
CubeListBuilder.create().texOffs(12, 0).addBox(-0.005f, -4.0f, -2.5f, 1f, 8.0f, 5.0f),
|
||||
PartPose.offsetAndRotation(-0.125f, 0, 1.5f, (float) Math.PI * (1f / 16), 0, 0)
|
||||
);
|
||||
parts.addOrReplaceChild(
|
||||
PAGE_3,
|
||||
CubeListBuilder.create().texOffs(12, 0).addBox(-0.005f, -4.0f, -2.5f, 1f, 8.0f, 5.0f),
|
||||
PartPose.offsetAndRotation(-0.25f, 0, -1.5f, (float) -Math.PI * (2f / 16), 0, 0)
|
||||
);
|
||||
|
||||
return mesh.getRoot().bake(TEXTURE_WIDTH, TEXTURE_HEIGHT);
|
||||
}
|
||||
|
||||
private static ModelPart buildBook() {
|
||||
var mesh = new MeshDefinition();
|
||||
var parts = mesh.getRoot();
|
||||
|
||||
parts.addOrReplaceChild(
|
||||
"spine",
|
||||
CubeListBuilder.create().texOffs(12, 15).addBox(-0.005f, -5.0f, -0.5f, 0, 10, 1.0f),
|
||||
PartPose.ZERO
|
||||
);
|
||||
|
||||
var angle = (float) Math.toRadians(5);
|
||||
parts.addOrReplaceChild(
|
||||
"left",
|
||||
CubeListBuilder.create()
|
||||
.texOffs(0, 10).addBox(0, -5.0f, -6.0f, 0, 10, 6.0f)
|
||||
.texOffs(0, 0).addBox(0.005f, -4.0f, -5.0f, 1.0f, 8.0f, 5.0f),
|
||||
PartPose.offsetAndRotation(-0.005f, 0, -0.5f, 0, -angle, 0)
|
||||
);
|
||||
|
||||
parts.addOrReplaceChild(
|
||||
"right",
|
||||
CubeListBuilder.create()
|
||||
.texOffs(14, 10).addBox(0, -5.0f, 0, 0, 10, 6.0f)
|
||||
.texOffs(0, 0).addBox(0.005f, -4.0f, 0, 1.0f, 8.0f, 5.0f),
|
||||
PartPose.offsetAndRotation(-0.005f, 0, 0.5f, 0, angle, 0)
|
||||
);
|
||||
|
||||
return mesh.getRoot().bake(TEXTURE_WIDTH, TEXTURE_HEIGHT);
|
||||
}
|
||||
|
||||
public void renderBook(PoseStack poseStack, VertexConsumer buffer, int packedLight, int packedOverlay) {
|
||||
bookRoot.render(poseStack, buffer, packedLight, packedOverlay, 1, 1, 1, 1);
|
||||
}
|
||||
|
||||
public void renderPages(PoseStack poseStack, VertexConsumer buffer, int packedLight, int packedOverlay, int pageCount) {
|
||||
if (pageCount > pages.length) pageCount = pages.length;
|
||||
var i = 0;
|
||||
for (; i < pageCount; i++) pages[i].visible = true;
|
||||
for (; i < pages.length; i++) pages[i].visible = false;
|
||||
|
||||
pagesRoot.render(poseStack, buffer, packedLight, packedOverlay, 1, 1, 1, 1);
|
||||
}
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.client.render;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.math.Axis;
|
||||
import dan200.computercraft.client.model.LecternPrintoutModel;
|
||||
import dan200.computercraft.shared.lectern.CustomLecternBlockEntity;
|
||||
import dan200.computercraft.shared.media.items.PrintoutItem;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
|
||||
import net.minecraft.client.renderer.blockentity.LecternRenderer;
|
||||
import net.minecraft.world.level.block.LecternBlock;
|
||||
|
||||
/**
|
||||
* A block entity renderer for our {@linkplain CustomLecternBlockEntity lectern}.
|
||||
* <p>
|
||||
* This largely follows {@link LecternRenderer}, but with support for multiple types of item.
|
||||
*/
|
||||
public class CustomLecternRenderer implements BlockEntityRenderer<CustomLecternBlockEntity> {
|
||||
private final LecternPrintoutModel printoutModel;
|
||||
|
||||
public CustomLecternRenderer(BlockEntityRendererProvider.Context context) {
|
||||
printoutModel = new LecternPrintoutModel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(CustomLecternBlockEntity lectern, float partialTick, PoseStack poseStack, MultiBufferSource buffer, int packedLight, int packedOverlay) {
|
||||
poseStack.pushPose();
|
||||
poseStack.translate(0.5f, 1.0625f, 0.5f);
|
||||
poseStack.mulPose(Axis.YP.rotationDegrees(-lectern.getBlockState().getValue(LecternBlock.FACING).getClockWise().toYRot()));
|
||||
poseStack.mulPose(Axis.ZP.rotationDegrees(67.5f));
|
||||
poseStack.translate(0, -0.125f, 0);
|
||||
|
||||
var item = lectern.getItem();
|
||||
if (item.getItem() instanceof PrintoutItem printout) {
|
||||
var vertexConsumer = LecternPrintoutModel.MATERIAL.buffer(buffer, RenderType::entitySolid);
|
||||
if (printout.getType() == PrintoutItem.Type.BOOK) {
|
||||
printoutModel.renderBook(poseStack, vertexConsumer, packedLight, packedOverlay);
|
||||
} else {
|
||||
printoutModel.renderPages(poseStack, vertexConsumer, packedLight, packedOverlay, PrintoutItem.getPageCount(item));
|
||||
}
|
||||
}
|
||||
|
||||
poseStack.popPose();
|
||||
}
|
||||
}
|
@@ -5,6 +5,7 @@
|
||||
package dan200.computercraft.data.client;
|
||||
|
||||
import dan200.computercraft.client.gui.GuiSprites;
|
||||
import dan200.computercraft.client.model.LecternPrintoutModel;
|
||||
import dan200.computercraft.data.DataProviders;
|
||||
import dan200.computercraft.shared.turtle.inventory.UpgradeSlot;
|
||||
import net.minecraft.client.renderer.texture.atlas.SpriteSource;
|
||||
@@ -30,7 +31,8 @@ public final class ClientDataProviders {
|
||||
generator.addFromCodec("Block atlases", PackType.CLIENT_RESOURCES, "atlases", SpriteSources.FILE_CODEC, out -> {
|
||||
out.accept(new ResourceLocation("blocks"), List.of(
|
||||
new SingleFile(UpgradeSlot.LEFT_UPGRADE, Optional.empty()),
|
||||
new SingleFile(UpgradeSlot.RIGHT_UPGRADE, Optional.empty())
|
||||
new SingleFile(UpgradeSlot.RIGHT_UPGRADE, Optional.empty()),
|
||||
new SingleFile(LecternPrintoutModel.TEXTURE, Optional.empty())
|
||||
));
|
||||
out.accept(GuiSprites.SPRITE_SHEET, Stream.of(
|
||||
// Buttons
|
||||
|
8
projects/common/src/generated/resources/assets/computercraft/blockstates/lectern.json
generated
Normal file
8
projects/common/src/generated/resources/assets/computercraft/blockstates/lectern.json
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"variants": {
|
||||
"facing=east": {"model": "minecraft:block/lectern", "y": 90},
|
||||
"facing=north": {"model": "minecraft:block/lectern", "y": 0},
|
||||
"facing=south": {"model": "minecraft:block/lectern", "y": 180},
|
||||
"facing=west": {"model": "minecraft:block/lectern", "y": 270}
|
||||
}
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"sources": [
|
||||
{"type": "minecraft:single", "resource": "computercraft:gui/turtle_upgrade_left"},
|
||||
{"type": "minecraft:single", "resource": "computercraft:gui/turtle_upgrade_right"}
|
||||
{"type": "minecraft:single", "resource": "computercraft:gui/turtle_upgrade_right"},
|
||||
{"type": "minecraft:single", "resource": "computercraft:entity/printout"}
|
||||
]
|
||||
}
|
||||
|
12
projects/common/src/generated/resources/data/computercraft/loot_tables/blocks/lectern.json
generated
Normal file
12
projects/common/src/generated/resources/data/computercraft/loot_tables/blocks/lectern.json
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"type": "minecraft:block",
|
||||
"pools": [
|
||||
{
|
||||
"bonus_rolls": 0.0,
|
||||
"conditions": [{"condition": "minecraft:survives_explosion"}],
|
||||
"entries": [{"type": "minecraft:item", "name": "minecraft:lectern"}],
|
||||
"rolls": 1.0
|
||||
}
|
||||
],
|
||||
"random_sequence": "computercraft:blocks/lectern"
|
||||
}
|
@@ -23,6 +23,7 @@ import net.minecraft.data.models.BlockModelGenerators;
|
||||
import net.minecraft.data.models.blockstates.*;
|
||||
import net.minecraft.data.models.model.*;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
||||
import net.minecraft.world.level.block.state.properties.BooleanProperty;
|
||||
import net.minecraft.world.level.block.state.properties.Property;
|
||||
@@ -100,6 +101,11 @@ class BlockModelProvider {
|
||||
registerTurtleUpgrade(generators, "block/turtle_speaker", "block/turtle_speaker_face");
|
||||
registerTurtleModem(generators, "block/turtle_modem_normal", "block/wireless_modem_normal_face");
|
||||
registerTurtleModem(generators, "block/turtle_modem_advanced", "block/wireless_modem_advanced_face");
|
||||
|
||||
generators.blockStateOutput.accept(MultiVariantGenerator.multiVariant(
|
||||
ModRegistry.Blocks.LECTERN.get(),
|
||||
Variant.variant().with(VariantProperties.MODEL, ModelLocationUtils.getModelLocation(Blocks.LECTERN))
|
||||
).with(createHorizontalFacingDispatch()));
|
||||
}
|
||||
|
||||
private static void registerDiskDrive(BlockModelGenerators generators) {
|
||||
|
@@ -36,13 +36,13 @@ public final class ItemModelProvider {
|
||||
}
|
||||
|
||||
private static void registerPocketComputer(ItemModelGenerators generators, ResourceLocation id, boolean off) {
|
||||
createFlatItem(generators, addSuffix(id, "_blinking"),
|
||||
createFlatItem(generators, id.withSuffix("_blinking"),
|
||||
new ResourceLocation(ComputerCraftAPI.MOD_ID, "item/pocket_computer_blink"),
|
||||
id,
|
||||
new ResourceLocation(ComputerCraftAPI.MOD_ID, "item/pocket_computer_light")
|
||||
);
|
||||
|
||||
createFlatItem(generators, addSuffix(id, "_on"),
|
||||
createFlatItem(generators, id.withSuffix("_on"),
|
||||
new ResourceLocation(ComputerCraftAPI.MOD_ID, "item/pocket_computer_on"),
|
||||
id,
|
||||
new ResourceLocation(ComputerCraftAPI.MOD_ID, "item/pocket_computer_light")
|
||||
@@ -94,8 +94,4 @@ public final class ItemModelProvider {
|
||||
new ModelTemplate(Optional.of(new ResourceLocation("item/generated")), Optional.empty(), slots)
|
||||
.create(model, mapping, generators.output);
|
||||
}
|
||||
|
||||
private static ResourceLocation addSuffix(ResourceLocation location, String suffix) {
|
||||
return new ResourceLocation(location.getNamespace(), location.getPath() + suffix);
|
||||
}
|
||||
}
|
||||
|
@@ -284,7 +284,9 @@ public final class LanguageProvider implements DataProvider {
|
||||
return Stream.of(
|
||||
RegistryWrappers.BLOCKS.stream()
|
||||
.filter(x -> RegistryWrappers.BLOCKS.getKey(x).getNamespace().equals(ComputerCraftAPI.MOD_ID))
|
||||
.map(Block::getDescriptionId),
|
||||
.map(Block::getDescriptionId)
|
||||
// Exclude blocks that just reuse vanilla translations, such as the lectern.
|
||||
.filter(x -> !x.startsWith("block.minecraft.")),
|
||||
RegistryWrappers.ITEMS.stream()
|
||||
.filter(x -> RegistryWrappers.ITEMS.getKey(x).getNamespace().equals(ComputerCraftAPI.MOD_ID))
|
||||
.map(Item::getDescriptionId),
|
||||
|
@@ -15,6 +15,7 @@ import dan200.computercraft.shared.peripheral.modem.wired.CableModemVariant;
|
||||
import net.minecraft.advancements.critereon.StatePropertiesPredicate;
|
||||
import net.minecraft.data.loot.LootTableProvider.SubProviderEntry;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.storage.loot.LootPool;
|
||||
import net.minecraft.world.level.storage.loot.LootTable;
|
||||
@@ -57,6 +58,8 @@ class LootTableProvider {
|
||||
computerDrop(add, ModRegistry.Blocks.TURTLE_NORMAL);
|
||||
computerDrop(add, ModRegistry.Blocks.TURTLE_ADVANCED);
|
||||
|
||||
blockDrop(add, ModRegistry.Blocks.LECTERN, LootItem.lootTableItem(Items.LECTERN), ExplosionCondition.survivesExplosion());
|
||||
|
||||
add.accept(ModRegistry.Blocks.CABLE.get().getLootTable(), LootTable
|
||||
.lootTable()
|
||||
.withPool(LootPool.lootPool()
|
||||
|
@@ -102,8 +102,14 @@ class TagProvider {
|
||||
ModRegistry.Items.MONITOR_ADVANCED.get()
|
||||
);
|
||||
|
||||
// Allow printed books to be placed in bookshelves.
|
||||
tags.tag(ItemTags.BOOKSHELF_BOOKS).add(ModRegistry.Items.PRINTED_BOOK.get());
|
||||
|
||||
// Allow any printout to be placed on lecterns. See also PrintoutItem and CustomLecternBlock.
|
||||
tags.tag(ItemTags.LECTERN_BOOKS).add(
|
||||
ModRegistry.Items.PRINTED_PAGE.get(), ModRegistry.Items.PRINTED_PAGES.get(), ModRegistry.Items.PRINTED_BOOK.get()
|
||||
);
|
||||
|
||||
tags.tag(ComputerCraftTags.Items.TURTLE_CAN_PLACE)
|
||||
.add(Items.GLASS_BOTTLE)
|
||||
.addTag(ItemTags.BOATS);
|
||||
|
@@ -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 {
|
||||
|
@@ -15,7 +15,7 @@ import java.util.*;
|
||||
* @param <T> The type of object that this registry provides details for.
|
||||
*/
|
||||
public class DetailRegistryImpl<T> implements DetailRegistry<T> {
|
||||
private final Collection<DetailProvider<T>> providers = new ArrayList<>();
|
||||
private final Collection<DetailProvider<? super T>> providers = new ArrayList<>();
|
||||
private final DetailProvider<T> basic;
|
||||
|
||||
public DetailRegistryImpl(DetailProvider<T> basic) {
|
||||
@@ -24,7 +24,7 @@ public class DetailRegistryImpl<T> implements DetailRegistry<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void addProvider(DetailProvider<T> provider) {
|
||||
public synchronized void addProvider(DetailProvider<? super T> provider) {
|
||||
Objects.requireNonNull(provider, "provider cannot be null");
|
||||
if (!providers.contains(provider)) providers.add(provider);
|
||||
}
|
||||
|
@@ -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;
|
||||
@@ -22,14 +23,12 @@ import dan200.computercraft.shared.command.arguments.TrackingFieldArgumentType;
|
||||
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;
|
||||
@@ -41,6 +40,9 @@ import dan200.computercraft.shared.data.PlayerCreativeLootCondition;
|
||||
import dan200.computercraft.shared.details.BlockDetails;
|
||||
import dan200.computercraft.shared.details.ItemDetails;
|
||||
import dan200.computercraft.shared.integration.PermissionRegistry;
|
||||
import dan200.computercraft.shared.lectern.CustomLecternBlock;
|
||||
import dan200.computercraft.shared.lectern.CustomLecternBlockEntity;
|
||||
import dan200.computercraft.shared.media.PrintoutMenu;
|
||||
import dan200.computercraft.shared.media.items.DiskItem;
|
||||
import dan200.computercraft.shared.media.items.PrintoutItem;
|
||||
import dan200.computercraft.shared.media.items.RecordMedia;
|
||||
@@ -49,7 +51,6 @@ import dan200.computercraft.shared.media.recipes.DiskRecipe;
|
||||
import dan200.computercraft.shared.media.recipes.PrintoutRecipe;
|
||||
import dan200.computercraft.shared.network.container.ComputerContainerData;
|
||||
import dan200.computercraft.shared.network.container.ContainerData;
|
||||
import dan200.computercraft.shared.network.container.HeldItemContainerData;
|
||||
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveBlock;
|
||||
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveBlockEntity;
|
||||
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveMenu;
|
||||
@@ -66,6 +67,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 +77,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;
|
||||
@@ -97,13 +102,16 @@ import net.minecraft.world.item.crafting.CustomRecipe;
|
||||
import net.minecraft.world.item.crafting.RecipeSerializer;
|
||||
import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.SoundType;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.properties.NoteBlockInstrument;
|
||||
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 +150,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",
|
||||
@@ -168,6 +175,10 @@ public final class ModRegistry {
|
||||
public static final RegistryEntry<WiredModemFullBlock> WIRED_MODEM_FULL = REGISTRY.register("wired_modem_full",
|
||||
() -> new WiredModemFullBlock(modemProperties().mapColor(MapColor.STONE)));
|
||||
public static final RegistryEntry<CableBlock> CABLE = REGISTRY.register("cable", () -> new CableBlock(modemProperties().mapColor(MapColor.STONE)));
|
||||
|
||||
public static final RegistryEntry<CustomLecternBlock> LECTERN = REGISTRY.register("lectern", () -> new CustomLecternBlock(
|
||||
BlockBehaviour.Properties.of().mapColor(MapColor.WOOD).instrument(NoteBlockInstrument.BASS).strength(2.5F).sound(SoundType.WOOD).ignitedByLava()
|
||||
));
|
||||
}
|
||||
|
||||
public static class BlockEntities {
|
||||
@@ -186,8 +197,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));
|
||||
@@ -209,6 +220,8 @@ public final class ModRegistry {
|
||||
ofBlock(Blocks.WIRELESS_MODEM_NORMAL, (p, s) -> new WirelessModemBlockEntity(BlockEntities.WIRELESS_MODEM_NORMAL.get(), p, s, false));
|
||||
public static final RegistryEntry<BlockEntityType<WirelessModemBlockEntity>> WIRELESS_MODEM_ADVANCED =
|
||||
ofBlock(Blocks.WIRELESS_MODEM_ADVANCED, (p, s) -> new WirelessModemBlockEntity(BlockEntities.WIRELESS_MODEM_ADVANCED.get(), p, s, true));
|
||||
|
||||
public static final RegistryEntry<BlockEntityType<CustomLecternBlockEntity>> LECTERN = ofBlock(Blocks.LECTERN, CustomLecternBlockEntity::new);
|
||||
}
|
||||
|
||||
public static final class Items {
|
||||
@@ -293,9 +306,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)));
|
||||
|
||||
@@ -308,14 +318,8 @@ public final class ModRegistry {
|
||||
public static final RegistryEntry<MenuType<PrinterMenu>> PRINTER = REGISTRY.register("printer",
|
||||
() -> new MenuType<>(PrinterMenu::new, FeatureFlags.VANILLA_SET));
|
||||
|
||||
public static final RegistryEntry<MenuType<HeldItemMenu>> PRINTOUT = REGISTRY.register("printout",
|
||||
() -> ContainerData.toType(
|
||||
HeldItemContainerData::new,
|
||||
(id, inventory, data) -> new HeldItemMenu(Menus.PRINTOUT.get(), id, inventory.player, data.getHand())
|
||||
));
|
||||
|
||||
public static final RegistryEntry<MenuType<ViewComputerMenu>> VIEW_COMPUTER = REGISTRY.register("view_computer",
|
||||
() -> ContainerData.toType(ComputerContainerData::new, ViewComputerMenu::new));
|
||||
public static final RegistryEntry<MenuType<PrintoutMenu>> PRINTOUT = REGISTRY.register("printout",
|
||||
() -> new MenuType<>((i, c) -> PrintoutMenu.createRemote(i), FeatureFlags.VANILLA_SET));
|
||||
}
|
||||
|
||||
static class ArgumentTypes {
|
||||
@@ -456,6 +460,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;
|
||||
|
@@ -63,7 +63,7 @@ public class TableBuilder {
|
||||
/**
|
||||
* Get the number of columns for this table.
|
||||
* <p>
|
||||
* This will be the same as {@link #getHeaders()}'s length if it is is non-{@code null},
|
||||
* This will be the same as {@link #getHeaders()}'s length if it is non-{@code null},
|
||||
* otherwise the length of the first column.
|
||||
*
|
||||
* @return The number of columns.
|
||||
|
@@ -1,68 +0,0 @@
|
||||
// Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
|
||||
//
|
||||
// SPDX-License-Identifier: LicenseRef-CCPL
|
||||
|
||||
package dan200.computercraft.shared.common;
|
||||
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.MenuProvider;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
import net.minecraft.world.inventory.MenuType;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class HeldItemMenu extends AbstractContainerMenu {
|
||||
private final ItemStack stack;
|
||||
private final InteractionHand hand;
|
||||
|
||||
public HeldItemMenu(MenuType<? extends HeldItemMenu> type, int id, Player player, InteractionHand hand) {
|
||||
super(type, id);
|
||||
|
||||
this.hand = hand;
|
||||
stack = player.getItemInHand(hand).copy();
|
||||
}
|
||||
|
||||
public ItemStack getStack() {
|
||||
return stack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack quickMoveStack(Player player, int slot) {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stillValid(Player player) {
|
||||
if (!player.isAlive()) return false;
|
||||
|
||||
var stack = player.getItemInHand(hand);
|
||||
return stack == this.stack || !stack.isEmpty() && !this.stack.isEmpty() && stack.getItem() == this.stack.getItem();
|
||||
}
|
||||
|
||||
public static class Factory implements MenuProvider {
|
||||
private final MenuType<HeldItemMenu> type;
|
||||
private final Component name;
|
||||
private final InteractionHand hand;
|
||||
|
||||
public Factory(MenuType<HeldItemMenu> type, ItemStack stack, InteractionHand hand) {
|
||||
this.type = type;
|
||||
name = stack.getHoverName();
|
||||
this.hand = hand;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getDisplayName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public AbstractContainerMenu createMenu(int id, Inventory inventory, Player player) {
|
||||
return new HeldItemMenu(type, id, player, hand);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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() };
|
||||
}
|
||||
|
||||
@@ -172,7 +177,7 @@ public class CommandAPI implements ILuaAPI {
|
||||
* blocks at once.
|
||||
* <p>
|
||||
* Blocks are traversed by ascending y level, followed by z and x - the returned
|
||||
* table may be indexed using `x + z*width + y*depth*depth`.
|
||||
* table may be indexed using `x + z*width + y*width*depth + 1`.
|
||||
*
|
||||
* @param minX The start x coordinate of the range to query.
|
||||
* @param minY The start y coordinate of the range to query.
|
||||
@@ -186,6 +191,24 @@ 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
|
||||
* -- Get a 3x3x3 cube around the computer
|
||||
* local x, y, z = commands.getBlockPosition()
|
||||
* local min_x, min_y, min_z, max_x, max_y, max_z = x - 1, y - 1, z - 1, x + 1, y + 1, z + 1
|
||||
* local blocks = commands.getBlockInfos(min_x, min_y, min_z, max_x, max_y, max_z)
|
||||
*
|
||||
* -- Then loop over all blocks and print them out.
|
||||
* local width, height, depth = max_x - min_x + 1, max_y - min_y + 1, max_z - min_z + 1
|
||||
* for x = min_x, max_x do
|
||||
* for y = min_y, max_y do
|
||||
* for z = min_z, max_z do
|
||||
* print(("%d, %d %d => %s"):format(x, y, z, blocks[(x - min_x) + (z - min_z) * width + (y - min_y) * width * depth + 1].name))
|
||||
* end
|
||||
* end
|
||||
* end
|
||||
* }</pre>
|
||||
*/
|
||||
@LuaFunction(mainThread = true)
|
||||
public final List<Map<?, ?>> getBlockInfos(int minX, int minY, int minZ, int maxX, int maxY, int maxZ, Optional<String> dimension) throws LuaException {
|
||||
@@ -201,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");
|
||||
}
|
||||
|
||||
@@ -246,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");
|
||||
@@ -259,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -35,9 +35,9 @@ import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.storage.loot.LootParams;
|
||||
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntity> extends HorizontalDirectionalBlock implements IBundledRedstoneBlock, EntityBlock {
|
||||
private static final ResourceLocation DROP = new ResourceLocation(ComputerCraftAPI.MOD_ID, "computer");
|
||||
@@ -110,9 +110,19 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit
|
||||
return super.getCloneItemStack(world, pos, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public List<ItemStack> getDrops(BlockState state, LootParams.Builder params) {
|
||||
if (params.getOptionalParameter(LootContextParams.BLOCK_ENTITY) instanceof AbstractComputerBlockEntity computer) {
|
||||
params = params.withDynamicDrop(DROP, out -> out.accept(getItem(computer)));
|
||||
}
|
||||
|
||||
return super.getDrops(state, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity tile, ItemStack tool) {
|
||||
// Don't drop blocks here - see onBlockHarvested.
|
||||
// Don't drop blocks here - see playerWillDestroy.
|
||||
player.awardStat(Stats.BLOCK_MINED.get(this));
|
||||
player.causeFoodExhaustion(0.005F);
|
||||
}
|
||||
@@ -120,25 +130,11 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit
|
||||
@Override
|
||||
public void playerWillDestroy(Level world, BlockPos pos, BlockState state, Player player) {
|
||||
super.playerWillDestroy(world, pos, state, player);
|
||||
if (!(world instanceof ServerLevel serverWorld)) return;
|
||||
if (!(world instanceof ServerLevel serverLevel)) return;
|
||||
|
||||
// We drop the item here instead of doing it in the harvest method, as we should
|
||||
// drop computers for creative players too.
|
||||
|
||||
var tile = world.getBlockEntity(pos);
|
||||
if (tile instanceof AbstractComputerBlockEntity computer) {
|
||||
var context = new LootParams.Builder(serverWorld)
|
||||
.withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(pos))
|
||||
.withParameter(LootContextParams.TOOL, player.getMainHandItem())
|
||||
.withParameter(LootContextParams.THIS_ENTITY, player)
|
||||
.withParameter(LootContextParams.BLOCK_ENTITY, tile)
|
||||
.withDynamicDrop(DROP, out -> out.accept(getItem(computer)));
|
||||
for (var item : state.getDrops(context)) {
|
||||
popResource(world, pos, item);
|
||||
}
|
||||
|
||||
state.spawnAfterBreak(serverWorld, pos, player.getMainHandItem(), true);
|
||||
}
|
||||
dropResources(state, serverLevel, pos, world.getBlockEntity(pos));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -146,7 +142,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());
|
||||
}
|
||||
|
||||
@@ -109,7 +110,7 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
|
||||
fresh = false;
|
||||
computerID = computer.getID();
|
||||
|
||||
// If the on state has changed, mark as as dirty.
|
||||
// If the on state has changed, mark as dirty.
|
||||
var newOn = computer.isOn();
|
||||
if (on != newOn) {
|
||||
on = newOn;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
@@ -4,16 +4,17 @@
|
||||
|
||||
package dan200.computercraft.shared.container;
|
||||
|
||||
import net.minecraft.core.NonNullList;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.ContainerHelper;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A basic implementation of {@link Container} which operates on a {@linkplain #getContents() list of stacks}.
|
||||
*/
|
||||
public interface BasicContainer extends Container {
|
||||
NonNullList<ItemStack> getContents();
|
||||
List<ItemStack> getContents();
|
||||
|
||||
@Override
|
||||
default int getContainerSize() {
|
||||
|
@@ -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
|
||||
|
@@ -50,12 +50,12 @@ public class ItemDetails {
|
||||
if (tag != null && tag.contains("display", Tag.TAG_COMPOUND)) {
|
||||
var displayTag = tag.getCompound("display");
|
||||
if (displayTag.contains("Lore", Tag.TAG_LIST)) {
|
||||
var loreTag = displayTag.getList("Lore", Tag.TAG_STRING);
|
||||
data.put("lore", loreTag.stream()
|
||||
var lore = displayTag.getList("Lore", Tag.TAG_STRING).stream()
|
||||
.map(ItemDetails::parseTextComponent)
|
||||
.filter(Objects::nonNull)
|
||||
.map(Component::getString)
|
||||
.toList());
|
||||
.toList();
|
||||
if (!lore.isEmpty()) data.put("lore", lore);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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));
|
||||
|
@@ -0,0 +1,142 @@
|
||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.lectern;
|
||||
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.media.items.PrintoutItem;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.stats.Stats;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.entity.item.ItemEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.item.context.UseOnContext;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.LecternBlock;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
|
||||
/**
|
||||
* Extends {@link LecternBlock} with support for {@linkplain PrintoutItem printouts}.
|
||||
* <p>
|
||||
* Unlike the vanilla lectern, this block is never empty. If the book is removed from the lectern, it converts back to
|
||||
* its vanilla version (see {@link #clearLectern(Level, BlockPos, BlockState)}).
|
||||
*
|
||||
* @see PrintoutItem#useOn(UseOnContext) Placing books into a lectern.
|
||||
*/
|
||||
public class CustomLecternBlock extends LecternBlock {
|
||||
public CustomLecternBlock(Properties properties) {
|
||||
super(properties);
|
||||
registerDefaultState(defaultBlockState().setValue(HAS_BOOK, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace a vanilla lectern with a custom one.
|
||||
*
|
||||
* @param level The current level.
|
||||
* @param pos The position of the lectern.
|
||||
* @param blockState The current state of the lectern.
|
||||
* @param item The item to place in the custom lectern.
|
||||
*/
|
||||
public static void replaceLectern(Level level, BlockPos pos, BlockState blockState, ItemStack item) {
|
||||
level.setBlockAndUpdate(pos, ModRegistry.Blocks.LECTERN.get().defaultBlockState()
|
||||
.setValue(HAS_BOOK, true)
|
||||
.setValue(FACING, blockState.getValue(FACING))
|
||||
.setValue(POWERED, blockState.getValue(POWERED)));
|
||||
|
||||
if (level.getBlockEntity(pos) instanceof CustomLecternBlockEntity be) be.setItem(item.split(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a custom lectern and replace it with an empty vanilla one.
|
||||
*
|
||||
* @param level The current level.
|
||||
* @param pos The position of the lectern.
|
||||
* @param blockState The current state of the lectern.
|
||||
*/
|
||||
static void clearLectern(Level level, BlockPos pos, BlockState blockState) {
|
||||
level.setBlockAndUpdate(pos, Blocks.LECTERN.defaultBlockState()
|
||||
.setValue(HAS_BOOK, false)
|
||||
.setValue(FACING, blockState.getValue(FACING))
|
||||
.setValue(POWERED, blockState.getValue(POWERED)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public ItemStack getCloneItemStack(BlockGetter level, BlockPos pos, BlockState state) {
|
||||
return new ItemStack(Items.LECTERN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
|
||||
// If we've no lectern, remove it.
|
||||
if (level.getBlockEntity(pos) instanceof CustomLecternBlockEntity lectern && lectern.getItem().isEmpty()) {
|
||||
clearLectern(level, pos, state);
|
||||
return;
|
||||
}
|
||||
|
||||
super.tick(state, level, pos, random);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) {
|
||||
if (state.is(newState.getBlock())) return;
|
||||
|
||||
if (level.getBlockEntity(pos) instanceof CustomLecternBlockEntity lectern) {
|
||||
dropItem(level, pos, state, lectern.getItem().copy());
|
||||
}
|
||||
|
||||
super.onRemove(state, level, pos, newState, isMoving);
|
||||
}
|
||||
|
||||
private static void dropItem(Level level, BlockPos pos, BlockState state, ItemStack stack) {
|
||||
if (stack.isEmpty()) return;
|
||||
|
||||
var direction = state.getValue(FACING);
|
||||
var dx = 0.25 * direction.getStepX();
|
||||
var dz = 0.25 * direction.getStepZ();
|
||||
var entity = new ItemEntity(level, pos.getX() + 0.5 + dx, pos.getY() + 1, pos.getZ() + 0.5 + dz, stack);
|
||||
entity.setDefaultPickUpDelay();
|
||||
level.addFreshEntity(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescriptionId() {
|
||||
return Blocks.LECTERN.getDescriptionId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomLecternBlockEntity newBlockEntity(BlockPos pos, BlockState state) {
|
||||
return new CustomLecternBlockEntity(pos, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAnalogOutputSignal(BlockState blockState, Level level, BlockPos pos) {
|
||||
return level.getBlockEntity(pos) instanceof CustomLecternBlockEntity lectern ? lectern.getRedstoneSignal() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResult use(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) {
|
||||
if (!level.isClientSide && level.getBlockEntity(pos) instanceof CustomLecternBlockEntity lectern) {
|
||||
if (player.isSecondaryUseActive()) {
|
||||
// When shift+clicked with an empty hand, drop the item and replace with the normal lectern.
|
||||
clearLectern(level, pos, state);
|
||||
} else {
|
||||
// Otherwise open the screen.
|
||||
player.openMenu(lectern);
|
||||
}
|
||||
|
||||
player.awardStat(Stats.INTERACT_WITH_LECTERN);
|
||||
}
|
||||
|
||||
return InteractionResult.sidedSuccess(level.isClientSide);
|
||||
}
|
||||
}
|
@@ -0,0 +1,193 @@
|
||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.lectern;
|
||||
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.container.BasicContainer;
|
||||
import dan200.computercraft.shared.container.SingleContainerData;
|
||||
import dan200.computercraft.shared.media.PrintoutMenu;
|
||||
import dan200.computercraft.shared.media.items.PrintoutItem;
|
||||
import dan200.computercraft.shared.util.BlockEntityHelpers;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.ClientGamePacketListener;
|
||||
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.MenuProvider;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
import net.minecraft.world.inventory.ContainerData;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.LecternBlock;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.LecternBlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.AbstractList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The block entity for our {@link CustomLecternBlock}.
|
||||
*
|
||||
* @see LecternBlockEntity
|
||||
*/
|
||||
public final class CustomLecternBlockEntity extends BlockEntity implements MenuProvider {
|
||||
private static final String NBT_ITEM = "Item";
|
||||
private static final String NBT_PAGE = "Page";
|
||||
|
||||
private ItemStack item = ItemStack.EMPTY;
|
||||
private int page, pageCount;
|
||||
|
||||
public CustomLecternBlockEntity(BlockPos pos, BlockState blockState) {
|
||||
super(ModRegistry.BlockEntities.LECTERN.get(), pos, blockState);
|
||||
}
|
||||
|
||||
public ItemStack getItem() {
|
||||
return item;
|
||||
}
|
||||
|
||||
void setItem(ItemStack item) {
|
||||
this.item = item;
|
||||
itemChanged();
|
||||
BlockEntityHelpers.updateBlock(this);
|
||||
}
|
||||
|
||||
int getRedstoneSignal() {
|
||||
if (item.getItem() instanceof PrintoutItem) {
|
||||
var progress = pageCount > 1 ? (float) page / (pageCount - 1) : 1F;
|
||||
return Mth.floor(progress * 14f) + 1;
|
||||
}
|
||||
|
||||
return 15;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after the item has changed. This sets up the state for the new item.
|
||||
*/
|
||||
private void itemChanged() {
|
||||
if (item.getItem() instanceof PrintoutItem) {
|
||||
pageCount = PrintoutItem.getPageCount(item);
|
||||
page = Mth.clamp(page, 0, pageCount - 1);
|
||||
} else {
|
||||
pageCount = page = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current page, emitting a redstone pulse if needed.
|
||||
*
|
||||
* @param page The new page.
|
||||
*/
|
||||
private void setPage(int page) {
|
||||
if (this.page == page) return;
|
||||
|
||||
this.page = page;
|
||||
setChanged();
|
||||
if (getLevel() != null) LecternBlock.signalPageChange(getLevel(), getBlockPos(), getBlockState());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load(CompoundTag tag) {
|
||||
super.load(tag);
|
||||
|
||||
item = tag.contains(NBT_ITEM, Tag.TAG_COMPOUND) ? ItemStack.of(tag.getCompound(NBT_ITEM)) : ItemStack.EMPTY;
|
||||
page = tag.getInt(NBT_PAGE);
|
||||
itemChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void saveAdditional(CompoundTag tag) {
|
||||
super.saveAdditional(tag);
|
||||
|
||||
if (!item.isEmpty()) tag.put(NBT_ITEM, item.save(new CompoundTag()));
|
||||
if (item.getItem() instanceof PrintoutItem) tag.putInt(NBT_PAGE, page);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Packet<ClientGamePacketListener> getUpdatePacket() {
|
||||
return ClientboundBlockEntityDataPacket.create(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag getUpdateTag() {
|
||||
var tag = super.getUpdateTag();
|
||||
tag.put(NBT_ITEM, item.save(new CompoundTag()));
|
||||
return tag;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public AbstractContainerMenu createMenu(int containerId, Inventory playerInventory, Player player) {
|
||||
var item = getItem();
|
||||
if (item.getItem() instanceof PrintoutItem) {
|
||||
return new PrintoutMenu(
|
||||
containerId, new LecternContainer(), 0,
|
||||
p -> Container.stillValidBlockEntity(this, player, Container.DEFAULT_DISTANCE_LIMIT),
|
||||
new PrintoutContainerData()
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getDisplayName() {
|
||||
return getItem().getDisplayName();
|
||||
}
|
||||
|
||||
/**
|
||||
* A read-only container storing the lectern's contents.
|
||||
*/
|
||||
private final class LecternContainer implements BasicContainer {
|
||||
private final List<ItemStack> itemView = new AbstractList<>() {
|
||||
@Override
|
||||
public ItemStack get(int index) {
|
||||
if (index != 0) throw new IndexOutOfBoundsException("Inventory only has one slot");
|
||||
return item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public List<ItemStack> getContents() {
|
||||
return itemView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChanged() {
|
||||
// Should never happen, so a no-op.
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stillValid(Player player) {
|
||||
return !isRemoved();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ContainerData} for a {@link PrintoutMenu}. This provides a read/write view of the current page.
|
||||
*/
|
||||
private final class PrintoutContainerData implements SingleContainerData {
|
||||
@Override
|
||||
public int get() {
|
||||
return page;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(int index, int value) {
|
||||
if (index == 0) setPage(value);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,136 @@
|
||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.media;
|
||||
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.container.InvisibleSlot;
|
||||
import dan200.computercraft.shared.media.items.PrintoutItem;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.SimpleContainer;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.inventory.*;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* The menus for {@linkplain PrintoutItem printouts}.
|
||||
* <p>
|
||||
* This is a somewhat similar design to {@link LecternMenu}, which is used to read written books.
|
||||
* <p>
|
||||
* This holds a single slot (containing the printout), and a single data slot ({@linkplain #DATA_CURRENT_PAGE holding
|
||||
* the current page}). The page is set by the client by sending a {@linkplain #clickMenuButton(Player, int) button
|
||||
* press} with an index of {@link #PAGE_BUTTON_OFFSET} plus the current page.
|
||||
* <p>
|
||||
* The client-side screen uses {@linkplain ContainerListener container listeners} to subscribe to item and page changes.
|
||||
* However, listeners aren't fired on the client, so we copy {@link LecternMenu}'s hack and call
|
||||
* {@link #broadcastChanges()} whenever an item or data value are changed.
|
||||
*/
|
||||
public class PrintoutMenu extends AbstractContainerMenu {
|
||||
public static final int DATA_CURRENT_PAGE = 0;
|
||||
private static final int DATA_SIZE = 1;
|
||||
|
||||
public static final int PAGE_BUTTON_OFFSET = 100;
|
||||
|
||||
private final Predicate<Player> valid;
|
||||
private final ContainerData currentPage;
|
||||
|
||||
public PrintoutMenu(
|
||||
int containerId, Container container, int slotIdx, Predicate<Player> valid, ContainerData currentPage
|
||||
) {
|
||||
super(ModRegistry.Menus.PRINTOUT.get(), containerId);
|
||||
this.valid = valid;
|
||||
this.currentPage = currentPage;
|
||||
|
||||
addSlot(new InvisibleSlot(container, slotIdx) {
|
||||
@Override
|
||||
public void setChanged() {
|
||||
super.setChanged();
|
||||
slotsChanged(container); // Trigger listeners on the client.
|
||||
}
|
||||
});
|
||||
addDataSlots(currentPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create {@link PrintoutMenu} for use a remote (client).
|
||||
*
|
||||
* @param containerId The current container id.
|
||||
* @return The constructed container.
|
||||
*/
|
||||
public static PrintoutMenu createRemote(int containerId) {
|
||||
return new PrintoutMenu(containerId, new SimpleContainer(1), 0, p -> true, new SimpleContainerData(DATA_SIZE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link PrintoutMenu} for the printout in the current player's hand.
|
||||
*
|
||||
* @param containerId The current container id.
|
||||
* @param player The player to open the container.
|
||||
* @param hand The hand containing the item.
|
||||
* @return The constructed container.
|
||||
*/
|
||||
public static PrintoutMenu createInHand(int containerId, Player player, InteractionHand hand) {
|
||||
var currentStack = player.getItemInHand(hand);
|
||||
var currentItem = currentStack.getItem();
|
||||
|
||||
var slot = switch (hand) {
|
||||
case MAIN_HAND -> player.getInventory().selected;
|
||||
case OFF_HAND -> Inventory.SLOT_OFFHAND;
|
||||
};
|
||||
return new PrintoutMenu(
|
||||
containerId, player.getInventory(), slot,
|
||||
p -> player.getItemInHand(hand).getItem() == currentItem, new SimpleContainerData(DATA_SIZE)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack quickMoveStack(Player player, int index) {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stillValid(Player player) {
|
||||
return valid.test(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean clickMenuButton(Player player, int id) {
|
||||
if (id >= PAGE_BUTTON_OFFSET) {
|
||||
var page = Mth.clamp(id - PAGE_BUTTON_OFFSET, 0, PrintoutItem.getPageCount(getPrintout()) - 1);
|
||||
setData(DATA_CURRENT_PAGE, page);
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.clickMenuButton(player, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current printout.
|
||||
*
|
||||
* @return The current printout.
|
||||
*/
|
||||
public ItemStack getPrintout() {
|
||||
return getSlot(0).getItem();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current page.
|
||||
*
|
||||
* @return The current page.
|
||||
*/
|
||||
public int getPage() {
|
||||
return currentPage.get(DATA_CURRENT_PAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setData(int id, int data) {
|
||||
super.setData(id, data);
|
||||
broadcastChanges(); // Trigger listeners on the client.
|
||||
}
|
||||
}
|
@@ -4,18 +4,23 @@
|
||||
|
||||
package dan200.computercraft.shared.media.items;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.common.HeldItemMenu;
|
||||
import dan200.computercraft.shared.network.container.HeldItemContainerData;
|
||||
import dan200.computercraft.shared.lectern.CustomLecternBlock;
|
||||
import dan200.computercraft.shared.media.PrintoutMenu;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.InteractionResultHolder;
|
||||
import net.minecraft.world.SimpleMenuProvider;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.TooltipFlag;
|
||||
import net.minecraft.world.item.context.UseOnContext;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.LecternBlock;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.List;
|
||||
@@ -50,12 +55,30 @@ public class PrintoutItem extends Item {
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResultHolder<ItemStack> use(Level world, Player player, InteractionHand hand) {
|
||||
if (!world.isClientSide) {
|
||||
new HeldItemContainerData(hand)
|
||||
.open(player, new HeldItemMenu.Factory(ModRegistry.Menus.PRINTOUT.get(), player.getItemInHand(hand), hand));
|
||||
public InteractionResult useOn(UseOnContext context) {
|
||||
var level = context.getLevel();
|
||||
var blockPos = context.getClickedPos();
|
||||
var blockState = level.getBlockState(blockPos);
|
||||
if (blockState.is(Blocks.LECTERN) && !blockState.getValue(LecternBlock.HAS_BOOK)) {
|
||||
// If we have an empty lectern, place our book into it.
|
||||
if (!level.isClientSide) {
|
||||
CustomLecternBlock.replaceLectern(level, blockPos, blockState, context.getItemInHand());
|
||||
}
|
||||
return InteractionResult.sidedSuccess(level.isClientSide);
|
||||
} else {
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
return new InteractionResultHolder<>(InteractionResult.sidedSuccess(world.isClientSide), player.getItemInHand(hand));
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResultHolder<ItemStack> use(Level world, Player player, InteractionHand hand) {
|
||||
var stack = player.getItemInHand(hand);
|
||||
if (!world.isClientSide) {
|
||||
var title = getTitle(stack);
|
||||
var displayTitle = Strings.isNullOrEmpty(title) ? stack.getDisplayName() : Component.literal(title);
|
||||
player.openMenu(new SimpleMenuProvider((id, playerInventory, p) -> PrintoutMenu.createInHand(id, p, hand), displayTitle));
|
||||
}
|
||||
return new InteractionResultHolder<>(InteractionResult.sidedSuccess(world.isClientSide), stack);
|
||||
}
|
||||
|
||||
private ItemStack createFromTitleAndText(@Nullable String title, @Nullable String[] text, @Nullable String[] colours) {
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -1,37 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2019 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.network.container;
|
||||
|
||||
import dan200.computercraft.shared.common.HeldItemMenu;
|
||||
import dan200.computercraft.shared.media.items.PrintoutItem;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
|
||||
/**
|
||||
* Opens a printout GUI based on the currently held item.
|
||||
*
|
||||
* @see HeldItemMenu
|
||||
* @see PrintoutItem
|
||||
*/
|
||||
public class HeldItemContainerData implements ContainerData {
|
||||
private final InteractionHand hand;
|
||||
|
||||
public HeldItemContainerData(InteractionHand hand) {
|
||||
this.hand = hand;
|
||||
}
|
||||
|
||||
public HeldItemContainerData(FriendlyByteBuf buffer) {
|
||||
hand = buffer.readEnum(InteractionHand.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBytes(FriendlyByteBuf buf) {
|
||||
buf.writeEnum(hand);
|
||||
}
|
||||
|
||||
public InteractionHand getHand() {
|
||||
return hand;
|
||||
}
|
||||
}
|
@@ -96,11 +96,13 @@ public final class DiskDriveBlockEntity extends AbstractContainerBlockEntity {
|
||||
|
||||
@Override
|
||||
public void clearRemoved() {
|
||||
super.clearRemoved();
|
||||
updateMedia();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRemoved() {
|
||||
super.setRemoved();
|
||||
if (recordPlaying) stopRecord();
|
||||
}
|
||||
|
||||
|
@@ -129,7 +129,8 @@ public class DiskDrivePeripheral implements IPeripheral {
|
||||
@LuaFunction
|
||||
@Nullable
|
||||
public final Object getAudioTitle() {
|
||||
return diskDrive.getMedia().getAudioTitle();
|
||||
var stack = diskDrive.getMedia();
|
||||
return stack.media() != null ? stack.getAudioTitle() : false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -70,10 +70,10 @@ public abstract class AbstractFluidMethods<T> implements GenericPeripheral {
|
||||
) throws LuaException;
|
||||
|
||||
/**
|
||||
* Move a fluid from a connected fluid container into this oneone.
|
||||
* Move a fluid from a connected fluid container into this one.
|
||||
* <p>
|
||||
* This allows you to pull fluid in the current fluid container from another container <em>on the same wired
|
||||
* network</em>. Both containers must attached to wired modems which are connected via a cable.
|
||||
* network</em>. Both containers must be attached to wired modems which are connected via a cable.
|
||||
*
|
||||
* @param to Container to move fluid to.
|
||||
* @param computer The current computer.
|
||||
|
@@ -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;
|
||||
@@ -30,14 +30,20 @@ import java.util.Objects;
|
||||
* print("On something else")
|
||||
* end
|
||||
* }</pre>
|
||||
* <p>
|
||||
* ## Recipes
|
||||
* <div class="recipe-container">
|
||||
* <mc-recipe recipe="computercraft:pocket_computer_normal"></mc-recipe>
|
||||
* <mc-recipe recipe="computercraft:pocket_computer_advanced"></mc-recipe>
|
||||
* </div>
|
||||
*
|
||||
* @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 +62,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 +79,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 +93,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,24 @@ 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();
|
||||
public void inventoryTick(ItemStack stack, Level world, Entity entity, int compartmentSlot, boolean selected) {
|
||||
// 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();
|
||||
// Find the actual slot the item exists in, aborting if it can't be found.
|
||||
var slot = InventoryUtil.getInventorySlotFromCompartment(player, compartmentSlot, stack);
|
||||
if (slot < 0) return;
|
||||
|
||||
// If we're in the inventory, create a computer and keep it alive.
|
||||
var holder = new PocketHolder.PlayerHolder(player, slot);
|
||||
var brain = getOrCreateBrain((ServerLevel) world, holder, stack);
|
||||
brain.computer().keepAlive();
|
||||
|
||||
tick(stack, holder, brain);
|
||||
}
|
||||
|
||||
@ForgeOverride
|
||||
@@ -122,8 +149,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 +161,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 +222,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.*;
|
||||
|
||||
@@ -47,28 +47,34 @@ import java.util.Optional;
|
||||
* <p>
|
||||
* ## Turtle upgrades
|
||||
* While a normal turtle can move about the world and place blocks, its functionality is limited. Thankfully, turtles
|
||||
* can be upgraded with *tools* and [peripherals][`peripheral`]. Turtles have two upgrade slots, one on the left and right
|
||||
* sides. Upgrades can be equipped by crafting a turtle with the upgrade, or calling the [`turtle.equipLeft`]/[`turtle.equipRight`]
|
||||
* functions.
|
||||
* can be upgraded with upgrades. Turtles have two upgrade slots, one on the left and right sides. Upgrades can be
|
||||
* equipped by crafting a turtle with the upgrade, or calling the [`turtle.equipLeft`]/[`turtle.equipRight`] functions.
|
||||
* <p>
|
||||
* Turtle tools allow you to break blocks ([`turtle.dig`]) and attack entities ([`turtle.attack`]). Some tools are more
|
||||
* suitable to a task than others. For instance, a diamond pickaxe can break every block, while a sword does more
|
||||
* damage. Other tools have more niche use-cases, for instance hoes can til dirt.
|
||||
* By default, any diamond tool may be used as an upgrade (though more may be added with [datapacks]). The diamond
|
||||
* pickaxe may be used to break blocks (with [`turtle.dig`]), while the sword can attack entities ([`turtle.attack`]).
|
||||
* Other tools have more niche use-cases, for instance hoes can til dirt.
|
||||
* <p>
|
||||
* Peripherals (such as the [wireless modem][`modem`] or [`speaker`]) can also be equipped as upgrades. These are then
|
||||
* accessible by accessing the `"left"` or `"right"` peripheral.
|
||||
* Some peripherals (namely [speakers][`speaker`] and Ender and Wireless [modems][`modem`]) can also be equipped as
|
||||
* upgrades. These are then accessible by accessing the `"left"` or `"right"` peripheral.
|
||||
* <p>
|
||||
* ## Recipes
|
||||
* <div class="recipe-container">
|
||||
* <mc-recipe recipe="computercraft:turtle_normal"></mc-recipe>
|
||||
* <mc-recipe recipe="computercraft:turtle_advanced"></mc-recipe>
|
||||
* </div>
|
||||
* <p>
|
||||
* [Turtle Graphics]: https://en.wikipedia.org/wiki/Turtle_graphics "Turtle graphics"
|
||||
* [datapacks]: https://datapacks.madefor.cc ""
|
||||
*
|
||||
* @cc.module turtle
|
||||
* @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 +84,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 +175,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 +190,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 +205,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)));
|
||||
}
|
||||
|
||||
@@ -345,7 +351,7 @@ public class TurtleAPI implements ILuaAPI {
|
||||
* For instance, if a slot contains 13 blocks of dirt, it has room for another 51.
|
||||
*
|
||||
* @param slot The slot we wish to check. Defaults to the {@link #select selected slot}.
|
||||
* @return The space left in in this slot.
|
||||
* @return The space left in this slot.
|
||||
* @throws LuaException If the slot is out of range.
|
||||
*/
|
||||
@LuaFunction
|
||||
|
@@ -125,11 +125,15 @@ public class TurtleBlock extends AbstractComputerBlock<TurtleBlockEntity> implem
|
||||
public final void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) {
|
||||
if (state.is(newState.getBlock())) return;
|
||||
|
||||
if (!level.isClientSide && level.getBlockEntity(pos) instanceof TurtleBlockEntity turtle && !turtle.hasMoved()) {
|
||||
Containers.dropContents(level, pos, turtle);
|
||||
}
|
||||
// Most blocks drop items and then remove the BE. However, if a turtle is consuming drops right now, that can
|
||||
// lead to loops where it tries to insert an item back into the inventory. To prevent this, take a reference to
|
||||
// the turtle BE now, remove it, and then drop the items.
|
||||
var turtle = !level.isClientSide && level.getBlockEntity(pos) instanceof TurtleBlockEntity t && !t.hasMoved()
|
||||
? t : null;
|
||||
|
||||
super.onRemove(state, level, pos, newState, isMoving);
|
||||
|
||||
if (turtle != null) Containers.dropContents(level, pos, turtle);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -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;
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user