Compare commits
48 Commits
v1.19.4-1.
...
v1.19.4-1.
Author | SHA1 | Date | |
---|---|---|---|
![]() |
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
@@ -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
@@ -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/*
|
||||||
|
@@ -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,9 +37,25 @@ java {
|
|||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven("https://squiddev.cc/maven") {
|
|
||||||
|
val mainMaven = maven("https://squiddev.cc/maven") {
|
||||||
name = "SquidDev"
|
name = "SquidDev"
|
||||||
content {
|
content {
|
||||||
|
// Until https://github.com/SpongePowered/Mixin/pull/593 is merged
|
||||||
|
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("org.squiddev")
|
includeGroup("org.squiddev")
|
||||||
includeGroup("cc.tweaked")
|
includeGroup("cc.tweaked")
|
||||||
// Things we mirror
|
// Things we mirror
|
||||||
@@ -49,8 +65,6 @@ repositories {
|
|||||||
includeGroup("me.shedaniel.cloth")
|
includeGroup("me.shedaniel.cloth")
|
||||||
includeGroup("mezz.jei")
|
includeGroup("mezz.jei")
|
||||||
includeModule("com.terraformersmc", "modmenu")
|
includeModule("com.terraformersmc", "modmenu")
|
||||||
// Until https://github.com/SpongePowered/Mixin/pull/593 is merged
|
|
||||||
includeModule("org.spongepowered", "mixin")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -104,6 +118,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"
|
||||||
|
@@ -32,11 +32,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.
|
||||||
@@ -63,6 +66,7 @@ abstract class ClientJavaExec : JavaExec() {
|
|||||||
task.copyToFull(this)
|
task.copyToFull(this)
|
||||||
|
|
||||||
if (!clientDebug) systemProperty("cctest.client", "")
|
if (!clientDebug) systemProperty("cctest.client", "")
|
||||||
|
if (renderdoc) environment("LD_PRELOAD", "/usr/lib/librenderdoc.so")
|
||||||
systemProperty("cctest.gametest-report", testResults.get().asFile.absoluteFile)
|
systemProperty("cctest.gametest-report", testResults.get().asFile.absoluteFile)
|
||||||
workingDir(project.buildDir.resolve("gametest").resolve(name))
|
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):
|
||||||
|
@@ -10,7 +10,7 @@ kotlin.jvm.target.validation.mode=error
|
|||||||
|
|
||||||
# Mod properties
|
# Mod properties
|
||||||
isUnstable=false
|
isUnstable=false
|
||||||
modVersion=1.105.0
|
modVersion=1.106.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
|
||||||
|
@@ -53,11 +53,11 @@ 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.+"
|
||||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
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
@@ -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
@@ -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,6 +11,7 @@ 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.nbt.CompoundTag;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
@@ -28,12 +29,27 @@ 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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtain the model to be used when rendering a turtle peripheral.
|
||||||
|
* <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);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A basic {@link TurtleUpgradeModeller} which renders using the upgrade's {@linkplain ITurtleUpgrade#getCraftingItem()
|
* A basic {@link TurtleUpgradeModeller} which renders using the upgrade's {@linkplain ITurtleUpgrade#getCraftingItem()
|
||||||
* crafting item}.
|
* crafting item}.
|
||||||
|
@@ -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,48 @@
|
|||||||
|
// 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.
|
||||||
|
*/
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
@@ -24,6 +24,7 @@ import org.apache.logging.log4j.Logger;
|
|||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -104,7 +105,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 +128,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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")
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1,98 @@
|
|||||||
|
// 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 final class ModelTransformer {
|
||||||
|
public static final int[] ORDER = new int[]{ 3, 2, 1, 0 };
|
||||||
|
public static final int STRIDE = DefaultVertexFormat.BLOCK.getIntegerSize();
|
||||||
|
private static final int POS_OFFSET = findOffset(DefaultVertexFormat.BLOCK, DefaultVertexFormat.ELEMENT_POSITION);
|
||||||
|
|
||||||
|
private final Matrix4f transformation;
|
||||||
|
private 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 = STRIDE * (invert ? ORDER[i] : i);
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
@@ -10,6 +10,7 @@ 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;
|
||||||
import dan200.computercraft.api.turtle.TurtleSide;
|
import dan200.computercraft.api.turtle.TurtleSide;
|
||||||
|
import dan200.computercraft.client.model.turtle.ModelTransformer;
|
||||||
import dan200.computercraft.client.platform.ClientPlatformHelper;
|
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;
|
||||||
@@ -30,6 +31,7 @@ import net.minecraft.resources.ResourceLocation;
|
|||||||
import net.minecraft.util.RandomSource;
|
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 org.joml.Vector4f;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -146,16 +148,30 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
|
|||||||
renderModel(transform, renderer, lightmapCoord, overlayLight, ClientPlatformHelper.get().getModel(modelManager, modelLocation), tints);
|
renderModel(transform, renderer, lightmapCoord, overlayLight, ClientPlatformHelper.get().getModel(modelManager, modelLocation), tints);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a block model.
|
||||||
|
*
|
||||||
|
* @param transform The current matrix stack.
|
||||||
|
* @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, VertexConsumer renderer, int lightmapCoord, int overlayLight, BakedModel model, @Nullable int[] tints) {
|
private void renderModel(PoseStack transform, VertexConsumer renderer, int lightmapCoord, int overlayLight, BakedModel model, @Nullable int[] tints) {
|
||||||
random.setSeed(0);
|
|
||||||
renderQuads(transform, renderer, lightmapCoord, overlayLight, model.getQuads(null, null, random), tints);
|
|
||||||
for (var facing : DirectionUtil.FACINGS) {
|
for (var facing : DirectionUtil.FACINGS) {
|
||||||
|
random.setSeed(42);
|
||||||
renderQuads(transform, renderer, lightmapCoord, overlayLight, model.getQuads(null, facing, random), tints);
|
renderQuads(transform, renderer, lightmapCoord, overlayLight, model.getQuads(null, facing, random), tints);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
random.setSeed(42);
|
||||||
|
renderQuads(transform, renderer, lightmapCoord, overlayLight, model.getQuads(null, null, random), tints);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void renderQuads(PoseStack transform, VertexConsumer buffer, int lightmapCoord, int overlayLight, List<BakedQuad> quads, @Nullable int[] tints) {
|
private static void renderQuads(PoseStack transform, VertexConsumer buffer, int lightmapCoord, int overlayLight, List<BakedQuad> quads, @Nullable int[] tints) {
|
||||||
var matrix = transform.last();
|
var matrix = transform.last();
|
||||||
|
var inverted = matrix.pose().determinant() < 0;
|
||||||
|
|
||||||
for (var bakedquad : quads) {
|
for (var bakedquad : quads) {
|
||||||
var tint = -1;
|
var tint = -1;
|
||||||
@@ -167,7 +183,50 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
|
|||||||
var r = (float) (tint >> 16 & 255) / 255.0F;
|
var r = (float) (tint >> 16 & 255) / 255.0F;
|
||||||
var g = (float) (tint >> 8 & 255) / 255.0F;
|
var g = (float) (tint >> 8 & 255) / 255.0F;
|
||||||
var b = (float) (tint & 255) / 255.0F;
|
var b = (float) (tint & 255) / 255.0F;
|
||||||
buffer.putBulkData(matrix, bakedquad, r, g, b, lightmapCoord, overlayLight);
|
if (inverted) {
|
||||||
|
putBulkQuadInvert(buffer, matrix, bakedquad, r, g, b, lightmapCoord, overlayLight);
|
||||||
|
} else {
|
||||||
|
buffer.putBulkData(matrix, bakedquad, r, g, b, lightmapCoord, overlayLight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A version of {@link VertexConsumer#putBulkData(PoseStack.Pose, BakedQuad, float, float, float, int, int)} for
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
private static void putBulkQuadInvert(VertexConsumer buffer, PoseStack.Pose pose, BakedQuad quad, float red, float green, float blue, int lightmapCoord, int overlayLight) {
|
||||||
|
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 normal = matrix.transform(new Vector4f(dirNormal.getX(), dirNormal.getY(), dirNormal.getZ(), 0.0f)).normalize();
|
||||||
|
|
||||||
|
var vertices = quad.getVertices();
|
||||||
|
for (var vertex : ModelTransformer.ORDER) {
|
||||||
|
var i = vertex * ModelTransformer.STRIDE;
|
||||||
|
|
||||||
|
var x = Float.intBitsToFloat(vertices[i]);
|
||||||
|
var y = Float.intBitsToFloat(vertices[i + 1]);
|
||||||
|
var z = Float.intBitsToFloat(vertices[i + 2]);
|
||||||
|
var transformed = matrix.transform(new Vector4f(x, y, z, 1));
|
||||||
|
|
||||||
|
var u = Float.intBitsToFloat(vertices[i + 4]);
|
||||||
|
var v = Float.intBitsToFloat(vertices[i + 5]);
|
||||||
|
buffer.vertex(
|
||||||
|
transformed.x(), transformed.y(), transformed.z(),
|
||||||
|
red, green, blue, 1.0F, u, v, overlayLight, lightmapCoord,
|
||||||
|
normal.x(), normal.y(), normal.z()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -14,8 +14,8 @@ 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 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;
|
||||||
@@ -52,12 +52,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;
|
||||||
|
@@ -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);
|
||||||
|
@@ -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,34 @@
|
|||||||
|
// 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 java.util.Collection;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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() {
|
||||||
|
return sources.stream().flatMap(GenericMethod::getMethods).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();
|
||||||
|
@@ -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()) {
|
||||||
|
@@ -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
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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 {
|
||||||
@@ -182,9 +179,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 +191,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 +399,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.
|
||||||
*
|
*
|
||||||
|
@@ -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,19 +74,7 @@ public class TurtlePlaceCommand implements TurtleCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean deployCopiedItem(
|
public static boolean deploy(
|
||||||
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(
|
|
||||||
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
|
||||||
) {
|
) {
|
||||||
|
@@ -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).copy() : null);
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ItemStack getToolStack(ITurtleAccess turtle, TurtleSide side) {
|
||||||
|
return getUpgradeItem(turtle.getUpgradeNBTData(side));
|
||||||
|
}
|
||||||
|
|
||||||
|
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,66 +204,125 @@ 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 level = (ServerLevel) turtle.getLevel();
|
var source = player.damageSources().playerAttack(player);
|
||||||
var turtlePosition = turtle.getPosition();
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
var blockPosition = turtlePosition.relative(direction);
|
// 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 blockPosition = turtle.getPosition().relative(direction);
|
||||||
if (level.isEmptyBlock(blockPosition) || WorldUtil.isLiquidBlock(level, blockPosition)) {
|
if (level.isEmptyBlock(blockPosition) || WorldUtil.isLiquidBlock(level, blockPosition)) {
|
||||||
return TurtleCommandResult.failure("Nothing to dig here");
|
return TurtleCommandResult.failure("Nothing to dig here");
|
||||||
}
|
}
|
||||||
|
|
||||||
var turtlePlayer = TurtlePlayer.getWithPosition(turtle, turtlePosition, direction);
|
return withEquippedItem(turtle, side, direction, turtlePlayer -> {
|
||||||
turtlePlayer.loadInventory(item.copy());
|
var stack = turtlePlayer.player().getItemInHand(InteractionHand.MAIN_HAND);
|
||||||
|
|
||||||
// Check if we can break the block
|
// Right-click the block when using a shovel/hoe.
|
||||||
var breakable = checkBlockBreakable(level, blockPosition, turtlePlayer);
|
if (PlatformHelper.get().hasToolUsage(item) && TurtlePlaceCommand.deploy(stack, turtle, turtlePlayer, direction, null, null)) {
|
||||||
if (!breakable.isSuccess()) return breakable;
|
return TurtleCommandResult.success();
|
||||||
|
}
|
||||||
|
|
||||||
DropConsumer.set(level, blockPosition, TurtleUtil.dropConsumer(turtle));
|
// Check if we can break the block
|
||||||
var broken = !turtlePlayer.isBlockProtected(level, blockPosition) && turtlePlayer.player().gameMode.destroyBlock(blockPosition);
|
var breakable = checkBlockBreakable(level, blockPosition, turtlePlayer);
|
||||||
TurtleUtil.stopConsuming(turtle);
|
if (!breakable.isSuccess()) return breakable;
|
||||||
|
|
||||||
// Check spawn protection
|
// And break it!
|
||||||
return broken ? TurtleCommandResult.success() : TurtleCommandResult.failure("Cannot break protected block");
|
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");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isTriviallyBreakable(BlockGetter reader, BlockPos pos, BlockState state) {
|
private static boolean isTriviallyBreakable(BlockGetter reader, BlockPos pos, BlockState state) {
|
||||||
return state.is(ComputerCraftTags.Blocks.TURTLE_ALWAYS_BREAKABLE)
|
return
|
||||||
|
state.is(ComputerCraftTags.Blocks.TURTLE_ALWAYS_BREAKABLE)
|
||||||
// Allow breaking any "instabreak" block.
|
// Allow breaking any "instabreak" block.
|
||||||
|| state.getDestroySpeed(reader, pos) == 0;
|
|| state.getDestroySpeed(reader, pos) == 0;
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
@@ -28,6 +29,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,7 +38,7 @@ 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
|
||||||
@@ -46,9 +49,11 @@ public final class TurtleToolSerialiser implements TurtleUpgradeSerialiser<Turtl
|
|||||||
// 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
|
||||||
@@ -57,6 +62,8 @@ public final class TurtleToolSerialiser implements TurtleUpgradeSerialiser<Turtl
|
|||||||
RegistryWrappers.writeId(buffer, RegistryWrappers.ITEMS, upgrade.getCraftingItem().getItem());
|
RegistryWrappers.writeId(buffer, 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());
|
||||||
}
|
}
|
||||||
|
@@ -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));
|
||||||
|
@@ -217,5 +217,15 @@
|
|||||||
"gui.computercraft.config.http.enabled.tooltip": "Active l'API \"http\" sur les ordinateurs. Cela désactive également les programmes \"pastebin\" et \"wget\",\nsur lesquels de nombreux utilisateurs comptent. Il est recommandé de laisser cette option activée et\nd'utiliser l'option de configuration \"rules\" pour imposer un contrôle plus précis.",
|
"gui.computercraft.config.http.enabled.tooltip": "Active l'API \"http\" sur les ordinateurs. Cela désactive également les programmes \"pastebin\" et \"wget\",\nsur lesquels de nombreux utilisateurs comptent. Il est recommandé de laisser cette option activée et\nd'utiliser l'option de configuration \"rules\" pour imposer un contrôle plus précis.",
|
||||||
"gui.computercraft.config.peripheral.modem_range.tooltip": "La portée des modems sans fil à basse altitude par temps dégagé, en mètres.\nPlage : 0 ~ 100000",
|
"gui.computercraft.config.peripheral.modem_range.tooltip": "La portée des modems sans fil à basse altitude par temps dégagé, en mètres.\nPlage : 0 ~ 100000",
|
||||||
"gui.computercraft.config.peripheral.monitor_bandwidth.tooltip": "La limite de la quantité de données du moniteur pouvant être envoyées *par tick*. Note :\n - La bande passante est mesurée avant la compression, donc les données envoyées\n au client sont plus petites.\n - Cela ignore le nombre de joueurs auxquels un paquet est envoyé. La mise à jour d'un\n moniteur pour un joueur consomme la même limite de bande passante que l'envoi à 20.\n - Un moniteur de taille normale envoie ~25ko de données. Ainsi, la valeur par défaut (1Mo) permet \n à environ 40 moniteurs d'être mis à jour en un seul tick.\nMettre à 0 pour désactiver.\nPlage : > 0",
|
"gui.computercraft.config.peripheral.monitor_bandwidth.tooltip": "La limite de la quantité de données du moniteur pouvant être envoyées *par tick*. Note :\n - La bande passante est mesurée avant la compression, donc les données envoyées\n au client sont plus petites.\n - Cela ignore le nombre de joueurs auxquels un paquet est envoyé. La mise à jour d'un\n moniteur pour un joueur consomme la même limite de bande passante que l'envoi à 20.\n - Un moniteur de taille normale envoie ~25ko de données. Ainsi, la valeur par défaut (1Mo) permet \n à environ 40 moniteurs d'être mis à jour en un seul tick.\nMettre à 0 pour désactiver.\nPlage : > 0",
|
||||||
"gui.computercraft.config.http.rules.tooltip": "Une liste de règles qui contrôlent le comportement de l'API \"http\" pour des domaines\nou des IP spécifiques. Chaque règle est un élément avec un 'hôte' à comparer et une série\nde propriétés. Les règles sont évaluées dans l'ordre, ce qui signifie que les règles antérieures\nremplacent les suivantes.\nL'hôte peut être un nom de domaine (\"pastebin.com\"), un astérisque (\"*.pastebin.com\") ou\nune notation CIDR (\"127.0.0.0/8\").\nS'il n'y a pas de règles, le domaine est bloqué."
|
"gui.computercraft.config.http.rules.tooltip": "Une liste de règles qui contrôlent le comportement de l'API \"http\" pour des domaines\nou des IP spécifiques. Chaque règle est un élément avec un 'hôte' à comparer et une série\nde propriétés. Les règles sont évaluées dans l'ordre, ce qui signifie que les règles antérieures\nremplacent les suivantes.\nL'hôte peut être un nom de domaine (\"pastebin.com\"), un astérisque (\"*.pastebin.com\") ou\nune notation CIDR (\"127.0.0.0/8\").\nS'il n'y a pas de règles, le domaine est bloqué.",
|
||||||
|
"gui.computercraft.config.http.proxy.host.tooltip": "Le nom d'hôte ou l'adresse IP du serveur proxy.",
|
||||||
|
"gui.computercraft.config.http.proxy.tooltip": "Tunnelise les requêtes HTTP et websocket via un serveur proxy. Affecte uniquement\nles règles HTTP avec \"use_proxy\" défini sur true (désactivé par défaut).\nSi l'authentification est requise pour le proxy, créez un fichier \"computercraft-proxy.pw\"\ndans le même dossier que \"computercraft-server.toml\", contenant le\nnom d'utilisateur et mot de passe séparés par deux-points, par ex. \"monutilisateur:monmotdepasse\". Pour\nProxy SOCKS4, seul le nom d'utilisateur est requis.",
|
||||||
|
"gui.computercraft.config.upload_max_size.tooltip": "La taille limite de téléversement de fichier, en octets. Doit être compris entre 1 Kio et 16 Mio.\nGardez à l'esprit que les téléversements sont traités en un seul clic - les fichiers volumineux ou\nde mauvaises performances réseau peuvent bloquer le thread du réseau. Et attention à l'espace disque !\nPlage : 1024 ~ 16777216",
|
||||||
|
"gui.computercraft.config.http.proxy": "Proxy",
|
||||||
|
"gui.computercraft.config.http.proxy.host": "Nom d'hôte",
|
||||||
|
"gui.computercraft.config.http.proxy.port": "Port",
|
||||||
|
"gui.computercraft.config.http.proxy.port.tooltip": "Le port du serveur proxy.\nPlage : 1 ~ 65536",
|
||||||
|
"gui.computercraft.config.http.proxy.type": "Type de proxy",
|
||||||
|
"gui.computercraft.config.http.proxy.type.tooltip": "Le type de proxy à utiliser.\nValeurs autorisées : HTTP, HTTPS, SOCKS4, SOCKS5",
|
||||||
|
"gui.computercraft.config.upload_max_size": "Taille limite de téléversement de fichiers (octets)"
|
||||||
}
|
}
|
||||||
|
@@ -115,5 +115,85 @@
|
|||||||
"upgrade.minecraft.diamond_pickaxe.adjective": "Добывающая",
|
"upgrade.minecraft.diamond_pickaxe.adjective": "Добывающая",
|
||||||
"upgrade.minecraft.diamond_shovel.adjective": "Копающая",
|
"upgrade.minecraft.diamond_shovel.adjective": "Копающая",
|
||||||
"upgrade.minecraft.diamond_sword.adjective": "Боевая",
|
"upgrade.minecraft.diamond_sword.adjective": "Боевая",
|
||||||
"gui.computercraft.pocket_computer_overlay": "Карманный компьютер открыт. Чтобы закрыть, нажми ESC."
|
"gui.computercraft.pocket_computer_overlay": "Карманный компьютер открыт. Чтобы закрыть, нажми ESC.",
|
||||||
|
"gui.computercraft.config.command_require_creative.tooltip": "Требовать творческий режим и права оператора для взаимодействия с\nкомандными компьютерами. Это поведение по умолчанию для Командных блоков ванильной игры.",
|
||||||
|
"gui.computercraft.config.default_computer_settings.tooltip": "Разделенный запятыми список системных настроек по умолчанию на новых компьютерах.\nНапример: \"shell.autocomplete=false,lua.autocomplete=false,edit.autocomplete=false\"\nотключит всё автодополнение.",
|
||||||
|
"gui.computercraft.config.execution.computer_threads.tooltip": "Устанавливает количество потоков, на которых работают компьютеры. Большее число\nозначает, что больше компьютеров сможет работать одновременно, но может привести к лагу.\nОбратите внимание, что некоторые моды могут не работать с более чем одним потоком. Используйте с осторожностью.\nОграничение: > 1",
|
||||||
|
"gui.computercraft.config.execution.max_main_computer_time.tooltip": "Идеальный максимум времени, которое отведено компьютеру на выполнение задач, в миллисекундах.\nМы вполне возможно выйдем за этот лимит, так как невозможно предсказать сколько\nвремени будет затрачено на выполнение задач, это лишь верхний лимит среднего значения времени.\nОграничение: > 1",
|
||||||
|
"gui.computercraft.config.execution.max_main_global_time.tooltip": "Максимум времени, которое может быть потрачено на выполнение задач за один тик, в \nмиллисекундах. \nМы вполне возможно выйдем за этот лимит, так как невозможно предсказать сколько\nвремени будет затрачено на выполнение задач, это лишь верхний лимит среднего значения времени.\nОграничение: > 1",
|
||||||
|
"gui.computercraft.config.http.bandwidth.global_download": "Глобальный лимит на скачивание",
|
||||||
|
"gui.computercraft.config.http.bandwidth.global_download.tooltip": "Количество байтов, которое можно скачать за секунду. Все компьютеры делят эту пропускную способность. (байты в секунду)\nОграничение: > 1",
|
||||||
|
"gui.computercraft.config.http.bandwidth.global_upload.tooltip": "Количество байтов, которое можно загрузить за секунду. Все компьютеры делят эту пропускную способность. (байты в секунду)\nОграничение: > 1",
|
||||||
|
"tracking_field.computercraft.http_requests.name": "HTTP запросы",
|
||||||
|
"tracking_field.computercraft.turtle_ops.name": "Операции Черепашек",
|
||||||
|
"gui.computercraft.config.http.enabled.tooltip": "Включить API \"http\" на Компьютерах. Это также отключает программы \"pastebin\" и \"wget\", \nкоторые нужны многим пользователям. Рекомендуется оставить это включенным и использовать \nконфиг \"rules\" для более тонкой настройки.",
|
||||||
|
"gui.computercraft.config.http.max_websockets.tooltip": "Количество одновременно открытых веб-сокетов, которые может иметь компьютер. Установите на 0 для неограниченных веб-сокетов.\nОграничение: > 1",
|
||||||
|
"gui.computercraft.config.term_sizes": "Размер терминала",
|
||||||
|
"gui.computercraft.config.term_sizes.computer.height": "Высота терминала",
|
||||||
|
"gui.computercraft.config.term_sizes.monitor.height": "Максимальная высота монитора",
|
||||||
|
"gui.computercraft.config.term_sizes.monitor.width.tooltip": "Ограничение: 1 ~ 32",
|
||||||
|
"gui.computercraft.config.turtle.advanced_fuel_limit": "Лимит топлива Продвинутых Черепашек",
|
||||||
|
"gui.computercraft.config.turtle.need_fuel.tooltip": "Устанавливает, нуждаются ли Черепашки в топливе для передвижения.",
|
||||||
|
"gui.computercraft.terminal": "Компьютерный терминал",
|
||||||
|
"tracking_field.computercraft.computer_tasks.name": "Задачи",
|
||||||
|
"tracking_field.computercraft.server_tasks.name": "Серверные задачи",
|
||||||
|
"gui.computercraft.upload.no_response": "Перенос файлов",
|
||||||
|
"tracking_field.computercraft.avg": "%s (среднее)",
|
||||||
|
"gui.computercraft.config.command_require_creative": "Для использования командных компьютеров нужен творческий режим",
|
||||||
|
"gui.computercraft.config.computer_space_limit": "Лимит места на компьютерах (в байтах)",
|
||||||
|
"gui.computercraft.config.computer_space_limit.tooltip": "Лимит места на дисках компьютеров и черепашек, в байтах.",
|
||||||
|
"gui.computercraft.config.default_computer_settings": "Настройки Компьютера по умолчанию",
|
||||||
|
"gui.computercraft.config.disable_lua51_features": "Отключить функции Lua 5.1",
|
||||||
|
"gui.computercraft.config.disable_lua51_features.tooltip": "Поставьте, чтобы отключить функции из Lua 5.1, которые будут убраны в будущих\nобновлениях. Полезно для того, чтобы улучшить совместимость вперед ваших программ.",
|
||||||
|
"gui.computercraft.config.execution": "Выполнение",
|
||||||
|
"gui.computercraft.config.execution.computer_threads": "Потоки компьютера",
|
||||||
|
"gui.computercraft.config.execution.max_main_global_time": "Глобальный лимит времени на тик сервера",
|
||||||
|
"gui.computercraft.config.execution.tooltip": "Контролирует поведение выполнения задач компьютеров. Эта настройка преднезначается для \nтонкой настройки серверов, и в основном не должна быть изменена.",
|
||||||
|
"gui.computercraft.config.floppy_space_limit": "Лимит места на дискетах (байты)",
|
||||||
|
"gui.computercraft.config.floppy_space_limit.tooltip": "Лимит места для хранения информации на дискетах, в байтах.",
|
||||||
|
"gui.computercraft.config.http": "HTTP",
|
||||||
|
"gui.computercraft.config.http.bandwidth": "Пропускная способность",
|
||||||
|
"gui.computercraft.config.http.bandwidth.global_upload": "Глобальный лимит загрузки",
|
||||||
|
"gui.computercraft.config.http.bandwidth.tooltip": "Ограничивает пропускную способность, используемую компьютерами.",
|
||||||
|
"gui.computercraft.config.http.enabled": "Включить HTTP API",
|
||||||
|
"gui.computercraft.config.http.max_requests": "Максимум одновременных запросов",
|
||||||
|
"gui.computercraft.config.http.max_requests.tooltip": "Количество http-запросов, которые компьютер может сделать одновременно. Дополнительные запросы \nбудут поставлены в очередь, и отправлены когда существующие запросы будут выполнены. Установите на 0 для \nнеограниченных запросов.\nОграничение: > 0",
|
||||||
|
"gui.computercraft.config.http.max_websockets": "Максимум одновременных веб-сокетов",
|
||||||
|
"gui.computercraft.config.term_sizes.computer": "Компьютер",
|
||||||
|
"gui.computercraft.config.term_sizes.computer.height.tooltip": "Ограничение: 1 ~ 255",
|
||||||
|
"gui.computercraft.config.term_sizes.computer.tooltip": "Размер терминала на компьютерах.",
|
||||||
|
"gui.computercraft.config.term_sizes.computer.width": "Ширина терминала",
|
||||||
|
"gui.computercraft.config.term_sizes.computer.width.tooltip": "Ограничение: 1 ~ 255",
|
||||||
|
"gui.computercraft.config.term_sizes.monitor": "Монитор",
|
||||||
|
"gui.computercraft.config.term_sizes.monitor.height.tooltip": "Ограничение: 1 ~ 32",
|
||||||
|
"gui.computercraft.config.term_sizes.monitor.tooltip": "Максимальный размер мониторов (в блоках).",
|
||||||
|
"gui.computercraft.config.term_sizes.monitor.width": "Максимальная ширина мониторов",
|
||||||
|
"gui.computercraft.config.term_sizes.pocket_computer.height": "Высота терминала",
|
||||||
|
"gui.computercraft.config.term_sizes.pocket_computer.height.tooltip": "Ограничение: 1 ~ 255",
|
||||||
|
"gui.computercraft.config.term_sizes.pocket_computer.width": "Ширина терминала",
|
||||||
|
"gui.computercraft.config.term_sizes.pocket_computer.width.tooltip": "Ограничение: 1 ~ 255",
|
||||||
|
"gui.computercraft.config.turtle": "Черепашки",
|
||||||
|
"gui.computercraft.config.turtle.advanced_fuel_limit.tooltip": "Лимит топлива для Продвинутых Черепашек.\nОграничение: > 0",
|
||||||
|
"gui.computercraft.config.turtle.need_fuel": "Включить механику топлива",
|
||||||
|
"gui.computercraft.config.turtle.normal_fuel_limit": "Лимит топлива Черепашек",
|
||||||
|
"gui.computercraft.config.turtle.normal_fuel_limit.tooltip": "Лимит топлива для Черепашек.\nОграничение: > 0",
|
||||||
|
"gui.computercraft.config.turtle.tooltip": "Разные настройки, связанные с черепашками.",
|
||||||
|
"gui.computercraft.config.http.proxy.port": "Порт",
|
||||||
|
"gui.computercraft.config.http.proxy.port.tooltip": "Порт прокси-сервера.\nДиапазон: 1 ~ 65536",
|
||||||
|
"gui.computercraft.config.http.proxy.host": "Имя хоста",
|
||||||
|
"gui.computercraft.config.http.proxy": "Proxy",
|
||||||
|
"gui.computercraft.config.http.proxy.host.tooltip": "Имя хоста или IP-адрес прокси-сервера.",
|
||||||
|
"gui.computercraft.config.http.proxy.tooltip": "Туннелирует HTTP-запросы и запросы websocket через прокси-сервер. Влияет только на HTTP\nправила с параметром \"use_proxy\" в значении true (отключено по умолчанию).\nЕсли для прокси-сервера требуется аутентификация, создайте \"computercraft-proxy.pw\"\nфайл в том же каталоге, что и \"computercraft-server.toml\", содержащий имя\nпользователя и пароль, разделенные двоеточием, например \"myuser:mypassword\". Для\nпрокси-серверов SOCKS4 требуется только имя пользователя.",
|
||||||
|
"gui.computercraft.config.http.proxy.type": "Тип прокси-сервера",
|
||||||
|
"gui.computercraft.config.http.proxy.type.tooltip": "Тип используемого прокси-сервера.\nДопустимые значения: HTTP, HTTPS, SOCKS4, SOCKS5",
|
||||||
|
"gui.computercraft.upload.no_response.msg": "Ваш компьютер не использовал переданные вами файлы. Возможно, вам потребуется запустить программу %s и повторить попытку.",
|
||||||
|
"tracking_field.computercraft.max": "%s (максимальное)",
|
||||||
|
"tracking_field.computercraft.count": "%s (количество)",
|
||||||
|
"gui.computercraft.config.http.rules": "Разрешающие/запрещающие правила",
|
||||||
|
"gui.computercraft.config.http.websocket_enabled": "Включить веб-сокеты",
|
||||||
|
"gui.computercraft.config.http.websocket_enabled.tooltip": "Включить использование http веб-сокетов. Для этого необходимо, чтобы параметр «http_enable» был true.",
|
||||||
|
"gui.computercraft.config.log_computer_errors": "Регистрировать ошибки компьютера",
|
||||||
|
"gui.computercraft.config.log_computer_errors.tooltip": "Регистрировать исключения, вызванные периферийными устройствами и другими объектами Lua. Это облегчает\nдля авторам модов устранение проблем, но может привести к спаму в логах, если люди будут использовать\nглючные методы.",
|
||||||
|
"gui.computercraft.config.maximum_open_files": "Максимальное количество файлов, открытых на одном компьютере",
|
||||||
|
"gui.computercraft.config.http.rules.tooltip": "Список правил, которые контролируют поведение «http» API для определенных доменов или\nIP-адресов. Каждое правило представляет собой элемент с «узлом» для сопоставления и набором\nсвойств. Правила оцениваются по порядку, то есть более ранние правила перевешивают\nболее поздние.\nХост может быть доменным именем (\"pastebin.com\"), wildcard-сертификатом (\"*.pastebin.com\") или\nнотацией CIDR (\"127.0.0.0/8\").\nЕсли правил нет, домен блокируется."
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"parent": "minecraft:block/block",
|
||||||
|
"render_type": "cutout",
|
||||||
|
"textures": {
|
||||||
|
"particle": "#front"
|
||||||
|
},
|
||||||
|
"display": {
|
||||||
|
"firstperson_righthand": {
|
||||||
|
"rotation": [ 0, 135, 0 ],
|
||||||
|
"translation": [ 0, 0, 0 ],
|
||||||
|
"scale": [ 0.40, 0.40, 0.40 ]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"from": [ 0, 0, 0 ],
|
||||||
|
"to": [ 16, 16, 16 ],
|
||||||
|
"faces": {
|
||||||
|
"down": { "texture": "#top", "cullface": "down" },
|
||||||
|
"up": { "texture": "#top", "cullface": "up" },
|
||||||
|
"north": { "texture": "#front", "cullface": "north" },
|
||||||
|
"south": { "texture": "#side", "cullface": "south" },
|
||||||
|
"west": { "texture": "#side", "cullface": "west" },
|
||||||
|
"east": { "texture": "#side", "cullface": "east" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"from": [ 0, 0, 0 ],
|
||||||
|
"to": [ 16, 16, 16 ],
|
||||||
|
"faces": {
|
||||||
|
"north": {
|
||||||
|
"texture": "#cursor",
|
||||||
|
"cullface": "north",
|
||||||
|
"forge_data": {"block_light": 15, "sky_light": 15}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@@ -0,0 +1,3 @@
|
|||||||
|
SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||||
|
|
||||||
|
SPDX-License-Identifier: MPL-2.0
|
Before Width: | Height: | Size: 476 B |
Before Width: | Height: | Size: 417 B |
After Width: | Height: | Size: 110 B |
Before Width: | Height: | Size: 534 B |
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"animation": {
|
|
||||||
"frametime": 8,
|
|
||||||
"frames": [ 0, 1 ]
|
|
||||||
}
|
|
||||||
}
|
|
Before Width: | Height: | Size: 426 B |
Before Width: | Height: | Size: 214 B |
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"animation": {
|
|
||||||
"frametime": 8,
|
|
||||||
"frames": [ 0, 1 ]
|
|
||||||
}
|
|
||||||
}
|
|
Before Width: | Height: | Size: 199 B |
After Width: | Height: | Size: 89 B |
Before Width: | Height: | Size: 1004 B After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 983 B After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 129 B |
@@ -0,0 +1,3 @@
|
|||||||
|
SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||||
|
|
||||||
|
SPDX-License-Identifier: MPL-2.0
|
After Width: | Height: | Size: 129 B |
@@ -0,0 +1,3 @@
|
|||||||
|
SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||||
|
|
||||||
|
SPDX-License-Identifier: MPL-2.0
|