mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-10-19 07:57:38 +00:00
Compare commits
73 Commits
v1.19.4-1.
...
v1.19.4-1.
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2a04fb71fd | ||
![]() |
df61389304 | ||
![]() |
b6632c9ed9 | ||
![]() |
41b6711b38 | ||
![]() |
02f0b7ec14 | ||
![]() |
a9d31cd3c6 | ||
![]() |
43744b0e85 | ||
![]() |
55510c42db | ||
![]() |
57a944fd90 | ||
![]() |
940f59b116 | ||
![]() |
dd08d1ec8e | ||
![]() |
90ed0b24e7 | ||
![]() |
0ad399a528 | ||
![]() |
1b88213eca | ||
![]() |
eef05b9854 | ||
![]() |
24d74f5c80 | ||
![]() |
c2988366d8 | ||
![]() |
ec0765ead1 | ||
![]() |
b94e34f372 | ||
![]() |
af3263dec2 | ||
![]() |
7f25c9a66b | ||
![]() |
9ca3efff3c | ||
![]() |
aaf8c248a8 | ||
![]() |
df26cd267a | ||
![]() |
8914b78816 | ||
![]() |
9ea7f45fa7 | ||
![]() |
d351bc33c6 | ||
![]() |
5d71770931 | ||
![]() |
4bbde8c50c | ||
![]() |
cc8c1f38e7 | ||
![]() |
cab9c9772a | ||
![]() |
e337a63712 | ||
![]() |
efa92b741b | ||
![]() |
a91ac6f214 | ||
![]() |
943a9406b1 | ||
![]() |
0b2bb5e7b5 | ||
![]() |
8708048b6e | ||
![]() |
d138d9c4a5 | ||
![]() |
f54cb8a432 | ||
![]() |
94f5ede75a | ||
![]() |
1977556da4 | ||
![]() |
9eabb29999 | ||
![]() |
ecf880ed82 | ||
![]() |
655d5aeca8 | ||
![]() |
34f41c4039 | ||
![]() |
f5b16261cc | ||
![]() |
7eb3b691da | ||
![]() |
910a63214e | ||
![]() |
591a7eca23 | ||
![]() |
a29a516a3f | ||
![]() |
4a5e03c11a | ||
![]() |
50d460624f | ||
![]() |
bc500df921 | ||
![]() |
4accda6b8e | ||
![]() |
54ab98473f | ||
![]() |
7ffdbb2316 | ||
![]() |
672c2cf029 | ||
![]() |
c3bdb0440e | ||
![]() |
88f0c44152 | ||
![]() |
c8523bf479 | ||
![]() |
953372b1b7 | ||
![]() |
36b9f4ec55 | ||
![]() |
ccfed0059b | ||
![]() |
7b4ba11fb4 | ||
![]() |
8ccd5a560c | ||
![]() |
0f866836a0 | ||
![]() |
df591cd7c6 | ||
![]() |
c7f3d4f45d | ||
![]() |
77ac04cb7a | ||
![]() |
201df7e987 | ||
![]() |
5722e51735 | ||
![]() |
7a291619ab | ||
![]() |
4b9b19b02d |
44
.github/workflows/main-ci.yml
vendored
44
.github/workflows/main-ci.yml
vendored
@@ -8,16 +8,16 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Clone repository
|
- name: 📥 Clone repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Set up Java
|
- name: 📥 Set up Java
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
java-version: 17
|
java-version: 17
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
|
|
||||||
- name: Setup Gradle
|
- name: 📥 Setup Gradle
|
||||||
uses: gradle/gradle-build-action@v2
|
uses: gradle/gradle-build-action@v2
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ !startsWith(github.ref, 'refs/heads/mc-') }}
|
cache-read-only: ${{ !startsWith(github.ref, 'refs/heads/mc-') }}
|
||||||
@@ -27,39 +27,45 @@ jobs:
|
|||||||
mkdir -p ~/.gradle
|
mkdir -p ~/.gradle
|
||||||
echo "org.gradle.daemon=false" >> ~/.gradle/gradle.properties
|
echo "org.gradle.daemon=false" >> ~/.gradle/gradle.properties
|
||||||
|
|
||||||
- name: Build with Gradle
|
- name: ⚒️ Build
|
||||||
run: |
|
run: ./gradlew assemble || ./gradlew assemble
|
||||||
./gradlew assemble || ./gradlew assemble
|
|
||||||
./gradlew downloadAssets || ./gradlew downloadAssets
|
|
||||||
./gradlew build
|
|
||||||
|
|
||||||
- name: Run client tests
|
- name: 💡 Lint
|
||||||
|
uses: pre-commit/action@v3.0.0
|
||||||
|
|
||||||
|
- name: 🧪 Run tests
|
||||||
|
run: ./gradlew test validateMixinNames checkChangelog
|
||||||
|
|
||||||
|
- name: 📥 Download assets for game tests
|
||||||
|
run: ./gradlew downloadAssets || ./gradlew downloadAssets
|
||||||
|
|
||||||
|
- name: 🧪 Run integration tests
|
||||||
|
run: ./gradlew runGametest
|
||||||
|
|
||||||
|
- name: 🧪 Run client tests
|
||||||
run: ./gradlew runGametestClient # Not checkClient, as no point running rendering tests.
|
run: ./gradlew runGametestClient # Not checkClient, as no point running rendering tests.
|
||||||
# These are a little flaky on GH actions: its useful to run them, but don't break the build.
|
# These are a little flaky on GH actions: its useful to run them, but don't break the build.
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
|
||||||
- name: Prepare Jars
|
- name: 🧪 Parse test reports
|
||||||
|
run: ./tools/parse-reports.py
|
||||||
|
if: ${{ failure() }}
|
||||||
|
|
||||||
|
- name: 📦 Prepare Jars
|
||||||
run: |
|
run: |
|
||||||
# Find the main jar and append the git hash onto it.
|
# Find the main jar and append the git hash onto it.
|
||||||
mkdir -p jars
|
mkdir -p jars
|
||||||
find projects/forge/build/libs projects/fabric/build/libs -type f -regex '.*[0-9.]+\(-SNAPSHOT\)?\.jar$' -exec bash -c 'cp {} "jars/$(basename {} .jar)-$(git rev-parse HEAD).jar"' \;
|
find projects/forge/build/libs projects/fabric/build/libs -type f -regex '.*[0-9.]+\(-SNAPSHOT\)?\.jar$' -exec bash -c 'cp {} "jars/$(basename {} .jar)-$(git rev-parse HEAD).jar"' \;
|
||||||
|
|
||||||
- name: Upload Jar
|
- name: 📤 Upload Jar
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: CC-Tweaked
|
name: CC-Tweaked
|
||||||
path: ./jars
|
path: ./jars
|
||||||
|
|
||||||
- name: Upload coverage
|
- name: 📤 Upload coverage
|
||||||
uses: codecov/codecov-action@v3
|
uses: codecov/codecov-action@v3
|
||||||
|
|
||||||
- name: Parse test reports
|
|
||||||
run: ./tools/parse-reports.py
|
|
||||||
if: ${{ failure() }}
|
|
||||||
|
|
||||||
- name: Run linters
|
|
||||||
uses: pre-commit/action@v3.0.0
|
|
||||||
|
|
||||||
build-core:
|
build-core:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
1
.github/workflows/make-doc.yml
vendored
1
.github/workflows/make-doc.yml
vendored
@@ -4,6 +4,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- mc-1.19.x
|
- mc-1.19.x
|
||||||
|
- mc-1.20.x
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
make_doc:
|
make_doc:
|
||||||
|
@@ -6,6 +6,7 @@ Upstream-Contact: Jonathan Coates <git@squiddev.cc>
|
|||||||
Files:
|
Files:
|
||||||
projects/common/src/main/resources/assets/computercraft/sounds.json
|
projects/common/src/main/resources/assets/computercraft/sounds.json
|
||||||
projects/common/src/main/resources/assets/computercraft/sounds/empty.ogg
|
projects/common/src/main/resources/assets/computercraft/sounds/empty.ogg
|
||||||
|
projects/common/src/testMod/resources/data/cctest/computercraft/turtle_upgrades/*
|
||||||
projects/common/src/testMod/resources/data/cctest/structures/*
|
projects/common/src/testMod/resources/data/cctest/structures/*
|
||||||
projects/fabric/src/generated/*
|
projects/fabric/src/generated/*
|
||||||
projects/forge/src/generated/*
|
projects/forge/src/generated/*
|
||||||
@@ -47,6 +48,7 @@ License: MPL-2.0
|
|||||||
|
|
||||||
Files:
|
Files:
|
||||||
doc/logo.png
|
doc/logo.png
|
||||||
|
doc/logo-darkmode.png
|
||||||
projects/common/src/main/resources/assets/computercraft/models/*
|
projects/common/src/main/resources/assets/computercraft/models/*
|
||||||
projects/common/src/main/resources/assets/computercraft/textures/*
|
projects/common/src/main/resources/assets/computercraft/textures/*
|
||||||
projects/common/src/main/resources/pack.mcmeta
|
projects/common/src/main/resources/pack.mcmeta
|
||||||
|
@@ -6,7 +6,7 @@ SPDX-License-Identifier: MPL-2.0
|
|||||||
|
|
||||||
# Contributing to CC: Tweaked
|
# Contributing to CC: Tweaked
|
||||||
As with many open source projects, CC: Tweaked thrives on contributions from other people! This document (hopefully)
|
As with many open source projects, CC: Tweaked thrives on contributions from other people! This document (hopefully)
|
||||||
provides an introduction as to how to get started in helping out.
|
provides an introduction as to how to get started with helping out.
|
||||||
|
|
||||||
If you've any other questions, [just ask the community][community] or [open an issue][new-issue].
|
If you've any other questions, [just ask the community][community] or [open an issue][new-issue].
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ automatically with GitHub, so please don't submit PRs adding/changing translatio
|
|||||||
## 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 instealled:
|
- Make sure you've got the following software installed:
|
||||||
- Java Development Kit (JDK) installed. This can be downloaded from [Adoptium].
|
- Java Development Kit (JDK) installed. This can be downloaded from [Adoptium].
|
||||||
- [Git](https://git-scm.com/).
|
- [Git](https://git-scm.com/).
|
||||||
- If you want to work on documentation, [NodeJS][node].
|
- If you want to work on documentation, [NodeJS][node].
|
||||||
@@ -51,10 +51,10 @@ If you want to run CC:T in a normal Minecraft instance, run `./gradlew assemble`
|
|||||||
## Developing CC: Tweaked
|
## Developing CC: Tweaked
|
||||||
Before making any major changes to CC: Tweaked, I'd recommend you have a read of the [the architecture
|
Before making any major changes to CC: Tweaked, I'd recommend you have a read of the [the architecture
|
||||||
document][architecture] first. While it's not a comprehensive document, it gives a good hint of where you should start
|
document][architecture] first. While it's not a comprehensive document, it gives a good hint of where you should start
|
||||||
looking to make your changes. As always, if you're not sure [do ask the community][community]!
|
looking to make your changes. As always, if you're not sure, [do ask the community][community]!
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
When making larger changes, it's may be useful to write a test to make sure your code works as expected.
|
When making larger changes, it may be useful to write a test to make sure your code works as expected.
|
||||||
|
|
||||||
CC: Tweaked has several test suites, each designed to test something different:
|
CC: Tweaked has several test suites, each designed to test something different:
|
||||||
|
|
||||||
@@ -91,11 +91,11 @@ file.
|
|||||||
|
|
||||||
Documentation is built using [illuaminate] which, while not currently documented (somewhat ironic), is largely the same
|
Documentation is built using [illuaminate] which, while not currently documented (somewhat ironic), is largely the same
|
||||||
as [ldoc][ldoc]. Documentation comments are written in Markdown, though note that we do not support many GitHub-specific
|
as [ldoc][ldoc]. Documentation comments are written in Markdown, though note that we do not support many GitHub-specific
|
||||||
markdown features - if you can, do check what the documentation looks like locally!
|
markdown features. If you can, do check what the documentation looks like locally!
|
||||||
|
|
||||||
When writing long-form documentation (such as the guides in [doc/guides](doc/guides)), I find it useful to tell a
|
When writing long-form documentation (such as the guides in [doc/guides](doc/guides)), I find it useful to tell a
|
||||||
narrative. Think of what you want the user to learn or achieve, then start introducing a simple concept and then talk
|
narrative. Think of what you want the user to learn or achieve, then start introducing a simple concept, and then talk
|
||||||
about how you can build on that, until you've covered everything!
|
about how you can build on that until you've covered everything!
|
||||||
|
|
||||||
[new-issue]: https://github.com/cc-tweaked/CC-Tweaked/issues/new/choose "Create a new issue"
|
[new-issue]: https://github.com/cc-tweaked/CC-Tweaked/issues/new/choose "Create a new issue"
|
||||||
[community]: README.md#community "Get in touch with the community."
|
[community]: README.md#community "Get in touch with the community."
|
||||||
|
22
README.md
22
README.md
@@ -4,7 +4,12 @@ SPDX-FileCopyrightText: 2017 The CC: Tweaked Developers
|
|||||||
SPDX-License-Identifier: MPL-2.0
|
SPDX-License-Identifier: MPL-2.0
|
||||||
-->
|
-->
|
||||||
|
|
||||||
# 
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="./doc/logo-darkmode.png">
|
||||||
|
<source media="(prefers-color-scheme: light)" srcset="./doc/logo.png">
|
||||||
|
<img alt="CC: Tweaked" src="./doc/logo.png">
|
||||||
|
</picture>
|
||||||
|
|
||||||
[](https://github.com/cc-tweaked/CC-Tweaked/actions "Current build status")
|
[](https://github.com/cc-tweaked/CC-Tweaked/actions "Current build status")
|
||||||
[][CurseForge]
|
[][CurseForge]
|
||||||
[][Modrinth]
|
[][Modrinth]
|
||||||
@@ -44,7 +49,7 @@ repositories {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// Vanilla (i.e. for multi-loader systems)
|
// Vanilla (i.e. for multi-loader systems)
|
||||||
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-common-api")
|
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-common-api:$cctVersion")
|
||||||
|
|
||||||
// Forge Gradle
|
// Forge Gradle
|
||||||
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-core-api:$cctVersion")
|
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-core-api:$cctVersion")
|
||||||
@@ -57,6 +62,19 @@ dependencies {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
When using ForgeGradle, you may also need to add the following:
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
minecraft {
|
||||||
|
runs {
|
||||||
|
configureEach {
|
||||||
|
property 'mixin.env.remapRefMap', 'true'
|
||||||
|
property 'mixin.env.refMapRemappingFile', "${buildDir}/createSrgToMcp/output.srg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
You should also be careful to only use classes within the `dan200.computercraft.api` package. Non-API classes are
|
You should also be careful to only use classes within the `dan200.computercraft.api` package. Non-API classes are
|
||||||
subject to change at any point. If you depend on functionality outside the API, file an issue, and we can look into
|
subject to change at any point. If you depend on functionality outside the API, file an issue, and we can look into
|
||||||
exposing more features.
|
exposing more features.
|
||||||
|
@@ -10,8 +10,9 @@ import cc.tweaked.gradle.IdeaRunConfigurations
|
|||||||
import cc.tweaked.gradle.MinecraftConfigurations
|
import cc.tweaked.gradle.MinecraftConfigurations
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("cc-tweaked.java-convention")
|
|
||||||
id("net.minecraftforge.gradle")
|
id("net.minecraftforge.gradle")
|
||||||
|
// We must apply java-convention after Forge, as we need the fg extension to be present.
|
||||||
|
id("cc-tweaked.java-convention")
|
||||||
id("org.parchmentmc.librarian.forgegradle")
|
id("org.parchmentmc.librarian.forgegradle")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -37,22 +37,37 @@ java {
|
|||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven("https://squiddev.cc/maven") {
|
|
||||||
|
val mainMaven = maven("https://squiddev.cc/maven") {
|
||||||
name = "SquidDev"
|
name = "SquidDev"
|
||||||
content {
|
content {
|
||||||
includeGroup("org.squiddev")
|
|
||||||
includeGroup("cc.tweaked")
|
|
||||||
// Things we mirror
|
|
||||||
includeGroup("dev.architectury")
|
|
||||||
includeGroup("maven.modrinth")
|
|
||||||
includeGroup("me.shedaniel")
|
|
||||||
includeGroup("me.shedaniel.cloth")
|
|
||||||
includeGroup("mezz.jei")
|
|
||||||
includeModule("com.terraformersmc", "modmenu")
|
|
||||||
// Until https://github.com/SpongePowered/Mixin/pull/593 is merged
|
// Until https://github.com/SpongePowered/Mixin/pull/593 is merged
|
||||||
includeModule("org.spongepowered", "mixin")
|
includeModule("org.spongepowered", "mixin")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exclusiveContent {
|
||||||
|
forRepositories(mainMaven)
|
||||||
|
|
||||||
|
// Include the ForgeGradle repository if present. This requires that ForgeGradle is already present, which we
|
||||||
|
// enforce in our Forge overlay.
|
||||||
|
val fg =
|
||||||
|
project.extensions.findByType(net.minecraftforge.gradle.userdev.DependencyManagementExtension::class.java)
|
||||||
|
if (fg != null) forRepositories(fg.repository)
|
||||||
|
|
||||||
|
filter {
|
||||||
|
includeGroup("cc.tweaked")
|
||||||
|
includeModule("org.squiddev", "Cobalt")
|
||||||
|
// Things we mirror
|
||||||
|
includeGroup("dev.architectury")
|
||||||
|
includeGroup("dev.emi")
|
||||||
|
includeGroup("maven.modrinth")
|
||||||
|
includeGroup("me.shedaniel.cloth")
|
||||||
|
includeGroup("me.shedaniel")
|
||||||
|
includeGroup("mezz.jei")
|
||||||
|
includeModule("com.terraformersmc", "modmenu")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@@ -104,6 +119,7 @@ tasks.withType(JavaCompile::class.java).configureEach {
|
|||||||
|
|
||||||
tasks.processResources {
|
tasks.processResources {
|
||||||
exclude("**/*.license")
|
exclude("**/*.license")
|
||||||
|
exclude(".cache")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType(AbstractArchiveTask::class.java).configureEach {
|
tasks.withType(AbstractArchiveTask::class.java).configureEach {
|
||||||
|
@@ -0,0 +1,26 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package cc.tweaked.gradle
|
||||||
|
|
||||||
|
import net.minecraftforge.gradle.common.util.RunConfig
|
||||||
|
import net.minecraftforge.gradle.common.util.runs.setRunConfigInternal
|
||||||
|
import org.gradle.api.plugins.JavaPluginExtension
|
||||||
|
import org.gradle.api.tasks.JavaExec
|
||||||
|
import org.gradle.jvm.toolchain.JavaToolchainService
|
||||||
|
import java.nio.file.Files
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set [JavaExec] task to run a given [RunConfig].
|
||||||
|
*/
|
||||||
|
fun JavaExec.setRunConfig(config: RunConfig) {
|
||||||
|
dependsOn("prepareRuns")
|
||||||
|
setRunConfigInternal(project, this, config)
|
||||||
|
doFirst("Create working directory") { Files.createDirectories(workingDir.toPath()) }
|
||||||
|
|
||||||
|
javaLauncher.set(
|
||||||
|
project.extensions.getByType(JavaToolchainService::class.java)
|
||||||
|
.launcherFor(project.extensions.getByType(JavaPluginExtension::class.java).toolchain),
|
||||||
|
)
|
||||||
|
}
|
@@ -60,7 +60,7 @@ class IlluaminatePlugin : Plugin<Project> {
|
|||||||
|
|
||||||
/** Define a dependency for illuaminate from a version number and the current operating system. */
|
/** Define a dependency for illuaminate from a version number and the current operating system. */
|
||||||
private fun illuaminateArtifact(project: Project, version: String): Dependency {
|
private fun illuaminateArtifact(project: Project, version: String): Dependency {
|
||||||
val osName = System.getProperty("os.name").toLowerCase()
|
val osName = System.getProperty("os.name").lowercase()
|
||||||
val (os, suffix) = when {
|
val (os, suffix) = when {
|
||||||
osName.contains("windows") -> Pair("windows", ".exe")
|
osName.contains("windows") -> Pair("windows", ".exe")
|
||||||
osName.contains("mac os") || osName.contains("darwin") -> Pair("macos", "")
|
osName.contains("mac os") || osName.contains("darwin") -> Pair("macos", "")
|
||||||
@@ -68,7 +68,7 @@ class IlluaminatePlugin : Plugin<Project> {
|
|||||||
else -> error("Unsupported OS $osName for illuaminate")
|
else -> error("Unsupported OS $osName for illuaminate")
|
||||||
}
|
}
|
||||||
|
|
||||||
val osArch = System.getProperty("os.arch").toLowerCase()
|
val osArch = System.getProperty("os.arch").lowercase()
|
||||||
val arch = when {
|
val arch = when {
|
||||||
// On macOS the x86_64 binary will work for both ARM and Intel Macs through Rosetta.
|
// On macOS the x86_64 binary will work for both ARM and Intel Macs through Rosetta.
|
||||||
os == "macos" -> "x86_64"
|
os == "macos" -> "x86_64"
|
||||||
|
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
package cc.tweaked.gradle
|
package cc.tweaked.gradle
|
||||||
|
|
||||||
|
import net.minecraftforge.gradle.common.util.RunConfig
|
||||||
import org.gradle.api.GradleException
|
import org.gradle.api.GradleException
|
||||||
import org.gradle.api.file.FileSystemOperations
|
import org.gradle.api.file.FileSystemOperations
|
||||||
import org.gradle.api.invocation.Gradle
|
import org.gradle.api.invocation.Gradle
|
||||||
@@ -32,11 +33,14 @@ abstract class ClientJavaExec : JavaExec() {
|
|||||||
usesService(clientRunner)
|
usesService(clientRunner)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@get:Input
|
||||||
|
val renderdoc get() = project.hasProperty("renderdoc")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When [false], tests will not be run automatically, allowing the user to debug rendering.
|
* When [false], tests will not be run automatically, allowing the user to debug rendering.
|
||||||
*/
|
*/
|
||||||
@get:Input
|
@get:Input
|
||||||
val clientDebug get() = project.hasProperty("clientDebug")
|
val clientDebug get() = renderdoc || project.hasProperty("clientDebug")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When [false], tests will not run under a framebuffer.
|
* When [false], tests will not run under a framebuffer.
|
||||||
@@ -50,6 +54,25 @@ abstract class ClientJavaExec : JavaExec() {
|
|||||||
@get:OutputFile
|
@get:OutputFile
|
||||||
val testResults = project.layout.buildDirectory.file("test-results/$name.xml")
|
val testResults = project.layout.buildDirectory.file("test-results/$name.xml")
|
||||||
|
|
||||||
|
private fun setTestProperties() {
|
||||||
|
if (!clientDebug) systemProperty("cctest.client", "")
|
||||||
|
if (renderdoc) environment("LD_PRELOAD", "/usr/lib/librenderdoc.so")
|
||||||
|
systemProperty("cctest.gametest-report", testResults.get().asFile.absoluteFile)
|
||||||
|
workingDir(project.buildDir.resolve("gametest").resolve(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
setTestProperties()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set this task to run a given [RunConfig].
|
||||||
|
*/
|
||||||
|
fun setRunConfig(config: RunConfig) {
|
||||||
|
(this as JavaExec).setRunConfig(config)
|
||||||
|
setTestProperties() // setRunConfig may clobber some properties, ensure everything is set.
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copy configuration from a task with the given name.
|
* Copy configuration from a task with the given name.
|
||||||
*/
|
*/
|
||||||
@@ -61,10 +84,7 @@ abstract class ClientJavaExec : JavaExec() {
|
|||||||
fun copyFrom(task: JavaExec) {
|
fun copyFrom(task: JavaExec) {
|
||||||
for (dep in task.dependsOn) dependsOn(dep)
|
for (dep in task.dependsOn) dependsOn(dep)
|
||||||
task.copyToFull(this)
|
task.copyToFull(this)
|
||||||
|
setTestProperties() // copyToFull may clobber some properties, ensure everything is set.
|
||||||
if (!clientDebug) systemProperty("cctest.client", "")
|
|
||||||
systemProperty("cctest.gametest-report", testResults.get().asFile.absoluteFile)
|
|
||||||
workingDir(project.buildDir.resolve("gametest").resolve(name))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -0,0 +1,51 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package net.minecraftforge.gradle.common.util.runs
|
||||||
|
|
||||||
|
import net.minecraftforge.gradle.common.util.RunConfig
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.process.CommandLineArgumentProvider
|
||||||
|
import org.gradle.process.JavaExecSpec
|
||||||
|
import java.io.File
|
||||||
|
import java.util.function.Supplier
|
||||||
|
import java.util.stream.Collectors
|
||||||
|
import java.util.stream.Stream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up a [JavaExecSpec] to execute a [RunConfig].
|
||||||
|
*
|
||||||
|
* [MinecraftRunTask] sets up all its properties when the task is executed, rather than when configured. As such, it's
|
||||||
|
* not possible to use [cc.tweaked.gradle.copyToFull] like we do for Fabric. Instead, we set up the task manually.
|
||||||
|
*
|
||||||
|
* Unfortunately most of the functionality we need is package-private, and so we have to put our code into the package.
|
||||||
|
*/
|
||||||
|
internal fun setRunConfigInternal(project: Project, spec: JavaExecSpec, config: RunConfig) {
|
||||||
|
spec.workingDir = File(config.workingDirectory)
|
||||||
|
|
||||||
|
spec.mainClass.set(config.main)
|
||||||
|
for (source in config.allSources) spec.classpath(source.runtimeClasspath)
|
||||||
|
|
||||||
|
val originalTask = project.tasks.named(config.taskName, MinecraftRunTask::class.java)
|
||||||
|
|
||||||
|
// Add argument and JVM argument via providers, to be as lazy as possible with fetching artifacts.
|
||||||
|
val lazyTokens = RunConfigGenerator.configureTokensLazy(
|
||||||
|
project, config, RunConfigGenerator.mapModClassesToGradle(project, config),
|
||||||
|
originalTask.get().minecraftArtifacts,
|
||||||
|
originalTask.get().runtimeClasspathArtifacts,
|
||||||
|
)
|
||||||
|
spec.argumentProviders.add(
|
||||||
|
CommandLineArgumentProvider {
|
||||||
|
RunConfigGenerator.getArgsStream(config, lazyTokens, false).toList()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
spec.jvmArgumentProviders.add(
|
||||||
|
CommandLineArgumentProvider {
|
||||||
|
(if (config.isClient) config.jvmArgs + originalTask.get().additionalClientArgs.get() else config.jvmArgs).map { config.replace(lazyTokens, it) } +
|
||||||
|
config.properties.map { (k, v) -> "-D${k}=${config.replace(lazyTokens, v)}" }
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
for ((key, value) in config.environment) spec.environment(key, config.replace(lazyTokens, value))
|
||||||
|
}
|
@@ -13,6 +13,15 @@ The @{websocket_closed} event is fired when an open WebSocket connection is clos
|
|||||||
## Return Values
|
## Return Values
|
||||||
1. @{string}: The event name.
|
1. @{string}: The event name.
|
||||||
2. @{string}: The URL of the WebSocket that was closed.
|
2. @{string}: The URL of the WebSocket that was closed.
|
||||||
|
3. <span class="type">@{string}|@{nil}</span>: The [server-provided reason][close_reason]
|
||||||
|
the websocket was closed. This will be @{nil} if the connection was closed
|
||||||
|
abnormally.
|
||||||
|
4. <span class="type">@{number}|@{nil}</span>: The [connection close code][close_code],
|
||||||
|
indicating why the socket was closed. This will be @{nil} if the connection
|
||||||
|
was closed abnormally.
|
||||||
|
|
||||||
|
[close_reason]: https://www.rfc-editor.org/rfc/rfc6455.html#section-7.1.6 "The WebSocket Connection Close Reason, RFC 6455"
|
||||||
|
[close_code]: https://www.rfc-editor.org/rfc/rfc6455.html#section-7.1.5 "The WebSocket Connection Close Code, RFC 6455"
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
Prints a message when a WebSocket is closed (this may take a minute):
|
Prints a message when a WebSocket is closed (this may take a minute):
|
||||||
|
BIN
doc/logo-darkmode.png
Normal file
BIN
doc/logo-darkmode.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
@@ -10,7 +10,7 @@ kotlin.jvm.target.validation.mode=error
|
|||||||
|
|
||||||
# Mod properties
|
# Mod properties
|
||||||
isUnstable=false
|
isUnstable=false
|
||||||
modVersion=1.105.0
|
modVersion=1.107.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.19.4
|
mcVersion=1.19.4
|
||||||
|
@@ -7,20 +7,20 @@
|
|||||||
# Minecraft
|
# Minecraft
|
||||||
# MC version is specified in gradle.properties, as we need that in settings.gradle.
|
# MC version is specified in gradle.properties, as we need that in settings.gradle.
|
||||||
# Remember to update corresponding versions in fabric.mod.json/mods.toml
|
# Remember to update corresponding versions in fabric.mod.json/mods.toml
|
||||||
fabric-api = "0.80.0+1.19.4"
|
fabric-api = "0.86.1+1.19.4"
|
||||||
fabric-loader = "0.14.19"
|
fabric-loader = "0.14.21"
|
||||||
forge = "45.0.42"
|
forge = "45.0.42"
|
||||||
forgeSpi = "6.0.0"
|
forgeSpi = "6.0.0"
|
||||||
mixin = "0.8.5"
|
mixin = "0.8.5"
|
||||||
parchment = "2023.03.12"
|
parchment = "2023.06.26"
|
||||||
parchmentMc = "1.19.3"
|
parchmentMc = "1.19.4"
|
||||||
|
|
||||||
# Normal dependencies
|
# Normal dependencies
|
||||||
asm = "9.3"
|
asm = "9.3"
|
||||||
autoService = "1.0.1"
|
autoService = "1.0.1"
|
||||||
checkerFramework = "3.32.0"
|
checkerFramework = "3.32.0"
|
||||||
cobalt = "0.7.0"
|
cobalt = "0.7.1"
|
||||||
cobalt-next = "0.7.1" # Not a real version, used to constrain the version we accept.
|
cobalt-next = "0.7.2" # Not a real version, used to constrain the version we accept.
|
||||||
fastutil = "8.5.9"
|
fastutil = "8.5.9"
|
||||||
guava = "31.1-jre"
|
guava = "31.1-jre"
|
||||||
jetbrainsAnnotations = "24.0.1"
|
jetbrainsAnnotations = "24.0.1"
|
||||||
@@ -33,6 +33,7 @@ nightConfig = "3.6.5"
|
|||||||
slf4j = "1.7.36"
|
slf4j = "1.7.36"
|
||||||
|
|
||||||
# Minecraft mods
|
# Minecraft mods
|
||||||
|
emi = "1.0.8+1.19.4"
|
||||||
iris = "1.5.2+1.19.4"
|
iris = "1.5.2+1.19.4"
|
||||||
jei = "13.1.0.11"
|
jei = "13.1.0.11"
|
||||||
modmenu = "6.1.0-rc.1"
|
modmenu = "6.1.0-rc.1"
|
||||||
@@ -53,16 +54,16 @@ checkstyle = "10.3.4"
|
|||||||
curseForgeGradle = "1.0.14"
|
curseForgeGradle = "1.0.14"
|
||||||
errorProne-core = "2.18.0"
|
errorProne-core = "2.18.0"
|
||||||
errorProne-plugin = "3.0.1"
|
errorProne-plugin = "3.0.1"
|
||||||
fabric-loom = "1.1.10"
|
fabric-loom = "1.3.7"
|
||||||
forgeGradle = "5.1.+"
|
forgeGradle = "6.0.8"
|
||||||
githubRelease = "2.2.12"
|
githubRelease = "2.2.12"
|
||||||
ideaExt = "1.1.6"
|
ideaExt = "1.1.6"
|
||||||
illuaminate = "0.1.0-24-gdb28902"
|
illuaminate = "0.1.0-28-ga7efd71"
|
||||||
librarian = "1.+"
|
librarian = "1.+"
|
||||||
minotaur = "2.+"
|
minotaur = "2.+"
|
||||||
mixinGradle = "0.7.+"
|
mixinGradle = "0.7.+"
|
||||||
nullAway = "0.9.9"
|
nullAway = "0.9.9"
|
||||||
quiltflower = "1.8.0"
|
quiltflower = "1.10.0"
|
||||||
spotless = "6.17.0"
|
spotless = "6.17.0"
|
||||||
taskTree = "2.1.1"
|
taskTree = "2.1.1"
|
||||||
vanillaGradle = "0.2.1-SNAPSHOT"
|
vanillaGradle = "0.2.1-SNAPSHOT"
|
||||||
@@ -92,6 +93,7 @@ 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" }
|
fabric-api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabric-api" }
|
||||||
fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" }
|
fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" }
|
||||||
|
emi = { module = "dev.emi:emi-xplat-mojmap", version.ref = "emi" }
|
||||||
iris = { module = "maven.modrinth:iris", version.ref = "iris" }
|
iris = { module = "maven.modrinth:iris", version.ref = "iris" }
|
||||||
jei-api = { module = "mezz.jei:jei-1.19.4-common-api", version.ref = "jei" }
|
jei-api = { module = "mezz.jei:jei-1.19.4-common-api", version.ref = "jei" }
|
||||||
jei-fabric = { module = "mezz.jei:jei-1.19.4-fabric", version.ref = "jei" }
|
jei-fabric = { module = "mezz.jei:jei-1.19.4-fabric", version.ref = "jei" }
|
||||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
3
gradle/wrapper/gradle-wrapper.properties
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
|
||||||
|
networkTimeout=10000
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
19
gradlew
vendored
19
gradlew
vendored
@@ -55,7 +55,7 @@
|
|||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (3) This script is generated from the Groovy template
|
||||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
# within the Gradle project.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
@@ -80,13 +80,10 @@ do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
# This is normally unused
|
||||||
|
# shellcheck disable=SC2034
|
||||||
APP_NAME="Gradle"
|
|
||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=${0##*/}
|
||||||
|
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
@@ -143,12 +140,16 @@ fi
|
|||||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
max*)
|
max*)
|
||||||
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC3045
|
||||||
MAX_FD=$( ulimit -H -n ) ||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
warn "Could not query maximum file descriptor limit"
|
warn "Could not query maximum file descriptor limit"
|
||||||
esac
|
esac
|
||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
'' | soft) :;; #(
|
'' | soft) :;; #(
|
||||||
*)
|
*)
|
||||||
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC3045
|
||||||
ulimit -n "$MAX_FD" ||
|
ulimit -n "$MAX_FD" ||
|
||||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
esac
|
esac
|
||||||
@@ -193,6 +194,10 @@ if "$cygwin" || "$msys" ; then
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
# Collect all arguments for the java command;
|
# Collect all arguments for the java command;
|
||||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||||
# shell script including quotes and variable substitutions, so put them in
|
# shell script including quotes and variable substitutions, so put them in
|
||||||
|
1
gradlew.bat
vendored
1
gradlew.bat
vendored
@@ -26,6 +26,7 @@ if "%OS%"=="Windows_NT" setlocal
|
|||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%"=="" set DIRNAME=.
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
|
@rem This is normally unused
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@@ -11,9 +11,13 @@ import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
|||||||
import dan200.computercraft.api.turtle.TurtleSide;
|
import dan200.computercraft.api.turtle.TurtleSide;
|
||||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
|
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
|
||||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
import net.minecraft.client.resources.model.ModelResourceLocation;
|
||||||
|
import net.minecraft.client.resources.model.UnbakedModel;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides models for a {@link ITurtleUpgrade}.
|
* Provides models for a {@link ITurtleUpgrade}.
|
||||||
@@ -28,15 +32,45 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
|
|||||||
* When the current turtle is {@literal null}, this function should be constant for a given upgrade and side.
|
* When the current turtle is {@literal null}, this function should be constant for a given upgrade and side.
|
||||||
*
|
*
|
||||||
* @param upgrade The upgrade that you're getting the model for.
|
* @param upgrade The upgrade that you're getting the model for.
|
||||||
* @param turtle Access to the turtle that the upgrade resides on. This will be null when getting item models!
|
* @param turtle Access to the turtle that the upgrade resides on. This will be null when getting item models, unless
|
||||||
|
* {@link #getModel(ITurtleUpgrade, CompoundTag, TurtleSide)} is overriden.
|
||||||
* @param side Which side of the turtle (left or right) the upgrade resides on.
|
* @param side Which side of the turtle (left or right) the upgrade resides on.
|
||||||
* @return The model that you wish to be used to render your upgrade.
|
* @return The model that you wish to be used to render your upgrade.
|
||||||
*/
|
*/
|
||||||
TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side);
|
TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A basic {@link TurtleUpgradeModeller} which renders using the upgrade's {@linkplain ITurtleUpgrade#getCraftingItem()
|
* Obtain the model to be used when rendering a turtle peripheral.
|
||||||
* crafting item}.
|
* <p>
|
||||||
|
* This is used when rendering the turtle's item model, and so no {@link ITurtleAccess} is available.
|
||||||
|
*
|
||||||
|
* @param upgrade The upgrade that you're getting the model for.
|
||||||
|
* @param data Upgrade data instance for current turtle side.
|
||||||
|
* @param side Which side of the turtle (left or right) the upgrade resides on.
|
||||||
|
* @return The model that you wish to be used to render your upgrade.
|
||||||
|
*/
|
||||||
|
default TransformedModel getModel(T upgrade, CompoundTag data, TurtleSide side) {
|
||||||
|
return getModel(upgrade, (ITurtleAccess) null, side);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of models that this turtle modeller depends on.
|
||||||
|
* <p>
|
||||||
|
* Models included in this list will be loaded and baked alongside item and block models, and so may be referenced
|
||||||
|
* by {@link TransformedModel#of(ResourceLocation)}. You do not need to override this method if you will load models
|
||||||
|
* by other means.
|
||||||
|
*
|
||||||
|
* @return A list of models that this modeller depends on.
|
||||||
|
* @see UnbakedModel#getDependencies()
|
||||||
|
*/
|
||||||
|
default Collection<ResourceLocation> getDependencies() {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A basic {@link TurtleUpgradeModeller} which renders using the upgrade's {@linkplain ITurtleUpgrade#getUpgradeItem(CompoundTag)}
|
||||||
|
* upgrade item}.
|
||||||
* <p>
|
* <p>
|
||||||
* This uses appropriate transformations for "flat" items, namely those extending the {@literal minecraft:item/generated}
|
* This uses appropriate transformations for "flat" items, namely those extending the {@literal minecraft:item/generated}
|
||||||
* model type. It will not appear correct for 3D models with additional depth, such as blocks.
|
* model type. It will not appear correct for 3D models with additional depth, such as blocks.
|
||||||
@@ -46,7 +80,7 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> flatItem() {
|
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> flatItem() {
|
||||||
return (TurtleUpgradeModeller<T>) TurtleUpgradeModellers.FLAT_ITEM;
|
return (TurtleUpgradeModeller<T>) TurtleUpgradeModellers.UPGRADE_ITEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -58,7 +92,8 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
|
|||||||
* @return The constructed modeller.
|
* @return The constructed modeller.
|
||||||
*/
|
*/
|
||||||
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ModelResourceLocation left, ModelResourceLocation right) {
|
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ModelResourceLocation left, ModelResourceLocation right) {
|
||||||
return (upgrade, turtle, side) -> TransformedModel.of(side == TurtleSide.LEFT ? left : right);
|
// TODO(1.21.0): Remove this.
|
||||||
|
return sided((ResourceLocation) left, right);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -70,6 +105,16 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
|
|||||||
* @return The constructed modeller.
|
* @return The constructed modeller.
|
||||||
*/
|
*/
|
||||||
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ResourceLocation left, ResourceLocation right) {
|
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ResourceLocation left, ResourceLocation right) {
|
||||||
return (upgrade, turtle, side) -> TransformedModel.of(side == TurtleSide.LEFT ? left : right);
|
return new TurtleUpgradeModeller<>() {
|
||||||
|
@Override
|
||||||
|
public TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side) {
|
||||||
|
return TransformedModel.of(side == TurtleSide.LEFT ? left : right);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<ResourceLocation> getDependencies() {
|
||||||
|
return List.of(left, right);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,13 +6,20 @@ package dan200.computercraft.api.client.turtle;
|
|||||||
|
|
||||||
import com.mojang.math.Transformation;
|
import com.mojang.math.Transformation;
|
||||||
import dan200.computercraft.api.client.TransformedModel;
|
import dan200.computercraft.api.client.TransformedModel;
|
||||||
|
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||||
import dan200.computercraft.api.turtle.TurtleSide;
|
import dan200.computercraft.api.turtle.TurtleSide;
|
||||||
|
import dan200.computercraft.impl.client.ClientPlatformHelper;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
import org.joml.Matrix4f;
|
import org.joml.Matrix4f;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
class TurtleUpgradeModellers {
|
class TurtleUpgradeModellers {
|
||||||
private static final Transformation leftTransform = getMatrixFor(-0.40625f);
|
private static final Transformation leftTransform = getMatrixFor(-0.4065f);
|
||||||
private static final Transformation rightTransform = getMatrixFor(0.40625f);
|
private static final Transformation rightTransform = getMatrixFor(0.4065f);
|
||||||
|
|
||||||
private static Transformation getMatrixFor(float offset) {
|
private static Transformation getMatrixFor(float offset) {
|
||||||
var matrix = new Matrix4f();
|
var matrix = new Matrix4f();
|
||||||
@@ -26,6 +33,23 @@ class TurtleUpgradeModellers {
|
|||||||
return new Transformation(matrix);
|
return new Transformation(matrix);
|
||||||
}
|
}
|
||||||
|
|
||||||
static final TurtleUpgradeModeller<ITurtleUpgrade> FLAT_ITEM = (upgrade, turtle, side) ->
|
static final TurtleUpgradeModeller<ITurtleUpgrade> UPGRADE_ITEM = new UpgradeItemModeller();
|
||||||
TransformedModel.of(upgrade.getCraftingItem(), side == TurtleSide.LEFT ? leftTransform : rightTransform);
|
|
||||||
|
private static class UpgradeItemModeller implements TurtleUpgradeModeller<ITurtleUpgrade> {
|
||||||
|
@Override
|
||||||
|
public TransformedModel getModel(ITurtleUpgrade upgrade, @Nullable ITurtleAccess turtle, TurtleSide side) {
|
||||||
|
return getModel(turtle == null ? upgrade.getCraftingItem() : upgrade.getUpgradeItem(turtle.getUpgradeNBTData(side)), side);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransformedModel getModel(ITurtleUpgrade upgrade, CompoundTag data, TurtleSide side) {
|
||||||
|
return getModel(upgrade.getUpgradeItem(data), side);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TransformedModel getModel(ItemStack stack, TurtleSide side) {
|
||||||
|
var model = Minecraft.getInstance().getItemRenderer().getItemModelShaper().getItemModel(stack);
|
||||||
|
if (stack.hasFoil()) model = ClientPlatformHelper.get().createdFoiledModel(model);
|
||||||
|
return new TransformedModel(model, side == TurtleSide.LEFT ? leftTransform : rightTransform);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,6 +5,7 @@
|
|||||||
package dan200.computercraft.impl.client;
|
package dan200.computercraft.impl.client;
|
||||||
|
|
||||||
import dan200.computercraft.impl.Services;
|
import dan200.computercraft.impl.Services;
|
||||||
|
import net.minecraft.client.renderer.RenderType;
|
||||||
import net.minecraft.client.resources.model.BakedModel;
|
import net.minecraft.client.resources.model.BakedModel;
|
||||||
import net.minecraft.client.resources.model.ModelManager;
|
import net.minecraft.client.resources.model.ModelManager;
|
||||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
import net.minecraft.client.resources.model.ModelResourceLocation;
|
||||||
@@ -24,6 +25,15 @@ public interface ClientPlatformHelper {
|
|||||||
*/
|
*/
|
||||||
BakedModel getModel(ModelManager manager, ResourceLocation location);
|
BakedModel getModel(ModelManager manager, ResourceLocation location);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap this model in a version which renders a foil/enchantment glint.
|
||||||
|
*
|
||||||
|
* @param model The model to wrap.
|
||||||
|
* @return The wrapped model.
|
||||||
|
* @see RenderType#glint()
|
||||||
|
*/
|
||||||
|
BakedModel createdFoiledModel(BakedModel model);
|
||||||
|
|
||||||
static ClientPlatformHelper get() {
|
static ClientPlatformHelper get() {
|
||||||
var instance = Instance.INSTANCE;
|
var instance = Instance.INSTANCE;
|
||||||
return instance == null ? Services.raise(ClientPlatformHelper.class, Instance.ERROR) : instance;
|
return instance == null ? Services.raise(ClientPlatformHelper.class, Instance.ERROR) : instance;
|
||||||
|
@@ -5,9 +5,11 @@
|
|||||||
package dan200.computercraft.api.pocket;
|
package dan200.computercraft.api.pocket;
|
||||||
|
|
||||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||||
|
import dan200.computercraft.api.upgrades.UpgradeBase;
|
||||||
import net.minecraft.nbt.CompoundTag;
|
import net.minecraft.nbt.CompoundTag;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.world.entity.Entity;
|
import net.minecraft.world.entity.Entity;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -69,6 +71,8 @@ public interface IPocketAccess {
|
|||||||
*
|
*
|
||||||
* @return The upgrade's NBT.
|
* @return The upgrade's NBT.
|
||||||
* @see #updateUpgradeNBTData()
|
* @see #updateUpgradeNBTData()
|
||||||
|
* @see UpgradeBase#getUpgradeItem(CompoundTag)
|
||||||
|
* @see UpgradeBase#getUpgradeData(ItemStack)
|
||||||
*/
|
*/
|
||||||
CompoundTag getUpgradeNBTData();
|
CompoundTag getUpgradeNBTData();
|
||||||
|
|
||||||
@@ -80,7 +84,10 @@ public interface IPocketAccess {
|
|||||||
void updateUpgradeNBTData();
|
void updateUpgradeNBTData();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove the current peripheral and create a new one. You may wish to do this if the methods available change.
|
* Remove the current peripheral and create a new one.
|
||||||
|
* <p>
|
||||||
|
* You may wish to do this if the methods available change, for instance when the {@linkplain #getEntity() owning
|
||||||
|
* entity} changes.
|
||||||
*/
|
*/
|
||||||
void invalidatePeripheral();
|
void invalidatePeripheral();
|
||||||
|
|
||||||
@@ -88,6 +95,8 @@ public interface IPocketAccess {
|
|||||||
* Get a list of all upgrades for the pocket computer.
|
* Get a list of all upgrades for the pocket computer.
|
||||||
*
|
*
|
||||||
* @return A collection of all upgrade names.
|
* @return A collection of all upgrade names.
|
||||||
|
* @deprecated This is a relic of a previous API, which no longer makes sense with newer versions of ComputerCraft.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(forRemoval = true)
|
||||||
Map<ResourceLocation, IPeripheral> getUpgrades();
|
Map<ResourceLocation, IPeripheral> getUpgrades();
|
||||||
}
|
}
|
||||||
|
@@ -8,10 +8,13 @@ import com.mojang.authlib.GameProfile;
|
|||||||
import dan200.computercraft.api.lua.ILuaCallback;
|
import dan200.computercraft.api.lua.ILuaCallback;
|
||||||
import dan200.computercraft.api.lua.MethodResult;
|
import dan200.computercraft.api.lua.MethodResult;
|
||||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||||
|
import dan200.computercraft.api.upgrades.UpgradeBase;
|
||||||
|
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.core.Direction;
|
import net.minecraft.core.Direction;
|
||||||
import net.minecraft.nbt.CompoundTag;
|
import net.minecraft.nbt.CompoundTag;
|
||||||
import net.minecraft.world.Container;
|
import net.minecraft.world.Container;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
import net.minecraft.world.level.Level;
|
import net.minecraft.world.level.Level;
|
||||||
import net.minecraft.world.phys.Vec3;
|
import net.minecraft.world.phys.Vec3;
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
@@ -245,23 +248,51 @@ public interface ITurtleAccess {
|
|||||||
void playAnimation(TurtleAnimation animation);
|
void playAnimation(TurtleAnimation animation);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the turtle on the specified side of the turtle, if there is one.
|
* Returns the upgrade on the specified side of the turtle, if there is one.
|
||||||
*
|
*
|
||||||
* @param side The side to get the upgrade from.
|
* @param side The side to get the upgrade from.
|
||||||
* @return The upgrade on the specified side of the turtle, if there is one.
|
* @return The upgrade on the specified side of the turtle, if there is one.
|
||||||
* @see #setUpgrade(TurtleSide, ITurtleUpgrade)
|
* @see #getUpgradeWithData(TurtleSide)
|
||||||
|
* @see #setUpgradeWithData(TurtleSide, UpgradeData)
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
ITurtleUpgrade getUpgrade(TurtleSide side);
|
ITurtleUpgrade getUpgrade(TurtleSide side);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the upgrade on the specified side of the turtle, along with its {@linkplain #getUpgradeNBTData(TurtleSide)
|
||||||
|
* update data}.
|
||||||
|
*
|
||||||
|
* @param side The side to get the upgrade from.
|
||||||
|
* @return The upgrade on the specified side of the turtle, along with its upgrade data, if there is one.
|
||||||
|
* @see #getUpgradeWithData(TurtleSide)
|
||||||
|
* @see #setUpgradeWithData(TurtleSide, UpgradeData)
|
||||||
|
*/
|
||||||
|
default @Nullable UpgradeData<ITurtleUpgrade> getUpgradeWithData(TurtleSide side) {
|
||||||
|
var upgrade = getUpgrade(side);
|
||||||
|
return upgrade == null ? null : UpgradeData.of(upgrade, getUpgradeNBTData(side));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the upgrade for a given side, resetting peripherals and clearing upgrade specific data.
|
* Set the upgrade for a given side, resetting peripherals and clearing upgrade specific data.
|
||||||
*
|
*
|
||||||
* @param side The side to set the upgrade on.
|
* @param side The side to set the upgrade on.
|
||||||
* @param upgrade The upgrade to set, may be {@code null} to clear.
|
* @param upgrade The upgrade to set, may be {@code null} to clear.
|
||||||
* @see #getUpgrade(TurtleSide)
|
* @see #getUpgrade(TurtleSide)
|
||||||
|
* @deprecated Use {@link #setUpgradeWithData(TurtleSide, UpgradeData)}
|
||||||
*/
|
*/
|
||||||
void setUpgrade(TurtleSide side, @Nullable ITurtleUpgrade upgrade);
|
@Deprecated
|
||||||
|
default void setUpgrade(TurtleSide side, @Nullable ITurtleUpgrade upgrade) {
|
||||||
|
setUpgradeWithData(side, upgrade == null ? null : UpgradeData.ofDefault(upgrade));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the upgrade for a given side and its upgrade data.
|
||||||
|
*
|
||||||
|
* @param side The side to set the upgrade on.
|
||||||
|
* @param upgrade The upgrade to set, may be {@code null} to clear.
|
||||||
|
* @see #getUpgradeWithData(TurtleSide)
|
||||||
|
*/
|
||||||
|
void setUpgradeWithData(TurtleSide side, @Nullable UpgradeData<ITurtleUpgrade> upgrade);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the peripheral created by the upgrade on the specified side of the turtle, if there is one.
|
* Returns the peripheral created by the upgrade on the specified side of the turtle, if there is one.
|
||||||
@@ -281,6 +312,8 @@ public interface ITurtleAccess {
|
|||||||
* @param side The side to get the upgrade data for.
|
* @param side The side to get the upgrade data for.
|
||||||
* @return The upgrade-specific data.
|
* @return The upgrade-specific data.
|
||||||
* @see #updateUpgradeNBTData(TurtleSide)
|
* @see #updateUpgradeNBTData(TurtleSide)
|
||||||
|
* @see UpgradeBase#getUpgradeItem(CompoundTag)
|
||||||
|
* @see UpgradeBase#getUpgradeData(ItemStack)
|
||||||
*/
|
*/
|
||||||
CompoundTag getUpgradeNBTData(TurtleSide side);
|
CompoundTag getUpgradeNBTData(TurtleSide side);
|
||||||
|
|
||||||
|
@@ -7,6 +7,7 @@ package dan200.computercraft.api.turtle;
|
|||||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||||
import dan200.computercraft.api.upgrades.UpgradeBase;
|
import dan200.computercraft.api.upgrades.UpgradeBase;
|
||||||
import net.minecraft.core.Direction;
|
import net.minecraft.core.Direction;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@@ -79,4 +80,17 @@ public interface ITurtleUpgrade extends UpgradeBase {
|
|||||||
*/
|
*/
|
||||||
default void update(ITurtleAccess turtle, TurtleSide side) {
|
default void update(ITurtleAccess turtle, TurtleSide side) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get upgrade data that should be persisted when the turtle was broken.
|
||||||
|
* <p>
|
||||||
|
* This method should be overridden when you don't need to store all upgrade data by default. For instance, if you
|
||||||
|
* store peripheral state in the upgrade data, which should be lost when the turtle is broken.
|
||||||
|
*
|
||||||
|
* @param upgradeData Data that currently stored for this upgrade
|
||||||
|
* @return Filtered version of this data.
|
||||||
|
*/
|
||||||
|
default CompoundTag getPersistedData(CompoundTag upgradeData) {
|
||||||
|
return upgradeData;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,49 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.api.turtle;
|
||||||
|
|
||||||
|
import net.minecraft.util.StringRepresentable;
|
||||||
|
import net.minecraft.world.entity.EquipmentSlot;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if an equipped turtle item will consume durability.
|
||||||
|
*
|
||||||
|
* @see TurtleUpgradeDataProvider.ToolBuilder#consumeDurability(TurtleToolDurability)
|
||||||
|
*/
|
||||||
|
public enum TurtleToolDurability implements StringRepresentable {
|
||||||
|
/**
|
||||||
|
* The equipped tool always consumes durability when using.
|
||||||
|
*/
|
||||||
|
ALWAYS("always"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The equipped tool consumes durability if it is {@linkplain ItemStack#isEnchanted() enchanted} or has
|
||||||
|
* {@linkplain ItemStack#getAttributeModifiers(EquipmentSlot) custom attribute modifiers}.
|
||||||
|
*/
|
||||||
|
WHEN_ENCHANTED("when_enchanted"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The equipped tool never consumes durability. Tools which have been damaged cannot be used as upgrades.
|
||||||
|
*/
|
||||||
|
NEVER("never");
|
||||||
|
|
||||||
|
private final String serialisedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The codec which may be used for serialising/deserialising {@link TurtleToolDurability}s.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public static final StringRepresentable.EnumCodec<TurtleToolDurability> CODEC = StringRepresentable.fromEnum(TurtleToolDurability::values);
|
||||||
|
|
||||||
|
TurtleToolDurability(String serialisedName) {
|
||||||
|
this.serialisedName = serialisedName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSerializedName() {
|
||||||
|
return serialisedName;
|
||||||
|
}
|
||||||
|
}
|
@@ -13,8 +13,10 @@ import net.minecraft.data.DataGenerator;
|
|||||||
import net.minecraft.data.PackOutput;
|
import net.minecraft.data.PackOutput;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.tags.TagKey;
|
import net.minecraft.tags.TagKey;
|
||||||
|
import net.minecraft.world.entity.EquipmentSlot;
|
||||||
import net.minecraft.world.entity.ai.attributes.Attributes;
|
import net.minecraft.world.entity.ai.attributes.Attributes;
|
||||||
import net.minecraft.world.item.Item;
|
import net.minecraft.world.item.Item;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
import net.minecraft.world.level.block.Block;
|
import net.minecraft.world.level.block.Block;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
@@ -61,6 +63,8 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITur
|
|||||||
private @Nullable Item craftingItem;
|
private @Nullable Item craftingItem;
|
||||||
private @Nullable Float damageMultiplier = null;
|
private @Nullable Float damageMultiplier = null;
|
||||||
private @Nullable TagKey<Block> breakable;
|
private @Nullable TagKey<Block> breakable;
|
||||||
|
private boolean allowEnchantments = false;
|
||||||
|
private TurtleToolDurability consumeDurability = TurtleToolDurability.NEVER;
|
||||||
|
|
||||||
ToolBuilder(ResourceLocation id, TurtleUpgradeSerialiser<?> serialiser, Item toolItem) {
|
ToolBuilder(ResourceLocation id, TurtleUpgradeSerialiser<?> serialiser, Item toolItem) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
@@ -104,6 +108,28 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITur
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate that this upgrade allows items which have been {@linkplain ItemStack#isEnchanted() enchanted} or have
|
||||||
|
* {@linkplain ItemStack#getAttributeModifiers(EquipmentSlot) custom attribute modifiers}.
|
||||||
|
*
|
||||||
|
* @return The tool builder, for further use.
|
||||||
|
*/
|
||||||
|
public ToolBuilder allowEnchantments() {
|
||||||
|
allowEnchantments = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set when the tool will consume durability.
|
||||||
|
*
|
||||||
|
* @param durability The durability predicate.
|
||||||
|
* @return The tool builder, for further use.
|
||||||
|
*/
|
||||||
|
public ToolBuilder consumeDurability(TurtleToolDurability durability) {
|
||||||
|
consumeDurability = durability;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provide a list of breakable blocks. If not given, the tool can break all blocks. If given, only blocks
|
* Provide a list of breakable blocks. If not given, the tool can break all blocks. If given, only blocks
|
||||||
* in this tag, those in {@link ComputerCraftTags.Blocks#TURTLE_ALWAYS_BREAKABLE} and "insta-mine" ones can
|
* in this tag, those in {@link ComputerCraftTags.Blocks#TURTLE_ALWAYS_BREAKABLE} and "insta-mine" ones can
|
||||||
@@ -132,6 +158,10 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITur
|
|||||||
}
|
}
|
||||||
if (damageMultiplier != null) s.addProperty("damageMultiplier", damageMultiplier);
|
if (damageMultiplier != null) s.addProperty("damageMultiplier", damageMultiplier);
|
||||||
if (breakable != null) s.addProperty("breakable", breakable.location().toString());
|
if (breakable != null) s.addProperty("breakable", breakable.location().toString());
|
||||||
|
if (allowEnchantments) s.addProperty("allowEnchantments", true);
|
||||||
|
if (consumeDurability != TurtleToolDurability.NEVER) {
|
||||||
|
s.addProperty("consumeDurability", consumeDurability.getSerializedName());
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,10 +4,14 @@
|
|||||||
|
|
||||||
package dan200.computercraft.api.upgrades;
|
package dan200.computercraft.api.upgrades;
|
||||||
|
|
||||||
|
import dan200.computercraft.api.pocket.IPocketAccess;
|
||||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
|
import dan200.computercraft.api.pocket.IPocketUpgrade;
|
||||||
|
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||||
|
import dan200.computercraft.api.turtle.TurtleSide;
|
||||||
import dan200.computercraft.impl.PlatformHelper;
|
import dan200.computercraft.impl.PlatformHelper;
|
||||||
import net.minecraft.Util;
|
import net.minecraft.Util;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
|
||||||
@@ -50,6 +54,42 @@ public interface UpgradeBase {
|
|||||||
*/
|
*/
|
||||||
ItemStack getCraftingItem();
|
ItemStack getCraftingItem();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the item stack representing a currently equipped turtle upgrade.
|
||||||
|
* <p>
|
||||||
|
* While upgrades can store upgrade data ({@link ITurtleAccess#getUpgradeNBTData(TurtleSide)} and
|
||||||
|
* {@link IPocketAccess#getUpgradeNBTData()}}, by default this data is discarded when an upgrade is unequipped,
|
||||||
|
* and the original item stack is returned.
|
||||||
|
* <p>
|
||||||
|
* By overriding this method, you can create a new {@link ItemStack} which contains enough data to
|
||||||
|
* {@linkplain #getUpgradeData(ItemStack) re-create the upgrade data} if the item is re-equipped.
|
||||||
|
* <p>
|
||||||
|
* When overriding this, you should override {@link #getUpgradeData(ItemStack)} and {@link #isItemSuitable(ItemStack)}
|
||||||
|
* at the same time,
|
||||||
|
*
|
||||||
|
* @param upgradeData The current upgrade data. This should <strong>NOT</strong> be mutated.
|
||||||
|
* @return The item stack returned when unequipping.
|
||||||
|
*/
|
||||||
|
default ItemStack getUpgradeItem(CompoundTag upgradeData) {
|
||||||
|
return getCraftingItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract upgrade data from an {@link ItemStack}.
|
||||||
|
* <p>
|
||||||
|
* This upgrade data will be available with {@link ITurtleAccess#getUpgradeNBTData(TurtleSide)} or
|
||||||
|
* {@link IPocketAccess#getUpgradeNBTData()}.
|
||||||
|
* <p>
|
||||||
|
* This should be an inverse to {@link #getUpgradeItem(CompoundTag)}.
|
||||||
|
*
|
||||||
|
* @param stack The stack that was equipped by the turtle or pocket computer. This will have the same item as
|
||||||
|
* {@link #getCraftingItem()}.
|
||||||
|
* @return The upgrade data that should be set on the turtle or pocket computer.
|
||||||
|
*/
|
||||||
|
default CompoundTag getUpgradeData(ItemStack stack) {
|
||||||
|
return new CompoundTag();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if an item is suitable for being used for this upgrade.
|
* Determine if an item is suitable for being used for this upgrade.
|
||||||
* <p>
|
* <p>
|
||||||
|
@@ -0,0 +1,82 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.api.upgrades;
|
||||||
|
|
||||||
|
import dan200.computercraft.api.pocket.IPocketUpgrade;
|
||||||
|
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import org.jetbrains.annotations.Contract;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An upgrade (i.e. a {@link ITurtleUpgrade}) and its current upgrade data.
|
||||||
|
* <p>
|
||||||
|
* <strong>IMPORTANT:</strong> The {@link #data()} in an upgrade data is often a reference to the original upgrade data.
|
||||||
|
* Be careful to take a {@linkplain #copy() defensive copy} if you plan to use the data in this upgrade.
|
||||||
|
*
|
||||||
|
* @param upgrade The current upgrade.
|
||||||
|
* @param data The upgrade's data.
|
||||||
|
* @param <T> The type of upgrade, either {@link ITurtleUpgrade} or {@link IPocketUpgrade}.
|
||||||
|
*/
|
||||||
|
public record UpgradeData<T extends UpgradeBase>(T upgrade, CompoundTag data) {
|
||||||
|
/**
|
||||||
|
* A utility method to construct a new {@link UpgradeData} instance.
|
||||||
|
*
|
||||||
|
* @param upgrade An upgrade.
|
||||||
|
* @param data The upgrade's data.
|
||||||
|
* @param <T> The type of upgrade.
|
||||||
|
* @return The new {@link UpgradeData} instance.
|
||||||
|
*/
|
||||||
|
public static <T extends UpgradeBase> UpgradeData<T> of(T upgrade, CompoundTag data) {
|
||||||
|
return new UpgradeData<>(upgrade, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an {@link UpgradeData} containing the default {@linkplain #data() data} for an upgrade.
|
||||||
|
*
|
||||||
|
* @param upgrade The upgrade instance.
|
||||||
|
* @param <T> The type of upgrade.
|
||||||
|
* @return The default upgrade data.
|
||||||
|
*/
|
||||||
|
public static <T extends UpgradeBase> UpgradeData<T> ofDefault(T upgrade) {
|
||||||
|
return of(upgrade, upgrade.getUpgradeData(upgrade.getCraftingItem()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Take a copy of a (possibly {@code null}) {@link UpgradeData} instance.
|
||||||
|
*
|
||||||
|
* @param upgrade The copied upgrade data.
|
||||||
|
* @param <T> The type of upgrade.
|
||||||
|
* @return The newly created upgrade data.
|
||||||
|
*/
|
||||||
|
@Contract("!null -> !null; null -> null")
|
||||||
|
public static <T extends UpgradeBase> @Nullable UpgradeData<T> copyOf(@Nullable UpgradeData<T> upgrade) {
|
||||||
|
return upgrade == null ? null : upgrade.copy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the {@linkplain UpgradeBase#getUpgradeItem(CompoundTag) upgrade item} for this upgrade.
|
||||||
|
* <p>
|
||||||
|
* This returns a defensive copy of the item, to prevent accidental mutation of the upgrade data or original
|
||||||
|
* {@linkplain UpgradeBase#getCraftingItem() upgrade stack}.
|
||||||
|
*
|
||||||
|
* @return This upgrade's item.
|
||||||
|
*/
|
||||||
|
public ItemStack getUpgradeItem() {
|
||||||
|
return upgrade.getUpgradeItem(data).copy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Take a copy of this {@link UpgradeData}. This returns a new instance with the same upgrade and a fresh copy of
|
||||||
|
* the upgrade data.
|
||||||
|
*
|
||||||
|
* @return A copy of the current instance.
|
||||||
|
*/
|
||||||
|
public UpgradeData<T> copy() {
|
||||||
|
return new UpgradeData<>(upgrade(), data().copy());
|
||||||
|
}
|
||||||
|
}
|
@@ -23,10 +23,7 @@ import org.apache.logging.log4j.LogManager;
|
|||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
@@ -104,7 +101,7 @@ public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends Upgra
|
|||||||
protected abstract void addUpgrades(Consumer<Upgrade<R>> addUpgrade);
|
protected abstract void addUpgrades(Consumer<Upgrade<R>> addUpgrade);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final CompletableFuture<?> run(CachedOutput cache) {
|
public CompletableFuture<?> run(CachedOutput cache) {
|
||||||
var base = output.getOutputFolder().resolve("data");
|
var base = output.getOutputFolder().resolve("data");
|
||||||
|
|
||||||
Set<ResourceLocation> seen = new HashSet<>();
|
Set<ResourceLocation> seen = new HashSet<>();
|
||||||
@@ -127,7 +124,7 @@ public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends Upgra
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.upgrades = upgrades;
|
this.upgrades = Collections.unmodifiableList(upgrades);
|
||||||
return Util.sequenceFailFast(futures);
|
return Util.sequenceFailFast(futures);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,5 +163,21 @@ public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends Upgra
|
|||||||
public void add(Consumer<Upgrade<R>> add) {
|
public void add(Consumer<Upgrade<R>> add) {
|
||||||
add.accept(this);
|
add.accept(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a new {@link Upgrade} which requires the given mod to be present.
|
||||||
|
* <p>
|
||||||
|
* This uses mod-loader-specific hooks (Forge's crafting conditions and Fabric's resource conditions). If using
|
||||||
|
* this in a multi-loader setup, you must generate resources separately for the two loaders.
|
||||||
|
*
|
||||||
|
* @param modId The id of the mod.
|
||||||
|
* @return A new upgrade instance.
|
||||||
|
*/
|
||||||
|
public Upgrade<R> requireMod(String modId) {
|
||||||
|
return new Upgrade<>(id, serialiser, json -> {
|
||||||
|
PlatformHelper.get().addRequiredModCondition(json, modId);
|
||||||
|
serialise.accept(json);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
package dan200.computercraft.impl;
|
package dan200.computercraft.impl;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import dan200.computercraft.api.upgrades.UpgradeDataProvider;
|
||||||
import net.minecraft.core.Registry;
|
import net.minecraft.core.Registry;
|
||||||
import net.minecraft.nbt.CompoundTag;
|
import net.minecraft.nbt.CompoundTag;
|
||||||
import net.minecraft.resources.ResourceKey;
|
import net.minecraft.resources.ResourceKey;
|
||||||
@@ -63,6 +65,15 @@ public interface PlatformHelper {
|
|||||||
return item.getTag();
|
return item.getTag();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a resource condition which requires a mod to be loaded. This should be used by data providers such as
|
||||||
|
* {@link UpgradeDataProvider}.
|
||||||
|
*
|
||||||
|
* @param object The JSON object we're generating.
|
||||||
|
* @param modId The mod ID that we require.
|
||||||
|
*/
|
||||||
|
void addRequiredModCondition(JsonObject object, String modId);
|
||||||
|
|
||||||
final class Instance {
|
final class Instance {
|
||||||
static final @Nullable PlatformHelper INSTANCE;
|
static final @Nullable PlatformHelper INSTANCE;
|
||||||
static final @Nullable Throwable ERROR;
|
static final @Nullable Throwable ERROR;
|
||||||
|
@@ -7,6 +7,7 @@ import cc.tweaked.gradle.clientClasses
|
|||||||
import cc.tweaked.gradle.commonClasses
|
import cc.tweaked.gradle.commonClasses
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
|
id("cc-tweaked.publishing")
|
||||||
id("cc-tweaked.vanilla")
|
id("cc-tweaked.vanilla")
|
||||||
id("cc-tweaked.gametest")
|
id("cc-tweaked.gametest")
|
||||||
}
|
}
|
||||||
@@ -25,6 +26,7 @@ dependencies {
|
|||||||
clientImplementation(clientClasses(project(":common-api")))
|
clientImplementation(clientClasses(project(":common-api")))
|
||||||
|
|
||||||
compileOnly(libs.bundles.externalMods.common)
|
compileOnly(libs.bundles.externalMods.common)
|
||||||
|
clientCompileOnly(variantOf(libs.emi) { classifier("api") })
|
||||||
|
|
||||||
compileOnly(libs.mixin)
|
compileOnly(libs.mixin)
|
||||||
annotationProcessorEverywhere(libs.autoService)
|
annotationProcessorEverywhere(libs.autoService)
|
||||||
|
@@ -13,6 +13,7 @@ import dan200.computercraft.client.render.RenderTypes;
|
|||||||
import dan200.computercraft.client.render.TurtleBlockEntityRenderer;
|
import dan200.computercraft.client.render.TurtleBlockEntityRenderer;
|
||||||
import dan200.computercraft.client.render.monitor.MonitorBlockEntityRenderer;
|
import dan200.computercraft.client.render.monitor.MonitorBlockEntityRenderer;
|
||||||
import dan200.computercraft.client.turtle.TurtleModemModeller;
|
import dan200.computercraft.client.turtle.TurtleModemModeller;
|
||||||
|
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
|
||||||
import dan200.computercraft.core.util.Colour;
|
import dan200.computercraft.core.util.Colour;
|
||||||
import dan200.computercraft.shared.ModRegistry;
|
import dan200.computercraft.shared.ModRegistry;
|
||||||
import dan200.computercraft.shared.common.IColouredItem;
|
import dan200.computercraft.shared.common.IColouredItem;
|
||||||
@@ -107,24 +108,6 @@ public final class ClientRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static final String[] EXTRA_MODELS = new String[]{
|
private static final String[] EXTRA_MODELS = new String[]{
|
||||||
// Turtle upgrades
|
|
||||||
"block/turtle_modem_normal_off_left",
|
|
||||||
"block/turtle_modem_normal_on_left",
|
|
||||||
"block/turtle_modem_normal_off_right",
|
|
||||||
"block/turtle_modem_normal_on_right",
|
|
||||||
|
|
||||||
"block/turtle_modem_advanced_off_left",
|
|
||||||
"block/turtle_modem_advanced_on_left",
|
|
||||||
"block/turtle_modem_advanced_off_right",
|
|
||||||
"block/turtle_modem_advanced_on_right",
|
|
||||||
|
|
||||||
"block/turtle_crafting_table_left",
|
|
||||||
"block/turtle_crafting_table_right",
|
|
||||||
|
|
||||||
"block/turtle_speaker_left",
|
|
||||||
"block/turtle_speaker_right",
|
|
||||||
|
|
||||||
// Turtle block renderer
|
|
||||||
"block/turtle_colour",
|
"block/turtle_colour",
|
||||||
"block/turtle_elf_overlay",
|
"block/turtle_elf_overlay",
|
||||||
"block/turtle_rainbow_overlay",
|
"block/turtle_rainbow_overlay",
|
||||||
@@ -133,6 +116,7 @@ public final class ClientRegistry {
|
|||||||
|
|
||||||
public static void registerExtraModels(Consumer<ResourceLocation> register) {
|
public static void registerExtraModels(Consumer<ResourceLocation> register) {
|
||||||
for (var model : EXTRA_MODELS) register.accept(new ResourceLocation(ComputerCraftAPI.MOD_ID, model));
|
for (var model : EXTRA_MODELS) register.accept(new ResourceLocation(ComputerCraftAPI.MOD_ID, model));
|
||||||
|
TurtleUpgradeModellers.getDependencies().forEach(register);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void registerItemColours(BiConsumer<ItemColor, ItemLike> register) {
|
public static void registerItemColours(BiConsumer<ItemColor, ItemLike> register) {
|
||||||
|
@@ -26,9 +26,11 @@ public class TurtleScreen extends AbstractComputerScreen<TurtleMenu> {
|
|||||||
private static final ResourceLocation BACKGROUND_NORMAL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "textures/gui/turtle_normal.png");
|
private static final ResourceLocation BACKGROUND_NORMAL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "textures/gui/turtle_normal.png");
|
||||||
private static final ResourceLocation BACKGROUND_ADVANCED = new ResourceLocation(ComputerCraftAPI.MOD_ID, "textures/gui/turtle_advanced.png");
|
private static final ResourceLocation BACKGROUND_ADVANCED = new ResourceLocation(ComputerCraftAPI.MOD_ID, "textures/gui/turtle_advanced.png");
|
||||||
|
|
||||||
private static final int TEX_WIDTH = 254;
|
private static final int TEX_WIDTH = 278;
|
||||||
private static final int TEX_HEIGHT = 217;
|
private static final int TEX_HEIGHT = 217;
|
||||||
|
|
||||||
|
private static final int FULL_TEX_SIZE = 512;
|
||||||
|
|
||||||
public TurtleScreen(TurtleMenu container, Inventory player, Component title) {
|
public TurtleScreen(TurtleMenu container, Inventory player, Component title) {
|
||||||
super(container, player, title, BORDER);
|
super(container, player, title, BORDER);
|
||||||
|
|
||||||
@@ -45,16 +47,16 @@ public class TurtleScreen extends AbstractComputerScreen<TurtleMenu> {
|
|||||||
protected void renderBg(PoseStack transform, float partialTicks, int mouseX, int mouseY) {
|
protected void renderBg(PoseStack transform, float partialTicks, int mouseX, int mouseY) {
|
||||||
var advanced = family == ComputerFamily.ADVANCED;
|
var advanced = family == ComputerFamily.ADVANCED;
|
||||||
RenderSystem.setShaderTexture(0, advanced ? BACKGROUND_ADVANCED : BACKGROUND_NORMAL);
|
RenderSystem.setShaderTexture(0, advanced ? BACKGROUND_ADVANCED : BACKGROUND_NORMAL);
|
||||||
blit(transform, leftPos + AbstractComputerMenu.SIDEBAR_WIDTH, topPos, 0, 0, TEX_WIDTH, TEX_HEIGHT);
|
blit(transform, leftPos + AbstractComputerMenu.SIDEBAR_WIDTH, topPos, 0, 0, 0, TEX_WIDTH, TEX_HEIGHT, FULL_TEX_SIZE, FULL_TEX_SIZE);
|
||||||
|
|
||||||
|
// Render selected slot
|
||||||
var slot = getMenu().getSelectedSlot();
|
var slot = getMenu().getSelectedSlot();
|
||||||
if (slot >= 0) {
|
if (slot >= 0) {
|
||||||
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
|
|
||||||
var slotX = slot % 4;
|
var slotX = slot % 4;
|
||||||
var slotY = slot / 4;
|
var slotY = slot / 4;
|
||||||
blit(transform,
|
blit(transform,
|
||||||
leftPos + TURTLE_START_X - 2 + slotX * 18, topPos + PLAYER_START_Y - 2 + slotY * 18,
|
leftPos + TURTLE_START_X - 2 + slotX * 18, topPos + PLAYER_START_Y - 2 + slotY * 18, 0,
|
||||||
0, 217, 24, 24
|
0, 217, 24, 24, FULL_TEX_SIZE, FULL_TEX_SIZE
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -68,14 +68,11 @@ public class DynamicImageButton extends Button {
|
|||||||
RenderSystem.enableDepthTest();
|
RenderSystem.enableDepthTest();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Component getMessage() {
|
|
||||||
return message.get().message;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(PoseStack stack, int mouseX, int mouseY, float partialTicks) {
|
public void render(PoseStack stack, int mouseX, int mouseY, float partialTicks) {
|
||||||
setTooltip(message.get().tooltip());
|
var message = this.message.get();
|
||||||
|
setMessage(message.message());
|
||||||
|
setTooltip(message.tooltip());
|
||||||
super.render(stack, mouseX, mouseY, partialTicks);
|
super.render(stack, mouseX, mouseY, partialTicks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1,44 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.client.integration.emi;
|
||||||
|
|
||||||
|
import dan200.computercraft.api.turtle.TurtleSide;
|
||||||
|
import dan200.computercraft.shared.ModRegistry;
|
||||||
|
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
||||||
|
import dan200.computercraft.shared.turtle.items.TurtleItem;
|
||||||
|
import dev.emi.emi.api.EmiEntrypoint;
|
||||||
|
import dev.emi.emi.api.EmiPlugin;
|
||||||
|
import dev.emi.emi.api.EmiRegistry;
|
||||||
|
import dev.emi.emi.api.stack.Comparison;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
|
||||||
|
import java.util.function.BiPredicate;
|
||||||
|
|
||||||
|
@EmiEntrypoint
|
||||||
|
public class EMIComputerCraft implements EmiPlugin {
|
||||||
|
@Override
|
||||||
|
public void register(EmiRegistry registry) {
|
||||||
|
registry.setDefaultComparison(ModRegistry.Items.TURTLE_NORMAL.get(), turtleComparison);
|
||||||
|
registry.setDefaultComparison(ModRegistry.Items.TURTLE_ADVANCED.get(), turtleComparison);
|
||||||
|
|
||||||
|
registry.setDefaultComparison(ModRegistry.Items.POCKET_COMPUTER_NORMAL.get(), pocketComparison);
|
||||||
|
registry.setDefaultComparison(ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get(), pocketComparison);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Comparison turtleComparison = compareStacks((left, right) ->
|
||||||
|
left.getItem() instanceof TurtleItem turtle
|
||||||
|
&& turtle.getUpgrade(left, TurtleSide.LEFT) == turtle.getUpgrade(right, TurtleSide.LEFT)
|
||||||
|
&& turtle.getUpgrade(left, TurtleSide.RIGHT) == turtle.getUpgrade(right, TurtleSide.RIGHT));
|
||||||
|
|
||||||
|
private static final Comparison pocketComparison = compareStacks((left, right) ->
|
||||||
|
left.getItem() instanceof PocketComputerItem && PocketComputerItem.getUpgrade(left) == PocketComputerItem.getUpgrade(right));
|
||||||
|
|
||||||
|
private static Comparison compareStacks(BiPredicate<ItemStack, ItemStack> test) {
|
||||||
|
return Comparison.of((left, right) -> {
|
||||||
|
ItemStack leftStack = left.getItemStack(), rightStack = right.getItemStack();
|
||||||
|
return leftStack.getItem() == rightStack.getItem() && test.test(leftStack, rightStack);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,103 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.client.model.turtle;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
|
||||||
|
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||||
|
import com.mojang.blaze3d.vertex.VertexFormatElement;
|
||||||
|
import com.mojang.math.Transformation;
|
||||||
|
import net.minecraft.client.renderer.block.model.BakedQuad;
|
||||||
|
import net.minecraft.client.resources.model.BakedModel;
|
||||||
|
import net.minecraft.core.Direction;
|
||||||
|
import org.joml.Matrix4f;
|
||||||
|
import org.joml.Vector4f;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies a {@link Transformation} (or rather a {@link Matrix4f}) to a list of {@link BakedQuad}s.
|
||||||
|
* <p>
|
||||||
|
* This does a little bit of magic compared with other system (i.e. Forge's {@code QuadTransformers}), as it needs to
|
||||||
|
* handle flipping models upside down.
|
||||||
|
* <p>
|
||||||
|
* This is typically used with a {@link BakedModel} subclass - see the loader-specific projects.
|
||||||
|
*/
|
||||||
|
public class ModelTransformer {
|
||||||
|
private static final int[] INVERSE_ORDER = new int[]{ 3, 2, 1, 0 };
|
||||||
|
|
||||||
|
private static final int STRIDE = DefaultVertexFormat.BLOCK.getIntegerSize();
|
||||||
|
private static final int POS_OFFSET = findOffset(DefaultVertexFormat.BLOCK, DefaultVertexFormat.ELEMENT_POSITION);
|
||||||
|
|
||||||
|
protected final Matrix4f transformation;
|
||||||
|
protected final boolean invert;
|
||||||
|
private @Nullable TransformedQuads cache;
|
||||||
|
|
||||||
|
public ModelTransformer(Transformation transformation) {
|
||||||
|
this.transformation = transformation.getMatrix();
|
||||||
|
invert = transformation.getMatrix().determinant() < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<BakedQuad> transform(List<BakedQuad> quads) {
|
||||||
|
if (quads.isEmpty()) return List.of();
|
||||||
|
|
||||||
|
// We do some basic caching here to avoid recomputing every frame. Most turtle models don't have culled faces,
|
||||||
|
// so it's not worth being smarter here.
|
||||||
|
var cache = this.cache;
|
||||||
|
if (cache != null && quads.equals(cache.original())) return cache.transformed();
|
||||||
|
|
||||||
|
List<BakedQuad> transformed = new ArrayList<>(quads.size());
|
||||||
|
for (var quad : quads) transformed.add(transformQuad(quad));
|
||||||
|
this.cache = new TransformedQuads(quads, transformed);
|
||||||
|
return transformed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BakedQuad transformQuad(BakedQuad quad) {
|
||||||
|
var inputData = quad.getVertices();
|
||||||
|
var outputData = new int[inputData.length];
|
||||||
|
for (var i = 0; i < 4; i++) {
|
||||||
|
var inStart = STRIDE * i;
|
||||||
|
// Reverse the order of the quads if we're inverting
|
||||||
|
var outStart = getVertexOffset(i, invert);
|
||||||
|
System.arraycopy(inputData, inStart, outputData, outStart, STRIDE);
|
||||||
|
|
||||||
|
// Apply the matrix to our position
|
||||||
|
var inPosStart = inStart + POS_OFFSET;
|
||||||
|
var outPosStart = outStart + POS_OFFSET;
|
||||||
|
|
||||||
|
var x = Float.intBitsToFloat(inputData[inPosStart]);
|
||||||
|
var y = Float.intBitsToFloat(inputData[inPosStart + 1]);
|
||||||
|
var z = Float.intBitsToFloat(inputData[inPosStart + 2]);
|
||||||
|
|
||||||
|
// Transform the position
|
||||||
|
var pos = new Vector4f(x, y, z, 1);
|
||||||
|
transformation.transformProject(pos);
|
||||||
|
|
||||||
|
outputData[outPosStart] = Float.floatToRawIntBits(pos.x());
|
||||||
|
outputData[outPosStart + 1] = Float.floatToRawIntBits(pos.y());
|
||||||
|
outputData[outPosStart + 2] = Float.floatToRawIntBits(pos.z());
|
||||||
|
}
|
||||||
|
|
||||||
|
var direction = Direction.rotate(transformation, quad.getDirection());
|
||||||
|
return new BakedQuad(outputData, quad.getTintIndex(), direction, quad.getSprite(), quad.isShade());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getVertexOffset(int vertex, boolean invert) {
|
||||||
|
return (invert ? ModelTransformer.INVERSE_ORDER[vertex] : vertex) * ModelTransformer.STRIDE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private record TransformedQuads(List<BakedQuad> original, List<BakedQuad> transformed) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int findOffset(VertexFormat format, VertexFormatElement element) {
|
||||||
|
var offset = 0;
|
||||||
|
for (var other : format.getElements()) {
|
||||||
|
if (other == element) return offset / Integer.BYTES;
|
||||||
|
offset += element.getByteSize();
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Cannot find " + element + " in " + format);
|
||||||
|
}
|
||||||
|
}
|
@@ -4,11 +4,13 @@
|
|||||||
|
|
||||||
package dan200.computercraft.client.model.turtle;
|
package dan200.computercraft.client.model.turtle;
|
||||||
|
|
||||||
|
import com.google.common.cache.CacheBuilder;
|
||||||
import com.mojang.blaze3d.vertex.PoseStack;
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
import com.mojang.math.Transformation;
|
import com.mojang.math.Transformation;
|
||||||
import dan200.computercraft.api.client.TransformedModel;
|
import dan200.computercraft.api.client.TransformedModel;
|
||||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||||
import dan200.computercraft.api.turtle.TurtleSide;
|
import dan200.computercraft.api.turtle.TurtleSide;
|
||||||
|
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||||
import dan200.computercraft.client.platform.ClientPlatformHelper;
|
import dan200.computercraft.client.platform.ClientPlatformHelper;
|
||||||
import dan200.computercraft.client.render.TurtleBlockEntityRenderer;
|
import dan200.computercraft.client.render.TurtleBlockEntityRenderer;
|
||||||
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
|
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
|
||||||
@@ -21,15 +23,17 @@ import net.minecraft.world.item.ItemStack;
|
|||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Combines several individual models together to form a turtle.
|
* Combines several individual models together to form a turtle.
|
||||||
|
*
|
||||||
|
* @param <T> The type of the resulting "baked model".
|
||||||
*/
|
*/
|
||||||
public final class TurtleModelParts {
|
public final class TurtleModelParts<T> {
|
||||||
private static final Transformation identity, flip;
|
private static final Transformation identity, flip;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
@@ -42,33 +46,67 @@ public final class TurtleModelParts {
|
|||||||
flip = new Transformation(stack.last().pose());
|
flip = new Transformation(stack.last().pose());
|
||||||
}
|
}
|
||||||
|
|
||||||
public record Combination(
|
private record Combination(
|
||||||
boolean colour,
|
boolean colour,
|
||||||
@Nullable ITurtleUpgrade leftUpgrade,
|
@Nullable UpgradeData<ITurtleUpgrade> leftUpgrade,
|
||||||
@Nullable ITurtleUpgrade rightUpgrade,
|
@Nullable UpgradeData<ITurtleUpgrade> rightUpgrade,
|
||||||
@Nullable ResourceLocation overlay,
|
@Nullable ResourceLocation overlay,
|
||||||
boolean christmas,
|
boolean christmas,
|
||||||
boolean flip
|
boolean flip
|
||||||
) {
|
) {
|
||||||
|
Combination copy() {
|
||||||
|
if (leftUpgrade == null && rightUpgrade == null) return this;
|
||||||
|
return new Combination(
|
||||||
|
colour, UpgradeData.copyOf(leftUpgrade), UpgradeData.copyOf(rightUpgrade),
|
||||||
|
overlay, christmas, flip
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final BakedModel familyModel;
|
private final BakedModel familyModel;
|
||||||
private final BakedModel colourModel;
|
private final BakedModel colourModel;
|
||||||
private final Function<TransformedModel, BakedModel> transformer;
|
private final Function<TransformedModel, BakedModel> transformer;
|
||||||
|
private final Function<Combination, T> buildModel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A cache of {@link TransformedModel} to the transformed {@link BakedModel}. This helps us pool the transformed
|
* A cache of {@link TransformedModel} to the transformed {@link BakedModel}. This helps us pool the transformed
|
||||||
* instances, reducing memory usage and hopefully ensuring their caches are hit more often!
|
* instances, reducing memory usage and hopefully ensuring their caches are hit more often!
|
||||||
*/
|
*/
|
||||||
private final Map<TransformedModel, BakedModel> transformCache = new HashMap<>();
|
private final Map<TransformedModel, BakedModel> transformCache = CacheBuilder.newBuilder()
|
||||||
|
.concurrencyLevel(1)
|
||||||
|
.expireAfterAccess(30, TimeUnit.SECONDS)
|
||||||
|
.<TransformedModel, BakedModel>build()
|
||||||
|
.asMap();
|
||||||
|
|
||||||
public TurtleModelParts(BakedModel familyModel, BakedModel colourModel, ModelTransformer transformer) {
|
/**
|
||||||
|
* A cache of {@link Combination}s to the combined model.
|
||||||
|
*/
|
||||||
|
private final Map<Combination, T> modelCache = CacheBuilder.newBuilder()
|
||||||
|
.concurrencyLevel(1)
|
||||||
|
.expireAfterAccess(30, TimeUnit.SECONDS)
|
||||||
|
.<Combination, T>build()
|
||||||
|
.asMap();
|
||||||
|
|
||||||
|
public TurtleModelParts(BakedModel familyModel, BakedModel colourModel, ModelTransformer transformer, Function<List<BakedModel>, T> combineModel) {
|
||||||
this.familyModel = familyModel;
|
this.familyModel = familyModel;
|
||||||
this.colourModel = colourModel;
|
this.colourModel = colourModel;
|
||||||
this.transformer = x -> transformer.transform(x.getModel(), x.getMatrix());
|
this.transformer = x -> transformer.transform(x.getModel(), x.getMatrix());
|
||||||
|
buildModel = x -> combineModel.apply(buildModel(x));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Combination getCombination(ItemStack stack) {
|
public T getModel(ItemStack stack) {
|
||||||
|
var combination = getCombination(stack);
|
||||||
|
var existing = modelCache.get(combination);
|
||||||
|
if (existing != null) return existing;
|
||||||
|
|
||||||
|
// Take a defensive copy of the upgrade data, and add it to the cache.
|
||||||
|
var newCombination = combination.copy();
|
||||||
|
var newModel = buildModel.apply(newCombination);
|
||||||
|
modelCache.put(newCombination, newModel);
|
||||||
|
return newModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Combination getCombination(ItemStack stack) {
|
||||||
var christmas = Holiday.getCurrent() == Holiday.CHRISTMAS;
|
var christmas = Holiday.getCurrent() == Holiday.CHRISTMAS;
|
||||||
|
|
||||||
if (!(stack.getItem() instanceof TurtleItem turtle)) {
|
if (!(stack.getItem() instanceof TurtleItem turtle)) {
|
||||||
@@ -76,8 +114,8 @@ public final class TurtleModelParts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var colour = turtle.getColour(stack);
|
var colour = turtle.getColour(stack);
|
||||||
var leftUpgrade = turtle.getUpgrade(stack, TurtleSide.LEFT);
|
var leftUpgrade = turtle.getUpgradeWithData(stack, TurtleSide.LEFT);
|
||||||
var rightUpgrade = turtle.getUpgrade(stack, TurtleSide.RIGHT);
|
var rightUpgrade = turtle.getUpgradeWithData(stack, TurtleSide.RIGHT);
|
||||||
var overlay = turtle.getOverlay(stack);
|
var overlay = turtle.getOverlay(stack);
|
||||||
var label = turtle.getLabel(stack);
|
var label = turtle.getLabel(stack);
|
||||||
var flip = label != null && (label.equals("Dinnerbone") || label.equals("Grumm"));
|
var flip = label != null && (label.equals("Dinnerbone") || label.equals("Grumm"));
|
||||||
@@ -85,7 +123,7 @@ public final class TurtleModelParts {
|
|||||||
return new Combination(colour != -1, leftUpgrade, rightUpgrade, overlay, christmas, flip);
|
return new Combination(colour != -1, leftUpgrade, rightUpgrade, overlay, christmas, flip);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<BakedModel> buildModel(Combination combo) {
|
private List<BakedModel> buildModel(Combination combo) {
|
||||||
var mc = Minecraft.getInstance();
|
var mc = Minecraft.getInstance();
|
||||||
var modelManager = mc.getItemRenderer().getItemModelShaper().getModelManager();
|
var modelManager = mc.getItemRenderer().getItemModelShaper().getModelManager();
|
||||||
|
|
||||||
@@ -97,19 +135,20 @@ public final class TurtleModelParts {
|
|||||||
if (overlayModelLocation != null) {
|
if (overlayModelLocation != null) {
|
||||||
parts.add(transform(ClientPlatformHelper.get().getModel(modelManager, overlayModelLocation), transformation));
|
parts.add(transform(ClientPlatformHelper.get().getModel(modelManager, overlayModelLocation), transformation));
|
||||||
}
|
}
|
||||||
if (combo.leftUpgrade() != null) {
|
|
||||||
var model = TurtleUpgradeModellers.getModel(combo.leftUpgrade(), null, TurtleSide.LEFT);
|
addUpgrade(parts, transformation, TurtleSide.LEFT, combo.leftUpgrade());
|
||||||
parts.add(transform(model.getModel(), transformation.compose(model.getMatrix())));
|
addUpgrade(parts, transformation, TurtleSide.RIGHT, combo.rightUpgrade());
|
||||||
}
|
|
||||||
if (combo.rightUpgrade() != null) {
|
|
||||||
var model = TurtleUpgradeModellers.getModel(combo.rightUpgrade(), null, TurtleSide.RIGHT);
|
|
||||||
parts.add(transform(model.getModel(), transformation.compose(model.getMatrix())));
|
|
||||||
}
|
|
||||||
|
|
||||||
return parts;
|
return parts;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BakedModel transform(BakedModel model, Transformation transformation) {
|
private void addUpgrade(List<BakedModel> parts, Transformation transformation, TurtleSide side, @Nullable UpgradeData<ITurtleUpgrade> upgrade) {
|
||||||
|
if (upgrade == null) return;
|
||||||
|
var model = TurtleUpgradeModellers.getModel(upgrade.upgrade(), upgrade.data(), side);
|
||||||
|
parts.add(transform(model.getModel(), transformation.compose(model.getMatrix())));
|
||||||
|
}
|
||||||
|
|
||||||
|
private BakedModel transform(BakedModel model, Transformation transformation) {
|
||||||
if (transformation.equals(Transformation.identity())) return model;
|
if (transformation.equals(Transformation.identity())) return model;
|
||||||
return transformCache.computeIfAbsent(new TransformedModel(model, transformation), transformer);
|
return transformCache.computeIfAbsent(new TransformedModel(model, transformation), transformer);
|
||||||
}
|
}
|
||||||
|
@@ -4,8 +4,13 @@
|
|||||||
|
|
||||||
package dan200.computercraft.client.platform;
|
package dan200.computercraft.client.platform;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
import dan200.computercraft.shared.network.NetworkMessage;
|
import dan200.computercraft.shared.network.NetworkMessage;
|
||||||
import dan200.computercraft.shared.network.server.ServerNetworkContext;
|
import dan200.computercraft.shared.network.server.ServerNetworkContext;
|
||||||
|
import net.minecraft.client.renderer.MultiBufferSource;
|
||||||
|
import net.minecraft.client.resources.model.BakedModel;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
public interface ClientPlatformHelper extends dan200.computercraft.impl.client.ClientPlatformHelper {
|
public interface ClientPlatformHelper extends dan200.computercraft.impl.client.ClientPlatformHelper {
|
||||||
static ClientPlatformHelper get() {
|
static ClientPlatformHelper get() {
|
||||||
@@ -18,4 +23,16 @@ public interface ClientPlatformHelper extends dan200.computercraft.impl.client.C
|
|||||||
* @param message The message to send.
|
* @param message The message to send.
|
||||||
*/
|
*/
|
||||||
void sendToServer(NetworkMessage<ServerNetworkContext> message);
|
void sendToServer(NetworkMessage<ServerNetworkContext> message);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a {@link BakedModel}, using any loader-specific hooks.
|
||||||
|
*
|
||||||
|
* @param transform The current matrix transformation to apply.
|
||||||
|
* @param buffers The current pool of render buffers.
|
||||||
|
* @param model The model to draw.
|
||||||
|
* @param lightmapCoord The current packed lightmap coordinate.
|
||||||
|
* @param overlayLight The current overlay light.
|
||||||
|
* @param tints Block colour tints to apply to the model.
|
||||||
|
*/
|
||||||
|
void renderBakedModel(PoseStack transform, MultiBufferSource buffers, BakedModel model, int lightmapCoord, int overlayLight, @Nullable int[] tints);
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,103 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.client.render;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||||
|
import dan200.computercraft.client.model.turtle.ModelTransformer;
|
||||||
|
import dan200.computercraft.client.platform.ClientPlatformHelper;
|
||||||
|
import net.minecraft.client.renderer.MultiBufferSource;
|
||||||
|
import net.minecraft.client.renderer.block.model.BakedQuad;
|
||||||
|
import net.minecraft.client.renderer.entity.ItemRenderer;
|
||||||
|
import net.minecraft.client.resources.model.BakedModel;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import org.joml.Vector4f;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utilities for rendering {@link BakedModel}s and {@link BakedQuad}s.
|
||||||
|
*/
|
||||||
|
public final class ModelRenderer {
|
||||||
|
private ModelRenderer() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a list of {@linkplain BakedQuad quads} to a buffer.
|
||||||
|
* <p>
|
||||||
|
* This is not intended to be used directly, but instead by {@link ClientPlatformHelper#renderBakedModel(PoseStack, MultiBufferSource, BakedModel, int, int, int[])}. The
|
||||||
|
* implementation here is pretty similar to {@link ItemRenderer#renderQuadList(PoseStack, VertexConsumer, List, ItemStack, int, int)},
|
||||||
|
* but supports inverted quads (i.e. those with a negative scale).
|
||||||
|
*
|
||||||
|
* @param transform The current matrix transformation to apply.
|
||||||
|
* @param buffer The buffer to draw to.
|
||||||
|
* @param quads The quads to draw.
|
||||||
|
* @param lightmapCoord The current packed lightmap coordinate.
|
||||||
|
* @param overlayLight The current overlay light.
|
||||||
|
* @param tints Block colour tints to apply to the model.
|
||||||
|
*/
|
||||||
|
public static void renderQuads(PoseStack transform, VertexConsumer buffer, List<BakedQuad> quads, int lightmapCoord, int overlayLight, @Nullable int[] tints) {
|
||||||
|
var matrix = transform.last();
|
||||||
|
var inverted = matrix.pose().determinant() < 0;
|
||||||
|
|
||||||
|
for (var bakedquad : quads) {
|
||||||
|
var tint = -1;
|
||||||
|
if (tints != null && bakedquad.isTinted()) {
|
||||||
|
var idx = bakedquad.getTintIndex();
|
||||||
|
if (idx >= 0 && idx < tints.length) tint = tints[bakedquad.getTintIndex()];
|
||||||
|
}
|
||||||
|
|
||||||
|
var r = (float) (tint >> 16 & 255) / 255.0F;
|
||||||
|
var g = (float) (tint >> 8 & 255) / 255.0F;
|
||||||
|
var b = (float) (tint & 255) / 255.0F;
|
||||||
|
putBulkQuad(buffer, matrix, bakedquad, r, g, b, lightmapCoord, overlayLight, inverted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A version of {@link VertexConsumer#putBulkData(PoseStack.Pose, BakedQuad, float, float, float, int, int)} which
|
||||||
|
* will reverse vertex order when the matrix is inverted.
|
||||||
|
*
|
||||||
|
* @param buffer The buffer to draw to.
|
||||||
|
* @param pose The current matrix stack.
|
||||||
|
* @param quad The quad to draw.
|
||||||
|
* @param red The red tint of this quad.
|
||||||
|
* @param green The green tint of this quad.
|
||||||
|
* @param blue The blue tint of this quad.
|
||||||
|
* @param lightmapCoord The lightmap coordinate
|
||||||
|
* @param overlayLight The overlay light.
|
||||||
|
* @param invert Whether to reverse the order of this quad.
|
||||||
|
*/
|
||||||
|
private static void putBulkQuad(VertexConsumer buffer, PoseStack.Pose pose, BakedQuad quad, float red, float green, float blue, int lightmapCoord, int overlayLight, boolean invert) {
|
||||||
|
var matrix = pose.pose();
|
||||||
|
// It's a little dubious to transform using this matrix rather than the normal matrix. This mirrors the logic in
|
||||||
|
// Direction.rotate (so not out of nowhere!), but is a little suspicious.
|
||||||
|
var dirNormal = quad.getDirection().getNormal();
|
||||||
|
var vector = new Vector4f();
|
||||||
|
|
||||||
|
matrix.transform(dirNormal.getX(), dirNormal.getY(), dirNormal.getZ(), 0.0f, vector).normalize();
|
||||||
|
float normalX = vector.x(), normalY = vector.y(), normalZ = vector.z();
|
||||||
|
|
||||||
|
var vertices = quad.getVertices();
|
||||||
|
for (var vertex = 0; vertex < 4; vertex++) {
|
||||||
|
var i = ModelTransformer.getVertexOffset(vertex, invert);
|
||||||
|
|
||||||
|
var x = Float.intBitsToFloat(vertices[i]);
|
||||||
|
var y = Float.intBitsToFloat(vertices[i + 1]);
|
||||||
|
var z = Float.intBitsToFloat(vertices[i + 2]);
|
||||||
|
|
||||||
|
matrix.transform(x, y, z, 1, vector);
|
||||||
|
|
||||||
|
var u = Float.intBitsToFloat(vertices[i + 4]);
|
||||||
|
var v = Float.intBitsToFloat(vertices[i + 5]);
|
||||||
|
buffer.vertex(
|
||||||
|
vector.x(), vector.y(), vector.z(),
|
||||||
|
red, green, blue, 1.0F, u, v, overlayLight, lightmapCoord,
|
||||||
|
normalX, normalY, normalZ
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -5,7 +5,6 @@
|
|||||||
package dan200.computercraft.client.render;
|
package dan200.computercraft.client.render;
|
||||||
|
|
||||||
import com.mojang.blaze3d.vertex.PoseStack;
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
|
||||||
import com.mojang.math.Axis;
|
import com.mojang.math.Axis;
|
||||||
import com.mojang.math.Transformation;
|
import com.mojang.math.Transformation;
|
||||||
import dan200.computercraft.api.ComputerCraftAPI;
|
import dan200.computercraft.api.ComputerCraftAPI;
|
||||||
@@ -14,25 +13,20 @@ import dan200.computercraft.client.platform.ClientPlatformHelper;
|
|||||||
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
|
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
|
||||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||||
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
|
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
|
||||||
import dan200.computercraft.shared.util.DirectionUtil;
|
|
||||||
import dan200.computercraft.shared.util.Holiday;
|
import dan200.computercraft.shared.util.Holiday;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.gui.Font;
|
import net.minecraft.client.gui.Font;
|
||||||
import net.minecraft.client.renderer.MultiBufferSource;
|
import net.minecraft.client.renderer.MultiBufferSource;
|
||||||
import net.minecraft.client.renderer.Sheets;
|
|
||||||
import net.minecraft.client.renderer.block.model.BakedQuad;
|
|
||||||
import net.minecraft.client.renderer.blockentity.BlockEntityRenderDispatcher;
|
import net.minecraft.client.renderer.blockentity.BlockEntityRenderDispatcher;
|
||||||
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
|
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
|
||||||
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
|
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
|
||||||
import net.minecraft.client.resources.model.BakedModel;
|
import net.minecraft.client.resources.model.BakedModel;
|
||||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
import net.minecraft.client.resources.model.ModelResourceLocation;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.util.RandomSource;
|
|
||||||
import net.minecraft.world.phys.BlockHitResult;
|
import net.minecraft.world.phys.BlockHitResult;
|
||||||
import net.minecraft.world.phys.HitResult;
|
import net.minecraft.world.phys.HitResult;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBlockEntity> {
|
public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBlockEntity> {
|
||||||
private static final ModelResourceLocation NORMAL_TURTLE_MODEL = new ModelResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_normal", "inventory");
|
private static final ModelResourceLocation NORMAL_TURTLE_MODEL = new ModelResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_normal", "inventory");
|
||||||
@@ -40,8 +34,6 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
|
|||||||
private static final ResourceLocation COLOUR_TURTLE_MODEL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_colour");
|
private static final ResourceLocation COLOUR_TURTLE_MODEL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_colour");
|
||||||
private static final ResourceLocation ELF_OVERLAY_MODEL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_elf_overlay");
|
private static final ResourceLocation ELF_OVERLAY_MODEL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_elf_overlay");
|
||||||
|
|
||||||
private final RandomSource random = RandomSource.create(0);
|
|
||||||
|
|
||||||
private final BlockEntityRenderDispatcher renderer;
|
private final BlockEntityRenderDispatcher renderer;
|
||||||
private final Font font;
|
private final Font font;
|
||||||
|
|
||||||
@@ -107,23 +99,22 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
|
|||||||
var family = turtle.getFamily();
|
var family = turtle.getFamily();
|
||||||
var overlay = turtle.getOverlay();
|
var overlay = turtle.getOverlay();
|
||||||
|
|
||||||
var buffer = buffers.getBuffer(Sheets.translucentCullBlockSheet());
|
renderModel(transform, buffers, lightmapCoord, overlayLight, getTurtleModel(family, colour != -1), colour == -1 ? null : new int[]{ colour });
|
||||||
renderModel(transform, buffer, lightmapCoord, overlayLight, getTurtleModel(family, colour != -1), colour == -1 ? null : new int[]{ colour });
|
|
||||||
|
|
||||||
// Render the overlay
|
// Render the overlay
|
||||||
var overlayModel = getTurtleOverlayModel(overlay, Holiday.getCurrent() == Holiday.CHRISTMAS);
|
var overlayModel = getTurtleOverlayModel(overlay, Holiday.getCurrent() == Holiday.CHRISTMAS);
|
||||||
if (overlayModel != null) {
|
if (overlayModel != null) {
|
||||||
renderModel(transform, buffer, lightmapCoord, overlayLight, overlayModel, null);
|
renderModel(transform, buffers, lightmapCoord, overlayLight, overlayModel, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render the upgrades
|
// Render the upgrades
|
||||||
renderUpgrade(transform, buffer, lightmapCoord, overlayLight, turtle, TurtleSide.LEFT, partialTicks);
|
renderUpgrade(transform, buffers, lightmapCoord, overlayLight, turtle, TurtleSide.LEFT, partialTicks);
|
||||||
renderUpgrade(transform, buffer, lightmapCoord, overlayLight, turtle, TurtleSide.RIGHT, partialTicks);
|
renderUpgrade(transform, buffers, lightmapCoord, overlayLight, turtle, TurtleSide.RIGHT, partialTicks);
|
||||||
|
|
||||||
transform.popPose();
|
transform.popPose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void renderUpgrade(PoseStack transform, VertexConsumer renderer, int lightmapCoord, int overlayLight, TurtleBlockEntity turtle, TurtleSide side, float f) {
|
private void renderUpgrade(PoseStack transform, MultiBufferSource buffers, int lightmapCoord, int overlayLight, TurtleBlockEntity turtle, TurtleSide side, float f) {
|
||||||
var upgrade = turtle.getUpgrade(side);
|
var upgrade = turtle.getUpgrade(side);
|
||||||
if (upgrade == null) return;
|
if (upgrade == null) return;
|
||||||
transform.pushPose();
|
transform.pushPose();
|
||||||
@@ -134,46 +125,33 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
|
|||||||
transform.translate(0.0f, -0.5f, -0.5f);
|
transform.translate(0.0f, -0.5f, -0.5f);
|
||||||
|
|
||||||
var model = TurtleUpgradeModellers.getModel(upgrade, turtle.getAccess(), side);
|
var model = TurtleUpgradeModellers.getModel(upgrade, turtle.getAccess(), side);
|
||||||
pushPoseFromTransformation(transform, model.getMatrix());
|
applyTransformation(transform, model.getMatrix());
|
||||||
renderModel(transform, renderer, lightmapCoord, overlayLight, model.getModel(), null);
|
renderModel(transform, buffers, lightmapCoord, overlayLight, model.getModel(), null);
|
||||||
transform.popPose();
|
|
||||||
|
|
||||||
transform.popPose();
|
transform.popPose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void renderModel(PoseStack transform, VertexConsumer renderer, int lightmapCoord, int overlayLight, ResourceLocation modelLocation, @Nullable int[] tints) {
|
private void renderModel(PoseStack transform, MultiBufferSource buffers, int lightmapCoord, int overlayLight, ResourceLocation modelLocation, @Nullable int[] tints) {
|
||||||
var modelManager = Minecraft.getInstance().getItemRenderer().getItemModelShaper().getModelManager();
|
var modelManager = Minecraft.getInstance().getItemRenderer().getItemModelShaper().getModelManager();
|
||||||
renderModel(transform, renderer, lightmapCoord, overlayLight, ClientPlatformHelper.get().getModel(modelManager, modelLocation), tints);
|
renderModel(transform, buffers, lightmapCoord, overlayLight, ClientPlatformHelper.get().getModel(modelManager, modelLocation), tints);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void renderModel(PoseStack transform, VertexConsumer renderer, int lightmapCoord, int overlayLight, BakedModel model, @Nullable int[] tints) {
|
/**
|
||||||
random.setSeed(0);
|
* Render a block model.
|
||||||
renderQuads(transform, renderer, lightmapCoord, overlayLight, model.getQuads(null, null, random), tints);
|
*
|
||||||
for (var facing : DirectionUtil.FACINGS) {
|
* @param transform The current matrix stack.
|
||||||
renderQuads(transform, renderer, lightmapCoord, overlayLight, model.getQuads(null, facing, random), tints);
|
* @param renderer The buffer to write to.
|
||||||
}
|
* @param lightmapCoord The current lightmap coordinate.
|
||||||
|
* @param overlayLight The overlay light.
|
||||||
|
* @param model The model to render.
|
||||||
|
* @param tints Tints for the quads, as an array of RGB values.
|
||||||
|
* @see net.minecraft.client.renderer.block.ModelBlockRenderer#renderModel
|
||||||
|
*/
|
||||||
|
private void renderModel(PoseStack transform, MultiBufferSource renderer, int lightmapCoord, int overlayLight, BakedModel model, @Nullable int[] tints) {
|
||||||
|
ClientPlatformHelper.get().renderBakedModel(transform, renderer, model, lightmapCoord, overlayLight, tints);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void renderQuads(PoseStack transform, VertexConsumer buffer, int lightmapCoord, int overlayLight, List<BakedQuad> quads, @Nullable int[] tints) {
|
private static void applyTransformation(PoseStack stack, Transformation transformation) {
|
||||||
var matrix = transform.last();
|
|
||||||
|
|
||||||
for (var bakedquad : quads) {
|
|
||||||
var tint = -1;
|
|
||||||
if (tints != null && bakedquad.isTinted()) {
|
|
||||||
var idx = bakedquad.getTintIndex();
|
|
||||||
if (idx >= 0 && idx < tints.length) tint = tints[bakedquad.getTintIndex()];
|
|
||||||
}
|
|
||||||
|
|
||||||
var r = (float) (tint >> 16 & 255) / 255.0F;
|
|
||||||
var g = (float) (tint >> 8 & 255) / 255.0F;
|
|
||||||
var b = (float) (tint & 255) / 255.0F;
|
|
||||||
buffer.putBulkData(matrix, bakedquad, r, g, b, lightmapCoord, overlayLight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void pushPoseFromTransformation(PoseStack stack, Transformation transformation) {
|
|
||||||
stack.pushPose();
|
|
||||||
|
|
||||||
var trans = transformation.getTranslation();
|
var trans = transformation.getTranslation();
|
||||||
stack.translate(trans.x(), trans.y(), trans.z());
|
stack.translate(trans.x(), trans.y(), trans.z());
|
||||||
|
|
||||||
|
@@ -13,6 +13,9 @@ import dan200.computercraft.shared.turtle.upgrades.TurtleModem;
|
|||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link TurtleUpgradeModeller} for modems, providing different models depending on if the modem is on/off.
|
* A {@link TurtleUpgradeModeller} for modems, providing different models depending on if the modem is on/off.
|
||||||
*/
|
*/
|
||||||
@@ -48,4 +51,9 @@ public class TurtleModemModeller implements TurtleUpgradeModeller<TurtleModem> {
|
|||||||
? TransformedModel.of(active ? leftOnModel : leftOffModel)
|
? TransformedModel.of(active ? leftOnModel : leftOffModel)
|
||||||
: TransformedModel.of(active ? rightOnModel : rightOffModel);
|
: TransformedModel.of(active ? rightOnModel : rightOffModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<ResourceLocation> getDependencies() {
|
||||||
|
return List.of(leftOffModel, rightOffModel, leftOnModel, rightOnModel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -14,11 +14,13 @@ import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
|
|||||||
import dan200.computercraft.impl.TurtleUpgrades;
|
import dan200.computercraft.impl.TurtleUpgrades;
|
||||||
import dan200.computercraft.impl.UpgradeManager;
|
import dan200.computercraft.impl.UpgradeManager;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A registry of {@link TurtleUpgradeModeller}s.
|
* A registry of {@link TurtleUpgradeModeller}s.
|
||||||
@@ -52,12 +54,18 @@ public final class TurtleUpgradeModellers {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TransformedModel getModel(ITurtleUpgrade upgrade, @Nullable ITurtleAccess access, TurtleSide side) {
|
public static TransformedModel getModel(ITurtleUpgrade upgrade, ITurtleAccess access, TurtleSide side) {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
var modeller = (TurtleUpgradeModeller<ITurtleUpgrade>) modelCache.computeIfAbsent(upgrade, TurtleUpgradeModellers::getModeller);
|
var modeller = (TurtleUpgradeModeller<ITurtleUpgrade>) modelCache.computeIfAbsent(upgrade, TurtleUpgradeModellers::getModeller);
|
||||||
return modeller.getModel(upgrade, access, side);
|
return modeller.getModel(upgrade, access, side);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static TransformedModel getModel(ITurtleUpgrade upgrade, CompoundTag data, TurtleSide side) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
var modeller = (TurtleUpgradeModeller<ITurtleUpgrade>) modelCache.computeIfAbsent(upgrade, TurtleUpgradeModellers::getModeller);
|
||||||
|
return modeller.getModel(upgrade, data, side);
|
||||||
|
}
|
||||||
|
|
||||||
private static TurtleUpgradeModeller<?> getModeller(ITurtleUpgrade upgradeA) {
|
private static TurtleUpgradeModeller<?> getModeller(ITurtleUpgrade upgradeA) {
|
||||||
var wrapper = TurtleUpgrades.instance().getWrapper(upgradeA);
|
var wrapper = TurtleUpgrades.instance().getWrapper(upgradeA);
|
||||||
if (wrapper == null) return NULL_TURTLE_MODELLER;
|
if (wrapper == null) return NULL_TURTLE_MODELLER;
|
||||||
@@ -65,4 +73,8 @@ public final class TurtleUpgradeModellers {
|
|||||||
var modeller = turtleModels.get(wrapper.serialiser());
|
var modeller = turtleModels.get(wrapper.serialiser());
|
||||||
return modeller == null ? NULL_TURTLE_MODELLER : modeller;
|
return modeller == null ? NULL_TURTLE_MODELLER : modeller;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Stream<ResourceLocation> getDependencies() {
|
||||||
|
return turtleModels.values().stream().flatMap(x -> x.getDependencies().stream());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,34 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.data.client;
|
||||||
|
|
||||||
|
import dan200.computercraft.data.DataProviders;
|
||||||
|
import dan200.computercraft.shared.turtle.inventory.UpgradeSlot;
|
||||||
|
import net.minecraft.client.renderer.texture.atlas.SpriteSources;
|
||||||
|
import net.minecraft.client.renderer.texture.atlas.sources.SingleFile;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.server.packs.PackType;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A version of {@link DataProviders} which relies on client-side classes.
|
||||||
|
* <p>
|
||||||
|
* This is called from {@link DataProviders#add(DataProviders.GeneratorSink)}.
|
||||||
|
*/
|
||||||
|
public final class ClientDataProviders {
|
||||||
|
private ClientDataProviders() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void add(DataProviders.GeneratorSink generator) {
|
||||||
|
generator.addFromCodec("Block atlases", PackType.CLIENT_RESOURCES, "atlases", SpriteSources.FILE_CODEC, out -> {
|
||||||
|
out.accept(new ResourceLocation("blocks"), List.of(
|
||||||
|
new SingleFile(UpgradeSlot.LEFT_UPGRADE, Optional.empty()),
|
||||||
|
new SingleFile(UpgradeSlot.RIGHT_UPGRADE, Optional.empty())
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -37,6 +37,14 @@ import static net.minecraft.data.models.model.ModelLocationUtils.getModelLocatio
|
|||||||
import static net.minecraft.data.models.model.TextureMapping.getBlockTexture;
|
import static net.minecraft.data.models.model.TextureMapping.getBlockTexture;
|
||||||
|
|
||||||
class BlockModelProvider {
|
class BlockModelProvider {
|
||||||
|
private static final TextureSlot CURSOR = TextureSlot.create("cursor");
|
||||||
|
|
||||||
|
private static final ModelTemplate COMPUTER_ON = new ModelTemplate(
|
||||||
|
Optional.of(new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/computer_on")),
|
||||||
|
Optional.empty(),
|
||||||
|
TextureSlot.FRONT, TextureSlot.SIDE, TextureSlot.TOP, CURSOR
|
||||||
|
);
|
||||||
|
|
||||||
private static final ModelTemplate MONITOR_BASE = new ModelTemplate(
|
private static final ModelTemplate MONITOR_BASE = new ModelTemplate(
|
||||||
Optional.of(new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/monitor_base")),
|
Optional.of(new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/monitor_base")),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
@@ -142,11 +150,18 @@ class BlockModelProvider {
|
|||||||
private static void registerComputer(BlockModelGenerators generators, ComputerBlock<?> block) {
|
private static void registerComputer(BlockModelGenerators generators, ComputerBlock<?> block) {
|
||||||
generators.blockStateOutput.accept(MultiVariantGenerator.multiVariant(block)
|
generators.blockStateOutput.accept(MultiVariantGenerator.multiVariant(block)
|
||||||
.with(createHorizontalFacingDispatch())
|
.with(createHorizontalFacingDispatch())
|
||||||
.with(createModelDispatch(ComputerBlock.STATE, state -> ModelTemplates.CUBE_ORIENTABLE.createWithSuffix(
|
.with(createModelDispatch(ComputerBlock.STATE, state -> switch (state) {
|
||||||
block, "_" + state.getSerializedName(),
|
case OFF -> ModelTemplates.CUBE_ORIENTABLE.createWithSuffix(
|
||||||
TextureMapping.orientableCube(block).put(TextureSlot.FRONT, getBlockTexture(block, "_front" + state.getTexture())),
|
block, "_" + state.getSerializedName(),
|
||||||
generators.modelOutput
|
TextureMapping.orientableCube(block),
|
||||||
)))
|
generators.modelOutput
|
||||||
|
);
|
||||||
|
case ON, BLINKING -> COMPUTER_ON.createWithSuffix(
|
||||||
|
block, "_" + state.getSerializedName(),
|
||||||
|
TextureMapping.orientableCube(block).put(CURSOR, new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/computer" + state.getTexture())),
|
||||||
|
generators.modelOutput
|
||||||
|
);
|
||||||
|
}))
|
||||||
);
|
);
|
||||||
generators.delegateItemModel(block, getModelLocation(block, "_blinking"));
|
generators.delegateItemModel(block, getModelLocation(block, "_blinking"));
|
||||||
}
|
}
|
||||||
|
@@ -4,16 +4,20 @@
|
|||||||
|
|
||||||
package dan200.computercraft.data;
|
package dan200.computercraft.data;
|
||||||
|
|
||||||
|
import com.mojang.serialization.Codec;
|
||||||
import net.minecraft.data.DataProvider;
|
import net.minecraft.data.DataProvider;
|
||||||
import net.minecraft.data.PackOutput;
|
import net.minecraft.data.PackOutput;
|
||||||
import net.minecraft.data.loot.LootTableProvider.SubProviderEntry;
|
import net.minecraft.data.loot.LootTableProvider.SubProviderEntry;
|
||||||
import net.minecraft.data.models.BlockModelGenerators;
|
import net.minecraft.data.models.BlockModelGenerators;
|
||||||
import net.minecraft.data.models.ItemModelGenerators;
|
import net.minecraft.data.models.ItemModelGenerators;
|
||||||
import net.minecraft.data.tags.TagsProvider;
|
import net.minecraft.data.tags.TagsProvider;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.server.packs.PackType;
|
||||||
import net.minecraft.world.item.Item;
|
import net.minecraft.world.item.Item;
|
||||||
import net.minecraft.world.level.block.Block;
|
import net.minecraft.world.level.block.Block;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -37,11 +41,22 @@ public final class DataProviders {
|
|||||||
generator.models(BlockModelProvider::addBlockModels, ItemModelProvider::addItemModels);
|
generator.models(BlockModelProvider::addBlockModels, ItemModelProvider::addItemModels);
|
||||||
|
|
||||||
generator.add(out -> new LanguageProvider(out, turtleUpgrades, pocketUpgrades));
|
generator.add(out -> new LanguageProvider(out, turtleUpgrades, pocketUpgrades));
|
||||||
|
|
||||||
|
// Unfortunately we rely on some client-side classes in this code. We just load in the client side data provider
|
||||||
|
// and invoke that.
|
||||||
|
try {
|
||||||
|
Class.forName("dan200.computercraft.data.client.ClientDataProviders")
|
||||||
|
.getMethod("add", GeneratorSink.class).invoke(null, generator);
|
||||||
|
} catch (ReflectiveOperationException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GeneratorSink {
|
public interface GeneratorSink {
|
||||||
<T extends DataProvider> T add(DataProvider.Factory<T> factory);
|
<T extends DataProvider> T add(DataProvider.Factory<T> factory);
|
||||||
|
|
||||||
|
<T> void addFromCodec(String name, PackType type, String directory, Codec<T> codec, Consumer<BiConsumer<ResourceLocation, T>> output);
|
||||||
|
|
||||||
void lootTable(List<SubProviderEntry> tables);
|
void lootTable(List<SubProviderEntry> tables);
|
||||||
|
|
||||||
TagsProvider<Block> blockTags(Consumer<TagProvider.TagConsumer<Block>> tags);
|
TagsProvider<Block> blockTags(Consumer<TagProvider.TagConsumer<Block>> tags);
|
||||||
|
@@ -6,6 +6,7 @@ package dan200.computercraft.data;
|
|||||||
|
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import dan200.computercraft.api.ComputerCraftAPI;
|
import dan200.computercraft.api.ComputerCraftAPI;
|
||||||
|
import dan200.computercraft.api.ComputerCraftTags;
|
||||||
import dan200.computercraft.api.pocket.PocketUpgradeDataProvider;
|
import dan200.computercraft.api.pocket.PocketUpgradeDataProvider;
|
||||||
import dan200.computercraft.api.turtle.TurtleUpgradeDataProvider;
|
import dan200.computercraft.api.turtle.TurtleUpgradeDataProvider;
|
||||||
import dan200.computercraft.api.upgrades.UpgradeBase;
|
import dan200.computercraft.api.upgrades.UpgradeBase;
|
||||||
@@ -20,6 +21,7 @@ import dan200.computercraft.shared.platform.RegistryWrappers;
|
|||||||
import net.minecraft.data.CachedOutput;
|
import net.minecraft.data.CachedOutput;
|
||||||
import net.minecraft.data.DataProvider;
|
import net.minecraft.data.DataProvider;
|
||||||
import net.minecraft.data.PackOutput;
|
import net.minecraft.data.PackOutput;
|
||||||
|
import net.minecraft.tags.TagKey;
|
||||||
import net.minecraft.world.item.Item;
|
import net.minecraft.world.item.Item;
|
||||||
import net.minecraft.world.level.block.Block;
|
import net.minecraft.world.level.block.Block;
|
||||||
|
|
||||||
@@ -97,6 +99,12 @@ public final class LanguageProvider implements DataProvider {
|
|||||||
add(ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get(), "Advanced Pocket Computer");
|
add(ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get(), "Advanced Pocket Computer");
|
||||||
add(ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get().getDescriptionId() + ".upgraded", "Advanced %s Pocket Computer");
|
add(ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get().getDescriptionId() + ".upgraded", "Advanced %s Pocket Computer");
|
||||||
|
|
||||||
|
// Tags (for EMI)
|
||||||
|
add(ComputerCraftTags.Items.COMPUTER, "Computers");
|
||||||
|
add(ComputerCraftTags.Items.TURTLE, "Turtles");
|
||||||
|
add(ComputerCraftTags.Items.WIRED_MODEM, "Wired modems");
|
||||||
|
add(ComputerCraftTags.Items.MONITOR, "Monitors");
|
||||||
|
|
||||||
// Turtle/pocket upgrades
|
// Turtle/pocket upgrades
|
||||||
add("upgrade.minecraft.diamond_sword.adjective", "Melee");
|
add("upgrade.minecraft.diamond_sword.adjective", "Melee");
|
||||||
add("upgrade.minecraft.diamond_shovel.adjective", "Digging");
|
add("upgrade.minecraft.diamond_shovel.adjective", "Digging");
|
||||||
@@ -150,9 +158,6 @@ public final class LanguageProvider implements DataProvider {
|
|||||||
add("commands.computercraft.track.dump.desc", "Dump the latest results of computer tracking.");
|
add("commands.computercraft.track.dump.desc", "Dump the latest results of computer tracking.");
|
||||||
add("commands.computercraft.track.dump.no_timings", "No timings available");
|
add("commands.computercraft.track.dump.no_timings", "No timings available");
|
||||||
add("commands.computercraft.track.dump.computer", "Computer");
|
add("commands.computercraft.track.dump.computer", "Computer");
|
||||||
add("commands.computercraft.reload.synopsis", "Reload the ComputerCraft config file");
|
|
||||||
add("commands.computercraft.reload.desc", "Reload the ComputerCraft config file");
|
|
||||||
add("commands.computercraft.reload.done", "Reloaded config");
|
|
||||||
add("commands.computercraft.queue.synopsis", "Send a computer_command event to a command computer");
|
add("commands.computercraft.queue.synopsis", "Send a computer_command event to a command computer");
|
||||||
add("commands.computercraft.queue.desc", "Send a computer_command event to a command computer, passing through the additional arguments. This is mostly designed for map makers, acting as a more computer-friendly version of /trigger. Any player can run the command, which would most likely be done through a text component's click event.");
|
add("commands.computercraft.queue.desc", "Send a computer_command event to a command computer, passing through the additional arguments. This is mostly designed for map makers, acting as a more computer-friendly version of /trigger. Any player can run the command, which would most likely be done through a text component's click event.");
|
||||||
|
|
||||||
@@ -214,6 +219,7 @@ public final class LanguageProvider implements DataProvider {
|
|||||||
addConfigEntry(ConfigSpec.defaultComputerSettings, "Default Computer settings");
|
addConfigEntry(ConfigSpec.defaultComputerSettings, "Default Computer settings");
|
||||||
addConfigEntry(ConfigSpec.logComputerErrors, "Log computer errors");
|
addConfigEntry(ConfigSpec.logComputerErrors, "Log computer errors");
|
||||||
addConfigEntry(ConfigSpec.commandRequireCreative, "Command computers require creative");
|
addConfigEntry(ConfigSpec.commandRequireCreative, "Command computers require creative");
|
||||||
|
addConfigEntry(ConfigSpec.disabledGenericMethods, "Disabled generic methods");
|
||||||
|
|
||||||
addConfigGroup(ConfigSpec.serverSpec, "execution", "Execution");
|
addConfigGroup(ConfigSpec.serverSpec, "execution", "Execution");
|
||||||
addConfigEntry(ConfigSpec.computerThreads, "Computer threads");
|
addConfigEntry(ConfigSpec.computerThreads, "Computer threads");
|
||||||
@@ -277,8 +283,8 @@ public final class LanguageProvider implements DataProvider {
|
|||||||
turtleUpgrades.getGeneratedUpgrades().stream().map(UpgradeBase::getUnlocalisedAdjective),
|
turtleUpgrades.getGeneratedUpgrades().stream().map(UpgradeBase::getUnlocalisedAdjective),
|
||||||
pocketUpgrades.getGeneratedUpgrades().stream().map(UpgradeBase::getUnlocalisedAdjective),
|
pocketUpgrades.getGeneratedUpgrades().stream().map(UpgradeBase::getUnlocalisedAdjective),
|
||||||
Metric.metrics().values().stream().map(x -> AggregatedMetric.TRANSLATION_PREFIX + x.name() + ".name"),
|
Metric.metrics().values().stream().map(x -> AggregatedMetric.TRANSLATION_PREFIX + x.name() + ".name"),
|
||||||
getConfigEntries(ConfigSpec.serverSpec).map(ConfigFile.Entry::translationKey),
|
ConfigSpec.serverSpec.entries().map(ConfigFile.Entry::translationKey),
|
||||||
getConfigEntries(ConfigSpec.clientSpec).map(ConfigFile.Entry::translationKey)
|
ConfigSpec.clientSpec.entries().map(ConfigFile.Entry::translationKey)
|
||||||
).flatMap(x -> x);
|
).flatMap(x -> x);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,6 +304,10 @@ public final class LanguageProvider implements DataProvider {
|
|||||||
add(AggregatedMetric.TRANSLATION_PREFIX + metric.name() + ".name", text);
|
add(AggregatedMetric.TRANSLATION_PREFIX + metric.name() + ".name", text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void add(TagKey<Item> tag, String text) {
|
||||||
|
add("tag.item." + tag.location().getNamespace() + "." + tag.location().getPath(), text);
|
||||||
|
}
|
||||||
|
|
||||||
private void addConfigGroup(ConfigFile spec, String path, String text) {
|
private void addConfigGroup(ConfigFile spec, String path, String text) {
|
||||||
var entry = spec.getEntry(path);
|
var entry = spec.getEntry(path);
|
||||||
if (!(entry instanceof ConfigFile.Group)) throw new IllegalArgumentException("Cannot find group " + path);
|
if (!(entry instanceof ConfigFile.Group)) throw new IllegalArgumentException("Cannot find group " + path);
|
||||||
@@ -308,16 +318,4 @@ public final class LanguageProvider implements DataProvider {
|
|||||||
add(value.translationKey(), text);
|
add(value.translationKey(), text);
|
||||||
add(value.translationKey() + ".tooltip", value.comment());
|
add(value.translationKey() + ".tooltip", value.comment());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Stream<ConfigFile.Entry> getConfigEntries(ConfigFile spec) {
|
|
||||||
return spec.entries().flatMap(LanguageProvider::getConfigEntries);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Stream<ConfigFile.Entry> getConfigEntries(ConfigFile.Entry entry) {
|
|
||||||
if (entry instanceof ConfigFile.Value<?>) return Stream.of(entry);
|
|
||||||
if (entry instanceof ConfigFile.Group group) {
|
|
||||||
return Stream.concat(Stream.of(entry), group.children().flatMap(LanguageProvider::getConfigEntries));
|
|
||||||
}
|
|
||||||
throw new IllegalStateException("Invalid config entry " + entry);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -8,6 +8,7 @@ import com.google.gson.JsonObject;
|
|||||||
import dan200.computercraft.api.ComputerCraftAPI;
|
import dan200.computercraft.api.ComputerCraftAPI;
|
||||||
import dan200.computercraft.api.pocket.PocketUpgradeDataProvider;
|
import dan200.computercraft.api.pocket.PocketUpgradeDataProvider;
|
||||||
import dan200.computercraft.api.turtle.TurtleUpgradeDataProvider;
|
import dan200.computercraft.api.turtle.TurtleUpgradeDataProvider;
|
||||||
|
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||||
import dan200.computercraft.core.util.Colour;
|
import dan200.computercraft.core.util.Colour;
|
||||||
import dan200.computercraft.shared.ModRegistry;
|
import dan200.computercraft.shared.ModRegistry;
|
||||||
import dan200.computercraft.shared.common.IColouredItem;
|
import dan200.computercraft.shared.common.IColouredItem;
|
||||||
@@ -110,7 +111,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
|||||||
var nameId = turtleItem.getFamily().name().toLowerCase(Locale.ROOT);
|
var nameId = turtleItem.getFamily().name().toLowerCase(Locale.ROOT);
|
||||||
|
|
||||||
for (var upgrade : turtleUpgrades.getGeneratedUpgrades()) {
|
for (var upgrade : turtleUpgrades.getGeneratedUpgrades()) {
|
||||||
var result = turtleItem.create(-1, null, -1, null, upgrade, -1, null);
|
var result = turtleItem.create(-1, null, -1, null, UpgradeData.ofDefault(upgrade), -1, null);
|
||||||
ShapedRecipeBuilder
|
ShapedRecipeBuilder
|
||||||
.shaped(RecipeCategory.REDSTONE, result.getItem())
|
.shaped(RecipeCategory.REDSTONE, result.getItem())
|
||||||
.group(String.format("%s:turtle_%s", ComputerCraftAPI.MOD_ID, nameId))
|
.group(String.format("%s:turtle_%s", ComputerCraftAPI.MOD_ID, nameId))
|
||||||
@@ -146,7 +147,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
|||||||
var nameId = pocket.getFamily().name().toLowerCase(Locale.ROOT);
|
var nameId = pocket.getFamily().name().toLowerCase(Locale.ROOT);
|
||||||
|
|
||||||
for (var upgrade : pocketUpgrades.getGeneratedUpgrades()) {
|
for (var upgrade : pocketUpgrades.getGeneratedUpgrades()) {
|
||||||
var result = pocket.create(-1, null, -1, upgrade);
|
var result = pocket.create(-1, null, -1, UpgradeData.ofDefault(upgrade));
|
||||||
ShapedRecipeBuilder
|
ShapedRecipeBuilder
|
||||||
.shaped(RecipeCategory.REDSTONE, result.getItem())
|
.shaped(RecipeCategory.REDSTONE, result.getItem())
|
||||||
.group(String.format("%s:pocket_%s", ComputerCraftAPI.MOD_ID, nameId))
|
.group(String.format("%s:pocket_%s", ComputerCraftAPI.MOD_ID, nameId))
|
||||||
|
@@ -19,8 +19,6 @@ import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
|
|||||||
import dan200.computercraft.api.redstone.BundledRedstoneProvider;
|
import dan200.computercraft.api.redstone.BundledRedstoneProvider;
|
||||||
import dan200.computercraft.api.turtle.TurtleRefuelHandler;
|
import dan200.computercraft.api.turtle.TurtleRefuelHandler;
|
||||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
|
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
|
||||||
import dan200.computercraft.core.apis.ApiFactories;
|
|
||||||
import dan200.computercraft.core.asm.GenericMethod;
|
|
||||||
import dan200.computercraft.core.filesystem.WritableFileMount;
|
import dan200.computercraft.core.filesystem.WritableFileMount;
|
||||||
import dan200.computercraft.impl.detail.DetailRegistryImpl;
|
import dan200.computercraft.impl.detail.DetailRegistryImpl;
|
||||||
import dan200.computercraft.impl.network.wired.WiredNodeImpl;
|
import dan200.computercraft.impl.network.wired.WiredNodeImpl;
|
||||||
@@ -79,7 +77,7 @@ public abstract class AbstractComputerCraftAPI implements ComputerCraftAPIServic
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void registerGenericSource(GenericSource source) {
|
public final void registerGenericSource(GenericSource source) {
|
||||||
GenericMethod.register(source);
|
GenericSources.register(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
package dan200.computercraft.core.apis;
|
package dan200.computercraft.impl;
|
||||||
|
|
||||||
import dan200.computercraft.api.lua.ILuaAPIFactory;
|
import dan200.computercraft.api.lua.ILuaAPIFactory;
|
||||||
|
|
||||||
@@ -11,19 +11,24 @@ import java.util.Collections;
|
|||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The global factory for {@link ILuaAPIFactory}s.
|
||||||
|
*
|
||||||
|
* @see dan200.computercraft.core.ComputerContext.Builder#apiFactories(Collection)
|
||||||
|
* @see dan200.computercraft.api.ComputerCraftAPI#registerAPIFactory(ILuaAPIFactory)
|
||||||
|
*/
|
||||||
public final class ApiFactories {
|
public final class ApiFactories {
|
||||||
private ApiFactories() {
|
private ApiFactories() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Collection<ILuaAPIFactory> factories = new LinkedHashSet<>();
|
private static final Collection<ILuaAPIFactory> factories = new LinkedHashSet<>();
|
||||||
private static final Collection<ILuaAPIFactory> factoriesView = Collections.unmodifiableCollection(factories);
|
|
||||||
|
|
||||||
public static synchronized void register(ILuaAPIFactory factory) {
|
static synchronized void register(ILuaAPIFactory factory) {
|
||||||
Objects.requireNonNull(factory, "provider cannot be null");
|
Objects.requireNonNull(factory, "provider cannot be null");
|
||||||
factories.add(factory);
|
factories.add(factory);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Iterable<ILuaAPIFactory> getAll() {
|
public static Collection<ILuaAPIFactory> getAll() {
|
||||||
return factoriesView;
|
return Collections.unmodifiableCollection(factories);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -0,0 +1,41 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.impl;
|
||||||
|
|
||||||
|
import dan200.computercraft.api.lua.GenericSource;
|
||||||
|
import dan200.computercraft.core.asm.GenericMethod;
|
||||||
|
import dan200.computercraft.shared.config.ConfigSpec;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The global registry for {@link GenericSource}s.
|
||||||
|
*
|
||||||
|
* @see dan200.computercraft.core.ComputerContext.Builder#genericMethods(Collection)
|
||||||
|
* @see dan200.computercraft.api.ComputerCraftAPI#registerGenericSource(GenericSource)
|
||||||
|
*/
|
||||||
|
public final class GenericSources {
|
||||||
|
private GenericSources() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Collection<GenericSource> sources = new LinkedHashSet<>();
|
||||||
|
|
||||||
|
static synchronized void register(GenericSource source) {
|
||||||
|
Objects.requireNonNull(source, "provider cannot be null");
|
||||||
|
sources.add(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Collection<GenericMethod> getAllMethods() {
|
||||||
|
var disabledMethods = Set.copyOf(ConfigSpec.disabledGenericMethods.get());
|
||||||
|
return sources.stream()
|
||||||
|
.filter(x -> !disabledMethods.contains(x.id()))
|
||||||
|
.flatMap(GenericMethod::getMethods)
|
||||||
|
.filter(x -> !disabledMethods.contains(x.id()))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
@@ -7,6 +7,7 @@ package dan200.computercraft.impl;
|
|||||||
import com.google.gson.*;
|
import com.google.gson.*;
|
||||||
import dan200.computercraft.api.ComputerCraftAPI;
|
import dan200.computercraft.api.ComputerCraftAPI;
|
||||||
import dan200.computercraft.api.upgrades.UpgradeBase;
|
import dan200.computercraft.api.upgrades.UpgradeBase;
|
||||||
|
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||||
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
|
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
|
||||||
import dan200.computercraft.shared.platform.PlatformHelper;
|
import dan200.computercraft.shared.platform.PlatformHelper;
|
||||||
import net.minecraft.core.Registry;
|
import net.minecraft.core.Registry;
|
||||||
@@ -74,13 +75,13 @@ public class UpgradeManager<R extends UpgradeSerialiser<? extends T>, T extends
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public T get(ItemStack stack) {
|
public UpgradeData<T> get(ItemStack stack) {
|
||||||
if (stack.isEmpty()) return null;
|
if (stack.isEmpty()) return null;
|
||||||
|
|
||||||
for (var wrapper : current.values()) {
|
for (var wrapper : current.values()) {
|
||||||
var craftingStack = wrapper.upgrade().getCraftingItem();
|
var craftingStack = wrapper.upgrade().getCraftingItem();
|
||||||
if (!craftingStack.isEmpty() && craftingStack.getItem() == stack.getItem() && wrapper.upgrade().isItemSuitable(stack)) {
|
if (!craftingStack.isEmpty() && craftingStack.getItem() == stack.getItem() && wrapper.upgrade().isItemSuitable(stack)) {
|
||||||
return wrapper.upgrade();
|
return UpgradeData.of(wrapper.upgrade, wrapper.upgrade.getUpgradeData(stack));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -11,6 +11,7 @@ import dan200.computercraft.api.detail.VanillaDetailRegistries;
|
|||||||
import dan200.computercraft.api.media.IMedia;
|
import dan200.computercraft.api.media.IMedia;
|
||||||
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
|
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
|
||||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
|
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
|
||||||
|
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||||
import dan200.computercraft.core.util.Colour;
|
import dan200.computercraft.core.util.Colour;
|
||||||
import dan200.computercraft.impl.PocketUpgrades;
|
import dan200.computercraft.impl.PocketUpgrades;
|
||||||
import dan200.computercraft.impl.TurtleUpgrades;
|
import dan200.computercraft.impl.TurtleUpgrades;
|
||||||
@@ -446,12 +447,12 @@ public final class ModRegistry {
|
|||||||
private static void addTurtle(CreativeModeTab.Output out, TurtleItem turtle) {
|
private static void addTurtle(CreativeModeTab.Output out, TurtleItem turtle) {
|
||||||
out.accept(turtle.create(-1, null, -1, null, null, 0, null));
|
out.accept(turtle.create(-1, null, -1, null, null, 0, null));
|
||||||
TurtleUpgrades.getVanillaUpgrades()
|
TurtleUpgrades.getVanillaUpgrades()
|
||||||
.map(x -> turtle.create(-1, null, -1, null, x, 0, null))
|
.map(x -> turtle.create(-1, null, -1, null, UpgradeData.ofDefault(x), 0, null))
|
||||||
.forEach(out::accept);
|
.forEach(out::accept);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addPocket(CreativeModeTab.Output out, PocketComputerItem pocket) {
|
private static void addPocket(CreativeModeTab.Output out, PocketComputerItem pocket) {
|
||||||
out.accept(pocket.create(-1, null, -1, null));
|
out.accept(pocket.create(-1, null, -1, null));
|
||||||
PocketUpgrades.getVanillaUpgrades().map(x -> pocket.create(-1, null, -1, x)).forEach(out::accept);
|
PocketUpgrades.getVanillaUpgrades().map(x -> pocket.create(-1, null, -1, UpgradeData.ofDefault(x))).forEach(out::accept);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,9 +6,12 @@ package dan200.computercraft.shared.command;
|
|||||||
|
|
||||||
import com.mojang.brigadier.CommandDispatcher;
|
import com.mojang.brigadier.CommandDispatcher;
|
||||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||||
|
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
|
||||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||||
|
import com.mojang.brigadier.suggestion.Suggestions;
|
||||||
import dan200.computercraft.core.computer.ComputerSide;
|
import dan200.computercraft.core.computer.ComputerSide;
|
||||||
import dan200.computercraft.core.metrics.Metrics;
|
import dan200.computercraft.core.metrics.Metrics;
|
||||||
|
import dan200.computercraft.shared.command.arguments.ComputersArgumentType;
|
||||||
import dan200.computercraft.shared.command.text.TableBuilder;
|
import dan200.computercraft.shared.command.text.TableBuilder;
|
||||||
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.ServerComputer;
|
||||||
@@ -169,7 +172,10 @@ public final class CommandComputerCraft {
|
|||||||
|
|
||||||
.then(command("queue")
|
.then(command("queue")
|
||||||
.requires(UserLevel.ANYONE)
|
.requires(UserLevel.ANYONE)
|
||||||
.arg("computer", manyComputers())
|
.arg(
|
||||||
|
RequiredArgumentBuilder.<CommandSourceStack, ComputersArgumentType.ComputersSupplier>argument("computer", manyComputers())
|
||||||
|
.suggests((context, builder) -> Suggestions.empty())
|
||||||
|
)
|
||||||
.argManyValue("args", StringArgumentType.string(), Collections.emptyList())
|
.argManyValue("args", StringArgumentType.string(), Collections.emptyList())
|
||||||
.executes((ctx, args) -> {
|
.executes((ctx, args) -> {
|
||||||
var computers = getComputersArgument(ctx, "computer");
|
var computers = getComputersArgument(ctx, "computer");
|
||||||
|
@@ -49,6 +49,29 @@ public enum UserLevel implements Predicate<CommandSourceStack> {
|
|||||||
return source.hasPermission(toLevel());
|
return source.hasPermission(toLevel());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Take the union of two {@link UserLevel}s.
|
||||||
|
* <p>
|
||||||
|
* This satisfies the property that for all sources {@code s}, {@code a.test(s) || b.test(s) == (a ∪ b).test(s)}.
|
||||||
|
*
|
||||||
|
* @param left The first user level to take the union of.
|
||||||
|
* @param right The second user level to take the union of.
|
||||||
|
* @return The union of two levels.
|
||||||
|
*/
|
||||||
|
public static UserLevel union(UserLevel left, UserLevel right) {
|
||||||
|
if (left == right) return left;
|
||||||
|
|
||||||
|
// x ∪ ANYONE = ANYONE
|
||||||
|
if (left == ANYONE || right == ANYONE) return ANYONE;
|
||||||
|
|
||||||
|
// x ∪ OWNER = OWNER
|
||||||
|
if (left == OWNER) return right;
|
||||||
|
if (right == OWNER) return left;
|
||||||
|
|
||||||
|
// At this point, we have x != y and x, y ∈ { OP, OWNER_OP }.
|
||||||
|
return OWNER_OP;
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean isOwner(CommandSourceStack source) {
|
private static boolean isOwner(CommandSourceStack source) {
|
||||||
var server = source.getServer();
|
var server = source.getServer();
|
||||||
var sender = source.getEntity();
|
var sender = source.getEntity();
|
||||||
|
@@ -44,12 +44,12 @@ public class ArgumentUtils {
|
|||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private static <A extends ArgumentType<?>, T extends ArgumentTypeInfo.Template<A>> void serializeToNetwork(FriendlyByteBuf buffer, ArgumentTypeInfo<A, T> type, ArgumentTypeInfo.Template<A> template) {
|
private static <A extends ArgumentType<?>, T extends ArgumentTypeInfo.Template<A>> void serializeToNetwork(FriendlyByteBuf buffer, ArgumentTypeInfo<A, T> type, ArgumentTypeInfo.Template<A> template) {
|
||||||
RegistryWrappers.writeId(buffer, RegistryWrappers.COMMAND_ARGUMENT_TYPES, type);
|
buffer.writeId(RegistryWrappers.COMMAND_ARGUMENT_TYPES, type);
|
||||||
type.serializeToNetwork((T) template, buffer);
|
type.serializeToNetwork((T) template, buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ArgumentTypeInfo.Template<?> deserialize(FriendlyByteBuf buffer) {
|
public static ArgumentTypeInfo.Template<?> deserialize(FriendlyByteBuf buffer) {
|
||||||
var type = RegistryWrappers.readId(buffer, RegistryWrappers.COMMAND_ARGUMENT_TYPES);
|
var type = buffer.readById(RegistryWrappers.COMMAND_ARGUMENT_TYPES);
|
||||||
Objects.requireNonNull(type, "Unknown argument type");
|
Objects.requireNonNull(type, "Unknown argument type");
|
||||||
return type.deserializeFromNetwork(buffer);
|
return type.deserializeFromNetwork(buffer);
|
||||||
}
|
}
|
||||||
|
@@ -18,7 +18,6 @@ import net.minecraft.commands.CommandBuildContext;
|
|||||||
import net.minecraft.commands.CommandSourceStack;
|
import net.minecraft.commands.CommandSourceStack;
|
||||||
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
|
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
@@ -159,14 +158,14 @@ public final class ComputersArgumentType implements ArgumentType<ComputersArgume
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ComputersArgumentType.Template unpack(@NotNull ComputersArgumentType argumentType) {
|
public ComputersArgumentType.Template unpack(ComputersArgumentType argumentType) {
|
||||||
return new ComputersArgumentType.Template(this, argumentType.requireSome);
|
return new ComputersArgumentType.Template(this, argumentType.requireSome);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public record Template(Info info, boolean requireSome) implements ArgumentTypeInfo.Template<ComputersArgumentType> {
|
public record Template(Info info, boolean requireSome) implements ArgumentTypeInfo.Template<ComputersArgumentType> {
|
||||||
@Override
|
@Override
|
||||||
public ComputersArgumentType instantiate(@NotNull CommandBuildContext context) {
|
public ComputersArgumentType instantiate(CommandBuildContext context) {
|
||||||
return requireSome ? SOME : MANY;
|
return requireSome ? SOME : MANY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -17,7 +17,6 @@ import net.minecraft.commands.synchronization.ArgumentTypeInfo;
|
|||||||
import net.minecraft.commands.synchronization.ArgumentTypeInfos;
|
import net.minecraft.commands.synchronization.ArgumentTypeInfos;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@@ -144,7 +143,7 @@ public final class RepeatArgumentType<T, U> implements ArgumentType<List<T>> {
|
|||||||
) implements ArgumentTypeInfo.Template<RepeatArgumentType<?, ?>> {
|
) implements ArgumentTypeInfo.Template<RepeatArgumentType<?, ?>> {
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||||
public RepeatArgumentType<?, ?> instantiate(@NotNull CommandBuildContext commandBuildContext) {
|
public RepeatArgumentType<?, ?> instantiate(CommandBuildContext commandBuildContext) {
|
||||||
var child = child().instantiate(commandBuildContext);
|
var child = child().instantiate(commandBuildContext);
|
||||||
return flatten ? RepeatArgumentType.someFlat((ArgumentType) child, some()) : RepeatArgumentType.some(child, some());
|
return flatten ? RepeatArgumentType.someFlat((ArgumentType) child, some()) : RepeatArgumentType.some(child, some());
|
||||||
}
|
}
|
||||||
|
@@ -48,11 +48,15 @@ public class CommandBuilder<S> implements CommandNodeBuilder<S, Command<S>> {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CommandBuilder<S> arg(String name, ArgumentType<?> type) {
|
public CommandBuilder<S> arg(ArgumentBuilder<S, ?> arg) {
|
||||||
args.add(RequiredArgumentBuilder.argument(name, type));
|
args.add(arg);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CommandBuilder<S> arg(String name, ArgumentType<?> type) {
|
||||||
|
return arg(RequiredArgumentBuilder.argument(name, type));
|
||||||
|
}
|
||||||
|
|
||||||
public <T> CommandNodeBuilder<S, ArgCommand<S, List<T>>> argManyValue(String name, ArgumentType<T> type, List<T> empty) {
|
public <T> CommandNodeBuilder<S, ArgCommand<S, List<T>>> argManyValue(String name, ArgumentType<T> type, List<T> empty) {
|
||||||
return argMany(name, type, () -> empty);
|
return argMany(name, type, () -> empty);
|
||||||
}
|
}
|
||||||
@@ -74,7 +78,7 @@ public class CommandBuilder<S> implements CommandNodeBuilder<S, Command<S>> {
|
|||||||
|
|
||||||
return command -> {
|
return command -> {
|
||||||
// The node for no arguments
|
// The node for no arguments
|
||||||
var tail = tail(ctx -> command.run(ctx, empty.get()));
|
var tail = setupTail(ctx -> command.run(ctx, empty.get()));
|
||||||
|
|
||||||
// The node for one or more arguments
|
// The node for one or more arguments
|
||||||
ArgumentBuilder<S, ?> moreArg = RequiredArgumentBuilder
|
ArgumentBuilder<S, ?> moreArg = RequiredArgumentBuilder
|
||||||
@@ -83,7 +87,7 @@ public class CommandBuilder<S> implements CommandNodeBuilder<S, Command<S>> {
|
|||||||
|
|
||||||
// Chain all of them together!
|
// Chain all of them together!
|
||||||
tail.then(moreArg);
|
tail.then(moreArg);
|
||||||
return link(tail);
|
return buildTail(tail);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,20 +98,16 @@ public class CommandBuilder<S> implements CommandNodeBuilder<S, Command<S>> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandNode<S> executes(Command<S> command) {
|
public CommandNode<S> executes(Command<S> command) {
|
||||||
if (args.isEmpty()) throw new IllegalStateException("Cannot have empty arg chain builder");
|
return buildTail(setupTail(command));
|
||||||
|
|
||||||
return link(tail(command));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ArgumentBuilder<S, ?> tail(Command<S> command) {
|
private ArgumentBuilder<S, ?> setupTail(Command<S> command) {
|
||||||
var defaultTail = args.get(args.size() - 1);
|
return args.get(args.size() - 1).executes(command);
|
||||||
defaultTail.executes(command);
|
|
||||||
if (requires != null) defaultTail.requires(requires);
|
|
||||||
return defaultTail;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private CommandNode<S> link(ArgumentBuilder<S, ?> tail) {
|
private CommandNode<S> buildTail(ArgumentBuilder<S, ?> tail) {
|
||||||
for (var i = args.size() - 2; i >= 0; i--) tail = args.get(i).then(tail);
|
for (var i = args.size() - 2; i >= 0; i--) tail = args.get(i).then(tail);
|
||||||
|
if (requires != null) tail.requires(requires);
|
||||||
return tail.build();
|
return tail.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,6 +10,7 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
|||||||
import com.mojang.brigadier.context.CommandContext;
|
import com.mojang.brigadier.context.CommandContext;
|
||||||
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.shared.command.UserLevel;
|
||||||
import net.minecraft.ChatFormatting;
|
import net.minecraft.ChatFormatting;
|
||||||
import net.minecraft.commands.CommandSourceStack;
|
import net.minecraft.commands.CommandSourceStack;
|
||||||
import net.minecraft.network.chat.ClickEvent;
|
import net.minecraft.network.chat.ClickEvent;
|
||||||
@@ -18,6 +19,8 @@ import net.minecraft.network.chat.Component;
|
|||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static dan200.computercraft.core.util.Nullability.assertNonNull;
|
import static dan200.computercraft.core.util.Nullability.assertNonNull;
|
||||||
import static dan200.computercraft.shared.command.text.ChatHelpers.coloured;
|
import static dan200.computercraft.shared.command.text.ChatHelpers.coloured;
|
||||||
@@ -37,6 +40,29 @@ public final class HelpingArgumentBuilder extends LiteralArgumentBuilder<Command
|
|||||||
return new HelpingArgumentBuilder(literal);
|
return new HelpingArgumentBuilder(literal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LiteralArgumentBuilder<CommandSourceStack> requires(Predicate<CommandSourceStack> requirement) {
|
||||||
|
throw new IllegalStateException("Cannot use requires on a HelpingArgumentBuilder");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Predicate<CommandSourceStack> getRequirement() {
|
||||||
|
// The requirement of this node is the union of all child's requirements.
|
||||||
|
var requirements = Stream.concat(
|
||||||
|
children.stream().map(ArgumentBuilder::getRequirement),
|
||||||
|
getArguments().stream().map(CommandNode::getRequirement)
|
||||||
|
).toList();
|
||||||
|
|
||||||
|
// If all requirements are a UserLevel, take the union of those instead.
|
||||||
|
var userLevel = UserLevel.OWNER;
|
||||||
|
for (var requirement : requirements) {
|
||||||
|
if (!(requirement instanceof UserLevel level)) return x -> requirements.stream().anyMatch(y -> y.test(x));
|
||||||
|
userLevel = UserLevel.union(userLevel, level);
|
||||||
|
}
|
||||||
|
|
||||||
|
return userLevel;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LiteralArgumentBuilder<CommandSourceStack> executes(final Command<CommandSourceStack> command) {
|
public LiteralArgumentBuilder<CommandSourceStack> executes(final Command<CommandSourceStack> command) {
|
||||||
throw new IllegalStateException("Cannot use executes on a HelpingArgumentBuilder");
|
throw new IllegalStateException("Cannot use executes on a HelpingArgumentBuilder");
|
||||||
@@ -80,9 +106,7 @@ public final class HelpingArgumentBuilder extends LiteralArgumentBuilder<Command
|
|||||||
helpCommand.node = node;
|
helpCommand.node = node;
|
||||||
|
|
||||||
// Set up a /... help command
|
// Set up a /... help command
|
||||||
var helpNode = LiteralArgumentBuilder.<CommandSourceStack>literal("help")
|
var helpNode = LiteralArgumentBuilder.<CommandSourceStack>literal("help").executes(helpCommand);
|
||||||
.requires(x -> getArguments().stream().anyMatch(y -> y.getRequirement().test(x)))
|
|
||||||
.executes(helpCommand);
|
|
||||||
|
|
||||||
// Add all normal command children to this and the help node
|
// Add all normal command children to this and the help node
|
||||||
for (var child : getArguments()) {
|
for (var child : getArguments()) {
|
||||||
|
@@ -199,6 +199,11 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
|
|||||||
return localSide;
|
return localSide;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateRedstoneInputs(ServerComputer computer) {
|
||||||
|
var pos = getBlockPos();
|
||||||
|
for (var dir : DirectionUtil.FACINGS) updateRedstoneInput(computer, dir, pos.relative(dir));
|
||||||
|
}
|
||||||
|
|
||||||
private void updateRedstoneInput(ServerComputer computer, Direction dir, BlockPos targetPos) {
|
private void updateRedstoneInput(ServerComputer computer, Direction dir, BlockPos targetPos) {
|
||||||
var offsetSide = dir.getOpposite();
|
var offsetSide = dir.getOpposite();
|
||||||
var localDir = remapToLocalSide(dir);
|
var localDir = remapToLocalSide(dir);
|
||||||
@@ -254,8 +259,7 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
|
|||||||
|
|
||||||
// If the position is not any adjacent one, update all inputs. This is pretty terrible, but some redstone mods
|
// If the position is not any adjacent one, update all inputs. This is pretty terrible, but some redstone mods
|
||||||
// handle this incorrectly.
|
// handle this incorrectly.
|
||||||
var pos = getBlockPos();
|
updateRedstoneInputs(computer);
|
||||||
for (var dir : DirectionUtil.FACINGS) updateRedstoneInput(computer, dir, pos.relative(dir));
|
|
||||||
invalidSides = (1 << 6) - 1; // Mark all peripherals as dirty.
|
invalidSides = (1 << 6) - 1; // Mark all peripherals as dirty.
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,9 +268,10 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
|
|||||||
*/
|
*/
|
||||||
public void updateOutput() {
|
public void updateOutput() {
|
||||||
BlockEntityHelpers.updateBlock(this);
|
BlockEntityHelpers.updateBlock(this);
|
||||||
for (var dir : DirectionUtil.FACINGS) {
|
for (var dir : DirectionUtil.FACINGS) RedstoneUtil.propagateRedstoneOutput(getLevel(), getBlockPos(), dir);
|
||||||
RedstoneUtil.propagateRedstoneOutput(getLevel(), getBlockPos(), dir);
|
|
||||||
}
|
var computer = getServerComputer();
|
||||||
|
if (computer != null) updateRedstoneInputs(computer);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract ServerComputer createComputer(int id);
|
protected abstract ServerComputer createComputer(int id);
|
||||||
|
@@ -9,13 +9,16 @@ import dan200.computercraft.api.ComputerCraftAPI;
|
|||||||
import dan200.computercraft.api.filesystem.Mount;
|
import dan200.computercraft.api.filesystem.Mount;
|
||||||
import dan200.computercraft.api.network.PacketNetwork;
|
import dan200.computercraft.api.network.PacketNetwork;
|
||||||
import dan200.computercraft.core.ComputerContext;
|
import dan200.computercraft.core.ComputerContext;
|
||||||
import dan200.computercraft.core.computer.ComputerThread;
|
|
||||||
import dan200.computercraft.core.computer.GlobalEnvironment;
|
import dan200.computercraft.core.computer.GlobalEnvironment;
|
||||||
import dan200.computercraft.core.computer.mainthread.MainThread;
|
import dan200.computercraft.core.computer.mainthread.MainThread;
|
||||||
import dan200.computercraft.core.computer.mainthread.MainThreadConfig;
|
import dan200.computercraft.core.computer.mainthread.MainThreadConfig;
|
||||||
import dan200.computercraft.core.lua.CobaltLuaMachine;
|
import dan200.computercraft.core.lua.CobaltLuaMachine;
|
||||||
import dan200.computercraft.core.lua.ILuaMachine;
|
import dan200.computercraft.core.lua.ILuaMachine;
|
||||||
|
import dan200.computercraft.core.methods.MethodSupplier;
|
||||||
|
import dan200.computercraft.core.methods.PeripheralMethod;
|
||||||
import dan200.computercraft.impl.AbstractComputerCraftAPI;
|
import dan200.computercraft.impl.AbstractComputerCraftAPI;
|
||||||
|
import dan200.computercraft.impl.ApiFactories;
|
||||||
|
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;
|
||||||
import dan200.computercraft.shared.config.ConfigSpec;
|
import dan200.computercraft.shared.config.ConfigSpec;
|
||||||
@@ -67,11 +70,13 @@ public final class ServerContext {
|
|||||||
this.server = server;
|
this.server = server;
|
||||||
storageDir = server.getWorldPath(FOLDER);
|
storageDir = server.getWorldPath(FOLDER);
|
||||||
mainThread = new MainThread(mainThreadConfig);
|
mainThread = new MainThread(mainThreadConfig);
|
||||||
context = new ComputerContext(
|
context = ComputerContext.builder(new Environment(server))
|
||||||
new Environment(server),
|
.computerThreads(ConfigSpec.computerThreads.get())
|
||||||
new ComputerThread(ConfigSpec.computerThreads.get()),
|
.mainThreadScheduler(mainThread)
|
||||||
mainThread, luaMachine
|
.luaFactory(luaMachine)
|
||||||
);
|
.apiFactories(ApiFactories.getAll())
|
||||||
|
.genericMethods(GenericSources.getAllMethods())
|
||||||
|
.build();
|
||||||
idAssigner = new IDAssigner(storageDir.resolve("ids.json"));
|
idAssigner = new IDAssigner(storageDir.resolve("ids.json"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,6 +138,16 @@ public final class ServerContext {
|
|||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the {@link MethodSupplier} used to find methods on peripherals.
|
||||||
|
*
|
||||||
|
* @return The {@link PeripheralMethod} method supplier.
|
||||||
|
* @see ComputerContext#peripheralMethods()
|
||||||
|
*/
|
||||||
|
public MethodSupplier<PeripheralMethod> peripheralMethods() {
|
||||||
|
return context.peripheralMethods();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tick all components of this server context. This should <em>NOT</em> be called outside of {@link CommonHooks}.
|
* Tick all components of this server context. This should <em>NOT</em> be called outside of {@link CommonHooks}.
|
||||||
*/
|
*/
|
||||||
|
@@ -7,7 +7,7 @@ package dan200.computercraft.shared.computer.upload;
|
|||||||
import dan200.computercraft.api.lua.LuaFunction;
|
import dan200.computercraft.api.lua.LuaFunction;
|
||||||
import dan200.computercraft.core.apis.handles.BinaryReadableHandle;
|
import dan200.computercraft.core.apis.handles.BinaryReadableHandle;
|
||||||
import dan200.computercraft.core.apis.handles.ByteBufferChannel;
|
import dan200.computercraft.core.apis.handles.ByteBufferChannel;
|
||||||
import dan200.computercraft.core.asm.ObjectSource;
|
import dan200.computercraft.core.methods.ObjectSource;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@@ -4,21 +4,19 @@
|
|||||||
|
|
||||||
package dan200.computercraft.shared.config;
|
package dan200.computercraft.shared.config;
|
||||||
|
|
||||||
|
import com.electronwill.nightconfig.core.CommentedConfig;
|
||||||
import com.electronwill.nightconfig.core.Config;
|
import com.electronwill.nightconfig.core.Config;
|
||||||
import com.electronwill.nightconfig.core.InMemoryCommentedFormat;
|
import com.electronwill.nightconfig.core.InMemoryCommentedFormat;
|
||||||
import com.electronwill.nightconfig.core.UnmodifiableConfig;
|
import com.electronwill.nightconfig.core.UnmodifiableConfig;
|
||||||
import dan200.computercraft.core.apis.http.options.Action;
|
import dan200.computercraft.core.apis.http.options.Action;
|
||||||
import dan200.computercraft.core.apis.http.options.AddressRule;
|
import dan200.computercraft.core.apis.http.options.AddressRule;
|
||||||
|
import dan200.computercraft.core.apis.http.options.InvalidRuleException;
|
||||||
import dan200.computercraft.core.apis.http.options.PartialOptions;
|
import dan200.computercraft.core.apis.http.options.PartialOptions;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import java.util.*;
|
||||||
import java.util.Locale;
|
import java.util.function.Consumer;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.OptionalInt;
|
|
||||||
import java.util.OptionalLong;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses, checks and generates {@link Config}s for {@link AddressRule}.
|
* Parses, checks and generates {@link Config}s for {@link AddressRule}.
|
||||||
@@ -26,49 +24,65 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
class AddressRuleConfig {
|
class AddressRuleConfig {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(AddressRuleConfig.class);
|
private static final Logger LOG = LoggerFactory.getLogger(AddressRuleConfig.class);
|
||||||
|
|
||||||
public static UnmodifiableConfig makeRule(String host, Action action) {
|
private static final AddressRule REJECT_ALL = AddressRule.parse("*", OptionalInt.empty(), Action.DENY.toPartial());
|
||||||
var config = InMemoryCommentedFormat.defaultInstance().createConfig(ConcurrentHashMap::new);
|
|
||||||
config.add("host", host);
|
|
||||||
config.add("action", action.name().toLowerCase(Locale.ROOT));
|
|
||||||
|
|
||||||
if (host.equals("*") && action == Action.ALLOW) {
|
public static List<UnmodifiableConfig> defaultRules() {
|
||||||
config.setComment("max_download", """
|
return List.of(
|
||||||
The maximum size (in bytes) that a computer can download in a single request.
|
makeRule(config -> {
|
||||||
Note that responses may receive more data than allowed, but this data will not
|
config.setComment("host", """
|
||||||
be returned to the client.""");
|
The magic "$private" host matches all private address ranges, such as localhost and 192.168.0.0/16.
|
||||||
config.set("max_download", AddressRule.MAX_DOWNLOAD);
|
This rule prevents computers accessing internal services, and is strongly recommended.""");
|
||||||
|
config.add("host", "$private");
|
||||||
|
|
||||||
config.setComment("max_upload", """
|
config.setComment("action", "Deny all requests to private IP addresses.");
|
||||||
The maximum size (in bytes) that a computer can upload in a single request. This
|
config.add("action", Action.DENY.name().toLowerCase(Locale.ROOT));
|
||||||
includes headers and POST text.""");
|
}),
|
||||||
config.set("max_upload", AddressRule.MAX_UPLOAD);
|
makeRule(config -> {
|
||||||
|
config.setComment("host", """
|
||||||
|
The wildcard "*" rule matches all remaining hosts.""");
|
||||||
|
config.add("host", "*");
|
||||||
|
|
||||||
config.setComment("max_websocket_message", "The maximum size (in bytes) that a computer can send or receive in one websocket packet.");
|
config.setComment("action", "Allow all non-denied hosts.");
|
||||||
config.set("max_websocket_message", AddressRule.WEBSOCKET_MESSAGE);
|
config.add("action", Action.ALLOW.name().toLowerCase(Locale.ROOT));
|
||||||
|
|
||||||
config.setComment("use_proxy", "Enable use of the HTTP/SOCKS proxy if it is configured.");
|
config.setComment("max_download", """
|
||||||
config.set("use_proxy", false);
|
The maximum size (in bytes) that a computer can download in a single request.
|
||||||
}
|
Note that responses may receive more data than allowed, but this data will not
|
||||||
|
be returned to the client.""");
|
||||||
|
config.set("max_download", AddressRule.MAX_DOWNLOAD);
|
||||||
|
|
||||||
|
config.setComment("max_upload", """
|
||||||
|
The maximum size (in bytes) that a computer can upload in a single request. This
|
||||||
|
includes headers and POST text.""");
|
||||||
|
config.set("max_upload", AddressRule.MAX_UPLOAD);
|
||||||
|
|
||||||
|
config.setComment("max_websocket_message", "The maximum size (in bytes) that a computer can send or receive in one websocket packet.");
|
||||||
|
config.set("max_websocket_message", AddressRule.WEBSOCKET_MESSAGE);
|
||||||
|
|
||||||
|
config.setComment("use_proxy", "Enable use of the HTTP/SOCKS proxy if it is configured.");
|
||||||
|
config.set("use_proxy", false);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static UnmodifiableConfig makeRule(Consumer<CommentedConfig> setup) {
|
||||||
|
var config = InMemoryCommentedFormat.defaultInstance().createConfig(LinkedHashMap::new);
|
||||||
|
setup.accept(config);
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean checkRule(UnmodifiableConfig builder) {
|
public static AddressRule parseRule(UnmodifiableConfig builder) {
|
||||||
var hostObj = get(builder, "host", String.class).orElse(null);
|
try {
|
||||||
var port = unboxOptInt(get(builder, "port", Number.class));
|
return doParseRule(builder);
|
||||||
return hostObj != null && checkEnum(builder, "action", Action.class)
|
} catch (InvalidRuleException e) {
|
||||||
&& check(builder, "port", Number.class)
|
LOG.error("Malformed HTTP rule: {} HTTP will NOT work until this is fixed.", e.getMessage());
|
||||||
&& check(builder, "max_upload", Number.class)
|
return REJECT_ALL;
|
||||||
&& check(builder, "max_download", Number.class)
|
}
|
||||||
&& check(builder, "websocket_message", Number.class)
|
|
||||||
&& check(builder, "use_proxy", Boolean.class)
|
|
||||||
&& AddressRule.parse(hostObj, port, PartialOptions.DEFAULT) != null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
public static AddressRule doParseRule(UnmodifiableConfig builder) {
|
||||||
public static AddressRule parseRule(UnmodifiableConfig builder) {
|
|
||||||
var hostObj = get(builder, "host", String.class).orElse(null);
|
var hostObj = get(builder, "host", String.class).orElse(null);
|
||||||
if (hostObj == null) return null;
|
if (hostObj == null) throw new InvalidRuleException("No 'host' specified");
|
||||||
|
|
||||||
var action = getEnum(builder, "action", Action.class).orElse(null);
|
var action = getEnum(builder, "action", Action.class).orElse(null);
|
||||||
var port = unboxOptInt(get(builder, "port", Number.class));
|
var port = unboxOptInt(get(builder, "port", Number.class));
|
||||||
@@ -88,38 +102,19 @@ class AddressRuleConfig {
|
|||||||
return AddressRule.parse(hostObj, port, options);
|
return AddressRule.parse(hostObj, port, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> boolean check(UnmodifiableConfig config, String field, Class<T> klass) {
|
|
||||||
var value = config.get(field);
|
|
||||||
if (value == null || klass.isInstance(value)) return true;
|
|
||||||
|
|
||||||
LOG.warn("HTTP rule's {} is not a {}.", field, klass.getSimpleName());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static <T extends Enum<T>> boolean checkEnum(UnmodifiableConfig config, String field, Class<T> klass) {
|
|
||||||
var value = config.get(field);
|
|
||||||
if (value == null) return true;
|
|
||||||
|
|
||||||
if (!(value instanceof String)) {
|
|
||||||
LOG.warn("HTTP rule's {} is not a string", field);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parseEnum(klass, (String) value) == null) {
|
|
||||||
LOG.warn("HTTP rule's {} is not a known option", field);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static <T> Optional<T> get(UnmodifiableConfig config, String field, Class<T> klass) {
|
private static <T> Optional<T> get(UnmodifiableConfig config, String field, Class<T> klass) {
|
||||||
var value = config.get(field);
|
var value = config.get(field);
|
||||||
return klass.isInstance(value) ? Optional.of(klass.cast(value)) : Optional.empty();
|
if (value == null) return Optional.empty();
|
||||||
|
if (klass.isInstance(value)) return Optional.of(klass.cast(value));
|
||||||
|
|
||||||
|
throw new InvalidRuleException(String.format(
|
||||||
|
"Field '%s' should be a '%s' but is a %s.",
|
||||||
|
field, klass.getSimpleName(), value.getClass().getSimpleName()
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T extends Enum<T>> Optional<T> getEnum(UnmodifiableConfig config, String field, Class<T> klass) {
|
private static <T extends Enum<T>> Optional<T> getEnum(UnmodifiableConfig config, String field, Class<T> klass) {
|
||||||
return get(config, field, String.class).map(x -> parseEnum(klass, x));
|
return get(config, field, String.class).map(x -> parseEnum(field, klass, x));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static OptionalLong unboxOptLong(Optional<? extends Number> value) {
|
private static OptionalLong unboxOptLong(Optional<? extends Number> value) {
|
||||||
@@ -130,11 +125,14 @@ class AddressRuleConfig {
|
|||||||
return value.map(Number::intValue).map(OptionalInt::of).orElse(OptionalInt.empty());
|
return value.map(Number::intValue).map(OptionalInt::of).orElse(OptionalInt.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
private static <T extends Enum<T>> T parseEnum(String field, Class<T> klass, String x) {
|
||||||
private static <T extends Enum<T>> T parseEnum(Class<T> klass, String x) {
|
|
||||||
for (var value : klass.getEnumConstants()) {
|
for (var value : klass.getEnumConstants()) {
|
||||||
if (value.name().equalsIgnoreCase(x)) return value;
|
if (value.name().equalsIgnoreCase(x)) return value;
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
|
throw new InvalidRuleException(String.format(
|
||||||
|
"Field '%s' should be one of %s, but is '%s'.",
|
||||||
|
field, Arrays.stream(klass.getEnumConstants()).map(Enum::name).toList(), x
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -54,12 +54,6 @@ public interface ConfigFile {
|
|||||||
* A group of config entries.
|
* A group of config entries.
|
||||||
*/
|
*/
|
||||||
non-sealed interface Group extends Entry {
|
non-sealed interface Group extends Entry {
|
||||||
/**
|
|
||||||
* Get all entries in this group.
|
|
||||||
*
|
|
||||||
* @return All child entries.
|
|
||||||
*/
|
|
||||||
Stream<Entry> children();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -9,7 +9,6 @@ import dan200.computercraft.api.ComputerCraftAPI;
|
|||||||
import dan200.computercraft.core.CoreConfig;
|
import dan200.computercraft.core.CoreConfig;
|
||||||
import dan200.computercraft.core.Logging;
|
import dan200.computercraft.core.Logging;
|
||||||
import dan200.computercraft.core.apis.http.NetworkUtils;
|
import dan200.computercraft.core.apis.http.NetworkUtils;
|
||||||
import dan200.computercraft.core.apis.http.options.Action;
|
|
||||||
import dan200.computercraft.core.apis.http.options.ProxyType;
|
import dan200.computercraft.core.apis.http.options.ProxyType;
|
||||||
import dan200.computercraft.core.computer.mainthread.MainThreadConfig;
|
import dan200.computercraft.core.computer.mainthread.MainThreadConfig;
|
||||||
import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer;
|
import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer;
|
||||||
@@ -20,9 +19,7 @@ import org.apache.logging.log4j.core.filter.MarkerFilter;
|
|||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public final class ConfigSpec {
|
public final class ConfigSpec {
|
||||||
@@ -38,6 +35,7 @@ public final class ConfigSpec {
|
|||||||
public static final ConfigFile.Value<Boolean> logComputerErrors;
|
public static final ConfigFile.Value<Boolean> logComputerErrors;
|
||||||
public static final ConfigFile.Value<Boolean> commandRequireCreative;
|
public static final ConfigFile.Value<Boolean> commandRequireCreative;
|
||||||
public static final ConfigFile.Value<Integer> uploadMaxSize;
|
public static final ConfigFile.Value<Integer> uploadMaxSize;
|
||||||
|
public static final ConfigFile.Value<List<? extends String>> disabledGenericMethods;
|
||||||
|
|
||||||
public static final ConfigFile.Value<Integer> computerThreads;
|
public static final ConfigFile.Value<Integer> computerThreads;
|
||||||
public static final ConfigFile.Value<Integer> maxMainGlobalTime;
|
public static final ConfigFile.Value<Integer> maxMainGlobalTime;
|
||||||
@@ -142,6 +140,19 @@ public final class ConfigSpec {
|
|||||||
Require players to be in creative mode and be opped in order to interact with
|
Require players to be in creative mode and be opped in order to interact with
|
||||||
command computers. This is the default behaviour for vanilla's Command blocks.""")
|
command computers. This is the default behaviour for vanilla's Command blocks.""")
|
||||||
.define("command_require_creative", Config.commandRequireCreative);
|
.define("command_require_creative", Config.commandRequireCreative);
|
||||||
|
|
||||||
|
disabledGenericMethods = builder
|
||||||
|
.comment("""
|
||||||
|
A list of generic methods or method sources to disable. Generic methods are
|
||||||
|
methods added to a block/block entity when there is no explicit peripheral
|
||||||
|
provider. This includes inventory methods (i.e. inventory.getItemDetail,
|
||||||
|
inventory.pushItems), and (if on Forge), the fluid_storage and energy_storage
|
||||||
|
methods.
|
||||||
|
Methods in this list can either be a whole group of methods (computercraft:inventory)
|
||||||
|
or a single method (computercraft:inventory#pushItems).
|
||||||
|
""")
|
||||||
|
.worldRestart()
|
||||||
|
.defineList("disabled_generic_methods", List.of(), x -> x instanceof String);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -182,9 +193,9 @@ public final class ConfigSpec {
|
|||||||
|
|
||||||
httpEnabled = builder
|
httpEnabled = builder
|
||||||
.comment("""
|
.comment("""
|
||||||
Enable the "http" API on Computers. This also disables the "pastebin" and "wget"
|
Enable the "http" API on Computers. Disabling this also disables the "pastebin" and
|
||||||
programs, that many users rely on. It's recommended to leave this on and use the
|
"wget" programs, that many users rely on. It's recommended to leave this on and use
|
||||||
"rules" config option to impose more fine-grained control.""")
|
the "rules" config option to impose more fine-grained control.""")
|
||||||
.define("enabled", CoreConfig.httpEnabled);
|
.define("enabled", CoreConfig.httpEnabled);
|
||||||
|
|
||||||
httpWebsocketEnabled = builder
|
httpWebsocketEnabled = builder
|
||||||
@@ -194,16 +205,23 @@ public final class ConfigSpec {
|
|||||||
httpRules = builder
|
httpRules = builder
|
||||||
.comment("""
|
.comment("""
|
||||||
A list of rules which control behaviour of the "http" API for specific domains or
|
A list of rules which control behaviour of the "http" API for specific domains or
|
||||||
IPs. Each rule is an item with a 'host' to match against, and a series of
|
IPs. Each rule matches against a hostname and an optional port, and then sets several
|
||||||
properties. Rules are evaluated in order, meaning earlier rules override later
|
properties for the request. Rules are evaluated in order, meaning earlier rules override
|
||||||
ones.
|
later ones.
|
||||||
The host may be a domain name ("pastebin.com"), wildcard ("*.pastebin.com") or
|
|
||||||
CIDR notation ("127.0.0.0/8").
|
Valid properties:
|
||||||
If no rules, the domain is blocked.""")
|
- "host" (required): The domain or IP address this rule matches. This may be a domain name
|
||||||
.defineList("rules", Arrays.asList(
|
("pastebin.com"), wildcard ("*.pastebin.com") or CIDR notation ("127.0.0.0/8").
|
||||||
AddressRuleConfig.makeRule("$private", Action.DENY),
|
- "port" (optional): Only match requests for a specific port, such as 80 or 443.
|
||||||
AddressRuleConfig.makeRule("*", Action.ALLOW)
|
|
||||||
), x -> x instanceof UnmodifiableConfig && AddressRuleConfig.checkRule((UnmodifiableConfig) x));
|
- "action" (optional): Whether to allow or deny this request.
|
||||||
|
- "max_download" (optional): The maximum size (in bytes) that a computer can download in this
|
||||||
|
request.
|
||||||
|
- "max_upload" (optional): The maximum size (in bytes) that a computer can upload in a this request.
|
||||||
|
- "max_websocket_message" (optional): The maximum size (in bytes) that a computer can send or
|
||||||
|
receive in one websocket packet.
|
||||||
|
- "use_proxy" (optional): Enable use of the HTTP/SOCKS proxy if it is configured.""")
|
||||||
|
.defineList("rules", AddressRuleConfig.defaultRules(), x -> x instanceof UnmodifiableConfig);
|
||||||
|
|
||||||
httpMaxRequests = builder
|
httpMaxRequests = builder
|
||||||
.comment("""
|
.comment("""
|
||||||
@@ -395,8 +413,8 @@ public final class ConfigSpec {
|
|||||||
// HTTP
|
// HTTP
|
||||||
CoreConfig.httpEnabled = httpEnabled.get();
|
CoreConfig.httpEnabled = httpEnabled.get();
|
||||||
CoreConfig.httpWebsocketEnabled = httpWebsocketEnabled.get();
|
CoreConfig.httpWebsocketEnabled = httpWebsocketEnabled.get();
|
||||||
CoreConfig.httpRules = httpRules.get().stream()
|
|
||||||
.map(AddressRuleConfig::parseRule).filter(Objects::nonNull).toList();
|
CoreConfig.httpRules = httpRules.get().stream().map(AddressRuleConfig::parseRule).toList();
|
||||||
|
|
||||||
CoreConfig.httpMaxRequests = httpMaxRequests.get();
|
CoreConfig.httpMaxRequests = httpMaxRequests.get();
|
||||||
CoreConfig.httpMaxWebsockets = httpMaxWebsockets.get();
|
CoreConfig.httpMaxWebsockets = httpMaxWebsockets.get();
|
||||||
|
@@ -7,7 +7,6 @@ package dan200.computercraft.shared.container;
|
|||||||
import net.minecraft.core.NonNullList;
|
import net.minecraft.core.NonNullList;
|
||||||
import net.minecraft.world.Container;
|
import net.minecraft.world.Container;
|
||||||
import net.minecraft.world.ContainerHelper;
|
import net.minecraft.world.ContainerHelper;
|
||||||
import net.minecraft.world.entity.player.Player;
|
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -16,24 +15,6 @@ import net.minecraft.world.item.ItemStack;
|
|||||||
public interface BasicContainer extends Container {
|
public interface BasicContainer extends Container {
|
||||||
NonNullList<ItemStack> getContents();
|
NonNullList<ItemStack> getContents();
|
||||||
|
|
||||||
@Override
|
|
||||||
default int getMaxStackSize() {
|
|
||||||
return 64;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
default void startOpen(Player player) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
default void stopOpen(Player player) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
default boolean canPlaceItem(int slot, ItemStack stack) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default int getContainerSize() {
|
default int getContainerSize() {
|
||||||
return getContents().size();
|
return getContents().size();
|
||||||
|
@@ -5,6 +5,7 @@
|
|||||||
package dan200.computercraft.shared.integration;
|
package dan200.computercraft.shared.integration;
|
||||||
|
|
||||||
import dan200.computercraft.api.ComputerCraftAPI;
|
import dan200.computercraft.api.ComputerCraftAPI;
|
||||||
|
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||||
import dan200.computercraft.impl.PocketUpgrades;
|
import dan200.computercraft.impl.PocketUpgrades;
|
||||||
import dan200.computercraft.impl.TurtleUpgrades;
|
import dan200.computercraft.impl.TurtleUpgrades;
|
||||||
import dan200.computercraft.shared.ModRegistry;
|
import dan200.computercraft.shared.ModRegistry;
|
||||||
@@ -56,14 +57,14 @@ public final class RecipeModHelpers {
|
|||||||
for (var turtleSupplier : TURTLES) {
|
for (var turtleSupplier : TURTLES) {
|
||||||
var turtle = turtleSupplier.get();
|
var turtle = turtleSupplier.get();
|
||||||
for (var upgrade : TurtleUpgrades.instance().getUpgrades()) {
|
for (var upgrade : TurtleUpgrades.instance().getUpgrades()) {
|
||||||
upgradeItems.add(turtle.create(-1, null, -1, null, upgrade, 0, null));
|
upgradeItems.add(turtle.create(-1, null, -1, null, UpgradeData.ofDefault(upgrade), 0, null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var pocketSupplier : POCKET_COMPUTERS) {
|
for (var pocketSupplier : POCKET_COMPUTERS) {
|
||||||
var pocket = pocketSupplier.get();
|
var pocket = pocketSupplier.get();
|
||||||
for (var upgrade : PocketUpgrades.instance().getUpgrades()) {
|
for (var upgrade : PocketUpgrades.instance().getUpgrades()) {
|
||||||
upgradeItems.add(pocket.create(-1, null, -1, upgrade));
|
upgradeItems.add(pocket.create(-1, null, -1, UpgradeData.ofDefault(upgrade)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -9,6 +9,7 @@ import dan200.computercraft.api.pocket.IPocketUpgrade;
|
|||||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||||
import dan200.computercraft.api.turtle.TurtleSide;
|
import dan200.computercraft.api.turtle.TurtleSide;
|
||||||
import dan200.computercraft.api.upgrades.UpgradeBase;
|
import dan200.computercraft.api.upgrades.UpgradeBase;
|
||||||
|
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||||
import dan200.computercraft.impl.PocketUpgrades;
|
import dan200.computercraft.impl.PocketUpgrades;
|
||||||
import dan200.computercraft.impl.TurtleUpgrades;
|
import dan200.computercraft.impl.TurtleUpgrades;
|
||||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
||||||
@@ -111,20 +112,22 @@ public class UpgradeRecipeGenerator<T> {
|
|||||||
|
|
||||||
if (stack.getItem() instanceof TurtleItem item) {
|
if (stack.getItem() instanceof TurtleItem item) {
|
||||||
// Suggest possible upgrades which can be applied to this turtle
|
// Suggest possible upgrades which can be applied to this turtle
|
||||||
var left = item.getUpgrade(stack, TurtleSide.LEFT);
|
var left = item.getUpgradeWithData(stack, TurtleSide.LEFT);
|
||||||
var right = item.getUpgrade(stack, TurtleSide.RIGHT);
|
var right = item.getUpgradeWithData(stack, TurtleSide.RIGHT);
|
||||||
if (left != null && right != null) return Collections.emptyList();
|
if (left != null && right != null) return Collections.emptyList();
|
||||||
|
|
||||||
List<T> recipes = new ArrayList<>();
|
List<T> recipes = new ArrayList<>();
|
||||||
var ingredient = Ingredient.of(stack);
|
var ingredient = Ingredient.of(stack);
|
||||||
for (var upgrade : turtleUpgrades) {
|
for (var upgrade : turtleUpgrades) {
|
||||||
|
if (upgrade.turtle == null) throw new NullPointerException();
|
||||||
|
|
||||||
// The turtle is facing towards us, so upgrades on the left are actually crafted on the right.
|
// The turtle is facing towards us, so upgrades on the left are actually crafted on the right.
|
||||||
if (left == null) {
|
if (left == null) {
|
||||||
recipes.add(turtle(ingredient, upgrade.ingredient, turtleWith(stack, upgrade.turtle, right)));
|
recipes.add(turtle(ingredient, upgrade.ingredient, turtleWith(stack, UpgradeData.ofDefault(upgrade.turtle), right)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (right == null) {
|
if (right == null) {
|
||||||
recipes.add(turtle(upgrade.ingredient, ingredient, turtleWith(stack, left, upgrade.turtle)));
|
recipes.add(turtle(upgrade.ingredient, ingredient, turtleWith(stack, left, UpgradeData.ofDefault(upgrade.turtle))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +140,8 @@ public class UpgradeRecipeGenerator<T> {
|
|||||||
List<T> recipes = new ArrayList<>();
|
List<T> recipes = new ArrayList<>();
|
||||||
var ingredient = Ingredient.of(stack);
|
var ingredient = Ingredient.of(stack);
|
||||||
for (var upgrade : pocketUpgrades) {
|
for (var upgrade : pocketUpgrades) {
|
||||||
recipes.add(pocket(upgrade.ingredient, ingredient, pocketWith(stack, upgrade.pocket)));
|
if (upgrade.pocket == null) throw new NullPointerException();
|
||||||
|
recipes.add(pocket(upgrade.ingredient, ingredient, pocketWith(stack, UpgradeData.ofDefault(upgrade.pocket))));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Collections.unmodifiableList(recipes);
|
return Collections.unmodifiableList(recipes);
|
||||||
@@ -180,21 +184,21 @@ public class UpgradeRecipeGenerator<T> {
|
|||||||
if (stack.getItem() instanceof TurtleItem item) {
|
if (stack.getItem() instanceof TurtleItem item) {
|
||||||
List<T> recipes = new ArrayList<>(0);
|
List<T> recipes = new ArrayList<>(0);
|
||||||
|
|
||||||
var left = item.getUpgrade(stack, TurtleSide.LEFT);
|
var left = item.getUpgradeWithData(stack, TurtleSide.LEFT);
|
||||||
var right = item.getUpgrade(stack, TurtleSide.RIGHT);
|
var right = item.getUpgradeWithData(stack, TurtleSide.RIGHT);
|
||||||
|
|
||||||
// The turtle is facing towards us, so upgrades on the left are actually crafted on the right.
|
// The turtle is facing towards us, so upgrades on the left are actually crafted on the right.
|
||||||
if (left != null) {
|
if (left != null) {
|
||||||
recipes.add(turtle(
|
recipes.add(turtle(
|
||||||
Ingredient.of(turtleWith(stack, null, right)),
|
Ingredient.of(turtleWith(stack, null, right)),
|
||||||
Ingredient.of(left.getCraftingItem()),
|
Ingredient.of(left.getUpgradeItem()),
|
||||||
stack
|
stack
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (right != null) {
|
if (right != null) {
|
||||||
recipes.add(turtle(
|
recipes.add(turtle(
|
||||||
Ingredient.of(right.getCraftingItem()),
|
Ingredient.of(right.getUpgradeItem()),
|
||||||
Ingredient.of(turtleWith(stack, left, null)),
|
Ingredient.of(turtleWith(stack, left, null)),
|
||||||
stack
|
stack
|
||||||
));
|
));
|
||||||
@@ -204,9 +208,9 @@ public class UpgradeRecipeGenerator<T> {
|
|||||||
} else if (stack.getItem() instanceof PocketComputerItem) {
|
} else if (stack.getItem() instanceof PocketComputerItem) {
|
||||||
List<T> recipes = new ArrayList<>(0);
|
List<T> recipes = new ArrayList<>(0);
|
||||||
|
|
||||||
var back = PocketComputerItem.getUpgrade(stack);
|
var back = PocketComputerItem.getUpgradeWithData(stack);
|
||||||
if (back != null) {
|
if (back != null) {
|
||||||
recipes.add(pocket(Ingredient.of(back.getCraftingItem()), Ingredient.of(pocketWith(stack, null)), stack));
|
recipes.add(pocket(Ingredient.of(back.getUpgradeItem()), Ingredient.of(pocketWith(stack, null)), stack));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Collections.unmodifiableList(recipes);
|
return Collections.unmodifiableList(recipes);
|
||||||
@@ -215,7 +219,7 @@ public class UpgradeRecipeGenerator<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ItemStack turtleWith(ItemStack stack, @Nullable ITurtleUpgrade left, @Nullable ITurtleUpgrade right) {
|
private static ItemStack turtleWith(ItemStack stack, @Nullable UpgradeData<ITurtleUpgrade> left, @Nullable UpgradeData<ITurtleUpgrade> right) {
|
||||||
var item = (TurtleItem) stack.getItem();
|
var item = (TurtleItem) stack.getItem();
|
||||||
return item.create(
|
return item.create(
|
||||||
item.getComputerID(stack), item.getLabel(stack), item.getColour(stack),
|
item.getComputerID(stack), item.getLabel(stack), item.getColour(stack),
|
||||||
@@ -223,7 +227,7 @@ public class UpgradeRecipeGenerator<T> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ItemStack pocketWith(ItemStack stack, @Nullable IPocketUpgrade back) {
|
private static ItemStack pocketWith(ItemStack stack, @Nullable UpgradeData<IPocketUpgrade> back) {
|
||||||
var item = (PocketComputerItem) stack.getItem();
|
var item = (PocketComputerItem) stack.getItem();
|
||||||
return item.create(
|
return item.create(
|
||||||
item.getComputerID(stack), item.getLabel(stack), item.getColour(stack), back
|
item.getComputerID(stack), item.getLabel(stack), item.getColour(stack), back
|
||||||
@@ -272,7 +276,7 @@ public class UpgradeRecipeGenerator<T> {
|
|||||||
recipes.add(turtle(
|
recipes.add(turtle(
|
||||||
ingredient, // Right upgrade, recipe on left
|
ingredient, // Right upgrade, recipe on left
|
||||||
Ingredient.of(turtleItem.create(-1, null, -1, null, null, 0, null)),
|
Ingredient.of(turtleItem.create(-1, null, -1, null, null, 0, null)),
|
||||||
turtleItem.create(-1, null, -1, null, turtle, 0, null)
|
turtleItem.create(-1, null, -1, null, UpgradeData.ofDefault(turtle), 0, null)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -283,7 +287,7 @@ public class UpgradeRecipeGenerator<T> {
|
|||||||
recipes.add(pocket(
|
recipes.add(pocket(
|
||||||
ingredient,
|
ingredient,
|
||||||
Ingredient.of(pocketItem.create(-1, null, -1, null)),
|
Ingredient.of(pocketItem.create(-1, null, -1, null)),
|
||||||
pocketItem.create(-1, null, -1, pocket)
|
pocketItem.create(-1, null, -1, UpgradeData.ofDefault(pocket))
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -12,19 +12,23 @@ import dan200.computercraft.api.peripheral.IComputerAccess;
|
|||||||
import dan200.computercraft.api.peripheral.IDynamicPeripheral;
|
import dan200.computercraft.api.peripheral.IDynamicPeripheral;
|
||||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||||
import dan200.computercraft.shared.platform.RegistryWrappers;
|
import dan200.computercraft.shared.platform.RegistryWrappers;
|
||||||
|
import net.minecraft.core.Direction;
|
||||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
class GenericPeripheral implements IDynamicPeripheral {
|
public final class GenericPeripheral implements IDynamicPeripheral {
|
||||||
|
private final BlockEntity tile;
|
||||||
|
private final Direction side;
|
||||||
|
|
||||||
private final String type;
|
private final String type;
|
||||||
private final Set<String> additionalTypes;
|
private final Set<String> additionalTypes;
|
||||||
private final BlockEntity tile;
|
|
||||||
private final List<SaturatedMethod> methods;
|
private final List<SaturatedMethod> methods;
|
||||||
|
|
||||||
GenericPeripheral(BlockEntity tile, @Nullable String name, Set<String> additionalTypes, List<SaturatedMethod> methods) {
|
GenericPeripheral(BlockEntity tile, Direction side, @Nullable String name, Set<String> additionalTypes, List<SaturatedMethod> methods) {
|
||||||
|
this.side = side;
|
||||||
var type = RegistryWrappers.BLOCK_ENTITY_TYPES.getKey(tile.getType());
|
var type = RegistryWrappers.BLOCK_ENTITY_TYPES.getKey(tile.getType());
|
||||||
this.tile = tile;
|
this.tile = tile;
|
||||||
this.type = name != null ? name : type.toString();
|
this.type = name != null ? name : type.toString();
|
||||||
@@ -32,6 +36,10 @@ class GenericPeripheral implements IDynamicPeripheral {
|
|||||||
this.methods = methods;
|
this.methods = methods;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Direction side() {
|
||||||
|
return side;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String[] getMethodNames() {
|
public String[] getMethodNames() {
|
||||||
var names = new String[methods.size()];
|
var names = new String[methods.size()];
|
||||||
@@ -54,7 +62,6 @@ class GenericPeripheral implements IDynamicPeripheral {
|
|||||||
return additionalTypes;
|
return additionalTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
@Override
|
||||||
public Object getTarget() {
|
public Object getTarget() {
|
||||||
return tile;
|
return tile;
|
||||||
|
@@ -0,0 +1,63 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.shared.peripheral.generic;
|
||||||
|
|
||||||
|
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||||
|
import dan200.computercraft.api.peripheral.PeripheralType;
|
||||||
|
import dan200.computercraft.core.methods.MethodSupplier;
|
||||||
|
import dan200.computercraft.core.methods.NamedMethod;
|
||||||
|
import dan200.computercraft.core.methods.PeripheralMethod;
|
||||||
|
import dan200.computercraft.shared.computer.core.ServerContext;
|
||||||
|
import net.minecraft.core.Direction;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A builder for a {@link GenericPeripheral}.
|
||||||
|
* <p>
|
||||||
|
* This handles building a list of {@linkplain SaturatedMethod methods} and computing the appropriate
|
||||||
|
* {@link PeripheralType} from the {@linkplain NamedMethod#genericType() methods' peripheral types}.
|
||||||
|
* <p>
|
||||||
|
* See the platform-specific peripheral providers for the usage of this.
|
||||||
|
*/
|
||||||
|
final class GenericPeripheralBuilder {
|
||||||
|
private final MethodSupplier<PeripheralMethod> peripheralMethods;
|
||||||
|
|
||||||
|
private @Nullable String name;
|
||||||
|
private final Set<String> additionalTypes = new HashSet<>(0);
|
||||||
|
private final ArrayList<SaturatedMethod> methods = new ArrayList<>();
|
||||||
|
|
||||||
|
GenericPeripheralBuilder(MinecraftServer server) {
|
||||||
|
peripheralMethods = ServerContext.get(server).peripheralMethods();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
IPeripheral toPeripheral(BlockEntity blockEntity, Direction side) {
|
||||||
|
if (methods.isEmpty()) return null;
|
||||||
|
|
||||||
|
methods.trimToSize();
|
||||||
|
return new GenericPeripheral(blockEntity, side, name, additionalTypes, methods);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean addMethods(Object target) {
|
||||||
|
return peripheralMethods.forEachSelfMethod(target, (name, method, info) -> {
|
||||||
|
methods.add(new SaturatedMethod(target, name, method));
|
||||||
|
|
||||||
|
// If we have a peripheral type, use it. Always pick the smallest one, so it's consistent (assuming mods
|
||||||
|
// don't change).
|
||||||
|
var type = info == null ? null : info.genericType();
|
||||||
|
if (type != null && type.getPrimaryType() != null) {
|
||||||
|
var primaryType = type.getPrimaryType();
|
||||||
|
if (this.name == null || this.name.compareTo(primaryType) > 0) this.name = primaryType;
|
||||||
|
}
|
||||||
|
if (type != null) additionalTypes.addAll(type.getAdditionalTypes());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -9,18 +9,20 @@ import dan200.computercraft.api.lua.ILuaContext;
|
|||||||
import dan200.computercraft.api.lua.LuaException;
|
import dan200.computercraft.api.lua.LuaException;
|
||||||
import dan200.computercraft.api.lua.MethodResult;
|
import dan200.computercraft.api.lua.MethodResult;
|
||||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||||
import dan200.computercraft.core.asm.NamedMethod;
|
import dan200.computercraft.core.methods.PeripheralMethod;
|
||||||
import dan200.computercraft.core.asm.PeripheralMethod;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link PeripheralMethod} along with the method's target.
|
||||||
|
*/
|
||||||
final class SaturatedMethod {
|
final class SaturatedMethod {
|
||||||
private final Object target;
|
private final Object target;
|
||||||
private final String name;
|
private final String name;
|
||||||
private final PeripheralMethod method;
|
private final PeripheralMethod method;
|
||||||
|
|
||||||
SaturatedMethod(Object target, NamedMethod<PeripheralMethod> method) {
|
SaturatedMethod(Object target, String name, PeripheralMethod method) {
|
||||||
this.target = target;
|
this.target = target;
|
||||||
name = method.getName();
|
this.name = name;
|
||||||
this.method = method.getMethod();
|
this.method = method;
|
||||||
}
|
}
|
||||||
|
|
||||||
MethodResult apply(ILuaContext context, IComputerAccess computer, IArguments args) throws LuaException {
|
MethodResult apply(ILuaContext context, IComputerAccess computer, IArguments args) throws LuaException {
|
||||||
|
@@ -16,10 +16,12 @@ import dan200.computercraft.api.peripheral.IPeripheral;
|
|||||||
import dan200.computercraft.api.peripheral.NotAttachedException;
|
import dan200.computercraft.api.peripheral.NotAttachedException;
|
||||||
import dan200.computercraft.api.peripheral.WorkMonitor;
|
import dan200.computercraft.api.peripheral.WorkMonitor;
|
||||||
import dan200.computercraft.core.apis.PeripheralAPI;
|
import dan200.computercraft.core.apis.PeripheralAPI;
|
||||||
import dan200.computercraft.core.asm.PeripheralMethod;
|
import dan200.computercraft.core.methods.PeripheralMethod;
|
||||||
import dan200.computercraft.core.util.LuaUtil;
|
import dan200.computercraft.core.util.LuaUtil;
|
||||||
|
import dan200.computercraft.shared.computer.core.ServerContext;
|
||||||
import dan200.computercraft.shared.peripheral.modem.ModemPeripheral;
|
import dan200.computercraft.shared.peripheral.modem.ModemPeripheral;
|
||||||
import dan200.computercraft.shared.peripheral.modem.ModemState;
|
import dan200.computercraft.shared.peripheral.modem.ModemState;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
import net.minecraft.world.level.Level;
|
import net.minecraft.world.level.Level;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -284,7 +286,8 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements Wi
|
|||||||
|
|
||||||
private void attachPeripheralImpl(IComputerAccess computer, ConcurrentMap<String, RemotePeripheralWrapper> peripherals, String periphName, IPeripheral peripheral) {
|
private void attachPeripheralImpl(IComputerAccess computer, ConcurrentMap<String, RemotePeripheralWrapper> peripherals, String periphName, IPeripheral peripheral) {
|
||||||
if (!peripherals.containsKey(periphName) && !periphName.equals(getLocalPeripheral().getConnectedName())) {
|
if (!peripherals.containsKey(periphName) && !periphName.equals(getLocalPeripheral().getConnectedName())) {
|
||||||
var wrapper = new RemotePeripheralWrapper(modem, peripheral, computer, periphName);
|
var methods = ServerContext.get(((ServerLevel) getLevel()).getServer()).peripheralMethods().getSelfMethods(peripheral);
|
||||||
|
var wrapper = new RemotePeripheralWrapper(modem, peripheral, computer, periphName, methods);
|
||||||
peripherals.put(periphName, wrapper);
|
peripherals.put(periphName, wrapper);
|
||||||
wrapper.attach();
|
wrapper.attach();
|
||||||
}
|
}
|
||||||
@@ -314,7 +317,7 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements Wi
|
|||||||
private volatile boolean attached;
|
private volatile boolean attached;
|
||||||
private final Set<String> mounts = new HashSet<>();
|
private final Set<String> mounts = new HashSet<>();
|
||||||
|
|
||||||
RemotePeripheralWrapper(WiredModemElement element, IPeripheral peripheral, IComputerAccess computer, String name) {
|
RemotePeripheralWrapper(WiredModemElement element, IPeripheral peripheral, IComputerAccess computer, String name, Map<String, PeripheralMethod> methods) {
|
||||||
this.element = element;
|
this.element = element;
|
||||||
this.peripheral = peripheral;
|
this.peripheral = peripheral;
|
||||||
this.computer = computer;
|
this.computer = computer;
|
||||||
@@ -322,7 +325,7 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements Wi
|
|||||||
|
|
||||||
type = Objects.requireNonNull(peripheral.getType(), "Peripheral type cannot be null");
|
type = Objects.requireNonNull(peripheral.getType(), "Peripheral type cannot be null");
|
||||||
additionalTypes = peripheral.getAdditionalTypes();
|
additionalTypes = peripheral.getAdditionalTypes();
|
||||||
methodMap = PeripheralAPI.getMethods(peripheral);
|
methodMap = methods;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void attach() {
|
public void attach() {
|
||||||
|
@@ -190,7 +190,7 @@ public abstract class SpeakerPeripheral implements IPeripheral {
|
|||||||
* The speaker supports [all of Minecraft's noteblock instruments](https://minecraft.fandom.com/wiki/Note_Block#Instruments).
|
* The speaker supports [all of Minecraft's noteblock instruments](https://minecraft.fandom.com/wiki/Note_Block#Instruments).
|
||||||
* These are:
|
* These are:
|
||||||
* <p>
|
* <p>
|
||||||
* {@code "harp"}, {@code "basedrum"}, {@code "snare"}, {@code "hat"}, {@code "bass"}, @code "flute"},
|
* {@code "harp"}, {@code "basedrum"}, {@code "snare"}, {@code "hat"}, {@code "bass"}, {@code "flute"},
|
||||||
* {@code "bell"}, {@code "guitar"}, {@code "chime"}, {@code "xylophone"}, {@code "iron_xylophone"},
|
* {@code "bell"}, {@code "guitar"}, {@code "chime"}, {@code "xylophone"}, {@code "iron_xylophone"},
|
||||||
* {@code "cow_bell"}, {@code "didgeridoo"}, {@code "bit"}, {@code "banjo"} and {@code "pling"}.
|
* {@code "cow_bell"}, {@code "didgeridoo"}, {@code "bit"}, {@code "banjo"} and {@code "pling"}.
|
||||||
*
|
*
|
||||||
|
@@ -0,0 +1,31 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.shared.platform;
|
||||||
|
|
||||||
|
import com.mojang.authlib.GameProfile;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.Direction;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared constants for {@linkplain PlatformHelper#createFakePlayer(ServerLevel, GameProfile) fake player}
|
||||||
|
* implementations.
|
||||||
|
*
|
||||||
|
* @see net.minecraft.server.level.ServerPlayer
|
||||||
|
* @see net.minecraft.world.entity.player.Player
|
||||||
|
*/
|
||||||
|
final class FakePlayerConstants {
|
||||||
|
private FakePlayerConstants() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum distance this player can reach.
|
||||||
|
* <p>
|
||||||
|
* This is used in the override of {@link net.minecraft.world.entity.player.Player#mayUseItemAt(BlockPos, Direction, ItemStack)},
|
||||||
|
* to prevent the fake player reaching more than 2 blocks away.
|
||||||
|
*/
|
||||||
|
static final double MAX_REACH = 2;
|
||||||
|
}
|
@@ -68,6 +68,13 @@ public interface PlatformHelper extends dan200.computercraft.impl.PlatformHelper
|
|||||||
return (PlatformHelper) dan200.computercraft.impl.PlatformHelper.get();
|
return (PlatformHelper) dan200.computercraft.impl.PlatformHelper.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if we're running in a development environment.
|
||||||
|
*
|
||||||
|
* @return If we're running in a development environment.
|
||||||
|
*/
|
||||||
|
boolean isDevelopmentEnvironment();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new config builder.
|
* Create a new config builder.
|
||||||
*
|
*
|
||||||
|
@@ -5,6 +5,7 @@
|
|||||||
package dan200.computercraft.shared.platform;
|
package dan200.computercraft.shared.platform;
|
||||||
|
|
||||||
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
|
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
|
||||||
|
import net.minecraft.core.IdMap;
|
||||||
import net.minecraft.core.Registry;
|
import net.minecraft.core.Registry;
|
||||||
import net.minecraft.core.registries.Registries;
|
import net.minecraft.core.registries.Registries;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
@@ -36,9 +37,7 @@ public final class RegistryWrappers {
|
|||||||
public static final RegistryWrapper<RecipeSerializer<?>> RECIPE_SERIALIZERS = PlatformHelper.get().wrap(Registries.RECIPE_SERIALIZER);
|
public static final RegistryWrapper<RecipeSerializer<?>> RECIPE_SERIALIZERS = PlatformHelper.get().wrap(Registries.RECIPE_SERIALIZER);
|
||||||
public static final RegistryWrapper<MenuType<?>> MENU = PlatformHelper.get().wrap(Registries.MENU);
|
public static final RegistryWrapper<MenuType<?>> MENU = PlatformHelper.get().wrap(Registries.MENU);
|
||||||
|
|
||||||
public interface RegistryWrapper<T> extends Iterable<T> {
|
public interface RegistryWrapper<T> extends IdMap<T> {
|
||||||
int getId(T object);
|
|
||||||
|
|
||||||
ResourceLocation getKey(T object);
|
ResourceLocation getKey(T object);
|
||||||
|
|
||||||
T get(ResourceLocation location);
|
T get(ResourceLocation location);
|
||||||
@@ -46,8 +45,6 @@ public final class RegistryWrappers {
|
|||||||
@Nullable
|
@Nullable
|
||||||
T tryGet(ResourceLocation location);
|
T tryGet(ResourceLocation location);
|
||||||
|
|
||||||
T get(int id);
|
|
||||||
|
|
||||||
default Stream<T> stream() {
|
default Stream<T> stream() {
|
||||||
return StreamSupport.stream(spliterator(), false);
|
return StreamSupport.stream(spliterator(), false);
|
||||||
}
|
}
|
||||||
@@ -56,15 +53,6 @@ public final class RegistryWrappers {
|
|||||||
private RegistryWrappers() {
|
private RegistryWrappers() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <K> void writeId(FriendlyByteBuf buf, RegistryWrapper<K> registry, K object) {
|
|
||||||
buf.writeVarInt(registry.getId(object));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <K> K readId(FriendlyByteBuf buf, RegistryWrapper<K> registry) {
|
|
||||||
var id = buf.readVarInt();
|
|
||||||
return registry.get(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <K> void writeKey(FriendlyByteBuf buf, RegistryWrapper<K> registry, K object) {
|
public static <K> void writeKey(FriendlyByteBuf buf, RegistryWrapper<K> registry, K object) {
|
||||||
buf.writeResourceLocation(registry.getKey(object));
|
buf.writeResourceLocation(registry.getKey(object));
|
||||||
}
|
}
|
||||||
|
@@ -7,6 +7,7 @@ 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.IPocketUpgrade;
|
import dan200.computercraft.api.pocket.IPocketUpgrade;
|
||||||
|
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||||
import dan200.computercraft.impl.PocketUpgrades;
|
import dan200.computercraft.impl.PocketUpgrades;
|
||||||
import dan200.computercraft.shared.pocket.core.PocketServerComputer;
|
import dan200.computercraft.shared.pocket.core.PocketServerComputer;
|
||||||
import net.minecraft.core.NonNullList;
|
import net.minecraft.core.NonNullList;
|
||||||
@@ -14,6 +15,7 @@ import net.minecraft.world.entity.player.Player;
|
|||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Control the current pocket computer, adding or removing upgrades.
|
* Control the current pocket computer, adding or removing upgrades.
|
||||||
@@ -68,7 +70,7 @@ public class PocketAPI implements ILuaAPI {
|
|||||||
if (newUpgrade == null) return new Object[]{ false, "Cannot find a valid upgrade" };
|
if (newUpgrade == null) return new Object[]{ false, "Cannot find a valid upgrade" };
|
||||||
|
|
||||||
// Remove the current upgrade
|
// Remove the current upgrade
|
||||||
if (previousUpgrade != null) storeItem(player, previousUpgrade.getCraftingItem().copy());
|
if (previousUpgrade != null) storeItem(player, previousUpgrade.getUpgradeItem());
|
||||||
|
|
||||||
// Set the new upgrade
|
// Set the new upgrade
|
||||||
computer.setUpgrade(newUpgrade);
|
computer.setUpgrade(newUpgrade);
|
||||||
@@ -93,7 +95,7 @@ public class PocketAPI implements ILuaAPI {
|
|||||||
|
|
||||||
computer.setUpgrade(null);
|
computer.setUpgrade(null);
|
||||||
|
|
||||||
storeItem(player, previousUpgrade.getCraftingItem().copy());
|
storeItem(player, previousUpgrade.getUpgradeItem());
|
||||||
|
|
||||||
return new Object[]{ true };
|
return new Object[]{ true };
|
||||||
}
|
}
|
||||||
@@ -105,13 +107,13 @@ public class PocketAPI implements ILuaAPI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static @Nullable IPocketUpgrade findUpgrade(NonNullList<ItemStack> inv, int start, @Nullable IPocketUpgrade previous) {
|
private static @Nullable UpgradeData<IPocketUpgrade> findUpgrade(NonNullList<ItemStack> inv, int start, @Nullable UpgradeData<IPocketUpgrade> previous) {
|
||||||
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(invStack);
|
var newUpgrade = PocketUpgrades.instance().get(invStack);
|
||||||
|
|
||||||
if (newUpgrade != null && 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
|
||||||
invStack = invStack.copy();
|
invStack = invStack.copy();
|
||||||
invStack.shrink(1);
|
invStack.shrink(1);
|
||||||
|
@@ -7,6 +7,7 @@ package dan200.computercraft.shared.pocket.core;
|
|||||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||||
import dan200.computercraft.api.pocket.IPocketAccess;
|
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.core.computer.ComputerSide;
|
import dan200.computercraft.core.computer.ComputerSide;
|
||||||
import dan200.computercraft.shared.common.IColouredItem;
|
import dan200.computercraft.shared.common.IColouredItem;
|
||||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||||
@@ -104,12 +105,13 @@ public class PocketServerComputer extends ServerComputer implements IPocketAcces
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated(forRemoval = true)
|
||||||
public Map<ResourceLocation, IPeripheral> getUpgrades() {
|
public Map<ResourceLocation, IPeripheral> getUpgrades() {
|
||||||
return upgrade == null ? Collections.emptyMap() : Collections.singletonMap(upgrade.getUpgradeID(), getPeripheral(ComputerSide.BACK));
|
return upgrade == null ? Collections.emptyMap() : Collections.singletonMap(upgrade.getUpgradeID(), getPeripheral(ComputerSide.BACK));
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable IPocketUpgrade getUpgrade() {
|
public @Nullable UpgradeData<IPocketUpgrade> getUpgrade() {
|
||||||
return upgrade;
|
return upgrade == null ? null : UpgradeData.of(upgrade, getUpgradeNBTData());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -119,13 +121,11 @@ public class PocketServerComputer extends ServerComputer implements IPocketAcces
|
|||||||
*
|
*
|
||||||
* @param upgrade The new upgrade to set it to, may be {@code null}.
|
* @param upgrade The new upgrade to set it to, may be {@code null}.
|
||||||
*/
|
*/
|
||||||
public void setUpgrade(@Nullable IPocketUpgrade upgrade) {
|
public void setUpgrade(@Nullable UpgradeData<IPocketUpgrade> upgrade) {
|
||||||
if (this.upgrade == upgrade) return;
|
|
||||||
|
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
PocketComputerItem.setUpgrade(stack, upgrade);
|
PocketComputerItem.setUpgrade(stack, upgrade);
|
||||||
updateUpgradeNBTData();
|
updateUpgradeNBTData();
|
||||||
this.upgrade = upgrade;
|
this.upgrade = upgrade == null ? null : upgrade.upgrade();
|
||||||
invalidatePeripheral();
|
invalidatePeripheral();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,6 +10,7 @@ import dan200.computercraft.api.ComputerCraftAPI;
|
|||||||
import dan200.computercraft.api.filesystem.Mount;
|
import dan200.computercraft.api.filesystem.Mount;
|
||||||
import dan200.computercraft.api.media.IMedia;
|
import dan200.computercraft.api.media.IMedia;
|
||||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
|
import dan200.computercraft.api.pocket.IPocketUpgrade;
|
||||||
|
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||||
import dan200.computercraft.core.computer.ComputerSide;
|
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;
|
||||||
@@ -23,6 +24,7 @@ import dan200.computercraft.shared.pocket.apis.PocketAPI;
|
|||||||
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.IDAssigner;
|
import dan200.computercraft.shared.util.IDAssigner;
|
||||||
|
import dan200.computercraft.shared.util.NBTUtil;
|
||||||
import net.minecraft.ChatFormatting;
|
import net.minecraft.ChatFormatting;
|
||||||
import net.minecraft.nbt.CompoundTag;
|
import net.minecraft.nbt.CompoundTag;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
@@ -58,7 +60,7 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I
|
|||||||
this.family = family;
|
this.family = family;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ItemStack create(int id, @Nullable String label, int colour, ComputerFamily family, @Nullable IPocketUpgrade upgrade) {
|
public static ItemStack create(int id, @Nullable String label, int colour, ComputerFamily family, @Nullable UpgradeData<IPocketUpgrade> upgrade) {
|
||||||
return switch (family) {
|
return switch (family) {
|
||||||
case NORMAL -> ModRegistry.Items.POCKET_COMPUTER_NORMAL.get().create(id, label, colour, upgrade);
|
case NORMAL -> ModRegistry.Items.POCKET_COMPUTER_NORMAL.get().create(id, label, colour, upgrade);
|
||||||
case ADVANCED -> ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get().create(id, label, colour, upgrade);
|
case ADVANCED -> ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get().create(id, label, colour, upgrade);
|
||||||
@@ -66,11 +68,14 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public ItemStack create(int id, @Nullable String label, int colour, @Nullable IPocketUpgrade upgrade) {
|
public ItemStack create(int id, @Nullable String label, int colour, @Nullable UpgradeData<IPocketUpgrade> upgrade) {
|
||||||
var result = new ItemStack(this);
|
var result = new ItemStack(this);
|
||||||
if (id >= 0) result.getOrCreateTag().putInt(NBT_ID, id);
|
if (id >= 0) result.getOrCreateTag().putInt(NBT_ID, id);
|
||||||
if (label != null) result.setHoverName(Component.literal(label));
|
if (label != null) result.setHoverName(Component.literal(label));
|
||||||
if (upgrade != null) result.getOrCreateTag().putString(NBT_UPGRADE, upgrade.getUpgradeID().toString());
|
if (upgrade != null) {
|
||||||
|
result.getOrCreateTag().putString(NBT_UPGRADE, upgrade.upgrade().getUpgradeID().toString());
|
||||||
|
if (!upgrade.data().isEmpty()) result.getOrCreateTag().put(NBT_UPGRADE_INFO, upgrade.data().copy());
|
||||||
|
}
|
||||||
if (colour != -1) result.getOrCreateTag().putInt(NBT_COLOUR, colour);
|
if (colour != -1) result.getOrCreateTag().putInt(NBT_COLOUR, colour);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -207,7 +212,9 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I
|
|||||||
setInstanceID(stack, computer.register());
|
setInstanceID(stack, computer.register());
|
||||||
setSessionID(stack, registry.getSessionID());
|
setSessionID(stack, registry.getSessionID());
|
||||||
|
|
||||||
computer.updateValues(entity, stack, getUpgrade(stack));
|
var upgrade = getUpgrade(stack);
|
||||||
|
|
||||||
|
computer.updateValues(entity, stack, upgrade);
|
||||||
computer.addAPI(new PocketAPI(computer));
|
computer.addAPI(new PocketAPI(computer));
|
||||||
|
|
||||||
// Only turn on when initially creating the computer, rather than each tick.
|
// Only turn on when initially creating the computer, rather than each tick.
|
||||||
@@ -244,7 +251,7 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I
|
|||||||
public ItemStack withFamily(ItemStack stack, ComputerFamily family) {
|
public ItemStack withFamily(ItemStack stack, ComputerFamily family) {
|
||||||
return create(
|
return create(
|
||||||
getComputerID(stack), getLabel(stack), getColour(stack),
|
getComputerID(stack), getLabel(stack), getColour(stack),
|
||||||
family, getUpgrade(stack)
|
family, getUpgradeWithData(stack)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,20 +301,27 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I
|
|||||||
|
|
||||||
public static @Nullable IPocketUpgrade getUpgrade(ItemStack stack) {
|
public static @Nullable IPocketUpgrade getUpgrade(ItemStack stack) {
|
||||||
var compound = stack.getTag();
|
var compound = stack.getTag();
|
||||||
return compound != null && compound.contains(NBT_UPGRADE)
|
if (compound == null || !compound.contains(NBT_UPGRADE)) return null;
|
||||||
? PocketUpgrades.instance().get(compound.getString(NBT_UPGRADE)) : null;
|
return PocketUpgrades.instance().get(compound.getString(NBT_UPGRADE));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setUpgrade(ItemStack stack, @Nullable IPocketUpgrade upgrade) {
|
public static @Nullable UpgradeData<IPocketUpgrade> getUpgradeWithData(ItemStack stack) {
|
||||||
|
var compound = stack.getTag();
|
||||||
|
if (compound == null || !compound.contains(NBT_UPGRADE)) return null;
|
||||||
|
var upgrade = PocketUpgrades.instance().get(compound.getString(NBT_UPGRADE));
|
||||||
|
return upgrade == null ? null : UpgradeData.of(upgrade, NBTUtil.getCompoundOrEmpty(compound, NBT_UPGRADE_INFO));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setUpgrade(ItemStack stack, @Nullable UpgradeData<IPocketUpgrade> upgrade) {
|
||||||
var compound = stack.getOrCreateTag();
|
var compound = stack.getOrCreateTag();
|
||||||
|
|
||||||
if (upgrade == null) {
|
if (upgrade == null) {
|
||||||
compound.remove(NBT_UPGRADE);
|
compound.remove(NBT_UPGRADE);
|
||||||
|
compound.remove(NBT_UPGRADE_INFO);
|
||||||
} else {
|
} else {
|
||||||
compound.putString(NBT_UPGRADE, upgrade.getUpgradeID().toString());
|
compound.putString(NBT_UPGRADE, upgrade.upgrade().getUpgradeID().toString());
|
||||||
|
compound.put(NBT_UPGRADE_INFO, upgrade.data().copy());
|
||||||
}
|
}
|
||||||
|
|
||||||
compound.remove(NBT_UPGRADE_INFO);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CompoundTag getUpgradeInfo(ItemStack stack) {
|
public static CompoundTag getUpgradeInfo(ItemStack stack) {
|
||||||
|
@@ -5,6 +5,7 @@
|
|||||||
package dan200.computercraft.shared.pocket.recipes;
|
package dan200.computercraft.shared.pocket.recipes;
|
||||||
|
|
||||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
|
import dan200.computercraft.api.pocket.IPocketUpgrade;
|
||||||
|
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||||
import dan200.computercraft.impl.PocketUpgrades;
|
import dan200.computercraft.impl.PocketUpgrades;
|
||||||
import dan200.computercraft.shared.ModRegistry;
|
import dan200.computercraft.shared.ModRegistry;
|
||||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
||||||
@@ -62,7 +63,7 @@ public final class PocketComputerUpgradeRecipe extends CustomRecipe {
|
|||||||
if (PocketComputerItem.getUpgrade(computer) != null) return ItemStack.EMPTY;
|
if (PocketComputerItem.getUpgrade(computer) != null) return ItemStack.EMPTY;
|
||||||
|
|
||||||
// Check for upgrades around the item
|
// Check for upgrades around the item
|
||||||
IPocketUpgrade upgrade = null;
|
UpgradeData<IPocketUpgrade> upgrade = null;
|
||||||
for (var y = 0; y < inventory.getHeight(); y++) {
|
for (var y = 0; y < inventory.getHeight(); y++) {
|
||||||
for (var x = 0; x < inventory.getWidth(); x++) {
|
for (var x = 0; x < inventory.getWidth(); x++) {
|
||||||
var item = inventory.getItem(x + y * inventory.getWidth());
|
var item = inventory.getItem(x + y * inventory.getWidth());
|
||||||
|
@@ -555,7 +555,7 @@ public class TurtleAPI implements ILuaAPI {
|
|||||||
* @cc.usage Refuel a turtle from the currently selected slot.
|
* @cc.usage Refuel a turtle from the currently selected slot.
|
||||||
* <pre>{@code
|
* <pre>{@code
|
||||||
* local level = turtle.getFuelLevel()
|
* local level = turtle.getFuelLevel()
|
||||||
* if new_level == "unlimited" then error("Turtle does not need fuel", 0) end
|
* if level == "unlimited" then error("Turtle does not need fuel", 0) end
|
||||||
*
|
*
|
||||||
* local ok, err = turtle.refuel()
|
* local ok, err = turtle.refuel()
|
||||||
* if ok then
|
* if ok then
|
||||||
|
@@ -5,7 +5,9 @@
|
|||||||
package dan200.computercraft.shared.turtle.blocks;
|
package dan200.computercraft.shared.turtle.blocks;
|
||||||
|
|
||||||
import dan200.computercraft.annotations.ForgeOverride;
|
import dan200.computercraft.annotations.ForgeOverride;
|
||||||
|
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||||
import dan200.computercraft.api.turtle.TurtleSide;
|
import dan200.computercraft.api.turtle.TurtleSide;
|
||||||
|
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||||
import dan200.computercraft.shared.computer.blocks.AbstractComputerBlock;
|
import dan200.computercraft.shared.computer.blocks.AbstractComputerBlock;
|
||||||
import dan200.computercraft.shared.computer.blocks.AbstractComputerBlockEntity;
|
import dan200.computercraft.shared.computer.blocks.AbstractComputerBlockEntity;
|
||||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||||
@@ -128,7 +130,7 @@ public class TurtleBlock extends AbstractComputerBlock<TurtleBlockEntity> implem
|
|||||||
if (stack.getItem() instanceof TurtleItem item) {
|
if (stack.getItem() instanceof TurtleItem item) {
|
||||||
// Set Upgrades
|
// Set Upgrades
|
||||||
for (var side : TurtleSide.values()) {
|
for (var side : TurtleSide.values()) {
|
||||||
turtle.getAccess().setUpgrade(side, item.getUpgrade(stack, side));
|
turtle.getAccess().setUpgradeWithData(side, item.getUpgradeWithData(stack, side));
|
||||||
}
|
}
|
||||||
|
|
||||||
turtle.getAccess().setFuelLevel(item.getFuelLevel(stack));
|
turtle.getAccess().setFuelLevel(item.getFuelLevel(stack));
|
||||||
@@ -161,11 +163,16 @@ public class TurtleBlock extends AbstractComputerBlock<TurtleBlockEntity> implem
|
|||||||
var access = turtle.getAccess();
|
var access = turtle.getAccess();
|
||||||
return TurtleItem.create(
|
return TurtleItem.create(
|
||||||
turtle.getComputerID(), turtle.getLabel(), access.getColour(), turtle.getFamily(),
|
turtle.getComputerID(), turtle.getLabel(), access.getColour(), turtle.getFamily(),
|
||||||
access.getUpgrade(TurtleSide.LEFT), access.getUpgrade(TurtleSide.RIGHT),
|
withPersistedData(access.getUpgradeWithData(TurtleSide.LEFT)),
|
||||||
|
withPersistedData(access.getUpgradeWithData(TurtleSide.RIGHT)),
|
||||||
access.getFuelLevel(), turtle.getOverlay()
|
access.getFuelLevel(), turtle.getOverlay()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static @Nullable UpgradeData<ITurtleUpgrade> withPersistedData(@Nullable UpgradeData<ITurtleUpgrade> upgrade) {
|
||||||
|
return upgrade == null ? null : UpgradeData.of(upgrade.upgrade(), upgrade.upgrade().getPersistedData(upgrade.data()));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public <U extends BlockEntity> BlockEntityTicker<U> getTicker(Level level, BlockState state, BlockEntityType<U> type) {
|
public <U extends BlockEntity> BlockEntityTicker<U> getTicker(Level level, BlockState state, BlockEntityType<U> type) {
|
||||||
|
@@ -13,8 +13,8 @@ import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
|||||||
import dan200.computercraft.api.turtle.TurtleAnimation;
|
import dan200.computercraft.api.turtle.TurtleAnimation;
|
||||||
import dan200.computercraft.api.turtle.TurtleCommand;
|
import dan200.computercraft.api.turtle.TurtleCommand;
|
||||||
import dan200.computercraft.api.turtle.TurtleSide;
|
import dan200.computercraft.api.turtle.TurtleSide;
|
||||||
|
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||||
import dan200.computercraft.core.computer.ComputerSide;
|
import dan200.computercraft.core.computer.ComputerSide;
|
||||||
import dan200.computercraft.core.util.Colour;
|
|
||||||
import dan200.computercraft.impl.TurtleUpgrades;
|
import dan200.computercraft.impl.TurtleUpgrades;
|
||||||
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.ServerComputer;
|
||||||
@@ -34,7 +34,6 @@ import net.minecraft.tags.FluidTags;
|
|||||||
import net.minecraft.world.Container;
|
import net.minecraft.world.Container;
|
||||||
import net.minecraft.world.entity.Entity;
|
import net.minecraft.world.entity.Entity;
|
||||||
import net.minecraft.world.entity.MoverType;
|
import net.minecraft.world.entity.MoverType;
|
||||||
import net.minecraft.world.item.DyeColor;
|
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
import net.minecraft.world.level.Level;
|
import net.minecraft.world.level.Level;
|
||||||
import net.minecraft.world.level.material.PushReaction;
|
import net.minecraft.world.level.material.PushReaction;
|
||||||
@@ -141,17 +140,16 @@ public class TurtleBrain implements TurtleAccessInternal {
|
|||||||
overlay = nbt.contains(NBT_OVERLAY) ? new ResourceLocation(nbt.getString(NBT_OVERLAY)) : null;
|
overlay = nbt.contains(NBT_OVERLAY) ? new ResourceLocation(nbt.getString(NBT_OVERLAY)) : null;
|
||||||
|
|
||||||
// Read upgrades
|
// Read upgrades
|
||||||
setUpgradeDirect(TurtleSide.LEFT, nbt.contains(NBT_LEFT_UPGRADE) ? TurtleUpgrades.instance().get(nbt.getString(NBT_LEFT_UPGRADE)) : null);
|
setUpgradeDirect(TurtleSide.LEFT, readUpgrade(nbt, NBT_LEFT_UPGRADE, NBT_LEFT_UPGRADE_DATA));
|
||||||
setUpgradeDirect(TurtleSide.RIGHT, nbt.contains(NBT_RIGHT_UPGRADE) ? TurtleUpgrades.instance().get(nbt.getString(NBT_RIGHT_UPGRADE)) : null);
|
setUpgradeDirect(TurtleSide.RIGHT, readUpgrade(nbt, NBT_RIGHT_UPGRADE, NBT_RIGHT_UPGRADE_DATA));
|
||||||
|
}
|
||||||
|
|
||||||
// NBT
|
private @Nullable UpgradeData<ITurtleUpgrade> readUpgrade(CompoundTag tag, String upgradeKey, String dataKey) {
|
||||||
upgradeNBTData.clear();
|
if (!tag.contains(upgradeKey)) return null;
|
||||||
if (nbt.contains(NBT_LEFT_UPGRADE_DATA)) {
|
var upgrade = TurtleUpgrades.instance().get(tag.getString(upgradeKey));
|
||||||
upgradeNBTData.put(TurtleSide.LEFT, nbt.getCompound(NBT_LEFT_UPGRADE_DATA).copy());
|
if (upgrade == null) return null;
|
||||||
}
|
|
||||||
if (nbt.contains(NBT_RIGHT_UPGRADE_DATA)) {
|
return UpgradeData.of(upgrade, tag.getCompound(dataKey));
|
||||||
upgradeNBTData.put(TurtleSide.RIGHT, nbt.getCompound(NBT_RIGHT_UPGRADE_DATA).copy());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeCommon(CompoundTag nbt) {
|
private void writeCommon(CompoundTag nbt) {
|
||||||
@@ -465,23 +463,6 @@ public class TurtleBrain implements TurtleAccessInternal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable DyeColor getDyeColour() {
|
|
||||||
if (colourHex == -1) return null;
|
|
||||||
var colour = Colour.fromHex(colourHex);
|
|
||||||
return colour == null ? null : DyeColor.byId(15 - colour.ordinal());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDyeColour(@Nullable DyeColor dyeColour) {
|
|
||||||
var newColour = -1;
|
|
||||||
if (dyeColour != null) {
|
|
||||||
newColour = Colour.values()[15 - dyeColour.getId()].getHex();
|
|
||||||
}
|
|
||||||
if (colourHex != newColour) {
|
|
||||||
colourHex = newColour;
|
|
||||||
BlockEntityHelpers.updateBlock(owner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setColour(int colour) {
|
public void setColour(int colour) {
|
||||||
if (colour >= 0 && colour <= 0xFFFFFF) {
|
if (colour >= 0 && colour <= 0xFFFFFF) {
|
||||||
@@ -516,7 +497,7 @@ public class TurtleBrain implements TurtleAccessInternal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUpgrade(TurtleSide side, @Nullable ITurtleUpgrade upgrade) {
|
public void setUpgradeWithData(TurtleSide side, @Nullable UpgradeData<ITurtleUpgrade> upgrade) {
|
||||||
if (!setUpgradeDirect(side, upgrade) || owner.getLevel() == null) return;
|
if (!setUpgradeDirect(side, upgrade) || owner.getLevel() == null) return;
|
||||||
|
|
||||||
// This is a separate function to avoid updating the block when reading the NBT. We don't need to do this as
|
// This is a separate function to avoid updating the block when reading the NBT. We don't need to do this as
|
||||||
@@ -529,19 +510,18 @@ public class TurtleBrain implements TurtleAccessInternal {
|
|||||||
owner.updateInputsImmediately();
|
owner.updateInputsImmediately();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean setUpgradeDirect(TurtleSide side, @Nullable ITurtleUpgrade upgrade) {
|
private boolean setUpgradeDirect(TurtleSide side, @Nullable UpgradeData<ITurtleUpgrade> upgrade) {
|
||||||
// Remove old upgrade
|
// Remove old upgrade
|
||||||
if (upgrades.containsKey(side)) {
|
var oldUpgrade = upgrades.remove(side);
|
||||||
if (upgrades.get(side) == upgrade) return false;
|
if (oldUpgrade == null && upgrade == null) return false;
|
||||||
upgrades.remove(side);
|
|
||||||
} else {
|
|
||||||
if (upgrade == null) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
upgradeNBTData.remove(side);
|
|
||||||
|
|
||||||
// Set new upgrade
|
// Set new upgrade
|
||||||
if (upgrade != null) upgrades.put(side, upgrade);
|
if (upgrade == null) {
|
||||||
|
upgradeNBTData.remove(side);
|
||||||
|
} else {
|
||||||
|
upgrades.put(side, upgrade.upgrade());
|
||||||
|
upgradeNBTData.put(side, upgrade.data().copy());
|
||||||
|
}
|
||||||
|
|
||||||
// Notify clients and create peripherals
|
// Notify clients and create peripherals
|
||||||
if (owner.getLevel() != null && !owner.getLevel().isClientSide) {
|
if (owner.getLevel() != null && !owner.getLevel().isClientSide) {
|
||||||
@@ -595,7 +575,7 @@ public class TurtleBrain implements TurtleAccessInternal {
|
|||||||
|
|
||||||
public float getToolRenderAngle(TurtleSide side, float f) {
|
public float getToolRenderAngle(TurtleSide side, float f) {
|
||||||
return (side == TurtleSide.LEFT && animation == TurtleAnimation.SWING_LEFT_TOOL) ||
|
return (side == TurtleSide.LEFT && animation == TurtleAnimation.SWING_LEFT_TOOL) ||
|
||||||
(side == TurtleSide.RIGHT && animation == TurtleAnimation.SWING_RIGHT_TOOL)
|
(side == TurtleSide.RIGHT && animation == TurtleAnimation.SWING_RIGHT_TOOL)
|
||||||
? 45.0f * (float) Math.sin(getAnimationFraction(f) * Math.PI)
|
? 45.0f * (float) Math.sin(getAnimationFraction(f) * Math.PI)
|
||||||
: 0.0f;
|
: 0.0f;
|
||||||
}
|
}
|
||||||
|
@@ -5,6 +5,7 @@
|
|||||||
package dan200.computercraft.shared.turtle.core;
|
package dan200.computercraft.shared.turtle.core;
|
||||||
|
|
||||||
import dan200.computercraft.api.turtle.*;
|
import dan200.computercraft.api.turtle.*;
|
||||||
|
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||||
import dan200.computercraft.impl.TurtleUpgrades;
|
import dan200.computercraft.impl.TurtleUpgrades;
|
||||||
import dan200.computercraft.shared.turtle.TurtleUtil;
|
import dan200.computercraft.shared.turtle.TurtleUtil;
|
||||||
|
|
||||||
@@ -18,10 +19,10 @@ public class TurtleEquipCommand implements TurtleCommand {
|
|||||||
@Override
|
@Override
|
||||||
public TurtleCommandResult execute(ITurtleAccess turtle) {
|
public TurtleCommandResult execute(ITurtleAccess turtle) {
|
||||||
// Determine the upgrade to replace
|
// Determine the upgrade to replace
|
||||||
var oldUpgrade = turtle.getUpgrade(side);
|
var oldUpgrade = turtle.getUpgradeWithData(side);
|
||||||
|
|
||||||
// Determine the upgrade to equipLeft
|
// Determine the upgrade to equipLeft
|
||||||
ITurtleUpgrade newUpgrade;
|
UpgradeData<ITurtleUpgrade> newUpgrade;
|
||||||
var selectedStack = turtle.getInventory().getItem(turtle.getSelectedSlot());
|
var selectedStack = turtle.getInventory().getItem(turtle.getSelectedSlot());
|
||||||
if (!selectedStack.isEmpty()) {
|
if (!selectedStack.isEmpty()) {
|
||||||
newUpgrade = TurtleUpgrades.instance().get(selectedStack);
|
newUpgrade = TurtleUpgrades.instance().get(selectedStack);
|
||||||
@@ -32,8 +33,8 @@ public class TurtleEquipCommand implements TurtleCommand {
|
|||||||
|
|
||||||
// Do the swapping:
|
// Do the swapping:
|
||||||
if (newUpgrade != null) turtle.getInventory().removeItem(turtle.getSelectedSlot(), 1);
|
if (newUpgrade != null) turtle.getInventory().removeItem(turtle.getSelectedSlot(), 1);
|
||||||
if (oldUpgrade != null) TurtleUtil.storeItemOrDrop(turtle, oldUpgrade.getCraftingItem().copy());
|
if (oldUpgrade != null) TurtleUtil.storeItemOrDrop(turtle, oldUpgrade.getUpgradeItem());
|
||||||
turtle.setUpgrade(side, newUpgrade);
|
turtle.setUpgradeWithData(side, newUpgrade);
|
||||||
|
|
||||||
// Animate
|
// Animate
|
||||||
if (newUpgrade != null || oldUpgrade != null) {
|
if (newUpgrade != null || oldUpgrade != null) {
|
||||||
|
@@ -74,18 +74,6 @@ public class TurtlePlaceCommand implements TurtleCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean deployCopiedItem(
|
|
||||||
ItemStack stack, ITurtleAccess turtle, Direction direction, @Nullable Object[] extraArguments, @Nullable ErrorMessage outErrorMessage
|
|
||||||
) {
|
|
||||||
// Create a fake player, and orient it appropriately
|
|
||||||
var playerPosition = turtle.getPosition().relative(direction);
|
|
||||||
var turtlePlayer = TurtlePlayer.getWithPosition(turtle, playerPosition, direction);
|
|
||||||
turtlePlayer.loadInventory(stack);
|
|
||||||
var result = deploy(stack, turtle, turtlePlayer, direction, extraArguments, outErrorMessage);
|
|
||||||
turtlePlayer.player().getInventory().clearContent();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean deploy(
|
private static boolean deploy(
|
||||||
ItemStack stack, ITurtleAccess turtle, TurtlePlayer turtlePlayer, Direction direction,
|
ItemStack stack, ITurtleAccess turtle, TurtlePlayer turtlePlayer, Direction direction,
|
||||||
@Nullable Object[] extraArguments, @Nullable ErrorMessage outErrorMessage
|
@Nullable Object[] extraArguments, @Nullable ErrorMessage outErrorMessage
|
||||||
@@ -152,6 +140,22 @@ public class TurtlePlaceCommand implements TurtleCommand {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate where a turtle would interact with a block.
|
||||||
|
*
|
||||||
|
* @param position The position of the block.
|
||||||
|
* @param side The side the turtle is clicking on.
|
||||||
|
* @return The hit result.
|
||||||
|
*/
|
||||||
|
public static BlockHitResult getHitResult(BlockPos position, Direction side) {
|
||||||
|
var hitX = 0.5 + side.getStepX() * 0.5;
|
||||||
|
var hitY = 0.5 + side.getStepY() * 0.5;
|
||||||
|
var hitZ = 0.5 + side.getStepZ() * 0.5;
|
||||||
|
if (Math.abs(hitY - 0.5) < 0.01) hitY = 0.45;
|
||||||
|
|
||||||
|
return new BlockHitResult(new Vec3(position.getX() + hitX, position.getY() + hitY, position.getZ() + hitZ), side, position, false);
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean deployOnBlock(
|
private static boolean deployOnBlock(
|
||||||
ItemStack stack, ITurtleAccess turtle, TurtlePlayer turtlePlayer, BlockPos position, Direction side,
|
ItemStack stack, ITurtleAccess turtle, TurtlePlayer turtlePlayer, BlockPos position, Direction side,
|
||||||
@Nullable Object[] extraArguments, boolean adjacent, @Nullable ErrorMessage outErrorMessage
|
@Nullable Object[] extraArguments, boolean adjacent, @Nullable ErrorMessage outErrorMessage
|
||||||
@@ -161,14 +165,8 @@ public class TurtlePlaceCommand implements TurtleCommand {
|
|||||||
var playerPosition = position.relative(side);
|
var playerPosition = position.relative(side);
|
||||||
turtlePlayer.setPosition(turtle, playerPosition, playerDir);
|
turtlePlayer.setPosition(turtle, playerPosition, playerDir);
|
||||||
|
|
||||||
// Calculate where the turtle would hit the block
|
|
||||||
var hitX = 0.5f + side.getStepX() * 0.5f;
|
|
||||||
var hitY = 0.5f + side.getStepY() * 0.5f;
|
|
||||||
var hitZ = 0.5f + side.getStepZ() * 0.5f;
|
|
||||||
if (Math.abs(hitY - 0.5f) < 0.01f) hitY = 0.45f;
|
|
||||||
|
|
||||||
// Check if there's something suitable to place onto
|
// Check if there's something suitable to place onto
|
||||||
var hit = new BlockHitResult(new Vec3(position.getX() + hitX, position.getY() + hitY, position.getZ() + hitZ), side, position, false);
|
var hit = getHitResult(position, side);
|
||||||
var context = new UseOnContext(turtlePlayer.player(), InteractionHand.MAIN_HAND, hit);
|
var context = new UseOnContext(turtlePlayer.player(), InteractionHand.MAIN_HAND, hit);
|
||||||
if (!canDeployOnBlock(new BlockPlaceContext(context), turtle, turtlePlayer, position, side, adjacent, outErrorMessage)) {
|
if (!canDeployOnBlock(new BlockPlaceContext(context), turtle, turtlePlayer, position, side, adjacent, outErrorMessage)) {
|
||||||
return false;
|
return false;
|
||||||
|
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
package dan200.computercraft.shared.turtle.inventory;
|
package dan200.computercraft.shared.turtle.inventory;
|
||||||
|
|
||||||
|
import dan200.computercraft.api.turtle.TurtleSide;
|
||||||
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.ServerComputer;
|
||||||
@@ -29,12 +30,13 @@ public final class TurtleMenu extends AbstractComputerMenu {
|
|||||||
public static final int PLAYER_START_Y = 134;
|
public static final int PLAYER_START_Y = 134;
|
||||||
public static final int TURTLE_START_X = SIDEBAR_WIDTH + 175;
|
public static final int TURTLE_START_X = SIDEBAR_WIDTH + 175;
|
||||||
public static final int PLAYER_START_X = SIDEBAR_WIDTH + BORDER;
|
public static final int PLAYER_START_X = SIDEBAR_WIDTH + BORDER;
|
||||||
|
public static final int UPGRADE_START_X = SIDEBAR_WIDTH + 254;
|
||||||
|
|
||||||
private final ContainerData data;
|
private final ContainerData data;
|
||||||
|
|
||||||
private TurtleMenu(
|
private TurtleMenu(
|
||||||
int id, Predicate<Player> canUse, ComputerFamily family, @Nullable ServerComputer computer, @Nullable ComputerContainerData menuData,
|
int id, Predicate<Player> canUse, ComputerFamily family, @Nullable ServerComputer computer, @Nullable ComputerContainerData menuData,
|
||||||
Inventory playerInventory, Container inventory, ContainerData data
|
Inventory playerInventory, Container inventory, Container turtleUpgrades, ContainerData data
|
||||||
) {
|
) {
|
||||||
super(ModRegistry.Menus.TURTLE.get(), id, canUse, family, computer, menuData);
|
super(ModRegistry.Menus.TURTLE.get(), id, canUse, family, computer, menuData);
|
||||||
this.data = data;
|
this.data = data;
|
||||||
@@ -58,19 +60,24 @@ public final class TurtleMenu extends AbstractComputerMenu {
|
|||||||
for (var x = 0; x < 9; x++) {
|
for (var x = 0; x < 9; x++) {
|
||||||
addSlot(new Slot(playerInventory, x, PLAYER_START_X + x * 18, PLAYER_START_Y + 3 * 18 + 5));
|
addSlot(new Slot(playerInventory, x, PLAYER_START_X + x * 18, PLAYER_START_Y + 3 * 18 + 5));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Turtle upgrades
|
||||||
|
addSlot(new UpgradeSlot(turtleUpgrades, TurtleSide.LEFT, 0, UPGRADE_START_X, PLAYER_START_Y + 1));
|
||||||
|
addSlot(new UpgradeSlot(turtleUpgrades, TurtleSide.RIGHT, 1, UPGRADE_START_X, PLAYER_START_Y + 1 + 18));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TurtleMenu ofBrain(int id, Inventory player, TurtleBrain turtle) {
|
public static TurtleMenu ofBrain(int id, Inventory player, TurtleBrain turtle) {
|
||||||
return new TurtleMenu(
|
return new TurtleMenu(
|
||||||
// Laziness in turtle.getOwner() is important here!
|
// Laziness in turtle.getOwner() is important here!
|
||||||
id, p -> turtle.getOwner().stillValid(p), turtle.getFamily(), turtle.getOwner().createServerComputer(), null,
|
id, p -> turtle.getOwner().stillValid(p), turtle.getFamily(), turtle.getOwner().createServerComputer(), null,
|
||||||
player, turtle.getInventory(), (SingleContainerData) turtle::getSelectedSlot
|
player, turtle.getInventory(), new UpgradeContainer(turtle), (SingleContainerData) turtle::getSelectedSlot
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TurtleMenu ofMenuData(int id, Inventory player, ComputerContainerData data) {
|
public static TurtleMenu ofMenuData(int id, Inventory player, ComputerContainerData data) {
|
||||||
return new TurtleMenu(
|
return new TurtleMenu(
|
||||||
id, x -> true, data.family(), null, data, player, new SimpleContainer(TurtleBlockEntity.INVENTORY_SIZE), new SimpleContainerData(1)
|
id, x -> true, data.family(), null, data,
|
||||||
|
player, new SimpleContainer(TurtleBlockEntity.INVENTORY_SIZE), new SimpleContainer(2), new SimpleContainerData(1)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1,117 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.shared.turtle.inventory;
|
||||||
|
|
||||||
|
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||||
|
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||||
|
import dan200.computercraft.api.turtle.TurtleSide;
|
||||||
|
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||||
|
import dan200.computercraft.impl.TurtleUpgrades;
|
||||||
|
import net.minecraft.core.NonNullList;
|
||||||
|
import net.minecraft.world.Container;
|
||||||
|
import net.minecraft.world.entity.player.Player;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A fake {@link Container} which exposes the {@linkplain ITurtleAccess#getUpgrade(TurtleSide) upgrades} a turtle has.
|
||||||
|
*
|
||||||
|
* @see TurtleMenu
|
||||||
|
* @see UpgradeSlot
|
||||||
|
*/
|
||||||
|
class UpgradeContainer implements Container {
|
||||||
|
private static final int SIZE = 2;
|
||||||
|
|
||||||
|
private final ITurtleAccess turtle;
|
||||||
|
|
||||||
|
private final List<UpgradeData<ITurtleUpgrade>> lastUpgrade = Arrays.asList(null, null);
|
||||||
|
private final NonNullList<ItemStack> lastStack = NonNullList.withSize(2, ItemStack.EMPTY);
|
||||||
|
|
||||||
|
UpgradeContainer(ITurtleAccess turtle) {
|
||||||
|
this.turtle = turtle;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TurtleSide getSide(int slot) {
|
||||||
|
return switch (slot) {
|
||||||
|
case 0 -> TurtleSide.LEFT;
|
||||||
|
case 1 -> TurtleSide.RIGHT;
|
||||||
|
default -> throw new IllegalArgumentException("Invalid slot " + slot);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ItemStack getItem(int slot) {
|
||||||
|
var side = getSide(slot);
|
||||||
|
var upgrade = turtle.getUpgradeWithData(side);
|
||||||
|
if (upgrade == null) return ItemStack.EMPTY;
|
||||||
|
|
||||||
|
// We don't want to return getUpgradeItem directly here, as we'd end up recreating the stack each tick. To
|
||||||
|
// avoid that, we maintain a simple cache.
|
||||||
|
if (upgrade.equals(lastUpgrade.get(slot))) return lastStack.get(slot);
|
||||||
|
|
||||||
|
return setUpgradeStack(slot, upgrade);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ItemStack setUpgradeStack(int slot, @Nullable UpgradeData<ITurtleUpgrade> upgrade) {
|
||||||
|
var stack = upgrade == null ? ItemStack.EMPTY : upgrade.getUpgradeItem();
|
||||||
|
lastUpgrade.set(slot, UpgradeData.copyOf(upgrade));
|
||||||
|
lastStack.set(slot, stack);
|
||||||
|
return stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setItem(int slot, ItemStack itemStack) {
|
||||||
|
var upgrade = TurtleUpgrades.instance().get(itemStack);
|
||||||
|
turtle.setUpgradeWithData(getSide(slot), upgrade);
|
||||||
|
setUpgradeStack(slot, upgrade);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getContainerSize() {
|
||||||
|
return SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxStackSize() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
for (var i = 0; i < SIZE; i++) {
|
||||||
|
if (!getItem(i).isEmpty()) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ItemStack removeItem(int slot, int count) {
|
||||||
|
return count <= 0 ? ItemStack.EMPTY : removeItemNoUpdate(slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ItemStack removeItemNoUpdate(int slot) {
|
||||||
|
var current = getItem(slot);
|
||||||
|
setItem(slot, ItemStack.EMPTY);
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setChanged() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean stillValid(Player player) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearContent() {
|
||||||
|
for (var i = 0; i < SIZE; i++) setItem(i, ItemStack.EMPTY);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,50 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.shared.turtle.inventory;
|
||||||
|
|
||||||
|
import com.mojang.datafixers.util.Pair;
|
||||||
|
import dan200.computercraft.api.ComputerCraftAPI;
|
||||||
|
import dan200.computercraft.api.turtle.TurtleSide;
|
||||||
|
import dan200.computercraft.impl.TurtleUpgrades;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.world.Container;
|
||||||
|
import net.minecraft.world.inventory.InventoryMenu;
|
||||||
|
import net.minecraft.world.inventory.Slot;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A slot in the turtle UI which holds the turtle's current upgrade.
|
||||||
|
*
|
||||||
|
* @see TurtleMenu
|
||||||
|
*/
|
||||||
|
public class UpgradeSlot extends Slot {
|
||||||
|
public static final ResourceLocation LEFT_UPGRADE = new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/turtle_upgrade_left");
|
||||||
|
public static final ResourceLocation RIGHT_UPGRADE = new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/turtle_upgrade_right");
|
||||||
|
|
||||||
|
private final TurtleSide side;
|
||||||
|
|
||||||
|
public UpgradeSlot(Container container, TurtleSide side, int slot, int xPos, int yPos) {
|
||||||
|
super(container, slot, xPos, yPos);
|
||||||
|
this.side = side;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean mayPlace(ItemStack stack) {
|
||||||
|
return TurtleUpgrades.instance().get(stack) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxStackSize() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Pair<ResourceLocation, ResourceLocation> getNoItemIcon() {
|
||||||
|
return Pair.of(InventoryMenu.BLOCK_ATLAS, side == TurtleSide.LEFT ? LEFT_UPGRADE : RIGHT_UPGRADE);
|
||||||
|
}
|
||||||
|
}
|
@@ -8,12 +8,14 @@ import dan200.computercraft.annotations.ForgeOverride;
|
|||||||
import dan200.computercraft.api.ComputerCraftAPI;
|
import dan200.computercraft.api.ComputerCraftAPI;
|
||||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||||
import dan200.computercraft.api.turtle.TurtleSide;
|
import dan200.computercraft.api.turtle.TurtleSide;
|
||||||
|
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||||
import dan200.computercraft.impl.TurtleUpgrades;
|
import dan200.computercraft.impl.TurtleUpgrades;
|
||||||
import dan200.computercraft.shared.ModRegistry;
|
import dan200.computercraft.shared.ModRegistry;
|
||||||
import dan200.computercraft.shared.common.IColouredItem;
|
import dan200.computercraft.shared.common.IColouredItem;
|
||||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||||
import dan200.computercraft.shared.computer.items.AbstractComputerItem;
|
import dan200.computercraft.shared.computer.items.AbstractComputerItem;
|
||||||
import dan200.computercraft.shared.turtle.blocks.TurtleBlock;
|
import dan200.computercraft.shared.turtle.blocks.TurtleBlock;
|
||||||
|
import dan200.computercraft.shared.util.NBTUtil;
|
||||||
import net.minecraft.core.cauldron.CauldronInteraction;
|
import net.minecraft.core.cauldron.CauldronInteraction;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
@@ -32,7 +34,7 @@ public class TurtleItem extends AbstractComputerItem implements IColouredItem {
|
|||||||
|
|
||||||
public static ItemStack create(
|
public static ItemStack create(
|
||||||
int id, @Nullable String label, int colour, ComputerFamily family,
|
int id, @Nullable String label, int colour, ComputerFamily family,
|
||||||
@Nullable ITurtleUpgrade leftUpgrade, @Nullable ITurtleUpgrade rightUpgrade,
|
@Nullable UpgradeData<ITurtleUpgrade> leftUpgrade, @Nullable UpgradeData<ITurtleUpgrade> rightUpgrade,
|
||||||
int fuelLevel, @Nullable ResourceLocation overlay
|
int fuelLevel, @Nullable ResourceLocation overlay
|
||||||
) {
|
) {
|
||||||
return switch (family) {
|
return switch (family) {
|
||||||
@@ -46,7 +48,7 @@ public class TurtleItem extends AbstractComputerItem implements IColouredItem {
|
|||||||
|
|
||||||
public ItemStack create(
|
public ItemStack create(
|
||||||
int id, @Nullable String label, int colour,
|
int id, @Nullable String label, int colour,
|
||||||
@Nullable ITurtleUpgrade leftUpgrade, @Nullable ITurtleUpgrade rightUpgrade,
|
@Nullable UpgradeData<ITurtleUpgrade> leftUpgrade, @Nullable UpgradeData<ITurtleUpgrade> rightUpgrade,
|
||||||
int fuelLevel, @Nullable ResourceLocation overlay
|
int fuelLevel, @Nullable ResourceLocation overlay
|
||||||
) {
|
) {
|
||||||
// Build the stack
|
// Build the stack
|
||||||
@@ -58,11 +60,15 @@ public class TurtleItem extends AbstractComputerItem implements IColouredItem {
|
|||||||
if (overlay != null) stack.getOrCreateTag().putString(NBT_OVERLAY, overlay.toString());
|
if (overlay != null) stack.getOrCreateTag().putString(NBT_OVERLAY, overlay.toString());
|
||||||
|
|
||||||
if (leftUpgrade != null) {
|
if (leftUpgrade != null) {
|
||||||
stack.getOrCreateTag().putString(NBT_LEFT_UPGRADE, leftUpgrade.getUpgradeID().toString());
|
var tag = stack.getOrCreateTag();
|
||||||
|
tag.putString(NBT_LEFT_UPGRADE, leftUpgrade.upgrade().getUpgradeID().toString());
|
||||||
|
if (!leftUpgrade.data().isEmpty()) tag.put(NBT_LEFT_UPGRADE_DATA, leftUpgrade.data().copy());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rightUpgrade != null) {
|
if (rightUpgrade != null) {
|
||||||
stack.getOrCreateTag().putString(NBT_RIGHT_UPGRADE, rightUpgrade.getUpgradeID().toString());
|
var tag = stack.getOrCreateTag();
|
||||||
|
tag.putString(NBT_RIGHT_UPGRADE, rightUpgrade.upgrade().getUpgradeID().toString());
|
||||||
|
if (!rightUpgrade.data().isEmpty()) tag.put(NBT_RIGHT_UPGRADE_DATA, rightUpgrade.data().copy());
|
||||||
}
|
}
|
||||||
|
|
||||||
return stack;
|
return stack;
|
||||||
@@ -117,7 +123,7 @@ public class TurtleItem extends AbstractComputerItem implements IColouredItem {
|
|||||||
return create(
|
return create(
|
||||||
getComputerID(stack), getLabel(stack),
|
getComputerID(stack), getLabel(stack),
|
||||||
getColour(stack), family,
|
getColour(stack), family,
|
||||||
getUpgrade(stack, TurtleSide.LEFT), getUpgrade(stack, TurtleSide.RIGHT),
|
getUpgradeWithData(stack, TurtleSide.LEFT), getUpgradeWithData(stack, TurtleSide.RIGHT),
|
||||||
getFuelLevel(stack), getOverlay(stack)
|
getFuelLevel(stack), getOverlay(stack)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -127,7 +133,20 @@ public class TurtleItem extends AbstractComputerItem implements IColouredItem {
|
|||||||
if (tag == null) return null;
|
if (tag == null) return null;
|
||||||
|
|
||||||
var key = side == TurtleSide.LEFT ? NBT_LEFT_UPGRADE : NBT_RIGHT_UPGRADE;
|
var key = side == TurtleSide.LEFT ? NBT_LEFT_UPGRADE : NBT_RIGHT_UPGRADE;
|
||||||
return tag.contains(key) ? TurtleUpgrades.instance().get(tag.getString(key)) : null;
|
if (!tag.contains(key)) return null;
|
||||||
|
return TurtleUpgrades.instance().get(tag.getString(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable UpgradeData<ITurtleUpgrade> getUpgradeWithData(ItemStack stack, TurtleSide side) {
|
||||||
|
var tag = stack.getTag();
|
||||||
|
if (tag == null) return null;
|
||||||
|
|
||||||
|
var key = side == TurtleSide.LEFT ? NBT_LEFT_UPGRADE : NBT_RIGHT_UPGRADE;
|
||||||
|
if (!tag.contains(key)) return null;
|
||||||
|
var upgrade = TurtleUpgrades.instance().get(tag.getString(key));
|
||||||
|
if (upgrade == null) return null;
|
||||||
|
var dataKey = side == TurtleSide.LEFT ? NBT_LEFT_UPGRADE_DATA : NBT_RIGHT_UPGRADE_DATA;
|
||||||
|
return UpgradeData.of(upgrade, NBTUtil.getCompoundOrEmpty(tag, dataKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable ResourceLocation getOverlay(ItemStack stack) {
|
public @Nullable ResourceLocation getOverlay(ItemStack stack) {
|
||||||
|
@@ -38,8 +38,8 @@ public class TurtleOverlayRecipe extends ShapelessRecipe {
|
|||||||
turtle.getComputerID(stack),
|
turtle.getComputerID(stack),
|
||||||
turtle.getLabel(stack),
|
turtle.getLabel(stack),
|
||||||
turtle.getColour(stack),
|
turtle.getColour(stack),
|
||||||
turtle.getUpgrade(stack, TurtleSide.LEFT),
|
turtle.getUpgradeWithData(stack, TurtleSide.LEFT),
|
||||||
turtle.getUpgrade(stack, TurtleSide.RIGHT),
|
turtle.getUpgradeWithData(stack, TurtleSide.RIGHT),
|
||||||
turtle.getFuelLevel(stack),
|
turtle.getFuelLevel(stack),
|
||||||
overlay
|
overlay
|
||||||
);
|
);
|
||||||
|
@@ -6,6 +6,7 @@ package dan200.computercraft.shared.turtle.recipes;
|
|||||||
|
|
||||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||||
import dan200.computercraft.api.turtle.TurtleSide;
|
import dan200.computercraft.api.turtle.TurtleSide;
|
||||||
|
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||||
import dan200.computercraft.impl.TurtleUpgrades;
|
import dan200.computercraft.impl.TurtleUpgrades;
|
||||||
import dan200.computercraft.shared.ModRegistry;
|
import dan200.computercraft.shared.ModRegistry;
|
||||||
import dan200.computercraft.shared.turtle.items.TurtleItem;
|
import dan200.computercraft.shared.turtle.items.TurtleItem;
|
||||||
@@ -104,9 +105,10 @@ public final class TurtleUpgradeRecipe extends CustomRecipe {
|
|||||||
// At this point we have a turtle + 1 or 2 items
|
// At this point we have a turtle + 1 or 2 items
|
||||||
// Get the turtle we already have
|
// Get the turtle we already have
|
||||||
var itemTurtle = (TurtleItem) turtle.getItem();
|
var itemTurtle = (TurtleItem) turtle.getItem();
|
||||||
var upgrades = new ITurtleUpgrade[]{
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||||
itemTurtle.getUpgrade(turtle, TurtleSide.LEFT),
|
UpgradeData<ITurtleUpgrade>[] upgrades = new UpgradeData[]{
|
||||||
itemTurtle.getUpgrade(turtle, TurtleSide.RIGHT),
|
itemTurtle.getUpgradeWithData(turtle, TurtleSide.LEFT),
|
||||||
|
itemTurtle.getUpgradeWithData(turtle, TurtleSide.RIGHT),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get the upgrades for the new items
|
// Get the upgrades for the new items
|
||||||
|
@@ -9,6 +9,7 @@ import dan200.computercraft.api.turtle.*;
|
|||||||
import dan200.computercraft.shared.peripheral.modem.ModemState;
|
import dan200.computercraft.shared.peripheral.modem.ModemState;
|
||||||
import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemPeripheral;
|
import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemPeripheral;
|
||||||
import net.minecraft.core.Direction;
|
import net.minecraft.core.Direction;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
import net.minecraft.world.level.Level;
|
import net.minecraft.world.level.Level;
|
||||||
@@ -77,4 +78,9 @@ public class TurtleModem extends AbstractTurtleUpgrade {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompoundTag getPersistedData(CompoundTag upgradeData) {
|
||||||
|
return new CompoundTag();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -14,16 +14,22 @@ import dan200.computercraft.shared.util.DropConsumer;
|
|||||||
import dan200.computercraft.shared.util.WorldUtil;
|
import dan200.computercraft.shared.util.WorldUtil;
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.core.Direction;
|
import net.minecraft.core.Direction;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.server.level.ServerLevel;
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
import net.minecraft.tags.TagKey;
|
import net.minecraft.tags.TagKey;
|
||||||
|
import net.minecraft.world.InteractionHand;
|
||||||
import net.minecraft.world.InteractionResult;
|
import net.minecraft.world.InteractionResult;
|
||||||
import net.minecraft.world.entity.Entity;
|
import net.minecraft.world.entity.Entity;
|
||||||
|
import net.minecraft.world.entity.LivingEntity;
|
||||||
|
import net.minecraft.world.entity.MobType;
|
||||||
import net.minecraft.world.entity.ai.attributes.Attributes;
|
import net.minecraft.world.entity.ai.attributes.Attributes;
|
||||||
import net.minecraft.world.entity.decoration.ArmorStand;
|
import net.minecraft.world.entity.decoration.ArmorStand;
|
||||||
import net.minecraft.world.entity.player.Player;
|
import net.minecraft.world.entity.player.Player;
|
||||||
import net.minecraft.world.item.Item;
|
import net.minecraft.world.item.Item;
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraft.world.item.enchantment.EnchantmentHelper;
|
||||||
import net.minecraft.world.level.BlockGetter;
|
import net.minecraft.world.level.BlockGetter;
|
||||||
import net.minecraft.world.level.Level;
|
import net.minecraft.world.level.Level;
|
||||||
import net.minecraft.world.level.block.Block;
|
import net.minecraft.world.level.block.Block;
|
||||||
@@ -32,46 +38,127 @@ import net.minecraft.world.level.block.state.BlockState;
|
|||||||
import net.minecraft.world.phys.EntityHitResult;
|
import net.minecraft.world.phys.EntityHitResult;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
import static net.minecraft.nbt.Tag.TAG_COMPOUND;
|
import static net.minecraft.nbt.Tag.TAG_COMPOUND;
|
||||||
import static net.minecraft.nbt.Tag.TAG_LIST;
|
import static net.minecraft.nbt.Tag.TAG_LIST;
|
||||||
|
|
||||||
public class TurtleTool extends AbstractTurtleUpgrade {
|
public class TurtleTool extends AbstractTurtleUpgrade {
|
||||||
protected static final TurtleCommandResult UNBREAKABLE = TurtleCommandResult.failure("Cannot break unbreakable block");
|
private static final TurtleCommandResult UNBREAKABLE = TurtleCommandResult.failure("Cannot break unbreakable block");
|
||||||
protected static final TurtleCommandResult INEFFECTIVE = TurtleCommandResult.failure("Cannot break block with this tool");
|
private static final TurtleCommandResult INEFFECTIVE = TurtleCommandResult.failure("Cannot break block with this tool");
|
||||||
|
|
||||||
|
private static final String TAG_ITEM_TAG = "Tag";
|
||||||
|
|
||||||
final ItemStack item;
|
final ItemStack item;
|
||||||
final float damageMulitiplier;
|
final float damageMulitiplier;
|
||||||
@Nullable
|
final boolean allowEnchantments;
|
||||||
final TagKey<Block> breakable;
|
final TurtleToolDurability consumeDurability;
|
||||||
|
final @Nullable TagKey<Block> breakable;
|
||||||
|
|
||||||
public TurtleTool(ResourceLocation id, String adjective, Item craftItem, ItemStack toolItem, float damageMulitiplier, @Nullable TagKey<Block> breakable) {
|
public TurtleTool(
|
||||||
|
ResourceLocation id, String adjective, Item craftItem, ItemStack toolItem, float damageMulitiplier,
|
||||||
|
boolean allowEnchantments, TurtleToolDurability consumeDurability, @Nullable TagKey<Block> breakable
|
||||||
|
) {
|
||||||
super(id, TurtleUpgradeType.TOOL, adjective, new ItemStack(craftItem));
|
super(id, TurtleUpgradeType.TOOL, adjective, new ItemStack(craftItem));
|
||||||
item = toolItem;
|
item = toolItem;
|
||||||
this.damageMulitiplier = damageMulitiplier;
|
this.damageMulitiplier = damageMulitiplier;
|
||||||
|
this.allowEnchantments = allowEnchantments;
|
||||||
|
this.consumeDurability = consumeDurability;
|
||||||
this.breakable = breakable;
|
this.breakable = breakable;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isItemSuitable(ItemStack stack) {
|
public boolean isItemSuitable(ItemStack stack) {
|
||||||
var tag = stack.getTag();
|
if (consumeDurability == TurtleToolDurability.NEVER && stack.isDamaged()) return false;
|
||||||
if (tag == null || tag.isEmpty()) return true;
|
if (!allowEnchantments && isEnchanted(stack)) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Check we've not got anything vaguely interesting on the item. We allow other mods to add their
|
private static boolean isEnchanted(ItemStack stack) {
|
||||||
// own NBT, with the understanding such details will be lost to the mist of time.
|
return !stack.isEmpty() && isEnchanted(stack.getTag());
|
||||||
if (stack.isDamaged() || stack.isEnchanted() || stack.hasCustomHoverName()) return false;
|
}
|
||||||
if (tag.contains("AttributeModifiers", TAG_LIST) && !tag.getList("AttributeModifiers", TAG_COMPOUND).isEmpty()) {
|
|
||||||
return false;
|
private static boolean isEnchanted(@Nullable CompoundTag tag) {
|
||||||
|
if (tag == null || tag.isEmpty()) return false;
|
||||||
|
return (tag.contains(ItemStack.TAG_ENCH, TAG_LIST) && !tag.getList(ItemStack.TAG_ENCH, TAG_COMPOUND).isEmpty())
|
||||||
|
|| (tag.contains("AttributeModifiers", TAG_LIST) && !tag.getList("AttributeModifiers", TAG_COMPOUND).isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompoundTag getUpgradeData(ItemStack stack) {
|
||||||
|
var upgradeData = super.getUpgradeData(stack);
|
||||||
|
|
||||||
|
// Store the item's current tag.
|
||||||
|
var itemTag = stack.getTag();
|
||||||
|
if (itemTag != null) upgradeData.put(TAG_ITEM_TAG, itemTag);
|
||||||
|
|
||||||
|
return upgradeData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ItemStack getUpgradeItem(CompoundTag upgradeData) {
|
||||||
|
// Copy upgrade data back to the item.
|
||||||
|
var item = super.getUpgradeItem(upgradeData).copy();
|
||||||
|
item.setTag(upgradeData.contains(TAG_ITEM_TAG, TAG_COMPOUND) ? upgradeData.getCompound(TAG_ITEM_TAG) : null);
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ItemStack getToolStack(ITurtleAccess turtle, TurtleSide side) {
|
||||||
|
return getUpgradeItem(turtle.getUpgradeNBTData(side)).copy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setToolStack(ITurtleAccess turtle, TurtleSide side, ItemStack stack) {
|
||||||
|
var upgradeData = turtle.getUpgradeNBTData(side);
|
||||||
|
|
||||||
|
var useDurability = switch (consumeDurability) {
|
||||||
|
case NEVER -> false;
|
||||||
|
case WHEN_ENCHANTED ->
|
||||||
|
upgradeData.contains(TAG_ITEM_TAG, TAG_COMPOUND) && isEnchanted(upgradeData.getCompound(TAG_ITEM_TAG));
|
||||||
|
case ALWAYS -> true;
|
||||||
|
};
|
||||||
|
if (!useDurability) return;
|
||||||
|
|
||||||
|
// If the tool has broken, remove the upgrade!
|
||||||
|
if (stack.isEmpty()) {
|
||||||
|
turtle.setUpgradeWithData(side, null);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
// If the tool has changed, no clue what's going on.
|
||||||
|
if (stack.getItem() != item.getItem()) return;
|
||||||
|
|
||||||
|
var itemTag = stack.getTag();
|
||||||
|
|
||||||
|
// Early return if the item hasn't changed to avoid redundant syncs with the client.
|
||||||
|
if (Objects.equals(itemTag, upgradeData.get(TAG_ITEM_TAG))) return;
|
||||||
|
|
||||||
|
if (itemTag == null) {
|
||||||
|
upgradeData.remove(TAG_ITEM_TAG);
|
||||||
|
} else {
|
||||||
|
upgradeData.put(TAG_ITEM_TAG, itemTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
turtle.updateUpgradeNBTData(side);
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> T withEquippedItem(ITurtleAccess turtle, TurtleSide side, Direction direction, Function<TurtlePlayer, T> action) {
|
||||||
|
var turtlePlayer = TurtlePlayer.getWithPosition(turtle, turtle.getPosition(), direction);
|
||||||
|
turtlePlayer.loadInventory(getToolStack(turtle, side));
|
||||||
|
|
||||||
|
var result = action.apply(turtlePlayer);
|
||||||
|
|
||||||
|
setToolStack(turtle, side, turtlePlayer.player().getItemInHand(InteractionHand.MAIN_HAND));
|
||||||
|
turtlePlayer.player().getInventory().clearContent();
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TurtleCommandResult useTool(ITurtleAccess turtle, TurtleSide side, TurtleVerb verb, Direction direction) {
|
public TurtleCommandResult useTool(ITurtleAccess turtle, TurtleSide side, TurtleVerb verb, Direction direction) {
|
||||||
return switch (verb) {
|
return switch (verb) {
|
||||||
case ATTACK -> attack(turtle, direction);
|
case ATTACK -> attack(turtle, side, direction);
|
||||||
case DIG -> dig(turtle, direction);
|
case DIG -> dig(turtle, side, direction);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,16 +173,14 @@ public class TurtleTool extends AbstractTurtleUpgrade {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attack an entity. This is a <em>very</em> cut down version of {@link Player#attack(Entity)}, which doesn't handle
|
* Attack an entity.
|
||||||
* enchantments, knockback, etc... Unfortunately we can't call attack directly as damage calculations are rather
|
|
||||||
* different (and we don't want to play sounds/particles).
|
|
||||||
*
|
*
|
||||||
* @param turtle The current turtle.
|
* @param turtle The current turtle.
|
||||||
|
* @param side The side the tool is on.
|
||||||
* @param direction The direction we're attacking in.
|
* @param direction The direction we're attacking in.
|
||||||
* @return Whether an attack occurred.
|
* @return Whether an attack occurred.
|
||||||
* @see Player#attack(Entity)
|
|
||||||
*/
|
*/
|
||||||
private TurtleCommandResult attack(ITurtleAccess turtle, Direction direction) {
|
private TurtleCommandResult attack(ITurtleAccess turtle, TurtleSide side, Direction direction) {
|
||||||
// Create a fake player, and orient it appropriately
|
// Create a fake player, and orient it appropriately
|
||||||
var world = turtle.getLevel();
|
var world = turtle.getLevel();
|
||||||
var position = turtle.getPosition();
|
var position = turtle.getPosition();
|
||||||
@@ -107,10 +192,11 @@ public class TurtleTool extends AbstractTurtleUpgrade {
|
|||||||
var turtlePos = player.position();
|
var turtlePos = player.position();
|
||||||
var rayDir = player.getViewVector(1.0f);
|
var rayDir = player.getViewVector(1.0f);
|
||||||
var hit = WorldUtil.clip(world, turtlePos, rayDir, 1.5, null);
|
var hit = WorldUtil.clip(world, turtlePos, rayDir, 1.5, null);
|
||||||
|
var attacked = false;
|
||||||
if (hit instanceof EntityHitResult entityHit) {
|
if (hit instanceof EntityHitResult entityHit) {
|
||||||
// Load up the turtle's inventory
|
// Load up the turtle's inventory
|
||||||
var stackCopy = item.copy();
|
var stack = getToolStack(turtle, side);
|
||||||
turtlePlayer.loadInventory(stackCopy);
|
turtlePlayer.loadInventory(stack);
|
||||||
|
|
||||||
var hitEntity = entityHit.getEntity();
|
var hitEntity = entityHit.getEntity();
|
||||||
|
|
||||||
@@ -118,62 +204,147 @@ public class TurtleTool extends AbstractTurtleUpgrade {
|
|||||||
DropConsumer.set(hitEntity, TurtleUtil.dropConsumer(turtle));
|
DropConsumer.set(hitEntity, TurtleUtil.dropConsumer(turtle));
|
||||||
|
|
||||||
// Attack the entity
|
// Attack the entity
|
||||||
var attacked = false;
|
|
||||||
var result = PlatformHelper.get().canAttackEntity(player, hitEntity);
|
var result = PlatformHelper.get().canAttackEntity(player, hitEntity);
|
||||||
if (result.consumesAction()) {
|
if (result.consumesAction()) {
|
||||||
attacked = true;
|
attacked = true;
|
||||||
} else if (result == InteractionResult.PASS && hitEntity.isAttackable() && !hitEntity.skipAttackInteraction(player)) {
|
} else if (result == InteractionResult.PASS && hitEntity.isAttackable() && !hitEntity.skipAttackInteraction(player)) {
|
||||||
var damage = (float) player.getAttributeValue(Attributes.ATTACK_DAMAGE) * damageMulitiplier;
|
attacked = attack(player, direction, hitEntity);
|
||||||
if (damage > 0.0f) {
|
|
||||||
var source = player.damageSources().playerAttack(player);
|
|
||||||
if (hitEntity instanceof ArmorStand) {
|
|
||||||
// Special case for armor stands: attack twice to guarantee destroy
|
|
||||||
hitEntity.hurt(source, damage);
|
|
||||||
if (hitEntity.isAlive()) hitEntity.hurt(source, damage);
|
|
||||||
attacked = true;
|
|
||||||
} else {
|
|
||||||
if (hitEntity.hurt(source, damage)) attacked = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop claiming drops
|
// Stop claiming drops
|
||||||
TurtleUtil.stopConsuming(turtle);
|
TurtleUtil.stopConsuming(turtle);
|
||||||
|
|
||||||
// Put everything we collected into the turtles inventory, then return
|
// Put everything we collected into the turtles inventory.
|
||||||
|
setToolStack(turtle, side, player.getItemInHand(InteractionHand.MAIN_HAND));
|
||||||
player.getInventory().clearContent();
|
player.getInventory().clearContent();
|
||||||
if (attacked) return TurtleCommandResult.success();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return TurtleCommandResult.failure("Nothing to attack here");
|
return attacked ? TurtleCommandResult.success() : TurtleCommandResult.failure("Nothing to attack here");
|
||||||
}
|
}
|
||||||
|
|
||||||
private TurtleCommandResult dig(ITurtleAccess turtle, Direction direction) {
|
/**
|
||||||
if (PlatformHelper.get().hasToolUsage(item) && TurtlePlaceCommand.deployCopiedItem(item.copy(), turtle, direction, null, null)) {
|
* Attack an entity. This is a copy of {@link Player#attack(Entity)}, with some unwanted features removed (sweeping
|
||||||
return TurtleCommandResult.success();
|
* edge). This is a little limited.
|
||||||
|
* <p>
|
||||||
|
* Ideally we'd use attack directly (if other mods mixin to that method, we won't support their features).
|
||||||
|
* Unfortunately,that doesn't give us any feedback to whether the attack occurred or not (and we don't want to play
|
||||||
|
* sounds/particles).
|
||||||
|
*
|
||||||
|
* @param player The fake player doing the attacking.
|
||||||
|
* @param direction The direction the turtle is attacking.
|
||||||
|
* @param entity The entity to attack.
|
||||||
|
* @return Whether we attacked or not.
|
||||||
|
* @see Player#attack(Entity)
|
||||||
|
*/
|
||||||
|
private boolean attack(ServerPlayer player, Direction direction, Entity entity) {
|
||||||
|
var baseDamage = (float) player.getAttributeValue(Attributes.ATTACK_DAMAGE) * damageMulitiplier;
|
||||||
|
var bonusDamage = EnchantmentHelper.getDamageBonus(
|
||||||
|
player.getItemInHand(InteractionHand.MAIN_HAND), entity instanceof LivingEntity target ? target.getMobType() : MobType.UNDEFINED
|
||||||
|
);
|
||||||
|
var damage = baseDamage + bonusDamage;
|
||||||
|
if (damage <= 0) return false;
|
||||||
|
|
||||||
|
var knockBack = EnchantmentHelper.getKnockbackBonus(player);
|
||||||
|
|
||||||
|
// We follow the logic in Player.attack of setting the entity on fire before attacking, so it's burning when it
|
||||||
|
// (possibly) dies.
|
||||||
|
var fireAspect = EnchantmentHelper.getFireAspect(player);
|
||||||
|
var onFire = false;
|
||||||
|
if (entity instanceof LivingEntity target && fireAspect > 0 && !target.isOnFire()) {
|
||||||
|
onFire = true;
|
||||||
|
target.setSecondsOnFire(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var source = player.damageSources().playerAttack(player);
|
||||||
|
if (!entity.hurt(source, damage)) {
|
||||||
|
// If we failed to damage the entity, undo us setting the entity on fire.
|
||||||
|
if (onFire) entity.clearFire();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special case for armor stands: attack twice to guarantee destroy
|
||||||
|
if (entity.isAlive() && entity instanceof ArmorStand) entity.hurt(source, damage);
|
||||||
|
|
||||||
|
// Apply knockback
|
||||||
|
if (knockBack > 0) {
|
||||||
|
if (entity instanceof LivingEntity target) {
|
||||||
|
target.knockback(knockBack * 0.5, -direction.getStepX(), -direction.getStepZ());
|
||||||
|
} else {
|
||||||
|
entity.push(direction.getStepX() * knockBack * 0.5, 0.1, direction.getStepZ() * knockBack * 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply remaining enchantments
|
||||||
|
if (entity instanceof LivingEntity target) EnchantmentHelper.doPostHurtEffects(target, player);
|
||||||
|
EnchantmentHelper.doPostDamageEffects(player, entity);
|
||||||
|
|
||||||
|
// Damage the original item stack.
|
||||||
|
if (entity instanceof LivingEntity target) {
|
||||||
|
player.getItemInHand(InteractionHand.MAIN_HAND).hurtEnemy(target, player);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply fire aspect
|
||||||
|
if (entity instanceof LivingEntity target && fireAspect > 0 && !target.isOnFire()) {
|
||||||
|
target.setSecondsOnFire(4 * fireAspect);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TurtleCommandResult dig(ITurtleAccess turtle, TurtleSide side, Direction direction) {
|
||||||
var level = (ServerLevel) turtle.getLevel();
|
var level = (ServerLevel) turtle.getLevel();
|
||||||
var turtlePosition = turtle.getPosition();
|
|
||||||
|
|
||||||
var blockPosition = turtlePosition.relative(direction);
|
return withEquippedItem(turtle, side, direction, turtlePlayer -> {
|
||||||
if (level.isEmptyBlock(blockPosition) || WorldUtil.isLiquidBlock(level, blockPosition)) {
|
var stack = turtlePlayer.player().getItemInHand(InteractionHand.MAIN_HAND);
|
||||||
return TurtleCommandResult.failure("Nothing to dig here");
|
|
||||||
|
// Right-click the block when using a shovel/hoe. Important that we do this before checking the block is
|
||||||
|
// present, as we allow doing these actions from slightly further away.
|
||||||
|
if (PlatformHelper.get().hasToolUsage(stack) && useTool(level, turtle, turtlePlayer, stack, direction)) {
|
||||||
|
return TurtleCommandResult.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
var blockPosition = turtle.getPosition().relative(direction);
|
||||||
|
if (level.isEmptyBlock(blockPosition) || WorldUtil.isLiquidBlock(level, blockPosition)) {
|
||||||
|
return TurtleCommandResult.failure("Nothing to dig here");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we can break the block
|
||||||
|
var breakable = checkBlockBreakable(level, blockPosition, turtlePlayer);
|
||||||
|
if (!breakable.isSuccess()) return breakable;
|
||||||
|
|
||||||
|
// And break it!
|
||||||
|
DropConsumer.set(level, blockPosition, TurtleUtil.dropConsumer(turtle));
|
||||||
|
var broken = !turtlePlayer.isBlockProtected(level, blockPosition) && turtlePlayer.player().gameMode.destroyBlock(blockPosition);
|
||||||
|
TurtleUtil.stopConsuming(turtle);
|
||||||
|
|
||||||
|
return broken ? TurtleCommandResult.success() : TurtleCommandResult.failure("Cannot break protected block");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to use a tool against a block instead.
|
||||||
|
*
|
||||||
|
* @param level The current level.
|
||||||
|
* @param turtle The current turtle.
|
||||||
|
* @param turtlePlayer The turtle player, already positioned and with a stack equipped.
|
||||||
|
* @param stack The current tool's stack.
|
||||||
|
* @param direction The direction this action occurs in.
|
||||||
|
* @return Whether the tool was successfully used.
|
||||||
|
* @see PlatformHelper#hasToolUsage(ItemStack)
|
||||||
|
*/
|
||||||
|
private boolean useTool(ServerLevel level, ITurtleAccess turtle, TurtlePlayer turtlePlayer, ItemStack stack, Direction direction) {
|
||||||
|
var position = turtle.getPosition().relative(direction);
|
||||||
|
// Allow digging one extra block below the turtle, as you can't till dirt/flatten grass if there's a block
|
||||||
|
// above.
|
||||||
|
if (direction == Direction.DOWN && level.isEmptyBlock(position)) position = position.relative(direction);
|
||||||
|
|
||||||
|
if (!level.isInWorldBounds(position) || level.isEmptyBlock(position) || turtlePlayer.isBlockProtected(level, position)) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var turtlePlayer = TurtlePlayer.getWithPosition(turtle, turtlePosition, direction);
|
var hit = TurtlePlaceCommand.getHitResult(position, direction.getOpposite());
|
||||||
turtlePlayer.loadInventory(item.copy());
|
var result = PlatformHelper.get().useOn(turtlePlayer.player(), stack, hit, x -> false);
|
||||||
|
return result.consumesAction();
|
||||||
// Check if we can break the block
|
|
||||||
var breakable = checkBlockBreakable(level, blockPosition, turtlePlayer);
|
|
||||||
if (!breakable.isSuccess()) return breakable;
|
|
||||||
|
|
||||||
DropConsumer.set(level, blockPosition, TurtleUtil.dropConsumer(turtle));
|
|
||||||
var broken = !turtlePlayer.isBlockProtected(level, blockPosition) && turtlePlayer.player().gameMode.destroyBlock(blockPosition);
|
|
||||||
TurtleUtil.stopConsuming(turtle);
|
|
||||||
|
|
||||||
// Check spawn protection
|
|
||||||
return broken ? TurtleCommandResult.success() : TurtleCommandResult.failure("Cannot break protected block");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isTriviallyBreakable(BlockGetter reader, BlockPos pos, BlockState state) {
|
private static boolean isTriviallyBreakable(BlockGetter reader, BlockPos pos, BlockState state) {
|
||||||
|
@@ -5,6 +5,7 @@
|
|||||||
package dan200.computercraft.shared.turtle.upgrades;
|
package dan200.computercraft.shared.turtle.upgrades;
|
||||||
|
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
|
import dan200.computercraft.api.turtle.TurtleToolDurability;
|
||||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
|
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
|
||||||
import dan200.computercraft.api.upgrades.UpgradeBase;
|
import dan200.computercraft.api.upgrades.UpgradeBase;
|
||||||
import dan200.computercraft.shared.platform.RegistryWrappers;
|
import dan200.computercraft.shared.platform.RegistryWrappers;
|
||||||
@@ -16,6 +17,8 @@ import net.minecraft.util.GsonHelper;
|
|||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
import net.minecraft.world.level.block.Block;
|
import net.minecraft.world.level.block.Block;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public final class TurtleToolSerialiser implements TurtleUpgradeSerialiser<TurtleTool> {
|
public final class TurtleToolSerialiser implements TurtleUpgradeSerialiser<TurtleTool> {
|
||||||
public static final TurtleToolSerialiser INSTANCE = new TurtleToolSerialiser();
|
public static final TurtleToolSerialiser INSTANCE = new TurtleToolSerialiser();
|
||||||
|
|
||||||
@@ -28,6 +31,8 @@ public final class TurtleToolSerialiser implements TurtleUpgradeSerialiser<Turtl
|
|||||||
var toolItem = GsonHelper.getAsItem(object, "item");
|
var toolItem = GsonHelper.getAsItem(object, "item");
|
||||||
var craftingItem = GsonHelper.getAsItem(object, "craftingItem", toolItem);
|
var craftingItem = GsonHelper.getAsItem(object, "craftingItem", toolItem);
|
||||||
var damageMultiplier = GsonHelper.getAsFloat(object, "damageMultiplier", 3.0f);
|
var damageMultiplier = GsonHelper.getAsFloat(object, "damageMultiplier", 3.0f);
|
||||||
|
var allowEnchantments = GsonHelper.getAsBoolean(object, "allowEnchantments", false);
|
||||||
|
var consumeDurability = TurtleToolDurability.CODEC.byName(GsonHelper.getAsString(object, "consumeDurability", null), TurtleToolDurability.NEVER);
|
||||||
|
|
||||||
TagKey<Block> breakable = null;
|
TagKey<Block> breakable = null;
|
||||||
if (object.has("breakable")) {
|
if (object.has("breakable")) {
|
||||||
@@ -35,28 +40,33 @@ public final class TurtleToolSerialiser implements TurtleUpgradeSerialiser<Turtl
|
|||||||
breakable = TagKey.create(Registries.BLOCK, tag);
|
breakable = TagKey.create(Registries.BLOCK, tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new TurtleTool(id, adjective, craftingItem, new ItemStack(toolItem), damageMultiplier, breakable);
|
return new TurtleTool(id, adjective, craftingItem, new ItemStack(toolItem), damageMultiplier, allowEnchantments, consumeDurability, breakable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TurtleTool fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) {
|
public TurtleTool fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) {
|
||||||
var adjective = buffer.readUtf();
|
var adjective = buffer.readUtf();
|
||||||
var craftingItem = RegistryWrappers.readId(buffer, RegistryWrappers.ITEMS);
|
var craftingItem = buffer.readById(RegistryWrappers.ITEMS);
|
||||||
|
Objects.requireNonNull(craftingItem, "Unknown crafting item");
|
||||||
var toolItem = buffer.readItem();
|
var toolItem = buffer.readItem();
|
||||||
// damageMultiplier and breakable aren't used by the client, but we need to construct the upgrade exactly
|
// damageMultiplier and breakable aren't used by the client, but we need to construct the upgrade exactly
|
||||||
// as otherwise syncing on an SP world will overwrite the (shared) upgrade registry with an invalid upgrade!
|
// as otherwise syncing on an SP world will overwrite the (shared) upgrade registry with an invalid upgrade!
|
||||||
var damageMultiplier = buffer.readFloat();
|
var damageMultiplier = buffer.readFloat();
|
||||||
|
var allowsEnchantments = buffer.readBoolean();
|
||||||
|
var consumesDurability = buffer.readEnum(TurtleToolDurability.class);
|
||||||
|
|
||||||
var breakable = buffer.readBoolean() ? TagKey.create(Registries.BLOCK, buffer.readResourceLocation()) : null;
|
var breakable = buffer.readBoolean() ? TagKey.create(Registries.BLOCK, buffer.readResourceLocation()) : null;
|
||||||
return new TurtleTool(id, adjective, craftingItem, toolItem, damageMultiplier, breakable);
|
return new TurtleTool(id, adjective, craftingItem, toolItem, damageMultiplier, allowsEnchantments, consumesDurability, breakable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void toNetwork(FriendlyByteBuf buffer, TurtleTool upgrade) {
|
public void toNetwork(FriendlyByteBuf buffer, TurtleTool upgrade) {
|
||||||
buffer.writeUtf(upgrade.getUnlocalisedAdjective());
|
buffer.writeUtf(upgrade.getUnlocalisedAdjective());
|
||||||
RegistryWrappers.writeId(buffer, RegistryWrappers.ITEMS, upgrade.getCraftingItem().getItem());
|
buffer.writeId(RegistryWrappers.ITEMS, upgrade.getCraftingItem().getItem());
|
||||||
buffer.writeItem(upgrade.item);
|
buffer.writeItem(upgrade.item);
|
||||||
buffer.writeFloat(upgrade.damageMulitiplier);
|
buffer.writeFloat(upgrade.damageMulitiplier);
|
||||||
|
buffer.writeBoolean(upgrade.allowEnchantments);
|
||||||
|
buffer.writeEnum(upgrade.consumeDurability);
|
||||||
buffer.writeBoolean(upgrade.breakable != null);
|
buffer.writeBoolean(upgrade.breakable != null);
|
||||||
if (upgrade.breakable != null) buffer.writeResourceLocation(upgrade.breakable.location());
|
if (upgrade.breakable != null) buffer.writeResourceLocation(upgrade.breakable.location());
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,56 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.shared.util;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.AbstractList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list which prepends a single value to another list.
|
||||||
|
*
|
||||||
|
* @param <T> The type of item in the list.
|
||||||
|
*/
|
||||||
|
public final class ConsList<T> extends AbstractList<T> {
|
||||||
|
private final T head;
|
||||||
|
private final List<T> tail;
|
||||||
|
|
||||||
|
public ConsList(T head, List<T> tail) {
|
||||||
|
this.head = head;
|
||||||
|
this.tail = tail;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T get(int index) {
|
||||||
|
return index == 0 ? head : tail.get(index - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return 1 + tail.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<T> iterator() {
|
||||||
|
return new Iterator<>() {
|
||||||
|
private @Nullable Iterator<T> tailIterator;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return tailIterator == null || tailIterator.hasNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T next() {
|
||||||
|
if (tailIterator != null) return tailIterator.next();
|
||||||
|
|
||||||
|
tailIterator = tail.iterator();
|
||||||
|
return head;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@@ -7,6 +7,7 @@ package dan200.computercraft.shared.util;
|
|||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.io.BaseEncoding;
|
import com.google.common.io.BaseEncoding;
|
||||||
import dan200.computercraft.core.util.Nullability;
|
import dan200.computercraft.core.util.Nullability;
|
||||||
|
import dan200.computercraft.shared.platform.PlatformHelper;
|
||||||
import net.minecraft.nbt.*;
|
import net.minecraft.nbt.*;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -19,6 +20,7 @@ import java.io.OutputStream;
|
|||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -27,9 +29,42 @@ public final class NBTUtil {
|
|||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static final BaseEncoding ENCODING = BaseEncoding.base16().lowerCase();
|
static final BaseEncoding ENCODING = BaseEncoding.base16().lowerCase();
|
||||||
|
|
||||||
|
private static final CompoundTag EMPTY_TAG;
|
||||||
|
|
||||||
|
static {
|
||||||
|
// If in a development environment, create a magic immutable compound tag.
|
||||||
|
// We avoid doing this in prod, as I fear it might mess up the JIT inlining things.
|
||||||
|
if (PlatformHelper.get().isDevelopmentEnvironment()) {
|
||||||
|
try {
|
||||||
|
var ctor = CompoundTag.class.getDeclaredConstructor(Map.class);
|
||||||
|
ctor.setAccessible(true);
|
||||||
|
EMPTY_TAG = ctor.newInstance(Collections.emptyMap());
|
||||||
|
} catch (ReflectiveOperationException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
EMPTY_TAG = new CompoundTag();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private NBTUtil() {
|
private NBTUtil() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a singleton empty {@link CompoundTag}. This tag should never be modified.
|
||||||
|
*
|
||||||
|
* @return The empty compound tag.
|
||||||
|
*/
|
||||||
|
public static CompoundTag emptyTag() {
|
||||||
|
if (EMPTY_TAG.size() != 0) LOG.error("The empty tag has been modified.");
|
||||||
|
return EMPTY_TAG;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CompoundTag getCompoundOrEmpty(CompoundTag tag, String key) {
|
||||||
|
var childTag = tag.get(key);
|
||||||
|
return childTag != null && childTag.getId() == Tag.TAG_COMPOUND ? (CompoundTag) childTag : emptyTag();
|
||||||
|
}
|
||||||
|
|
||||||
private static @Nullable Tag toNBTTag(@Nullable Object object) {
|
private static @Nullable Tag toNBTTag(@Nullable Object object) {
|
||||||
if (object == null) return null;
|
if (object == null) return null;
|
||||||
if (object instanceof Boolean) return ByteTag.valueOf((byte) ((boolean) (Boolean) object ? 1 : 0));
|
if (object instanceof Boolean) return ByteTag.valueOf((byte) ((boolean) (Boolean) object ? 1 : 0));
|
||||||
|
@@ -50,6 +50,8 @@ public final class RedstoneUtil {
|
|||||||
|
|
||||||
var neighbourPos = pos.relative(side);
|
var neighbourPos = pos.relative(side);
|
||||||
world.neighborChanged(neighbourPos, block.getBlock(), pos);
|
world.neighborChanged(neighbourPos, block.getBlock(), pos);
|
||||||
world.updateNeighborsAtExceptFromFacing(neighbourPos, block.getBlock(), side.getOpposite());
|
// We intentionally use updateNeighborsAt here instead of updateNeighborsAtExceptFromFacing, as computers can
|
||||||
|
// both send and receive redstone, and so also need to be updated.
|
||||||
|
world.updateNeighborsAt(neighbourPos, block.getBlock());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,7 +7,6 @@ package dan200.computercraft.shared.util;
|
|||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,12 +37,6 @@ public class Trie<K, V> {
|
|||||||
getChild(key).current = value;
|
getChild(key).current = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Stream<V> children() {
|
|
||||||
return children == null
|
|
||||||
? Stream.empty()
|
|
||||||
: children.values().stream().map(x -> x.current).filter(Objects::nonNull);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Stream<V> stream() {
|
public Stream<V> stream() {
|
||||||
return Stream.concat(
|
return Stream.concat(
|
||||||
current == null ? Stream.empty() : Stream.of(current),
|
current == null ? Stream.empty() : Stream.of(current),
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
{
|
{
|
||||||
|
"argument.computercraft.argument_expected": "Očekáván argument",
|
||||||
"argument.computercraft.computer.many_matching": "Pro '%s' se shoduje více počítačů (instance %s)",
|
"argument.computercraft.computer.many_matching": "Pro '%s' se shoduje více počítačů (instance %s)",
|
||||||
"argument.computercraft.computer.no_matching": "Pro '%s' se neshodují žádné počítače",
|
"argument.computercraft.computer.no_matching": "Pro '%s' se neshodují žádné počítače",
|
||||||
|
"argument.computercraft.tracking_field.no_field": "Neznámé pole '%s'",
|
||||||
"block.computercraft.cable": "Síťový kabel",
|
"block.computercraft.cable": "Síťový kabel",
|
||||||
"block.computercraft.computer_advanced": "Pokročilý počítač",
|
"block.computercraft.computer_advanced": "Pokročilý počítač",
|
||||||
"block.computercraft.computer_command": "Příkazový počítač",
|
"block.computercraft.computer_command": "Příkazový počítač",
|
||||||
@@ -12,6 +14,7 @@
|
|||||||
"block.computercraft.speaker": "Reproduktor",
|
"block.computercraft.speaker": "Reproduktor",
|
||||||
"block.computercraft.turtle_advanced": "Pokročilý robot",
|
"block.computercraft.turtle_advanced": "Pokročilý robot",
|
||||||
"block.computercraft.turtle_advanced.upgraded": "Pokročilý %s robot",
|
"block.computercraft.turtle_advanced.upgraded": "Pokročilý %s robot",
|
||||||
|
"block.computercraft.turtle_advanced.upgraded_twice": "Pokročilý %s %s robot",
|
||||||
"block.computercraft.turtle_normal": "Robot",
|
"block.computercraft.turtle_normal": "Robot",
|
||||||
"block.computercraft.turtle_normal.upgraded": "%s robot",
|
"block.computercraft.turtle_normal.upgraded": "%s robot",
|
||||||
"block.computercraft.turtle_normal.upgraded_twice": "%s %s robot",
|
"block.computercraft.turtle_normal.upgraded_twice": "%s %s robot",
|
||||||
@@ -21,117 +24,103 @@
|
|||||||
"block.computercraft.wireless_modem_normal": "Bezdrátový modem",
|
"block.computercraft.wireless_modem_normal": "Bezdrátový modem",
|
||||||
"chat.computercraft.wired_modem.peripheral_connected": "Periferie \"%s\" připojena k sítí",
|
"chat.computercraft.wired_modem.peripheral_connected": "Periferie \"%s\" připojena k sítí",
|
||||||
"chat.computercraft.wired_modem.peripheral_disconnected": "Periferie \"%s\" odpojena od sítě",
|
"chat.computercraft.wired_modem.peripheral_disconnected": "Periferie \"%s\" odpojena od sítě",
|
||||||
|
"commands.computercraft.desc": "Příkaz /computercraft dává různé ladící a správcovské nástroje pro ovládání a interakci s počítači.",
|
||||||
"commands.computercraft.dump.action": "Ukázat více informací o tomto počítači",
|
"commands.computercraft.dump.action": "Ukázat více informací o tomto počítači",
|
||||||
|
"commands.computercraft.dump.desc": "Ukáže stav všech počítačů nebo specifické informace o jednom počítači. Můžeš specifikovat ID počítačové instance (tř. 123), ID počítače (tř #123) nebo štítek (tř. \"@Můj počítač\").",
|
||||||
"commands.computercraft.dump.open_path": "Ukázat soubory tohoto počítače",
|
"commands.computercraft.dump.open_path": "Ukázat soubory tohoto počítače",
|
||||||
"commands.computercraft.dump.synopsis": "Ukázat stav počítačů.",
|
"commands.computercraft.dump.synopsis": "Ukázat stav počítačů.",
|
||||||
"commands.computercraft.generic.additional_rows": "%d řádků navíc…",
|
"commands.computercraft.generic.additional_rows": "%d řádků navíc…",
|
||||||
"commands.computercraft.generic.exception": "Neočekávaná chyba (%s)",
|
"commands.computercraft.generic.exception": "Neočekávaná chyba (%s)",
|
||||||
"commands.computercraft.generic.no": "N",
|
"commands.computercraft.generic.no": "N",
|
||||||
|
"commands.computercraft.generic.no_position": "<žádná pozice>",
|
||||||
"commands.computercraft.generic.position": "%s, %s, %s",
|
"commands.computercraft.generic.position": "%s, %s, %s",
|
||||||
"commands.computercraft.generic.yes": "A",
|
"commands.computercraft.generic.yes": "A",
|
||||||
"commands.computercraft.help.desc": "Ukáže tuto pomocnou zprávu",
|
"commands.computercraft.help.desc": "Ukáže tuto pomocnou zprávu",
|
||||||
|
"commands.computercraft.help.no_children": "%s nemá žádné podpříkazy",
|
||||||
"commands.computercraft.help.no_command": "Neznámý příkaz '%s'",
|
"commands.computercraft.help.no_command": "Neznámý příkaz '%s'",
|
||||||
"commands.computercraft.help.synopsis": "Zaslat pomoc pro specifický příkaz",
|
"commands.computercraft.help.synopsis": "Zaslat pomoc pro specifický příkaz",
|
||||||
|
"commands.computercraft.queue.desc": "Poslat událost computer_command příkazovému počítači, procházející přes ostatní argumenty. Toto je většinou určeno pro tvůrce map, chovající se jako pro počítač více přátelská verze příkazu /trigger. Jakýkoliv hráč může spustit příkaz, což by ale bylo většinou uděláno přes událost kliknutím na textový komponent.",
|
||||||
"commands.computercraft.queue.synopsis": "Poslat událost computer_command příkazovému počítači",
|
"commands.computercraft.queue.synopsis": "Poslat událost computer_command příkazovému počítači",
|
||||||
"commands.computercraft.reload.desc": "Znovu načíst konfigurační soubor ComputerCraftu",
|
|
||||||
"commands.computercraft.reload.done": "Konfigurace znovu načtena",
|
|
||||||
"commands.computercraft.reload.synopsis": "Znovu načíst konfigurační soubor ComputerCraftu",
|
|
||||||
"commands.computercraft.shutdown.desc": "Vypnout zapsané počítače nebo všechny pokud nejsou specifikovány. Můžeš specifikovat ID počítačové instance (tř. 123) ID počítače (tř. #123) nebo štítek (tř. \"@Můj počítač\").",
|
"commands.computercraft.shutdown.desc": "Vypnout zapsané počítače nebo všechny pokud nejsou specifikovány. Můžeš specifikovat ID počítačové instance (tř. 123) ID počítače (tř. #123) nebo štítek (tř. \"@Můj počítač\").",
|
||||||
"commands.computercraft.shutdown.done": "Vypnuto %s/%s počítačů",
|
"commands.computercraft.shutdown.done": "Vypnuto %s/%s počítačů",
|
||||||
"commands.computercraft.shutdown.synopsis": "Vypne počítače na dálku.",
|
"commands.computercraft.shutdown.synopsis": "Vypne počítače na dálku.",
|
||||||
"commands.computercraft.tp.not_player": "Nelze otevřít terminál pro nehráče",
|
|
||||||
"commands.computercraft.tp.not_there": "Nelze najít počítač ve světě",
|
|
||||||
"commands.computercraft.track.dump.no_timings": "Nejsou k dispozici žádná časování",
|
|
||||||
"commands.computercraft.track.dump.synopsis": "Stáhnout nejnovější výsledky sledování",
|
|
||||||
"commands.computercraft.track.start.synopsis": "Začít sledovat všechny počítače",
|
|
||||||
"commands.computercraft.track.stop.action": "Klikni pro přestání sledování",
|
|
||||||
"commands.computercraft.track.synopsis": "Sledovat časy spuštení počítačů.",
|
|
||||||
"commands.computercraft.turn_on.done": "Zapnuto %s/%s počítačů",
|
|
||||||
"commands.computercraft.view.action": "Ukázat tento počítač",
|
|
||||||
"commands.computercraft.view.not_player": "Nelze otevřít terminál pro nehráče",
|
|
||||||
"commands.computercraft.view.synopsis": "Ukázat terminál počítače.",
|
|
||||||
"gui.computercraft.config.command_require_creative": "Příkazové počítače vyžadují tvořivý režim",
|
|
||||||
"upgrade.computercraft.wireless_modem_advanced.adjective": "Endový",
|
|
||||||
"upgrade.computercraft.wireless_modem_normal.adjective": "Bezdrátový",
|
|
||||||
"upgrade.minecraft.crafting_table.adjective": "Tvořivý",
|
|
||||||
"upgrade.minecraft.diamond_axe.adjective": "Kácející",
|
|
||||||
"upgrade.minecraft.diamond_hoe.adjective": "Farmářský",
|
|
||||||
"upgrade.minecraft.diamond_pickaxe.adjective": "Těžební",
|
|
||||||
"tracking_field.computercraft.avg": "%s (průměr)",
|
|
||||||
"tracking_field.computercraft.computer_tasks.name": "Úlohy",
|
|
||||||
"tracking_field.computercraft.coroutines_dead.name": "Vyhozené koprogramy",
|
|
||||||
"tracking_field.computercraft.count": "%s (počet)",
|
|
||||||
"tracking_field.computercraft.fs.name": "Operace souborového systému",
|
|
||||||
"tracking_field.computercraft.http_download.name": "Stahování HTTP",
|
|
||||||
"tracking_field.computercraft.http_requests.name": "Požadavky HTTP",
|
|
||||||
"tracking_field.computercraft.http_upload.name": "Nahrávání HTTP",
|
|
||||||
"tracking_field.computercraft.max": "%s (max)",
|
|
||||||
"tracking_field.computercraft.peripheral.name": "Periferní volání",
|
|
||||||
"tracking_field.computercraft.server_tasks.name": "Serverové úlohy",
|
|
||||||
"tracking_field.computercraft.turtle_ops.name": "Operace robotů",
|
|
||||||
"tracking_field.computercraft.websocket_outgoing.name": "Odchozí websocket",
|
|
||||||
"upgrade.minecraft.diamond_shovel.adjective": "Kopací",
|
|
||||||
"upgrade.minecraft.diamond_sword.adjective": "Bojový",
|
|
||||||
"argument.computercraft.argument_expected": "Očekáván argument",
|
|
||||||
"argument.computercraft.tracking_field.no_field": "Neznámé pole '%s'",
|
|
||||||
"block.computercraft.turtle_advanced.upgraded_twice": "Pokročilý %s %s robot",
|
|
||||||
"commands.computercraft.desc": "Příkaz /computercraft dává různé ladící a správcovské nástroje pro ovládání a interakci s počítači.",
|
|
||||||
"commands.computercraft.dump.desc": "Ukáže stav všech počítačů nebo specifické informace o jednom počítači. Můžeš specifikovat ID počítačové instance (tř. 123), ID počítače (tř #123) nebo štítek (tř. \"@Můj počítač\").",
|
|
||||||
"commands.computercraft.generic.no_position": "<žádná pozice>",
|
|
||||||
"commands.computercraft.help.no_children": "%s nemá žádné podpříkazy",
|
|
||||||
"commands.computercraft.queue.desc": "Poslat událost computer_command příkazovému počítači, procházející přes ostatní argumenty. Toto je většinou určeno pro tvůrce map, chovající se jako pro počítač více přátelská verze příkazu /trigger. Jakýkoliv hráč může spustit příkaz, což by ale bylo většinou uděláno přes událost kliknutím na textový komponent.",
|
|
||||||
"commands.computercraft.synopsis": "Různé příkazy pro ovládání počítačů.",
|
"commands.computercraft.synopsis": "Různé příkazy pro ovládání počítačů.",
|
||||||
"commands.computercraft.tp.action": "Teleportovat se k počítači",
|
"commands.computercraft.tp.action": "Teleportovat se k počítači",
|
||||||
"commands.computercraft.tp.desc": "Teleportovat se na místo počítače. Můžeš specifikovat ID počítačové instance (tř. 123) nebo ID počítače (tř. #123).",
|
"commands.computercraft.tp.desc": "Teleportovat se na místo počítače. Můžeš specifikovat ID počítačové instance (tř. 123) nebo ID počítače (tř. #123).",
|
||||||
|
"commands.computercraft.tp.not_player": "Nelze otevřít terminál pro nehráče",
|
||||||
|
"commands.computercraft.tp.not_there": "Nelze najít počítač ve světě",
|
||||||
"commands.computercraft.tp.synopsis": "Teleportovat se ke specifickému počítači.",
|
"commands.computercraft.tp.synopsis": "Teleportovat se ke specifickému počítači.",
|
||||||
"commands.computercraft.track.desc": "Sledovat jak dlouho se počítače spustí, a také kolik událostí zpracují. Toto uvádí informace v podobné cestě jako /forge track a může být dobré pro diagnostiku lagu.",
|
"commands.computercraft.track.desc": "Sledovat jak dlouho se počítače spustí, a také kolik událostí zpracují. Toto uvádí informace v podobné cestě jako /forge track a může být dobré pro diagnostiku lagu.",
|
||||||
"commands.computercraft.track.dump.computer": "Počítač",
|
"commands.computercraft.track.dump.computer": "Počítač",
|
||||||
"commands.computercraft.track.dump.desc": "Stáhnout nejnovější výsledky sledování počítačů.",
|
"commands.computercraft.track.dump.desc": "Stáhnout nejnovější výsledky sledování počítačů.",
|
||||||
"upgrade.computercraft.speaker.adjective": "Hlučný",
|
"commands.computercraft.track.dump.no_timings": "Nejsou k dispozici žádná časování",
|
||||||
"tracking_field.computercraft.coroutines_created.name": "Vytvořené koprogramy",
|
"commands.computercraft.track.dump.synopsis": "Stáhnout nejnovější výsledky sledování",
|
||||||
"tracking_field.computercraft.websocket_incoming.name": "Přichozí websocket",
|
|
||||||
"commands.computercraft.track.start.desc": "Začne sledovat spouštěcí časy všech počítačů a počty událostí. Toto vyhodí výsledky předchozích spuštení.",
|
"commands.computercraft.track.start.desc": "Začne sledovat spouštěcí časy všech počítačů a počty událostí. Toto vyhodí výsledky předchozích spuštení.",
|
||||||
"commands.computercraft.track.start.stop": "Spusť %s pro zastavení sledování a ukázání výsledků",
|
"commands.computercraft.track.start.stop": "Spusť %s pro zastavení sledování a ukázání výsledků",
|
||||||
|
"commands.computercraft.track.start.synopsis": "Začít sledovat všechny počítače",
|
||||||
|
"commands.computercraft.track.stop.action": "Klikni pro přestání sledování",
|
||||||
"commands.computercraft.track.stop.desc": "Přestane sledovat všechny události a časy spuštení počítačů",
|
"commands.computercraft.track.stop.desc": "Přestane sledovat všechny události a časy spuštení počítačů",
|
||||||
"commands.computercraft.track.stop.not_enabled": "Nyní se nesledují žádné počítače",
|
"commands.computercraft.track.stop.not_enabled": "Nyní se nesledují žádné počítače",
|
||||||
"commands.computercraft.track.stop.synopsis": "Přestat sledovat všechny počítače",
|
"commands.computercraft.track.stop.synopsis": "Přestat sledovat všechny počítače",
|
||||||
|
"commands.computercraft.track.synopsis": "Sledovat časy spuštení počítačů.",
|
||||||
"commands.computercraft.turn_on.desc": "Zapne počítače na seznamu. Můžeš specifikovat ID počítačové instance (tř. 123), ID počítače (tř. 123) nebo štítek (tř. \"@Můj počítač\").",
|
"commands.computercraft.turn_on.desc": "Zapne počítače na seznamu. Můžeš specifikovat ID počítačové instance (tř. 123), ID počítače (tř. 123) nebo štítek (tř. \"@Můj počítač\").",
|
||||||
|
"commands.computercraft.turn_on.done": "Zapnuto %s/%s počítačů",
|
||||||
"commands.computercraft.turn_on.synopsis": "Zapnout počítače na dálku.",
|
"commands.computercraft.turn_on.synopsis": "Zapnout počítače na dálku.",
|
||||||
|
"commands.computercraft.view.action": "Ukázat tento počítač",
|
||||||
"commands.computercraft.view.desc": "Otevře terminál počítače, umožnující dálkové ovládání počítače. Toto nedává přístup k inventářům robotů. Můžeš specifikovat ID počítačové instance (tř. 123) nebo ID počítače (tř. #123).",
|
"commands.computercraft.view.desc": "Otevře terminál počítače, umožnující dálkové ovládání počítače. Toto nedává přístup k inventářům robotů. Můžeš specifikovat ID počítačové instance (tř. 123) nebo ID počítače (tř. #123).",
|
||||||
|
"commands.computercraft.view.not_player": "Nelze otevřít terminál pro nehráče",
|
||||||
|
"commands.computercraft.view.synopsis": "Ukázat terminál počítače.",
|
||||||
|
"gui.computercraft.config.command_require_creative": "Příkazové počítače vyžadují tvořivý režim",
|
||||||
"gui.computercraft.config.command_require_creative.tooltip": "Vyžadovat, aby hráči byli v tvořivém režimu a měli operátorská prává pro interakci\ns příkazovýmí počítači. Toto je základní chování pro vanilla Příkazové bloky.",
|
"gui.computercraft.config.command_require_creative.tooltip": "Vyžadovat, aby hráči byli v tvořivém režimu a měli operátorská prává pro interakci\ns příkazovýmí počítači. Toto je základní chování pro vanilla Příkazové bloky.",
|
||||||
"gui.computercraft.config.computer_space_limit": "Limit počítačového místa (v bytech)",
|
"gui.computercraft.config.computer_space_limit": "Limit počítačového místa (v bytech)",
|
||||||
"gui.computercraft.config.disable_lua51_features": "Vypnout Lua 5.1 funkce",
|
|
||||||
"gui.computercraft.config.execution": "Spuštení",
|
|
||||||
"gui.computercraft.config.execution.computer_threads": "Počítačová vlákna",
|
|
||||||
"gui.computercraft.config.execution.max_main_computer_time": "Serverový časový limit počítačových ticků",
|
|
||||||
"gui.computercraft.config.http": "HTTP",
|
|
||||||
"gui.computercraft.config.http.bandwidth.global_download": "Globální limit stahování",
|
|
||||||
"gui.computercraft.config.http.bandwidth.global_upload": "Globální nahrávací limit",
|
|
||||||
"gui.computercraft.config.computer_space_limit.tooltip": "Limit diskového místa pro počítače a roboty, v bytech.",
|
"gui.computercraft.config.computer_space_limit.tooltip": "Limit diskového místa pro počítače a roboty, v bytech.",
|
||||||
"gui.computercraft.config.default_computer_settings": "Vychozí nastavení počítače",
|
"gui.computercraft.config.default_computer_settings": "Vychozí nastavení počítače",
|
||||||
"gui.computercraft.config.default_computer_settings.tooltip": "Čárkami oddělený seznam základních systémových nastavení pro nastavení na nových počítačích.\nPříklad: \"shell.autocomplete=false,lua.autocomplete=false,edit.autocomplete=false\"\nvypne všechno automatické vyplňování.",
|
"gui.computercraft.config.default_computer_settings.tooltip": "Čárkami oddělený seznam základních systémových nastavení pro nastavení na nových počítačích.\nPříklad: \"shell.autocomplete=false,lua.autocomplete=false,edit.autocomplete=false\"\nvypne všechno automatické vyplňování.",
|
||||||
|
"gui.computercraft.config.disable_lua51_features": "Vypnout Lua 5.1 funkce",
|
||||||
"gui.computercraft.config.disable_lua51_features.tooltip": "Nastav toto na pravdivou hodnotu pro vypnutí Lua 5.1 funkcí které budou odstraněny v budoucí\naktualizaci. Dobré pro ověření kompatibilty tvých programů nyní a předem.",
|
"gui.computercraft.config.disable_lua51_features.tooltip": "Nastav toto na pravdivou hodnotu pro vypnutí Lua 5.1 funkcí které budou odstraněny v budoucí\naktualizaci. Dobré pro ověření kompatibilty tvých programů nyní a předem.",
|
||||||
|
"gui.computercraft.config.disabled_generic_methods": "Vypnuté obecné metody",
|
||||||
|
"gui.computercraft.config.execution": "Spuštení",
|
||||||
|
"gui.computercraft.config.execution.computer_threads": "Počítačová vlákna",
|
||||||
"gui.computercraft.config.execution.computer_threads.tooltip": "Nastavit číslo vláken, na kterých můžou běžet počítače. Vyšší číslo znamená že\nvíce počítačů může běžet najednou, ale to může způsobit lag. Prosím měj na paměti že nejaké módy nemusí\nfungovat s počtem vláken vyšší než 1. Používej opatrně.\nHodnota: > 1",
|
"gui.computercraft.config.execution.computer_threads.tooltip": "Nastavit číslo vláken, na kterých můžou běžet počítače. Vyšší číslo znamená že\nvíce počítačů může běžet najednou, ale to může způsobit lag. Prosím měj na paměti že nejaké módy nemusí\nfungovat s počtem vláken vyšší než 1. Používej opatrně.\nHodnota: > 1",
|
||||||
|
"gui.computercraft.config.execution.max_main_computer_time": "Serverový časový limit počítačových ticků",
|
||||||
|
"gui.computercraft.config.execution.max_main_computer_time.tooltip": "Ideální maximální v kterém počítač se může spustit na tick, v milisekundách.\nPoznámka, možná přejdeme přes tento limit protože tu nelze rozhodnout jak\ndlouho to bude trvat - toto se zaměřuje jako vrchní hranice průměrného času.\nRozsah: > 1",
|
||||||
"gui.computercraft.config.execution.max_main_global_time": "Globální časový limit serverových ticků",
|
"gui.computercraft.config.execution.max_main_global_time": "Globální časový limit serverových ticků",
|
||||||
"gui.computercraft.config.http.bandwidth": "Šířka pásma",
|
|
||||||
"gui.computercraft.config.http.bandwidth.global_download.tooltip": "Počet bytů které můžou být staženy za sekundu. Toto je sdíleno mezi všemi počítači. (byty/s)\nHodnota: > 1",
|
|
||||||
"gui.computercraft.config.http.enabled": "Zapnout HTTP API",
|
|
||||||
"gui.computercraft.config.execution.max_main_global_time.tooltip": "Maximální čas který může být využit pro spouštení úloh v jednom ticku,\nv milisekundách.\nPoznámka, možná přejdeme přes tento limit protože nelze rozhodnout jak\ndlouho to bude trvat - toto se zaměřuje jako horní hranice průměrného času.\nRozsah: > 1",
|
"gui.computercraft.config.execution.max_main_global_time.tooltip": "Maximální čas který může být využit pro spouštení úloh v jednom ticku,\nv milisekundách.\nPoznámka, možná přejdeme přes tento limit protože nelze rozhodnout jak\ndlouho to bude trvat - toto se zaměřuje jako horní hranice průměrného času.\nRozsah: > 1",
|
||||||
"gui.computercraft.config.execution.tooltip": "Ovládá chování počítačového spuštení. Toto je většně určeno pro\nfine-tuning serverů, a obecně by nemělo být dotčeno.",
|
"gui.computercraft.config.execution.tooltip": "Ovládá chování počítačového spuštení. Toto je většně určeno pro\nfine-tuning serverů, a obecně by nemělo být dotčeno.",
|
||||||
"gui.computercraft.config.floppy_space_limit": "Limit místa disket (v bajtech)",
|
"gui.computercraft.config.floppy_space_limit": "Limit místa disket (v bajtech)",
|
||||||
"gui.computercraft.config.floppy_space_limit.tooltip": "Limit místa pro diskety, v bajtech.",
|
"gui.computercraft.config.floppy_space_limit.tooltip": "Limit místa pro diskety, v bajtech.",
|
||||||
|
"gui.computercraft.config.http": "HTTP",
|
||||||
|
"gui.computercraft.config.http.bandwidth": "Šířka pásma",
|
||||||
|
"gui.computercraft.config.http.bandwidth.global_download": "Globální limit stahování",
|
||||||
|
"gui.computercraft.config.http.bandwidth.global_download.tooltip": "Počet bytů které můžou být staženy za sekundu. Toto je sdíleno mezi všemi počítači. (byty/s)\nHodnota: > 1",
|
||||||
|
"gui.computercraft.config.http.bandwidth.global_upload": "Globální nahrávací limit",
|
||||||
|
"gui.computercraft.config.http.bandwidth.global_upload.tooltip": "Počet bytů které můžou být nahrány za sekundu. Toto je sdíleno mezi všemi počítači (bajty/s).\nRozsah: > 1",
|
||||||
"gui.computercraft.config.http.bandwidth.tooltip": "Omezuje šířku pásma použitou počítači.",
|
"gui.computercraft.config.http.bandwidth.tooltip": "Omezuje šířku pásma použitou počítači.",
|
||||||
|
"gui.computercraft.config.http.enabled": "Zapnout HTTP API",
|
||||||
|
"gui.computercraft.config.http.enabled.tooltip": "Zapnout \"http\" API na počítačích. Vypnutí tohoto také vypne programy \"pastebin\" a \"wget\"\nna kterých záleží hodně uživatelům. Je doporučeno nechat toto zapnuté a použít\nmožnost konfigurace \"rules\" pro určení více jemné kontroly.",
|
||||||
"gui.computercraft.config.http.max_requests": "Maximální souběžné požadavky",
|
"gui.computercraft.config.http.max_requests": "Maximální souběžné požadavky",
|
||||||
"gui.computercraft.config.http.max_requests.tooltip": "Počet HTTP požadavků které počítač může udělat v jedné chvíli. Požadavky navíc\nbudou zaslány do fronty, a poslány když běžící požadavky byly dokončeny. Nastav\nna 0 pro neomezeno.\nRozsah: > 0",
|
"gui.computercraft.config.http.max_requests.tooltip": "Počet HTTP požadavků které počítač může udělat v jedné chvíli. Požadavky navíc\nbudou zaslány do fronty, a poslány když běžící požadavky byly dokončeny. Nastav\nna 0 pro neomezeno.\nRozsah: > 0",
|
||||||
"gui.computercraft.config.http.max_websockets": "Maximální souběžné websockety",
|
"gui.computercraft.config.http.max_websockets": "Maximální souběžné websockety",
|
||||||
"gui.computercraft.config.http.max_websockets.tooltip": "Počet websocketů které může mít počítač otevřené najednou: Nastav na 0 pro neomezeno.\nRozsah: > 1",
|
"gui.computercraft.config.http.max_websockets.tooltip": "Počet websocketů které může mít počítač otevřené najednou: Nastav na 0 pro neomezeno.\nRozsah: > 1",
|
||||||
|
"gui.computercraft.config.http.proxy.host": "Název hostitele",
|
||||||
|
"gui.computercraft.config.http.proxy.host.tooltip": "Název hostitele nebo IP adresa proxy serveru.",
|
||||||
|
"gui.computercraft.config.http.proxy.port": "Port",
|
||||||
|
"gui.computercraft.config.http.proxy.port.tooltip": "Port proxy serveru.\nRozsah: 1 ~ 65536",
|
||||||
|
"gui.computercraft.config.http.proxy.type": "Typ proxy",
|
||||||
|
"gui.computercraft.config.http.proxy.type.tooltip": "Typ proxy k použití.\nPovolené hodnoty: HTTP, HTTPS, SOCKS4, SOCKS5",
|
||||||
"gui.computercraft.config.http.rules": "Pravidla povolení/zakázání",
|
"gui.computercraft.config.http.rules": "Pravidla povolení/zakázání",
|
||||||
|
"gui.computercraft.config.http.rules.tooltip": "Seznam pravidel které ovládají chování \"http\" API pro specifické domény nebo\nIP adresy. Každé pravidlo je položka s 'hostem' pro shodování, a také série\nvlastností. Pravidla jsou hodnocena v řádech, což znamená že dřívejší pravidla přepíšou\npozdější.\nHost může být jméno domény (\"pastebin.com\") shoda (\"*.pastebin.com\") nebo\nnotace CIDR (\"127.0.0.0/8\").\nPokud nejsou pravidla, doména je blokována.",
|
||||||
"gui.computercraft.config.http.tooltip": "Ovládá HTTP API",
|
"gui.computercraft.config.http.tooltip": "Ovládá HTTP API",
|
||||||
"gui.computercraft.config.http.websocket_enabled": "Zapnout websockety",
|
"gui.computercraft.config.http.websocket_enabled": "Zapnout websockety",
|
||||||
|
"gui.computercraft.config.http.websocket_enabled.tooltip": "Zapnout použití HTTP websocketů. Toto vyžaduje možnost \"http_enable\" aby byla zapnuta.",
|
||||||
"gui.computercraft.config.log_computer_errors": "Zapisovat počítačové chyby",
|
"gui.computercraft.config.log_computer_errors": "Zapisovat počítačové chyby",
|
||||||
|
"gui.computercraft.config.log_computer_errors.tooltip": "Zapisovat chyby hozené periferiemi a ostatními Lua objekty. Toto usnadňuje\nautorům módů ladění problémů, ale může způsobit spam zápisů když hráči\npoužívají problematické metody.",
|
||||||
"gui.computercraft.config.maximum_open_files": "Maximální počet souborů otevřených na počítačích",
|
"gui.computercraft.config.maximum_open_files": "Maximální počet souborů otevřených na počítačích",
|
||||||
"gui.computercraft.config.maximum_open_files.tooltip": "Nastavit kolik soborů může mít jeden počítač otevřených najednou. Nastav na 0 pro neomezeno.\nRozsah: > 0",
|
"gui.computercraft.config.maximum_open_files.tooltip": "Nastavit kolik soborů může mít jeden počítač otevřených najednou. Nastav na 0 pro neomezeno.\nRozsah: > 0",
|
||||||
|
"gui.computercraft.config.monitor_distance": "Dálka monitoru",
|
||||||
|
"gui.computercraft.config.monitor_distance.tooltip": "Maximální dálka kde se budou monitory načítač. Toto je vychozí na standartní limit\ntile entit, ale může být prodloužen pokud chceš stavět větší monitory.\nRozsah: 16 ~ 1024",
|
||||||
"gui.computercraft.config.monitor_renderer": "Monitorový renderer",
|
"gui.computercraft.config.monitor_renderer": "Monitorový renderer",
|
||||||
|
"gui.computercraft.config.monitor_renderer.tooltip": "Renderer, který bude použit pro monitory. Obecně by toto mělo být nastaveno na \"nejlepší\" - pokud\nmonitory mají problémy s výkonem, možná budeš chtít experimentovat s alternativními\nrenderery.\nPovolené hodnoty: BEST, TBO, VBO",
|
||||||
"gui.computercraft.config.peripheral": "Periferie",
|
"gui.computercraft.config.peripheral": "Periferie",
|
||||||
"gui.computercraft.config.peripheral.command_block_enabled": "Zapnout periferii příkazových bloků",
|
"gui.computercraft.config.peripheral.command_block_enabled": "Zapnout periferii příkazových bloků",
|
||||||
"gui.computercraft.config.peripheral.command_block_enabled.tooltip": "Zapnout podporu periferie příkazových bloků",
|
"gui.computercraft.config.peripheral.command_block_enabled.tooltip": "Zapnout podporu periferie příkazových bloků",
|
||||||
@@ -142,80 +131,94 @@
|
|||||||
"gui.computercraft.config.peripheral.modem_high_altitude_range_during_storm": "Dosah modemu (vysoká výška, špatné počasí)",
|
"gui.computercraft.config.peripheral.modem_high_altitude_range_during_storm": "Dosah modemu (vysoká výška, špatné počasí)",
|
||||||
"gui.computercraft.config.peripheral.modem_high_altitude_range_during_storm.tooltip": "Dosah bezdrátových modemů v maximální výšce v bouřlivém počasí, v metrech.\nRozsah: 0 ~ 100000",
|
"gui.computercraft.config.peripheral.modem_high_altitude_range_during_storm.tooltip": "Dosah bezdrátových modemů v maximální výšce v bouřlivém počasí, v metrech.\nRozsah: 0 ~ 100000",
|
||||||
"gui.computercraft.config.peripheral.modem_range": "Dosah modemu (vychozí)",
|
"gui.computercraft.config.peripheral.modem_range": "Dosah modemu (vychozí)",
|
||||||
|
"gui.computercraft.config.peripheral.modem_range.tooltip": "Dosah bezdrátových modemů v nízké výšce v jasném počasí, v metrech.\nRozsah: 0 ~ 100000",
|
||||||
"gui.computercraft.config.peripheral.modem_range_during_storm": "Dosah modemu (špatné počasí)",
|
"gui.computercraft.config.peripheral.modem_range_during_storm": "Dosah modemu (špatné počasí)",
|
||||||
"gui.computercraft.config.peripheral.modem_range_during_storm.tooltip": "Dosah bezdrátových modemů v nízké výšce v špatném počasí, v metrech.\nRozsah: 0 ~ 100000",
|
"gui.computercraft.config.peripheral.modem_range_during_storm.tooltip": "Dosah bezdrátových modemů v nízké výšce v špatném počasí, v metrech.\nRozsah: 0 ~ 100000",
|
||||||
"gui.computercraft.config.peripheral.monitor_bandwidth": "šířka pásma monitorů",
|
"gui.computercraft.config.peripheral.monitor_bandwidth": "šířka pásma monitorů",
|
||||||
|
"gui.computercraft.config.peripheral.monitor_bandwidth.tooltip": "Limit kolik monitorových dat může být posláno *za tick*. Poznámka:\n - Šířka pásma je měřena před kompresí, takže data zaslaná klientovi jsou\nmenší.\n - Toto ignoruje počet hráčů kterým je poslán packet. Aktualizování monitoru pro\njednoho hráče spotřebuje stejný limit šířky pásma jako posíláním 20.\n - Plně velký monitor posílá ~25 kb dat. Takže vychozí hodnota (1MB) dovoluje ~40\nmonitorům aby byly aktualizovány v jednom ticku.\nNastav na 0 pro vypnutí.\nRozsah: > 0",
|
||||||
"gui.computercraft.config.peripheral.tooltip": "Různé možnosti o periferiích.",
|
"gui.computercraft.config.peripheral.tooltip": "Různé možnosti o periferiích.",
|
||||||
|
"gui.computercraft.config.term_sizes": "Velikosti terminálu",
|
||||||
"gui.computercraft.config.term_sizes.computer": "Počítač",
|
"gui.computercraft.config.term_sizes.computer": "Počítač",
|
||||||
"gui.computercraft.config.term_sizes.computer.height": "Výška terminálu",
|
"gui.computercraft.config.term_sizes.computer.height": "Výška terminálu",
|
||||||
"gui.computercraft.config.term_sizes.computer.height.tooltip": "Rozsash: 1 ~ 255",
|
"gui.computercraft.config.term_sizes.computer.height.tooltip": "Rozsash: 1 ~ 255",
|
||||||
"gui.computercraft.config.term_sizes.computer.tooltip": "Velikost počítačového terminálu.",
|
"gui.computercraft.config.term_sizes.computer.tooltip": "Velikost počítačového terminálu.",
|
||||||
|
"gui.computercraft.config.term_sizes.computer.width": "Šířka terminálu",
|
||||||
"gui.computercraft.config.term_sizes.computer.width.tooltip": "Rozsah: 1 ~ 255",
|
"gui.computercraft.config.term_sizes.computer.width.tooltip": "Rozsah: 1 ~ 255",
|
||||||
"gui.computercraft.config.term_sizes.monitor": "Monitor",
|
"gui.computercraft.config.term_sizes.monitor": "Monitor",
|
||||||
"gui.computercraft.config.term_sizes.monitor.height": "Maximální výška monitoru",
|
"gui.computercraft.config.term_sizes.monitor.height": "Maximální výška monitoru",
|
||||||
"gui.computercraft.config.term_sizes.monitor.height.tooltip": "Rozsah: 1 ~ 32",
|
"gui.computercraft.config.term_sizes.monitor.height.tooltip": "Rozsah: 1 ~ 32",
|
||||||
|
"gui.computercraft.config.term_sizes.monitor.tooltip": "Maximální velikost monitorů (v blocích).",
|
||||||
"gui.computercraft.config.term_sizes.monitor.width": "Maximální šířka monitoru",
|
"gui.computercraft.config.term_sizes.monitor.width": "Maximální šířka monitoru",
|
||||||
"gui.computercraft.config.term_sizes.monitor.width.tooltip": "Rozsah: 1 ~ 32",
|
"gui.computercraft.config.term_sizes.monitor.width.tooltip": "Rozsah: 1 ~ 32",
|
||||||
"gui.computercraft.config.term_sizes.pocket_computer": "Kapesní počítač",
|
"gui.computercraft.config.term_sizes.pocket_computer": "Kapesní počítač",
|
||||||
"gui.computercraft.config.term_sizes.pocket_computer.height": "Výška terminálu",
|
"gui.computercraft.config.term_sizes.pocket_computer.height": "Výška terminálu",
|
||||||
|
"gui.computercraft.config.term_sizes.pocket_computer.height.tooltip": "Rozsah: 1 ~ 255",
|
||||||
"gui.computercraft.config.term_sizes.pocket_computer.tooltip": "Velikost terminálu kapesního počítače.",
|
"gui.computercraft.config.term_sizes.pocket_computer.tooltip": "Velikost terminálu kapesního počítače.",
|
||||||
"gui.computercraft.config.term_sizes.pocket_computer.width": "Šířka terminálu",
|
"gui.computercraft.config.term_sizes.pocket_computer.width": "Šířka terminálu",
|
||||||
"gui.computercraft.config.term_sizes.pocket_computer.width.tooltip": "Rozsah: 1 ~ 255",
|
"gui.computercraft.config.term_sizes.pocket_computer.width.tooltip": "Rozsah: 1 ~ 255",
|
||||||
|
"gui.computercraft.config.term_sizes.tooltip": "Nastav velikost terminálu různých počítačů.\nVětší terminály vyžadují větší šířku pásma, takže používej opatrně.",
|
||||||
"gui.computercraft.config.turtle": "Roboti",
|
"gui.computercraft.config.turtle": "Roboti",
|
||||||
"gui.computercraft.config.turtle.advanced_fuel_limit": "Limit paliva pokročilého robota",
|
"gui.computercraft.config.turtle.advanced_fuel_limit": "Limit paliva pokročilého robota",
|
||||||
"gui.computercraft.config.turtle.advanced_fuel_limit.tooltip": "Limit paliva pro pokročilé roboty.\nRozsah: > 0",
|
"gui.computercraft.config.turtle.advanced_fuel_limit.tooltip": "Limit paliva pro pokročilé roboty.\nRozsah: > 0",
|
||||||
|
"gui.computercraft.config.turtle.can_push": "Roboti můžou strkat stvoření",
|
||||||
|
"gui.computercraft.config.turtle.can_push.tooltip": "Jestli nastaveno na pravdivou hodnotu, Roboti budou odstrkávat stvoření z cesty místo zastavení pokud\nje tu místo.",
|
||||||
"gui.computercraft.config.turtle.need_fuel": "Zapnout palivo",
|
"gui.computercraft.config.turtle.need_fuel": "Zapnout palivo",
|
||||||
"gui.computercraft.config.turtle.need_fuel.tooltip": "Nastaví jestli roboti potřebují palivo pro pohyb.",
|
"gui.computercraft.config.turtle.need_fuel.tooltip": "Nastaví jestli roboti potřebují palivo pro pohyb.",
|
||||||
"gui.computercraft.config.turtle.normal_fuel_limit": "Limit paliva robotů",
|
"gui.computercraft.config.turtle.normal_fuel_limit": "Limit paliva robotů",
|
||||||
"gui.computercraft.config.turtle.normal_fuel_limit.tooltip": "Limit paliva pro roboty.\nRozsah: > 0",
|
"gui.computercraft.config.turtle.normal_fuel_limit.tooltip": "Limit paliva pro roboty.\nRozsah: > 0",
|
||||||
"gui.computercraft.config.turtle.tooltip": "Různé možnosti o robotech.",
|
"gui.computercraft.config.turtle.tooltip": "Různé možnosti o robotech.",
|
||||||
|
"gui.computercraft.config.upload_max_size": "Limit velikosti nahrávaného souboru (v bajtech)",
|
||||||
"gui.computercraft.config.upload_nag_delay": "Nahrání zpoždění nag",
|
"gui.computercraft.config.upload_nag_delay": "Nahrání zpoždění nag",
|
||||||
|
"gui.computercraft.config.upload_nag_delay.tooltip": "Zpoždění v sekundách po kterém oznámíme o nezpracovaných importech. Nastav na 0 pro vypnutí.\nRozsah: 0 ~ 60",
|
||||||
"gui.computercraft.pocket_computer_overlay": "Kapesní počítač otevřen. Zmáčkni ESC pro uzavření.",
|
"gui.computercraft.pocket_computer_overlay": "Kapesní počítač otevřen. Zmáčkni ESC pro uzavření.",
|
||||||
|
"gui.computercraft.terminal": "Počítačový terminál",
|
||||||
"gui.computercraft.tooltip.computer_id": "ID počítače: %s",
|
"gui.computercraft.tooltip.computer_id": "ID počítače: %s",
|
||||||
"gui.computercraft.tooltip.copy": "Kopírovat do schránky",
|
"gui.computercraft.tooltip.copy": "Kopírovat do schránky",
|
||||||
"gui.computercraft.tooltip.disk_id": "ID disku: %s",
|
"gui.computercraft.tooltip.disk_id": "ID disku: %s",
|
||||||
"gui.computercraft.tooltip.terminate": "Zastavit nyní běžící kód",
|
"gui.computercraft.tooltip.terminate": "Zastavit nyní běžící kód",
|
||||||
"gui.computercraft.tooltip.terminate.key": "Podrž Ctrl+T",
|
"gui.computercraft.tooltip.terminate.key": "Podrž Ctrl+T",
|
||||||
|
"gui.computercraft.tooltip.turn_off": "Vypnout tento počítač",
|
||||||
"gui.computercraft.tooltip.turn_off.key": "Zmáčkni Ctrl+S",
|
"gui.computercraft.tooltip.turn_off.key": "Zmáčkni Ctrl+S",
|
||||||
"gui.computercraft.tooltip.turn_on": "Zapnout teno počítač",
|
"gui.computercraft.tooltip.turn_on": "Zapnout teno počítač",
|
||||||
"gui.computercraft.upload.failed": "Nahrávání se nezdařilo",
|
"gui.computercraft.upload.failed": "Nahrávání se nezdařilo",
|
||||||
|
"gui.computercraft.upload.failed.computer_off": "Před nahráním souborů musíš vypnout počítač.",
|
||||||
"gui.computercraft.upload.failed.corrupted": "Soubory byly porušeny při nahrávání. Prosím zkus to znovu.",
|
"gui.computercraft.upload.failed.corrupted": "Soubory byly porušeny při nahrávání. Prosím zkus to znovu.",
|
||||||
"gui.computercraft.upload.failed.generic": "Nahrávání souborů se nezdařilo (%s)",
|
"gui.computercraft.upload.failed.generic": "Nahrávání souborů se nezdařilo (%s)",
|
||||||
"gui.computercraft.upload.failed.name_too_long": "Jména souborů pro nahrání jsou příliš dlouhá.",
|
"gui.computercraft.upload.failed.name_too_long": "Jména souborů pro nahrání jsou příliš dlouhá.",
|
||||||
"gui.computercraft.upload.failed.too_many_files": "Nelze nahrát tolik souborů.",
|
"gui.computercraft.upload.failed.too_many_files": "Nelze nahrát tolik souborů.",
|
||||||
|
"gui.computercraft.upload.failed.too_much": "Tvoje soubory jsou příliš velké pro nahrání.",
|
||||||
"gui.computercraft.upload.no_response": "Přenos souborů",
|
"gui.computercraft.upload.no_response": "Přenos souborů",
|
||||||
"gui.computercraft.upload.no_response.msg": "Tvůj počítač nepoužil tvé přenesené soubory. Budeš muset spustit program %s a zkusit to znovu.",
|
"gui.computercraft.upload.no_response.msg": "Tvůj počítač nepoužil tvé přenesené soubory. Budeš muset spustit program %s a zkusit to znovu.",
|
||||||
"item.computercraft.disk": "Disketa",
|
"item.computercraft.disk": "Disketa",
|
||||||
"item.computercraft.pocket_computer_advanced": "Pokročilý kapesní počítač",
|
"item.computercraft.pocket_computer_advanced": "Pokročilý kapesní počítač",
|
||||||
|
"item.computercraft.pocket_computer_advanced.upgraded": "Pokročilý %s kapesní počítač",
|
||||||
"item.computercraft.pocket_computer_normal": "Kapesní počítač",
|
"item.computercraft.pocket_computer_normal": "Kapesní počítač",
|
||||||
"item.computercraft.pocket_computer_normal.upgraded": "%s kapesní počítač",
|
"item.computercraft.pocket_computer_normal.upgraded": "%s kapesní počítač",
|
||||||
"item.computercraft.printed_pages": "Tisknuté stránky",
|
|
||||||
"item.computercraft.treasure_disk": "Disketa",
|
|
||||||
"gui.computercraft.config.log_computer_errors.tooltip": "Zapisovat chyby hozené periferiemi a ostatními Lua objekty. Toto usnadňuje\nautorům módů ladění problémů, ale může způsobit spam zápisů když hráči\npoužívají problematické metody.",
|
|
||||||
"gui.computercraft.config.monitor_distance": "Dálka monitoru",
|
|
||||||
"gui.computercraft.config.monitor_distance.tooltip": "Maximální dálka kde se budou monitory načítač. Toto je vychozí na standartní limit\ntile entit, ale může být prodloužen pokud chceš stavět větší monitory.\nRozsah: 16 ~ 1024",
|
|
||||||
"gui.computercraft.config.http.rules.tooltip": "Seznam pravidel které ovládají chování \"http\" API pro specifické domény nebo\nIP adresy. Každé pravidlo je položka s 'hostem' pro shodování, a také série\nvlastností. Pravidla jsou hodnocena v řádech, což znamená že dřívejší pravidla přepíšou\npozdější.\nHost může být jméno domény (\"pastebin.com\") shoda (\"*.pastebin.com\") nebo\nnotace CIDR (\"127.0.0.0/8\").\nPokud nejsou pravidla, doména je blokována.",
|
|
||||||
"gui.computercraft.config.monitor_renderer.tooltip": "Renderer, který bude použit pro monitory. Obecně by toto mělo být nastaveno na \"nejlepší\" - pokud\nmonitory mají problémy s výkonem, možná budeš chtít experimentovat s alternativními\nrenderery.\nPovolené hodnoty: BEST, TBO, VBO",
|
|
||||||
"gui.computercraft.config.term_sizes": "Velikosti terminálu",
|
|
||||||
"gui.computercraft.config.term_sizes.computer.width": "Šířka terminálu",
|
|
||||||
"gui.computercraft.config.term_sizes.monitor.tooltip": "Maximální velikost monitorů (v blocích).",
|
|
||||||
"gui.computercraft.config.term_sizes.pocket_computer.height.tooltip": "Rozsah: 1 ~ 255",
|
|
||||||
"gui.computercraft.config.term_sizes.tooltip": "Nastav velikost terminálu různých počítačů.\nVětší terminály vyžadují větší šířku pásma, takže používej opatrně.",
|
|
||||||
"gui.computercraft.config.turtle.can_push": "Roboti můžou strkat stvoření",
|
|
||||||
"gui.computercraft.config.turtle.can_push.tooltip": "Jestli nastaveno na pravdivou hodnotu, Roboti budou odstrkávat stvoření z cesty místo zastavení pokud\nje tu místo.",
|
|
||||||
"gui.computercraft.config.peripheral.modem_range.tooltip": "Dosah bezdrátových modemů v nízké výšce v jasném počasí, v metrech.\nRozsah: 0 ~ 100000",
|
|
||||||
"gui.computercraft.config.peripheral.monitor_bandwidth.tooltip": "Limit kolik monitorových dat může být posláno *za tick*. Poznámka:\n - Šířka pásma je měřena před kompresí, takže data zaslaná klientovi jsou\nmenší.\n - Toto ignoruje počet hráčů kterým je poslán packet. Aktualizování monitoru pro\njednoho hráče spotřebuje stejný limit šířky pásma jako posíláním 20.\n - Plně velký monitor posílá ~25 kb dat. Takže vychozí hodnota (1MB) dovoluje ~40\nmonitorům aby byly aktualizovány v jednom ticku.\nNastav na 0 pro vypnutí.\nRozsah: > 0",
|
|
||||||
"gui.computercraft.config.upload_nag_delay.tooltip": "Zpoždění v sekundách po kterém oznámíme o nezpracovaných importech. Nastav na 0 pro vypnutí.\nRozsah: 0 ~ 60",
|
|
||||||
"gui.computercraft.terminal": "Počítačový terminál",
|
|
||||||
"gui.computercraft.tooltip.turn_off": "Vypnout tento počítač",
|
|
||||||
"gui.computercraft.upload.failed.computer_off": "Před nahráním souborů musíš vypnout počítač.",
|
|
||||||
"gui.computercraft.upload.failed.too_much": "Tvoje soubory jsou příliš velké pro nahrání.",
|
|
||||||
"item.computercraft.pocket_computer_advanced.upgraded": "Pokročilý %s kapesní počítač",
|
|
||||||
"item.computercraft.printed_book": "Tisknutá kniha",
|
"item.computercraft.printed_book": "Tisknutá kniha",
|
||||||
"item.computercraft.printed_page": "Tisknutá stránka",
|
"item.computercraft.printed_page": "Tisknutá stránka",
|
||||||
|
"item.computercraft.printed_pages": "Tisknuté stránky",
|
||||||
|
"item.computercraft.treasure_disk": "Disketa",
|
||||||
"itemGroup.computercraft": "ComputerCraft",
|
"itemGroup.computercraft": "ComputerCraft",
|
||||||
"gui.computercraft.config.execution.max_main_computer_time.tooltip": "Ideální maximální v kterém počítač se může spustit na tick, v milisekundách.\nPoznámka, možná přejdeme přes tento limit protože tu nelze rozhodnout jak\ndlouho to bude trvat - toto se zaměřuje jako vrchní hranice průměrného času.\nRozsah: > 1",
|
"tracking_field.computercraft.avg": "%s (průměr)",
|
||||||
"gui.computercraft.config.http.bandwidth.global_upload.tooltip": "Počet bytů které můžou být nahrány za sekundu. Toto je sdíleno mezi všemi počítači (bajty/s).\nRozsah: > 1",
|
"tracking_field.computercraft.computer_tasks.name": "Úlohy",
|
||||||
"gui.computercraft.config.http.enabled.tooltip": "Zapnout \"http\" API na počítačích. Toto také vypne programy \"pastebin\" a \"wget\"\nna kterých záleží hodně uživatelům. Je doporučeno nechat toto zapnuté a použít\nmožnost konfigurace \"rules\" pro určení více jemné kontroly.",
|
"tracking_field.computercraft.count": "%s (počet)",
|
||||||
"gui.computercraft.config.http.websocket_enabled.tooltip": "Zapnout použití HTTP websocketů. Toto vyžaduje možnost \"http_enable\" aby byla zapnuta."
|
"tracking_field.computercraft.fs.name": "Operace souborového systému",
|
||||||
|
"tracking_field.computercraft.http_download.name": "Stahování HTTP",
|
||||||
|
"tracking_field.computercraft.http_requests.name": "Požadavky HTTP",
|
||||||
|
"tracking_field.computercraft.http_upload.name": "Nahrávání HTTP",
|
||||||
|
"tracking_field.computercraft.max": "%s (max)",
|
||||||
|
"tracking_field.computercraft.peripheral.name": "Periferní volání",
|
||||||
|
"tracking_field.computercraft.server_tasks.name": "Serverové úlohy",
|
||||||
|
"tracking_field.computercraft.turtle_ops.name": "Operace robotů",
|
||||||
|
"tracking_field.computercraft.websocket_incoming.name": "Přichozí websocket",
|
||||||
|
"tracking_field.computercraft.websocket_outgoing.name": "Odchozí websocket",
|
||||||
|
"upgrade.computercraft.speaker.adjective": "Hlučný",
|
||||||
|
"upgrade.computercraft.wireless_modem_advanced.adjective": "Endový",
|
||||||
|
"upgrade.computercraft.wireless_modem_normal.adjective": "Bezdrátový",
|
||||||
|
"upgrade.minecraft.crafting_table.adjective": "Tvořivý",
|
||||||
|
"upgrade.minecraft.diamond_axe.adjective": "Kácející",
|
||||||
|
"upgrade.minecraft.diamond_hoe.adjective": "Farmářský",
|
||||||
|
"upgrade.minecraft.diamond_pickaxe.adjective": "Těžební",
|
||||||
|
"upgrade.minecraft.diamond_shovel.adjective": "Kopací",
|
||||||
|
"upgrade.minecraft.diamond_sword.adjective": "Bojový"
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user