mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-10-30 21:23:00 +00:00 
			
		
		
		
	Merge branch 'mc-1.20.x' into mc-1.21.x
This commit is contained in:
		| @@ -27,7 +27,7 @@ repos: | |||||||
|     exclude: "^(.*\\.(bat)|LICENSE)$" |     exclude: "^(.*\\.(bat)|LICENSE)$" | ||||||
|  |  | ||||||
| - repo: https://github.com/fsfe/reuse-tool | - repo: https://github.com/fsfe/reuse-tool | ||||||
|   rev: v2.1.0 |   rev: v4.0.3 | ||||||
|   hooks: |   hooks: | ||||||
|   - id: reuse |   - id: reuse | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										100
									
								
								.reuse/dep5
									
									
									
									
									
								
							
							
						
						
									
										100
									
								
								.reuse/dep5
									
									
									
									
									
								
							| @@ -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 |  | ||||||
| @@ -12,7 +12,6 @@ If you've any other questions, [just ask the community][community] or [open an i | |||||||
| 
 | 
 | ||||||
| ## Table of Contents | ## Table of Contents | ||||||
|  - [Reporting issues](#reporting-issues) |  - [Reporting issues](#reporting-issues) | ||||||
|  - [Translations](#translations) |  | ||||||
|  - [Setting up a development environment](#setting-up-a-development-environment) |  - [Setting up a development environment](#setting-up-a-development-environment) | ||||||
|  - [Developing CC: Tweaked](#developing-cc-tweaked) |  - [Developing CC: Tweaked](#developing-cc-tweaked) | ||||||
|  - [Writing documentation](#writing-documentation) |  - [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 | 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. | 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 | ## Setting up a development environment | ||||||
| In order to develop CC: Tweaked, you'll need to download the source code and then run it. | 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: |  - 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/). |    - [Git](https://git-scm.com/). | ||||||
|    - [NodeJS][node]. |    - [NodeJS 20 or later][node]. | ||||||
| 
 | 
 | ||||||
|  - Download CC: Tweaked's source code: |  - 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." | [community]: README.md#community "Get in touch with the community." | ||||||
| [Adoptium]: https://adoptium.net/temurin/releases?version=17 "Download OpenJDK 17" | [Adoptium]: https://adoptium.net/temurin/releases?version=17 "Download OpenJDK 17" | ||||||
| [illuaminate]: https://github.com/SquidDev/illuaminate/ "Illuaminate on GitHub" | [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" | [docs]: https://tweaked.cc/ "CC: Tweaked documentation" | ||||||
| [ldoc]: http://stevedonovan.github.io/ldoc/ "ldoc, a Lua documentation generator." | [ldoc]: http://stevedonovan.github.io/ldoc/ "ldoc, a Lua documentation generator." | ||||||
| [mc-test]: https://www.youtube.com/watch?v=vXaWOJTCYNg | [mc-test]: https://www.youtube.com/watch?v=vXaWOJTCYNg | ||||||
|   | |||||||
| @@ -26,8 +26,9 @@ developing the mod, [check out the instructions here](CONTRIBUTING.md#developing | |||||||
| 
 | 
 | ||||||
| ## Community | ## Community | ||||||
| If you need help getting started with CC: Tweaked, want to show off your latest project, or just want to chat about | 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 | ComputerCraft, do check out our [GitHub discussions page][GitHub discussions]! There's also a fairly populated, | ||||||
| populated, albeit quiet [IRC channel][irc], if that's more your cup of tea. | 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"). | We also host fairly comprehensive documentation at [tweaked.cc](https://tweaked.cc/ "The CC: Tweaked website"). | ||||||
| 
 | 
 | ||||||
| @@ -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" | [modrinth]: https://modrinth.com/mod/gu7yAYhd "Download CC: Tweaked from Modrinth" | ||||||
| [Minecraft Forge]: https://files.minecraftforge.net/ "Download Minecraft Forge." | [Minecraft Forge]: https://files.minecraftforge.net/ "Download Minecraft Forge." | ||||||
| [Fabric]: https://fabricmc.net/use/installer/ "Download Fabric." | [Fabric]: https://fabricmc.net/use/installer/ "Download Fabric." | ||||||
| [forum]: https://forums.computercraft.cc/ |  | ||||||
| [GitHub Discussions]: https://github.com/cc-tweaked/CC-Tweaked/discussions | [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
									
								
							
							
						
						
									
										111
									
								
								REUSE.toml
									
									
									
									
									
										Normal 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" | ||||||
| @@ -47,6 +47,7 @@ repositories { | |||||||
|         filter { |         filter { | ||||||
|             includeGroup("cc.tweaked") |             includeGroup("cc.tweaked") | ||||||
|             // Things we mirror |             // Things we mirror | ||||||
|  |             includeGroup("com.simibubi.create") | ||||||
|             includeGroup("commoble.morered") |             includeGroup("commoble.morered") | ||||||
|             includeGroup("dev.architectury") |             includeGroup("dev.architectury") | ||||||
|             includeGroup("dev.emi") |             includeGroup("dev.emi") | ||||||
|   | |||||||
| @@ -22,7 +22,6 @@ import org.gradle.api.tasks.SourceSet | |||||||
| import org.gradle.api.tasks.bundling.Jar | import org.gradle.api.tasks.bundling.Jar | ||||||
| import org.gradle.api.tasks.compile.JavaCompile | import org.gradle.api.tasks.compile.JavaCompile | ||||||
| import org.gradle.api.tasks.javadoc.Javadoc | import org.gradle.api.tasks.javadoc.Javadoc | ||||||
| import org.gradle.configurationcache.extensions.capitalized |  | ||||||
| import org.gradle.language.base.plugins.LifecycleBasePlugin | import org.gradle.language.base.plugins.LifecycleBasePlugin | ||||||
| import org.gradle.language.jvm.tasks.ProcessResources | import org.gradle.language.jvm.tasks.ProcessResources | ||||||
| import org.gradle.process.JavaForkOptions | import org.gradle.process.JavaForkOptions | ||||||
| @@ -181,7 +180,7 @@ abstract class CCTweakedExtension( | |||||||
| 
 | 
 | ||||||
|     fun <T> jacoco(task: NamedDomainObjectProvider<T>) where T : Task, T : JavaForkOptions { |     fun <T> jacoco(task: NamedDomainObjectProvider<T>) where T : Task, T : JavaForkOptions { | ||||||
|         val classDump = project.layout.buildDirectory.dir("jacocoClassDump/${task.name}") |         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) |         val jacoco = project.extensions.getByType(JacocoPluginExtension::class.java) | ||||||
|         task.configure { |         task.configure { | ||||||
|   | |||||||
| @@ -171,3 +171,15 @@ fun getNextVersion(version: String): String { | |||||||
|     if (dashIndex >= 0) out.append(version, dashIndex, version.length) |     if (dashIndex >= 0) out.append(version, dashIndex, version.length) | ||||||
|     return out.toString() |     return out.toString() | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Capitalise the first letter of the string. | ||||||
|  |  * | ||||||
|  |  * This is a replacement for the now deprecated [String.capitalize]. | ||||||
|  |  */ | ||||||
|  | fun String.capitalise(): String { | ||||||
|  |     if (isEmpty()) return this | ||||||
|  |     val first = this[0] | ||||||
|  |     val firstTitle = first.titlecaseChar() | ||||||
|  |     return if (first == firstTitle) this else firstTitle + substring(1) | ||||||
|  | } | ||||||
|   | |||||||
| @@ -191,7 +191,7 @@ end | |||||||
| 
 | 
 | ||||||
| > [Confused?][!NOTE] | > [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 | > 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 | 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. | 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" | [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" | [Ring Buffer]: https://en.wikipedia.org/wiki/Circular_buffer "Circular buffer - Wikipedia" | ||||||
| [Sine Wave]: https://en.wikipedia.org/wiki/Sine_wave "Sine wave - Wikipedia" | [Sine Wave]: https://en.wikipedia.org/wiki/Sine_wave "Sine wave - Wikipedia" | ||||||
| [GitHub Discussions]: https://github.com/cc-tweaked/CC-Tweaked/discussions | [Community]: /#community | ||||||
| [IRC]: https://webchat.esper.net/?channels=computercraft "#computercraft on EsperNet" |  | ||||||
|   | |||||||
| @@ -50,7 +50,11 @@ little daunting getting started. Thankfully, there's several fantastic tutorials | |||||||
| Once you're a little more familiar with the mod, the sidebar and links below provide more detailed documentation on the | 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. | 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 | ## Get Involved | ||||||
| CC: Tweaked lives on [GitHub]. If you've got any ideas, feedback or bugs please do [create an issue][bug]. | 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." | [Fabric]: https://fabricmc.net/use/installer/ "Download Fabric." | ||||||
| [lua]: https://www.lua.org/ "Lua's main website" | [lua]: https://www.lua.org/ "Lua's main website" | ||||||
| [GitHub Discussions]: https://github.com/cc-tweaked/CC-Tweaked/discussions | [GitHub Discussions]: https://github.com/cc-tweaked/CC-Tweaked/discussions | ||||||
| [IRC]: https://webchat.esper.net/?channels=computercraft "#computercraft on EsperNet" | [EsperNet]: https://www.esper.net/ | ||||||
|  | [KiwiIRC]: https://kiwiirc.com/nextclient/#irc://irc.esper.net:+6697/#computercraft "#computercraft on EsperNet" | ||||||
|   | |||||||
| @@ -50,7 +50,11 @@ little daunting getting started. Thankfully, there's several fantastic tutorials | |||||||
| Once you're a little more familiar with the mod, the [wiki](https://tweaked.cc/) provides more detailed documentation on the | 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. | 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 | ## Get Involved | ||||||
| CC: Tweaked lives on [GitHub]. If you've got any ideas, feedback or bugs please do [create an issue][bug]. | 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" | [computercraft]: https://github.com/dan200/ComputerCraft "ComputerCraft on GitHub" | ||||||
| [lua]: https://www.lua.org/ "Lua's main website" | [lua]: https://www.lua.org/ "Lua's main website" | ||||||
| [GitHub Discussions]: https://github.com/cc-tweaked/CC-Tweaked/discussions | [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" | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ neogradle.subsystems.conventions.runs.enabled=false | |||||||
|  |  | ||||||
| # Mod properties | # Mod properties | ||||||
| isUnstable=true | isUnstable=true | ||||||
| modVersion=1.111.1 | modVersion=1.112.0 | ||||||
|  |  | ||||||
| # Minecraft properties: We want to configure this here so we can read it in settings.gradle | # Minecraft properties: We want to configure this here so we can read it in settings.gradle | ||||||
| mcVersion=1.21 | mcVersion=1.21 | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								gradle/gradle-daemon-jvm.properties
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								gradle/gradle-daemon-jvm.properties
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | |||||||
|  | #This file is generated by updateDaemonJvm | ||||||
|  | toolchainVersion=21 | ||||||
| @@ -47,6 +47,8 @@ rei = "16.0.729" | |||||||
| rubidium = "0.6.1" | rubidium = "0.6.1" | ||||||
| sodium = "mc1.20-0.4.10" | sodium = "mc1.20-0.4.10" | ||||||
| mixinExtra = "0.3.5" | mixinExtra = "0.3.5" | ||||||
|  | create-forge = "0.5.1.f-33" | ||||||
|  | create-fabric = "0.5.1-f-build.1467+mc1.20.1" | ||||||
|  |  | ||||||
| # Testing | # Testing | ||||||
| hamcrest = "2.2" | hamcrest = "2.2" | ||||||
| @@ -100,11 +102,13 @@ nightConfig-toml = { module = "com.electronwill.night-config:toml", version.ref | |||||||
| slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } | slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } | ||||||
|  |  | ||||||
| # Minecraft mods | # Minecraft mods | ||||||
| fabric-api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabric-api" } | create-fabric = { module = "com.simibubi.create:create-fabric-1.20.1", version.ref = "create-fabric" } | ||||||
| fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" } | create-forge = { module = "com.simibubi.create:create-1.20.1", version.ref = "create-forge" } | ||||||
| fabric-junit = { module = "net.fabricmc:fabric-loader-junit", version.ref = "fabric-loader" } |  | ||||||
| fabricPermissions = { module = "me.lucko:fabric-permissions-api", version.ref = "fabricPermissions" } |  | ||||||
| emi = { module = "dev.emi:emi-xplat-mojmap", version.ref = "emi" } | 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" } | iris = { module = "maven.modrinth:iris", version.ref = "iris" } | ||||||
| jei-api = { module = "mezz.jei:jei-1.21-common-api", version.ref = "jei" } | 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" } | jei-fabric = { module = "mezz.jei:jei-1.21-fabric", version.ref = "jei" } | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							| @@ -1,6 +1,6 @@ | |||||||
| distributionBase=GRADLE_USER_HOME | distributionBase=GRADLE_USER_HOME | ||||||
| distributionPath=wrapper/dists | distributionPath=wrapper/dists | ||||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip | ||||||
| networkTimeout=10000 | networkTimeout=10000 | ||||||
| validateDistributionUrl=true | validateDistributionUrl=true | ||||||
| zipStoreBase=GRADLE_USER_HOME | zipStoreBase=GRADLE_USER_HOME | ||||||
|   | |||||||
| @@ -4,9 +4,11 @@ | |||||||
| 
 | 
 | ||||||
| package dan200.computercraft.api; | package dan200.computercraft.api; | ||||||
| 
 | 
 | ||||||
|  | import dan200.computercraft.api.component.ComputerComponent; | ||||||
| import dan200.computercraft.api.filesystem.Mount; | import dan200.computercraft.api.filesystem.Mount; | ||||||
| import dan200.computercraft.api.filesystem.WritableMount; | import dan200.computercraft.api.filesystem.WritableMount; | ||||||
| import dan200.computercraft.api.lua.GenericSource; | import dan200.computercraft.api.lua.GenericSource; | ||||||
|  | import dan200.computercraft.api.lua.IComputerSystem; | ||||||
| import dan200.computercraft.api.lua.ILuaAPI; | import dan200.computercraft.api.lua.ILuaAPI; | ||||||
| import dan200.computercraft.api.lua.ILuaAPIFactory; | import dan200.computercraft.api.lua.ILuaAPIFactory; | ||||||
| import dan200.computercraft.api.media.IMedia; | 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. |      * Register a custom {@link ILuaAPI}, which may be added onto all computers without requiring a peripheral. | ||||||
|      * <p> |      * <p> | ||||||
|      * Before implementing this interface, consider alternative methods of providing methods. It is generally preferred |      * 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. |      * @param factory The factory for your API subclass. | ||||||
|      * @see ILuaAPIFactory |      * @see ILuaAPIFactory | ||||||
|   | |||||||
| @@ -0,0 +1,24 @@ | |||||||
|  | // SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers | ||||||
|  | // | ||||||
|  | // SPDX-License-Identifier: MPL-2.0 | ||||||
|  | 
 | ||||||
|  | package dan200.computercraft.api.component; | ||||||
|  | 
 | ||||||
|  | import net.minecraft.commands.CommandSourceStack; | ||||||
|  | import org.jetbrains.annotations.ApiStatus; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * A computer which has permission to perform administrative/op commands, such as the command computer. | ||||||
|  |  */ | ||||||
|  | @ApiStatus.NonExtendable | ||||||
|  | public interface AdminComputer { | ||||||
|  |     /** | ||||||
|  |      * The permission level that this computer can operate at. | ||||||
|  |      * | ||||||
|  |      * @return The permission level for this computer. | ||||||
|  |      * @see CommandSourceStack#hasPermission(int) | ||||||
|  |      */ | ||||||
|  |     default int permissionLevel() { | ||||||
|  |         return 2; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,48 @@ | |||||||
|  | // SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers | ||||||
|  | // | ||||||
|  | // SPDX-License-Identifier: MPL-2.0 | ||||||
|  | 
 | ||||||
|  | package dan200.computercraft.api.component; | ||||||
|  | 
 | ||||||
|  | import dan200.computercraft.api.lua.IComputerSystem; | ||||||
|  | import dan200.computercraft.api.lua.ILuaAPIFactory; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * A component attached to a computer. | ||||||
|  |  * <p> | ||||||
|  |  * Components provide a mechanism to attach additional data to a computer, that can then be queried with | ||||||
|  |  * {@link IComputerSystem#getComponent(ComputerComponent)}. | ||||||
|  |  * <p> | ||||||
|  |  * This is largely designed for {@linkplain ILuaAPIFactory custom APIs}, allowing APIs to read additional properties | ||||||
|  |  * of the computer, such as its position. | ||||||
|  |  * | ||||||
|  |  * @param <T> The type of this component. | ||||||
|  |  * @see ComputerComponents The built-in components. | ||||||
|  |  */ | ||||||
|  | @SuppressWarnings("UnusedTypeParameter") | ||||||
|  | public final class ComputerComponent<T> { | ||||||
|  |     private final String id; | ||||||
|  | 
 | ||||||
|  |     private ComputerComponent(String id) { | ||||||
|  |         this.id = id; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Create a new computer component. | ||||||
|  |      * <p> | ||||||
|  |      * Mods typically will not need to create their own components. | ||||||
|  |      * | ||||||
|  |      * @param namespace The namespace of this component. This should be the mod id. | ||||||
|  |      * @param id        The unique id of this component. | ||||||
|  |      * @param <T>       The component | ||||||
|  |      * @return The newly created component. | ||||||
|  |      */ | ||||||
|  |     public static <T> ComputerComponent<T> create(String namespace, String id) { | ||||||
|  |         return new ComputerComponent<>(namespace + ":" + id); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public String toString() { | ||||||
|  |         return "ComputerComponent(" + id + ")"; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,29 @@ | |||||||
|  | // SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers | ||||||
|  | // | ||||||
|  | // SPDX-License-Identifier: MPL-2.0 | ||||||
|  | 
 | ||||||
|  | package dan200.computercraft.api.component; | ||||||
|  | 
 | ||||||
|  | import dan200.computercraft.api.ComputerCraftAPI; | ||||||
|  | import dan200.computercraft.api.pocket.IPocketAccess; | ||||||
|  | import dan200.computercraft.api.turtle.ITurtleAccess; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * The {@link ComputerComponent}s provided by ComputerCraft. | ||||||
|  |  */ | ||||||
|  | public class ComputerComponents { | ||||||
|  |     /** | ||||||
|  |      * The {@link ITurtleAccess} associated with a turtle. | ||||||
|  |      */ | ||||||
|  |     public static final ComputerComponent<ITurtleAccess> TURTLE = ComputerComponent.create(ComputerCraftAPI.MOD_ID, "turtle"); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * The {@link IPocketAccess} associated with a pocket computer. | ||||||
|  |      */ | ||||||
|  |     public static final ComputerComponent<IPocketAccess> POCKET = ComputerComponent.create(ComputerCraftAPI.MOD_ID, "pocket"); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * This component is only present on "command computers", and other computers with admin capabilities. | ||||||
|  |      */ | ||||||
|  |     public static final ComputerComponent<AdminComputer> ADMIN_COMPUTER = ComputerComponent.create(ComputerCraftAPI.MOD_ID, "admin_computer"); | ||||||
|  | } | ||||||
| @@ -0,0 +1,59 @@ | |||||||
|  | // SPDX-FileCopyrightText: 2017 The CC: Tweaked Developers | ||||||
|  | // | ||||||
|  | // SPDX-License-Identifier: MPL-2.0 | ||||||
|  | 
 | ||||||
|  | package dan200.computercraft.api.lua; | ||||||
|  | 
 | ||||||
|  | import dan200.computercraft.api.component.ComputerComponent; | ||||||
|  | import dan200.computercraft.api.peripheral.IComputerAccess; | ||||||
|  | import net.minecraft.core.BlockPos; | ||||||
|  | import net.minecraft.server.level.ServerLevel; | ||||||
|  | import org.jetbrains.annotations.ApiStatus; | ||||||
|  | 
 | ||||||
|  | import javax.annotation.Nullable; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * An interface passed to {@link ILuaAPIFactory} in order to provide additional information | ||||||
|  |  * about a computer. | ||||||
|  |  */ | ||||||
|  | @ApiStatus.NonExtendable | ||||||
|  | public interface IComputerSystem extends IComputerAccess { | ||||||
|  |     /** | ||||||
|  |      * Get the level this computer is currently in. | ||||||
|  |      * <p> | ||||||
|  |      * This method is not guaranteed to remain the same (even for stationary computers). | ||||||
|  |      * | ||||||
|  |      * @return The computer's current level. | ||||||
|  |      */ | ||||||
|  |     ServerLevel getLevel(); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get the position this computer is currently at. | ||||||
|  |      * <p> | ||||||
|  |      * This method is not guaranteed to remain the same (even for stationary computers). | ||||||
|  |      * | ||||||
|  |      * @return The computer's current position. | ||||||
|  |      */ | ||||||
|  |     BlockPos getPosition(); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get the label for this computer. | ||||||
|  |      * | ||||||
|  |      * @return This computer's label, or {@code null} if it is not set. | ||||||
|  |      */ | ||||||
|  |     @Nullable | ||||||
|  |     String getLabel(); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get a component attached to this computer. | ||||||
|  |      * <p> | ||||||
|  |      * No component is guaranteed to be on a computer, and so this method should always be guarded with a null check. | ||||||
|  |      * <p> | ||||||
|  |      * This method will always return the same value for a given component, and so may be cached. | ||||||
|  |      * | ||||||
|  |      * @param component The component to query. | ||||||
|  |      * @param <T>       The type of the component. | ||||||
|  |      * @return The component, if present. | ||||||
|  |      */ | ||||||
|  |     <T> @Nullable T getComponent(ComputerComponent<T> component); | ||||||
|  | } | ||||||
| @@ -4,13 +4,15 @@ | |||||||
| 
 | 
 | ||||||
| package dan200.computercraft.api.lua; | package dan200.computercraft.api.lua; | ||||||
| 
 | 
 | ||||||
|  | import dan200.computercraft.api.ComputerCraftAPI; | ||||||
|  | 
 | ||||||
| import javax.annotation.Nullable; | import javax.annotation.Nullable; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Construct an {@link ILuaAPI} for a specific computer. |  * Construct an {@link ILuaAPI} for a computer. | ||||||
|  * |  * | ||||||
|  * @see ILuaAPI |  * @see ILuaAPI | ||||||
|  * @see dan200.computercraft.api.ComputerCraftAPI#registerAPIFactory(ILuaAPIFactory) |  * @see ComputerCraftAPI#registerAPIFactory(ILuaAPIFactory) | ||||||
|  */ |  */ | ||||||
| @FunctionalInterface | @FunctionalInterface | ||||||
| public interface ILuaAPIFactory { | public interface ILuaAPIFactory { | ||||||
| @@ -5,16 +5,35 @@ | |||||||
| package dan200.computercraft.api.pocket; | package dan200.computercraft.api.pocket; | ||||||
| 
 | 
 | ||||||
| import dan200.computercraft.api.upgrades.UpgradeBase; | import dan200.computercraft.api.upgrades.UpgradeBase; | ||||||
|  | import dan200.computercraft.api.upgrades.UpgradeData; | ||||||
| import net.minecraft.core.component.DataComponentPatch; | import net.minecraft.core.component.DataComponentPatch; | ||||||
|  | import net.minecraft.server.level.ServerLevel; | ||||||
| import net.minecraft.world.entity.Entity; | import net.minecraft.world.entity.Entity; | ||||||
| import net.minecraft.world.item.ItemStack; | import net.minecraft.world.item.ItemStack; | ||||||
|  | import net.minecraft.world.phys.Vec3; | ||||||
|  | import org.jetbrains.annotations.ApiStatus; | ||||||
| 
 | 
 | ||||||
| import javax.annotation.Nullable; | import javax.annotation.Nullable; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Wrapper class for pocket computers. |  * Wrapper class for pocket computers. | ||||||
|  */ |  */ | ||||||
|  | @ApiStatus.NonExtendable | ||||||
| public interface IPocketAccess { | 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. |      * Gets the entity holding this item. | ||||||
|      * <p> |      * <p> | ||||||
| @@ -61,6 +80,26 @@ public interface IPocketAccess { | |||||||
|      */ |      */ | ||||||
|     void setLight(int colour); |     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. |      * Get the upgrade-specific NBT. | ||||||
|      * <p> |      * <p> | ||||||
| @@ -70,6 +109,7 @@ public interface IPocketAccess { | |||||||
|      * @see #setUpgradeData(DataComponentPatch) |      * @see #setUpgradeData(DataComponentPatch) | ||||||
|      * @see UpgradeBase#getUpgradeItem(DataComponentPatch) |      * @see UpgradeBase#getUpgradeItem(DataComponentPatch) | ||||||
|      * @see UpgradeBase#getUpgradeData(ItemStack) |      * @see UpgradeBase#getUpgradeData(ItemStack) | ||||||
|  |      * @see #getUpgrade() | ||||||
|      */ |      */ | ||||||
|     DataComponentPatch getUpgradeData(); |     DataComponentPatch getUpgradeData(); | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -45,6 +45,7 @@ dependencies { | |||||||
|     compileOnly(libs.mixin) |     compileOnly(libs.mixin) | ||||||
|     compileOnly(libs.mixinExtra) |     compileOnly(libs.mixinExtra) | ||||||
|     compileOnly(libs.bundles.externalMods.common) |     compileOnly(libs.bundles.externalMods.common) | ||||||
|  |     compileOnly(variantOf(libs.create.forge) { classifier("slim") }) { isTransitive = false } | ||||||
|     clientCompileOnly(variantOf(libs.emi) { classifier("api") }) |     clientCompileOnly(variantOf(libs.emi) { classifier("api") }) | ||||||
| 
 | 
 | ||||||
|     annotationProcessorEverywhere(libs.autoService) |     annotationProcessorEverywhere(libs.autoService) | ||||||
|   | |||||||
| @@ -14,7 +14,6 @@ import java.util.Objects; | |||||||
| /** | /** | ||||||
|  * The global factory for {@link ILuaAPIFactory}s. |  * The global factory for {@link ILuaAPIFactory}s. | ||||||
|  * |  * | ||||||
|  * @see dan200.computercraft.core.ComputerContext.Builder#apiFactories(Collection) |  | ||||||
|  * @see dan200.computercraft.api.ComputerCraftAPI#registerAPIFactory(ILuaAPIFactory) |  * @see dan200.computercraft.api.ComputerCraftAPI#registerAPIFactory(ILuaAPIFactory) | ||||||
|  */ |  */ | ||||||
| public final class ApiFactories { | public final class ApiFactories { | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ import com.mojang.brigadier.arguments.ArgumentType; | |||||||
| import com.mojang.serialization.Codec; | import com.mojang.serialization.Codec; | ||||||
| import com.mojang.serialization.MapCodec; | import com.mojang.serialization.MapCodec; | ||||||
| import dan200.computercraft.api.ComputerCraftAPI; | import dan200.computercraft.api.ComputerCraftAPI; | ||||||
|  | import dan200.computercraft.api.component.ComputerComponents; | ||||||
| import dan200.computercraft.api.detail.DetailProvider; | import dan200.computercraft.api.detail.DetailProvider; | ||||||
| import dan200.computercraft.api.detail.VanillaDetailRegistries; | import dan200.computercraft.api.detail.VanillaDetailRegistries; | ||||||
| import dan200.computercraft.api.media.IMedia; | 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.ColourableRecipe; | ||||||
| import dan200.computercraft.shared.common.DefaultBundledRedstoneProvider; | import dan200.computercraft.shared.common.DefaultBundledRedstoneProvider; | ||||||
| import dan200.computercraft.shared.common.HeldItemMenu; | 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.CommandComputerBlock; | ||||||
| import dan200.computercraft.shared.computer.blocks.ComputerBlock; | import dan200.computercraft.shared.computer.blocks.ComputerBlock; | ||||||
| import dan200.computercraft.shared.computer.blocks.ComputerBlockEntity; | 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.PlatformHelper; | ||||||
| import dan200.computercraft.shared.platform.RegistrationHelper; | import dan200.computercraft.shared.platform.RegistrationHelper; | ||||||
| import dan200.computercraft.shared.platform.RegistryEntry; | 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.items.PocketComputerItem; | ||||||
| import dan200.computercraft.shared.pocket.peripherals.PocketModem; | import dan200.computercraft.shared.pocket.peripherals.PocketModem; | ||||||
| import dan200.computercraft.shared.pocket.peripherals.PocketSpeaker; | import dan200.computercraft.shared.pocket.peripherals.PocketSpeaker; | ||||||
| @@ -73,8 +76,10 @@ import dan200.computercraft.shared.recipe.function.CopyComponents; | |||||||
| import dan200.computercraft.shared.recipe.function.RecipeFunction; | import dan200.computercraft.shared.recipe.function.RecipeFunction; | ||||||
| import dan200.computercraft.shared.turtle.FurnaceRefuelHandler; | import dan200.computercraft.shared.turtle.FurnaceRefuelHandler; | ||||||
| import dan200.computercraft.shared.turtle.TurtleOverlay; | 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.TurtleBlock; | ||||||
| import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity; | 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.inventory.TurtleMenu; | ||||||
| import dan200.computercraft.shared.turtle.items.TurtleItem; | import dan200.computercraft.shared.turtle.items.TurtleItem; | ||||||
| import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe; | import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe; | ||||||
| @@ -82,6 +87,7 @@ import dan200.computercraft.shared.turtle.upgrades.TurtleCraftingTable; | |||||||
| import dan200.computercraft.shared.turtle.upgrades.TurtleModem; | import dan200.computercraft.shared.turtle.upgrades.TurtleModem; | ||||||
| import dan200.computercraft.shared.turtle.upgrades.TurtleSpeaker; | import dan200.computercraft.shared.turtle.upgrades.TurtleSpeaker; | ||||||
| import dan200.computercraft.shared.turtle.upgrades.TurtleTool; | import dan200.computercraft.shared.turtle.upgrades.TurtleTool; | ||||||
|  | import dan200.computercraft.shared.util.ComponentMap; | ||||||
| import dan200.computercraft.shared.util.DataComponentUtil; | import dan200.computercraft.shared.util.DataComponentUtil; | ||||||
| import dan200.computercraft.shared.util.NonNegativeId; | import dan200.computercraft.shared.util.NonNegativeId; | ||||||
| import net.minecraft.commands.CommandSourceStack; | import net.minecraft.commands.CommandSourceStack; | ||||||
| @@ -114,6 +120,7 @@ import net.minecraft.world.level.block.state.BlockBehaviour; | |||||||
| import net.minecraft.world.level.material.MapColor; | import net.minecraft.world.level.material.MapColor; | ||||||
| import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType; | import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType; | ||||||
| 
 | 
 | ||||||
|  | import java.util.Objects; | ||||||
| import java.util.function.BiFunction; | import java.util.function.BiFunction; | ||||||
| import java.util.function.Predicate; | import java.util.function.Predicate; | ||||||
| import java.util.function.UnaryOperator; | import java.util.function.UnaryOperator; | ||||||
| @@ -579,6 +586,22 @@ public final class ModRegistry { | |||||||
|             return null; |             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.ITEM_STACK.addProvider(ItemDetails::fill); | ||||||
|         VanillaDetailRegistries.BLOCK_IN_WORLD.addProvider(BlockDetails::fill); |         VanillaDetailRegistries.BLOCK_IN_WORLD.addProvider(BlockDetails::fill); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -6,11 +6,11 @@ package dan200.computercraft.shared.computer.apis; | |||||||
| 
 | 
 | ||||||
| import com.mojang.brigadier.tree.CommandNode; | import com.mojang.brigadier.tree.CommandNode; | ||||||
| import com.mojang.brigadier.tree.LiteralCommandNode; | import com.mojang.brigadier.tree.LiteralCommandNode; | ||||||
|  | import dan200.computercraft.api.component.AdminComputer; | ||||||
| import dan200.computercraft.api.detail.BlockReference; | import dan200.computercraft.api.detail.BlockReference; | ||||||
| import dan200.computercraft.api.detail.VanillaDetailRegistries; | import dan200.computercraft.api.detail.VanillaDetailRegistries; | ||||||
| import dan200.computercraft.api.lua.*; | import dan200.computercraft.api.lua.*; | ||||||
| import dan200.computercraft.core.Logging; | import dan200.computercraft.core.Logging; | ||||||
| import dan200.computercraft.shared.computer.core.ServerComputer; |  | ||||||
| import dan200.computercraft.shared.util.NBTUtil; | import dan200.computercraft.shared.util.NBTUtil; | ||||||
| import net.minecraft.commands.CommandSource; | import net.minecraft.commands.CommandSource; | ||||||
| import net.minecraft.commands.CommandSourceStack; | import net.minecraft.commands.CommandSourceStack; | ||||||
| @@ -35,11 +35,13 @@ import java.util.*; | |||||||
| public class CommandAPI implements ILuaAPI { | public class CommandAPI implements ILuaAPI { | ||||||
|     private static final Logger LOG = LoggerFactory.getLogger(CommandAPI.class); |     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(); |     private final OutputReceiver receiver = new OutputReceiver(); | ||||||
| 
 | 
 | ||||||
|     public CommandAPI(ServerComputer computer) { |     public CommandAPI(IComputerSystem computer, AdminComputer admin) { | ||||||
|         this.computer = computer; |         this.computer = computer; | ||||||
|  |         this.admin = admin; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
| @@ -295,7 +297,7 @@ public class CommandAPI implements ILuaAPI { | |||||||
| 
 | 
 | ||||||
|         return new CommandSourceStack(receiver, |         return new CommandSourceStack(receiver, | ||||||
|             Vec3.atCenterOf(computer.getPosition()), Vec2.ZERO, |             Vec3.atCenterOf(computer.getPosition()), Vec2.ZERO, | ||||||
|             computer.getLevel(), 2, |             computer.getLevel(), admin.permissionLevel(), | ||||||
|             name, Component.literal(name), |             name, Component.literal(name), | ||||||
|             computer.getLevel().getServer(), null |             computer.getLevel().getServer(), null | ||||||
|         ); |         ); | ||||||
|   | |||||||
| @@ -39,7 +39,7 @@ import javax.annotation.Nullable; | |||||||
| import java.util.Objects; | import java.util.Objects; | ||||||
| import java.util.UUID; | 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_ID = "ComputerId"; | ||||||
|     private static final String NBT_LABEL = "Label"; |     private static final String NBT_LABEL = "Label"; | ||||||
|     private static final String NBT_ON = "On"; |     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); |         for (var dir : DirectionUtil.FACINGS) updateRedstoneTo(dir); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |  | ||||||
|     public final int getComputerID() { |     public final int getComputerID() { | ||||||
|         return computerID; |         return computerID; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |  | ||||||
|     public final @Nullable String getLabel() { |     public final @Nullable String getLabel() { | ||||||
|         return label; |         return label; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |  | ||||||
|     public final void setComputerID(int id) { |     public final void setComputerID(int id) { | ||||||
|         if (getLevel().isClientSide || computerID == id) return; |         if (getLevel().isClientSide || computerID == id) return; | ||||||
| 
 | 
 | ||||||
| @@ -344,7 +341,6 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements | |||||||
|         BlockEntityHelpers.updateBlock(this); |         BlockEntityHelpers.updateBlock(this); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |  | ||||||
|     public final void setLabel(@Nullable String label) { |     public final void setLabel(@Nullable String label) { | ||||||
|         if (getLevel().isClientSide || Objects.equals(this.label, label)) return; |         if (getLevel().isClientSide || Objects.equals(this.label, label)) return; | ||||||
| 
 | 
 | ||||||
| @@ -354,7 +350,6 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements | |||||||
|         BlockEntityHelpers.updateBlock(this); |         BlockEntityHelpers.updateBlock(this); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |  | ||||||
|     public ComputerFamily getFamily() { |     public ComputerFamily getFamily() { | ||||||
|         return family; |         return family; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ import dan200.computercraft.shared.computer.core.ComputerState; | |||||||
| import dan200.computercraft.shared.computer.core.ServerComputer; | import dan200.computercraft.shared.computer.core.ServerComputer; | ||||||
| import dan200.computercraft.shared.computer.inventory.ComputerMenuWithoutInventory; | import dan200.computercraft.shared.computer.inventory.ComputerMenuWithoutInventory; | ||||||
| import dan200.computercraft.shared.config.Config; | import dan200.computercraft.shared.config.Config; | ||||||
|  | import dan200.computercraft.shared.util.ComponentMap; | ||||||
| import net.minecraft.core.BlockPos; | import net.minecraft.core.BlockPos; | ||||||
| import net.minecraft.core.Direction; | import net.minecraft.core.Direction; | ||||||
| import net.minecraft.server.level.ServerLevel; | import net.minecraft.server.level.ServerLevel; | ||||||
| @@ -34,7 +35,8 @@ public class ComputerBlockEntity extends AbstractComputerBlockEntity { | |||||||
|     protected ServerComputer createComputer(int id) { |     protected ServerComputer createComputer(int id) { | ||||||
|         return new ServerComputer( |         return new ServerComputer( | ||||||
|             (ServerLevel) getLevel(), getBlockPos(), id, label, |             (ServerLevel) getLevel(), getBlockPos(), id, label, | ||||||
|             getFamily(), Config.computerTermWidth, Config.computerTermHeight |             getFamily(), Config.computerTermWidth, Config.computerTermHeight, | ||||||
|  |             ComponentMap.empty() | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -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(); |  | ||||||
| } |  | ||||||
| @@ -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); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -5,15 +5,16 @@ | |||||||
| package dan200.computercraft.shared.computer.core; | package dan200.computercraft.shared.computer.core; | ||||||
| 
 | 
 | ||||||
| import dan200.computercraft.api.ComputerCraftAPI; | 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.filesystem.WritableMount; | ||||||
| import dan200.computercraft.api.lua.ILuaAPI; |  | ||||||
| import dan200.computercraft.api.peripheral.IPeripheral; | import dan200.computercraft.api.peripheral.IPeripheral; | ||||||
| import dan200.computercraft.api.peripheral.WorkMonitor; | import dan200.computercraft.api.peripheral.WorkMonitor; | ||||||
| import dan200.computercraft.core.computer.Computer; | import dan200.computercraft.core.computer.Computer; | ||||||
| import dan200.computercraft.core.computer.ComputerEnvironment; | import dan200.computercraft.core.computer.ComputerEnvironment; | ||||||
| import dan200.computercraft.core.computer.ComputerSide; | import dan200.computercraft.core.computer.ComputerSide; | ||||||
| import dan200.computercraft.core.metrics.MetricsObserver; | 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.menu.ComputerMenu; | ||||||
| import dan200.computercraft.shared.computer.terminal.NetworkedTerminal; | import dan200.computercraft.shared.computer.terminal.NetworkedTerminal; | ||||||
| import dan200.computercraft.shared.computer.terminal.TerminalState; | 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.ClientNetworkContext; | ||||||
| import dan200.computercraft.shared.network.client.ComputerTerminalClientMessage; | import dan200.computercraft.shared.network.client.ComputerTerminalClientMessage; | ||||||
| import dan200.computercraft.shared.network.server.ServerNetworking; | import dan200.computercraft.shared.network.server.ServerNetworking; | ||||||
|  | import dan200.computercraft.shared.util.ComponentMap; | ||||||
| import net.minecraft.core.BlockPos; | import net.minecraft.core.BlockPos; | ||||||
| import net.minecraft.server.level.ServerLevel; | import net.minecraft.server.level.ServerLevel; | ||||||
| import net.minecraft.world.entity.player.Player; | import net.minecraft.world.entity.player.Player; | ||||||
| @@ -48,7 +50,8 @@ public class ServerComputer implements InputHandler, ComputerEnvironment { | |||||||
|     private int ticksSincePing; |     private int ticksSincePing; | ||||||
| 
 | 
 | ||||||
|     public ServerComputer( |     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.level = level; | ||||||
|         this.position = position; |         this.position = position; | ||||||
| @@ -58,10 +61,27 @@ public class ServerComputer implements InputHandler, ComputerEnvironment { | |||||||
|         terminal = new NetworkedTerminal(terminalWidth, terminalHeight, family != ComputerFamily.NORMAL, this::markTerminalChanged); |         terminal = new NetworkedTerminal(terminalWidth, terminalHeight, family != ComputerFamily.NORMAL, this::markTerminalChanged); | ||||||
|         metrics = context.metrics().createMetricObserver(this); |         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 = new Computer(context.computerContext(), this, terminal, computerID); | ||||||
|         computer.setLabel(label); |         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() { |     public ComputerFamily getFamily() { | ||||||
| @@ -211,10 +231,6 @@ public class ServerComputer implements InputHandler, ComputerEnvironment { | |||||||
|         computer.getEnvironment().setBundledRedstoneInput(side, combination); |         computer.getEnvironment().setBundledRedstoneInput(side, combination); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void addAPI(ILuaAPI api) { |  | ||||||
|         computer.addApi(api); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public void setPeripheral(ComputerSide side, @Nullable IPeripheral peripheral) { |     public void setPeripheral(ComputerSide side, @Nullable IPeripheral peripheral) { | ||||||
|         computer.getEnvironment().setPeripheral(side, peripheral); |         computer.getEnvironment().setPeripheral(side, peripheral); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -17,7 +17,6 @@ import dan200.computercraft.core.lua.ILuaMachine; | |||||||
| import dan200.computercraft.core.methods.MethodSupplier; | import dan200.computercraft.core.methods.MethodSupplier; | ||||||
| import dan200.computercraft.core.methods.PeripheralMethod; | import dan200.computercraft.core.methods.PeripheralMethod; | ||||||
| import dan200.computercraft.impl.AbstractComputerCraftAPI; | import dan200.computercraft.impl.AbstractComputerCraftAPI; | ||||||
| import dan200.computercraft.impl.ApiFactories; |  | ||||||
| import dan200.computercraft.impl.GenericSources; | import dan200.computercraft.impl.GenericSources; | ||||||
| import dan200.computercraft.shared.CommonHooks; | import dan200.computercraft.shared.CommonHooks; | ||||||
| import dan200.computercraft.shared.computer.metrics.GlobalMetrics; | import dan200.computercraft.shared.computer.metrics.GlobalMetrics; | ||||||
| @@ -74,7 +73,6 @@ public final class ServerContext { | |||||||
|             .computerThreads(ConfigSpec.computerThreads.get()) |             .computerThreads(ConfigSpec.computerThreads.get()) | ||||||
|             .mainThreadScheduler(mainThread) |             .mainThreadScheduler(mainThread) | ||||||
|             .luaFactory(luaMachine) |             .luaFactory(luaMachine) | ||||||
|             .apiFactories(ApiFactories.getAll()) |  | ||||||
|             .genericMethods(GenericSources.getAllMethods()) |             .genericMethods(GenericSources.getAllMethods()) | ||||||
|             .build(); |             .build(); | ||||||
|         idAssigner = new IDAssigner(storageDir.resolve("ids.json")); |         idAssigner = new IDAssigner(storageDir.resolve("ids.json")); | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ | |||||||
| package dan200.computercraft.shared.data; | package dan200.computercraft.shared.data; | ||||||
| 
 | 
 | ||||||
| import dan200.computercraft.shared.ModRegistry; | 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.LootContext; | ||||||
| import net.minecraft.world.level.storage.loot.parameters.LootContextParam; | import net.minecraft.world.level.storage.loot.parameters.LootContextParam; | ||||||
| import net.minecraft.world.level.storage.loot.parameters.LootContextParams; | import net.minecraft.world.level.storage.loot.parameters.LootContextParams; | ||||||
| @@ -27,7 +27,7 @@ public final class HasComputerIdLootCondition implements LootItemCondition { | |||||||
|     @Override |     @Override | ||||||
|     public boolean test(LootContext lootContext) { |     public boolean test(LootContext lootContext) { | ||||||
|         var tile = lootContext.getParamOrNull(LootContextParams.BLOCK_ENTITY); |         var tile = lootContext.getParamOrNull(LootContextParams.BLOCK_ENTITY); | ||||||
|         return tile instanceof IComputerBlockEntity computer && computer.getComputerID() >= 0; |         return tile instanceof AbstractComputerBlockEntity computer && computer.getComputerID() >= 0; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|   | |||||||
| @@ -0,0 +1,34 @@ | |||||||
|  | // SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers | ||||||
|  | // | ||||||
|  | // SPDX-License-Identifier: MPL-2.0 | ||||||
|  | 
 | ||||||
|  | package dan200.computercraft.shared.integration; | ||||||
|  | 
 | ||||||
|  | import com.simibubi.create.content.contraptions.BlockMovementChecks; | ||||||
|  | import com.simibubi.create.content.contraptions.BlockMovementChecks.CheckResult; | ||||||
|  | import dan200.computercraft.shared.peripheral.modem.wired.CableBlock; | ||||||
|  | import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemBlock; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Integration with Create. | ||||||
|  |  */ | ||||||
|  | public final class CreateIntegration { | ||||||
|  |     public static final String ID = "create"; | ||||||
|  | 
 | ||||||
|  |     private CreateIntegration() { | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static void setup() { | ||||||
|  |         // Allow modems to be treated as "attached" to their adjacent block. | ||||||
|  |         BlockMovementChecks.registerAttachedCheck((state, world, pos, direction) -> { | ||||||
|  |             var block = state.getBlock(); | ||||||
|  |             if (block instanceof WirelessModemBlock) { | ||||||
|  |                 return CheckResult.of(state.getValue(WirelessModemBlock.FACING) == direction); | ||||||
|  |             } else if (block instanceof CableBlock) { | ||||||
|  |                 return CheckResult.of(state.getValue(CableBlock.MODEM).getFacing() == direction); | ||||||
|  |             } else { | ||||||
|  |                 return CheckResult.PASS; | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -8,6 +8,7 @@ import net.minecraft.core.registries.Registries; | |||||||
| import net.minecraft.resources.ResourceLocation; | import net.minecraft.resources.ResourceLocation; | ||||||
| import net.minecraft.tags.TagKey; | import net.minecraft.tags.TagKey; | ||||||
| import net.minecraft.world.level.block.Block; | import net.minecraft.world.level.block.Block; | ||||||
|  | import net.minecraft.world.level.block.state.BlockState; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Tags defined by external mods. |  * 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. |          * 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) { |         private static TagKey<Block> make(String mod, String name) { | ||||||
|             return TagKey.create(Registries.BLOCK, ResourceLocation.fromNamespaceAndPath(mod, name)); |             return TagKey.create(Registries.BLOCK, ResourceLocation.fromNamespaceAndPath(mod, name)); | ||||||
|   | |||||||
| @@ -43,7 +43,7 @@ public record PocketComputerDataMessage( | |||||||
|         this( |         this( | ||||||
|             computer.getInstanceUUID(), |             computer.getInstanceUUID(), | ||||||
|             computer.getState(), |             computer.getState(), | ||||||
|             computer.getLight(), |             computer.getBrain().getLight(), | ||||||
|             sendTerminal ? Optional.of(computer.getTerminalState()) : Optional.empty() |             sendTerminal ? Optional.of(computer.getTerminalState()) : Optional.empty() | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -276,16 +276,18 @@ public abstract class SpeakerPeripheral implements IPeripheral { | |||||||
|      * Attempt to stream some audio data to the speaker. |      * Attempt to stream some audio data to the speaker. | ||||||
|      * <p> |      * <p> | ||||||
|      * This accepts a list of audio samples as amplitudes between -128 and 127. These are stored in an internal buffer |      * 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 |      * and played back at 48kHz. If this buffer is full, this function will return {@literal false}. Programs should | ||||||
|      * a [`speaker_audio_empty`] event before trying again. |      * wait for a [`speaker_audio_empty`] event before trying to play audio again. | ||||||
|      * <p> |      * <p> | ||||||
|      * > [!NOTE] |      * The speaker only buffers a single call to {@link #playAudio} at once. This means if you try to play a small | ||||||
|      * > 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 | ||||||
|      * > 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 | ||||||
|      * > (up to 128×1024), as this reduces the chances of audio stuttering or halting, especially when the server or |      * computer is lagging. | ||||||
|      * > computer is lagging. |  | ||||||
|      * <p> |      * <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 context The Lua context. | ||||||
|      * @param audio   The audio data to play. |      * @param audio   The audio data to play. | ||||||
|   | |||||||
| @@ -6,10 +6,10 @@ package dan200.computercraft.shared.pocket.apis; | |||||||
| 
 | 
 | ||||||
| import dan200.computercraft.api.lua.ILuaAPI; | import dan200.computercraft.api.lua.ILuaAPI; | ||||||
| import dan200.computercraft.api.lua.LuaFunction; | import dan200.computercraft.api.lua.LuaFunction; | ||||||
|  | import dan200.computercraft.api.pocket.IPocketAccess; | ||||||
| import dan200.computercraft.api.pocket.IPocketUpgrade; | import dan200.computercraft.api.pocket.IPocketUpgrade; | ||||||
| import dan200.computercraft.api.upgrades.UpgradeData; | import dan200.computercraft.api.upgrades.UpgradeData; | ||||||
| import dan200.computercraft.impl.PocketUpgrades; | import dan200.computercraft.impl.PocketUpgrades; | ||||||
| import dan200.computercraft.shared.pocket.core.PocketServerComputer; |  | ||||||
| import net.minecraft.core.NonNullList; | import net.minecraft.core.NonNullList; | ||||||
| import net.minecraft.world.entity.player.Player; | import net.minecraft.world.entity.player.Player; | ||||||
| import net.minecraft.world.item.ItemStack; | import net.minecraft.world.item.ItemStack; | ||||||
| @@ -34,10 +34,10 @@ import java.util.Objects; | |||||||
|  * @cc.module pocket |  * @cc.module pocket | ||||||
|  */ |  */ | ||||||
| public class PocketAPI implements ILuaAPI { | public class PocketAPI implements ILuaAPI { | ||||||
|     private final PocketServerComputer computer; |     private final IPocketAccess pocket; | ||||||
| 
 | 
 | ||||||
|     public PocketAPI(PocketServerComputer computer) { |     public PocketAPI(IPocketAccess pocket) { | ||||||
|         this.computer = computer; |         this.pocket = pocket; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
| @@ -56,10 +56,10 @@ public class PocketAPI implements ILuaAPI { | |||||||
|      */ |      */ | ||||||
|     @LuaFunction(mainThread = true) |     @LuaFunction(mainThread = true) | ||||||
|     public final Object[] equipBack() { |     public final Object[] equipBack() { | ||||||
|         var entity = computer.getEntity(); |         var entity = pocket.getEntity(); | ||||||
|         if (!(entity instanceof Player player)) return new Object[]{ false, "Cannot find player" }; |         if (!(entity instanceof Player player)) return new Object[]{ false, "Cannot find player" }; | ||||||
|         var inventory = player.getInventory(); |         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 |         // 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. |         // 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()); |         if (previousUpgrade != null) storeItem(player, previousUpgrade.getUpgradeItem()); | ||||||
| 
 | 
 | ||||||
|         // Set the new upgrade |         // Set the new upgrade | ||||||
|         computer.setUpgrade(newUpgrade); |         pocket.setUpgrade(newUpgrade); | ||||||
| 
 | 
 | ||||||
|         return new Object[]{ true }; |         return new Object[]{ true }; | ||||||
|     } |     } | ||||||
| @@ -87,13 +87,13 @@ public class PocketAPI implements ILuaAPI { | |||||||
|      */ |      */ | ||||||
|     @LuaFunction(mainThread = true) |     @LuaFunction(mainThread = true) | ||||||
|     public final Object[] unequipBack() { |     public final Object[] unequipBack() { | ||||||
|         var entity = computer.getEntity(); |         var entity = pocket.getEntity(); | ||||||
|         if (!(entity instanceof Player player)) return new Object[]{ false, "Cannot find player" }; |         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" }; |         if (previousUpgrade == null) return new Object[]{ false, "Nothing to unequip" }; | ||||||
| 
 | 
 | ||||||
|         computer.setUpgrade(null); |         pocket.setUpgrade(null); | ||||||
| 
 | 
 | ||||||
|         storeItem(player, previousUpgrade.getUpgradeItem()); |         storeItem(player, previousUpgrade.getUpgradeItem()); | ||||||
| 
 | 
 | ||||||
| @@ -111,7 +111,7 @@ public class PocketAPI implements ILuaAPI { | |||||||
|         for (var i = 0; i < inv.size(); i++) { |         for (var i = 0; i < inv.size(); i++) { | ||||||
|             var invStack = inv.get((i + start) % inv.size()); |             var invStack = inv.get((i + start) % inv.size()); | ||||||
|             if (!invStack.isEmpty()) { |             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)) { |                 if (newUpgrade != null && !Objects.equals(newUpgrade, previous)) { | ||||||
|                     // Consume an item from this stack and exit the loop |                     // Consume an item from this stack and exit the loop | ||||||
|   | |||||||
| @@ -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(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,115 @@ | |||||||
|  | // SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers | ||||||
|  | // | ||||||
|  | // SPDX-License-Identifier: MPL-2.0 | ||||||
|  | 
 | ||||||
|  | package dan200.computercraft.shared.pocket.core; | ||||||
|  | 
 | ||||||
|  | import dan200.computercraft.shared.computer.core.ServerComputer; | ||||||
|  | import dan200.computercraft.shared.pocket.items.PocketComputerItem; | ||||||
|  | import net.minecraft.core.BlockPos; | ||||||
|  | import net.minecraft.server.level.ServerLevel; | ||||||
|  | import net.minecraft.server.level.ServerPlayer; | ||||||
|  | import net.minecraft.world.entity.Entity; | ||||||
|  | import net.minecraft.world.entity.item.ItemEntity; | ||||||
|  | import net.minecraft.world.phys.Vec3; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * An object that holds a pocket computer item. | ||||||
|  |  */ | ||||||
|  | public sealed interface PocketHolder permits PocketHolder.EntityHolder { | ||||||
|  |     /** | ||||||
|  |      * The level this holder is in. | ||||||
|  |      * | ||||||
|  |      * @return The holder's level. | ||||||
|  |      */ | ||||||
|  |     ServerLevel level(); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * The position of this holder. | ||||||
|  |      * | ||||||
|  |      * @return The position of this holder. | ||||||
|  |      */ | ||||||
|  |     Vec3 pos(); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * The block position of this holder. | ||||||
|  |      * | ||||||
|  |      * @return The position of this holder. | ||||||
|  |      */ | ||||||
|  |     BlockPos blockPos(); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Determine if this holder is still valid for a particular computer. | ||||||
|  |      * | ||||||
|  |      * @param computer The current computer. | ||||||
|  |      * @return Whether this holder is valid. | ||||||
|  |      */ | ||||||
|  |     boolean isValid(ServerComputer computer); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Mark the pocket computer item as having changed. | ||||||
|  |      */ | ||||||
|  |     void setChanged(); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * An {@link Entity} holding a pocket computer. | ||||||
|  |      */ | ||||||
|  |     sealed interface EntityHolder extends PocketHolder permits PocketHolder.PlayerHolder, PocketHolder.ItemEntityHolder { | ||||||
|  |         /** | ||||||
|  |          * Get the entity holding this pocket computer. | ||||||
|  |          * | ||||||
|  |          * @return The holding entity. | ||||||
|  |          */ | ||||||
|  |         Entity entity(); | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         default ServerLevel level() { | ||||||
|  |             return (ServerLevel) entity().level(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         default Vec3 pos() { | ||||||
|  |             return entity().getEyePosition(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         default BlockPos blockPos() { | ||||||
|  |             return entity().blockPosition(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * A pocket computer in a player's slot. | ||||||
|  |      * | ||||||
|  |      * @param entity The current player. | ||||||
|  |      * @param slot   The slot the pocket computer is in. | ||||||
|  |      */ | ||||||
|  |     record PlayerHolder(ServerPlayer entity, int slot) implements EntityHolder { | ||||||
|  |         @Override | ||||||
|  |         public boolean isValid(ServerComputer computer) { | ||||||
|  |             return entity().isAlive() && PocketComputerItem.isServerComputer(computer, entity().getInventory().getItem(this.slot())); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         public void setChanged() { | ||||||
|  |             entity.getInventory().setChanged(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * A pocket computer in an {@link ItemEntity}. | ||||||
|  |      * | ||||||
|  |      * @param entity The item entity. | ||||||
|  |      */ | ||||||
|  |     record ItemEntityHolder(ItemEntity entity) implements EntityHolder { | ||||||
|  |         @Override | ||||||
|  |         public boolean isValid(ServerComputer computer) { | ||||||
|  |             return entity().isAlive() && PocketComputerItem.isServerComputer(computer, this.entity().getItem()); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         public void setChanged() { | ||||||
|  |             entity.setItem(entity.getItem().copy()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -4,11 +4,7 @@ | |||||||
| 
 | 
 | ||||||
| package dan200.computercraft.shared.pocket.core; | package dan200.computercraft.shared.pocket.core; | ||||||
| 
 | 
 | ||||||
| import dan200.computercraft.api.pocket.IPocketAccess; | import dan200.computercraft.api.component.ComputerComponents; | ||||||
| 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.shared.computer.core.ComputerFamily; | import dan200.computercraft.shared.computer.core.ComputerFamily; | ||||||
| import dan200.computercraft.shared.computer.core.ComputerState; | import dan200.computercraft.shared.computer.core.ComputerState; | ||||||
| import dan200.computercraft.shared.computer.core.ServerComputer; | 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.client.PocketComputerDeletedClientMessage; | ||||||
| import dan200.computercraft.shared.network.server.ServerNetworking; | import dan200.computercraft.shared.network.server.ServerNetworking; | ||||||
| import dan200.computercraft.shared.pocket.items.PocketComputerItem; | import dan200.computercraft.shared.pocket.items.PocketComputerItem; | ||||||
| import net.minecraft.core.BlockPos; | import dan200.computercraft.shared.util.ComponentMap; | ||||||
| import net.minecraft.core.component.DataComponentPatch; |  | ||||||
| import net.minecraft.core.component.DataComponents; |  | ||||||
| import net.minecraft.server.level.ServerLevel; |  | ||||||
| import net.minecraft.server.level.ServerPlayer; | import net.minecraft.server.level.ServerPlayer; | ||||||
| import net.minecraft.world.entity.Entity; | import net.minecraft.world.level.ChunkPos; | ||||||
| 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 javax.annotation.Nullable; | import javax.annotation.Nullable; | ||||||
| import java.util.ArrayList; |  | ||||||
| import java.util.HashSet; |  | ||||||
| import java.util.List; |  | ||||||
| import java.util.Set; | import java.util.Set; | ||||||
| 
 | 
 | ||||||
| public class PocketServerComputer extends ServerComputer implements IPocketAccess { | /** | ||||||
|     private @Nullable IPocketUpgrade upgrade; |  * A {@link ServerComputer}-subclass for {@linkplain PocketComputerItem pocket computers}. | ||||||
|     private @Nullable Entity entity; |  * <p> | ||||||
|     private ItemStack stack = ItemStack.EMPTY; |  * This extends default {@link ServerComputer} behaviour by also syncing pocket computer state to nearby players, and | ||||||
| 
 |  * syncing the terminal to the current player. | ||||||
|     private int lightColour = -1; |  * <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. |     // The state the previous tick, used to determine if the state needs to be sent to the client. | ||||||
|     private int oldLightColour = -1; |     private int oldLightColour = -1; | ||||||
|     private @Nullable ComputerState oldComputerState; |     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) { |     PocketServerComputer(PocketBrain brain, PocketHolder holder, int computerID, @Nullable String label, ComputerFamily family) { | ||||||
|         super(world, position, computerID, label, family, Config.pocketTermWidth, Config.pocketTermHeight); |         super( | ||||||
|  |             holder.level(), holder.blockPos(), computerID, label, family, Config.pocketTermWidth, Config.pocketTermHeight, | ||||||
|  |             ComponentMap.builder().add(ComputerComponents.POCKET, brain).build() | ||||||
|  |         ); | ||||||
|  |         this.brain = brain; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Nullable |     public PocketBrain getBrain() { | ||||||
|     @Override |         return brain; | ||||||
|     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(); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     protected void tickServer() { |     protected void tickServer() { | ||||||
|         super.tickServer(); |         super.tickServer(); | ||||||
| 
 | 
 | ||||||
|         // Find any players which have gone missing and remove them from the tracking list. |         // Get the new set of players tracking the current position. | ||||||
|         tracking.removeIf(player -> !player.isAlive() || player.level() != getLevel()); |         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. |         // And now find any new players, add them to the tracking list, and broadcast state where appropriate. | ||||||
|         var state = getState(); |         var state = getState(); | ||||||
|         if (oldLightColour != lightColour || oldComputerState != state) { |         var light = brain.getLight(); | ||||||
|  |         if (oldLightColour != light || oldComputerState != state) { | ||||||
|             oldComputerState = state; |             oldComputerState = state; | ||||||
|             oldLightColour = lightColour; |             oldLightColour = light; | ||||||
| 
 | 
 | ||||||
|             // Broadcast the state to all players |             // Broadcast the state to all players | ||||||
|             tracking.addAll(getLevel().players()); |             ServerNetworking.sendToPlayers(new PocketComputerDataMessage(this, false), newTracking); | ||||||
|             ServerNetworking.sendToPlayers(new PocketComputerDataMessage(this, false), tracking); |         } else if (trackingChanged) { | ||||||
|         } else { |  | ||||||
|             // Broadcast the state to new players. |             // Broadcast the state to new players. | ||||||
|             List<ServerPlayer> added = new ArrayList<>(); |             var added = newTracking.stream().filter(x -> !tracking.contains(x)).toList(); | ||||||
|             for (var player : getLevel().players()) { |  | ||||||
|                 if (tracking.add(player)) added.add(player); |  | ||||||
|             } |  | ||||||
|             if (!added.isEmpty()) { |             if (!added.isEmpty()) { | ||||||
|                 ServerNetworking.sendToPlayers(new PocketComputerDataMessage(this, false), added); |                 ServerNetworking.sendToPlayers(new PocketComputerDataMessage(this, false), added); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|  |         if (trackingChanged) tracking = Set.copyOf(newTracking); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     protected void onTerminalChanged() { |     protected void onTerminalChanged() { | ||||||
|         super.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. |             // Broadcast the terminal to the current player. | ||||||
|             ServerNetworking.sendToPlayer(new PocketComputerDataMessage(this, true), player); |             ServerNetworking.sendToPlayer(new PocketComputerDataMessage(this, true), holder.entity()); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -14,22 +14,26 @@ import dan200.computercraft.core.computer.ComputerSide; | |||||||
| import dan200.computercraft.impl.PocketUpgrades; | import dan200.computercraft.impl.PocketUpgrades; | ||||||
| import dan200.computercraft.shared.ModRegistry; | import dan200.computercraft.shared.ModRegistry; | ||||||
| import dan200.computercraft.shared.computer.core.ComputerFamily; | 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.core.ServerContext; | ||||||
| import dan200.computercraft.shared.computer.items.ServerComputerReference; | import dan200.computercraft.shared.computer.items.ServerComputerReference; | ||||||
| import dan200.computercraft.shared.config.Config; | import dan200.computercraft.shared.config.Config; | ||||||
| import dan200.computercraft.shared.network.container.ComputerContainerData; | 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.core.PocketServerComputer; | ||||||
| import dan200.computercraft.shared.pocket.inventory.PocketComputerMenuProvider; | import dan200.computercraft.shared.pocket.inventory.PocketComputerMenuProvider; | ||||||
| import dan200.computercraft.shared.util.DataComponentUtil; | import dan200.computercraft.shared.util.DataComponentUtil; | ||||||
| import dan200.computercraft.shared.util.IDAssigner; | import dan200.computercraft.shared.util.IDAssigner; | ||||||
|  | import dan200.computercraft.shared.util.InventoryUtil; | ||||||
| import dan200.computercraft.shared.util.NonNegativeId; | import dan200.computercraft.shared.util.NonNegativeId; | ||||||
| import net.minecraft.ChatFormatting; | import net.minecraft.ChatFormatting; | ||||||
| import net.minecraft.core.HolderLookup; | import net.minecraft.core.HolderLookup; | ||||||
| import net.minecraft.network.chat.Component; | import net.minecraft.network.chat.Component; | ||||||
| import net.minecraft.server.MinecraftServer; | import net.minecraft.server.MinecraftServer; | ||||||
| import net.minecraft.server.level.ServerLevel; | 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.InteractionHand; | ||||||
| import net.minecraft.world.InteractionResult; | import net.minecraft.world.InteractionResult; | ||||||
| import net.minecraft.world.InteractionResultHolder; | import net.minecraft.world.InteractionResultHolder; | ||||||
| @@ -53,12 +57,33 @@ public class PocketComputerItem extends Item implements IMedia { | |||||||
|         this.family = family; |         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 |         // Sync label | ||||||
|         var label = computer.getLabel(); |         var label = computer.getLabel(); | ||||||
| @@ -73,21 +98,20 @@ public class PocketComputerItem extends Item implements IMedia { | |||||||
|             stack.set(ModRegistry.DataComponents.ON.get(), on); |             stack.set(ModRegistry.DataComponents.ON.get(), on); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Update pocket upgrade |  | ||||||
|         if (upgrade != null) upgrade.update(computer, computer.getPeripheral(ComputerSide.BACK)); |  | ||||||
| 
 |  | ||||||
|         return changed; |         return changed; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void inventoryTick(ItemStack stack, Level world, Entity entity, int slotNum, boolean selected) { |     public void inventoryTick(ItemStack stack, Level world, Entity entity, int slotNum, boolean selected) { | ||||||
|         if (world.isClientSide) return; |         // This (in vanilla at least) is only called for players. Don't bother to handle other entities. | ||||||
|         Container inventory = entity instanceof Player player ? player.getInventory() : null; |         if (world.isClientSide || !(entity instanceof ServerPlayer player)) return; | ||||||
|         var computer = createServerComputer((ServerLevel) world, entity, inventory, stack); |  | ||||||
|         computer.keepAlive(); |  | ||||||
| 
 | 
 | ||||||
|         var changed = tick(stack, entity, computer); |         // If we're in the inventory, create a computer and keep it alive. | ||||||
|         if (changed && inventory != null) inventory.setChanged(); |         var holder = new PocketHolder.PlayerHolder(player, slotNum); | ||||||
|  |         var brain = getOrCreateBrain((ServerLevel) world, holder, stack); | ||||||
|  |         brain.computer().keepAlive(); | ||||||
|  | 
 | ||||||
|  |         tick(stack, holder, brain); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @ForgeOverride |     @ForgeOverride | ||||||
| @@ -95,8 +119,11 @@ public class PocketComputerItem extends Item implements IMedia { | |||||||
|         var level = entity.level(); |         var level = entity.level(); | ||||||
|         if (level.isClientSide || level.getServer() == null) return false; |         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); |         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; |         return false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @@ -104,14 +131,18 @@ public class PocketComputerItem extends Item implements IMedia { | |||||||
|     public InteractionResultHolder<ItemStack> use(Level world, Player player, InteractionHand hand) { |     public InteractionResultHolder<ItemStack> use(Level world, Player player, InteractionHand hand) { | ||||||
|         var stack = player.getItemInHand(hand); |         var stack = player.getItemInHand(hand); | ||||||
|         if (!world.isClientSide) { |         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(); |             computer.turnOn(); | ||||||
| 
 | 
 | ||||||
|             var stop = false; |             var stop = false; | ||||||
|             var upgrade = getUpgrade(stack); |             var upgrade = getUpgrade(stack); | ||||||
|             if (upgrade != null) { |             if (upgrade != null) { | ||||||
|                 computer.updateValues(player, stack, upgrade); |                 brain.updateHolder(holder); | ||||||
|                 stop = upgrade.onRightClick(world, computer, computer.getPeripheral(ComputerSide.BACK)); |                 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) { |             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 registry = ServerContext.get(level.getServer()).registry(); | ||||||
|         var computer = (PocketServerComputer) ServerComputerReference.get(stack, registry); |         { | ||||||
|         if (computer == null) { |             var computer = getServerComputer(registry, stack); | ||||||
|             var computerID = NonNegativeId.getOrCreate(level.getServer(), stack, ModRegistry.DataComponents.COMPUTER_ID.get(), IDAssigner.COMPUTER); |             if (computer != null) return computer.getBrain(); | ||||||
|             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(); |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         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 |     @Nullable | ||||||
|     public static PocketServerComputer getServerComputer(MinecraftServer server, ItemStack stack) { |     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() { |     public ComputerFamily getFamily() { | ||||||
|   | |||||||
| @@ -41,8 +41,6 @@ public class PocketModem extends AbstractPocketUpgrade { | |||||||
|     public void update(IPocketAccess access, @Nullable IPeripheral peripheral) { |     public void update(IPocketAccess access, @Nullable IPeripheral peripheral) { | ||||||
|         if (!(peripheral instanceof PocketModemPeripheral modem)) return; |         if (!(peripheral instanceof PocketModemPeripheral modem)) return; | ||||||
| 
 | 
 | ||||||
|         modem.setLocation(access); |  | ||||||
| 
 |  | ||||||
|         var state = modem.getModemState(); |         var state = modem.getModemState(); | ||||||
|         if (state.pollChanged()) access.setLight(state.isOpen() ? 0xBA0000 : -1); |         if (state.pollChanged()) access.setLight(state.isOpen() ? 0xBA0000 : -1); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -14,31 +14,21 @@ import net.minecraft.world.phys.Vec3; | |||||||
| import javax.annotation.Nullable; | import javax.annotation.Nullable; | ||||||
| 
 | 
 | ||||||
| public class PocketModemPeripheral extends WirelessModemPeripheral { | public class PocketModemPeripheral extends WirelessModemPeripheral { | ||||||
|     private @Nullable Level level = null; |     private final IPocketAccess access; | ||||||
|     private Vec3 position = Vec3.ZERO; |  | ||||||
| 
 | 
 | ||||||
|     public PocketModemPeripheral(boolean advanced, IPocketAccess access) { |     public PocketModemPeripheral(boolean advanced, IPocketAccess access) { | ||||||
|         super(new ModemState(), advanced); |         super(new ModemState(), advanced); | ||||||
|         setLocation(access); |         this.access = access; | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     void setLocation(IPocketAccess access) { |  | ||||||
|         var entity = access.getEntity(); |  | ||||||
|         if (entity != null) { |  | ||||||
|             level = entity.level(); |  | ||||||
|             position = entity.getEyePosition(1); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public Level getLevel() { |     public Level getLevel() { | ||||||
|         if (level == null) throw new IllegalStateException("Using modem before position has been defined"); |         return access.getLevel(); | ||||||
|         return level; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public Vec3 getPosition() { |     public Vec3 getPosition() { | ||||||
|         return position; |         return access.getPosition(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|   | |||||||
| @@ -8,15 +8,11 @@ import dan200.computercraft.api.peripheral.IPeripheral; | |||||||
| import dan200.computercraft.api.pocket.IPocketAccess; | import dan200.computercraft.api.pocket.IPocketAccess; | ||||||
| import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition; | import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition; | ||||||
| import dan200.computercraft.shared.peripheral.speaker.UpgradeSpeakerPeripheral; | import dan200.computercraft.shared.peripheral.speaker.UpgradeSpeakerPeripheral; | ||||||
| import net.minecraft.world.level.Level; |  | ||||||
| import net.minecraft.world.phys.Vec3; |  | ||||||
| 
 | 
 | ||||||
| import javax.annotation.Nullable; | import javax.annotation.Nullable; | ||||||
| 
 | 
 | ||||||
| public class PocketSpeakerPeripheral extends UpgradeSpeakerPeripheral { | public class PocketSpeakerPeripheral extends UpgradeSpeakerPeripheral { | ||||||
|     private final IPocketAccess access; |     private final IPocketAccess access; | ||||||
|     private @Nullable Level level; |  | ||||||
|     private Vec3 position = Vec3.ZERO; |  | ||||||
| 
 | 
 | ||||||
|     public PocketSpeakerPeripheral(IPocketAccess access) { |     public PocketSpeakerPeripheral(IPocketAccess access) { | ||||||
|         this.access = access; |         this.access = access; | ||||||
| @@ -25,7 +21,7 @@ public class PocketSpeakerPeripheral extends UpgradeSpeakerPeripheral { | |||||||
|     @Override |     @Override | ||||||
|     public SpeakerPosition getPosition() { |     public SpeakerPosition getPosition() { | ||||||
|         var entity = access.getEntity(); |         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 |     @Override | ||||||
| @@ -35,12 +31,6 @@ public class PocketSpeakerPeripheral extends UpgradeSpeakerPeripheral { | |||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void update() { |     public void update() { | ||||||
|         var entity = access.getEntity(); |  | ||||||
|         if (entity != null) { |  | ||||||
|             level = entity.level(); |  | ||||||
|             position = entity.position(); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         super.update(); |         super.update(); | ||||||
| 
 | 
 | ||||||
|         access.setLight(madeSound() ? 0x3320fc : -1); |         access.setLight(madeSound() ? 0x3320fc : -1); | ||||||
|   | |||||||
| @@ -11,7 +11,6 @@ import dan200.computercraft.api.turtle.TurtleCommandResult; | |||||||
| import dan200.computercraft.api.turtle.TurtleSide; | import dan200.computercraft.api.turtle.TurtleSide; | ||||||
| import dan200.computercraft.core.metrics.Metrics; | import dan200.computercraft.core.metrics.Metrics; | ||||||
| import dan200.computercraft.core.metrics.MetricsObserver; | 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.peripheral.generic.methods.AbstractInventoryMethods; | ||||||
| import dan200.computercraft.shared.turtle.core.*; | import dan200.computercraft.shared.turtle.core.*; | ||||||
| 
 | 
 | ||||||
| @@ -68,8 +67,8 @@ public class TurtleAPI implements ILuaAPI { | |||||||
|     private final MetricsObserver metrics; |     private final MetricsObserver metrics; | ||||||
|     private final TurtleAccessInternal turtle; |     private final TurtleAccessInternal turtle; | ||||||
| 
 | 
 | ||||||
|     public TurtleAPI(ServerComputer computer, TurtleAccessInternal turtle) { |     public TurtleAPI(MetricsObserver metrics, TurtleAccessInternal turtle) { | ||||||
|         this.metrics = computer.getMetrics(); |         this.metrics = metrics; | ||||||
|         this.turtle = turtle; |         this.turtle = turtle; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ | |||||||
| package dan200.computercraft.shared.turtle.blocks; | package dan200.computercraft.shared.turtle.blocks; | ||||||
| 
 | 
 | ||||||
| import com.mojang.authlib.GameProfile; | import com.mojang.authlib.GameProfile; | ||||||
|  | import dan200.computercraft.api.component.ComputerComponents; | ||||||
| import dan200.computercraft.api.peripheral.IPeripheral; | import dan200.computercraft.api.peripheral.IPeripheral; | ||||||
| import dan200.computercraft.api.turtle.ITurtleAccess; | import dan200.computercraft.api.turtle.ITurtleAccess; | ||||||
| import dan200.computercraft.api.turtle.ITurtleUpgrade; | import dan200.computercraft.api.turtle.ITurtleUpgrade; | ||||||
| @@ -21,9 +22,9 @@ import dan200.computercraft.shared.config.Config; | |||||||
| import dan200.computercraft.shared.container.BasicContainer; | import dan200.computercraft.shared.container.BasicContainer; | ||||||
| import dan200.computercraft.shared.platform.PlatformHelper; | import dan200.computercraft.shared.platform.PlatformHelper; | ||||||
| import dan200.computercraft.shared.turtle.TurtleOverlay; | import dan200.computercraft.shared.turtle.TurtleOverlay; | ||||||
| import dan200.computercraft.shared.turtle.apis.TurtleAPI; |  | ||||||
| import dan200.computercraft.shared.turtle.core.TurtleBrain; | import dan200.computercraft.shared.turtle.core.TurtleBrain; | ||||||
| import dan200.computercraft.shared.turtle.inventory.TurtleMenu; | import dan200.computercraft.shared.turtle.inventory.TurtleMenu; | ||||||
|  | import dan200.computercraft.shared.util.ComponentMap; | ||||||
| import net.minecraft.core.BlockPos; | import net.minecraft.core.BlockPos; | ||||||
| import net.minecraft.core.Direction; | import net.minecraft.core.Direction; | ||||||
| import net.minecraft.core.HolderLookup; | import net.minecraft.core.HolderLookup; | ||||||
| @@ -81,10 +82,9 @@ public class TurtleBlockEntity extends AbstractComputerBlockEntity implements Ba | |||||||
|     protected ServerComputer createComputer(int id) { |     protected ServerComputer createComputer(int id) { | ||||||
|         var computer = new ServerComputer( |         var computer = new ServerComputer( | ||||||
|             (ServerLevel) getLevel(), getBlockPos(), id, label, |             (ServerLevel) getLevel(), getBlockPos(), id, label, | ||||||
|             getFamily(), Config.turtleTermWidth, |             getFamily(), Config.turtleTermWidth, Config.turtleTermHeight, | ||||||
|             Config.turtleTermHeight |             ComponentMap.builder().add(ComputerComponents.TURTLE, brain).build() | ||||||
|         ); |         ); | ||||||
|         computer.addAPI(new TurtleAPI(computer, brain)); |  | ||||||
|         brain.setupComputer(computer); |         brain.setupComputer(computer); | ||||||
|         return computer; |         return computer; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -0,0 +1,66 @@ | |||||||
|  | // SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers | ||||||
|  | // | ||||||
|  | // SPDX-License-Identifier: MPL-2.0 | ||||||
|  | 
 | ||||||
|  | package dan200.computercraft.shared.util; | ||||||
|  | 
 | ||||||
|  | import dan200.computercraft.api.component.ComputerComponent; | ||||||
|  | import dan200.computercraft.core.metrics.MetricsObserver; | ||||||
|  | 
 | ||||||
|  | import javax.annotation.Nullable; | ||||||
|  | import java.util.HashMap; | ||||||
|  | import java.util.Map; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * An immutable map of components. | ||||||
|  |  */ | ||||||
|  | public final class ComponentMap { | ||||||
|  |     public static final ComputerComponent<MetricsObserver> METRICS = ComputerComponent.create("computercraft", "metrics"); | ||||||
|  | 
 | ||||||
|  |     private static final ComponentMap EMPTY = new ComponentMap(Map.of()); | ||||||
|  | 
 | ||||||
|  |     private final Map<ComputerComponent<?>, Object> components; | ||||||
|  | 
 | ||||||
|  |     private ComponentMap(Map<ComputerComponent<?>, Object> components) { | ||||||
|  |         this.components = components; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @SuppressWarnings("unchecked") | ||||||
|  |     public <T> @Nullable T get(ComputerComponent<T> component) { | ||||||
|  |         return (T) components.get(component); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static ComponentMap empty() { | ||||||
|  |         return EMPTY; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static Builder builder() { | ||||||
|  |         return new Builder(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static final class Builder { | ||||||
|  |         private final Map<ComputerComponent<?>, Object> components = new HashMap<>(); | ||||||
|  | 
 | ||||||
|  |         private Builder() { | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public <T> Builder add(ComputerComponent<T> component, T value) { | ||||||
|  |             addImpl(component, value); | ||||||
|  |             return this; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public Builder add(ComponentMap components) { | ||||||
|  |             for (var component : components.components.entrySet()) addImpl(component.getKey(), component.getValue()); | ||||||
|  |             return this; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private void addImpl(ComputerComponent<?> component, Object value) { | ||||||
|  |             if (components.containsKey(component)) throw new IllegalArgumentException(component + " is already set"); | ||||||
|  |             components.put(component, value); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public ComponentMap build() { | ||||||
|  |             return new ComponentMap(Map.copyOf(components)); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -8,6 +8,9 @@ import net.minecraft.core.BlockPos; | |||||||
| import net.minecraft.core.Direction; | import net.minecraft.core.Direction; | ||||||
| import net.minecraft.server.level.ServerLevel; | import net.minecraft.server.level.ServerLevel; | ||||||
| import net.minecraft.world.Container; | 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.item.ItemStack; | ||||||
| import net.minecraft.world.phys.EntityHitResult; | import net.minecraft.world.phys.EntityHitResult; | ||||||
| import net.minecraft.world.phys.Vec3; | import net.minecraft.world.phys.Vec3; | ||||||
| @@ -18,6 +21,20 @@ public final class InventoryUtil { | |||||||
|     private 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) { |     public static @Nullable Container getEntityContainer(ServerLevel level, BlockPos pos, Direction side) { | ||||||
|         var vecStart = new Vec3( |         var vecStart = new Vec3( | ||||||
|             pos.getX() + 0.5 + 0.6 * side.getStepX(), |             pos.getX() + 0.5 + 0.6 * side.getStepX(), | ||||||
|   | |||||||
| @@ -29,6 +29,7 @@ import dan200.computercraft.shared.util.WaterloggableHelpers | |||||||
| import dan200.computercraft.test.core.assertArrayEquals | import dan200.computercraft.test.core.assertArrayEquals | ||||||
| import dan200.computercraft.test.core.computer.LuaTaskContext | import dan200.computercraft.test.core.computer.LuaTaskContext | ||||||
| import dan200.computercraft.test.core.computer.getApi | import dan200.computercraft.test.core.computer.getApi | ||||||
|  | import dan200.computercraft.test.shared.ItemStackMatcher.isStack | ||||||
| import net.minecraft.core.BlockPos | import net.minecraft.core.BlockPos | ||||||
| import net.minecraft.core.registries.Registries | import net.minecraft.core.registries.Registries | ||||||
| import net.minecraft.gametest.framework.GameTest | import net.minecraft.gametest.framework.GameTest | ||||||
| @@ -44,8 +45,7 @@ import net.minecraft.world.level.block.FenceBlock | |||||||
| import net.minecraft.world.level.block.entity.BlockEntityType | import net.minecraft.world.level.block.entity.BlockEntityType | ||||||
| import net.minecraft.world.level.block.state.properties.BlockStateProperties | import net.minecraft.world.level.block.state.properties.BlockStateProperties | ||||||
| import org.hamcrest.MatcherAssert.assertThat | import org.hamcrest.MatcherAssert.assertThat | ||||||
| import org.hamcrest.Matchers.array | import org.hamcrest.Matchers.* | ||||||
| import org.hamcrest.Matchers.instanceOf |  | ||||||
| import org.junit.jupiter.api.Assertions.assertEquals | import org.junit.jupiter.api.Assertions.assertEquals | ||||||
| import org.junit.jupiter.api.Assertions.assertNotEquals | import org.junit.jupiter.api.Assertions.assertNotEquals | ||||||
| import java.util.* | import java.util.* | ||||||
| @@ -693,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. |      * Render turtles as an item. | ||||||
|      */ |      */ | ||||||
|   | |||||||
| @@ -179,7 +179,6 @@ fun <T : Comparable<T>> GameTestHelper.assertBlockHas(pos: BlockPos, property: P | |||||||
| fun GameTestHelper.getContainerAt(pos: BlockPos): Container = | fun GameTestHelper.getContainerAt(pos: BlockPos): Container = | ||||||
|     when (val container: BlockEntity = getBlockEntity(pos)) { |     when (val container: BlockEntity = getBlockEntity(pos)) { | ||||||
|         is Container -> container |         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) |         else -> failVerbose("Expected a container at $pos, found ${getName(container.type)}", pos) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|   | |||||||
							
								
								
									
										137
									
								
								projects/common/src/testMod/resources/data/cctest/structures/turtle_test.craft.snbt
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								projects/common/src/testMod/resources/data/cctest/structures/turtle_test.craft.snbt
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,137 @@ | |||||||
|  | { | ||||||
|  |     DataVersion: 3465, | ||||||
|  |     size: [5, 5, 5], | ||||||
|  |     data: [ | ||||||
|  |         {pos: [0, 0, 0], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [0, 0, 1], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [0, 0, 2], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [0, 0, 3], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [0, 0, 4], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [1, 0, 0], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [1, 0, 1], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [1, 0, 2], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [1, 0, 3], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [1, 0, 4], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [2, 0, 0], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [2, 0, 1], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [2, 0, 2], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [2, 0, 3], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [2, 0, 4], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [3, 0, 0], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [3, 0, 1], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [3, 0, 2], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [3, 0, 3], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [3, 0, 4], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [4, 0, 0], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [4, 0, 1], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [4, 0, 2], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [4, 0, 3], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [4, 0, 4], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [0, 1, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 1, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 1, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 1, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 1, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 1, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 1, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 1, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 1, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 1, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 1, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 1, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 1, 2], state: "computercraft:turtle_normal{facing:north,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 0, Items: [{Count: 2b, Slot: 0b, id: "minecraft:diamond"}, {Count: 2b, Slot: 1b, id: "minecraft:diamond"}, {Count: 2b, Slot: 2b, id: "minecraft:diamond"}, {Count: 2b, Slot: 5b, id: "minecraft:stick"}, {Count: 2b, Slot: 9b, id: "minecraft:stick"}], Label: "turtle_test.craft", LeftUpgrade: "minecraft:crafting_table", LeftUpgradeNbt: {}, On: 1b, Slot: 0, id: "computercraft:turtle_normal"}}, | ||||||
|  |         {pos: [2, 1, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 1, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 1, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 1, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 1, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 1, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 1, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 1, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 1, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 1, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 1, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 1, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 2, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 2, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 2, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 2, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 2, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 2, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 2, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 2, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 2, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 2, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 2, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 2, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 2, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 2, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 2, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 2, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 2, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 2, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 2, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 2, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 2, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 2, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 2, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 2, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 2, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 3, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 3, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 3, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 3, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 3, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 3, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 3, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 3, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 3, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 3, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 3, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 3, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 3, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 3, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 3, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 3, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 3, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 3, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 3, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 3, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 3, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 3, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 3, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 3, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 3, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 4, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 4, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 4, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 4, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 4, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 4, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 4, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 4, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 4, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 4, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 4, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 4, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 4, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 4, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 4, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 4, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 4, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 4, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 4, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 4, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 4, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 4, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 4, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 4, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 4, 4], state: "minecraft:air"} | ||||||
|  |     ], | ||||||
|  |     entities: [], | ||||||
|  |     palette: [ | ||||||
|  |         "minecraft:polished_andesite", | ||||||
|  |         "minecraft:air", | ||||||
|  |         "computercraft:turtle_normal{facing:north,waterlogged:false}" | ||||||
|  |     ] | ||||||
|  | } | ||||||
							
								
								
									
										137
									
								
								projects/common/src/testMod/resources/data/cctest/structures/turtle_test.equip_tool.snbt
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								projects/common/src/testMod/resources/data/cctest/structures/turtle_test.equip_tool.snbt
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,137 @@ | |||||||
|  | { | ||||||
|  |     DataVersion: 3465, | ||||||
|  |     size: [5, 5, 5], | ||||||
|  |     data: [ | ||||||
|  |         {pos: [0, 0, 0], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [0, 0, 1], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [0, 0, 2], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [0, 0, 3], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [0, 0, 4], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [1, 0, 0], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [1, 0, 1], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [1, 0, 2], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [1, 0, 3], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [1, 0, 4], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [2, 0, 0], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [2, 0, 1], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [2, 0, 2], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [2, 0, 3], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [2, 0, 4], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [3, 0, 0], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [3, 0, 1], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [3, 0, 2], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [3, 0, 3], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [3, 0, 4], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [4, 0, 0], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [4, 0, 1], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [4, 0, 2], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [4, 0, 3], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [4, 0, 4], state: "minecraft:polished_andesite"}, | ||||||
|  |         {pos: [0, 1, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 1, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 1, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 1, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 1, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 1, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 1, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 1, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 1, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 1, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 1, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 1, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 1, 2], state: "computercraft:turtle_normal{facing:north,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 0, Items: [{Count: 1b, Slot: 0b, id: "minecraft:diamond_pickaxe"}], Label: "turtle_test.equip_tool", On: 1b, Slot: 0, id: "computercraft:turtle_normal"}}, | ||||||
|  |         {pos: [2, 1, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 1, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 1, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 1, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 1, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 1, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 1, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 1, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 1, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 1, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 1, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 1, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 2, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 2, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 2, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 2, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 2, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 2, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 2, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 2, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 2, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 2, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 2, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 2, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 2, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 2, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 2, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 2, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 2, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 2, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 2, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 2, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 2, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 2, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 2, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 2, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 2, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 3, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 3, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 3, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 3, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 3, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 3, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 3, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 3, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 3, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 3, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 3, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 3, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 3, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 3, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 3, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 3, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 3, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 3, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 3, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 3, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 3, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 3, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 3, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 3, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 3, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 4, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 4, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 4, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 4, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [0, 4, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 4, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 4, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 4, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 4, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [1, 4, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 4, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 4, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 4, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 4, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [2, 4, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 4, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 4, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 4, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 4, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [3, 4, 4], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 4, 0], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 4, 1], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 4, 2], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 4, 3], state: "minecraft:air"}, | ||||||
|  |         {pos: [4, 4, 4], state: "minecraft:air"} | ||||||
|  |     ], | ||||||
|  |     entities: [], | ||||||
|  |     palette: [ | ||||||
|  |         "minecraft:polished_andesite", | ||||||
|  |         "minecraft:air", | ||||||
|  |         "computercraft:turtle_normal{facing:north,waterlogged:false}" | ||||||
|  |     ] | ||||||
|  | } | ||||||
| @@ -1,23 +0,0 @@ | |||||||
| // SPDX-FileCopyrightText: 2017 The CC: Tweaked Developers |  | ||||||
| // |  | ||||||
| // SPDX-License-Identifier: MPL-2.0 |  | ||||||
| 
 |  | ||||||
| package dan200.computercraft.api.lua; |  | ||||||
| 
 |  | ||||||
| import dan200.computercraft.api.peripheral.IComputerAccess; |  | ||||||
| 
 |  | ||||||
| import javax.annotation.Nullable; |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * An interface passed to {@link ILuaAPIFactory} in order to provide additional information |  | ||||||
|  * about a computer. |  | ||||||
|  */ |  | ||||||
| public interface IComputerSystem extends IComputerAccess { |  | ||||||
|     /** |  | ||||||
|      * Get the label for this computer. |  | ||||||
|      * |  | ||||||
|      * @return This computer's label, or {@code null} if it is not set. |  | ||||||
|      */ |  | ||||||
|     @Nullable |  | ||||||
|     String getLabel(); |  | ||||||
| } |  | ||||||
| @@ -4,7 +4,6 @@ | |||||||
| 
 | 
 | ||||||
| package dan200.computercraft.core; | package dan200.computercraft.core; | ||||||
| 
 | 
 | ||||||
| import dan200.computercraft.api.lua.ILuaAPIFactory; |  | ||||||
| import dan200.computercraft.core.asm.GenericMethod; | import dan200.computercraft.core.asm.GenericMethod; | ||||||
| import dan200.computercraft.core.asm.LuaMethodSupplier; | import dan200.computercraft.core.asm.LuaMethodSupplier; | ||||||
| import dan200.computercraft.core.asm.PeripheralMethodSupplier; | import dan200.computercraft.core.asm.PeripheralMethodSupplier; | ||||||
| @@ -35,21 +34,19 @@ public final class ComputerContext { | |||||||
|     private final ComputerScheduler computerScheduler; |     private final ComputerScheduler computerScheduler; | ||||||
|     private final MainThreadScheduler mainThreadScheduler; |     private final MainThreadScheduler mainThreadScheduler; | ||||||
|     private final ILuaMachine.Factory luaFactory; |     private final ILuaMachine.Factory luaFactory; | ||||||
|     private final List<ILuaAPIFactory> apiFactories; |  | ||||||
|     private final MethodSupplier<LuaMethod> luaMethods; |     private final MethodSupplier<LuaMethod> luaMethods; | ||||||
|     private final MethodSupplier<PeripheralMethod> peripheralMethods; |     private final MethodSupplier<PeripheralMethod> peripheralMethods; | ||||||
| 
 | 
 | ||||||
|     ComputerContext( |     private ComputerContext( | ||||||
|         GlobalEnvironment globalEnvironment, ComputerScheduler computerScheduler, |         GlobalEnvironment globalEnvironment, ComputerScheduler computerScheduler, | ||||||
|         MainThreadScheduler mainThreadScheduler, ILuaMachine.Factory luaFactory, |         MainThreadScheduler mainThreadScheduler, ILuaMachine.Factory luaFactory, | ||||||
|         List<ILuaAPIFactory> apiFactories, MethodSupplier<LuaMethod> luaMethods, |         MethodSupplier<LuaMethod> luaMethods, | ||||||
|         MethodSupplier<PeripheralMethod> peripheralMethods |         MethodSupplier<PeripheralMethod> peripheralMethods | ||||||
|     ) { |     ) { | ||||||
|         this.globalEnvironment = globalEnvironment; |         this.globalEnvironment = globalEnvironment; | ||||||
|         this.computerScheduler = computerScheduler; |         this.computerScheduler = computerScheduler; | ||||||
|         this.mainThreadScheduler = mainThreadScheduler; |         this.mainThreadScheduler = mainThreadScheduler; | ||||||
|         this.luaFactory = luaFactory; |         this.luaFactory = luaFactory; | ||||||
|         this.apiFactories = apiFactories; |  | ||||||
|         this.luaMethods = luaMethods; |         this.luaMethods = luaMethods; | ||||||
|         this.peripheralMethods = peripheralMethods; |         this.peripheralMethods = peripheralMethods; | ||||||
|     } |     } | ||||||
| @@ -91,15 +88,6 @@ public final class ComputerContext { | |||||||
|         return luaFactory; |         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. |      * 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 ComputerScheduler computerScheduler = null; | ||||||
|         private @Nullable MainThreadScheduler mainThreadScheduler; |         private @Nullable MainThreadScheduler mainThreadScheduler; | ||||||
|         private @Nullable ILuaMachine.Factory luaFactory; |         private @Nullable ILuaMachine.Factory luaFactory; | ||||||
|         private @Nullable List<ILuaAPIFactory> apiFactories; |  | ||||||
|         private @Nullable List<GenericMethod> genericMethods; |         private @Nullable List<GenericMethod> genericMethods; | ||||||
| 
 | 
 | ||||||
|         Builder(GlobalEnvironment environment) { |         Builder(GlobalEnvironment environment) { | ||||||
| @@ -227,20 +214,6 @@ public final class ComputerContext { | |||||||
|             return this; |             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}. |          * 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, |                 computerScheduler == null ? new ComputerThread(1) : computerScheduler, | ||||||
|                 mainThreadScheduler == null ? new NoWorkMainThreadScheduler() : mainThreadScheduler, |                 mainThreadScheduler == null ? new NoWorkMainThreadScheduler() : mainThreadScheduler, | ||||||
|                 luaFactory == null ? CobaltLuaMachine::new : luaFactory, |                 luaFactory == null ? CobaltLuaMachine::new : luaFactory, | ||||||
|                 apiFactories == null ? List.of() : apiFactories, |  | ||||||
|                 LuaMethodSupplier.create(genericMethods == null ? List.of() : genericMethods), |                 LuaMethodSupplier.create(genericMethods == null ? List.of() : genericMethods), | ||||||
|                 PeripheralMethodSupplier.create(genericMethods == null ? List.of() : genericMethods) |                 PeripheralMethodSupplier.create(genericMethods == null ? List.of() : genericMethods) | ||||||
|             ); |             ); | ||||||
|   | |||||||
| @@ -21,7 +21,7 @@ public abstract class ComputerAccess implements IComputerAccess { | |||||||
|     private static final Logger LOG = LoggerFactory.getLogger(ComputerAccess.class); |     private static final Logger LOG = LoggerFactory.getLogger(ComputerAccess.class); | ||||||
| 
 | 
 | ||||||
|     private final IAPIEnvironment environment; |     private final IAPIEnvironment environment; | ||||||
|     private final Set<String> mounts = new HashSet<>(); |     private final Set<String> mounts = new HashSet<>(0); | ||||||
| 
 | 
 | ||||||
|     protected ComputerAccess(IAPIEnvironment environment) { |     protected ComputerAccess(IAPIEnvironment environment) { | ||||||
|         this.environment = environment; |         this.environment = environment; | ||||||
|   | |||||||
| @@ -0,0 +1,28 @@ | |||||||
|  | // SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers | ||||||
|  | // | ||||||
|  | // SPDX-License-Identifier: MPL-2.0 | ||||||
|  | 
 | ||||||
|  | package dan200.computercraft.core.computer; | ||||||
|  | 
 | ||||||
|  | import dan200.computercraft.api.lua.ILuaAPI; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Hooks for managing the lifecycle of an API. This allows adding additional logic to an API's {@link ILuaAPI#startup()} | ||||||
|  |  * and {@link ILuaAPI#shutdown()} methods. | ||||||
|  |  * | ||||||
|  |  * @see ILuaAPI | ||||||
|  |  * @see Computer#addApi(ILuaAPI, ApiLifecycle) | ||||||
|  |  */ | ||||||
|  | public interface ApiLifecycle { | ||||||
|  |     /** | ||||||
|  |      * Called before the API's {@link ILuaAPI#startup()} method, may be used to set up resources. | ||||||
|  |      */ | ||||||
|  |     default void startup() { | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Called after the API's {@link ILuaAPI#shutdown()} method, may be used to tear down resources. | ||||||
|  |      */ | ||||||
|  |     default void shutdown() { | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -9,18 +9,14 @@ import dan200.computercraft.api.lua.ILuaAPI; | |||||||
| import javax.annotation.Nullable; | 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 { | record ApiWrapper(ILuaAPI api, @Nullable ApiLifecycle lifecycle) { | ||||||
|     private final ILuaAPI api; |  | ||||||
|     private final @Nullable ComputerSystem system; |  | ||||||
| 
 |  | ||||||
|     ApiWrapper(ILuaAPI api, @Nullable ComputerSystem system) { |  | ||||||
|         this.api = api; |  | ||||||
|         this.system = system; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public void startup() { |     public void startup() { | ||||||
|  |         if (lifecycle != null) lifecycle.startup(); | ||||||
|         api.startup(); |         api.startup(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @@ -30,7 +26,7 @@ final class ApiWrapper { | |||||||
| 
 | 
 | ||||||
|     public void shutdown() { |     public void shutdown() { | ||||||
|         api.shutdown(); |         api.shutdown(); | ||||||
|         if (system != null) system.unmountAll(); |         if (lifecycle != null) lifecycle.shutdown(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public ILuaAPI api() { |     public ILuaAPI api() { | ||||||
|   | |||||||
| @@ -184,6 +184,10 @@ public class Computer { | |||||||
|         executor.addApi(api); |         executor.addApi(api); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public void addApi(ILuaAPI api, ApiLifecycle lifecycleHooks) { | ||||||
|  |         executor.addApi(api, lifecycleHooks); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     long getUniqueTaskId() { |     long getUniqueTaskId() { | ||||||
|         return lastTaskId.incrementAndGet(); |         return lastTaskId.incrementAndGet(); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -162,13 +162,6 @@ final class ComputerExecutor implements ComputerScheduler.Worker { | |||||||
|         addApi(new PeripheralAPI(environment, context.peripheralMethods())); |         addApi(new PeripheralAPI(environment, context.peripheralMethods())); | ||||||
|         addApi(new OSAPI(environment)); |         addApi(new OSAPI(environment)); | ||||||
|         if (CoreConfig.httpEnabled) addApi(new HTTPAPI(environment)); |         if (CoreConfig.httpEnabled) addApi(new HTTPAPI(environment)); | ||||||
| 
 |  | ||||||
|         // Load in the externally registered APIs. |  | ||||||
|         for (var factory : context.apiFactories()) { |  | ||||||
|             var system = new ComputerSystem(environment); |  | ||||||
|             var api = factory.create(system); |  | ||||||
|             if (api != null) apis.add(new ApiWrapper(api, system)); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
| @@ -190,6 +183,10 @@ final class ComputerExecutor implements ComputerScheduler.Worker { | |||||||
|         apis.add(new ApiWrapper(api, null)); |         apis.add(new ApiWrapper(api, null)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     void addApi(ILuaAPI api, ApiLifecycle lifecycleHooks) { | ||||||
|  |         apis.add(new ApiWrapper(api, lifecycleHooks)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Schedule this computer to be started if not already on. |      * Schedule this computer to be started if not already on. | ||||||
|      */ |      */ | ||||||
|   | |||||||
| @@ -1,53 +0,0 @@ | |||||||
| // SPDX-FileCopyrightText: 2019 The CC: Tweaked Developers |  | ||||||
| // |  | ||||||
| // SPDX-License-Identifier: MPL-2.0 |  | ||||||
| 
 |  | ||||||
| package dan200.computercraft.core.computer; |  | ||||||
| 
 |  | ||||||
| import dan200.computercraft.api.lua.IComputerSystem; |  | ||||||
| import dan200.computercraft.api.lua.ILuaAPIFactory; |  | ||||||
| import dan200.computercraft.api.peripheral.IComputerAccess; |  | ||||||
| import dan200.computercraft.api.peripheral.IPeripheral; |  | ||||||
| import dan200.computercraft.core.apis.ComputerAccess; |  | ||||||
| import dan200.computercraft.core.apis.IAPIEnvironment; |  | ||||||
| 
 |  | ||||||
| import javax.annotation.Nullable; |  | ||||||
| import java.util.Map; |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Implementation of {@link IComputerAccess}/{@link IComputerSystem} for usage by externally registered APIs. |  | ||||||
|  * |  | ||||||
|  * @see ILuaAPIFactory |  | ||||||
|  * @see ApiWrapper |  | ||||||
|  */ |  | ||||||
| public class ComputerSystem extends ComputerAccess implements IComputerSystem { |  | ||||||
|     private final IAPIEnvironment environment; |  | ||||||
| 
 |  | ||||||
|     ComputerSystem(IAPIEnvironment environment) { |  | ||||||
|         super(environment); |  | ||||||
|         this.environment = environment; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     public String getAttachmentName() { |  | ||||||
|         return "computer"; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Nullable |  | ||||||
|     @Override |  | ||||||
|     public String getLabel() { |  | ||||||
|         return environment.getLabel(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     public Map<String, IPeripheral> getAvailablePeripherals() { |  | ||||||
|         // TODO: Should this return peripherals on the current computer? |  | ||||||
|         return Map.of(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Nullable |  | ||||||
|     @Override |  | ||||||
|     public IPeripheral getAvailablePeripheral(String name) { |  | ||||||
|         return null; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -7,9 +7,13 @@ | |||||||
| -- @module textutils | -- @module textutils | ||||||
| -- @since 1.2 | -- @since 1.2 | ||||||
|  |  | ||||||
| local expect = dofile("rom/modules/main/cc/expect.lua") | local pgk_env = setmetatable({}, { __index = _ENV }) | ||||||
|  | pgk_env.require = dofile("rom/modules/main/cc/require.lua").make(pgk_env, "rom/modules/main") | ||||||
|  | local require = pgk_env.require | ||||||
|  |  | ||||||
|  | local expect = require("cc.expect") | ||||||
| local expect, field = expect.expect, expect.field | local expect, field = expect.expect, expect.field | ||||||
| local wrap = dofile("rom/modules/main/cc/strings.lua").wrap | local wrap = require("cc.strings").wrap | ||||||
|  |  | ||||||
| --- Slowly writes string text at current cursor position, | --- Slowly writes string text at current cursor position, | ||||||
| -- character-by-character. | -- character-by-character. | ||||||
|   | |||||||
| @@ -1,3 +1,16 @@ | |||||||
|  | # New features in CC: Tweaked 1.112.0 | ||||||
|  | 
 | ||||||
|  | * Report a custom error when using `!` instead of `not`. | ||||||
|  | * Update several translations (zyxkad, MineKID-LP). | ||||||
|  | * Add `cc.strings.split` function. | ||||||
|  | 
 | ||||||
|  | Several bug fixes: | ||||||
|  | * Fix `drive.getAudioTitle` returning `nil` when no disk is inserted. | ||||||
|  | * Preserve item data when upgrading pocket computers. | ||||||
|  | * Add missing bounds check to `cc.strings.wrap` (Lupus950). | ||||||
|  | * Fix dyed turtles rendering transparent. | ||||||
|  | * Fix dupe bug when crafting with turtles. | ||||||
|  | 
 | ||||||
| # New features in CC: Tweaked 1.111.1 | # New features in CC: Tweaked 1.111.1 | ||||||
| 
 | 
 | ||||||
| * Add support for data-driven turtle upgrades. | * Add support for data-driven turtle upgrades. | ||||||
|   | |||||||
| @@ -1,11 +1,14 @@ | |||||||
| New features in CC: Tweaked 1.111.1 | New features in CC: Tweaked 1.112.0 | ||||||
| 
 | 
 | ||||||
| * Add support for data-driven turtle upgrades. | * Report a custom error when using `!` instead of `not`. | ||||||
|  | * Update several translations (zyxkad, MineKID-LP). | ||||||
|  | * Add `cc.strings.split` function. | ||||||
| 
 | 
 | ||||||
| Several bug fixes: | Several bug fixes: | ||||||
| * Fix monitors not rendering on NeoForge. | * Fix `drive.getAudioTitle` returning `nil` when no disk is inserted. | ||||||
| * Fix turtle labels not rendering. | * Preserve item data when upgrading pocket computers. | ||||||
| * Fix compatibility with newer versions of NeoForge. | * Add missing bounds check to `cc.strings.wrap` (Lupus950). | ||||||
| * Fix heights of turtle flags. | * Fix dyed turtles rendering transparent. | ||||||
|  | * Fix dupe bug when crafting with turtles. | ||||||
| 
 | 
 | ||||||
| Type "help changelog" to see the full version history. | Type "help changelog" to see the full version history. | ||||||
|   | |||||||
| @@ -118,8 +118,8 @@ end | |||||||
| --- Expect a number to be within a specific range. | --- Expect a number to be within a specific range. | ||||||
| -- | -- | ||||||
| -- @tparam number num The value to check. | -- @tparam number num The value to check. | ||||||
| -- @tparam number min The minimum value, if nil then `-math.huge` is used. | -- @tparam[opt=-math.huge] number min The minimum value. | ||||||
| -- @tparam number max The maximum value, if nil then `math.huge` is used. | -- @tparam[opt=math.huge] number max The maximum value. | ||||||
| -- @return The given `value`. | -- @return The given `value`. | ||||||
| -- @throws If the value is outside of the allowed range. | -- @throws If the value is outside of the allowed range. | ||||||
| -- @since 1.96.0 | -- @since 1.96.0 | ||||||
|   | |||||||
| @@ -8,7 +8,8 @@ | |||||||
| -- @since 1.95.0 | -- @since 1.95.0 | ||||||
| -- @see textutils For additional string related utilities. | -- @see textutils For additional string related utilities. | ||||||
|  |  | ||||||
| local expect = (require and require("cc.expect") or dofile("rom/modules/main/cc/expect.lua")).expect | local expect = require("cc.expect") | ||||||
|  | local expect, range = expect.expect, expect.range | ||||||
|  |  | ||||||
| --[[- Wraps a block of text, so that each line fits within the given width. | --[[- Wraps a block of text, so that each line fits within the given width. | ||||||
|  |  | ||||||
| @@ -32,7 +33,7 @@ local function wrap(text, width) | |||||||
|     expect(1, text, "string") |     expect(1, text, "string") | ||||||
|     expect(2, width, "number", "nil") |     expect(2, width, "number", "nil") | ||||||
|     width = width or term.getSize() |     width = width or term.getSize() | ||||||
|  |     range(width, 1) | ||||||
|  |  | ||||||
|     local lines, lines_n, current_line = {}, 0, "" |     local lines, lines_n, current_line = {}, 0, "" | ||||||
|     local function push_line() |     local function push_line() | ||||||
| @@ -109,7 +110,63 @@ local function ensure_width(line, width) | |||||||
|     return line |     return line | ||||||
| end | end | ||||||
|  |  | ||||||
|  | --[[- Split a string into parts, each separated by a deliminator. | ||||||
|  |  | ||||||
|  | For instance, splitting the string `"a b c"` with the deliminator `" "`, would | ||||||
|  | return a table with three strings: `"a"`, `"b"`, and `"c"`. | ||||||
|  |  | ||||||
|  | By default, the deliminator is given as a [Lua pattern][pattern]. Passing `true` | ||||||
|  | to the `plain` argument will cause the deliminator to be treated as a litteral | ||||||
|  | string. | ||||||
|  |  | ||||||
|  | [pattern]: https://www.lua.org/manual/5.3/manual.html#6.4.1 | ||||||
|  |  | ||||||
|  | @tparam string str The string to split. | ||||||
|  | @tparam string deliminator The pattern to split this string on. | ||||||
|  | @tparam[opt=false] boolean plain Treat the deliminator as a plain string, rather than a pattern. | ||||||
|  | @tparam[opt] number limit The maximum number of elements in the returned list. | ||||||
|  | @treturn { string... } The list of split strings. | ||||||
|  |  | ||||||
|  | @usage Split a string into words. | ||||||
|  |  | ||||||
|  |     require "cc.strings".split("This is a sentence.", "%s+") | ||||||
|  |  | ||||||
|  | @usage Split a string by "-" into at most 3 elements. | ||||||
|  |  | ||||||
|  |     require "cc.strings".split("a-separated-string-of-sorts", "-", true, 3) | ||||||
|  |  | ||||||
|  | @see table.concat To join strings together. | ||||||
|  |  | ||||||
|  | @since 1.112.0 | ||||||
|  | ]] | ||||||
|  | local function split(str, deliminator, plain, limit) | ||||||
|  |     expect(1, str, "string") | ||||||
|  |     expect(2, deliminator, "string") | ||||||
|  |     expect(3, plain, "boolean", "nil") | ||||||
|  |     expect(4, limit, "number", "nil") | ||||||
|  |  | ||||||
|  |     local out, out_n, pos = {}, 0, 1 | ||||||
|  |     while not limit or out_n < limit - 1 do | ||||||
|  |         local start, finish = str:find(deliminator, pos, plain) | ||||||
|  |         if not start then break end | ||||||
|  |  | ||||||
|  |         out_n = out_n + 1 | ||||||
|  |         out[out_n] = str:sub(pos, start - 1) | ||||||
|  |  | ||||||
|  |         -- Require us to advance by at least one character. | ||||||
|  |         if finish < start then error("separator is empty", 2) end | ||||||
|  |  | ||||||
|  |         pos = finish + 1 | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     if pos == 1 then return { str } end | ||||||
|  |  | ||||||
|  |     out[out_n + 1] = str:sub(pos) | ||||||
|  |     return out | ||||||
|  | end | ||||||
|  |  | ||||||
| return { | return { | ||||||
|     wrap = wrap, |     wrap = wrap, | ||||||
|     ensure_width = ensure_width, |     ensure_width = ensure_width, | ||||||
|  |     split = split, | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ | |||||||
| -- | -- | ||||||
| -- SPDX-License-Identifier: MPL-2.0 | -- SPDX-License-Identifier: MPL-2.0 | ||||||
|  |  | ||||||
| describe("cc.pretty", function() | describe("cc.strings", function() | ||||||
|     local str = require("cc.strings") |     local str = require("cc.strings") | ||||||
|  |  | ||||||
|     describe("wrap", function() |     describe("wrap", function() | ||||||
| @@ -11,6 +11,8 @@ describe("cc.pretty", function() | |||||||
|             str.wrap("test string is long", 11) |             str.wrap("test string is long", 11) | ||||||
|             expect.error(str.wrap, nil):eq("bad argument #1 (string expected, got nil)") |             expect.error(str.wrap, nil):eq("bad argument #1 (string expected, got nil)") | ||||||
|             expect.error(str.wrap, "", false):eq("bad argument #2 (number expected, got boolean)") |             expect.error(str.wrap, "", false):eq("bad argument #2 (number expected, got boolean)") | ||||||
|  |  | ||||||
|  |             expect.error(str.wrap, "", 0):eq("number outside of range (expected 0 to be within 1 and inf)") | ||||||
|         end) |         end) | ||||||
|  |  | ||||||
|         it("wraps lines", function() |         it("wraps lines", function() | ||||||
| @@ -42,4 +44,33 @@ describe("cc.pretty", function() | |||||||
|             expect(str.ensure_width("test string is long", 15)):eq("test string is ") |             expect(str.ensure_width("test string is long", 15)):eq("test string is ") | ||||||
|         end) |         end) | ||||||
|     end) |     end) | ||||||
|  |  | ||||||
|  |     describe("split", function() | ||||||
|  |         it("splits with empty segments", function() | ||||||
|  |             expect(str.split("", "%-")):same { "" } | ||||||
|  |             expect(str.split("-", "%-")):same { "", "" } | ||||||
|  |             expect(str.split("---", "%-")):same { "", "", "", "" } | ||||||
|  |             expect(str.split("-a", "%-")):same { "", "a" } | ||||||
|  |             expect(str.split("a-", "%-")):same { "a", "" } | ||||||
|  |         end) | ||||||
|  |  | ||||||
|  |         it("cannot split with an empty separator", function() | ||||||
|  |             expect.error(str.split, "abc", ""):eq("separator is empty") | ||||||
|  |         end) | ||||||
|  |  | ||||||
|  |         it("splits on patterns", function() | ||||||
|  |             expect(str.split("a.bcd      ef", "%W+")):same { "a", "bcd", "ef" } | ||||||
|  |         end) | ||||||
|  |  | ||||||
|  |         it("splits on literal strings", function() | ||||||
|  |             expect(str.split("a-bcd-ef", "-", true)):same { "a", "bcd", "ef" } | ||||||
|  |         end) | ||||||
|  |  | ||||||
|  |         it("accepts a limit", function() | ||||||
|  |             expect(str.split("foo-bar-baz-qux-quyux", "-", true, 3)):same { "foo", "bar", "baz-qux-quyux" } | ||||||
|  |             expect(str.split("foo-bar-baz", "-", true, 5)):same { "foo", "bar", "baz" } | ||||||
|  |             expect(str.split("foo-bar-baz", "-", true, 1)):same { "foo-bar-baz" } | ||||||
|  |             expect(str.split("foo-bar-baz", "-", true, 1)):same { "foo-bar-baz" } | ||||||
|  |         end) | ||||||
|  |     end) | ||||||
| end) | end) | ||||||
|   | |||||||
| @@ -69,6 +69,7 @@ dependencies { | |||||||
|         exclude("net.fabricmc", "fabric-loader") |         exclude("net.fabricmc", "fabric-loader") | ||||||
|         exclude("net.fabricmc.fabric-api") |         exclude("net.fabricmc.fabric-api") | ||||||
|     } |     } | ||||||
|  |     modCompileOnly(libs.create.fabric) { isTransitive = false } | ||||||
| 
 | 
 | ||||||
|     modClientRuntimeOnly(libs.bundles.externalMods.fabric.runtime) { |     modClientRuntimeOnly(libs.bundles.externalMods.fabric.runtime) { | ||||||
|         exclude("net.fabricmc", "fabric-loader") |         exclude("net.fabricmc", "fabric-loader") | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ import dan200.computercraft.shared.command.CommandComputerCraft; | |||||||
| import dan200.computercraft.shared.config.Config; | import dan200.computercraft.shared.config.Config; | ||||||
| import dan200.computercraft.shared.config.ConfigSpec; | import dan200.computercraft.shared.config.ConfigSpec; | ||||||
| import dan200.computercraft.shared.details.FluidDetails; | import dan200.computercraft.shared.details.FluidDetails; | ||||||
|  | import dan200.computercraft.shared.integration.CreateIntegration; | ||||||
| import dan200.computercraft.shared.network.NetworkMessages; | import dan200.computercraft.shared.network.NetworkMessages; | ||||||
| import dan200.computercraft.shared.peripheral.commandblock.CommandBlockPeripheral; | import dan200.computercraft.shared.peripheral.commandblock.CommandBlockPeripheral; | ||||||
| import dan200.computercraft.shared.peripheral.generic.methods.InventoryMethods; | import dan200.computercraft.shared.peripheral.generic.methods.InventoryMethods; | ||||||
| @@ -143,6 +144,8 @@ public class ComputerCraft { | |||||||
| 
 | 
 | ||||||
|     private static <B extends FriendlyByteBuf, T extends CustomPacketPayload> void registerPayloadType(PayloadTypeRegistry<B> registry, CustomPacketPayload.TypeAndCodec<B, T> type) { |     private static <B extends FriendlyByteBuf, T extends CustomPacketPayload> void registerPayloadType(PayloadTypeRegistry<B> registry, CustomPacketPayload.TypeAndCodec<B, T> type) { | ||||||
|         registry.register(type.type(), type.codec()); |         registry.register(type.type(), type.codec()); | ||||||
|  | 
 | ||||||
|  |         if (FabricLoader.getInstance().isModLoaded(CreateIntegration.ID)) CreateIntegration.setup(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private record ReloadListener(String name, PreparableReloadListener listener) |     private record ReloadListener(String name, PreparableReloadListener listener) | ||||||
|   | |||||||
| @@ -120,6 +120,7 @@ dependencies { | |||||||
|     clientCompileOnly(variantOf(libs.emi) { classifier("api") }) |     clientCompileOnly(variantOf(libs.emi) { classifier("api") }) | ||||||
|     compileOnly(libs.bundles.externalMods.forge.compile) |     compileOnly(libs.bundles.externalMods.forge.compile) | ||||||
|     runtimeOnly(libs.bundles.externalMods.forge.runtime) { cct.exclude(this) } |     runtimeOnly(libs.bundles.externalMods.forge.runtime) { cct.exclude(this) } | ||||||
|  |     compileOnly(variantOf(libs.create.forge) { classifier("slim") }) { isTransitive = false } | ||||||
| 
 | 
 | ||||||
|     implementation("net.neoforged:neoforge:${libs.versions.neoForge.get()}") |     implementation("net.neoforged:neoforge:${libs.versions.neoForge.get()}") | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -19,6 +19,7 @@ import dan200.computercraft.shared.ModRegistry; | |||||||
| import dan200.computercraft.shared.config.Config; | import dan200.computercraft.shared.config.Config; | ||||||
| import dan200.computercraft.shared.config.ConfigSpec; | import dan200.computercraft.shared.config.ConfigSpec; | ||||||
| import dan200.computercraft.shared.details.FluidData; | import dan200.computercraft.shared.details.FluidData; | ||||||
|  | import dan200.computercraft.shared.integration.CreateIntegration; | ||||||
| import dan200.computercraft.shared.network.NetworkMessage; | import dan200.computercraft.shared.network.NetworkMessage; | ||||||
| import dan200.computercraft.shared.network.NetworkMessages; | import dan200.computercraft.shared.network.NetworkMessages; | ||||||
| import dan200.computercraft.shared.network.client.ClientNetworkContext; | import dan200.computercraft.shared.network.client.ClientNetworkContext; | ||||||
| @@ -39,6 +40,7 @@ import net.minecraft.server.level.ServerPlayer; | |||||||
| import net.minecraft.world.level.block.entity.BlockEntityType; | import net.minecraft.world.level.block.entity.BlockEntityType; | ||||||
| import net.neoforged.bus.api.IEventBus; | import net.neoforged.bus.api.IEventBus; | ||||||
| import net.neoforged.bus.api.SubscribeEvent; | import net.neoforged.bus.api.SubscribeEvent; | ||||||
|  | import net.neoforged.fml.ModList; | ||||||
| import net.neoforged.fml.ModLoadingContext; | import net.neoforged.fml.ModLoadingContext; | ||||||
| import net.neoforged.fml.common.EventBusSubscriber; | import net.neoforged.fml.common.EventBusSubscriber; | ||||||
| import net.neoforged.fml.common.Mod; | import net.neoforged.fml.common.Mod; | ||||||
| @@ -108,6 +110,8 @@ public final class ComputerCraft { | |||||||
|         ForgeComputerCraftAPI.registerGenericCapability(Capabilities.EnergyStorage.BLOCK); |         ForgeComputerCraftAPI.registerGenericCapability(Capabilities.EnergyStorage.BLOCK); | ||||||
| 
 | 
 | ||||||
|         ForgeDetailRegistries.FLUID_STACK.addProvider(FluidData::fill); |         ForgeDetailRegistries.FLUID_STACK.addProvider(FluidData::fill); | ||||||
|  | 
 | ||||||
|  |         if (ModList.get().isLoaded(CreateIntegration.ID)) event.enqueueWork(CreateIntegration::setup); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @SubscribeEvent |     @SubscribeEvent | ||||||
|   | |||||||
| @@ -17,12 +17,11 @@ ensure language files are mostly correct. | |||||||
|  |  | ||||||
| import json | import json | ||||||
| import pathlib | import pathlib | ||||||
| from collections import OrderedDict |  | ||||||
|  |  | ||||||
| root = pathlib.Path("projects/common/src/main/resources/assets/computercraft/lang") | root = pathlib.Path("projects/common/src/main/resources/assets/computercraft/lang") | ||||||
|  |  | ||||||
| with open("projects/fabric/src/generated/resources/assets/computercraft/lang/en_us.json", encoding="utf-8") as file: | with open("projects/common/src/generated/resources/assets/computercraft/lang/en_us.json", encoding="utf-8") as file: | ||||||
|     en_us = json.load(file, object_hook=OrderedDict) |     en_us = json.load(file) | ||||||
|  |  | ||||||
| for path in root.glob("*.json"): | for path in root.glob("*.json"): | ||||||
|     if path.name == "en_us.json": |     if path.name == "en_us.json": | ||||||
| @@ -31,7 +30,7 @@ for path in root.glob("*.json"): | |||||||
|     with path.open(encoding="utf-8") as file: |     with path.open(encoding="utf-8") as file: | ||||||
|         lang = json.load(file) |         lang = json.load(file) | ||||||
|  |  | ||||||
|     out = OrderedDict() |     out = {} | ||||||
|     missing = 0 |     missing = 0 | ||||||
|     for k in en_us.keys(): |     for k in en_us.keys(): | ||||||
|         if k not in lang: |         if k not in lang: | ||||||
| @@ -46,4 +45,6 @@ for path in root.glob("*.json"): | |||||||
|         file.write("\n") |         file.write("\n") | ||||||
|  |  | ||||||
|     if missing > 0: |     if missing > 0: | ||||||
|         print("{} has {} missing translations.".format(path.name, missing)) |         print("{} has {} missing translations. {:.2f}% complete".format(path.name, missing, len(out) / len(en_us) * 100)) | ||||||
|  |     else: | ||||||
|  |         print("{} is complete".format(path.name)) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Jonathan Coates
					Jonathan Coates