1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-10-16 06:27:39 +00:00

Compare commits

..

35 Commits

Author SHA1 Message Date
Jonathan Coates
45cb597ecc Merge branch 'mc-1.20.x' into mc-1.21.x 2024-07-31 07:34:49 +01:00
Jonathan Coates
08d4f91c8b Bump CC:T to 1.112.0 2024-07-31 07:05:08 +01:00
Jonathan Coates
b9eac4e509 Computer components (#1915)
This adds a new mechanism for attaching additional objects to a
computer, allowing them to be queried by other mods. This is primarily
designed for mods which add external APIs, allowing them to add APIs
which depend on the computer's position or can interact with the turtle
inventory.

I will stress that the use-cases for custom APIs are few and far
between. Almost all the time a peripheral would be the better option,
and I am wary that this PR will encourage misuse of APIs. However, there
are some legitimate use-cases, and I think we should enable them.

 - Add a new "ComputerComponent" class, and several built-in components
   (for turtle, pocket and command computers).

 - Add a method to `IComputerSystem` to read a component from the
   computer. We also add methods to get the level and position of the
   computer.

 - Move all our existing APIs (built-in turtle, pocket, command) to use
   the public API.
2024-07-31 06:57:38 +01:00
Jonathan Coates
16577783d3 Fix incorrect offset in turtle crafting
This means we end up looking at the wrong slots, and thus fail to remove
items! Fixes #1918.
2024-07-31 06:40:37 +01:00
Jonathan Coates
c179da28f0 Make turtle colour opaque in BER
Fixes #1893
2024-07-31 06:30:43 +01:00
Jonathan Coates
dc3d8ea198 Move API factories to the common package
We don't actually use this functionality in other projects (e.g.
emulators). In fact the method to add new APIs only exists in the mod
itself!

We still need some mechanism to remove mounts when the computer is
shutdown. We add a new ApiLifecycle interface (with startup and
shutdown hooks), and use those in the ComputerSystem impl.
2024-07-29 19:46:25 +01:00
Jonathan Coates
cbe075b001 Expose level+position in IPocketAccess
This allows pocket upgrades (modems and speakers) to read the position
directly, rather than checking whether the entity is present.
2024-07-28 21:15:25 +01:00
Jonathan Coates
ed0b156e05 Attempt at splitting up pocket computer logic
Oh, I hate the pocket computer code so much. Minecraft was really not
designed to attach this sort of behaviour to computers. This commit is
an attempt of cleaning this up[^1].

Firstly, we move the the pocket computer state (upgrades, light) out of
PocketServerComputer and into a new PocketBrain class. This now acts as
the sole source-of-truth, with all state being synced back to the
original item stack on the entity tick.

This also adds a new PocketHolder interface, which generalises over the
various types that can hold a pocket computer (players and item
entities right now, possibly lecterns in the future).

[^1]: I'd say simplifying, but this would be a lie.
2024-07-28 21:13:07 +01:00
Jonathan Coates
2765abf971 Udpate to latest Neo, Fabric and Parchment
- Update to latest NeoForge, fixing issues with config API changes.
   Closes #1903.
 - Update to latest Fabric, switching to the ender pearl conventional
   tag, and new loot API.
2024-07-28 16:47:41 +01:00
Jonathan Coates
4dd0735066 Register modems as attached to their adjacent block
In c8eadf4011 we marked our various modems
as "brittle", which ensures they do not pop-off computers when the whole
structure moves.

However, this still requires the modem to be glued — if the modem is
outside the superglue range, it will still pop off. We can fix it by
registering a special "attached check" for the various modem blocks,
which says that the modem should be moved when the adjacent block does.

Fixes #1913
2024-07-26 18:28:13 +01:00
Jonathan Coates
38e516d7c7 Update to Reuse 4.0 2024-07-26 18:28:00 +01:00
Jonathan Coates
70a31855ac Only send pocket computer updates to players in range
Previously we sent it to all players in the current level. This updates
the check to only send it to players tracking the current chunk.
2024-07-26 10:07:26 +01:00
Jonathan Coates
6c8e64ffcd Add get/setUpgrade to IPocketAccess
We have similar methods in ITurtleAccess, so makes sense for them to be
here too.
2024-07-26 09:51:09 +01:00
Jonathan Coates
7285c32d58 Document that the speaker re-encoded audio samples 2024-07-25 20:32:08 +01:00
Jonathan Coates
99c60ac54b Remove IComputerBlockEntity
We only really made use of it in the has_computer_id loot condition, so
probably easier to remove it.
2024-07-25 09:28:00 +01:00
Jonathan Coates
63e40cf3cb Add a cc.strings.split method
This is largely copied from metis, with the documentation updated.
2024-07-24 22:18:50 +01:00
Jonathan Coates
1d45935a25 Update links to IRC
The EsperNet webchat has been decommissioned, with no plans to
replace. KiwiIRC is the recommended replacement, but I'm not comfortable
using it as a drop-in replacement[^1], so I've rephrased this section to
make it more clear.

[^1]: There seems to be ongoing issues with TLS certificates
  (https://github.com/kiwiirc/kiwiirc/issues/1870), which meant it
  wasn't usable when I first tried.
2024-07-24 21:38:14 +01:00
Jonathan Coates
f80373e7a2 Add bounds check to cc.strings.wrap
Fixes #1905, closes #1906.

Co-authored-by: Lupus590 <lupussolitarius590@gmail.com>
2024-07-24 19:40:10 +01:00
Jonathan Coates
63185629b7 Use "require" in textutils
This avoids us having to support requireless environments inside
cc.strings.

I do kinda wonder if os.loadAPI-loaded files should also have their own
shared "require", just so we're not loading 10 copies of cc.expect.
2024-07-24 19:40:10 +01:00
Jonathan Coates
4bfb9ac323 Fix/update language checker script
- Update location of the generated language file to point to common
   rather than Fabric.
 - Remove usage of OrderedDict, as dicts are ordered on recent versions
   of Python.
2024-07-24 19:40:09 +01:00
Jonathan Coates
5926b6c994 Update Gradle to 8.9
Fix several deprecation warnings, and specify the toolchain required to
launch the daemon.
2024-07-24 19:40:09 +01:00
Jonathan Coates
f5ed43584d Add tests for turtle equipping and crafting 2024-07-24 19:40:09 +01:00
Jonathan Coates
2c740bb904 Bump CC:T to 1.111.1 2024-07-04 19:08:06 +01:00
csqrb
d77f5f135f Preserve item data when upgrading pocket computers (#1888) 2024-07-03 07:21:02 +00:00
Jonathan Coates
0e4710a956 Update NF and NG
- Rename ToolActions to ItemAbilities. Closes #1881.
 - Remove our source set helper, as NG has built-in support for this
   now.
 - Remove our code to generate new JavaExec tasks from runs, as NG now
   generates JavaExec tasks normally.
2024-06-29 12:58:03 +01:00
Jonathan Coates
aca1d43550 Merge branch 'mc-1.20.x' into mc-1.21.x 2024-06-29 10:50:44 +01:00
Jonathan Coates
f10e401aea Load turtle overlays from a registry
- Add a new computercraft:turtle_overlay dynamic registry, which stores
   turtle overlays. Turtle overlays are just a model id and an
   (optional) boolean flag, which specifies whether this overlay is
   compatible with the elf/christmas model.

 - Change the computercraft:overlay component to accept a
   Holder<TurtleOverlay> (instead of just a model ID). This accepts both
   an overlay ID or an inline overlay object (e.g. you can do
   cc:turtle_normal[computercraft:overlay={model:"foo"}].

 - Update turtle model and BE rendering code to render both the overlay
   and elf (if compatible). Fixes #1663.

 - Ideally we'd automatically load all models listed in the overlay
   registry. However, resource loading happens separately to datapacks,
   so we can't link the two.

   Instead, we add a new assets/computercraft/extra_models.json file
   that lists any additional models that should be loaded and baked.

   This file includes all built-in overlay models, but external resource
   packs and/or mods can easily extend it.
2024-06-27 20:57:43 +01:00
Jonathan Coates
7744d2663b Fix heights of turtle flags
They were 0.5 squares too high, so the textures were a little stretched.
2024-06-27 20:41:42 +01:00
Jonathan Coates
4566cb8273 Add path-based error constructor to FileSystemException
This doesn't change any functionality, but means we only construct
"/{path}: {message}" strings in one location.
2024-06-26 21:12:44 +01:00
Jonathan Coates
052e7a7ae5 Make FileSystem.toLocal private
Use a custom to-local function in the various ArchiveMounts, which don't
faff around with sanitising paths.
2024-06-26 21:12:05 +01:00
Jonathan Coates
0895200681 Small bits of cleanup
Build system:
 - Switch to our new maven server. This has a cleaner separation between
   published packages and mirrored packages, to avoid leaking those into
   other people's builds.
 - Update Gradle and Loom versions.

Code:
 - Link to definitions instead in the breaking changes page.
 - Fix several unused variable warnings.

Other:
 - Remove unsupported Minecraft versions from the issue template.
2024-06-26 18:07:57 +01:00
Jonathan Coates
1a1623075f Fix turtle labels not rendering
The X scale factor should now be flipped. I'm not quite sure what in MC
has meant this should be changed, possibly the cameraOrientation matrix?

Fixes #1872
2024-06-26 18:06:48 +01:00
Jonathan Coates
54a95e07a4 Fix monitors not updating on NeoForge 2024-06-23 09:21:15 +01:00
Jonathan Coates
09d0f563b7 Add 1.21 to version list 2024-06-23 09:14:32 +01:00
Jonathan Coates
e188f1d3fa Link to os.time in os.setAlarm docs 2024-06-23 08:43:42 +01:00
132 changed files with 2233 additions and 856 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -1,100 +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_upgrade/*
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/forge/src/main/resources/computercraft.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

View File

@@ -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 21 (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

View File

@@ -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")
}
@@ -85,6 +86,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"

111
REUSE.toml Normal file
View File

@@ -0,0 +1,111 @@
# 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_upgrade/**",
"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/forge/src/main/resources/computercraft.forge.mixins.json",
"projects/web/src/frontend/mount/.settings",
"projects/web/src/frontend/mount/example.nfp",
"projects/web/src/frontend/mount/example.nft",
"projects/web/src/frontend/mount/expr_template.lua",
"projects/web/tsconfig.json",
]
[[annotations]]
# Bulk-license original assets as CCPL.
SPDX-FileCopyrightText = "2011 Daniel Ratcliffe"
SPDX-License-Identifier = "LicenseRef-CCPL"
path = [
"doc/logo.png",
"doc/logo-darkmode.png",
"projects/common/src/main/resources/assets/computercraft/models/**",
"projects/common/src/main/resources/assets/computercraft/textures/**",
"projects/common/src/main/resources/pack.mcmeta",
"projects/common/src/main/resources/pack.png",
"projects/core/src/main/resources/assets/computercraft/textures/gui/term_font.png",
"projects/core/src/main/resources/data/computercraft/lua/rom/autorun/.ignoreme",
"projects/core/src/main/resources/data/computercraft/lua/rom/help/**",
"projects/core/src/main/resources/data/computercraft/lua/rom/programs/fun/advanced/levels/**",
"projects/web/src/htmlTransform/export/items/computercraft/**",
]
[[annotations]]
# Community-contributed license files
SPDX-FileCopyrightText = "2017 The CC: Tweaked Developers"
SPDX-License-Identifier = "LicenseRef-CCPL"
path = [
"projects/common/src/main/resources/assets/computercraft/lang/cs_cz.json",
"projects/common/src/main/resources/assets/computercraft/lang/ko_kr.json",
"projects/common/src/main/resources/assets/computercraft/lang/pl_pl.json",
"projects/common/src/main/resources/assets/computercraft/lang/pt_br.json",
"projects/common/src/main/resources/assets/computercraft/lang/ru_ru.json",
"projects/common/src/main/resources/assets/computercraft/lang/uk_ua.json",
"projects/common/src/main/resources/assets/computercraft/lang/zh_cn.json",
]
[[annotations]]
# Community-contributed license files
SPDX-FileCopyrightText = "2017 The CC: Tweaked Developers"
SPDX-License-Identifier = "MPL-2.0"
path = "projects/common/src/main/resources/assets/computercraft/lang/**"
[[annotations]]
# GitHub build scripts are CC0. While we could add a header to each file,
# it's unclear if it will break actions or issue templates in some way.
SPDX-FileCopyrightText = "Jonathan Coates <git@squiddev.cc>"
SPDX-License-Identifier = "CC0-1.0"
path = ".github/**"
[[annotations]]
path = ["gradle/wrapper/**", "gradlew", "gradlew.bat"]
SPDX-FileCopyrightText = "Gradle Inc"
SPDX-License-Identifier = "Apache-2.0"
[[annotations]]
path = "projects/core/src/test/resources/test-rom/data/json-parsing/**"
SPDX-FileCopyrightText = "2016 Nicolas Seriot"
SPDX-License-Identifier = "MIT"

View File

@@ -32,7 +32,7 @@ repositories {
}
}
maven("https://squiddev.cc/maven") {
maven("https://maven.squiddev.cc") {
name = "SquidDev"
content {
includeGroup("cc.tweaked.vanilla-extract")

View File

@@ -38,7 +38,7 @@ java {
repositories {
mavenCentral()
val mainMaven = maven("https://squiddev.cc/maven") {
val mainMaven = maven("https://maven.squiddev.cc/mirror") {
name = "SquidDev"
}
@@ -47,6 +47,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 {

View File

@@ -38,7 +38,7 @@ publishing {
}
repositories {
maven("https://squiddev.cc/maven") {
maven("https://maven.squiddev.cc") {
name = "SquidDev"
credentials(PasswordCredentials::class)

View File

@@ -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 {

View File

@@ -9,6 +9,7 @@ import org.gradle.api.file.FileSystemLocation
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.JavaExec
import org.gradle.kotlin.dsl.getByName
import org.gradle.process.BaseExecSpec
import org.gradle.process.JavaExecSpec
import org.gradle.process.ProcessForkOptions
@@ -46,6 +47,21 @@ fun JavaExec.copyToFull(spec: JavaExec) {
copyToExec(spec)
}
/**
* Base this [JavaExec] task on an existing task.
*/
fun JavaExec.copyFromTask(task: JavaExec) {
for (dep in task.dependsOn) dependsOn(dep)
task.copyToFull(this)
}
/**
* Base this [JavaExec] task on an existing task.
*/
fun JavaExec.copyFromTask(task: String) {
copyFromTask(project.tasks.getByName<JavaExec>(task))
}
/**
* Copy additional [BaseExecSpec] options which aren't handled by [ProcessForkOptions.copyTo].
*/
@@ -155,3 +171,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)
}

View File

@@ -1,62 +0,0 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package cc.tweaked.gradle
import net.neoforged.gradle.common.runs.run.RunImpl
import net.neoforged.gradle.common.runs.tasks.RunExec
import net.neoforged.gradle.dsl.common.extensions.RunnableSourceSet
import net.neoforged.gradle.dsl.common.runs.run.Run
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.tasks.JavaExec
import org.gradle.api.tasks.SourceSet
import org.gradle.jvm.toolchain.JavaToolchainService
import org.gradle.kotlin.dsl.assign
import org.gradle.kotlin.dsl.create
import org.gradle.kotlin.dsl.findByType
import java.nio.file.Files
/**
* Set [JavaExec] task to run a given [RunConfig].
*
* See also [RunExec].
*/
fun JavaExec.setRunConfig(config: Run) {
mainClass.set(config.mainClass)
workingDir = config.workingDirectory.get().asFile
argumentProviders.add { config.programArguments.get() }
jvmArgumentProviders.add { config.jvmArguments.get() }
environment(config.environmentVariables.get())
systemProperties(config.systemProperties.get())
config.modSources.all().get().values().forEach { classpath(it.runtimeClasspath) }
classpath(config.classpath)
classpath(config.dependencies.get().runtimeConfiguration)
(config as RunImpl).taskDependencies.forEach { dependsOn(it) }
javaLauncher.set(
project.extensions.getByType(JavaToolchainService::class.java)
.launcherFor(project.extensions.getByType(JavaPluginExtension::class.java).toolchain),
)
doFirst("Create working directory") { Files.createDirectories(workingDir.toPath()) }
}
/**
* Add a new [Run.modSource] with a specific mod id.
*/
fun Run.modSourceAs(sourceSet: SourceSet, mod: String) {
// NeoGradle requires a RunnableSourceSet to be present, so we inject it into other project's source sets.
val runnable = sourceSet.extensions.findByType<RunnableSourceSet>() ?: run {
val extension = sourceSet.extensions.create<RunnableSourceSet>(RunnableSourceSet.NAME, project)
extension.modIdentifier = mod
extension.modIdentifier.finalizeValueOnRead()
extension
}
if (runnable.modIdentifier.get() != mod) throw IllegalArgumentException("Multiple mod identifiers")
modSource(sourceSet)
}

View File

@@ -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

View File

@@ -50,7 +50,11 @@ little daunting getting started. Thankfully, there's several fantastic tutorials
Once you're a little more familiar with the mod, the sidebar and links below provide more detailed documentation on the
various APIs and peripherals provided by the mod.
If you get stuck, do [ask a question on GitHub][GitHub Discussions] or pop in to the ComputerCraft's [IRC channel][IRC].
<h2 id="community">Community</h2>
If you need help getting started with CC: Tweaked, want to show off your latest project, or just want to chat about
ComputerCraft, do check out our [GitHub discussions page][GitHub discussions]! There's also a fairly populated,
albeit quiet IRC channel on [EsperNet], if that's more your cup of tea. You can join `#computercraft` through your
desktop client, or online using [KiwiIRC].
## Get Involved
CC: Tweaked lives on [GitHub]. If you've got any ideas, feedback or bugs please do [create an issue][bug].
@@ -65,4 +69,5 @@ CC: Tweaked lives on [GitHub]. If you've got any ideas, feedback or bugs please
[Fabric]: https://fabricmc.net/use/installer/ "Download Fabric."
[lua]: https://www.lua.org/ "Lua's main website"
[GitHub Discussions]: https://github.com/cc-tweaked/CC-Tweaked/discussions
[IRC]: https://webchat.esper.net/?channels=computercraft "#computercraft on EsperNet"
[EsperNet]: https://www.esper.net/
[KiwiIRC]: https://kiwiirc.com/nextclient/#irc://irc.esper.net:+6697/#computercraft "#computercraft on EsperNet"

View File

@@ -50,7 +50,11 @@ little daunting getting started. Thankfully, there's several fantastic tutorials
Once you're a little more familiar with the mod, the [wiki](https://tweaked.cc/) provides more detailed documentation on the
various APIs and peripherals provided by the mod.
If you get stuck, do [ask a question on GitHub][GitHub Discussions] or pop in to the ComputerCraft's [IRC channel][IRC].
## Community
If you need help getting started with CC: Tweaked, want to show off your latest project, or just want to chat about
ComputerCraft, do check out our [GitHub discussions page][GitHub discussions]! There's also a fairly populated,
albeit quiet IRC channel on [EsperNet], if that's more your cup of tea. You can join `#computercraft` through your
desktop client, or online using [KiwiIRC].
## Get Involved
CC: Tweaked lives on [GitHub]. If you've got any ideas, feedback or bugs please do [create an issue][bug].
@@ -60,4 +64,5 @@ CC: Tweaked lives on [GitHub]. If you've got any ideas, feedback or bugs please
[computercraft]: https://github.com/dan200/ComputerCraft "ComputerCraft on GitHub"
[lua]: https://www.lua.org/ "Lua's main website"
[GitHub Discussions]: https://github.com/cc-tweaked/CC-Tweaked/discussions
[IRC]: http://webchat.esper.net/?channels=computercraft "#computercraft on EsperNet"
[EsperNet]: https://www.esper.net/
[KiwiIRC]: https://kiwiirc.com/nextclient/#irc://irc.esper.net:+6697/#computercraft "#computercraft on EsperNet"

View File

@@ -25,13 +25,13 @@ as documentation for breaking changes and "gotchas" one should look out for betw
- Update to Lua 5.2:
- Support for Lua 5.0's pseudo-argument `arg` has been removed. You should always use `...` for varargs.
- Environments are no longer baked into the runtime, and instead use the `_ENV` local or upvalue. `getfenv`/`setfenv`
now only work on Lua functions with an `_ENV` upvalue. `getfenv` will return the global environment when called
with other functions, and `setfenv` will have no effect.
- `load`/`loadstring` defaults to using the global environment (`_G`) rather than the current coroutine's
- Environments are no longer baked into the runtime, and instead use the `_ENV` local or upvalue. [`getfenv`]/[`setfenv`]
now only work on Lua functions with an `_ENV` upvalue. [`getfenv`] will return the global environment when called
with other functions, and [`setfenv`] will have no effect.
- [`load`]/[`loadstring`] defaults to using the global environment (`_G`) rather than the current coroutine's
environment.
- Support for dumping functions (`string.dump`) and loading binary chunks has been removed.
- `math.random` now uses Lua 5.4's random number generator.
- Support for dumping functions ([`string.dump`]) and loading binary chunks has been removed.
- [`math.random`] now uses Lua 5.4's random number generator.
- File handles, HTTP requests and websockets now always use the original bytes rather than encoding/decoding to UTF-8.
@@ -44,7 +44,7 @@ as documentation for breaking changes and "gotchas" one should look out for betw
`keys.enter` constant was queued when the key was pressed)
- Minecraft 1.13 removed the concept of item damage and block metadata (see ["The Flattening"][flattening]). As a
result `turtle.inspect` no longer provides block metadata, and `turtle.getItemDetail` no longer provides damage.
result [`turtle.inspect`] no longer provides block metadata, and [`turtle.getItemDetail`] no longer provides damage.
- Block states (`turtle.inspect().state`) should provide all the same information as block metadata, but in a much
more understandable format.
@@ -70,7 +70,7 @@ as documentation for breaking changes and "gotchas" one should look out for betw
- Unlabelled computers and turtles now keep their ID when broken, meaning that unlabelled computers/items do not stack.
## ComputerCraft 1.80pr1 {#cc-1.80}
- Programs run via `shell.run` are now started in their own isolated environment. This means globals set by programs
- Programs run via [`shell.run`] are now started in their own isolated environment. This means globals set by programs
will not be accessible outside of this program.
- Programs containing `/` are looked up in the current directory and are no longer looked up on the path. For instance,

View File

@@ -12,7 +12,7 @@ neogradle.subsystems.conventions.runs.enabled=false
# Mod properties
isUnstable=true
modVersion=1.111.0
modVersion=1.112.0
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
mcVersion=1.21

View File

@@ -0,0 +1,2 @@
#This file is generated by updateDaemonJvm
toolchainVersion=21

View File

@@ -7,13 +7,13 @@
# Minecraft
# MC version is specified in gradle.properties, as we need that in settings.gradle.
# Remember to update corresponding versions in fabric.mod.json/neoforge.mods.toml
fabric-api = "0.100.3+1.21"
fabric-api = "0.100.7+1.21"
fabric-loader = "0.15.11"
neoForge = "21.0.21-beta"
neoForge = "21.0.143"
neoForgeSpi = "8.0.1"
mixin = "0.8.5"
parchment = "2024.06.16"
parchmentMc = "1.20.6"
parchment = "2024.07.28"
parchmentMc = "1.21"
yarn = "1.21+build.1"
# Core dependencies (these versions are tied to the version Minecraft uses)
@@ -47,6 +47,8 @@ rei = "16.0.729"
rubidium = "0.6.1"
sodium = "mc1.20-0.4.10"
mixinExtra = "0.3.5"
create-forge = "0.5.1.f-33"
create-fabric = "0.5.1-f-build.1467+mc1.20.1"
# Testing
hamcrest = "2.2"
@@ -60,18 +62,18 @@ checkstyle = "10.14.1"
curseForgeGradle = "1.1.18"
errorProne-core = "2.27.0"
errorProne-plugin = "3.1.0"
fabric-loom = "1.6.7"
fabric-loom = "1.7.1"
githubRelease = "2.5.2"
gradleVersions = "0.50.0"
ideaExt = "1.1.7"
illuaminate = "0.1.0-73-g43ee16c"
lwjgl = "3.3.3"
minotaur = "2.8.7"
neoGradle = "7.0.145"
neoGradle = "7.0.152"
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.21-common-api", version.ref = "jei" }
jei-fabric = { module = "mezz.jei:jei-1.21-fabric", version.ref = "jei" }

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

2
gradlew vendored
View File

@@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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 + ")";
}
}

View File

@@ -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");
}

View File

@@ -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);
}

View File

@@ -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 {

View File

@@ -5,16 +5,35 @@
package dan200.computercraft.api.pocket;
import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeData;
import net.minecraft.core.component.DataComponentPatch;
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;
/**
* 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>
@@ -61,6 +80,26 @@ public interface IPocketAccess {
*/
void setLight(int colour);
/**
* Get the currently equipped upgrade.
*
* @return The currently equipped upgrade.
* @see #getUpgradeData()
* @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>
@@ -70,6 +109,7 @@ public interface IPocketAccess {
* @see #setUpgradeData(DataComponentPatch)
* @see UpgradeBase#getUpgradeItem(DataComponentPatch)
* @see UpgradeBase#getUpgradeData(ItemStack)
* @see #getUpgrade()
*/
DataComponentPatch getUpgradeData();

View File

@@ -45,6 +45,7 @@ dependencies {
compileOnly(libs.mixin)
compileOnly(libs.mixinExtra)
compileOnly(libs.bundles.externalMods.common)
compileOnly(variantOf(libs.create.forge) { classifier("slim") }) { isTransitive = false }
clientCompileOnly(variantOf(libs.emi) { classifier("api") })
annotationProcessorEverywhere(libs.autoService)

View File

@@ -25,6 +25,7 @@ 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.media.items.DiskItem;
import dan200.computercraft.shared.turtle.TurtleOverlay;
import net.minecraft.Util;
import net.minecraft.client.Minecraft;
import net.minecraft.client.color.item.ItemColor;
@@ -53,6 +54,7 @@ import net.minecraft.world.level.ItemLike;
import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -143,15 +145,14 @@ public final class ClientRegistry {
register.accept(GuiSprites.initialise(minecraft.getTextureManager()));
}
private static final String[] EXTRA_MODELS = new String[]{
"block/turtle_colour",
"block/turtle_elf_overlay",
"block/turtle_rainbow_overlay",
"block/turtle_trans_overlay",
private static final ResourceLocation[] EXTRA_MODELS = {
TurtleOverlay.ELF_MODEL,
TurtleBlockEntityRenderer.COLOUR_TURTLE_MODEL,
};
public static void registerExtraModels(Consumer<ResourceLocation> register) {
for (var model : EXTRA_MODELS) register.accept(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, model));
public static void registerExtraModels(Consumer<ResourceLocation> register, Collection<ResourceLocation> extraModels) {
for (var model : EXTRA_MODELS) register.accept(model);
extraModels.forEach(register);
TurtleUpgradeModellers.getDependencies().forEach(register);
}

View File

@@ -0,0 +1,68 @@
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.client.model;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.shared.turtle.TurtleOverlay;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.*;
/**
* A list of extra models to load on the client.
* <p>
* This is largely intended for use with {@linkplain TurtleOverlay turtle overlays}. As overlays are stored in a dynamic
* registry, they are not available when resources are loaded, and so we need a way to request the overlays' models be
* loaded.
*
* @param models The models to load.
*/
public record ExtraModels(List<ResourceLocation> models) {
private static final Logger LOG = LoggerFactory.getLogger(ExtraModels.class);
private static final Gson GSON = new Gson();
/**
* The path where the extra models are listed.
*/
public static final ResourceLocation PATH = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "extra_models.json");
/**
* The coded used to store the extra model file.
*/
public static final Codec<ExtraModels> CODEC = ResourceLocation.CODEC.listOf().xmap(ExtraModels::new, ExtraModels::models);
/**
* Get the list of all extra models to load.
*
* @param resources The current resource manager.
* @return A set of all resources to load.
*/
public static Collection<ResourceLocation> loadAll(ResourceManager resources) {
Set<ResourceLocation> out = new HashSet<>();
for (var path : resources.getResourceStack(PATH)) {
ExtraModels models;
try (var stream = path.openAsReader()) {
models = ExtraModels.CODEC.parse(JsonOps.INSTANCE, GSON.fromJson(stream, JsonElement.class)).getOrThrow(JsonParseException::new);
} catch (IOException | RuntimeException e) {
LOG.error("Failed to load extra models from {}", path.sourcePackId());
continue;
}
out.addAll(models.models());
}
return Collections.unmodifiableCollection(out);
}
}

View File

@@ -12,13 +12,14 @@ import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.client.platform.ClientPlatformHelper;
import dan200.computercraft.client.render.TurtleBlockEntityRenderer;
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
import dan200.computercraft.shared.turtle.TurtleOverlay;
import dan200.computercraft.shared.turtle.items.TurtleItem;
import dan200.computercraft.shared.util.DataComponentUtil;
import dan200.computercraft.shared.util.Holiday;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.core.component.DataComponents;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
@@ -52,7 +53,7 @@ public final class TurtleModelParts<T> {
boolean colour,
@Nullable UpgradeData<ITurtleUpgrade> leftUpgrade,
@Nullable UpgradeData<ITurtleUpgrade> rightUpgrade,
@Nullable ResourceLocation overlay,
@Nullable TurtleOverlay overlay,
boolean christmas,
boolean flip
) {
@@ -113,10 +114,10 @@ public final class TurtleModelParts<T> {
var parts = new ArrayList<BakedModel>(4);
parts.add(transform(combo.colour() ? colourModel : familyModel, transformation));
var overlayModelLocation = TurtleBlockEntityRenderer.getTurtleOverlayModel(combo.overlay(), combo.christmas());
if (overlayModelLocation != null) {
parts.add(transform(ClientPlatformHelper.get().getModel(modelManager, overlayModelLocation), transformation));
}
if (combo.overlay() != null) addPart(parts, modelManager, transformation, combo.overlay().model());
var showChristmas = TurtleOverlay.showElfOverlay(combo.overlay(), combo.christmas());
if (showChristmas) addPart(parts, modelManager, transformation, TurtleOverlay.ELF_MODEL);
addUpgrade(parts, transformation, TurtleSide.LEFT, combo.leftUpgrade());
addUpgrade(parts, transformation, TurtleSide.RIGHT, combo.rightUpgrade());
@@ -124,6 +125,10 @@ public final class TurtleModelParts<T> {
return parts;
}
private void addPart(List<BakedModel> parts, ModelManager modelManager, Transformation transformation, ResourceLocation model) {
parts.add(transform(ClientPlatformHelper.get().getModel(modelManager, model), transformation));
}
private void addUpgrade(List<BakedModel> parts, Transformation transformation, TurtleSide side, @Nullable UpgradeData<ITurtleUpgrade> upgrade) {
if (upgrade == null) return;
var model = TurtleUpgradeModellers.getModel(upgrade.upgrade(), upgrade.data(), side);

View File

@@ -11,6 +11,7 @@ import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.client.platform.ClientPlatformHelper;
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
import dan200.computercraft.shared.turtle.TurtleOverlay;
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
import dan200.computercraft.shared.util.Holiday;
import net.minecraft.client.Minecraft;
@@ -21,14 +22,15 @@ import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.CommonColors;
import net.minecraft.util.FastColor;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import javax.annotation.Nullable;
public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBlockEntity> {
private static final ResourceLocation COLOUR_TURTLE_MODEL = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_colour");
private static final ResourceLocation ELF_OVERLAY_MODEL = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_elf_overlay");
public static final ResourceLocation COLOUR_TURTLE_MODEL = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_colour");
private final BlockEntityRenderDispatcher renderer;
private final Font font;
@@ -38,12 +40,6 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
font = context.getFont();
}
public static @Nullable ResourceLocation getTurtleOverlayModel(@Nullable ResourceLocation overlay, boolean christmas) {
if (overlay != null) return overlay;
if (christmas) return ELF_OVERLAY_MODEL;
return null;
}
@Override
public void render(TurtleBlockEntity turtle, float partialTicks, PoseStack transform, MultiBufferSource buffers, int lightmapCoord, int overlayLight) {
transform.pushPose();
@@ -62,13 +58,13 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
transform.pushPose();
transform.translate(0.5, 1.2, 0.5);
transform.mulPose(mc.getEntityRenderDispatcher().cameraOrientation());
transform.scale(-0.025f, -0.025f, 0.025f);
transform.scale(0.025f, -0.025f, 0.025f);
var matrix = transform.last().pose();
var opacity = (int) (mc.options.getBackgroundOpacity(0.25f) * 255) << 24;
var width = -font.width(label) / 2.0f;
font.drawInBatch(label, width, (float) 0, 0x20ffffff, false, matrix, buffers, Font.DisplayMode.SEE_THROUGH, opacity, lightmapCoord);
font.drawInBatch(label, width, (float) 0, 0xffffffff, false, matrix, buffers, Font.DisplayMode.NORMAL, 0, lightmapCoord);
font.drawInBatch(label, width, (float) 0, CommonColors.WHITE, false, matrix, buffers, Font.DisplayMode.NORMAL, 0, lightmapCoord);
transform.popPose();
}
@@ -94,14 +90,15 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
renderModel(transform, buffers, lightmapCoord, overlayLight, model, null);
} else {
// Otherwise render it using the colour item.
renderModel(transform, buffers, lightmapCoord, overlayLight, COLOUR_TURTLE_MODEL, new int[]{ colour });
renderModel(transform, buffers, lightmapCoord, overlayLight, COLOUR_TURTLE_MODEL, new int[]{ FastColor.ARGB32.opaque(colour) });
}
// Render the overlay
var overlayModel = getTurtleOverlayModel(overlay, Holiday.getCurrent() == Holiday.CHRISTMAS);
if (overlayModel != null) {
renderModel(transform, buffers, lightmapCoord, overlayLight, overlayModel, null);
}
if (overlay != null) renderModel(transform, buffers, lightmapCoord, overlayLight, overlay.model(), null);
// And the Christmas overlay.
var showChristmas = TurtleOverlay.showElfOverlay(overlay, Holiday.getCurrent() == Holiday.CHRISTMAS);
if (showChristmas) renderModel(transform, buffers, lightmapCoord, overlayLight, TurtleOverlay.ELF_MODEL, null);
// Render the upgrades
renderUpgrade(transform, buffers, lightmapCoord, overlayLight, turtle, TurtleSide.LEFT, partialTicks);

View File

@@ -6,15 +6,18 @@ package dan200.computercraft.data.client;
import dan200.computercraft.client.gui.GuiSprites;
import dan200.computercraft.data.DataProviders;
import dan200.computercraft.shared.turtle.TurtleOverlay;
import dan200.computercraft.shared.turtle.inventory.UpgradeSlot;
import net.minecraft.client.renderer.texture.atlas.SpriteSource;
import net.minecraft.client.renderer.texture.atlas.SpriteSources;
import net.minecraft.client.renderer.texture.atlas.sources.SingleFile;
import net.minecraft.core.HolderLookup;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.PackType;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
/**
@@ -26,7 +29,7 @@ public final class ClientDataProviders {
private ClientDataProviders() {
}
public static void add(DataProviders.GeneratorSink generator) {
public static void add(DataProviders.GeneratorSink generator, CompletableFuture<HolderLookup.Provider> registries) {
generator.addFromCodec("Block atlases", PackType.CLIENT_RESOURCES, "atlases", SpriteSources.FILE_CODEC, out -> {
out.accept(ResourceLocation.withDefaultNamespace("blocks"), List.of(
new SingleFile(UpgradeSlot.LEFT_UPGRADE, Optional.empty()),
@@ -44,5 +47,12 @@ public final class ClientDataProviders {
GuiSprites.COMPUTER_COLOUR.textures()
).flatMap(x -> x).<SpriteSource>map(x -> new SingleFile(x, Optional.empty())).toList());
});
generator.add(pack -> new ExtraModelsProvider(pack, registries) {
@Override
public Stream<ResourceLocation> getModels(HolderLookup.Provider registries) {
return registries.lookupOrThrow(TurtleOverlay.REGISTRY).listElements().map(x -> x.value().model());
}
});
}
}

View File

@@ -0,0 +1,52 @@
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.data.client;
import com.mojang.serialization.JsonOps;
import dan200.computercraft.client.model.ExtraModels;
import net.minecraft.core.HolderLookup;
import net.minecraft.data.CachedOutput;
import net.minecraft.data.DataProvider;
import net.minecraft.data.PackOutput;
import net.minecraft.resources.ResourceLocation;
import java.nio.file.Path;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
/**
* A data provider to generate {@link ExtraModels}.
*/
abstract class ExtraModelsProvider implements DataProvider {
private final Path path;
private final CompletableFuture<HolderLookup.Provider> registries;
ExtraModelsProvider(PackOutput output, CompletableFuture<HolderLookup.Provider> registries) {
path = output.getOutputFolder(PackOutput.Target.RESOURCE_PACK).resolve(ExtraModels.PATH.getNamespace()).resolve(ExtraModels.PATH.getPath());
this.registries = registries;
}
/**
* Return a stream of models to load.
*
* @param registries The current registries.
* @return The collection of extra models to load.
*/
public abstract Stream<ResourceLocation> getModels(HolderLookup.Provider registries);
@Override
public final CompletableFuture<?> run(CachedOutput output) {
return registries.thenCompose(registries -> {
var models = new ExtraModels(getModels(registries).sorted().toList());
var json = ExtraModels.CODEC.encodeStart(JsonOps.INSTANCE, models).getOrThrow(IllegalStateException::new);
return DataProvider.saveStable(output, json, path);
});
}
@Override
public final String getName() {
return "Extra Models";
}
}

View File

@@ -0,0 +1 @@
["computercraft:block/turtle_rainbow_overlay", "computercraft:block/turtle_trans_overlay"]

View File

@@ -3,7 +3,7 @@
"criteria": {
"has_dye": {"conditions": {"items": [{"items": "#c:dyes"}]}, "trigger": "minecraft:inventory_changed"},
"has_the_recipe": {
"conditions": {"recipe": "computercraft:turtle_advanced_overlays/turtle_trans_overlay"},
"conditions": {"recipe": "computercraft:turtle_advanced_overlays/rainbow_flag"},
"trigger": "minecraft:recipe_unlocked"
},
"has_turtle": {
@@ -12,5 +12,5 @@
}
},
"requirements": [["has_the_recipe", "has_turtle", "has_dye"]],
"rewards": {"recipes": ["computercraft:turtle_advanced_overlays/turtle_trans_overlay"]}
"rewards": {"recipes": ["computercraft:turtle_advanced_overlays/rainbow_flag"]}
}

View File

@@ -3,7 +3,7 @@
"criteria": {
"has_dye": {"conditions": {"items": [{"items": "#c:dyes"}]}, "trigger": "minecraft:inventory_changed"},
"has_the_recipe": {
"conditions": {"recipe": "computercraft:turtle_advanced_overlays/turtle_rainbow_overlay"},
"conditions": {"recipe": "computercraft:turtle_advanced_overlays/trans_flag"},
"trigger": "minecraft:recipe_unlocked"
},
"has_turtle": {
@@ -12,5 +12,5 @@
}
},
"requirements": [["has_the_recipe", "has_turtle", "has_dye"]],
"rewards": {"recipes": ["computercraft:turtle_advanced_overlays/turtle_rainbow_overlay"]}
"rewards": {"recipes": ["computercraft:turtle_advanced_overlays/trans_flag"]}
}

View File

@@ -3,7 +3,7 @@
"criteria": {
"has_dye": {"conditions": {"items": [{"items": "#c:dyes"}]}, "trigger": "minecraft:inventory_changed"},
"has_the_recipe": {
"conditions": {"recipe": "computercraft:turtle_normal_overlays/turtle_trans_overlay"},
"conditions": {"recipe": "computercraft:turtle_normal_overlays/rainbow_flag"},
"trigger": "minecraft:recipe_unlocked"
},
"has_turtle": {
@@ -12,5 +12,5 @@
}
},
"requirements": [["has_the_recipe", "has_turtle", "has_dye"]],
"rewards": {"recipes": ["computercraft:turtle_normal_overlays/turtle_trans_overlay"]}
"rewards": {"recipes": ["computercraft:turtle_normal_overlays/rainbow_flag"]}
}

View File

@@ -3,7 +3,7 @@
"criteria": {
"has_dye": {"conditions": {"items": [{"items": "#c:dyes"}]}, "trigger": "minecraft:inventory_changed"},
"has_the_recipe": {
"conditions": {"recipe": "computercraft:turtle_normal_overlays/turtle_rainbow_overlay"},
"conditions": {"recipe": "computercraft:turtle_normal_overlays/trans_flag"},
"trigger": "minecraft:recipe_unlocked"
},
"has_turtle": {
@@ -12,5 +12,5 @@
}
},
"requirements": [["has_the_recipe", "has_turtle", "has_dye"]],
"rewards": {"recipes": ["computercraft:turtle_normal_overlays/turtle_rainbow_overlay"]}
"rewards": {"recipes": ["computercraft:turtle_normal_overlays/trans_flag"]}
}

View File

@@ -0,0 +1 @@
{"model": "computercraft:block/turtle_rainbow_overlay", "show_elf_overlay": true}

View File

@@ -0,0 +1 @@
{"model": "computercraft:block/turtle_trans_overlay", "show_elf_overlay": true}

View File

@@ -20,7 +20,7 @@
{"item": "computercraft:turtle_advanced"}
],
"result": {
"components": {"computercraft:overlay": "computercraft:block/turtle_rainbow_overlay"},
"components": {"computercraft:overlay": "computercraft:rainbow_flag"},
"count": 1,
"id": "computercraft:turtle_advanced"
}

View File

@@ -17,7 +17,7 @@
{"item": "computercraft:turtle_advanced"}
],
"result": {
"components": {"computercraft:overlay": "computercraft:block/turtle_trans_overlay"},
"components": {"computercraft:overlay": "computercraft:trans_flag"},
"count": 1,
"id": "computercraft:turtle_advanced"
}

View File

@@ -20,7 +20,7 @@
{"item": "computercraft:turtle_normal"}
],
"result": {
"components": {"computercraft:overlay": "computercraft:block/turtle_rainbow_overlay"},
"components": {"computercraft:overlay": "computercraft:rainbow_flag"},
"count": 1,
"id": "computercraft:turtle_normal"
}

View File

@@ -17,7 +17,7 @@
{"item": "computercraft:turtle_normal"}
],
"result": {
"components": {"computercraft:overlay": "computercraft:block/turtle_trans_overlay"},
"components": {"computercraft:overlay": "computercraft:trans_flag"},
"count": 1,
"id": "computercraft:turtle_normal"
}

View File

@@ -7,6 +7,7 @@ package dan200.computercraft.data;
import com.mojang.serialization.Codec;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.shared.turtle.TurtleOverlay;
import net.minecraft.Util;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.RegistrySetBuilder;
@@ -38,6 +39,7 @@ public final class DataProviders {
Util.make(new RegistrySetBuilder(), builder -> {
builder.add(ITurtleUpgrade.REGISTRY, TurtleUpgradeProvider::addUpgrades);
builder.add(IPocketUpgrade.REGISTRY, PocketUpgradeProvider::addUpgrades);
builder.add(TurtleOverlay.REGISTRY, TurtleOverlays::register);
}));
var fullRegistries = fullRegistryPatch.thenApply(RegistrySetBuilder.PatchedRegistries::full);
@@ -57,7 +59,8 @@ public final class DataProviders {
// and invoke that.
try {
Class.forName("dan200.computercraft.data.client.ClientDataProviders")
.getMethod("add", GeneratorSink.class).invoke(null, generator);
.getMethod("add", GeneratorSink.class, CompletableFuture.class)
.invoke(null, generator, fullRegistries);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}

View File

@@ -28,6 +28,7 @@ import dan200.computercraft.shared.recipe.ImpostorShapelessRecipe;
import dan200.computercraft.shared.recipe.TransformShapedRecipe;
import dan200.computercraft.shared.recipe.TransformShapelessRecipe;
import dan200.computercraft.shared.recipe.function.CopyComponents;
import dan200.computercraft.shared.turtle.TurtleOverlay;
import dan200.computercraft.shared.turtle.items.TurtleItem;
import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe;
import dan200.computercraft.shared.util.ColourUtils;
@@ -45,6 +46,7 @@ import net.minecraft.data.recipes.RecipeCategory;
import net.minecraft.data.recipes.RecipeOutput;
import net.minecraft.data.recipes.ShapedRecipeBuilder;
import net.minecraft.data.recipes.ShapelessRecipeBuilder;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.ItemTags;
import net.minecraft.tags.TagKey;
@@ -96,7 +98,7 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
diskColours(add);
pocketUpgrades(add, registries);
turtleUpgrades(add, registries);
turtleOverlays(add);
turtleOverlays(add, registries);
addSpecial(add, new DiskRecipe(CraftingBookCategory.MISC));
addSpecial(add, new ColourableRecipe(CraftingBookCategory.MISC));
@@ -189,8 +191,8 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
}
}
private void turtleOverlays(RecipeOutput add) {
turtleOverlay(add, "turtle_trans_overlay", x -> x
private void turtleOverlays(RecipeOutput add, HolderLookup.Provider registries) {
turtleOverlay(add, registries, TurtleOverlays.TRANS_FLAG, x -> x
.unlockedBy("has_dye", inventoryChange(itemPredicate(ingredients.dye())))
.requires(ColourUtils.getDyeTag(DyeColor.LIGHT_BLUE))
.requires(ColourUtils.getDyeTag(DyeColor.PINK))
@@ -198,7 +200,7 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
.requires(Items.STICK)
);
turtleOverlay(add, "turtle_rainbow_overlay", x -> x
turtleOverlay(add, registries, TurtleOverlays.RAINBOW_FLAG, x -> x
.unlockedBy("has_dye", inventoryChange(itemPredicate(ingredients.dye())))
.requires(ColourUtils.getDyeTag(DyeColor.RED))
.requires(ColourUtils.getDyeTag(DyeColor.ORANGE))
@@ -210,14 +212,14 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
);
}
private void turtleOverlay(RecipeOutput add, String overlay, Consumer<ShapelessSpecBuilder> build) {
var overlayId = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/" + overlay);
private void turtleOverlay(RecipeOutput add, HolderLookup.Provider registries, ResourceKey<TurtleOverlay> overlay, Consumer<ShapelessSpecBuilder> build) {
var holder = registries.lookupOrThrow(overlay.registryKey()).getOrThrow(overlay);
for (var turtleItem : turtleItems()) {
var name = RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, turtleItem);
var builder = ShapelessSpecBuilder
.shapeless(RecipeCategory.REDSTONE, DataComponentUtil.createStack(turtleItem, ModRegistry.DataComponents.OVERLAY.get(), overlayId))
.shapeless(RecipeCategory.REDSTONE, DataComponentUtil.createStack(turtleItem, ModRegistry.DataComponents.OVERLAY.get(), holder))
.group(name.withSuffix("_overlay").toString())
.unlockedBy("has_turtle", inventoryChange(turtleItem));
build.accept(builder);
@@ -226,7 +228,7 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
.build(s -> new TransformShapelessRecipe(s, List.of(
CopyComponents.builder(turtleItem).exclude(ModRegistry.DataComponents.OVERLAY.get()).build()
)))
.save(add, name.withSuffix("_overlays/" + overlay));
.save(add, name.withSuffix("_overlays/" + overlay.location().getPath()));
}
}

View File

@@ -0,0 +1,38 @@
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.data;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.shared.turtle.TurtleOverlay;
import net.minecraft.data.worldgen.BootstrapContext;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
/**
* Built-in turtle overlays.
*/
final class TurtleOverlays {
public static final ResourceKey<TurtleOverlay> RAINBOW_FLAG = create("rainbow_flag");
public static final ResourceKey<TurtleOverlay> TRANS_FLAG = create("trans_flag");
private static ResourceKey<TurtleOverlay> create(String name) {
return ResourceKey.create(TurtleOverlay.REGISTRY, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, name));
}
private TurtleOverlays() {
}
public static void register(BootstrapContext<TurtleOverlay> registry) {
registry.register(RAINBOW_FLAG, new TurtleOverlay(
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_rainbow_overlay"),
true
));
registry.register(TRANS_FLAG, new TurtleOverlay(
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_trans_overlay"),
true
));
}
}

View File

@@ -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 {

View File

@@ -31,7 +31,7 @@ abstract class ItemStackComponentizationFixMixin extends DataFix {
}
@Inject(method = "fixItemStack", at = @At("TAIL"))
@SuppressWarnings("UnusedMethod")
@SuppressWarnings("unused")
private static void fixItemStack(ItemStackComponentizationFix.ItemStackData data, Dynamic<?> ops, CallbackInfo ci) {
ComponentizationFixers.fixItemComponents(data, ops);
}

View File

@@ -8,6 +8,7 @@ import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.serialization.Codec;
import com.mojang.serialization.MapCodec;
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;
@@ -26,6 +27,7 @@ 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.ComputerBlock;
import dan200.computercraft.shared.computer.blocks.ComputerBlockEntity;
@@ -64,6 +66,7 @@ import dan200.computercraft.shared.peripheral.speaker.SpeakerBlockEntity;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.platform.RegistrationHelper;
import dan200.computercraft.shared.platform.RegistryEntry;
import dan200.computercraft.shared.pocket.apis.PocketAPI;
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
import dan200.computercraft.shared.pocket.peripherals.PocketModem;
import dan200.computercraft.shared.pocket.peripherals.PocketSpeaker;
@@ -72,8 +75,11 @@ import dan200.computercraft.shared.recipe.*;
import dan200.computercraft.shared.recipe.function.CopyComponents;
import dan200.computercraft.shared.recipe.function.RecipeFunction;
import dan200.computercraft.shared.turtle.FurnaceRefuelHandler;
import dan200.computercraft.shared.turtle.TurtleOverlay;
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.TurtleUpgradeRecipe;
@@ -81,6 +87,7 @@ import dan200.computercraft.shared.turtle.upgrades.TurtleCraftingTable;
import dan200.computercraft.shared.turtle.upgrades.TurtleModem;
import dan200.computercraft.shared.turtle.upgrades.TurtleSpeaker;
import dan200.computercraft.shared.turtle.upgrades.TurtleTool;
import dan200.computercraft.shared.util.ComponentMap;
import dan200.computercraft.shared.util.DataComponentUtil;
import dan200.computercraft.shared.util.NonNegativeId;
import net.minecraft.commands.CommandSourceStack;
@@ -95,7 +102,6 @@ import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.flag.FeatureFlags;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.BlockItem;
@@ -114,6 +120,7 @@ import net.minecraft.world.level.block.state.BlockBehaviour;
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;
import java.util.function.UnaryOperator;
@@ -320,8 +327,8 @@ public final class ModRegistry {
/**
* The overlay on a turtle.
*/
public static final RegistryEntry<DataComponentType<ResourceLocation>> OVERLAY = register("overlay", b -> b
.persistent(ResourceLocation.CODEC).networkSynchronized(ResourceLocation.STREAM_CODEC)
public static final RegistryEntry<DataComponentType<Holder<TurtleOverlay>>> OVERLAY = register("overlay", b -> b
.persistent(TurtleOverlay.CODEC).networkSynchronized(TurtleOverlay.STREAM_CODEC)
);
/**
@@ -579,6 +586,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);
}

View File

@@ -6,11 +6,11 @@ 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.core.ServerComputer;
import dan200.computercraft.shared.util.NBTUtil;
import net.minecraft.commands.CommandSource;
import net.minecraft.commands.CommandSourceStack;
@@ -35,11 +35,13 @@ import java.util.*;
public class CommandAPI implements ILuaAPI {
private static final Logger LOG = LoggerFactory.getLogger(CommandAPI.class);
private final ServerComputer computer;
private final IComputerSystem computer;
private final AdminComputer admin;
private final OutputReceiver receiver = new OutputReceiver();
public CommandAPI(ServerComputer computer) {
public CommandAPI(IComputerSystem computer, AdminComputer admin) {
this.computer = computer;
this.admin = admin;
}
@Override
@@ -295,7 +297,7 @@ public class CommandAPI implements ILuaAPI {
return new CommandSourceStack(receiver,
Vec3.atCenterOf(computer.getPosition()), Vec2.ZERO,
computer.getLevel(), 2,
computer.getLevel(), admin.permissionLevel(),
name, Component.literal(name),
computer.getLevel().getServer(), null
);

View File

@@ -39,7 +39,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";
@@ -326,17 +326,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;
@@ -344,7 +341,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;
@@ -354,7 +350,6 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
BlockEntityHelpers.updateBlock(this);
}
@Override
public ComputerFamily getFamily() {
return family;
}

View File

@@ -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()
);
}

View File

@@ -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();
}

View File

@@ -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);
}
}

View File

@@ -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) {

View File

@@ -5,15 +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.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.shared.computer.apis.CommandAPI;
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;
@@ -22,6 +23,7 @@ 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;
@@ -48,7 +50,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,10 +61,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);
if (family == ComputerFamily.COMMAND) addAPI(new CommandAPI(this));
// 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() {
@@ -211,10 +231,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);
}

View File

@@ -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"));

View File

@@ -69,6 +69,13 @@ class AddressRuleConfig {
);
}
public static UnmodifiableConfig newRule() {
return makeRule(config -> {
config.add("host", "example.com");
config.add("action", Action.DENY.name().toLowerCase(Locale.ROOT));
});
}
private static UnmodifiableConfig makeRule(Consumer<CommentedConfig> setup) {
var config = InMemoryCommentedFormat.defaultInstance().createConfig(LinkedHashMap::new);
setup.accept(config);

View File

@@ -126,7 +126,7 @@ public interface ConfigFile {
public abstract ConfigFile.Value<Integer> defineInRange(String path, int defaultValue, int min, int max);
public abstract <T> ConfigFile.Value<List<? extends T>> defineList(String path, List<? extends T> defaultValue, Predicate<Object> elementValidator);
public abstract <T> ConfigFile.Value<List<? extends T>> defineList(String path, List<? extends T> defaultValue, Supplier<T> newValue, Predicate<Object> elementValidator);
public abstract <V extends Enum<V>> ConfigFile.Value<V> defineEnum(String path, V defaultValue);

View File

@@ -145,7 +145,7 @@ public final class ConfigSpec {
or a single method (computercraft:inventory#pushItems).
""")
.worldRestart()
.defineList("disabled_generic_methods", List.of(), x -> x instanceof String);
.defineList("disabled_generic_methods", List.of(), () -> "", x -> x instanceof String);
}
{
@@ -214,7 +214,7 @@ public final class ConfigSpec {
- "max_websocket_message" (optional): The maximum size (in bytes) that a computer can send or
receive in one websocket packet.
- "use_proxy" (optional): Enable use of the HTTP/SOCKS proxy if it is configured.""")
.defineList("rules", AddressRuleConfig.defaultRules(), x -> x instanceof UnmodifiableConfig);
.defineList("rules", AddressRuleConfig.defaultRules(), AddressRuleConfig::newRule, x -> x instanceof UnmodifiableConfig);
httpMaxRequests = builder
.comment("""

View File

@@ -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

View File

@@ -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;
}
});
}
}

View File

@@ -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, ResourceLocation.fromNamespaceAndPath(mod, name));

View File

@@ -43,7 +43,7 @@ public record PocketComputerDataMessage(
this(
computer.getInstanceUUID(),
computer.getState(),
computer.getLight(),
computer.getBrain().getLight(),
sendTerminal ? Optional.of(computer.getTerminalState()) : Optional.empty()
);
}

View File

@@ -276,16 +276,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.

View File

@@ -6,10 +6,10 @@ package dan200.computercraft.shared.pocket.apis;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.pocket.IPocketAccess;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.impl.PocketUpgrades;
import dan200.computercraft.shared.pocket.core.PocketServerComputer;
import net.minecraft.core.NonNullList;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
@@ -34,10 +34,10 @@ import java.util.Objects;
* @cc.module pocket
*/
public class PocketAPI implements ILuaAPI {
private final PocketServerComputer computer;
private final IPocketAccess pocket;
public PocketAPI(PocketServerComputer computer) {
this.computer = computer;
public PocketAPI(IPocketAccess pocket) {
this.pocket = pocket;
}
@Override
@@ -56,10 +56,10 @@ public class PocketAPI implements ILuaAPI {
*/
@LuaFunction(mainThread = true)
public final Object[] equipBack() {
var entity = computer.getEntity();
var entity = pocket.getEntity();
if (!(entity instanceof Player player)) return new Object[]{ false, "Cannot find player" };
var inventory = player.getInventory();
var previousUpgrade = computer.getUpgrade();
var previousUpgrade = pocket.getUpgrade();
// Attempt to find the upgrade, starting in the main segment, and then looking in the opposite
// one. We start from the position the item is currently in and loop round to the start.
@@ -73,7 +73,7 @@ public class PocketAPI implements ILuaAPI {
if (previousUpgrade != null) storeItem(player, previousUpgrade.getUpgradeItem());
// Set the new upgrade
computer.setUpgrade(newUpgrade);
pocket.setUpgrade(newUpgrade);
return new Object[]{ true };
}
@@ -87,13 +87,13 @@ public class PocketAPI implements ILuaAPI {
*/
@LuaFunction(mainThread = true)
public final Object[] unequipBack() {
var entity = computer.getEntity();
var entity = pocket.getEntity();
if (!(entity instanceof Player player)) return new Object[]{ false, "Cannot find player" };
var previousUpgrade = computer.getUpgrade();
var previousUpgrade = pocket.getUpgrade();
if (previousUpgrade == null) return new Object[]{ false, "Nothing to unequip" };
computer.setUpgrade(null);
pocket.setUpgrade(null);
storeItem(player, previousUpgrade.getUpgradeItem());
@@ -111,7 +111,7 @@ public class PocketAPI implements ILuaAPI {
for (var i = 0; i < inv.size(); i++) {
var invStack = inv.get((i + start) % inv.size());
if (!invStack.isEmpty()) {
var newUpgrade = PocketUpgrades.instance().get(computer.getLevel().registryAccess(), invStack);
var newUpgrade = PocketUpgrades.instance().get(pocket.getLevel().registryAccess(), invStack);
if (newUpgrade != null && !Objects.equals(newUpgrade, previous)) {
// Consume an item from this stack and exit the loop

View File

@@ -0,0 +1,176 @@
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.pocket.core;
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.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.core.component.DataComponentPatch;
import net.minecraft.core.component.DataComponents;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.component.DyedItemColor;
import net.minecraft.world.phys.Vec3;
import javax.annotation.Nullable;
/**
* 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 = 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;
stack.set(DataComponents.DYED_COLOR, colour == -1 ? null : new DyedItemColor(colour, false));
PocketComputerItem.setUpgrade(stack, 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 DataComponentPatch getUpgradeData() {
var upgrade = this.upgrade;
return upgrade == null ? DataComponentPatch.EMPTY : upgrade.data();
}
@Override
public void setUpgradeData(DataComponentPatch data) {
var upgrade = this.upgrade;
if (upgrade == null) return;
this.upgrade = UpgradeData.of(upgrade.holder(), data);
}
@Override
public void invalidatePeripheral() {
var peripheral = upgrade == null ? null : upgrade.upgrade().createPeripheral(this);
computer.setPeripheral(ComputerSide.BACK, peripheral);
}
@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();
}
}

View File

@@ -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());
}
}
}

View File

@@ -4,11 +4,7 @@
package dan200.computercraft.shared.pocket.core;
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.ModRegistry;
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;
@@ -17,176 +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.core.component.DataComponentPatch;
import net.minecraft.core.component.DataComponents;
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.item.component.DyedItemColor;
import net.minecraft.world.level.ChunkPos;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
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;
}
}
@Override
public int getColour() {
return DyedItemColor.getOrDefault(stack, -1);
}
@Override
public void setColour(int colour) {
stack.set(DataComponents.DYED_COLOR, colour == -1 ? null : new DyedItemColor(colour, false));
setItemChanged();
}
@Override
public int getLight() {
return lightColour;
}
@Override
public void setLight(int colour) {
if (colour < 0 || colour > 0xFFFFFF) colour = -1;
lightColour = colour;
}
@Override
public DataComponentPatch getUpgradeData() {
var upgrade = PocketComputerItem.getUpgradeWithData(stack);
return upgrade == null ? DataComponentPatch.EMPTY : upgrade.data();
}
@Override
public void setUpgradeData(DataComponentPatch data) {
var upgrade = PocketComputerItem.getUpgradeWithData(stack);
if (upgrade == null) return;
PocketComputerItem.setUpgrade(stack, new UpgradeData<>(upgrade.holder(), data));
setItemChanged();
}
private void setItemChanged() {
if (entity instanceof Player player) player.getInventory().setChanged();
}
@Override
public void invalidatePeripheral() {
var peripheral = upgrade == null ? null : upgrade.createPeripheral(this);
setPeripheral(ComputerSide.BACK, peripheral);
}
public @Nullable UpgradeData<IPocketUpgrade> getUpgrade() {
return PocketComputerItem.getUpgradeWithData(stack);
}
/**
* 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) {
stack.set(ModRegistry.DataComponents.POCKET_UPGRADE.get(), upgrade);
setItemChanged();
this.upgrade = upgrade == null ? null : upgrade.upgrade();
invalidatePeripheral();
}
}
public synchronized void updateValues(@Nullable Entity entity, ItemStack stack, @Nullable IPocketUpgrade upgrade) {
if (entity != null) setPosition((ServerLevel) entity.level(), 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();
}
public PocketBrain getBrain() {
return brain;
}
@Override
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());
}
}

View File

@@ -14,22 +14,26 @@ import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.impl.PocketUpgrades;
import dan200.computercraft.shared.ModRegistry;
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.ServerComputerReference;
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.DataComponentUtil;
import dan200.computercraft.shared.util.IDAssigner;
import dan200.computercraft.shared.util.InventoryUtil;
import dan200.computercraft.shared.util.NonNegativeId;
import net.minecraft.ChatFormatting;
import net.minecraft.core.HolderLookup;
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;
@@ -53,12 +57,33 @@ public class PocketComputerItem extends Item implements IMedia {
this.family = family;
}
private boolean tick(ItemStack stack, 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.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 label
var label = computer.getLabel();
@@ -73,21 +98,20 @@ public class PocketComputerItem extends Item implements IMedia {
stack.set(ModRegistry.DataComponents.ON.get(), on);
}
// Update pocket upgrade
if (upgrade != null) upgrade.update(computer, computer.getPeripheral(ComputerSide.BACK));
return changed;
}
@Override
public void inventoryTick(ItemStack stack, Level world, Entity entity, int slotNum, boolean selected) {
if (world.isClientSide) return;
Container inventory = entity instanceof Player player ? player.getInventory() : null;
var computer = createServerComputer((ServerLevel) world, entity, inventory, stack);
computer.keepAlive();
// This (in vanilla at least) is only called for players. Don't bother to handle other entities.
if (world.isClientSide || !(entity instanceof ServerPlayer player)) return;
var changed = tick(stack, entity, computer);
if (changed && inventory != null) inventory.setChanged();
// If we're in the inventory, create a computer and keep it alive.
var holder = new PocketHolder.PlayerHolder(player, slotNum);
var brain = getOrCreateBrain((ServerLevel) world, holder, stack);
brain.computer().keepAlive();
tick(stack, holder, brain);
}
@ForgeOverride
@@ -95,8 +119,11 @@ public class PocketComputerItem extends Item implements IMedia {
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, computer)) entity.setItem(stack.copy());
if (computer != null) tick(stack, new PocketHolder.ItemEntityHolder(entity), computer.getBrain());
return false;
}
@@ -104,14 +131,18 @@ public class PocketComputerItem extends Item implements IMedia {
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) {
@@ -153,34 +184,42 @@ public class PocketComputerItem extends Item implements IMedia {
}
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) ServerComputerReference.get(stack, registry);
if (computer == null) {
var computerID = NonNegativeId.getOrCreate(level.getServer(), stack, ModRegistry.DataComponents.COMPUTER_ID.get(), IDAssigner.COMPUTER);
computer = new PocketServerComputer(level, entity.blockPosition(), computerID, getLabel(stack), getFamily());
var instanceId = computer.register();
stack.set(ModRegistry.DataComponents.COMPUTER.get(), new ServerComputerReference(registry.getSessionID(), instanceId));
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();
}
return computer;
var computerID = NonNegativeId.getOrCreate(level.getServer(), stack, ModRegistry.DataComponents.COMPUTER_ID.get(), IDAssigner.COMPUTER);
var brain = new PocketBrain(holder, computerID, getLabel(stack), getFamily(), getUpgradeWithData(stack));
var computer = brain.computer();
stack.set(ModRegistry.DataComponents.COMPUTER.get(), new ServerComputerReference(registry.getSessionID(), 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) ServerComputerReference.get(stack, registry);
}
@Nullable
public static PocketServerComputer getServerComputer(MinecraftServer server, ItemStack stack) {
return (PocketServerComputer) ServerComputerReference.get(stack, ServerContext.get(server).registry());
return getServerComputer(ServerContext.get(server).registry(), stack);
}
public ComputerFamily getFamily() {

View File

@@ -41,8 +41,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);
}

View File

@@ -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.level();
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

View File

@@ -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);

View File

@@ -15,17 +15,17 @@ import java.util.function.Function;
* A custom version of {@link ShapedRecipe}, which can be converted to and from a {@link ShapedRecipeSpec}.
*/
public abstract class CustomShapedRecipe extends ShapedRecipe {
private final ShapedRecipePattern pattern;
private final ShapedRecipePattern shapedPattern;
private final ItemStack result;
public CustomShapedRecipe(ShapedRecipeSpec recipe) {
super(recipe.properties().group(), recipe.properties().category(), recipe.pattern(), recipe.result(), recipe.properties().showNotification());
this.pattern = recipe.pattern();
this.shapedPattern = recipe.pattern();
this.result = recipe.result();
}
public final ShapedRecipeSpec toSpec() {
return new ShapedRecipeSpec(RecipeProperties.of(this), pattern, result);
return new ShapedRecipeSpec(RecipeProperties.of(this), shapedPattern, result);
}
@Override

View File

@@ -0,0 +1,78 @@
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.turtle;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.util.Holiday;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.RegistryFileCodec;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import javax.annotation.Nullable;
/**
* A cosmetic overlay on a turtle.
*
* @param model The path to the overlay's model.
* @param showElfOverlay Whether this overlay is compatible with the {@linkplain #ELF_MODEL Christmas elf model}.
* @see ModRegistry.DataComponents#OVERLAY
*/
public record TurtleOverlay(ResourceLocation model, boolean showElfOverlay) {
/**
* The registry turtle overlays are stored in.
*/
public static final ResourceKey<Registry<TurtleOverlay>> REGISTRY = ResourceKey.createRegistryKey(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "turtle_overlay"));
/**
* The codec used to read/write turtle overlay definitions from datapacks.
*/
public static final Codec<TurtleOverlay> DIRECT_CODEC = RecordCodecBuilder.create(instance -> instance.group(
ResourceLocation.CODEC.fieldOf("model").forGetter(TurtleOverlay::model),
Codec.BOOL.optionalFieldOf("show_elf_overlay", false).forGetter(TurtleOverlay::showElfOverlay)
).apply(instance, TurtleOverlay::new));
/**
* The codec used for {@link TurtleOverlay} instances.
*
* @see ModRegistry.DataComponents#OVERLAY
*/
public static final Codec<Holder<TurtleOverlay>> CODEC = RegistryFileCodec.create(REGISTRY, DIRECT_CODEC);
/**
* The stream codec used for {@link TurtleOverlay} instances.
*/
public static final StreamCodec<RegistryFriendlyByteBuf, Holder<TurtleOverlay>> STREAM_CODEC = ByteBufCodecs.holder(REGISTRY, StreamCodec.composite(
ResourceLocation.STREAM_CODEC, TurtleOverlay::model,
ByteBufCodecs.BOOL, TurtleOverlay::showElfOverlay,
TurtleOverlay::new
));
/**
* An additional overlay that is rendered on all turtles at {@linkplain Holiday#CHRISTMAS Christmas}.
*
* @see #showElfOverlay()
* @see #showElfOverlay(TurtleOverlay, boolean)
*/
public static final ResourceLocation ELF_MODEL = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_elf_overlay");
/**
* Determine whether we should show the {@linkplain #ELF_MODEL elf overlay}.
*
* @param overlay The current {@link TurtleOverlay}.
* @param christmas Whether it is Christmas.
* @return Whether we should show the elf overlay.
*/
public static boolean showElfOverlay(@Nullable TurtleOverlay overlay, boolean christmas) {
return christmas && (overlay == null || overlay.showElfOverlay());
}
}

View File

@@ -11,7 +11,6 @@ import dan200.computercraft.api.turtle.TurtleCommandResult;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.core.metrics.Metrics;
import dan200.computercraft.core.metrics.MetricsObserver;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.peripheral.generic.methods.AbstractInventoryMethods;
import dan200.computercraft.shared.turtle.core.*;
@@ -68,8 +67,8 @@ public class TurtleAPI implements ILuaAPI {
private final MetricsObserver metrics;
private final TurtleAccessInternal turtle;
public TurtleAPI(ServerComputer computer, TurtleAccessInternal turtle) {
this.metrics = computer.getMetrics();
public TurtleAPI(MetricsObserver metrics, TurtleAccessInternal turtle) {
this.metrics = metrics;
this.turtle = turtle;
}

View File

@@ -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;
@@ -20,9 +21,10 @@ import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.config.Config;
import dan200.computercraft.shared.container.BasicContainer;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.turtle.apis.TurtleAPI;
import dan200.computercraft.shared.turtle.TurtleOverlay;
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.HolderLookup;
@@ -30,7 +32,6 @@ import net.minecraft.core.NonNullList;
import net.minecraft.core.component.DataComponentMap;
import net.minecraft.core.component.DataComponents;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.Container;
import net.minecraft.world.ContainerHelper;
@@ -81,10 +82,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, brain));
brain.setupComputer(computer);
return computer;
}
@@ -222,8 +222,9 @@ public class TurtleBlockEntity extends AbstractComputerBlockEntity implements Ba
return brain.getColour();
}
public @Nullable ResourceLocation getOverlay() {
return brain.getOverlay();
public @Nullable TurtleOverlay getOverlay() {
var overlay = brain.getOverlay();
return overlay == null ? null : overlay.value();
}
public ITurtleAccess getAccess() {

View File

@@ -20,6 +20,7 @@ import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.config.Config;
import dan200.computercraft.shared.container.InventoryDelegate;
import dan200.computercraft.shared.turtle.TurtleOverlay;
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
import dan200.computercraft.shared.util.BlockEntityHelpers;
import dan200.computercraft.shared.util.Holiday;
@@ -32,7 +33,6 @@ import net.minecraft.core.component.DataComponentPatch;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.tags.FluidTags;
import net.minecraft.world.Container;
@@ -74,7 +74,7 @@ public class TurtleBrain implements TurtleAccessInternal {
private int selectedSlot = 0;
private int fuelLevel = 0;
private int colourHex = -1;
private @Nullable ResourceLocation overlay = null;
private @Nullable Holder<TurtleOverlay> overlay = null;
private TurtleAnimation animation = TurtleAnimation.NONE;
private int animationProgress = 0;
@@ -134,7 +134,7 @@ public class TurtleBrain implements TurtleAccessInternal {
// Read fields
colourHex = nbt.contains(NBT_COLOUR) ? nbt.getInt(NBT_COLOUR) : -1;
fuelLevel = nbt.contains(NBT_FUEL) ? nbt.getInt(NBT_FUEL) : 0;
overlay = nbt.contains(NBT_OVERLAY) ? ResourceLocation.parse(nbt.getString(NBT_OVERLAY)) : null;
overlay = nbt.contains(NBT_OVERLAY) ? NBTUtil.decodeFrom(TurtleOverlay.CODEC, registries, nbt, NBT_OVERLAY) : null;
// Read upgrades
setUpgradeDirect(TurtleSide.LEFT, NBTUtil.decodeFrom(TurtleUpgrades.instance().upgradeDataCodec(), registries, nbt, NBT_LEFT_UPGRADE));
@@ -144,7 +144,7 @@ public class TurtleBrain implements TurtleAccessInternal {
private void writeCommon(CompoundTag nbt, HolderLookup.Provider registries) {
nbt.putInt(NBT_FUEL, fuelLevel);
if (colourHex != -1) nbt.putInt(NBT_COLOUR, colourHex);
if (overlay != null) nbt.putString(NBT_OVERLAY, overlay.toString());
if (overlay != null) NBTUtil.encodeTo(TurtleOverlay.CODEC, registries, nbt, NBT_OVERLAY, overlay);
// Write upgrades
NBTUtil.encodeTo(TurtleUpgrades.instance().upgradeDataCodec(), registries, nbt, NBT_LEFT_UPGRADE, getUpgradeWithData(TurtleSide.LEFT));
@@ -418,11 +418,11 @@ public class TurtleBrain implements TurtleAccessInternal {
BlockEntityHelpers.updateBlock(owner);
}
public @Nullable ResourceLocation getOverlay() {
public @Nullable Holder<TurtleOverlay> getOverlay() {
return overlay;
}
public void setOverlay(@Nullable ResourceLocation overlay) {
public void setOverlay(@Nullable Holder<TurtleOverlay> overlay) {
if (!Objects.equals(this.overlay, overlay)) {
this.overlay = overlay;
BlockEntityHelpers.updateBlock(owner);

View File

@@ -12,11 +12,11 @@ import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.impl.TurtleUpgrades;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.computer.items.AbstractComputerItem;
import dan200.computercraft.shared.turtle.TurtleOverlay;
import dan200.computercraft.shared.turtle.blocks.TurtleBlock;
import net.minecraft.core.cauldron.CauldronInteraction;
import net.minecraft.core.component.DataComponents;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.ItemInteractionResult;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.LayeredCauldronBlock;
@@ -74,8 +74,9 @@ public class TurtleItem extends AbstractComputerItem {
return stack.get(side == TurtleSide.LEFT ? ModRegistry.DataComponents.LEFT_TURTLE_UPGRADE.get() : ModRegistry.DataComponents.RIGHT_TURTLE_UPGRADE.get());
}
public static @Nullable ResourceLocation getOverlay(ItemStack stack) {
return stack.get(ModRegistry.DataComponents.OVERLAY.get());
public static @Nullable TurtleOverlay getOverlay(ItemStack stack) {
var overlay = stack.get(ModRegistry.DataComponents.OVERLAY.get());
return overlay == null ? null : overlay.value();
}
public static int getFuelLevel(ItemStack stack) {

View File

@@ -80,7 +80,7 @@ public final class TurtleInventoryCrafting {
var recipe = candidate.recipe();
var input = candidate.input();
var xStart = candidate.xStart();
var yStart = candidate.xStart();
var yStart = candidate.yStart();
var player = TurtlePlayer.get(turtle).player();

View File

@@ -0,0 +1,66 @@
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.util;
import dan200.computercraft.api.component.ComputerComponent;
import dan200.computercraft.core.metrics.MetricsObserver;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
/**
* An immutable map of components.
*/
public final class ComponentMap {
public static final ComputerComponent<MetricsObserver> METRICS = ComputerComponent.create("computercraft", "metrics");
private static final ComponentMap EMPTY = new ComponentMap(Map.of());
private final Map<ComputerComponent<?>, Object> components;
private ComponentMap(Map<ComputerComponent<?>, Object> components) {
this.components = components;
}
@SuppressWarnings("unchecked")
public <T> @Nullable T get(ComputerComponent<T> component) {
return (T) components.get(component);
}
public static ComponentMap empty() {
return EMPTY;
}
public static Builder builder() {
return new Builder();
}
public static final class Builder {
private final Map<ComputerComponent<?>, Object> components = new HashMap<>();
private Builder() {
}
public <T> Builder add(ComputerComponent<T> component, T value) {
addImpl(component, value);
return this;
}
public Builder add(ComponentMap components) {
for (var component : components.components.entrySet()) addImpl(component.getKey(), component.getValue());
return this;
}
private void addImpl(ComputerComponent<?> component, Object value) {
if (components.containsKey(component)) throw new IllegalArgumentException(component + " is already set");
components.put(component, value);
}
public ComponentMap build() {
return new ComponentMap(Map.copyOf(components));
}
}
}

View File

@@ -82,7 +82,8 @@ public class ComponentizationFixers {
if (item.is(TURTLES)) {
item.moveTagToComponent("Fuel", "computercraft:fuel");
item.moveTagToComponent("Overlay", "computercraft:overlay");
item.removeTag("Overlay").result().ifPresent(x -> item.setComponent("computercraft:overlay", fixOverlay(x)));
moveUpgradeToComponent(item, ops, "LeftUpgrade", "LeftUpgradeNbt", "computercraft:left_turtle_upgrade");
moveUpgradeToComponent(item, ops, "RightUpgrade", "RightUpgradeNbt", "computercraft:right_turtle_upgrade");
@@ -152,6 +153,7 @@ public class ComponentizationFixers {
return typed -> typed.updateTyped(input, output, typed2 -> typed2.update(DSL.remainderFinder(), x -> {
x = moveUpgradeData(x, "LeftUpgrade", "LeftUpgradeNbt");
x = moveUpgradeData(x, "RightUpgrade", "RightUpgradeNbt");
x = x.update("Overlay", ComponentizationFixers::fixOverlay);
return x;
}));
}
@@ -164,6 +166,20 @@ public class ComponentizationFixers {
return ops.set(key, createUpgradeData(ops, upgradeId, ops.get(dataKey))).remove(dataKey);
}
private static Dynamic<?> fixOverlay(Dynamic<?> overlay) {
// Rewrite known overlays to their new ids.
var overlayId = overlay.asString(null);
if (overlayId == null) return overlay;
return switch (overlayId) {
// Map known overlays to their new id.
case "computercraft:block/turtle_trans_overlay" -> overlay.createString("computercraft:trans_flag");
case "computercraft:block/turtle_rainbow_overlay" -> overlay.createString("computercraft:rainbow_flag");
// And unknown overlays to a direct entry.
default -> overlay.emptyMap().set("model", overlay);
};
}
/**
* Create a new upgrade data.
*

View File

@@ -8,6 +8,9 @@ import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.Container;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.EntityHitResult;
import net.minecraft.world.phys.Vec3;
@@ -18,6 +21,20 @@ public final class InventoryUtil {
private InventoryUtil() {
}
/**
* Get the inventory slot for a given hand.
*
* @param player The player to get the slot from.
* @param hand The hand to get.
* @return The current slot.
*/
public static int getHandSlot(Player player, InteractionHand hand) {
return switch (hand) {
case MAIN_HAND -> player.getInventory().selected;
case OFF_HAND -> Inventory.SLOT_OFFHAND;
};
}
public static @Nullable Container getEntityContainer(ServerLevel level, BlockPos pos, Direction side) {
var vecStart = new Vec3(
pos.getX() + 0.5 + 0.6 * side.getStepX(),

View File

@@ -1,38 +1,37 @@
{
"parent": "block/block",
"textures": {
"particle": "computercraft:block/turtle_rainbow_overlay",
"texture": "computercraft:block/turtle_rainbow_overlay"
},
"elements": [
{
"name": "Flag",
"from": [1.5, 13, 10.5],
"to": [2, 16.5, 15.5],
"rotation": {"angle": 22.5, "axis": "x", "origin": [2, 11, 10.75]},
"faces": {
"north": {"uv": [0, 0, 1, 6], "texture": "#texture"},
"east": {"uv": [0, 0, 7, 6], "texture": "#texture"},
"south": {"uv": [0, 0, 1, 6], "texture": "#texture"},
"west": {"uv": [0, 0, 7, 6], "texture": "#texture"},
"up": {"uv": [15, 0, 16, 6], "texture": "#texture"},
"down": {"uv": [8, 0, 9, 6], "texture": "#texture"}
}
},
{
"name": "Stick",
"from": [1.5, 10.5, 10.5],
"to": [2, 13, 11],
"rotation": {"angle": 22.5, "axis": "x", "origin": [2, 11, 10.75]},
"faces": {
"north": {"uv": [12, 0, 13, 6], "texture": "#texture"},
"east": {"uv": [13, 0, 14, 6], "texture": "#texture"},
"south": {"uv": [12, 0, 13, 6], "texture": "#texture"},
"west": {"uv": [13, 0, 14, 6], "texture": "#texture"},
"up": {"uv": [12, 6, 13, 7], "texture": "#texture"},
"down": {"uv": [13, 6, 14, 7], "texture": "#texture"}
}
}
],
"display": {}
"parent": "block/block",
"textures": {
"particle": "computercraft:block/turtle_rainbow_overlay",
"texture": "computercraft:block/turtle_rainbow_overlay"
},
"elements": [
{
"name": "Flag",
"from": [1.5, 13.5, 10.5],
"to": [2, 16.5, 15.5],
"rotation": {"angle": 22.5, "axis": "x", "origin": [2, 11, 10.75]},
"faces": {
"north": {"uv": [0, 0, 1, 6], "texture": "#texture"},
"east": {"uv": [0, 0, 7, 6], "texture": "#texture"},
"south": {"uv": [0, 0, 1, 6], "texture": "#texture"},
"west": {"uv": [0, 0, 7, 6], "texture": "#texture"},
"up": {"uv": [15, 0, 16, 6], "texture": "#texture"},
"down": {"uv": [8, 0, 9, 6], "texture": "#texture"}
}
},
{
"name": "Stick",
"from": [1.5, 10.5, 10.5],
"to": [2, 13.5, 11],
"rotation": {"angle": 22.5, "axis": "x", "origin": [2, 11, 10.75]},
"faces": {
"north": {"uv": [12, 0, 13, 6], "texture": "#texture"},
"east": {"uv": [13, 0, 14, 6], "texture": "#texture"},
"south": {"uv": [12, 0, 13, 6], "texture": "#texture"},
"west": {"uv": [13, 0, 14, 6], "texture": "#texture"},
"up": {"uv": [12, 6, 13, 7], "texture": "#texture"},
"down": {"uv": [13, 6, 14, 7], "texture": "#texture"}
}
}
]
}

View File

@@ -1,38 +1,37 @@
{
"parent": "block/block",
"textures": {
"particle": "computercraft:block/turtle_trans_overlay",
"texture": "computercraft:block/turtle_trans_overlay"
},
"elements": [
{
"name": "Flag",
"from": [1.5, 13.5, 10.5],
"to": [2, 16.5, 15.5],
"rotation": {"angle": 22.5, "axis": "x", "origin": [2, 11, 10.75]},
"faces": {
"north": {"uv": [0, 0, 1, 5], "texture": "#texture"},
"east": {"uv": [0, 0, 7, 5], "texture": "#texture"},
"south": {"uv": [0, 0, 1, 5], "texture": "#texture"},
"west": {"uv": [0, 0, 7, 5], "texture": "#texture"},
"up": {"uv": [15, 0, 16, 5], "texture": "#texture"},
"down": {"uv": [15, 0, 16, 5], "texture": "#texture"}
}
},
{
"name": "Stick",
"from": [1.5, 10.5, 10.5],
"to": [2, 13.5, 11],
"rotation": {"angle": 22.5, "axis": "x", "origin": [2, 11, 10.75]},
"faces": {
"north": {"uv": [12, 0, 13, 6], "texture": "#texture"},
"east": {"uv": [13, 0, 14, 6], "texture": "#texture"},
"south": {"uv": [12, 0, 13, 6], "texture": "#texture"},
"west": {"uv": [13, 0, 14, 6], "texture": "#texture"},
"up": {"uv": [12, 6, 13, 7], "texture": "#texture"},
"down": {"uv": [13, 6, 14, 7], "texture": "#texture"}
}
}
],
"display": {}
"parent": "block/block",
"textures": {
"particle": "computercraft:block/turtle_trans_overlay",
"texture": "computercraft:block/turtle_trans_overlay"
},
"elements": [
{
"name": "Flag",
"from": [1.5, 13.5, 10.5],
"to": [2, 16, 15.5],
"rotation": {"angle": 22.5, "axis": "x", "origin": [2, 11, 10.75]},
"faces": {
"north": {"uv": [0, 0, 1, 5], "texture": "#texture"},
"east": {"uv": [0, 0, 7, 5], "texture": "#texture"},
"south": {"uv": [0, 0, 1, 5], "texture": "#texture"},
"west": {"uv": [0, 0, 7, 5], "texture": "#texture"},
"up": {"uv": [15, 0, 16, 5], "texture": "#texture"},
"down": {"uv": [15, 0, 16, 5], "texture": "#texture"}
}
},
{
"name": "Stick",
"from": [1.5, 10.5, 10.5],
"to": [2, 13.5, 11],
"rotation": {"angle": 22.5, "axis": "x", "origin": [2, 11, 10.75]},
"faces": {
"north": {"uv": [12, 0, 13, 6], "texture": "#texture"},
"east": {"uv": [13, 0, 14, 6], "texture": "#texture"},
"south": {"uv": [12, 0, 13, 6], "texture": "#texture"},
"west": {"uv": [13, 0, 14, 6], "texture": "#texture"},
"up": {"uv": [12, 6, 13, 7], "texture": "#texture"},
"down": {"uv": [13, 6, 14, 7], "texture": "#texture"}
}
}
]
}

View File

@@ -4,6 +4,7 @@
package dan200.computercraft.gametest
import dan200.computercraft.api.ComputerCraftAPI
import dan200.computercraft.api.detail.BasicItemDetailProvider
import dan200.computercraft.api.detail.VanillaDetailRegistries
import dan200.computercraft.api.lua.ObjectArguments
@@ -21,11 +22,14 @@ import dan200.computercraft.shared.peripheral.modem.wired.CableBlock
import dan200.computercraft.shared.peripheral.modem.wired.CableModemVariant
import dan200.computercraft.shared.peripheral.monitor.MonitorBlock
import dan200.computercraft.shared.peripheral.monitor.MonitorEdgeState
import dan200.computercraft.shared.turtle.TurtleOverlay
import dan200.computercraft.shared.turtle.apis.TurtleAPI
import dan200.computercraft.shared.turtle.items.TurtleItem
import dan200.computercraft.shared.util.WaterloggableHelpers
import dan200.computercraft.test.core.assertArrayEquals
import dan200.computercraft.test.core.computer.LuaTaskContext
import dan200.computercraft.test.core.computer.getApi
import dan200.computercraft.test.shared.ItemStackMatcher.isStack
import net.minecraft.core.BlockPos
import net.minecraft.core.registries.Registries
import net.minecraft.gametest.framework.GameTest
@@ -41,8 +45,7 @@ import net.minecraft.world.level.block.FenceBlock
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.properties.BlockStateProperties
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.array
import org.hamcrest.Matchers.instanceOf
import org.hamcrest.Matchers.*
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotEquals
import java.util.*
@@ -654,6 +657,27 @@ class Turtle_Test {
}
}
/**
* Loads a structure created on an older version of the game, and checks that data fixers have been applied.
*/
@GameTest
fun Data_fixers(helper: GameTestHelper) = helper.sequence {
thenExecute {
val overlay = helper.level.registryAccess().registryOrThrow(TurtleOverlay.REGISTRY)
.get(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "trans_flag"))!!
val upgrade = helper.level.registryAccess().registryOrThrow(ITurtleUpgrade.REGISTRY)
.get(ResourceLocation.withDefaultNamespace("diamond_pickaxe"))!!
val turtleBe = helper.getBlockEntity(BlockPos(1, 2, 1), ModRegistry.BlockEntities.TURTLE_NORMAL.get())
assertEquals(overlay, turtleBe.overlay)
assertEquals(upgrade, turtleBe.getUpgrade(TurtleSide.LEFT))
val turtleItem = turtleBe.getItem(0)
assertEquals(overlay, TurtleItem.getOverlay(turtleItem))
assertEquals(upgrade, TurtleItem.getUpgrade(turtleItem, TurtleSide.LEFT))
}
}
/**
* `turtle.suck` only pulls for the current side.
*/
@@ -669,6 +693,47 @@ class Turtle_Test {
}
}
/**
* `turtle.craft` works as expected
*/
@GameTest
fun Craft(helper: GameTestHelper) = helper.sequence {
thenOnComputer {
callPeripheral("left", "craft", 1).assertArrayEquals(true)
}
thenExecute {
val turtle = helper.getBlockEntity(BlockPos(2, 2, 2), ModRegistry.BlockEntities.TURTLE_NORMAL.get())
assertThat(
"Inventory is as expected.",
turtle.items,
contains(
isStack(Items.DIAMOND, 1), isStack(Items.DIAMOND, 1), isStack(Items.DIAMOND, 1), isStack(Items.DIAMOND_PICKAXE, 1),
isStack(ItemStack.EMPTY), isStack(Items.STICK, 1), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY),
isStack(ItemStack.EMPTY), isStack(Items.STICK, 1), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY),
isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY), isStack(ItemStack.EMPTY),
),
)
}
}
/**
* `turtle.equipLeft` equips a tool.
*/
@GameTest
fun Equip_tool(helper: GameTestHelper) = helper.sequence {
thenOnComputer {
turtle.equipLeft().await().assertArrayEquals(true)
}
thenExecute {
val turtle = helper.getBlockEntity(BlockPos(2, 2, 2), ModRegistry.BlockEntities.TURTLE_NORMAL.get())
assertEquals(
helper.level.registryAccess().registryOrThrow(ITurtleUpgrade.REGISTRY)
.get(ResourceLocation.withDefaultNamespace("diamond_pickaxe")),
turtle.getUpgrade(TurtleSide.LEFT),
)
}
}
/**
* Render turtles as an item.
*/

View File

@@ -179,7 +179,6 @@ fun <T : Comparable<T>> GameTestHelper.assertBlockHas(pos: BlockPos, property: P
fun GameTestHelper.getContainerAt(pos: BlockPos): Container =
when (val container: BlockEntity = getBlockEntity(pos)) {
is Container -> container
null -> failVerbose("Expected a container at $pos, found nothing", pos)
else -> failVerbose("Expected a container at $pos, found ${getName(container.type)}", pos)
}

View File

@@ -0,0 +1,137 @@
{
DataVersion: 3465,
size: [5, 5, 5],
data: [
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
{pos: [0, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], state: "minecraft:air"},
{pos: [0, 1, 3], state: "minecraft:air"},
{pos: [0, 1, 4], state: "minecraft:air"},
{pos: [1, 1, 0], state: "minecraft:air"},
{pos: [1, 1, 1], state: "minecraft:air"},
{pos: [1, 1, 2], state: "minecraft:air"},
{pos: [1, 1, 3], state: "minecraft:air"},
{pos: [1, 1, 4], state: "minecraft:air"},
{pos: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "minecraft:air"},
{pos: [2, 1, 2], state: "computercraft:turtle_normal{facing:north,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 0, Items: [{Count: 2b, Slot: 0b, id: "minecraft:diamond"}, {Count: 2b, Slot: 1b, id: "minecraft:diamond"}, {Count: 2b, Slot: 2b, id: "minecraft:diamond"}, {Count: 2b, Slot: 5b, id: "minecraft:stick"}, {Count: 2b, Slot: 9b, id: "minecraft:stick"}], Label: "turtle_test.craft", LeftUpgrade: "minecraft:crafting_table", LeftUpgradeNbt: {}, On: 1b, Slot: 0, id: "computercraft:turtle_normal"}},
{pos: [2, 1, 3], state: "minecraft:air"},
{pos: [2, 1, 4], state: "minecraft:air"},
{pos: [3, 1, 0], state: "minecraft:air"},
{pos: [3, 1, 1], state: "minecraft:air"},
{pos: [3, 1, 2], state: "minecraft:air"},
{pos: [3, 1, 3], state: "minecraft:air"},
{pos: [3, 1, 4], state: "minecraft:air"},
{pos: [4, 1, 0], state: "minecraft:air"},
{pos: [4, 1, 1], state: "minecraft:air"},
{pos: [4, 1, 2], state: "minecraft:air"},
{pos: [4, 1, 3], state: "minecraft:air"},
{pos: [4, 1, 4], state: "minecraft:air"},
{pos: [0, 2, 0], state: "minecraft:air"},
{pos: [0, 2, 1], state: "minecraft:air"},
{pos: [0, 2, 2], state: "minecraft:air"},
{pos: [0, 2, 3], state: "minecraft:air"},
{pos: [0, 2, 4], state: "minecraft:air"},
{pos: [1, 2, 0], state: "minecraft:air"},
{pos: [1, 2, 1], state: "minecraft:air"},
{pos: [1, 2, 2], state: "minecraft:air"},
{pos: [1, 2, 3], state: "minecraft:air"},
{pos: [1, 2, 4], state: "minecraft:air"},
{pos: [2, 2, 0], state: "minecraft:air"},
{pos: [2, 2, 1], state: "minecraft:air"},
{pos: [2, 2, 2], state: "minecraft:air"},
{pos: [2, 2, 3], state: "minecraft:air"},
{pos: [2, 2, 4], state: "minecraft:air"},
{pos: [3, 2, 0], state: "minecraft:air"},
{pos: [3, 2, 1], state: "minecraft:air"},
{pos: [3, 2, 2], state: "minecraft:air"},
{pos: [3, 2, 3], state: "minecraft:air"},
{pos: [3, 2, 4], state: "minecraft:air"},
{pos: [4, 2, 0], state: "minecraft:air"},
{pos: [4, 2, 1], state: "minecraft:air"},
{pos: [4, 2, 2], state: "minecraft:air"},
{pos: [4, 2, 3], state: "minecraft:air"},
{pos: [4, 2, 4], state: "minecraft:air"},
{pos: [0, 3, 0], state: "minecraft:air"},
{pos: [0, 3, 1], state: "minecraft:air"},
{pos: [0, 3, 2], state: "minecraft:air"},
{pos: [0, 3, 3], state: "minecraft:air"},
{pos: [0, 3, 4], state: "minecraft:air"},
{pos: [1, 3, 0], state: "minecraft:air"},
{pos: [1, 3, 1], state: "minecraft:air"},
{pos: [1, 3, 2], state: "minecraft:air"},
{pos: [1, 3, 3], state: "minecraft:air"},
{pos: [1, 3, 4], state: "minecraft:air"},
{pos: [2, 3, 0], state: "minecraft:air"},
{pos: [2, 3, 1], state: "minecraft:air"},
{pos: [2, 3, 2], state: "minecraft:air"},
{pos: [2, 3, 3], state: "minecraft:air"},
{pos: [2, 3, 4], state: "minecraft:air"},
{pos: [3, 3, 0], state: "minecraft:air"},
{pos: [3, 3, 1], state: "minecraft:air"},
{pos: [3, 3, 2], state: "minecraft:air"},
{pos: [3, 3, 3], state: "minecraft:air"},
{pos: [3, 3, 4], state: "minecraft:air"},
{pos: [4, 3, 0], state: "minecraft:air"},
{pos: [4, 3, 1], state: "minecraft:air"},
{pos: [4, 3, 2], state: "minecraft:air"},
{pos: [4, 3, 3], state: "minecraft:air"},
{pos: [4, 3, 4], state: "minecraft:air"},
{pos: [0, 4, 0], state: "minecraft:air"},
{pos: [0, 4, 1], state: "minecraft:air"},
{pos: [0, 4, 2], state: "minecraft:air"},
{pos: [0, 4, 3], state: "minecraft:air"},
{pos: [0, 4, 4], state: "minecraft:air"},
{pos: [1, 4, 0], state: "minecraft:air"},
{pos: [1, 4, 1], state: "minecraft:air"},
{pos: [1, 4, 2], state: "minecraft:air"},
{pos: [1, 4, 3], state: "minecraft:air"},
{pos: [1, 4, 4], state: "minecraft:air"},
{pos: [2, 4, 0], state: "minecraft:air"},
{pos: [2, 4, 1], state: "minecraft:air"},
{pos: [2, 4, 2], state: "minecraft:air"},
{pos: [2, 4, 3], state: "minecraft:air"},
{pos: [2, 4, 4], state: "minecraft:air"},
{pos: [3, 4, 0], state: "minecraft:air"},
{pos: [3, 4, 1], state: "minecraft:air"},
{pos: [3, 4, 2], state: "minecraft:air"},
{pos: [3, 4, 3], state: "minecraft:air"},
{pos: [3, 4, 4], state: "minecraft:air"},
{pos: [4, 4, 0], state: "minecraft:air"},
{pos: [4, 4, 1], state: "minecraft:air"},
{pos: [4, 4, 2], state: "minecraft:air"},
{pos: [4, 4, 3], state: "minecraft:air"},
{pos: [4, 4, 4], state: "minecraft:air"}
],
entities: [],
palette: [
"minecraft:polished_andesite",
"minecraft:air",
"computercraft:turtle_normal{facing:north,waterlogged:false}"
]
}

View File

@@ -0,0 +1,39 @@
{
DataVersion: 3465,
size: [3, 3, 3],
data: [
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
{pos: [0, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], state: "minecraft:air"},
{pos: [1, 1, 0], state: "minecraft:air"},
{pos: [1, 1, 1], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 123, Fuel: 0, Items: [{Count: 1b, Slot: 0b, id: "computercraft:turtle_normal", tag: {ComputerId: 123, LeftUpgrade: "minecraft:diamond_pickaxe", Overlay: "computercraft:block/turtle_trans_overlay"}}], LeftUpgrade: "minecraft:diamond_pickaxe", LeftUpgradeNbt: {}, On: 0b, Overlay: "computercraft:block/turtle_trans_overlay", Owner: {LowerId: -4770154215762454977L, Name: "Player436", UpperId: 114963728012426084L}, Slot: 0, id: "computercraft:turtle_normal"}},
{pos: [1, 1, 2], state: "minecraft:air"},
{pos: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "minecraft:air"},
{pos: [2, 1, 2], state: "minecraft:air"},
{pos: [0, 2, 0], state: "minecraft:air"},
{pos: [0, 2, 1], state: "minecraft:air"},
{pos: [0, 2, 2], state: "minecraft:air"},
{pos: [1, 2, 0], state: "minecraft:air"},
{pos: [1, 2, 1], state: "minecraft:air"},
{pos: [1, 2, 2], state: "minecraft:air"},
{pos: [2, 2, 0], state: "minecraft:air"},
{pos: [2, 2, 1], state: "minecraft:air"},
{pos: [2, 2, 2], state: "minecraft:air"}
],
entities: [],
palette: [
"minecraft:polished_andesite",
"minecraft:air",
"computercraft:turtle_normal{facing:south,waterlogged:false}"
]
}

View File

@@ -0,0 +1,137 @@
{
DataVersion: 3465,
size: [5, 5, 5],
data: [
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
{pos: [0, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], state: "minecraft:air"},
{pos: [0, 1, 3], state: "minecraft:air"},
{pos: [0, 1, 4], state: "minecraft:air"},
{pos: [1, 1, 0], state: "minecraft:air"},
{pos: [1, 1, 1], state: "minecraft:air"},
{pos: [1, 1, 2], state: "minecraft:air"},
{pos: [1, 1, 3], state: "minecraft:air"},
{pos: [1, 1, 4], state: "minecraft:air"},
{pos: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "minecraft:air"},
{pos: [2, 1, 2], state: "computercraft:turtle_normal{facing:north,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 0, Items: [{Count: 1b, Slot: 0b, id: "minecraft:diamond_pickaxe"}], Label: "turtle_test.equip_tool", On: 1b, Slot: 0, id: "computercraft:turtle_normal"}},
{pos: [2, 1, 3], state: "minecraft:air"},
{pos: [2, 1, 4], state: "minecraft:air"},
{pos: [3, 1, 0], state: "minecraft:air"},
{pos: [3, 1, 1], state: "minecraft:air"},
{pos: [3, 1, 2], state: "minecraft:air"},
{pos: [3, 1, 3], state: "minecraft:air"},
{pos: [3, 1, 4], state: "minecraft:air"},
{pos: [4, 1, 0], state: "minecraft:air"},
{pos: [4, 1, 1], state: "minecraft:air"},
{pos: [4, 1, 2], state: "minecraft:air"},
{pos: [4, 1, 3], state: "minecraft:air"},
{pos: [4, 1, 4], state: "minecraft:air"},
{pos: [0, 2, 0], state: "minecraft:air"},
{pos: [0, 2, 1], state: "minecraft:air"},
{pos: [0, 2, 2], state: "minecraft:air"},
{pos: [0, 2, 3], state: "minecraft:air"},
{pos: [0, 2, 4], state: "minecraft:air"},
{pos: [1, 2, 0], state: "minecraft:air"},
{pos: [1, 2, 1], state: "minecraft:air"},
{pos: [1, 2, 2], state: "minecraft:air"},
{pos: [1, 2, 3], state: "minecraft:air"},
{pos: [1, 2, 4], state: "minecraft:air"},
{pos: [2, 2, 0], state: "minecraft:air"},
{pos: [2, 2, 1], state: "minecraft:air"},
{pos: [2, 2, 2], state: "minecraft:air"},
{pos: [2, 2, 3], state: "minecraft:air"},
{pos: [2, 2, 4], state: "minecraft:air"},
{pos: [3, 2, 0], state: "minecraft:air"},
{pos: [3, 2, 1], state: "minecraft:air"},
{pos: [3, 2, 2], state: "minecraft:air"},
{pos: [3, 2, 3], state: "minecraft:air"},
{pos: [3, 2, 4], state: "minecraft:air"},
{pos: [4, 2, 0], state: "minecraft:air"},
{pos: [4, 2, 1], state: "minecraft:air"},
{pos: [4, 2, 2], state: "minecraft:air"},
{pos: [4, 2, 3], state: "minecraft:air"},
{pos: [4, 2, 4], state: "minecraft:air"},
{pos: [0, 3, 0], state: "minecraft:air"},
{pos: [0, 3, 1], state: "minecraft:air"},
{pos: [0, 3, 2], state: "minecraft:air"},
{pos: [0, 3, 3], state: "minecraft:air"},
{pos: [0, 3, 4], state: "minecraft:air"},
{pos: [1, 3, 0], state: "minecraft:air"},
{pos: [1, 3, 1], state: "minecraft:air"},
{pos: [1, 3, 2], state: "minecraft:air"},
{pos: [1, 3, 3], state: "minecraft:air"},
{pos: [1, 3, 4], state: "minecraft:air"},
{pos: [2, 3, 0], state: "minecraft:air"},
{pos: [2, 3, 1], state: "minecraft:air"},
{pos: [2, 3, 2], state: "minecraft:air"},
{pos: [2, 3, 3], state: "minecraft:air"},
{pos: [2, 3, 4], state: "minecraft:air"},
{pos: [3, 3, 0], state: "minecraft:air"},
{pos: [3, 3, 1], state: "minecraft:air"},
{pos: [3, 3, 2], state: "minecraft:air"},
{pos: [3, 3, 3], state: "minecraft:air"},
{pos: [3, 3, 4], state: "minecraft:air"},
{pos: [4, 3, 0], state: "minecraft:air"},
{pos: [4, 3, 1], state: "minecraft:air"},
{pos: [4, 3, 2], state: "minecraft:air"},
{pos: [4, 3, 3], state: "minecraft:air"},
{pos: [4, 3, 4], state: "minecraft:air"},
{pos: [0, 4, 0], state: "minecraft:air"},
{pos: [0, 4, 1], state: "minecraft:air"},
{pos: [0, 4, 2], state: "minecraft:air"},
{pos: [0, 4, 3], state: "minecraft:air"},
{pos: [0, 4, 4], state: "minecraft:air"},
{pos: [1, 4, 0], state: "minecraft:air"},
{pos: [1, 4, 1], state: "minecraft:air"},
{pos: [1, 4, 2], state: "minecraft:air"},
{pos: [1, 4, 3], state: "minecraft:air"},
{pos: [1, 4, 4], state: "minecraft:air"},
{pos: [2, 4, 0], state: "minecraft:air"},
{pos: [2, 4, 1], state: "minecraft:air"},
{pos: [2, 4, 2], state: "minecraft:air"},
{pos: [2, 4, 3], state: "minecraft:air"},
{pos: [2, 4, 4], state: "minecraft:air"},
{pos: [3, 4, 0], state: "minecraft:air"},
{pos: [3, 4, 1], state: "minecraft:air"},
{pos: [3, 4, 2], state: "minecraft:air"},
{pos: [3, 4, 3], state: "minecraft:air"},
{pos: [3, 4, 4], state: "minecraft:air"},
{pos: [4, 4, 0], state: "minecraft:air"},
{pos: [4, 4, 1], state: "minecraft:air"},
{pos: [4, 4, 2], state: "minecraft:air"},
{pos: [4, 4, 3], state: "minecraft:air"},
{pos: [4, 4, 4], state: "minecraft:air"}
],
entities: [],
palette: [
"minecraft:polished_andesite",
"minecraft:air",
"computercraft:turtle_normal{facing:north,waterlogged:false}"
]
}

View File

@@ -1,23 +0,0 @@
// SPDX-FileCopyrightText: 2017 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api.lua;
import dan200.computercraft.api.peripheral.IComputerAccess;
import javax.annotation.Nullable;
/**
* An interface passed to {@link ILuaAPIFactory} in order to provide additional information
* about a computer.
*/
public interface IComputerSystem extends IComputerAccess {
/**
* Get the label for this computer.
*
* @return This computer's label, or {@code null} if it is not set.
*/
@Nullable
String getLabel();
}

View File

@@ -4,7 +4,6 @@
package dan200.computercraft.core;
import dan200.computercraft.api.lua.ILuaAPIFactory;
import dan200.computercraft.core.asm.GenericMethod;
import dan200.computercraft.core.asm.LuaMethodSupplier;
import dan200.computercraft.core.asm.PeripheralMethodSupplier;
@@ -35,21 +34,19 @@ public final class ComputerContext {
private final ComputerScheduler computerScheduler;
private final MainThreadScheduler mainThreadScheduler;
private final ILuaMachine.Factory luaFactory;
private final List<ILuaAPIFactory> apiFactories;
private final MethodSupplier<LuaMethod> luaMethods;
private final MethodSupplier<PeripheralMethod> peripheralMethods;
ComputerContext(
private ComputerContext(
GlobalEnvironment globalEnvironment, ComputerScheduler computerScheduler,
MainThreadScheduler mainThreadScheduler, ILuaMachine.Factory luaFactory,
List<ILuaAPIFactory> apiFactories, MethodSupplier<LuaMethod> luaMethods,
MethodSupplier<LuaMethod> luaMethods,
MethodSupplier<PeripheralMethod> peripheralMethods
) {
this.globalEnvironment = globalEnvironment;
this.computerScheduler = computerScheduler;
this.mainThreadScheduler = mainThreadScheduler;
this.luaFactory = luaFactory;
this.apiFactories = apiFactories;
this.luaMethods = luaMethods;
this.peripheralMethods = peripheralMethods;
}
@@ -91,15 +88,6 @@ public final class ComputerContext {
return luaFactory;
}
/**
* Additional APIs to inject into each computer.
*
* @return All available API factories.
*/
public List<ILuaAPIFactory> apiFactories() {
return apiFactories;
}
/**
* Get the {@link MethodSupplier} used to find methods on Lua values.
*
@@ -166,7 +154,6 @@ public final class ComputerContext {
private @Nullable ComputerScheduler computerScheduler = null;
private @Nullable MainThreadScheduler mainThreadScheduler;
private @Nullable ILuaMachine.Factory luaFactory;
private @Nullable List<ILuaAPIFactory> apiFactories;
private @Nullable List<GenericMethod> genericMethods;
Builder(GlobalEnvironment environment) {
@@ -227,20 +214,6 @@ public final class ComputerContext {
return this;
}
/**
* Set the additional {@linkplain ILuaAPIFactory APIs} to add to each computer.
*
* @param apis A list of API factories.
* @return {@code this}, for chaining
* @see ComputerContext#apiFactories()
*/
public Builder apiFactories(Collection<ILuaAPIFactory> apis) {
Objects.requireNonNull(apis);
if (apiFactories != null) throw new IllegalStateException("Main-thread scheduler already specified");
apiFactories = List.copyOf(apis);
return this;
}
/**
* Set the set of {@link GenericMethod}s used by the {@linkplain MethodSupplier method suppliers}.
*
@@ -267,7 +240,6 @@ public final class ComputerContext {
computerScheduler == null ? new ComputerThread(1) : computerScheduler,
mainThreadScheduler == null ? new NoWorkMainThreadScheduler() : mainThreadScheduler,
luaFactory == null ? CobaltLuaMachine::new : luaFactory,
apiFactories == null ? List.of() : apiFactories,
LuaMethodSupplier.create(genericMethods == null ? List.of() : genericMethods),
PeripheralMethodSupplier.create(genericMethods == null ? List.of() : genericMethods)
);

View File

@@ -21,7 +21,7 @@ public abstract class ComputerAccess implements IComputerAccess {
private static final Logger LOG = LoggerFactory.getLogger(ComputerAccess.class);
private final IAPIEnvironment environment;
private final Set<String> mounts = new HashSet<>();
private final Set<String> mounts = new HashSet<>(0);
protected ComputerAccess(IAPIEnvironment environment) {
this.environment = environment;

View File

@@ -175,9 +175,9 @@ public class OSAPI implements ILuaAPI {
}
/**
* Sets an alarm that will fire at the specified in-game time. When it
* fires, * an {@code alarm} event will be added to the event queue with the
* ID * returned from this function as the first parameter.
* Sets an alarm that will fire at the specified {@linkplain #time(IArguments) in-game time}.
* When it fires, an {@code alarm} event will be added to the event queue with the
* ID returned from this function as the first parameter.
*
* @param time The time at which to fire the alarm, in the range [0.0, 24.0).
* @return The ID of the new alarm. This can be used to filter the

View File

@@ -0,0 +1,28 @@
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.core.computer;
import dan200.computercraft.api.lua.ILuaAPI;
/**
* Hooks for managing the lifecycle of an API. This allows adding additional logic to an API's {@link ILuaAPI#startup()}
* and {@link ILuaAPI#shutdown()} methods.
*
* @see ILuaAPI
* @see Computer#addApi(ILuaAPI, ApiLifecycle)
*/
public interface ApiLifecycle {
/**
* Called before the API's {@link ILuaAPI#startup()} method, may be used to set up resources.
*/
default void startup() {
}
/**
* Called after the API's {@link ILuaAPI#shutdown()} method, may be used to tear down resources.
*/
default void shutdown() {
}
}

View File

@@ -9,18 +9,14 @@ import dan200.computercraft.api.lua.ILuaAPI;
import javax.annotation.Nullable;
/**
* A wrapper for {@link ILuaAPI}s which optionally manages the lifecycle of a {@link ComputerSystem}.
* A wrapper for {@link ILuaAPI}s which provides an optional shutdown hook to clean up resources.
*
* @param api The original API.
* @param lifecycle The optional lifecycle hooks for this API.
*/
final class ApiWrapper {
private final ILuaAPI api;
private final @Nullable ComputerSystem system;
ApiWrapper(ILuaAPI api, @Nullable ComputerSystem system) {
this.api = api;
this.system = system;
}
record ApiWrapper(ILuaAPI api, @Nullable ApiLifecycle lifecycle) {
public void startup() {
if (lifecycle != null) lifecycle.startup();
api.startup();
}
@@ -30,7 +26,7 @@ final class ApiWrapper {
public void shutdown() {
api.shutdown();
if (system != null) system.unmountAll();
if (lifecycle != null) lifecycle.shutdown();
}
public ILuaAPI api() {

Some files were not shown because too many files have changed in this diff Show More