mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-10-20 00:17:38 +00:00
Compare commits
73 Commits
v1.20-1.10
...
v1.19.4-1.
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2a04fb71fd | ||
![]() |
df61389304 | ||
![]() |
b6632c9ed9 | ||
![]() |
41b6711b38 | ||
![]() |
02f0b7ec14 | ||
![]() |
a9d31cd3c6 | ||
![]() |
43744b0e85 | ||
![]() |
55510c42db | ||
![]() |
57a944fd90 | ||
![]() |
940f59b116 | ||
![]() |
dd08d1ec8e | ||
![]() |
90ed0b24e7 | ||
![]() |
0ad399a528 | ||
![]() |
1b88213eca | ||
![]() |
eef05b9854 | ||
![]() |
24d74f5c80 | ||
![]() |
c2988366d8 | ||
![]() |
ec0765ead1 | ||
![]() |
b94e34f372 | ||
![]() |
af3263dec2 | ||
![]() |
7f25c9a66b | ||
![]() |
9ca3efff3c | ||
![]() |
aaf8c248a8 | ||
![]() |
df26cd267a | ||
![]() |
8914b78816 | ||
![]() |
9ea7f45fa7 | ||
![]() |
d351bc33c6 | ||
![]() |
5d71770931 | ||
![]() |
4bbde8c50c | ||
![]() |
cc8c1f38e7 | ||
![]() |
cab9c9772a | ||
![]() |
e337a63712 | ||
![]() |
efa92b741b | ||
![]() |
a91ac6f214 | ||
![]() |
943a9406b1 | ||
![]() |
0b2bb5e7b5 | ||
![]() |
8708048b6e | ||
![]() |
d138d9c4a5 | ||
![]() |
f54cb8a432 | ||
![]() |
94f5ede75a | ||
![]() |
1977556da4 | ||
![]() |
9eabb29999 | ||
![]() |
ecf880ed82 | ||
![]() |
655d5aeca8 | ||
![]() |
34f41c4039 | ||
![]() |
f5b16261cc | ||
![]() |
7eb3b691da | ||
![]() |
910a63214e | ||
![]() |
591a7eca23 | ||
![]() |
a29a516a3f | ||
![]() |
4a5e03c11a | ||
![]() |
50d460624f | ||
![]() |
bc500df921 | ||
![]() |
4accda6b8e | ||
![]() |
54ab98473f | ||
![]() |
7ffdbb2316 | ||
![]() |
672c2cf029 | ||
![]() |
c3bdb0440e | ||
![]() |
88f0c44152 | ||
![]() |
c8523bf479 | ||
![]() |
953372b1b7 | ||
![]() |
36b9f4ec55 | ||
![]() |
ccfed0059b | ||
![]() |
7b4ba11fb4 | ||
![]() |
8ccd5a560c | ||
![]() |
0f866836a0 | ||
![]() |
df591cd7c6 | ||
![]() |
c7f3d4f45d | ||
![]() |
77ac04cb7a | ||
![]() |
201df7e987 | ||
![]() |
5722e51735 | ||
![]() |
7a291619ab | ||
![]() |
4b9b19b02d |
44
.github/workflows/main-ci.yml
vendored
44
.github/workflows/main-ci.yml
vendored
@@ -8,16 +8,16 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Clone repository
|
- name: 📥 Clone repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Set up Java
|
- name: 📥 Set up Java
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
java-version: 17
|
java-version: 17
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
|
|
||||||
- name: Setup Gradle
|
- name: 📥 Setup Gradle
|
||||||
uses: gradle/gradle-build-action@v2
|
uses: gradle/gradle-build-action@v2
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ !startsWith(github.ref, 'refs/heads/mc-') }}
|
cache-read-only: ${{ !startsWith(github.ref, 'refs/heads/mc-') }}
|
||||||
@@ -27,39 +27,45 @@ jobs:
|
|||||||
mkdir -p ~/.gradle
|
mkdir -p ~/.gradle
|
||||||
echo "org.gradle.daemon=false" >> ~/.gradle/gradle.properties
|
echo "org.gradle.daemon=false" >> ~/.gradle/gradle.properties
|
||||||
|
|
||||||
- name: Build with Gradle
|
- name: ⚒️ Build
|
||||||
run: |
|
run: ./gradlew assemble || ./gradlew assemble
|
||||||
./gradlew assemble || ./gradlew assemble
|
|
||||||
./gradlew downloadAssets || ./gradlew downloadAssets
|
|
||||||
./gradlew build
|
|
||||||
|
|
||||||
- name: Run client tests
|
- name: 💡 Lint
|
||||||
|
uses: pre-commit/action@v3.0.0
|
||||||
|
|
||||||
|
- name: 🧪 Run tests
|
||||||
|
run: ./gradlew test validateMixinNames checkChangelog
|
||||||
|
|
||||||
|
- name: 📥 Download assets for game tests
|
||||||
|
run: ./gradlew downloadAssets || ./gradlew downloadAssets
|
||||||
|
|
||||||
|
- name: 🧪 Run integration tests
|
||||||
|
run: ./gradlew runGametest
|
||||||
|
|
||||||
|
- name: 🧪 Run client tests
|
||||||
run: ./gradlew runGametestClient # Not checkClient, as no point running rendering tests.
|
run: ./gradlew runGametestClient # Not checkClient, as no point running rendering tests.
|
||||||
# These are a little flaky on GH actions: its useful to run them, but don't break the build.
|
# These are a little flaky on GH actions: its useful to run them, but don't break the build.
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
|
||||||
- name: Prepare Jars
|
- name: 🧪 Parse test reports
|
||||||
|
run: ./tools/parse-reports.py
|
||||||
|
if: ${{ failure() }}
|
||||||
|
|
||||||
|
- name: 📦 Prepare Jars
|
||||||
run: |
|
run: |
|
||||||
# Find the main jar and append the git hash onto it.
|
# Find the main jar and append the git hash onto it.
|
||||||
mkdir -p jars
|
mkdir -p jars
|
||||||
find projects/forge/build/libs projects/fabric/build/libs -type f -regex '.*[0-9.]+\(-SNAPSHOT\)?\.jar$' -exec bash -c 'cp {} "jars/$(basename {} .jar)-$(git rev-parse HEAD).jar"' \;
|
find projects/forge/build/libs projects/fabric/build/libs -type f -regex '.*[0-9.]+\(-SNAPSHOT\)?\.jar$' -exec bash -c 'cp {} "jars/$(basename {} .jar)-$(git rev-parse HEAD).jar"' \;
|
||||||
|
|
||||||
- name: Upload Jar
|
- name: 📤 Upload Jar
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: CC-Tweaked
|
name: CC-Tweaked
|
||||||
path: ./jars
|
path: ./jars
|
||||||
|
|
||||||
- name: Upload coverage
|
- name: 📤 Upload coverage
|
||||||
uses: codecov/codecov-action@v3
|
uses: codecov/codecov-action@v3
|
||||||
|
|
||||||
- name: Parse test reports
|
|
||||||
run: ./tools/parse-reports.py
|
|
||||||
if: ${{ failure() }}
|
|
||||||
|
|
||||||
- name: Run linters
|
|
||||||
uses: pre-commit/action@v3.0.0
|
|
||||||
|
|
||||||
build-core:
|
build-core:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
1
.github/workflows/make-doc.yml
vendored
1
.github/workflows/make-doc.yml
vendored
@@ -4,6 +4,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- mc-1.19.x
|
- mc-1.19.x
|
||||||
|
- mc-1.20.x
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
make_doc:
|
make_doc:
|
||||||
|
@@ -6,6 +6,7 @@ Upstream-Contact: Jonathan Coates <git@squiddev.cc>
|
|||||||
Files:
|
Files:
|
||||||
projects/common/src/main/resources/assets/computercraft/sounds.json
|
projects/common/src/main/resources/assets/computercraft/sounds.json
|
||||||
projects/common/src/main/resources/assets/computercraft/sounds/empty.ogg
|
projects/common/src/main/resources/assets/computercraft/sounds/empty.ogg
|
||||||
|
projects/common/src/testMod/resources/data/cctest/computercraft/turtle_upgrades/*
|
||||||
projects/common/src/testMod/resources/data/cctest/structures/*
|
projects/common/src/testMod/resources/data/cctest/structures/*
|
||||||
projects/fabric/src/generated/*
|
projects/fabric/src/generated/*
|
||||||
projects/forge/src/generated/*
|
projects/forge/src/generated/*
|
||||||
@@ -47,6 +48,7 @@ License: MPL-2.0
|
|||||||
|
|
||||||
Files:
|
Files:
|
||||||
doc/logo.png
|
doc/logo.png
|
||||||
|
doc/logo-darkmode.png
|
||||||
projects/common/src/main/resources/assets/computercraft/models/*
|
projects/common/src/main/resources/assets/computercraft/models/*
|
||||||
projects/common/src/main/resources/assets/computercraft/textures/*
|
projects/common/src/main/resources/assets/computercraft/textures/*
|
||||||
projects/common/src/main/resources/pack.mcmeta
|
projects/common/src/main/resources/pack.mcmeta
|
||||||
|
@@ -6,7 +6,7 @@ SPDX-License-Identifier: MPL-2.0
|
|||||||
|
|
||||||
# Contributing to CC: Tweaked
|
# Contributing to CC: Tweaked
|
||||||
As with many open source projects, CC: Tweaked thrives on contributions from other people! This document (hopefully)
|
As with many open source projects, CC: Tweaked thrives on contributions from other people! This document (hopefully)
|
||||||
provides an introduction as to how to get started in helping out.
|
provides an introduction as to how to get started with helping out.
|
||||||
|
|
||||||
If you've any other questions, [just ask the community][community] or [open an issue][new-issue].
|
If you've any other questions, [just ask the community][community] or [open an issue][new-issue].
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ automatically with GitHub, so please don't submit PRs adding/changing translatio
|
|||||||
## Setting up a development environment
|
## Setting up a development environment
|
||||||
In order to develop CC: Tweaked, you'll need to download the source code and then run it.
|
In order to develop CC: Tweaked, you'll need to download the source code and then run it.
|
||||||
|
|
||||||
- Make sure you've got the following software instealled:
|
- Make sure you've got the following software installed:
|
||||||
- Java Development Kit (JDK) installed. This can be downloaded from [Adoptium].
|
- Java Development Kit (JDK) installed. This can be downloaded from [Adoptium].
|
||||||
- [Git](https://git-scm.com/).
|
- [Git](https://git-scm.com/).
|
||||||
- If you want to work on documentation, [NodeJS][node].
|
- If you want to work on documentation, [NodeJS][node].
|
||||||
@@ -51,10 +51,10 @@ If you want to run CC:T in a normal Minecraft instance, run `./gradlew assemble`
|
|||||||
## Developing CC: Tweaked
|
## Developing CC: Tweaked
|
||||||
Before making any major changes to CC: Tweaked, I'd recommend you have a read of the [the architecture
|
Before making any major changes to CC: Tweaked, I'd recommend you have a read of the [the architecture
|
||||||
document][architecture] first. While it's not a comprehensive document, it gives a good hint of where you should start
|
document][architecture] first. While it's not a comprehensive document, it gives a good hint of where you should start
|
||||||
looking to make your changes. As always, if you're not sure [do ask the community][community]!
|
looking to make your changes. As always, if you're not sure, [do ask the community][community]!
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
When making larger changes, it's may be useful to write a test to make sure your code works as expected.
|
When making larger changes, it may be useful to write a test to make sure your code works as expected.
|
||||||
|
|
||||||
CC: Tweaked has several test suites, each designed to test something different:
|
CC: Tweaked has several test suites, each designed to test something different:
|
||||||
|
|
||||||
@@ -91,11 +91,11 @@ file.
|
|||||||
|
|
||||||
Documentation is built using [illuaminate] which, while not currently documented (somewhat ironic), is largely the same
|
Documentation is built using [illuaminate] which, while not currently documented (somewhat ironic), is largely the same
|
||||||
as [ldoc][ldoc]. Documentation comments are written in Markdown, though note that we do not support many GitHub-specific
|
as [ldoc][ldoc]. Documentation comments are written in Markdown, though note that we do not support many GitHub-specific
|
||||||
markdown features - if you can, do check what the documentation looks like locally!
|
markdown features. If you can, do check what the documentation looks like locally!
|
||||||
|
|
||||||
When writing long-form documentation (such as the guides in [doc/guides](doc/guides)), I find it useful to tell a
|
When writing long-form documentation (such as the guides in [doc/guides](doc/guides)), I find it useful to tell a
|
||||||
narrative. Think of what you want the user to learn or achieve, then start introducing a simple concept and then talk
|
narrative. Think of what you want the user to learn or achieve, then start introducing a simple concept, and then talk
|
||||||
about how you can build on that, until you've covered everything!
|
about how you can build on that until you've covered everything!
|
||||||
|
|
||||||
[new-issue]: https://github.com/cc-tweaked/CC-Tweaked/issues/new/choose "Create a new issue"
|
[new-issue]: https://github.com/cc-tweaked/CC-Tweaked/issues/new/choose "Create a new issue"
|
||||||
[community]: README.md#community "Get in touch with the community."
|
[community]: README.md#community "Get in touch with the community."
|
||||||
|
22
README.md
22
README.md
@@ -4,7 +4,12 @@ SPDX-FileCopyrightText: 2017 The CC: Tweaked Developers
|
|||||||
SPDX-License-Identifier: MPL-2.0
|
SPDX-License-Identifier: MPL-2.0
|
||||||
-->
|
-->
|
||||||
|
|
||||||
# 
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="./doc/logo-darkmode.png">
|
||||||
|
<source media="(prefers-color-scheme: light)" srcset="./doc/logo.png">
|
||||||
|
<img alt="CC: Tweaked" src="./doc/logo.png">
|
||||||
|
</picture>
|
||||||
|
|
||||||
[](https://github.com/cc-tweaked/CC-Tweaked/actions "Current build status")
|
[](https://github.com/cc-tweaked/CC-Tweaked/actions "Current build status")
|
||||||
[][CurseForge]
|
[][CurseForge]
|
||||||
[][Modrinth]
|
[][Modrinth]
|
||||||
@@ -44,7 +49,7 @@ repositories {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// Vanilla (i.e. for multi-loader systems)
|
// Vanilla (i.e. for multi-loader systems)
|
||||||
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-common-api")
|
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-common-api:$cctVersion")
|
||||||
|
|
||||||
// Forge Gradle
|
// Forge Gradle
|
||||||
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-core-api:$cctVersion")
|
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-core-api:$cctVersion")
|
||||||
@@ -57,6 +62,19 @@ dependencies {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
When using ForgeGradle, you may also need to add the following:
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
minecraft {
|
||||||
|
runs {
|
||||||
|
configureEach {
|
||||||
|
property 'mixin.env.remapRefMap', 'true'
|
||||||
|
property 'mixin.env.refMapRemappingFile', "${buildDir}/createSrgToMcp/output.srg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
You should also be careful to only use classes within the `dan200.computercraft.api` package. Non-API classes are
|
You should also be careful to only use classes within the `dan200.computercraft.api` package. Non-API classes are
|
||||||
subject to change at any point. If you depend on functionality outside the API, file an issue, and we can look into
|
subject to change at any point. If you depend on functionality outside the API, file an issue, and we can look into
|
||||||
exposing more features.
|
exposing more features.
|
||||||
|
@@ -10,8 +10,9 @@ import cc.tweaked.gradle.IdeaRunConfigurations
|
|||||||
import cc.tweaked.gradle.MinecraftConfigurations
|
import cc.tweaked.gradle.MinecraftConfigurations
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("cc-tweaked.java-convention")
|
|
||||||
id("net.minecraftforge.gradle")
|
id("net.minecraftforge.gradle")
|
||||||
|
// We must apply java-convention after Forge, as we need the fg extension to be present.
|
||||||
|
id("cc-tweaked.java-convention")
|
||||||
id("org.parchmentmc.librarian.forgegradle")
|
id("org.parchmentmc.librarian.forgegradle")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -37,22 +37,37 @@ java {
|
|||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven("https://squiddev.cc/maven") {
|
|
||||||
|
val mainMaven = maven("https://squiddev.cc/maven") {
|
||||||
name = "SquidDev"
|
name = "SquidDev"
|
||||||
content {
|
content {
|
||||||
includeGroup("org.squiddev")
|
|
||||||
includeGroup("cc.tweaked")
|
|
||||||
// Things we mirror
|
|
||||||
includeGroup("dev.architectury")
|
|
||||||
includeGroup("maven.modrinth")
|
|
||||||
includeGroup("me.shedaniel")
|
|
||||||
includeGroup("me.shedaniel.cloth")
|
|
||||||
includeGroup("mezz.jei")
|
|
||||||
includeModule("com.terraformersmc", "modmenu")
|
|
||||||
// Until https://github.com/SpongePowered/Mixin/pull/593 is merged
|
// Until https://github.com/SpongePowered/Mixin/pull/593 is merged
|
||||||
includeModule("org.spongepowered", "mixin")
|
includeModule("org.spongepowered", "mixin")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exclusiveContent {
|
||||||
|
forRepositories(mainMaven)
|
||||||
|
|
||||||
|
// Include the ForgeGradle repository if present. This requires that ForgeGradle is already present, which we
|
||||||
|
// enforce in our Forge overlay.
|
||||||
|
val fg =
|
||||||
|
project.extensions.findByType(net.minecraftforge.gradle.userdev.DependencyManagementExtension::class.java)
|
||||||
|
if (fg != null) forRepositories(fg.repository)
|
||||||
|
|
||||||
|
filter {
|
||||||
|
includeGroup("cc.tweaked")
|
||||||
|
includeModule("org.squiddev", "Cobalt")
|
||||||
|
// Things we mirror
|
||||||
|
includeGroup("dev.architectury")
|
||||||
|
includeGroup("dev.emi")
|
||||||
|
includeGroup("maven.modrinth")
|
||||||
|
includeGroup("me.shedaniel.cloth")
|
||||||
|
includeGroup("me.shedaniel")
|
||||||
|
includeGroup("mezz.jei")
|
||||||
|
includeModule("com.terraformersmc", "modmenu")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@@ -104,6 +119,7 @@ tasks.withType(JavaCompile::class.java).configureEach {
|
|||||||
|
|
||||||
tasks.processResources {
|
tasks.processResources {
|
||||||
exclude("**/*.license")
|
exclude("**/*.license")
|
||||||
|
exclude(".cache")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType(AbstractArchiveTask::class.java).configureEach {
|
tasks.withType(AbstractArchiveTask::class.java).configureEach {
|
||||||
|
@@ -0,0 +1,26 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package cc.tweaked.gradle
|
||||||
|
|
||||||
|
import net.minecraftforge.gradle.common.util.RunConfig
|
||||||
|
import net.minecraftforge.gradle.common.util.runs.setRunConfigInternal
|
||||||
|
import org.gradle.api.plugins.JavaPluginExtension
|
||||||
|
import org.gradle.api.tasks.JavaExec
|
||||||
|
import org.gradle.jvm.toolchain.JavaToolchainService
|
||||||
|
import java.nio.file.Files
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set [JavaExec] task to run a given [RunConfig].
|
||||||
|
*/
|
||||||
|
fun JavaExec.setRunConfig(config: RunConfig) {
|
||||||
|
dependsOn("prepareRuns")
|
||||||
|
setRunConfigInternal(project, this, config)
|
||||||
|
doFirst("Create working directory") { Files.createDirectories(workingDir.toPath()) }
|
||||||
|
|
||||||
|
javaLauncher.set(
|
||||||
|
project.extensions.getByType(JavaToolchainService::class.java)
|
||||||
|
.launcherFor(project.extensions.getByType(JavaPluginExtension::class.java).toolchain),
|
||||||
|
)
|
||||||
|
}
|
@@ -60,7 +60,7 @@ class IlluaminatePlugin : Plugin<Project> {
|
|||||||
|
|
||||||
/** Define a dependency for illuaminate from a version number and the current operating system. */
|
/** Define a dependency for illuaminate from a version number and the current operating system. */
|
||||||
private fun illuaminateArtifact(project: Project, version: String): Dependency {
|
private fun illuaminateArtifact(project: Project, version: String): Dependency {
|
||||||
val osName = System.getProperty("os.name").toLowerCase()
|
val osName = System.getProperty("os.name").lowercase()
|
||||||
val (os, suffix) = when {
|
val (os, suffix) = when {
|
||||||
osName.contains("windows") -> Pair("windows", ".exe")
|
osName.contains("windows") -> Pair("windows", ".exe")
|
||||||
osName.contains("mac os") || osName.contains("darwin") -> Pair("macos", "")
|
osName.contains("mac os") || osName.contains("darwin") -> Pair("macos", "")
|
||||||
@@ -68,7 +68,7 @@ class IlluaminatePlugin : Plugin<Project> {
|
|||||||
else -> error("Unsupported OS $osName for illuaminate")
|
else -> error("Unsupported OS $osName for illuaminate")
|
||||||
}
|
}
|
||||||
|
|
||||||
val osArch = System.getProperty("os.arch").toLowerCase()
|
val osArch = System.getProperty("os.arch").lowercase()
|
||||||
val arch = when {
|
val arch = when {
|
||||||
// On macOS the x86_64 binary will work for both ARM and Intel Macs through Rosetta.
|
// On macOS the x86_64 binary will work for both ARM and Intel Macs through Rosetta.
|
||||||
os == "macos" -> "x86_64"
|
os == "macos" -> "x86_64"
|
||||||
|
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
package cc.tweaked.gradle
|
package cc.tweaked.gradle
|
||||||
|
|
||||||
|
import net.minecraftforge.gradle.common.util.RunConfig
|
||||||
import org.gradle.api.GradleException
|
import org.gradle.api.GradleException
|
||||||
import org.gradle.api.file.FileSystemOperations
|
import org.gradle.api.file.FileSystemOperations
|
||||||
import org.gradle.api.invocation.Gradle
|
import org.gradle.api.invocation.Gradle
|
||||||
@@ -32,11 +33,14 @@ abstract class ClientJavaExec : JavaExec() {
|
|||||||
usesService(clientRunner)
|
usesService(clientRunner)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@get:Input
|
||||||
|
val renderdoc get() = project.hasProperty("renderdoc")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When [false], tests will not be run automatically, allowing the user to debug rendering.
|
* When [false], tests will not be run automatically, allowing the user to debug rendering.
|
||||||
*/
|
*/
|
||||||
@get:Input
|
@get:Input
|
||||||
val clientDebug get() = project.hasProperty("clientDebug")
|
val clientDebug get() = renderdoc || project.hasProperty("clientDebug")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When [false], tests will not run under a framebuffer.
|
* When [false], tests will not run under a framebuffer.
|
||||||
@@ -50,6 +54,25 @@ abstract class ClientJavaExec : JavaExec() {
|
|||||||
@get:OutputFile
|
@get:OutputFile
|
||||||
val testResults = project.layout.buildDirectory.file("test-results/$name.xml")
|
val testResults = project.layout.buildDirectory.file("test-results/$name.xml")
|
||||||
|
|
||||||
|
private fun setTestProperties() {
|
||||||
|
if (!clientDebug) systemProperty("cctest.client", "")
|
||||||
|
if (renderdoc) environment("LD_PRELOAD", "/usr/lib/librenderdoc.so")
|
||||||
|
systemProperty("cctest.gametest-report", testResults.get().asFile.absoluteFile)
|
||||||
|
workingDir(project.buildDir.resolve("gametest").resolve(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
setTestProperties()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set this task to run a given [RunConfig].
|
||||||
|
*/
|
||||||
|
fun setRunConfig(config: RunConfig) {
|
||||||
|
(this as JavaExec).setRunConfig(config)
|
||||||
|
setTestProperties() // setRunConfig may clobber some properties, ensure everything is set.
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copy configuration from a task with the given name.
|
* Copy configuration from a task with the given name.
|
||||||
*/
|
*/
|
||||||
@@ -61,10 +84,7 @@ abstract class ClientJavaExec : JavaExec() {
|
|||||||
fun copyFrom(task: JavaExec) {
|
fun copyFrom(task: JavaExec) {
|
||||||
for (dep in task.dependsOn) dependsOn(dep)
|
for (dep in task.dependsOn) dependsOn(dep)
|
||||||
task.copyToFull(this)
|
task.copyToFull(this)
|
||||||
|
setTestProperties() // copyToFull may clobber some properties, ensure everything is set.
|
||||||
if (!clientDebug) systemProperty("cctest.client", "")
|
|
||||||
systemProperty("cctest.gametest-report", testResults.get().asFile.absoluteFile)
|
|
||||||
workingDir(project.buildDir.resolve("gametest").resolve(name))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -0,0 +1,51 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package net.minecraftforge.gradle.common.util.runs
|
||||||
|
|
||||||
|
import net.minecraftforge.gradle.common.util.RunConfig
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.process.CommandLineArgumentProvider
|
||||||
|
import org.gradle.process.JavaExecSpec
|
||||||
|
import java.io.File
|
||||||
|
import java.util.function.Supplier
|
||||||
|
import java.util.stream.Collectors
|
||||||
|
import java.util.stream.Stream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up a [JavaExecSpec] to execute a [RunConfig].
|
||||||
|
*
|
||||||
|
* [MinecraftRunTask] sets up all its properties when the task is executed, rather than when configured. As such, it's
|
||||||
|
* not possible to use [cc.tweaked.gradle.copyToFull] like we do for Fabric. Instead, we set up the task manually.
|
||||||
|
*
|
||||||
|
* Unfortunately most of the functionality we need is package-private, and so we have to put our code into the package.
|
||||||
|
*/
|
||||||
|
internal fun setRunConfigInternal(project: Project, spec: JavaExecSpec, config: RunConfig) {
|
||||||
|
spec.workingDir = File(config.workingDirectory)
|
||||||
|
|
||||||
|
spec.mainClass.set(config.main)
|
||||||
|
for (source in config.allSources) spec.classpath(source.runtimeClasspath)
|
||||||
|
|
||||||
|
val originalTask = project.tasks.named(config.taskName, MinecraftRunTask::class.java)
|
||||||
|
|
||||||
|
// Add argument and JVM argument via providers, to be as lazy as possible with fetching artifacts.
|
||||||
|
val lazyTokens = RunConfigGenerator.configureTokensLazy(
|
||||||
|
project, config, RunConfigGenerator.mapModClassesToGradle(project, config),
|
||||||
|
originalTask.get().minecraftArtifacts,
|
||||||
|
originalTask.get().runtimeClasspathArtifacts,
|
||||||
|
)
|
||||||
|
spec.argumentProviders.add(
|
||||||
|
CommandLineArgumentProvider {
|
||||||
|
RunConfigGenerator.getArgsStream(config, lazyTokens, false).toList()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
spec.jvmArgumentProviders.add(
|
||||||
|
CommandLineArgumentProvider {
|
||||||
|
(if (config.isClient) config.jvmArgs + originalTask.get().additionalClientArgs.get() else config.jvmArgs).map { config.replace(lazyTokens, it) } +
|
||||||
|
config.properties.map { (k, v) -> "-D${k}=${config.replace(lazyTokens, v)}" }
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
for ((key, value) in config.environment) spec.environment(key, config.replace(lazyTokens, value))
|
||||||
|
}
|
@@ -13,6 +13,15 @@ The @{websocket_closed} event is fired when an open WebSocket connection is clos
|
|||||||
## Return Values
|
## Return Values
|
||||||
1. @{string}: The event name.
|
1. @{string}: The event name.
|
||||||
2. @{string}: The URL of the WebSocket that was closed.
|
2. @{string}: The URL of the WebSocket that was closed.
|
||||||
|
3. <span class="type">@{string}|@{nil}</span>: The [server-provided reason][close_reason]
|
||||||
|
the websocket was closed. This will be @{nil} if the connection was closed
|
||||||
|
abnormally.
|
||||||
|
4. <span class="type">@{number}|@{nil}</span>: The [connection close code][close_code],
|
||||||
|
indicating why the socket was closed. This will be @{nil} if the connection
|
||||||
|
was closed abnormally.
|
||||||
|
|
||||||
|
[close_reason]: https://www.rfc-editor.org/rfc/rfc6455.html#section-7.1.6 "The WebSocket Connection Close Reason, RFC 6455"
|
||||||
|
[close_code]: https://www.rfc-editor.org/rfc/rfc6455.html#section-7.1.5 "The WebSocket Connection Close Code, RFC 6455"
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
Prints a message when a WebSocket is closed (this may take a minute):
|
Prints a message when a WebSocket is closed (this may take a minute):
|
||||||
|
BIN
doc/logo-darkmode.png
Normal file
BIN
doc/logo-darkmode.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
@@ -10,7 +10,7 @@ kotlin.jvm.target.validation.mode=error
|
|||||||
|
|
||||||
# Mod properties
|
# Mod properties
|
||||||
isUnstable=false
|
isUnstable=false
|
||||||
modVersion=1.105.0
|
modVersion=1.107.0
|
||||||
|
|
||||||
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
|
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
|
||||||
mcVersion=1.20
|
mcVersion=1.19.4
|
||||||
|
@@ -7,20 +7,20 @@
|
|||||||
# Minecraft
|
# Minecraft
|
||||||
# MC version is specified in gradle.properties, as we need that in settings.gradle.
|
# MC version is specified in gradle.properties, as we need that in settings.gradle.
|
||||||
# Remember to update corresponding versions in fabric.mod.json/mods.toml
|
# Remember to update corresponding versions in fabric.mod.json/mods.toml
|
||||||
fabric-api = "0.83.0+1.20"
|
fabric-api = "0.86.1+1.19.4"
|
||||||
fabric-loader = "0.14.21"
|
fabric-loader = "0.14.21"
|
||||||
forge = "46.0.1"
|
forge = "45.0.42"
|
||||||
forgeSpi = "6.0.0"
|
forgeSpi = "6.0.0"
|
||||||
mixin = "0.8.5"
|
mixin = "0.8.5"
|
||||||
parchment = "2023.03.12"
|
parchment = "2023.06.26"
|
||||||
parchmentMc = "1.19.3"
|
parchmentMc = "1.19.4"
|
||||||
|
|
||||||
# Normal dependencies
|
# Normal dependencies
|
||||||
asm = "9.3"
|
asm = "9.3"
|
||||||
autoService = "1.0.1"
|
autoService = "1.0.1"
|
||||||
checkerFramework = "3.32.0"
|
checkerFramework = "3.32.0"
|
||||||
cobalt = "0.7.0"
|
cobalt = "0.7.1"
|
||||||
cobalt-next = "0.7.1" # Not a real version, used to constrain the version we accept.
|
cobalt-next = "0.7.2" # Not a real version, used to constrain the version we accept.
|
||||||
fastutil = "8.5.9"
|
fastutil = "8.5.9"
|
||||||
guava = "31.1-jre"
|
guava = "31.1-jre"
|
||||||
jetbrainsAnnotations = "24.0.1"
|
jetbrainsAnnotations = "24.0.1"
|
||||||
@@ -33,6 +33,7 @@ nightConfig = "3.6.5"
|
|||||||
slf4j = "1.7.36"
|
slf4j = "1.7.36"
|
||||||
|
|
||||||
# Minecraft mods
|
# Minecraft mods
|
||||||
|
emi = "1.0.8+1.19.4"
|
||||||
iris = "1.5.2+1.19.4"
|
iris = "1.5.2+1.19.4"
|
||||||
jei = "13.1.0.11"
|
jei = "13.1.0.11"
|
||||||
modmenu = "6.1.0-rc.1"
|
modmenu = "6.1.0-rc.1"
|
||||||
@@ -53,16 +54,16 @@ checkstyle = "10.3.4"
|
|||||||
curseForgeGradle = "1.0.14"
|
curseForgeGradle = "1.0.14"
|
||||||
errorProne-core = "2.18.0"
|
errorProne-core = "2.18.0"
|
||||||
errorProne-plugin = "3.0.1"
|
errorProne-plugin = "3.0.1"
|
||||||
fabric-loom = "1.1.10"
|
fabric-loom = "1.3.7"
|
||||||
forgeGradle = "5.1.+"
|
forgeGradle = "6.0.8"
|
||||||
githubRelease = "2.2.12"
|
githubRelease = "2.2.12"
|
||||||
ideaExt = "1.1.6"
|
ideaExt = "1.1.6"
|
||||||
illuaminate = "0.1.0-24-gdb28902"
|
illuaminate = "0.1.0-28-ga7efd71"
|
||||||
librarian = "1.+"
|
librarian = "1.+"
|
||||||
minotaur = "2.+"
|
minotaur = "2.+"
|
||||||
mixinGradle = "0.7.+"
|
mixinGradle = "0.7.+"
|
||||||
nullAway = "0.9.9"
|
nullAway = "0.9.9"
|
||||||
quiltflower = "1.8.0"
|
quiltflower = "1.10.0"
|
||||||
spotless = "6.17.0"
|
spotless = "6.17.0"
|
||||||
taskTree = "2.1.1"
|
taskTree = "2.1.1"
|
||||||
vanillaGradle = "0.2.1-SNAPSHOT"
|
vanillaGradle = "0.2.1-SNAPSHOT"
|
||||||
@@ -92,6 +93,7 @@ slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
|
|||||||
# Minecraft mods
|
# Minecraft mods
|
||||||
fabric-api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabric-api" }
|
fabric-api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabric-api" }
|
||||||
fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" }
|
fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" }
|
||||||
|
emi = { module = "dev.emi:emi-xplat-mojmap", version.ref = "emi" }
|
||||||
iris = { module = "maven.modrinth:iris", version.ref = "iris" }
|
iris = { module = "maven.modrinth:iris", version.ref = "iris" }
|
||||||
jei-api = { module = "mezz.jei:jei-1.19.4-common-api", version.ref = "jei" }
|
jei-api = { module = "mezz.jei:jei-1.19.4-common-api", version.ref = "jei" }
|
||||||
jei-fabric = { module = "mezz.jei:jei-1.19.4-fabric", version.ref = "jei" }
|
jei-fabric = { module = "mezz.jei:jei-1.19.4-fabric", version.ref = "jei" }
|
||||||
@@ -150,10 +152,10 @@ kotlin = ["kotlin-stdlib", "kotlin-coroutines"]
|
|||||||
# Minecraft
|
# Minecraft
|
||||||
externalMods-common = ["jei-api", "nightConfig-core", "nightConfig-toml"]
|
externalMods-common = ["jei-api", "nightConfig-core", "nightConfig-toml"]
|
||||||
externalMods-forge-compile = ["oculus", "jei-api"]
|
externalMods-forge-compile = ["oculus", "jei-api"]
|
||||||
externalMods-forge-runtime = []
|
externalMods-forge-runtime = ["jei-forge"]
|
||||||
externalMods-fabric = ["nightConfig-core", "nightConfig-toml"]
|
externalMods-fabric = ["nightConfig-core", "nightConfig-toml"]
|
||||||
externalMods-fabric-compile = ["iris", "jei-api", "rei-api", "rei-builtin"]
|
externalMods-fabric-compile = ["iris", "jei-api", "rei-api", "rei-builtin"]
|
||||||
externalMods-fabric-runtime = []
|
externalMods-fabric-runtime = ["jei-fabric", "modmenu"]
|
||||||
|
|
||||||
# Testing
|
# Testing
|
||||||
test = ["junit-jupiter-api", "junit-jupiter-params", "hamcrest", "jqwik-api"]
|
test = ["junit-jupiter-api", "junit-jupiter-params", "hamcrest", "jqwik-api"]
|
||||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
3
gradle/wrapper/gradle-wrapper.properties
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
|
||||||
|
networkTimeout=10000
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
19
gradlew
vendored
19
gradlew
vendored
@@ -55,7 +55,7 @@
|
|||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (3) This script is generated from the Groovy template
|
||||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
# within the Gradle project.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
@@ -80,13 +80,10 @@ do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
# This is normally unused
|
||||||
|
# shellcheck disable=SC2034
|
||||||
APP_NAME="Gradle"
|
|
||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=${0##*/}
|
||||||
|
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
@@ -143,12 +140,16 @@ fi
|
|||||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
max*)
|
max*)
|
||||||
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC3045
|
||||||
MAX_FD=$( ulimit -H -n ) ||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
warn "Could not query maximum file descriptor limit"
|
warn "Could not query maximum file descriptor limit"
|
||||||
esac
|
esac
|
||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
'' | soft) :;; #(
|
'' | soft) :;; #(
|
||||||
*)
|
*)
|
||||||
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC3045
|
||||||
ulimit -n "$MAX_FD" ||
|
ulimit -n "$MAX_FD" ||
|
||||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
esac
|
esac
|
||||||
@@ -193,6 +194,10 @@ if "$cygwin" || "$msys" ; then
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
# Collect all arguments for the java command;
|
# Collect all arguments for the java command;
|
||||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||||
# shell script including quotes and variable substitutions, so put them in
|
# shell script including quotes and variable substitutions, so put them in
|
||||||
|
1
gradlew.bat
vendored
1
gradlew.bat
vendored
@@ -26,6 +26,7 @@ if "%OS%"=="Windows_NT" setlocal
|
|||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%"=="" set DIRNAME=.
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
|
@rem This is normally unused
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@@ -11,9 +11,13 @@ import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
|||||||
import dan200.computercraft.api.turtle.TurtleSide;
|
import dan200.computercraft.api.turtle.TurtleSide;
|
||||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
|
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
|
||||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
import net.minecraft.client.resources.model.ModelResourceLocation;
|
||||||
|
import net.minecraft.client.resources.model.UnbakedModel;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides models for a {@link ITurtleUpgrade}.
|
* Provides models for a {@link ITurtleUpgrade}.
|
||||||
@@ -28,15 +32,45 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
|
|||||||
* When the current turtle is {@literal null}, this function should be constant for a given upgrade and side.
|
* When the current turtle is {@literal null}, this function should be constant for a given upgrade and side.
|
||||||
*
|
*
|
||||||
* @param upgrade The upgrade that you're getting the model for.
|
* @param upgrade The upgrade that you're getting the model for.
|
||||||
* @param turtle Access to the turtle that the upgrade resides on. This will be null when getting item models!
|
* @param turtle Access to the turtle that the upgrade resides on. This will be null when getting item models, unless
|
||||||
|
* {@link #getModel(ITurtleUpgrade, CompoundTag, TurtleSide)} is overriden.
|
||||||
* @param side Which side of the turtle (left or right) the upgrade resides on.
|
* @param side Which side of the turtle (left or right) the upgrade resides on.
|
||||||
* @return The model that you wish to be used to render your upgrade.
|
* @return The model that you wish to be used to render your upgrade.
|
||||||
*/
|
*/
|
||||||
TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side);
|
TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A basic {@link TurtleUpgradeModeller} which renders using the upgrade's {@linkplain ITurtleUpgrade#getCraftingItem()
|
* Obtain the model to be used when rendering a turtle peripheral.
|
||||||
* crafting item}.
|
* <p>
|
||||||
|
* This is used when rendering the turtle's item model, and so no {@link ITurtleAccess} is available.
|
||||||
|
*
|
||||||
|
* @param upgrade The upgrade that you're getting the model for.
|
||||||
|
* @param data Upgrade data instance for current turtle side.
|
||||||
|
* @param side Which side of the turtle (left or right) the upgrade resides on.
|
||||||
|
* @return The model that you wish to be used to render your upgrade.
|
||||||
|
*/
|
||||||
|
default TransformedModel getModel(T upgrade, CompoundTag data, TurtleSide side) {
|
||||||
|
return getModel(upgrade, (ITurtleAccess) null, side);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of models that this turtle modeller depends on.
|
||||||
|
* <p>
|
||||||
|
* Models included in this list will be loaded and baked alongside item and block models, and so may be referenced
|
||||||
|
* by {@link TransformedModel#of(ResourceLocation)}. You do not need to override this method if you will load models
|
||||||
|
* by other means.
|
||||||
|
*
|
||||||
|
* @return A list of models that this modeller depends on.
|
||||||
|
* @see UnbakedModel#getDependencies()
|
||||||
|
*/
|
||||||
|
default Collection<ResourceLocation> getDependencies() {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A basic {@link TurtleUpgradeModeller} which renders using the upgrade's {@linkplain ITurtleUpgrade#getUpgradeItem(CompoundTag)}
|
||||||
|
* upgrade item}.
|
||||||
* <p>
|
* <p>
|
||||||
* This uses appropriate transformations for "flat" items, namely those extending the {@literal minecraft:item/generated}
|
* This uses appropriate transformations for "flat" items, namely those extending the {@literal minecraft:item/generated}
|
||||||
* model type. It will not appear correct for 3D models with additional depth, such as blocks.
|
* model type. It will not appear correct for 3D models with additional depth, such as blocks.
|
||||||
@@ -46,7 +80,7 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> flatItem() {
|
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> flatItem() {
|
||||||
return (TurtleUpgradeModeller<T>) TurtleUpgradeModellers.FLAT_ITEM;
|
return (TurtleUpgradeModeller<T>) TurtleUpgradeModellers.UPGRADE_ITEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -58,7 +92,8 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
|
|||||||
* @return The constructed modeller.
|
* @return The constructed modeller.
|
||||||
*/
|
*/
|
||||||
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ModelResourceLocation left, ModelResourceLocation right) {
|
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ModelResourceLocation left, ModelResourceLocation right) {
|
||||||
return (upgrade, turtle, side) -> TransformedModel.of(side == TurtleSide.LEFT ? left : right);
|
// TODO(1.21.0): Remove this.
|
||||||
|
return sided((ResourceLocation) left, right);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -70,6 +105,16 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
|
|||||||
* @return The constructed modeller.
|
* @return The constructed modeller.
|
||||||
*/
|
*/
|
||||||
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ResourceLocation left, ResourceLocation right) {
|
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ResourceLocation left, ResourceLocation right) {
|
||||||
return (upgrade, turtle, side) -> TransformedModel.of(side == TurtleSide.LEFT ? left : right);
|
return new TurtleUpgradeModeller<>() {
|
||||||
|
@Override
|
||||||
|
public TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side) {
|
||||||
|
return TransformedModel.of(side == TurtleSide.LEFT ? left : right);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<ResourceLocation> getDependencies() {
|
||||||
|
return List.of(left, right);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,13 +6,20 @@ package dan200.computercraft.api.client.turtle;
|
|||||||
|
|
||||||
import com.mojang.math.Transformation;
|
import com.mojang.math.Transformation;
|
||||||
import dan200.computercraft.api.client.TransformedModel;
|
import dan200.computercraft.api.client.TransformedModel;
|
||||||
|
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||||
import dan200.computercraft.api.turtle.TurtleSide;
|
import dan200.computercraft.api.turtle.TurtleSide;
|
||||||
|
import dan200.computercraft.impl.client.ClientPlatformHelper;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
import org.joml.Matrix4f;
|
import org.joml.Matrix4f;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
class TurtleUpgradeModellers {
|
class TurtleUpgradeModellers {
|
||||||
private static final Transformation leftTransform = getMatrixFor(-0.40625f);
|
private static final Transformation leftTransform = getMatrixFor(-0.4065f);
|
||||||
private static final Transformation rightTransform = getMatrixFor(0.40625f);
|
private static final Transformation rightTransform = getMatrixFor(0.4065f);
|
||||||
|
|
||||||
private static Transformation getMatrixFor(float offset) {
|
private static Transformation getMatrixFor(float offset) {
|
||||||
var matrix = new Matrix4f();
|
var matrix = new Matrix4f();
|
||||||
@@ -26,6 +33,23 @@ class TurtleUpgradeModellers {
|
|||||||
return new Transformation(matrix);
|
return new Transformation(matrix);
|
||||||
}
|
}
|
||||||
|
|
||||||
static final TurtleUpgradeModeller<ITurtleUpgrade> FLAT_ITEM = (upgrade, turtle, side) ->
|
static final TurtleUpgradeModeller<ITurtleUpgrade> UPGRADE_ITEM = new UpgradeItemModeller();
|
||||||
TransformedModel.of(upgrade.getCraftingItem(), side == TurtleSide.LEFT ? leftTransform : rightTransform);
|
|
||||||
|
private static class UpgradeItemModeller implements TurtleUpgradeModeller<ITurtleUpgrade> {
|
||||||
|
@Override
|
||||||
|
public TransformedModel getModel(ITurtleUpgrade upgrade, @Nullable ITurtleAccess turtle, TurtleSide side) {
|
||||||
|
return getModel(turtle == null ? upgrade.getCraftingItem() : upgrade.getUpgradeItem(turtle.getUpgradeNBTData(side)), side);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransformedModel getModel(ITurtleUpgrade upgrade, CompoundTag data, TurtleSide side) {
|
||||||
|
return getModel(upgrade.getUpgradeItem(data), side);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TransformedModel getModel(ItemStack stack, TurtleSide side) {
|
||||||
|
var model = Minecraft.getInstance().getItemRenderer().getItemModelShaper().getItemModel(stack);
|
||||||
|
if (stack.hasFoil()) model = ClientPlatformHelper.get().createdFoiledModel(model);
|
||||||
|
return new TransformedModel(model, side == TurtleSide.LEFT ? leftTransform : rightTransform);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,6 +5,7 @@
|
|||||||
package dan200.computercraft.impl.client;
|
package dan200.computercraft.impl.client;
|
||||||
|
|
||||||
import dan200.computercraft.impl.Services;
|
import dan200.computercraft.impl.Services;
|
||||||
|
import net.minecraft.client.renderer.RenderType;
|
||||||
import net.minecraft.client.resources.model.BakedModel;
|
import net.minecraft.client.resources.model.BakedModel;
|
||||||
import net.minecraft.client.resources.model.ModelManager;
|
import net.minecraft.client.resources.model.ModelManager;
|
||||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
import net.minecraft.client.resources.model.ModelResourceLocation;
|
||||||
@@ -24,6 +25,15 @@ public interface ClientPlatformHelper {
|
|||||||
*/
|
*/
|
||||||
BakedModel getModel(ModelManager manager, ResourceLocation location);
|
BakedModel getModel(ModelManager manager, ResourceLocation location);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap this model in a version which renders a foil/enchantment glint.
|
||||||
|
*
|
||||||
|
* @param model The model to wrap.
|
||||||
|
* @return The wrapped model.
|
||||||
|
* @see RenderType#glint()
|
||||||
|
*/
|
||||||
|
BakedModel createdFoiledModel(BakedModel model);
|
||||||
|
|
||||||
static ClientPlatformHelper get() {
|
static ClientPlatformHelper get() {
|
||||||
var instance = Instance.INSTANCE;
|
var instance = Instance.INSTANCE;
|
||||||
return instance == null ? Services.raise(ClientPlatformHelper.class, Instance.ERROR) : instance;
|
return instance == null ? Services.raise(ClientPlatformHelper.class, Instance.ERROR) : instance;
|
||||||
|
@@ -5,9 +5,11 @@
|
|||||||
package dan200.computercraft.api.pocket;
|
package dan200.computercraft.api.pocket;
|
||||||
|
|
||||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||||
|
import dan200.computercraft.api.upgrades.UpgradeBase;
|
||||||
import net.minecraft.nbt.CompoundTag;
|
import net.minecraft.nbt.CompoundTag;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.world.entity.Entity;
|
import net.minecraft.world.entity.Entity;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -69,6 +71,8 @@ public interface IPocketAccess {
|
|||||||
*
|
*
|
||||||
* @return The upgrade's NBT.
|
* @return The upgrade's NBT.
|
||||||
* @see #updateUpgradeNBTData()
|
* @see #updateUpgradeNBTData()
|
||||||
|
* @see UpgradeBase#getUpgradeItem(CompoundTag)
|
||||||
|
* @see UpgradeBase#getUpgradeData(ItemStack)
|
||||||
*/
|
*/
|
||||||
CompoundTag getUpgradeNBTData();
|
CompoundTag getUpgradeNBTData();
|
||||||
|
|
||||||
@@ -80,7 +84,10 @@ public interface IPocketAccess {
|
|||||||
void updateUpgradeNBTData();
|
void updateUpgradeNBTData();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove the current peripheral and create a new one. You may wish to do this if the methods available change.
|
* Remove the current peripheral and create a new one.
|
||||||
|
* <p>
|
||||||
|
* You may wish to do this if the methods available change, for instance when the {@linkplain #getEntity() owning
|
||||||
|
* entity} changes.
|
||||||
*/
|
*/
|
||||||
void invalidatePeripheral();
|
void invalidatePeripheral();
|
||||||
|
|
||||||
@@ -88,6 +95,8 @@ public interface IPocketAccess {
|
|||||||
* Get a list of all upgrades for the pocket computer.
|
* Get a list of all upgrades for the pocket computer.
|
||||||
*
|
*
|
||||||
* @return A collection of all upgrade names.
|
* @return A collection of all upgrade names.
|
||||||
|
* @deprecated This is a relic of a previous API, which no longer makes sense with newer versions of ComputerCraft.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(forRemoval = true)
|
||||||
Map<ResourceLocation, IPeripheral> getUpgrades();
|
Map<ResourceLocation, IPeripheral> getUpgrades();
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
package dan200.computercraft.api.pocket;
|
package dan200.computercraft.api.pocket;
|
||||||
|
|
||||||
|
import dan200.computercraft.api.ComputerCraftAPI;
|
||||||
import dan200.computercraft.api.upgrades.UpgradeBase;
|
import dan200.computercraft.api.upgrades.UpgradeBase;
|
||||||
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
|
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
|
||||||
import dan200.computercraft.impl.ComputerCraftAPIService;
|
import dan200.computercraft.impl.ComputerCraftAPIService;
|
||||||
@@ -29,6 +30,14 @@ import java.util.function.Function;
|
|||||||
* @see PocketUpgradeDataProvider
|
* @see PocketUpgradeDataProvider
|
||||||
*/
|
*/
|
||||||
public interface PocketUpgradeSerialiser<T extends IPocketUpgrade> extends UpgradeSerialiser<T> {
|
public interface PocketUpgradeSerialiser<T extends IPocketUpgrade> extends UpgradeSerialiser<T> {
|
||||||
|
/**
|
||||||
|
* The ID for the associated registry.
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link #registryId()} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated(forRemoval = true)
|
||||||
|
ResourceKey<Registry<PocketUpgradeSerialiser<?>>> REGISTRY_ID = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "pocket_upgrade_serialiser"));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ID for the associated registry.
|
* The ID for the associated registry.
|
||||||
*
|
*
|
||||||
|
@@ -8,11 +8,15 @@ 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 org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
@@ -67,6 +71,29 @@ public interface ITurtleAccess {
|
|||||||
*/
|
*/
|
||||||
boolean teleportTo(Level world, BlockPos pos);
|
boolean teleportTo(Level world, BlockPos pos);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a vector containing the floating point co-ordinates at which the turtle is rendered.
|
||||||
|
* This will shift when the turtle is moving.
|
||||||
|
*
|
||||||
|
* @param f The subframe fraction.
|
||||||
|
* @return A vector containing the floating point co-ordinates at which the turtle resides.
|
||||||
|
* @see #getVisualYaw(float)
|
||||||
|
* @deprecated Will be removed in 1.20.
|
||||||
|
*/
|
||||||
|
@Deprecated(forRemoval = true)
|
||||||
|
Vec3 getVisualPosition(float f);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the yaw the turtle is facing when it is rendered.
|
||||||
|
*
|
||||||
|
* @param f The subframe fraction.
|
||||||
|
* @return The yaw the turtle is facing.
|
||||||
|
* @see #getVisualPosition(float)
|
||||||
|
* @deprecated Will be removed in 1.20.
|
||||||
|
*/
|
||||||
|
@Deprecated(forRemoval = true)
|
||||||
|
float getVisualYaw(float f);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the world direction the turtle is currently facing.
|
* Returns the world direction the turtle is currently facing.
|
||||||
*
|
*
|
||||||
@@ -221,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.
|
||||||
@@ -257,6 +312,8 @@ public interface ITurtleAccess {
|
|||||||
* @param side The side to get the upgrade data for.
|
* @param side The side to get the upgrade data for.
|
||||||
* @return The upgrade-specific data.
|
* @return The upgrade-specific data.
|
||||||
* @see #updateUpgradeNBTData(TurtleSide)
|
* @see #updateUpgradeNBTData(TurtleSide)
|
||||||
|
* @see UpgradeBase#getUpgradeItem(CompoundTag)
|
||||||
|
* @see UpgradeBase#getUpgradeData(ItemStack)
|
||||||
*/
|
*/
|
||||||
CompoundTag getUpgradeNBTData(TurtleSide side);
|
CompoundTag getUpgradeNBTData(TurtleSide side);
|
||||||
|
|
||||||
|
@@ -7,6 +7,7 @@ package dan200.computercraft.api.turtle;
|
|||||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||||
import dan200.computercraft.api.upgrades.UpgradeBase;
|
import dan200.computercraft.api.upgrades.UpgradeBase;
|
||||||
import net.minecraft.core.Direction;
|
import net.minecraft.core.Direction;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@@ -79,4 +80,17 @@ public interface ITurtleUpgrade extends UpgradeBase {
|
|||||||
*/
|
*/
|
||||||
default void update(ITurtleAccess turtle, TurtleSide side) {
|
default void update(ITurtleAccess turtle, TurtleSide side) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get upgrade data that should be persisted when the turtle was broken.
|
||||||
|
* <p>
|
||||||
|
* This method should be overridden when you don't need to store all upgrade data by default. For instance, if you
|
||||||
|
* store peripheral state in the upgrade data, which should be lost when the turtle is broken.
|
||||||
|
*
|
||||||
|
* @param upgradeData Data that currently stored for this upgrade
|
||||||
|
* @return Filtered version of this data.
|
||||||
|
*/
|
||||||
|
default CompoundTag getPersistedData(CompoundTag upgradeData) {
|
||||||
|
return upgradeData;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,49 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.api.turtle;
|
||||||
|
|
||||||
|
import net.minecraft.util.StringRepresentable;
|
||||||
|
import net.minecraft.world.entity.EquipmentSlot;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if an equipped turtle item will consume durability.
|
||||||
|
*
|
||||||
|
* @see TurtleUpgradeDataProvider.ToolBuilder#consumeDurability(TurtleToolDurability)
|
||||||
|
*/
|
||||||
|
public enum TurtleToolDurability implements StringRepresentable {
|
||||||
|
/**
|
||||||
|
* The equipped tool always consumes durability when using.
|
||||||
|
*/
|
||||||
|
ALWAYS("always"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The equipped tool consumes durability if it is {@linkplain ItemStack#isEnchanted() enchanted} or has
|
||||||
|
* {@linkplain ItemStack#getAttributeModifiers(EquipmentSlot) custom attribute modifiers}.
|
||||||
|
*/
|
||||||
|
WHEN_ENCHANTED("when_enchanted"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The equipped tool never consumes durability. Tools which have been damaged cannot be used as upgrades.
|
||||||
|
*/
|
||||||
|
NEVER("never");
|
||||||
|
|
||||||
|
private final String serialisedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The codec which may be used for serialising/deserialising {@link TurtleToolDurability}s.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public static final StringRepresentable.EnumCodec<TurtleToolDurability> CODEC = StringRepresentable.fromEnum(TurtleToolDurability::values);
|
||||||
|
|
||||||
|
TurtleToolDurability(String serialisedName) {
|
||||||
|
this.serialisedName = serialisedName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSerializedName() {
|
||||||
|
return serialisedName;
|
||||||
|
}
|
||||||
|
}
|
@@ -13,8 +13,10 @@ import net.minecraft.data.DataGenerator;
|
|||||||
import net.minecraft.data.PackOutput;
|
import net.minecraft.data.PackOutput;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.tags.TagKey;
|
import net.minecraft.tags.TagKey;
|
||||||
|
import net.minecraft.world.entity.EquipmentSlot;
|
||||||
import net.minecraft.world.entity.ai.attributes.Attributes;
|
import net.minecraft.world.entity.ai.attributes.Attributes;
|
||||||
import net.minecraft.world.item.Item;
|
import net.minecraft.world.item.Item;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
import net.minecraft.world.level.block.Block;
|
import net.minecraft.world.level.block.Block;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
@@ -61,6 +63,8 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITur
|
|||||||
private @Nullable Item craftingItem;
|
private @Nullable Item craftingItem;
|
||||||
private @Nullable Float damageMultiplier = null;
|
private @Nullable Float damageMultiplier = null;
|
||||||
private @Nullable TagKey<Block> breakable;
|
private @Nullable TagKey<Block> breakable;
|
||||||
|
private boolean allowEnchantments = false;
|
||||||
|
private TurtleToolDurability consumeDurability = TurtleToolDurability.NEVER;
|
||||||
|
|
||||||
ToolBuilder(ResourceLocation id, TurtleUpgradeSerialiser<?> serialiser, Item toolItem) {
|
ToolBuilder(ResourceLocation id, TurtleUpgradeSerialiser<?> serialiser, Item toolItem) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
@@ -104,6 +108,28 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITur
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate that this upgrade allows items which have been {@linkplain ItemStack#isEnchanted() enchanted} or have
|
||||||
|
* {@linkplain ItemStack#getAttributeModifiers(EquipmentSlot) custom attribute modifiers}.
|
||||||
|
*
|
||||||
|
* @return The tool builder, for further use.
|
||||||
|
*/
|
||||||
|
public ToolBuilder allowEnchantments() {
|
||||||
|
allowEnchantments = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set when the tool will consume durability.
|
||||||
|
*
|
||||||
|
* @param durability The durability predicate.
|
||||||
|
* @return The tool builder, for further use.
|
||||||
|
*/
|
||||||
|
public ToolBuilder consumeDurability(TurtleToolDurability durability) {
|
||||||
|
consumeDurability = durability;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provide a list of breakable blocks. If not given, the tool can break all blocks. If given, only blocks
|
* Provide a list of breakable blocks. If not given, the tool can break all blocks. If given, only blocks
|
||||||
* in this tag, those in {@link ComputerCraftTags.Blocks#TURTLE_ALWAYS_BREAKABLE} and "insta-mine" ones can
|
* in this tag, those in {@link ComputerCraftTags.Blocks#TURTLE_ALWAYS_BREAKABLE} and "insta-mine" ones can
|
||||||
@@ -132,6 +158,10 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITur
|
|||||||
}
|
}
|
||||||
if (damageMultiplier != null) s.addProperty("damageMultiplier", damageMultiplier);
|
if (damageMultiplier != null) s.addProperty("damageMultiplier", damageMultiplier);
|
||||||
if (breakable != null) s.addProperty("breakable", breakable.location().toString());
|
if (breakable != null) s.addProperty("breakable", breakable.location().toString());
|
||||||
|
if (allowEnchantments) s.addProperty("allowEnchantments", true);
|
||||||
|
if (consumeDurability != TurtleToolDurability.NEVER) {
|
||||||
|
s.addProperty("consumeDurability", consumeDurability.getSerializedName());
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
package dan200.computercraft.api.turtle;
|
package dan200.computercraft.api.turtle;
|
||||||
|
|
||||||
|
import dan200.computercraft.api.ComputerCraftAPI;
|
||||||
import dan200.computercraft.api.upgrades.UpgradeBase;
|
import dan200.computercraft.api.upgrades.UpgradeBase;
|
||||||
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
|
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
|
||||||
import dan200.computercraft.impl.ComputerCraftAPIService;
|
import dan200.computercraft.impl.ComputerCraftAPIService;
|
||||||
@@ -64,6 +65,14 @@ import java.util.function.Function;
|
|||||||
* @see dan200.computercraft.api.client.turtle.TurtleUpgradeModeller
|
* @see dan200.computercraft.api.client.turtle.TurtleUpgradeModeller
|
||||||
*/
|
*/
|
||||||
public interface TurtleUpgradeSerialiser<T extends ITurtleUpgrade> extends UpgradeSerialiser<T> {
|
public interface TurtleUpgradeSerialiser<T extends ITurtleUpgrade> extends UpgradeSerialiser<T> {
|
||||||
|
/**
|
||||||
|
* The ID for the associated registry.
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link #registryId()} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated(forRemoval = true)
|
||||||
|
ResourceKey<Registry<TurtleUpgradeSerialiser<?>>> REGISTRY_ID = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_upgrade_serialiser"));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ID for the associated registry.
|
* The ID for the associated registry.
|
||||||
*
|
*
|
||||||
|
@@ -4,10 +4,14 @@
|
|||||||
|
|
||||||
package dan200.computercraft.api.upgrades;
|
package dan200.computercraft.api.upgrades;
|
||||||
|
|
||||||
|
import dan200.computercraft.api.pocket.IPocketAccess;
|
||||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
|
import dan200.computercraft.api.pocket.IPocketUpgrade;
|
||||||
|
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||||
|
import dan200.computercraft.api.turtle.TurtleSide;
|
||||||
import dan200.computercraft.impl.PlatformHelper;
|
import dan200.computercraft.impl.PlatformHelper;
|
||||||
import net.minecraft.Util;
|
import net.minecraft.Util;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
|
||||||
@@ -50,6 +54,42 @@ public interface UpgradeBase {
|
|||||||
*/
|
*/
|
||||||
ItemStack getCraftingItem();
|
ItemStack getCraftingItem();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the item stack representing a currently equipped turtle upgrade.
|
||||||
|
* <p>
|
||||||
|
* While upgrades can store upgrade data ({@link ITurtleAccess#getUpgradeNBTData(TurtleSide)} and
|
||||||
|
* {@link IPocketAccess#getUpgradeNBTData()}}, by default this data is discarded when an upgrade is unequipped,
|
||||||
|
* and the original item stack is returned.
|
||||||
|
* <p>
|
||||||
|
* By overriding this method, you can create a new {@link ItemStack} which contains enough data to
|
||||||
|
* {@linkplain #getUpgradeData(ItemStack) re-create the upgrade data} if the item is re-equipped.
|
||||||
|
* <p>
|
||||||
|
* When overriding this, you should override {@link #getUpgradeData(ItemStack)} and {@link #isItemSuitable(ItemStack)}
|
||||||
|
* at the same time,
|
||||||
|
*
|
||||||
|
* @param upgradeData The current upgrade data. This should <strong>NOT</strong> be mutated.
|
||||||
|
* @return The item stack returned when unequipping.
|
||||||
|
*/
|
||||||
|
default ItemStack getUpgradeItem(CompoundTag upgradeData) {
|
||||||
|
return getCraftingItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract upgrade data from an {@link ItemStack}.
|
||||||
|
* <p>
|
||||||
|
* This upgrade data will be available with {@link ITurtleAccess#getUpgradeNBTData(TurtleSide)} or
|
||||||
|
* {@link IPocketAccess#getUpgradeNBTData()}.
|
||||||
|
* <p>
|
||||||
|
* This should be an inverse to {@link #getUpgradeItem(CompoundTag)}.
|
||||||
|
*
|
||||||
|
* @param stack The stack that was equipped by the turtle or pocket computer. This will have the same item as
|
||||||
|
* {@link #getCraftingItem()}.
|
||||||
|
* @return The upgrade data that should be set on the turtle or pocket computer.
|
||||||
|
*/
|
||||||
|
default CompoundTag getUpgradeData(ItemStack stack) {
|
||||||
|
return new CompoundTag();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if an item is suitable for being used for this upgrade.
|
* Determine if an item is suitable for being used for this upgrade.
|
||||||
* <p>
|
* <p>
|
||||||
|
@@ -0,0 +1,82 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.api.upgrades;
|
||||||
|
|
||||||
|
import dan200.computercraft.api.pocket.IPocketUpgrade;
|
||||||
|
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import org.jetbrains.annotations.Contract;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An upgrade (i.e. a {@link ITurtleUpgrade}) and its current upgrade data.
|
||||||
|
* <p>
|
||||||
|
* <strong>IMPORTANT:</strong> The {@link #data()} in an upgrade data is often a reference to the original upgrade data.
|
||||||
|
* Be careful to take a {@linkplain #copy() defensive copy} if you plan to use the data in this upgrade.
|
||||||
|
*
|
||||||
|
* @param upgrade The current upgrade.
|
||||||
|
* @param data The upgrade's data.
|
||||||
|
* @param <T> The type of upgrade, either {@link ITurtleUpgrade} or {@link IPocketUpgrade}.
|
||||||
|
*/
|
||||||
|
public record UpgradeData<T extends UpgradeBase>(T upgrade, CompoundTag data) {
|
||||||
|
/**
|
||||||
|
* A utility method to construct a new {@link UpgradeData} instance.
|
||||||
|
*
|
||||||
|
* @param upgrade An upgrade.
|
||||||
|
* @param data The upgrade's data.
|
||||||
|
* @param <T> The type of upgrade.
|
||||||
|
* @return The new {@link UpgradeData} instance.
|
||||||
|
*/
|
||||||
|
public static <T extends UpgradeBase> UpgradeData<T> of(T upgrade, CompoundTag data) {
|
||||||
|
return new UpgradeData<>(upgrade, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an {@link UpgradeData} containing the default {@linkplain #data() data} for an upgrade.
|
||||||
|
*
|
||||||
|
* @param upgrade The upgrade instance.
|
||||||
|
* @param <T> The type of upgrade.
|
||||||
|
* @return The default upgrade data.
|
||||||
|
*/
|
||||||
|
public static <T extends UpgradeBase> UpgradeData<T> ofDefault(T upgrade) {
|
||||||
|
return of(upgrade, upgrade.getUpgradeData(upgrade.getCraftingItem()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Take a copy of a (possibly {@code null}) {@link UpgradeData} instance.
|
||||||
|
*
|
||||||
|
* @param upgrade The copied upgrade data.
|
||||||
|
* @param <T> The type of upgrade.
|
||||||
|
* @return The newly created upgrade data.
|
||||||
|
*/
|
||||||
|
@Contract("!null -> !null; null -> null")
|
||||||
|
public static <T extends UpgradeBase> @Nullable UpgradeData<T> copyOf(@Nullable UpgradeData<T> upgrade) {
|
||||||
|
return upgrade == null ? null : upgrade.copy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the {@linkplain UpgradeBase#getUpgradeItem(CompoundTag) upgrade item} for this upgrade.
|
||||||
|
* <p>
|
||||||
|
* This returns a defensive copy of the item, to prevent accidental mutation of the upgrade data or original
|
||||||
|
* {@linkplain UpgradeBase#getCraftingItem() upgrade stack}.
|
||||||
|
*
|
||||||
|
* @return This upgrade's item.
|
||||||
|
*/
|
||||||
|
public ItemStack getUpgradeItem() {
|
||||||
|
return upgrade.getUpgradeItem(data).copy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Take a copy of this {@link UpgradeData}. This returns a new instance with the same upgrade and a fresh copy of
|
||||||
|
* the upgrade data.
|
||||||
|
*
|
||||||
|
* @return A copy of the current instance.
|
||||||
|
*/
|
||||||
|
public UpgradeData<T> copy() {
|
||||||
|
return new UpgradeData<>(upgrade(), data().copy());
|
||||||
|
}
|
||||||
|
}
|
@@ -23,10 +23,7 @@ import org.apache.logging.log4j.LogManager;
|
|||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
@@ -104,7 +101,7 @@ public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends Upgra
|
|||||||
protected abstract void addUpgrades(Consumer<Upgrade<R>> addUpgrade);
|
protected abstract void addUpgrades(Consumer<Upgrade<R>> addUpgrade);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final CompletableFuture<?> run(CachedOutput cache) {
|
public CompletableFuture<?> run(CachedOutput cache) {
|
||||||
var base = output.getOutputFolder().resolve("data");
|
var base = output.getOutputFolder().resolve("data");
|
||||||
|
|
||||||
Set<ResourceLocation> seen = new HashSet<>();
|
Set<ResourceLocation> seen = new HashSet<>();
|
||||||
@@ -127,7 +124,7 @@ public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends Upgra
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.upgrades = upgrades;
|
this.upgrades = Collections.unmodifiableList(upgrades);
|
||||||
return Util.sequenceFailFast(futures);
|
return Util.sequenceFailFast(futures);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,5 +163,21 @@ public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends Upgra
|
|||||||
public void add(Consumer<Upgrade<R>> add) {
|
public void add(Consumer<Upgrade<R>> add) {
|
||||||
add.accept(this);
|
add.accept(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a new {@link Upgrade} which requires the given mod to be present.
|
||||||
|
* <p>
|
||||||
|
* This uses mod-loader-specific hooks (Forge's crafting conditions and Fabric's resource conditions). If using
|
||||||
|
* this in a multi-loader setup, you must generate resources separately for the two loaders.
|
||||||
|
*
|
||||||
|
* @param modId The id of the mod.
|
||||||
|
* @return A new upgrade instance.
|
||||||
|
*/
|
||||||
|
public Upgrade<R> requireMod(String modId) {
|
||||||
|
return new Upgrade<>(id, serialiser, json -> {
|
||||||
|
PlatformHelper.get().addRequiredModCondition(json, modId);
|
||||||
|
serialise.accept(json);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
package dan200.computercraft.impl;
|
package dan200.computercraft.impl;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import dan200.computercraft.api.upgrades.UpgradeDataProvider;
|
||||||
import net.minecraft.core.Registry;
|
import net.minecraft.core.Registry;
|
||||||
import net.minecraft.nbt.CompoundTag;
|
import net.minecraft.nbt.CompoundTag;
|
||||||
import net.minecraft.resources.ResourceKey;
|
import net.minecraft.resources.ResourceKey;
|
||||||
@@ -63,6 +65,15 @@ public interface PlatformHelper {
|
|||||||
return item.getTag();
|
return item.getTag();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a resource condition which requires a mod to be loaded. This should be used by data providers such as
|
||||||
|
* {@link UpgradeDataProvider}.
|
||||||
|
*
|
||||||
|
* @param object The JSON object we're generating.
|
||||||
|
* @param modId The mod ID that we require.
|
||||||
|
*/
|
||||||
|
void addRequiredModCondition(JsonObject object, String modId);
|
||||||
|
|
||||||
final class Instance {
|
final class Instance {
|
||||||
static final @Nullable PlatformHelper INSTANCE;
|
static final @Nullable PlatformHelper INSTANCE;
|
||||||
static final @Nullable Throwable ERROR;
|
static final @Nullable Throwable ERROR;
|
||||||
|
@@ -7,6 +7,7 @@ import cc.tweaked.gradle.clientClasses
|
|||||||
import cc.tweaked.gradle.commonClasses
|
import cc.tweaked.gradle.commonClasses
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
|
id("cc-tweaked.publishing")
|
||||||
id("cc-tweaked.vanilla")
|
id("cc-tweaked.vanilla")
|
||||||
id("cc-tweaked.gametest")
|
id("cc-tweaked.gametest")
|
||||||
}
|
}
|
||||||
@@ -25,6 +26,7 @@ dependencies {
|
|||||||
clientImplementation(clientClasses(project(":common-api")))
|
clientImplementation(clientClasses(project(":common-api")))
|
||||||
|
|
||||||
compileOnly(libs.bundles.externalMods.common)
|
compileOnly(libs.bundles.externalMods.common)
|
||||||
|
clientCompileOnly(variantOf(libs.emi) { classifier("api") })
|
||||||
|
|
||||||
compileOnly(libs.mixin)
|
compileOnly(libs.mixin)
|
||||||
annotationProcessorEverywhere(libs.autoService)
|
annotationProcessorEverywhere(libs.autoService)
|
||||||
|
@@ -13,6 +13,7 @@ import dan200.computercraft.client.render.RenderTypes;
|
|||||||
import dan200.computercraft.client.render.TurtleBlockEntityRenderer;
|
import dan200.computercraft.client.render.TurtleBlockEntityRenderer;
|
||||||
import dan200.computercraft.client.render.monitor.MonitorBlockEntityRenderer;
|
import dan200.computercraft.client.render.monitor.MonitorBlockEntityRenderer;
|
||||||
import dan200.computercraft.client.turtle.TurtleModemModeller;
|
import dan200.computercraft.client.turtle.TurtleModemModeller;
|
||||||
|
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
|
||||||
import dan200.computercraft.core.util.Colour;
|
import dan200.computercraft.core.util.Colour;
|
||||||
import dan200.computercraft.shared.ModRegistry;
|
import dan200.computercraft.shared.ModRegistry;
|
||||||
import dan200.computercraft.shared.common.IColouredItem;
|
import dan200.computercraft.shared.common.IColouredItem;
|
||||||
@@ -107,24 +108,6 @@ public final class ClientRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static final String[] EXTRA_MODELS = new String[]{
|
private static final String[] EXTRA_MODELS = new String[]{
|
||||||
// Turtle upgrades
|
|
||||||
"block/turtle_modem_normal_off_left",
|
|
||||||
"block/turtle_modem_normal_on_left",
|
|
||||||
"block/turtle_modem_normal_off_right",
|
|
||||||
"block/turtle_modem_normal_on_right",
|
|
||||||
|
|
||||||
"block/turtle_modem_advanced_off_left",
|
|
||||||
"block/turtle_modem_advanced_on_left",
|
|
||||||
"block/turtle_modem_advanced_off_right",
|
|
||||||
"block/turtle_modem_advanced_on_right",
|
|
||||||
|
|
||||||
"block/turtle_crafting_table_left",
|
|
||||||
"block/turtle_crafting_table_right",
|
|
||||||
|
|
||||||
"block/turtle_speaker_left",
|
|
||||||
"block/turtle_speaker_right",
|
|
||||||
|
|
||||||
// Turtle block renderer
|
|
||||||
"block/turtle_colour",
|
"block/turtle_colour",
|
||||||
"block/turtle_elf_overlay",
|
"block/turtle_elf_overlay",
|
||||||
"block/turtle_rainbow_overlay",
|
"block/turtle_rainbow_overlay",
|
||||||
@@ -133,6 +116,7 @@ public final class ClientRegistry {
|
|||||||
|
|
||||||
public static void registerExtraModels(Consumer<ResourceLocation> register) {
|
public static void registerExtraModels(Consumer<ResourceLocation> register) {
|
||||||
for (var model : EXTRA_MODELS) register.accept(new ResourceLocation(ComputerCraftAPI.MOD_ID, model));
|
for (var model : EXTRA_MODELS) register.accept(new ResourceLocation(ComputerCraftAPI.MOD_ID, model));
|
||||||
|
TurtleUpgradeModellers.getDependencies().forEach(register);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void registerItemColours(BiConsumer<ItemColor, ItemLike> register) {
|
public static void registerItemColours(BiConsumer<ItemColor, ItemLike> register) {
|
||||||
|
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
package dan200.computercraft.client.gui;
|
package dan200.computercraft.client.gui;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
import dan200.computercraft.client.gui.widgets.ComputerSidebar;
|
import dan200.computercraft.client.gui.widgets.ComputerSidebar;
|
||||||
import dan200.computercraft.client.gui.widgets.DynamicImageButton;
|
import dan200.computercraft.client.gui.widgets.DynamicImageButton;
|
||||||
import dan200.computercraft.client.gui.widgets.TerminalWidget;
|
import dan200.computercraft.client.gui.widgets.TerminalWidget;
|
||||||
@@ -18,7 +19,6 @@ import dan200.computercraft.shared.config.Config;
|
|||||||
import dan200.computercraft.shared.network.server.UploadFileMessage;
|
import dan200.computercraft.shared.network.server.UploadFileMessage;
|
||||||
import net.minecraft.ChatFormatting;
|
import net.minecraft.ChatFormatting;
|
||||||
import net.minecraft.Util;
|
import net.minecraft.Util;
|
||||||
import net.minecraft.client.gui.GuiGraphics;
|
|
||||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraft.world.entity.player.Inventory;
|
import net.minecraft.world.entity.player.Inventory;
|
||||||
@@ -124,10 +124,10 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
|
public void render(PoseStack stack, int mouseX, int mouseY, float partialTicks) {
|
||||||
renderBackground(graphics);
|
renderBackground(stack);
|
||||||
super.render(graphics, mouseX, mouseY, partialTicks);
|
super.render(stack, mouseX, mouseY, partialTicks);
|
||||||
renderTooltip(graphics, mouseX, mouseY);
|
renderTooltip(stack, mouseX, mouseY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -147,7 +147,7 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
|
|||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void renderLabels(GuiGraphics graphics, int mouseX, int mouseY) {
|
protected void renderLabels(PoseStack transform, int mouseX, int mouseY) {
|
||||||
// Skip rendering labels.
|
// Skip rendering labels.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -4,11 +4,11 @@
|
|||||||
|
|
||||||
package dan200.computercraft.client.gui;
|
package dan200.computercraft.client.gui;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
import dan200.computercraft.client.gui.widgets.ComputerSidebar;
|
import dan200.computercraft.client.gui.widgets.ComputerSidebar;
|
||||||
import dan200.computercraft.client.gui.widgets.TerminalWidget;
|
import dan200.computercraft.client.gui.widgets.TerminalWidget;
|
||||||
import dan200.computercraft.client.render.ComputerBorderRenderer;
|
import dan200.computercraft.client.render.ComputerBorderRenderer;
|
||||||
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
|
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
|
||||||
import net.minecraft.client.gui.GuiGraphics;
|
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraft.world.entity.player.Inventory;
|
import net.minecraft.world.entity.player.Inventory;
|
||||||
|
|
||||||
@@ -36,14 +36,13 @@ public final class ComputerScreen<T extends AbstractComputerMenu> extends Abstra
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void renderBg(GuiGraphics graphics, float partialTicks, int mouseX, int mouseY) {
|
public void renderBg(PoseStack stack, float partialTicks, int mouseX, int mouseY) {
|
||||||
// Draw a border around the terminal
|
// Draw a border around the terminal
|
||||||
var terminal = getTerminal();
|
var terminal = getTerminal();
|
||||||
var texture = ComputerBorderRenderer.getTexture(family);
|
|
||||||
ComputerBorderRenderer.render(
|
ComputerBorderRenderer.render(
|
||||||
graphics.pose().last().pose(), texture, terminal.getX(), terminal.getY(),
|
stack.last().pose(), ComputerBorderRenderer.getTexture(family), terminal.getX(), terminal.getY(),
|
||||||
FULL_BRIGHT_LIGHTMAP, terminal.getWidth(), terminal.getHeight()
|
FULL_BRIGHT_LIGHTMAP, terminal.getWidth(), terminal.getHeight()
|
||||||
);
|
);
|
||||||
ComputerSidebar.renderBackground(graphics, texture, leftPos, topPos + sidebarYOffset);
|
ComputerSidebar.renderBackground(stack, leftPos, topPos + sidebarYOffset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,8 +4,9 @@
|
|||||||
|
|
||||||
package dan200.computercraft.client.gui;
|
package dan200.computercraft.client.gui;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.systems.RenderSystem;
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveMenu;
|
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveMenu;
|
||||||
import net.minecraft.client.gui.GuiGraphics;
|
|
||||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
@@ -22,14 +23,16 @@ public class DiskDriveScreen extends AbstractContainerScreen<DiskDriveMenu> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void renderBg(GuiGraphics graphics, float partialTicks, int mouseX, int mouseY) {
|
protected void renderBg(PoseStack transform, float partialTicks, int mouseX, int mouseY) {
|
||||||
graphics.blit(BACKGROUND, leftPos, topPos, 0, 0, imageWidth, imageHeight);
|
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
|
||||||
|
RenderSystem.setShaderTexture(0, BACKGROUND);
|
||||||
|
blit(transform, leftPos, topPos, 0, 0, imageWidth, imageHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
|
public void render(PoseStack transform, int mouseX, int mouseY, float partialTicks) {
|
||||||
renderBackground(graphics);
|
renderBackground(transform);
|
||||||
super.render(graphics, mouseX, mouseY, partialTicks);
|
super.render(transform, mouseX, mouseY, partialTicks);
|
||||||
renderTooltip(graphics, mouseX, mouseY);
|
renderTooltip(transform, mouseX, mouseY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,8 +4,10 @@
|
|||||||
|
|
||||||
package dan200.computercraft.client.gui;
|
package dan200.computercraft.client.gui;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.systems.RenderSystem;
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.gui.GuiGraphics;
|
import net.minecraft.client.gui.GuiComponent;
|
||||||
import net.minecraft.client.gui.components.toasts.Toast;
|
import net.minecraft.client.gui.components.toasts.Toast;
|
||||||
import net.minecraft.client.gui.components.toasts.ToastComponent;
|
import net.minecraft.client.gui.components.toasts.ToastComponent;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
@@ -71,52 +73,55 @@ public class ItemToast implements Toast {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Visibility render(GuiGraphics graphics, ToastComponent component, long time) {
|
public Visibility render(PoseStack transform, ToastComponent component, long time) {
|
||||||
if (isNew) {
|
if (isNew) {
|
||||||
|
|
||||||
firstDisplay = time;
|
firstDisplay = time;
|
||||||
isNew = false;
|
isNew = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RenderSystem.setShaderTexture(0, TEXTURE);
|
||||||
|
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
|
||||||
|
|
||||||
if (width == 160 && message.size() <= 1) {
|
if (width == 160 && message.size() <= 1) {
|
||||||
graphics.blit(TEXTURE, 0, 0, 0, 64, width, height());
|
GuiComponent.blit(transform, 0, 0, 0, 64, width, height());
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
var height = height();
|
var height = height();
|
||||||
|
|
||||||
var bottom = Math.min(4, height - 28);
|
var bottom = Math.min(4, height - 28);
|
||||||
renderBackgroundRow(graphics, width, 0, 0, 28);
|
renderBackgroundRow(transform, component, width, 0, 0, 28);
|
||||||
|
|
||||||
for (var i = 28; i < height - bottom; i += 10) {
|
for (var i = 28; i < height - bottom; i += 10) {
|
||||||
renderBackgroundRow(graphics, width, 16, i, Math.min(16, height - i - bottom));
|
renderBackgroundRow(transform, component, width, 16, i, Math.min(16, height - i - bottom));
|
||||||
}
|
}
|
||||||
|
|
||||||
renderBackgroundRow(graphics, width, 32 - bottom, height - bottom, bottom);
|
renderBackgroundRow(transform, component, width, 32 - bottom, height - bottom, bottom);
|
||||||
}
|
}
|
||||||
|
|
||||||
var textX = MARGIN;
|
var textX = MARGIN;
|
||||||
if (!stack.isEmpty()) {
|
if (!stack.isEmpty()) {
|
||||||
textX += MARGIN + IMAGE_SIZE;
|
textX += MARGIN + IMAGE_SIZE;
|
||||||
graphics.renderFakeItem(stack, MARGIN, MARGIN + height() / 2 - IMAGE_SIZE);
|
component.getMinecraft().getItemRenderer().renderAndDecorateFakeItem(transform, stack, MARGIN, MARGIN + height() / 2 - IMAGE_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
graphics.drawString(component.getMinecraft().font, title, textX, MARGIN, 0xff500050);
|
component.getMinecraft().font.draw(transform, title, textX, MARGIN, 0xff500050);
|
||||||
for (var i = 0; i < message.size(); ++i) {
|
for (var i = 0; i < message.size(); ++i) {
|
||||||
graphics.drawString(component.getMinecraft().font, message.get(i), textX, LINE_SPACING + (i + 1) * LINE_SPACING, 0xff000000);
|
component.getMinecraft().font.draw(transform, message.get(i), textX, (float) (LINE_SPACING + (i + 1) * LINE_SPACING), 0xff000000);
|
||||||
}
|
}
|
||||||
|
|
||||||
return time - firstDisplay < DISPLAY_TIME ? Visibility.SHOW : Visibility.HIDE;
|
return time - firstDisplay < DISPLAY_TIME ? Visibility.SHOW : Visibility.HIDE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void renderBackgroundRow(GuiGraphics graphics, int x, int u, int y, int height) {
|
private static void renderBackgroundRow(PoseStack transform, ToastComponent component, int x, int u, int y, int height) {
|
||||||
var leftOffset = 5;
|
var leftOffset = 5;
|
||||||
var rightOffset = Math.min(60, x - leftOffset);
|
var rightOffset = Math.min(60, x - leftOffset);
|
||||||
|
|
||||||
graphics.blit(TEXTURE, 0, y, 0, 32 + u, leftOffset, height);
|
GuiComponent.blit(transform, 0, y, 0, 32 + u, leftOffset, height);
|
||||||
for (var k = leftOffset; k < x - rightOffset; k += 64) {
|
for (var k = leftOffset; k < x - rightOffset; k += 64) {
|
||||||
graphics.blit(TEXTURE, k, y, 32, 32 + u, Math.min(64, x - k - rightOffset), height);
|
GuiComponent.blit(transform, k, y, 32, 32 + u, Math.min(64, x - k - rightOffset), height);
|
||||||
}
|
}
|
||||||
|
|
||||||
graphics.blit(TEXTURE, x - rightOffset, y, 160 - rightOffset, 32 + u, rightOffset, height);
|
GuiComponent.blit(transform, x - rightOffset, y, 160 - rightOffset, 32 + u, rightOffset, height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,11 +4,11 @@
|
|||||||
|
|
||||||
package dan200.computercraft.client.gui;
|
package dan200.computercraft.client.gui;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
import dan200.computercraft.client.gui.widgets.TerminalWidget;
|
import dan200.computercraft.client.gui.widgets.TerminalWidget;
|
||||||
import dan200.computercraft.core.terminal.Terminal;
|
import dan200.computercraft.core.terminal.Terminal;
|
||||||
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
|
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
|
||||||
import net.minecraft.client.KeyMapping;
|
import net.minecraft.client.KeyMapping;
|
||||||
import net.minecraft.client.gui.GuiGraphics;
|
|
||||||
import net.minecraft.client.gui.screens.Screen;
|
import net.minecraft.client.gui.screens.Screen;
|
||||||
import net.minecraft.client.gui.screens.inventory.MenuAccess;
|
import net.minecraft.client.gui.screens.inventory.MenuAccess;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
@@ -42,7 +42,7 @@ public class NoTermComputerScreen<T extends AbstractComputerMenu> extends Screen
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void init() {
|
protected void init() {
|
||||||
// FIXME: passEvents = true; // Pass mouse vents through to the game's mouse handler.
|
passEvents = true; // Pass mouse vents through to the game's mouse handler.
|
||||||
// First ensure we're still grabbing the mouse, so the user can look around. Then reset bits of state that
|
// First ensure we're still grabbing the mouse, so the user can look around. Then reset bits of state that
|
||||||
// grabbing unsets.
|
// grabbing unsets.
|
||||||
minecraft.mouseHandler.grabMouse();
|
minecraft.mouseHandler.grabMouse();
|
||||||
@@ -91,15 +91,15 @@ public class NoTermComputerScreen<T extends AbstractComputerMenu> extends Screen
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
|
public void render(PoseStack transform, int mouseX, int mouseY, float partialTicks) {
|
||||||
super.render(graphics, mouseX, mouseY, partialTicks);
|
super.render(transform, mouseX, mouseY, partialTicks);
|
||||||
|
|
||||||
var font = minecraft.font;
|
var font = minecraft.font;
|
||||||
var lines = font.split(Component.translatable("gui.computercraft.pocket_computer_overlay"), (int) (width * 0.8));
|
var lines = font.split(Component.translatable("gui.computercraft.pocket_computer_overlay"), (int) (width * 0.8));
|
||||||
var y = 10;
|
var y = 10.0f;
|
||||||
for (var line : lines) {
|
for (var line : lines) {
|
||||||
graphics.drawString(font, line, (width / 2) - (minecraft.font.width(line) / 2), y, 0xFFFFFF, true);
|
font.drawShadow(transform, line, (float) ((width / 2) - (minecraft.font.width(line) / 2)), y, 0xFFFFFF);
|
||||||
y += 9;
|
y += 9.0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,8 +4,9 @@
|
|||||||
|
|
||||||
package dan200.computercraft.client.gui;
|
package dan200.computercraft.client.gui;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.systems.RenderSystem;
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.gui.GuiGraphics;
|
|
||||||
import net.minecraft.client.gui.components.AbstractWidget;
|
import net.minecraft.client.gui.components.AbstractWidget;
|
||||||
import net.minecraft.client.gui.components.Button;
|
import net.minecraft.client.gui.components.Button;
|
||||||
import net.minecraft.client.gui.components.MultiLineLabel;
|
import net.minecraft.client.gui.components.MultiLineLabel;
|
||||||
@@ -85,19 +86,20 @@ public final class OptionScreen extends Screen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
|
public void render(PoseStack transform, int mouseX, int mouseY, float partialTicks) {
|
||||||
renderBackground(graphics);
|
renderBackground(transform);
|
||||||
|
|
||||||
// Render the actual texture.
|
// Render the actual texture.
|
||||||
graphics.blit(BACKGROUND, x, y, 0, 0, innerWidth, PADDING);
|
RenderSystem.setShaderTexture(0, BACKGROUND);
|
||||||
graphics.blit(BACKGROUND,
|
blit(transform, x, y, 0, 0, innerWidth, PADDING);
|
||||||
|
blit(transform,
|
||||||
x, y + PADDING, 0, PADDING, innerWidth, innerHeight - PADDING * 2,
|
x, y + PADDING, 0, PADDING, innerWidth, innerHeight - PADDING * 2,
|
||||||
innerWidth, PADDING
|
innerWidth, PADDING
|
||||||
);
|
);
|
||||||
graphics.blit(BACKGROUND, x, y + innerHeight - PADDING, 0, 256 - PADDING, innerWidth, PADDING);
|
blit(transform, x, y + innerHeight - PADDING, 0, 256 - PADDING, innerWidth, PADDING);
|
||||||
|
|
||||||
assertNonNull(messageRenderer).renderLeftAlignedNoShadow(graphics, x + PADDING, y + PADDING, FONT_HEIGHT, 0x404040);
|
assertNonNull(messageRenderer).renderLeftAlignedNoShadow(transform, x + PADDING, y + PADDING, FONT_HEIGHT, 0x404040);
|
||||||
super.render(graphics, mouseX, mouseY, partialTicks);
|
super.render(transform, mouseX, mouseY, partialTicks);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@@ -4,8 +4,9 @@
|
|||||||
|
|
||||||
package dan200.computercraft.client.gui;
|
package dan200.computercraft.client.gui;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.systems.RenderSystem;
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
import dan200.computercraft.shared.peripheral.printer.PrinterMenu;
|
import dan200.computercraft.shared.peripheral.printer.PrinterMenu;
|
||||||
import net.minecraft.client.gui.GuiGraphics;
|
|
||||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
@@ -22,16 +23,18 @@ public class PrinterScreen extends AbstractContainerScreen<PrinterMenu> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void renderBg(GuiGraphics graphics, float partialTicks, int mouseX, int mouseY) {
|
protected void renderBg(PoseStack transform, float partialTicks, int mouseX, int mouseY) {
|
||||||
graphics.blit(BACKGROUND, leftPos, topPos, 0, 0, imageWidth, imageHeight);
|
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
|
||||||
|
RenderSystem.setShaderTexture(0, BACKGROUND);
|
||||||
|
blit(transform, leftPos, topPos, 0, 0, imageWidth, imageHeight);
|
||||||
|
|
||||||
if (getMenu().isPrinting()) graphics.blit(BACKGROUND, leftPos + 34, topPos + 21, 176, 0, 25, 45);
|
if (getMenu().isPrinting()) blit(transform, leftPos + 34, topPos + 21, 176, 0, 25, 45);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
|
public void render(PoseStack stack, int mouseX, int mouseY, float partialTicks) {
|
||||||
renderBackground(graphics);
|
renderBackground(stack);
|
||||||
super.render(graphics, mouseX, mouseY, partialTicks);
|
super.render(stack, mouseX, mouseY, partialTicks);
|
||||||
renderTooltip(graphics, mouseX, mouseY);
|
renderTooltip(stack, mouseX, mouseY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,11 +4,12 @@
|
|||||||
|
|
||||||
package dan200.computercraft.client.gui;
|
package dan200.computercraft.client.gui;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.systems.RenderSystem;
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
import com.mojang.blaze3d.vertex.Tesselator;
|
import com.mojang.blaze3d.vertex.Tesselator;
|
||||||
import dan200.computercraft.core.terminal.TextBuffer;
|
import dan200.computercraft.core.terminal.TextBuffer;
|
||||||
import dan200.computercraft.shared.common.HeldItemMenu;
|
import dan200.computercraft.shared.common.HeldItemMenu;
|
||||||
import dan200.computercraft.shared.media.items.PrintoutItem;
|
import dan200.computercraft.shared.media.items.PrintoutItem;
|
||||||
import net.minecraft.client.gui.GuiGraphics;
|
|
||||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||||
import net.minecraft.client.renderer.MultiBufferSource;
|
import net.minecraft.client.renderer.MultiBufferSource;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
@@ -82,27 +83,30 @@ public class PrintoutScreen extends AbstractContainerScreen<HeldItemMenu> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void renderBg(GuiGraphics graphics, float partialTicks, int mouseX, int mouseY) {
|
protected void renderBg(PoseStack transform, float partialTicks, int mouseX, int mouseY) {
|
||||||
// Draw the printout
|
// Draw the printout
|
||||||
|
RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||||||
|
RenderSystem.enableDepthTest();
|
||||||
|
|
||||||
var renderer = MultiBufferSource.immediate(Tesselator.getInstance().getBuilder());
|
var renderer = MultiBufferSource.immediate(Tesselator.getInstance().getBuilder());
|
||||||
drawBorder(graphics.pose(), renderer, leftPos, topPos, 0, page, pages, book, FULL_BRIGHT_LIGHTMAP);
|
drawBorder(transform, renderer, leftPos, topPos, 0, page, pages, book, FULL_BRIGHT_LIGHTMAP);
|
||||||
drawText(graphics.pose(), renderer, leftPos + X_TEXT_MARGIN, topPos + Y_TEXT_MARGIN, PrintoutItem.LINES_PER_PAGE * page, FULL_BRIGHT_LIGHTMAP, text, colours);
|
drawText(transform, renderer, leftPos + X_TEXT_MARGIN, topPos + Y_TEXT_MARGIN, PrintoutItem.LINES_PER_PAGE * page, FULL_BRIGHT_LIGHTMAP, text, colours);
|
||||||
renderer.endBatch();
|
renderer.endBatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
|
public void render(PoseStack stack, int mouseX, int mouseY, float partialTicks) {
|
||||||
// We must take the background further back in order to not overlap with our printed pages.
|
// We must take the background further back in order to not overlap with our printed pages.
|
||||||
graphics.pose().pushPose();
|
stack.pushPose();
|
||||||
graphics.pose().translate(0, 0, -1);
|
stack.translate(0, 0, -1);
|
||||||
renderBackground(graphics);
|
renderBackground(stack);
|
||||||
graphics.pose().popPose();
|
stack.popPose();
|
||||||
|
|
||||||
super.render(graphics, mouseX, mouseY, partialTicks);
|
super.render(stack, mouseX, mouseY, partialTicks);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void renderLabels(GuiGraphics graphics, int mouseX, int mouseY) {
|
protected void renderLabels(PoseStack transform, int mouseX, int mouseY) {
|
||||||
// Skip rendering labels.
|
// Skip rendering labels.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
package dan200.computercraft.client.gui;
|
package dan200.computercraft.client.gui;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.systems.RenderSystem;
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
import dan200.computercraft.api.ComputerCraftAPI;
|
import dan200.computercraft.api.ComputerCraftAPI;
|
||||||
import dan200.computercraft.client.gui.widgets.ComputerSidebar;
|
import dan200.computercraft.client.gui.widgets.ComputerSidebar;
|
||||||
import dan200.computercraft.client.gui.widgets.TerminalWidget;
|
import dan200.computercraft.client.gui.widgets.TerminalWidget;
|
||||||
@@ -11,7 +13,6 @@ import dan200.computercraft.client.render.ComputerBorderRenderer;
|
|||||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||||
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
|
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
|
||||||
import dan200.computercraft.shared.turtle.inventory.TurtleMenu;
|
import dan200.computercraft.shared.turtle.inventory.TurtleMenu;
|
||||||
import net.minecraft.client.gui.GuiGraphics;
|
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.world.entity.player.Inventory;
|
import net.minecraft.world.entity.player.Inventory;
|
||||||
@@ -25,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);
|
||||||
|
|
||||||
@@ -41,21 +44,23 @@ public class TurtleScreen extends AbstractComputerScreen<TurtleMenu> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void renderBg(GuiGraphics graphics, 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;
|
||||||
var texture = advanced ? BACKGROUND_ADVANCED : BACKGROUND_NORMAL;
|
RenderSystem.setShaderTexture(0, advanced ? BACKGROUND_ADVANCED : BACKGROUND_NORMAL);
|
||||||
graphics.blit(texture, 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) {
|
||||||
var slotX = slot % 4;
|
var slotX = slot % 4;
|
||||||
var slotY = slot / 4;
|
var slotY = slot / 4;
|
||||||
graphics.blit(texture,
|
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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ComputerSidebar.renderBackground(graphics, ComputerBorderRenderer.getTexture(family), leftPos, topPos + sidebarYOffset);
|
RenderSystem.setShaderTexture(0, advanced ? ComputerBorderRenderer.BACKGROUND_ADVANCED : ComputerBorderRenderer.BACKGROUND_NORMAL);
|
||||||
|
ComputerSidebar.renderBackground(transform, leftPos, topPos + sidebarYOffset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,13 +4,14 @@
|
|||||||
|
|
||||||
package dan200.computercraft.client.gui.widgets;
|
package dan200.computercraft.client.gui.widgets;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
import dan200.computercraft.api.ComputerCraftAPI;
|
import dan200.computercraft.api.ComputerCraftAPI;
|
||||||
import dan200.computercraft.client.gui.widgets.DynamicImageButton.HintedMessage;
|
import dan200.computercraft.client.gui.widgets.DynamicImageButton.HintedMessage;
|
||||||
import dan200.computercraft.client.render.ComputerBorderRenderer;
|
import dan200.computercraft.client.render.ComputerBorderRenderer;
|
||||||
import dan200.computercraft.shared.computer.core.InputHandler;
|
import dan200.computercraft.shared.computer.core.InputHandler;
|
||||||
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
|
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
|
||||||
import net.minecraft.client.gui.GuiGraphics;
|
|
||||||
import net.minecraft.client.gui.components.AbstractWidget;
|
import net.minecraft.client.gui.components.AbstractWidget;
|
||||||
|
import net.minecraft.client.gui.screens.Screen;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
|
||||||
@@ -67,19 +68,19 @@ public final class ComputerSidebar {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void renderBackground(GuiGraphics graphics, ResourceLocation texture, int x, int y) {
|
public static void renderBackground(PoseStack transform, int x, int y) {
|
||||||
graphics.blit(texture,
|
Screen.blit(transform,
|
||||||
x, y, 0, 102, AbstractComputerMenu.SIDEBAR_WIDTH, FULL_BORDER,
|
x, y, 0, 102, AbstractComputerMenu.SIDEBAR_WIDTH, FULL_BORDER,
|
||||||
ComputerBorderRenderer.TEX_SIZE, ComputerBorderRenderer.TEX_SIZE
|
ComputerBorderRenderer.TEX_SIZE, ComputerBorderRenderer.TEX_SIZE
|
||||||
);
|
);
|
||||||
|
|
||||||
graphics.blit(texture,
|
Screen.blit(transform,
|
||||||
x, y + FULL_BORDER, AbstractComputerMenu.SIDEBAR_WIDTH, HEIGHT - FULL_BORDER * 2,
|
x, y + FULL_BORDER, AbstractComputerMenu.SIDEBAR_WIDTH, HEIGHT - FULL_BORDER * 2,
|
||||||
0, 107, AbstractComputerMenu.SIDEBAR_WIDTH, 4,
|
0, 107, AbstractComputerMenu.SIDEBAR_WIDTH, 4,
|
||||||
ComputerBorderRenderer.TEX_SIZE, ComputerBorderRenderer.TEX_SIZE
|
ComputerBorderRenderer.TEX_SIZE, ComputerBorderRenderer.TEX_SIZE
|
||||||
);
|
);
|
||||||
|
|
||||||
graphics.blit(texture,
|
Screen.blit(transform,
|
||||||
x, y + HEIGHT - FULL_BORDER, 0, 111, AbstractComputerMenu.SIDEBAR_WIDTH, FULL_BORDER,
|
x, y + HEIGHT - FULL_BORDER, 0, 111, AbstractComputerMenu.SIDEBAR_WIDTH, FULL_BORDER,
|
||||||
ComputerBorderRenderer.TEX_SIZE, ComputerBorderRenderer.TEX_SIZE
|
ComputerBorderRenderer.TEX_SIZE, ComputerBorderRenderer.TEX_SIZE
|
||||||
);
|
);
|
||||||
|
@@ -5,8 +5,8 @@
|
|||||||
package dan200.computercraft.client.gui.widgets;
|
package dan200.computercraft.client.gui.widgets;
|
||||||
|
|
||||||
import com.mojang.blaze3d.systems.RenderSystem;
|
import com.mojang.blaze3d.systems.RenderSystem;
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
import net.minecraft.ChatFormatting;
|
import net.minecraft.ChatFormatting;
|
||||||
import net.minecraft.client.gui.GuiGraphics;
|
|
||||||
import net.minecraft.client.gui.components.Button;
|
import net.minecraft.client.gui.components.Button;
|
||||||
import net.minecraft.client.gui.components.Tooltip;
|
import net.minecraft.client.gui.components.Tooltip;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
@@ -57,25 +57,23 @@ public class DynamicImageButton extends Button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void renderWidget(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
|
public void renderWidget(PoseStack stack, int mouseX, int mouseY, float partialTicks) {
|
||||||
RenderSystem.enableBlend();
|
RenderSystem.setShaderTexture(0, texture);
|
||||||
RenderSystem.enableDepthTest();
|
RenderSystem.disableDepthTest();
|
||||||
|
|
||||||
var yTex = yTexStart;
|
var yTex = yTexStart;
|
||||||
if (isHoveredOrFocused()) yTex += yDiffTex;
|
if (isHoveredOrFocused()) yTex += yDiffTex;
|
||||||
|
|
||||||
graphics.blit(texture, getX(), getY(), xTexStart.getAsInt(), yTex, width, height, textureWidth, textureHeight);
|
blit(stack, getX(), getY(), xTexStart.getAsInt(), yTex, width, height, textureWidth, textureHeight);
|
||||||
|
RenderSystem.enableDepthTest();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Component getMessage() {
|
public void render(PoseStack stack, int mouseX, int mouseY, float partialTicks) {
|
||||||
return message.get().message;
|
var message = this.message.get();
|
||||||
}
|
setMessage(message.message());
|
||||||
|
setTooltip(message.tooltip());
|
||||||
@Override
|
super.render(stack, mouseX, mouseY, partialTicks);
|
||||||
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
|
|
||||||
setTooltip(message.get().tooltip());
|
|
||||||
super.render(graphics, mouseX, mouseY, partialTicks);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public record HintedMessage(Component message, Tooltip tooltip) {
|
public record HintedMessage(Component message, Tooltip tooltip) {
|
||||||
|
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
package dan200.computercraft.client.gui.widgets;
|
package dan200.computercraft.client.gui.widgets;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
import com.mojang.blaze3d.vertex.Tesselator;
|
import com.mojang.blaze3d.vertex.Tesselator;
|
||||||
import dan200.computercraft.client.render.RenderTypes;
|
import dan200.computercraft.client.render.RenderTypes;
|
||||||
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
|
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
|
||||||
@@ -11,7 +12,6 @@ import dan200.computercraft.core.terminal.Terminal;
|
|||||||
import dan200.computercraft.shared.computer.core.InputHandler;
|
import dan200.computercraft.shared.computer.core.InputHandler;
|
||||||
import net.minecraft.SharedConstants;
|
import net.minecraft.SharedConstants;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.gui.GuiGraphics;
|
|
||||||
import net.minecraft.client.gui.components.AbstractWidget;
|
import net.minecraft.client.gui.components.AbstractWidget;
|
||||||
import net.minecraft.client.gui.narration.NarratedElementType;
|
import net.minecraft.client.gui.narration.NarratedElementType;
|
||||||
import net.minecraft.client.gui.narration.NarrationElementOutput;
|
import net.minecraft.client.gui.narration.NarrationElementOutput;
|
||||||
@@ -274,11 +274,11 @@ public class TerminalWidget extends AbstractWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void renderWidget(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
|
public void renderWidget(PoseStack transform, int mouseX, int mouseY, float partialTicks) {
|
||||||
if (!visible) return;
|
if (!visible) return;
|
||||||
|
|
||||||
var bufferSource = MultiBufferSource.immediate(Tesselator.getInstance().getBuilder());
|
var bufferSource = MultiBufferSource.immediate(Tesselator.getInstance().getBuilder());
|
||||||
var emitter = FixedWidthFontRenderer.toVertexConsumer(graphics.pose(), bufferSource.getBuffer(RenderTypes.TERMINAL));
|
var emitter = FixedWidthFontRenderer.toVertexConsumer(transform, bufferSource.getBuffer(RenderTypes.TERMINAL));
|
||||||
|
|
||||||
FixedWidthFontRenderer.drawTerminal(
|
FixedWidthFontRenderer.drawTerminal(
|
||||||
emitter,
|
emitter,
|
||||||
|
@@ -0,0 +1,44 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.client.integration.emi;
|
||||||
|
|
||||||
|
import dan200.computercraft.api.turtle.TurtleSide;
|
||||||
|
import dan200.computercraft.shared.ModRegistry;
|
||||||
|
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
||||||
|
import dan200.computercraft.shared.turtle.items.TurtleItem;
|
||||||
|
import dev.emi.emi.api.EmiEntrypoint;
|
||||||
|
import dev.emi.emi.api.EmiPlugin;
|
||||||
|
import dev.emi.emi.api.EmiRegistry;
|
||||||
|
import dev.emi.emi.api.stack.Comparison;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
|
||||||
|
import java.util.function.BiPredicate;
|
||||||
|
|
||||||
|
@EmiEntrypoint
|
||||||
|
public class EMIComputerCraft implements EmiPlugin {
|
||||||
|
@Override
|
||||||
|
public void register(EmiRegistry registry) {
|
||||||
|
registry.setDefaultComparison(ModRegistry.Items.TURTLE_NORMAL.get(), turtleComparison);
|
||||||
|
registry.setDefaultComparison(ModRegistry.Items.TURTLE_ADVANCED.get(), turtleComparison);
|
||||||
|
|
||||||
|
registry.setDefaultComparison(ModRegistry.Items.POCKET_COMPUTER_NORMAL.get(), pocketComparison);
|
||||||
|
registry.setDefaultComparison(ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get(), pocketComparison);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Comparison turtleComparison = compareStacks((left, right) ->
|
||||||
|
left.getItem() instanceof TurtleItem turtle
|
||||||
|
&& turtle.getUpgrade(left, TurtleSide.LEFT) == turtle.getUpgrade(right, TurtleSide.LEFT)
|
||||||
|
&& turtle.getUpgrade(left, TurtleSide.RIGHT) == turtle.getUpgrade(right, TurtleSide.RIGHT));
|
||||||
|
|
||||||
|
private static final Comparison pocketComparison = compareStacks((left, right) ->
|
||||||
|
left.getItem() instanceof PocketComputerItem && PocketComputerItem.getUpgrade(left) == PocketComputerItem.getUpgrade(right));
|
||||||
|
|
||||||
|
private static Comparison compareStacks(BiPredicate<ItemStack, ItemStack> test) {
|
||||||
|
return Comparison.of((left, right) -> {
|
||||||
|
ItemStack leftStack = left.getItemStack(), rightStack = right.getItemStack();
|
||||||
|
return leftStack.getItem() == rightStack.getItem() && test.test(leftStack, rightStack);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,103 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.client.model.turtle;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
|
||||||
|
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||||
|
import com.mojang.blaze3d.vertex.VertexFormatElement;
|
||||||
|
import com.mojang.math.Transformation;
|
||||||
|
import net.minecraft.client.renderer.block.model.BakedQuad;
|
||||||
|
import net.minecraft.client.resources.model.BakedModel;
|
||||||
|
import net.minecraft.core.Direction;
|
||||||
|
import org.joml.Matrix4f;
|
||||||
|
import org.joml.Vector4f;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies a {@link Transformation} (or rather a {@link Matrix4f}) to a list of {@link BakedQuad}s.
|
||||||
|
* <p>
|
||||||
|
* This does a little bit of magic compared with other system (i.e. Forge's {@code QuadTransformers}), as it needs to
|
||||||
|
* handle flipping models upside down.
|
||||||
|
* <p>
|
||||||
|
* This is typically used with a {@link BakedModel} subclass - see the loader-specific projects.
|
||||||
|
*/
|
||||||
|
public class ModelTransformer {
|
||||||
|
private static final int[] INVERSE_ORDER = new int[]{ 3, 2, 1, 0 };
|
||||||
|
|
||||||
|
private static final int STRIDE = DefaultVertexFormat.BLOCK.getIntegerSize();
|
||||||
|
private static final int POS_OFFSET = findOffset(DefaultVertexFormat.BLOCK, DefaultVertexFormat.ELEMENT_POSITION);
|
||||||
|
|
||||||
|
protected final Matrix4f transformation;
|
||||||
|
protected final boolean invert;
|
||||||
|
private @Nullable TransformedQuads cache;
|
||||||
|
|
||||||
|
public ModelTransformer(Transformation transformation) {
|
||||||
|
this.transformation = transformation.getMatrix();
|
||||||
|
invert = transformation.getMatrix().determinant() < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<BakedQuad> transform(List<BakedQuad> quads) {
|
||||||
|
if (quads.isEmpty()) return List.of();
|
||||||
|
|
||||||
|
// We do some basic caching here to avoid recomputing every frame. Most turtle models don't have culled faces,
|
||||||
|
// so it's not worth being smarter here.
|
||||||
|
var cache = this.cache;
|
||||||
|
if (cache != null && quads.equals(cache.original())) return cache.transformed();
|
||||||
|
|
||||||
|
List<BakedQuad> transformed = new ArrayList<>(quads.size());
|
||||||
|
for (var quad : quads) transformed.add(transformQuad(quad));
|
||||||
|
this.cache = new TransformedQuads(quads, transformed);
|
||||||
|
return transformed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BakedQuad transformQuad(BakedQuad quad) {
|
||||||
|
var inputData = quad.getVertices();
|
||||||
|
var outputData = new int[inputData.length];
|
||||||
|
for (var i = 0; i < 4; i++) {
|
||||||
|
var inStart = STRIDE * i;
|
||||||
|
// Reverse the order of the quads if we're inverting
|
||||||
|
var outStart = getVertexOffset(i, invert);
|
||||||
|
System.arraycopy(inputData, inStart, outputData, outStart, STRIDE);
|
||||||
|
|
||||||
|
// Apply the matrix to our position
|
||||||
|
var inPosStart = inStart + POS_OFFSET;
|
||||||
|
var outPosStart = outStart + POS_OFFSET;
|
||||||
|
|
||||||
|
var x = Float.intBitsToFloat(inputData[inPosStart]);
|
||||||
|
var y = Float.intBitsToFloat(inputData[inPosStart + 1]);
|
||||||
|
var z = Float.intBitsToFloat(inputData[inPosStart + 2]);
|
||||||
|
|
||||||
|
// Transform the position
|
||||||
|
var pos = new Vector4f(x, y, z, 1);
|
||||||
|
transformation.transformProject(pos);
|
||||||
|
|
||||||
|
outputData[outPosStart] = Float.floatToRawIntBits(pos.x());
|
||||||
|
outputData[outPosStart + 1] = Float.floatToRawIntBits(pos.y());
|
||||||
|
outputData[outPosStart + 2] = Float.floatToRawIntBits(pos.z());
|
||||||
|
}
|
||||||
|
|
||||||
|
var direction = Direction.rotate(transformation, quad.getDirection());
|
||||||
|
return new BakedQuad(outputData, quad.getTintIndex(), direction, quad.getSprite(), quad.isShade());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getVertexOffset(int vertex, boolean invert) {
|
||||||
|
return (invert ? ModelTransformer.INVERSE_ORDER[vertex] : vertex) * ModelTransformer.STRIDE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private record TransformedQuads(List<BakedQuad> original, List<BakedQuad> transformed) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int findOffset(VertexFormat format, VertexFormatElement element) {
|
||||||
|
var offset = 0;
|
||||||
|
for (var other : format.getElements()) {
|
||||||
|
if (other == element) return offset / Integer.BYTES;
|
||||||
|
offset += element.getByteSize();
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Cannot find " + element + " in " + format);
|
||||||
|
}
|
||||||
|
}
|
@@ -4,11 +4,13 @@
|
|||||||
|
|
||||||
package dan200.computercraft.client.model.turtle;
|
package dan200.computercraft.client.model.turtle;
|
||||||
|
|
||||||
|
import com.google.common.cache.CacheBuilder;
|
||||||
import com.mojang.blaze3d.vertex.PoseStack;
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
import com.mojang.math.Transformation;
|
import com.mojang.math.Transformation;
|
||||||
import dan200.computercraft.api.client.TransformedModel;
|
import dan200.computercraft.api.client.TransformedModel;
|
||||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||||
import dan200.computercraft.api.turtle.TurtleSide;
|
import dan200.computercraft.api.turtle.TurtleSide;
|
||||||
|
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||||
import dan200.computercraft.client.platform.ClientPlatformHelper;
|
import dan200.computercraft.client.platform.ClientPlatformHelper;
|
||||||
import dan200.computercraft.client.render.TurtleBlockEntityRenderer;
|
import dan200.computercraft.client.render.TurtleBlockEntityRenderer;
|
||||||
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
|
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
|
||||||
@@ -21,15 +23,17 @@ import net.minecraft.world.item.ItemStack;
|
|||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Combines several individual models together to form a turtle.
|
* Combines several individual models together to form a turtle.
|
||||||
|
*
|
||||||
|
* @param <T> The type of the resulting "baked model".
|
||||||
*/
|
*/
|
||||||
public final class TurtleModelParts {
|
public final class TurtleModelParts<T> {
|
||||||
private static final Transformation identity, flip;
|
private static final Transformation identity, flip;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
@@ -42,33 +46,67 @@ public final class TurtleModelParts {
|
|||||||
flip = new Transformation(stack.last().pose());
|
flip = new Transformation(stack.last().pose());
|
||||||
}
|
}
|
||||||
|
|
||||||
public record Combination(
|
private record Combination(
|
||||||
boolean colour,
|
boolean colour,
|
||||||
@Nullable ITurtleUpgrade leftUpgrade,
|
@Nullable UpgradeData<ITurtleUpgrade> leftUpgrade,
|
||||||
@Nullable ITurtleUpgrade rightUpgrade,
|
@Nullable UpgradeData<ITurtleUpgrade> rightUpgrade,
|
||||||
@Nullable ResourceLocation overlay,
|
@Nullable ResourceLocation overlay,
|
||||||
boolean christmas,
|
boolean christmas,
|
||||||
boolean flip
|
boolean flip
|
||||||
) {
|
) {
|
||||||
|
Combination copy() {
|
||||||
|
if (leftUpgrade == null && rightUpgrade == null) return this;
|
||||||
|
return new Combination(
|
||||||
|
colour, UpgradeData.copyOf(leftUpgrade), UpgradeData.copyOf(rightUpgrade),
|
||||||
|
overlay, christmas, flip
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final BakedModel familyModel;
|
private final BakedModel familyModel;
|
||||||
private final BakedModel colourModel;
|
private final BakedModel colourModel;
|
||||||
private final Function<TransformedModel, BakedModel> transformer;
|
private final Function<TransformedModel, BakedModel> transformer;
|
||||||
|
private final Function<Combination, T> buildModel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A cache of {@link TransformedModel} to the transformed {@link BakedModel}. This helps us pool the transformed
|
* A cache of {@link TransformedModel} to the transformed {@link BakedModel}. This helps us pool the transformed
|
||||||
* instances, reducing memory usage and hopefully ensuring their caches are hit more often!
|
* instances, reducing memory usage and hopefully ensuring their caches are hit more often!
|
||||||
*/
|
*/
|
||||||
private final Map<TransformedModel, BakedModel> transformCache = new HashMap<>();
|
private final Map<TransformedModel, BakedModel> transformCache = CacheBuilder.newBuilder()
|
||||||
|
.concurrencyLevel(1)
|
||||||
|
.expireAfterAccess(30, TimeUnit.SECONDS)
|
||||||
|
.<TransformedModel, BakedModel>build()
|
||||||
|
.asMap();
|
||||||
|
|
||||||
public TurtleModelParts(BakedModel familyModel, BakedModel colourModel, ModelTransformer transformer) {
|
/**
|
||||||
|
* A cache of {@link Combination}s to the combined model.
|
||||||
|
*/
|
||||||
|
private final Map<Combination, T> modelCache = CacheBuilder.newBuilder()
|
||||||
|
.concurrencyLevel(1)
|
||||||
|
.expireAfterAccess(30, TimeUnit.SECONDS)
|
||||||
|
.<Combination, T>build()
|
||||||
|
.asMap();
|
||||||
|
|
||||||
|
public TurtleModelParts(BakedModel familyModel, BakedModel colourModel, ModelTransformer transformer, Function<List<BakedModel>, T> combineModel) {
|
||||||
this.familyModel = familyModel;
|
this.familyModel = familyModel;
|
||||||
this.colourModel = colourModel;
|
this.colourModel = colourModel;
|
||||||
this.transformer = x -> transformer.transform(x.getModel(), x.getMatrix());
|
this.transformer = x -> transformer.transform(x.getModel(), x.getMatrix());
|
||||||
|
buildModel = x -> combineModel.apply(buildModel(x));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Combination getCombination(ItemStack stack) {
|
public T getModel(ItemStack stack) {
|
||||||
|
var combination = getCombination(stack);
|
||||||
|
var existing = modelCache.get(combination);
|
||||||
|
if (existing != null) return existing;
|
||||||
|
|
||||||
|
// Take a defensive copy of the upgrade data, and add it to the cache.
|
||||||
|
var newCombination = combination.copy();
|
||||||
|
var newModel = buildModel.apply(newCombination);
|
||||||
|
modelCache.put(newCombination, newModel);
|
||||||
|
return newModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Combination getCombination(ItemStack stack) {
|
||||||
var christmas = Holiday.getCurrent() == Holiday.CHRISTMAS;
|
var christmas = Holiday.getCurrent() == Holiday.CHRISTMAS;
|
||||||
|
|
||||||
if (!(stack.getItem() instanceof TurtleItem turtle)) {
|
if (!(stack.getItem() instanceof TurtleItem turtle)) {
|
||||||
@@ -76,8 +114,8 @@ public final class TurtleModelParts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var colour = turtle.getColour(stack);
|
var colour = turtle.getColour(stack);
|
||||||
var leftUpgrade = turtle.getUpgrade(stack, TurtleSide.LEFT);
|
var leftUpgrade = turtle.getUpgradeWithData(stack, TurtleSide.LEFT);
|
||||||
var rightUpgrade = turtle.getUpgrade(stack, TurtleSide.RIGHT);
|
var rightUpgrade = turtle.getUpgradeWithData(stack, TurtleSide.RIGHT);
|
||||||
var overlay = turtle.getOverlay(stack);
|
var overlay = turtle.getOverlay(stack);
|
||||||
var label = turtle.getLabel(stack);
|
var label = turtle.getLabel(stack);
|
||||||
var flip = label != null && (label.equals("Dinnerbone") || label.equals("Grumm"));
|
var flip = label != null && (label.equals("Dinnerbone") || label.equals("Grumm"));
|
||||||
@@ -85,7 +123,7 @@ public final class TurtleModelParts {
|
|||||||
return new Combination(colour != -1, leftUpgrade, rightUpgrade, overlay, christmas, flip);
|
return new Combination(colour != -1, leftUpgrade, rightUpgrade, overlay, christmas, flip);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<BakedModel> buildModel(Combination combo) {
|
private List<BakedModel> buildModel(Combination combo) {
|
||||||
var mc = Minecraft.getInstance();
|
var mc = Minecraft.getInstance();
|
||||||
var modelManager = mc.getItemRenderer().getItemModelShaper().getModelManager();
|
var modelManager = mc.getItemRenderer().getItemModelShaper().getModelManager();
|
||||||
|
|
||||||
@@ -97,19 +135,20 @@ public final class TurtleModelParts {
|
|||||||
if (overlayModelLocation != null) {
|
if (overlayModelLocation != null) {
|
||||||
parts.add(transform(ClientPlatformHelper.get().getModel(modelManager, overlayModelLocation), transformation));
|
parts.add(transform(ClientPlatformHelper.get().getModel(modelManager, overlayModelLocation), transformation));
|
||||||
}
|
}
|
||||||
if (combo.leftUpgrade() != null) {
|
|
||||||
var model = TurtleUpgradeModellers.getModel(combo.leftUpgrade(), null, TurtleSide.LEFT);
|
addUpgrade(parts, transformation, TurtleSide.LEFT, combo.leftUpgrade());
|
||||||
parts.add(transform(model.getModel(), transformation.compose(model.getMatrix())));
|
addUpgrade(parts, transformation, TurtleSide.RIGHT, combo.rightUpgrade());
|
||||||
}
|
|
||||||
if (combo.rightUpgrade() != null) {
|
|
||||||
var model = TurtleUpgradeModellers.getModel(combo.rightUpgrade(), null, TurtleSide.RIGHT);
|
|
||||||
parts.add(transform(model.getModel(), transformation.compose(model.getMatrix())));
|
|
||||||
}
|
|
||||||
|
|
||||||
return parts;
|
return parts;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BakedModel transform(BakedModel model, Transformation transformation) {
|
private void addUpgrade(List<BakedModel> parts, Transformation transformation, TurtleSide side, @Nullable UpgradeData<ITurtleUpgrade> upgrade) {
|
||||||
|
if (upgrade == null) return;
|
||||||
|
var model = TurtleUpgradeModellers.getModel(upgrade.upgrade(), upgrade.data(), side);
|
||||||
|
parts.add(transform(model.getModel(), transformation.compose(model.getMatrix())));
|
||||||
|
}
|
||||||
|
|
||||||
|
private BakedModel transform(BakedModel model, Transformation transformation) {
|
||||||
if (transformation.equals(Transformation.identity())) return model;
|
if (transformation.equals(Transformation.identity())) return model;
|
||||||
return transformCache.computeIfAbsent(new TransformedModel(model, transformation), transformer);
|
return transformCache.computeIfAbsent(new TransformedModel(model, transformation), transformer);
|
||||||
}
|
}
|
||||||
|
@@ -52,7 +52,7 @@ public abstract class AbstractClientNetworkContext implements ClientNetworkConte
|
|||||||
var player = Minecraft.getInstance().player;
|
var player = Minecraft.getInstance().player;
|
||||||
if (player == null) return;
|
if (player == null) return;
|
||||||
|
|
||||||
var te = player.level().getBlockEntity(pos);
|
var te = player.level.getBlockEntity(pos);
|
||||||
if (!(te instanceof MonitorBlockEntity monitor)) return;
|
if (!(te instanceof MonitorBlockEntity monitor)) return;
|
||||||
|
|
||||||
monitor.read(terminal);
|
monitor.read(terminal);
|
||||||
|
@@ -4,8 +4,13 @@
|
|||||||
|
|
||||||
package dan200.computercraft.client.platform;
|
package dan200.computercraft.client.platform;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
import dan200.computercraft.shared.network.NetworkMessage;
|
import dan200.computercraft.shared.network.NetworkMessage;
|
||||||
import dan200.computercraft.shared.network.server.ServerNetworkContext;
|
import dan200.computercraft.shared.network.server.ServerNetworkContext;
|
||||||
|
import net.minecraft.client.renderer.MultiBufferSource;
|
||||||
|
import net.minecraft.client.resources.model.BakedModel;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
public interface ClientPlatformHelper extends dan200.computercraft.impl.client.ClientPlatformHelper {
|
public interface ClientPlatformHelper extends dan200.computercraft.impl.client.ClientPlatformHelper {
|
||||||
static ClientPlatformHelper get() {
|
static ClientPlatformHelper get() {
|
||||||
@@ -18,4 +23,16 @@ public interface ClientPlatformHelper extends dan200.computercraft.impl.client.C
|
|||||||
* @param message The message to send.
|
* @param message The message to send.
|
||||||
*/
|
*/
|
||||||
void sendToServer(NetworkMessage<ServerNetworkContext> message);
|
void sendToServer(NetworkMessage<ServerNetworkContext> message);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a {@link BakedModel}, using any loader-specific hooks.
|
||||||
|
*
|
||||||
|
* @param transform The current matrix transformation to apply.
|
||||||
|
* @param buffers The current pool of render buffers.
|
||||||
|
* @param model The model to draw.
|
||||||
|
* @param lightmapCoord The current packed lightmap coordinate.
|
||||||
|
* @param overlayLight The current overlay light.
|
||||||
|
* @param tints Block colour tints to apply to the model.
|
||||||
|
*/
|
||||||
|
void renderBakedModel(PoseStack transform, MultiBufferSource buffers, BakedModel model, int lightmapCoord, int overlayLight, @Nullable int[] tints);
|
||||||
}
|
}
|
||||||
|
@@ -18,9 +18,9 @@ import org.joml.Matrix4f;
|
|||||||
* {@linkplain PocketItemRenderer in-hand pocket computers}.
|
* {@linkplain PocketItemRenderer in-hand pocket computers}.
|
||||||
*/
|
*/
|
||||||
public class ComputerBorderRenderer {
|
public class ComputerBorderRenderer {
|
||||||
private static final ResourceLocation BACKGROUND_NORMAL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "textures/gui/corners_normal.png");
|
public static final ResourceLocation BACKGROUND_NORMAL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "textures/gui/corners_normal.png");
|
||||||
private static final ResourceLocation BACKGROUND_ADVANCED = new ResourceLocation(ComputerCraftAPI.MOD_ID, "textures/gui/corners_advanced.png");
|
public static final ResourceLocation BACKGROUND_ADVANCED = new ResourceLocation(ComputerCraftAPI.MOD_ID, "textures/gui/corners_advanced.png");
|
||||||
private static final ResourceLocation BACKGROUND_COMMAND = new ResourceLocation(ComputerCraftAPI.MOD_ID, "textures/gui/corners_command.png");
|
public static final ResourceLocation BACKGROUND_COMMAND = new ResourceLocation(ComputerCraftAPI.MOD_ID, "textures/gui/corners_command.png");
|
||||||
public static final ResourceLocation BACKGROUND_COLOUR = new ResourceLocation(ComputerCraftAPI.MOD_ID, "textures/gui/corners_colour.png");
|
public static final ResourceLocation BACKGROUND_COLOUR = new ResourceLocation(ComputerCraftAPI.MOD_ID, "textures/gui/corners_colour.png");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -0,0 +1,103 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.client.render;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||||
|
import dan200.computercraft.client.model.turtle.ModelTransformer;
|
||||||
|
import dan200.computercraft.client.platform.ClientPlatformHelper;
|
||||||
|
import net.minecraft.client.renderer.MultiBufferSource;
|
||||||
|
import net.minecraft.client.renderer.block.model.BakedQuad;
|
||||||
|
import net.minecraft.client.renderer.entity.ItemRenderer;
|
||||||
|
import net.minecraft.client.resources.model.BakedModel;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import org.joml.Vector4f;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utilities for rendering {@link BakedModel}s and {@link BakedQuad}s.
|
||||||
|
*/
|
||||||
|
public final class ModelRenderer {
|
||||||
|
private ModelRenderer() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a list of {@linkplain BakedQuad quads} to a buffer.
|
||||||
|
* <p>
|
||||||
|
* This is not intended to be used directly, but instead by {@link ClientPlatformHelper#renderBakedModel(PoseStack, MultiBufferSource, BakedModel, int, int, int[])}. The
|
||||||
|
* implementation here is pretty similar to {@link ItemRenderer#renderQuadList(PoseStack, VertexConsumer, List, ItemStack, int, int)},
|
||||||
|
* but supports inverted quads (i.e. those with a negative scale).
|
||||||
|
*
|
||||||
|
* @param transform The current matrix transformation to apply.
|
||||||
|
* @param buffer The buffer to draw to.
|
||||||
|
* @param quads The quads to draw.
|
||||||
|
* @param lightmapCoord The current packed lightmap coordinate.
|
||||||
|
* @param overlayLight The current overlay light.
|
||||||
|
* @param tints Block colour tints to apply to the model.
|
||||||
|
*/
|
||||||
|
public static void renderQuads(PoseStack transform, VertexConsumer buffer, List<BakedQuad> quads, int lightmapCoord, int overlayLight, @Nullable int[] tints) {
|
||||||
|
var matrix = transform.last();
|
||||||
|
var inverted = matrix.pose().determinant() < 0;
|
||||||
|
|
||||||
|
for (var bakedquad : quads) {
|
||||||
|
var tint = -1;
|
||||||
|
if (tints != null && bakedquad.isTinted()) {
|
||||||
|
var idx = bakedquad.getTintIndex();
|
||||||
|
if (idx >= 0 && idx < tints.length) tint = tints[bakedquad.getTintIndex()];
|
||||||
|
}
|
||||||
|
|
||||||
|
var r = (float) (tint >> 16 & 255) / 255.0F;
|
||||||
|
var g = (float) (tint >> 8 & 255) / 255.0F;
|
||||||
|
var b = (float) (tint & 255) / 255.0F;
|
||||||
|
putBulkQuad(buffer, matrix, bakedquad, r, g, b, lightmapCoord, overlayLight, inverted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A version of {@link VertexConsumer#putBulkData(PoseStack.Pose, BakedQuad, float, float, float, int, int)} which
|
||||||
|
* will reverse vertex order when the matrix is inverted.
|
||||||
|
*
|
||||||
|
* @param buffer The buffer to draw to.
|
||||||
|
* @param pose The current matrix stack.
|
||||||
|
* @param quad The quad to draw.
|
||||||
|
* @param red The red tint of this quad.
|
||||||
|
* @param green The green tint of this quad.
|
||||||
|
* @param blue The blue tint of this quad.
|
||||||
|
* @param lightmapCoord The lightmap coordinate
|
||||||
|
* @param overlayLight The overlay light.
|
||||||
|
* @param invert Whether to reverse the order of this quad.
|
||||||
|
*/
|
||||||
|
private static void putBulkQuad(VertexConsumer buffer, PoseStack.Pose pose, BakedQuad quad, float red, float green, float blue, int lightmapCoord, int overlayLight, boolean invert) {
|
||||||
|
var matrix = pose.pose();
|
||||||
|
// It's a little dubious to transform using this matrix rather than the normal matrix. This mirrors the logic in
|
||||||
|
// Direction.rotate (so not out of nowhere!), but is a little suspicious.
|
||||||
|
var dirNormal = quad.getDirection().getNormal();
|
||||||
|
var vector = new Vector4f();
|
||||||
|
|
||||||
|
matrix.transform(dirNormal.getX(), dirNormal.getY(), dirNormal.getZ(), 0.0f, vector).normalize();
|
||||||
|
float normalX = vector.x(), normalY = vector.y(), normalZ = vector.z();
|
||||||
|
|
||||||
|
var vertices = quad.getVertices();
|
||||||
|
for (var vertex = 0; vertex < 4; vertex++) {
|
||||||
|
var i = ModelTransformer.getVertexOffset(vertex, invert);
|
||||||
|
|
||||||
|
var x = Float.intBitsToFloat(vertices[i]);
|
||||||
|
var y = Float.intBitsToFloat(vertices[i + 1]);
|
||||||
|
var z = Float.intBitsToFloat(vertices[i + 2]);
|
||||||
|
|
||||||
|
matrix.transform(x, y, z, 1, vector);
|
||||||
|
|
||||||
|
var u = Float.intBitsToFloat(vertices[i + 4]);
|
||||||
|
var v = Float.intBitsToFloat(vertices[i + 5]);
|
||||||
|
buffer.vertex(
|
||||||
|
vector.x(), vector.y(), vector.z(),
|
||||||
|
red, green, blue, 1.0F, u, v, overlayLight, lightmapCoord,
|
||||||
|
normalX, normalY, normalZ
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -5,7 +5,6 @@
|
|||||||
package dan200.computercraft.client.render;
|
package dan200.computercraft.client.render;
|
||||||
|
|
||||||
import com.mojang.blaze3d.vertex.PoseStack;
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
|
||||||
import com.mojang.math.Axis;
|
import com.mojang.math.Axis;
|
||||||
import com.mojang.math.Transformation;
|
import com.mojang.math.Transformation;
|
||||||
import dan200.computercraft.api.ComputerCraftAPI;
|
import dan200.computercraft.api.ComputerCraftAPI;
|
||||||
@@ -14,25 +13,20 @@ import dan200.computercraft.client.platform.ClientPlatformHelper;
|
|||||||
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
|
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
|
||||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||||
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
|
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
|
||||||
import dan200.computercraft.shared.util.DirectionUtil;
|
|
||||||
import dan200.computercraft.shared.util.Holiday;
|
import dan200.computercraft.shared.util.Holiday;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.gui.Font;
|
import net.minecraft.client.gui.Font;
|
||||||
import net.minecraft.client.renderer.MultiBufferSource;
|
import net.minecraft.client.renderer.MultiBufferSource;
|
||||||
import net.minecraft.client.renderer.Sheets;
|
|
||||||
import net.minecraft.client.renderer.block.model.BakedQuad;
|
|
||||||
import net.minecraft.client.renderer.blockentity.BlockEntityRenderDispatcher;
|
import net.minecraft.client.renderer.blockentity.BlockEntityRenderDispatcher;
|
||||||
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
|
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
|
||||||
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
|
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
|
||||||
import net.minecraft.client.resources.model.BakedModel;
|
import net.minecraft.client.resources.model.BakedModel;
|
||||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
import net.minecraft.client.resources.model.ModelResourceLocation;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.util.RandomSource;
|
|
||||||
import net.minecraft.world.phys.BlockHitResult;
|
import net.minecraft.world.phys.BlockHitResult;
|
||||||
import net.minecraft.world.phys.HitResult;
|
import net.minecraft.world.phys.HitResult;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBlockEntity> {
|
public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBlockEntity> {
|
||||||
private static final ModelResourceLocation NORMAL_TURTLE_MODEL = new ModelResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_normal", "inventory");
|
private static final ModelResourceLocation NORMAL_TURTLE_MODEL = new ModelResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_normal", "inventory");
|
||||||
@@ -40,8 +34,6 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
|
|||||||
private static final ResourceLocation COLOUR_TURTLE_MODEL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_colour");
|
private static final ResourceLocation COLOUR_TURTLE_MODEL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_colour");
|
||||||
private static final ResourceLocation ELF_OVERLAY_MODEL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_elf_overlay");
|
private static final ResourceLocation ELF_OVERLAY_MODEL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_elf_overlay");
|
||||||
|
|
||||||
private final RandomSource random = RandomSource.create(0);
|
|
||||||
|
|
||||||
private final BlockEntityRenderDispatcher renderer;
|
private final BlockEntityRenderDispatcher renderer;
|
||||||
private final Font font;
|
private final Font font;
|
||||||
|
|
||||||
@@ -107,23 +99,22 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
|
|||||||
var family = turtle.getFamily();
|
var family = turtle.getFamily();
|
||||||
var overlay = turtle.getOverlay();
|
var overlay = turtle.getOverlay();
|
||||||
|
|
||||||
var buffer = buffers.getBuffer(Sheets.translucentCullBlockSheet());
|
renderModel(transform, buffers, lightmapCoord, overlayLight, getTurtleModel(family, colour != -1), colour == -1 ? null : new int[]{ colour });
|
||||||
renderModel(transform, buffer, lightmapCoord, overlayLight, getTurtleModel(family, colour != -1), colour == -1 ? null : new int[]{ colour });
|
|
||||||
|
|
||||||
// Render the overlay
|
// Render the overlay
|
||||||
var overlayModel = getTurtleOverlayModel(overlay, Holiday.getCurrent() == Holiday.CHRISTMAS);
|
var overlayModel = getTurtleOverlayModel(overlay, Holiday.getCurrent() == Holiday.CHRISTMAS);
|
||||||
if (overlayModel != null) {
|
if (overlayModel != null) {
|
||||||
renderModel(transform, buffer, lightmapCoord, overlayLight, overlayModel, null);
|
renderModel(transform, buffers, lightmapCoord, overlayLight, overlayModel, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render the upgrades
|
// Render the upgrades
|
||||||
renderUpgrade(transform, buffer, lightmapCoord, overlayLight, turtle, TurtleSide.LEFT, partialTicks);
|
renderUpgrade(transform, buffers, lightmapCoord, overlayLight, turtle, TurtleSide.LEFT, partialTicks);
|
||||||
renderUpgrade(transform, buffer, lightmapCoord, overlayLight, turtle, TurtleSide.RIGHT, partialTicks);
|
renderUpgrade(transform, buffers, lightmapCoord, overlayLight, turtle, TurtleSide.RIGHT, partialTicks);
|
||||||
|
|
||||||
transform.popPose();
|
transform.popPose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void renderUpgrade(PoseStack transform, VertexConsumer renderer, int lightmapCoord, int overlayLight, TurtleBlockEntity turtle, TurtleSide side, float f) {
|
private void renderUpgrade(PoseStack transform, MultiBufferSource buffers, int lightmapCoord, int overlayLight, TurtleBlockEntity turtle, TurtleSide side, float f) {
|
||||||
var upgrade = turtle.getUpgrade(side);
|
var upgrade = turtle.getUpgrade(side);
|
||||||
if (upgrade == null) return;
|
if (upgrade == null) return;
|
||||||
transform.pushPose();
|
transform.pushPose();
|
||||||
@@ -134,46 +125,33 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
|
|||||||
transform.translate(0.0f, -0.5f, -0.5f);
|
transform.translate(0.0f, -0.5f, -0.5f);
|
||||||
|
|
||||||
var model = TurtleUpgradeModellers.getModel(upgrade, turtle.getAccess(), side);
|
var model = TurtleUpgradeModellers.getModel(upgrade, turtle.getAccess(), side);
|
||||||
pushPoseFromTransformation(transform, model.getMatrix());
|
applyTransformation(transform, model.getMatrix());
|
||||||
renderModel(transform, renderer, lightmapCoord, overlayLight, model.getModel(), null);
|
renderModel(transform, buffers, lightmapCoord, overlayLight, model.getModel(), null);
|
||||||
transform.popPose();
|
|
||||||
|
|
||||||
transform.popPose();
|
transform.popPose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void renderModel(PoseStack transform, VertexConsumer renderer, int lightmapCoord, int overlayLight, ResourceLocation modelLocation, @Nullable int[] tints) {
|
private void renderModel(PoseStack transform, MultiBufferSource buffers, int lightmapCoord, int overlayLight, ResourceLocation modelLocation, @Nullable int[] tints) {
|
||||||
var modelManager = Minecraft.getInstance().getItemRenderer().getItemModelShaper().getModelManager();
|
var modelManager = Minecraft.getInstance().getItemRenderer().getItemModelShaper().getModelManager();
|
||||||
renderModel(transform, renderer, lightmapCoord, overlayLight, ClientPlatformHelper.get().getModel(modelManager, modelLocation), tints);
|
renderModel(transform, buffers, lightmapCoord, overlayLight, ClientPlatformHelper.get().getModel(modelManager, modelLocation), tints);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void renderModel(PoseStack transform, VertexConsumer renderer, int lightmapCoord, int overlayLight, BakedModel model, @Nullable int[] tints) {
|
/**
|
||||||
random.setSeed(0);
|
* Render a block model.
|
||||||
renderQuads(transform, renderer, lightmapCoord, overlayLight, model.getQuads(null, null, random), tints);
|
*
|
||||||
for (var facing : DirectionUtil.FACINGS) {
|
* @param transform The current matrix stack.
|
||||||
renderQuads(transform, renderer, lightmapCoord, overlayLight, model.getQuads(null, facing, random), tints);
|
* @param renderer The buffer to write to.
|
||||||
}
|
* @param lightmapCoord The current lightmap coordinate.
|
||||||
|
* @param overlayLight The overlay light.
|
||||||
|
* @param model The model to render.
|
||||||
|
* @param tints Tints for the quads, as an array of RGB values.
|
||||||
|
* @see net.minecraft.client.renderer.block.ModelBlockRenderer#renderModel
|
||||||
|
*/
|
||||||
|
private void renderModel(PoseStack transform, MultiBufferSource renderer, int lightmapCoord, int overlayLight, BakedModel model, @Nullable int[] tints) {
|
||||||
|
ClientPlatformHelper.get().renderBakedModel(transform, renderer, model, lightmapCoord, overlayLight, tints);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void renderQuads(PoseStack transform, VertexConsumer buffer, int lightmapCoord, int overlayLight, List<BakedQuad> quads, @Nullable int[] tints) {
|
private static void applyTransformation(PoseStack stack, Transformation transformation) {
|
||||||
var matrix = transform.last();
|
|
||||||
|
|
||||||
for (var bakedquad : quads) {
|
|
||||||
var tint = -1;
|
|
||||||
if (tints != null && bakedquad.isTinted()) {
|
|
||||||
var idx = bakedquad.getTintIndex();
|
|
||||||
if (idx >= 0 && idx < tints.length) tint = tints[bakedquad.getTintIndex()];
|
|
||||||
}
|
|
||||||
|
|
||||||
var r = (float) (tint >> 16 & 255) / 255.0F;
|
|
||||||
var g = (float) (tint >> 8 & 255) / 255.0F;
|
|
||||||
var b = (float) (tint & 255) / 255.0F;
|
|
||||||
buffer.putBulkData(matrix, bakedquad, r, g, b, lightmapCoord, overlayLight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void pushPoseFromTransformation(PoseStack stack, Transformation transformation) {
|
|
||||||
stack.pushPose();
|
|
||||||
|
|
||||||
var trans = transformation.getTranslation();
|
var trans = transformation.getTranslation();
|
||||||
stack.translate(trans.x(), trans.y(), trans.z());
|
stack.translate(trans.x(), trans.y(), trans.z());
|
||||||
|
|
||||||
|
@@ -7,7 +7,10 @@ package dan200.computercraft.client.render.monitor;
|
|||||||
import com.mojang.blaze3d.platform.GlStateManager;
|
import com.mojang.blaze3d.platform.GlStateManager;
|
||||||
import com.mojang.blaze3d.platform.MemoryTracker;
|
import com.mojang.blaze3d.platform.MemoryTracker;
|
||||||
import com.mojang.blaze3d.systems.RenderSystem;
|
import com.mojang.blaze3d.systems.RenderSystem;
|
||||||
import com.mojang.blaze3d.vertex.*;
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
import com.mojang.blaze3d.vertex.Tesselator;
|
||||||
|
import com.mojang.blaze3d.vertex.VertexBuffer;
|
||||||
|
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||||
import com.mojang.math.Axis;
|
import com.mojang.math.Axis;
|
||||||
import dan200.computercraft.client.FrameInfo;
|
import dan200.computercraft.client.FrameInfo;
|
||||||
import dan200.computercraft.client.integration.ShaderMod;
|
import dan200.computercraft.client.integration.ShaderMod;
|
||||||
@@ -167,7 +170,7 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl
|
|||||||
tboVertex(buffer, matrix, -xMargin, pixelHeight + yMargin);
|
tboVertex(buffer, matrix, -xMargin, pixelHeight + yMargin);
|
||||||
tboVertex(buffer, matrix, pixelWidth + xMargin, -yMargin);
|
tboVertex(buffer, matrix, pixelWidth + xMargin, -yMargin);
|
||||||
tboVertex(buffer, matrix, pixelWidth + xMargin, pixelHeight + yMargin);
|
tboVertex(buffer, matrix, pixelWidth + xMargin, pixelHeight + yMargin);
|
||||||
RenderTypes.MONITOR_TBO.end(buffer, VertexSorting.DISTANCE_TO_ORIGIN);
|
RenderTypes.MONITOR_TBO.end(buffer, 0, 0, 0);
|
||||||
}
|
}
|
||||||
case VBO -> {
|
case VBO -> {
|
||||||
var backgroundBuffer = assertNonNull(renderState.backgroundBuffer);
|
var backgroundBuffer = assertNonNull(renderState.backgroundBuffer);
|
||||||
|
@@ -25,7 +25,6 @@ public class DirectVertexBuffer extends VertexBuffer {
|
|||||||
private int actualIndexCount;
|
private int actualIndexCount;
|
||||||
|
|
||||||
public DirectVertexBuffer() {
|
public DirectVertexBuffer() {
|
||||||
super(Usage.STATIC);
|
|
||||||
if (DirectBuffers.HAS_DSA) {
|
if (DirectBuffers.HAS_DSA) {
|
||||||
RenderSystem.glDeleteBuffers(vertexBufferId);
|
RenderSystem.glDeleteBuffers(vertexBufferId);
|
||||||
if (DirectBuffers.ON_LINUX) BufferUploader.reset(); // See comment on DirectBuffers.deleteBuffer.
|
if (DirectBuffers.ON_LINUX) BufferUploader.reset(); // See comment on DirectBuffers.deleteBuffer.
|
||||||
|
@@ -13,6 +13,9 @@ import dan200.computercraft.shared.turtle.upgrades.TurtleModem;
|
|||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link TurtleUpgradeModeller} for modems, providing different models depending on if the modem is on/off.
|
* A {@link TurtleUpgradeModeller} for modems, providing different models depending on if the modem is on/off.
|
||||||
*/
|
*/
|
||||||
@@ -48,4 +51,9 @@ public class TurtleModemModeller implements TurtleUpgradeModeller<TurtleModem> {
|
|||||||
? TransformedModel.of(active ? leftOnModel : leftOffModel)
|
? TransformedModel.of(active ? leftOnModel : leftOffModel)
|
||||||
: TransformedModel.of(active ? rightOnModel : rightOffModel);
|
: TransformedModel.of(active ? rightOnModel : rightOffModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<ResourceLocation> getDependencies() {
|
||||||
|
return List.of(leftOffModel, rightOffModel, leftOnModel, rightOnModel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -14,11 +14,13 @@ import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
|
|||||||
import dan200.computercraft.impl.TurtleUpgrades;
|
import dan200.computercraft.impl.TurtleUpgrades;
|
||||||
import dan200.computercraft.impl.UpgradeManager;
|
import dan200.computercraft.impl.UpgradeManager;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A registry of {@link TurtleUpgradeModeller}s.
|
* A registry of {@link TurtleUpgradeModeller}s.
|
||||||
@@ -52,12 +54,18 @@ public final class TurtleUpgradeModellers {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TransformedModel getModel(ITurtleUpgrade upgrade, @Nullable ITurtleAccess access, TurtleSide side) {
|
public static TransformedModel getModel(ITurtleUpgrade upgrade, ITurtleAccess access, TurtleSide side) {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
var modeller = (TurtleUpgradeModeller<ITurtleUpgrade>) modelCache.computeIfAbsent(upgrade, TurtleUpgradeModellers::getModeller);
|
var modeller = (TurtleUpgradeModeller<ITurtleUpgrade>) modelCache.computeIfAbsent(upgrade, TurtleUpgradeModellers::getModeller);
|
||||||
return modeller.getModel(upgrade, access, side);
|
return modeller.getModel(upgrade, access, side);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static TransformedModel getModel(ITurtleUpgrade upgrade, CompoundTag data, TurtleSide side) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
var modeller = (TurtleUpgradeModeller<ITurtleUpgrade>) modelCache.computeIfAbsent(upgrade, TurtleUpgradeModellers::getModeller);
|
||||||
|
return modeller.getModel(upgrade, data, side);
|
||||||
|
}
|
||||||
|
|
||||||
private static TurtleUpgradeModeller<?> getModeller(ITurtleUpgrade upgradeA) {
|
private static TurtleUpgradeModeller<?> getModeller(ITurtleUpgrade upgradeA) {
|
||||||
var wrapper = TurtleUpgrades.instance().getWrapper(upgradeA);
|
var wrapper = TurtleUpgrades.instance().getWrapper(upgradeA);
|
||||||
if (wrapper == null) return NULL_TURTLE_MODELLER;
|
if (wrapper == null) return NULL_TURTLE_MODELLER;
|
||||||
@@ -65,4 +73,8 @@ public final class TurtleUpgradeModellers {
|
|||||||
var modeller = turtleModels.get(wrapper.serialiser());
|
var modeller = turtleModels.get(wrapper.serialiser());
|
||||||
return modeller == null ? NULL_TURTLE_MODELLER : modeller;
|
return modeller == null ? NULL_TURTLE_MODELLER : modeller;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Stream<ResourceLocation> getDependencies() {
|
||||||
|
return turtleModels.values().stream().flatMap(x -> x.getDependencies().stream());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,34 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.data.client;
|
||||||
|
|
||||||
|
import dan200.computercraft.data.DataProviders;
|
||||||
|
import dan200.computercraft.shared.turtle.inventory.UpgradeSlot;
|
||||||
|
import net.minecraft.client.renderer.texture.atlas.SpriteSources;
|
||||||
|
import net.minecraft.client.renderer.texture.atlas.sources.SingleFile;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.server.packs.PackType;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A version of {@link DataProviders} which relies on client-side classes.
|
||||||
|
* <p>
|
||||||
|
* This is called from {@link DataProviders#add(DataProviders.GeneratorSink)}.
|
||||||
|
*/
|
||||||
|
public final class ClientDataProviders {
|
||||||
|
private ClientDataProviders() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void add(DataProviders.GeneratorSink generator) {
|
||||||
|
generator.addFromCodec("Block atlases", PackType.CLIENT_RESOURCES, "atlases", SpriteSources.FILE_CODEC, out -> {
|
||||||
|
out.accept(new ResourceLocation("blocks"), List.of(
|
||||||
|
new SingleFile(UpgradeSlot.LEFT_UPGRADE, Optional.empty()),
|
||||||
|
new SingleFile(UpgradeSlot.RIGHT_UPGRADE, Optional.empty())
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -37,6 +37,14 @@ import static net.minecraft.data.models.model.ModelLocationUtils.getModelLocatio
|
|||||||
import static net.minecraft.data.models.model.TextureMapping.getBlockTexture;
|
import static net.minecraft.data.models.model.TextureMapping.getBlockTexture;
|
||||||
|
|
||||||
class BlockModelProvider {
|
class BlockModelProvider {
|
||||||
|
private static final TextureSlot CURSOR = TextureSlot.create("cursor");
|
||||||
|
|
||||||
|
private static final ModelTemplate COMPUTER_ON = new ModelTemplate(
|
||||||
|
Optional.of(new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/computer_on")),
|
||||||
|
Optional.empty(),
|
||||||
|
TextureSlot.FRONT, TextureSlot.SIDE, TextureSlot.TOP, CURSOR
|
||||||
|
);
|
||||||
|
|
||||||
private static final ModelTemplate MONITOR_BASE = new ModelTemplate(
|
private static final ModelTemplate MONITOR_BASE = new ModelTemplate(
|
||||||
Optional.of(new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/monitor_base")),
|
Optional.of(new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/monitor_base")),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
@@ -142,11 +150,18 @@ class BlockModelProvider {
|
|||||||
private static void registerComputer(BlockModelGenerators generators, ComputerBlock<?> block) {
|
private static void registerComputer(BlockModelGenerators generators, ComputerBlock<?> block) {
|
||||||
generators.blockStateOutput.accept(MultiVariantGenerator.multiVariant(block)
|
generators.blockStateOutput.accept(MultiVariantGenerator.multiVariant(block)
|
||||||
.with(createHorizontalFacingDispatch())
|
.with(createHorizontalFacingDispatch())
|
||||||
.with(createModelDispatch(ComputerBlock.STATE, state -> ModelTemplates.CUBE_ORIENTABLE.createWithSuffix(
|
.with(createModelDispatch(ComputerBlock.STATE, state -> switch (state) {
|
||||||
block, "_" + state.getSerializedName(),
|
case OFF -> ModelTemplates.CUBE_ORIENTABLE.createWithSuffix(
|
||||||
TextureMapping.orientableCube(block).put(TextureSlot.FRONT, getBlockTexture(block, "_front" + state.getTexture())),
|
block, "_" + state.getSerializedName(),
|
||||||
generators.modelOutput
|
TextureMapping.orientableCube(block),
|
||||||
)))
|
generators.modelOutput
|
||||||
|
);
|
||||||
|
case ON, BLINKING -> COMPUTER_ON.createWithSuffix(
|
||||||
|
block, "_" + state.getSerializedName(),
|
||||||
|
TextureMapping.orientableCube(block).put(CURSOR, new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/computer" + state.getTexture())),
|
||||||
|
generators.modelOutput
|
||||||
|
);
|
||||||
|
}))
|
||||||
);
|
);
|
||||||
generators.delegateItemModel(block, getModelLocation(block, "_blinking"));
|
generators.delegateItemModel(block, getModelLocation(block, "_blinking"));
|
||||||
}
|
}
|
||||||
|
@@ -4,16 +4,20 @@
|
|||||||
|
|
||||||
package dan200.computercraft.data;
|
package dan200.computercraft.data;
|
||||||
|
|
||||||
|
import com.mojang.serialization.Codec;
|
||||||
import net.minecraft.data.DataProvider;
|
import net.minecraft.data.DataProvider;
|
||||||
import net.minecraft.data.PackOutput;
|
import net.minecraft.data.PackOutput;
|
||||||
import net.minecraft.data.loot.LootTableProvider.SubProviderEntry;
|
import net.minecraft.data.loot.LootTableProvider.SubProviderEntry;
|
||||||
import net.minecraft.data.models.BlockModelGenerators;
|
import net.minecraft.data.models.BlockModelGenerators;
|
||||||
import net.minecraft.data.models.ItemModelGenerators;
|
import net.minecraft.data.models.ItemModelGenerators;
|
||||||
import net.minecraft.data.tags.TagsProvider;
|
import net.minecraft.data.tags.TagsProvider;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.server.packs.PackType;
|
||||||
import net.minecraft.world.item.Item;
|
import net.minecraft.world.item.Item;
|
||||||
import net.minecraft.world.level.block.Block;
|
import net.minecraft.world.level.block.Block;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -37,11 +41,22 @@ public final class DataProviders {
|
|||||||
generator.models(BlockModelProvider::addBlockModels, ItemModelProvider::addItemModels);
|
generator.models(BlockModelProvider::addBlockModels, ItemModelProvider::addItemModels);
|
||||||
|
|
||||||
generator.add(out -> new LanguageProvider(out, turtleUpgrades, pocketUpgrades));
|
generator.add(out -> new LanguageProvider(out, turtleUpgrades, pocketUpgrades));
|
||||||
|
|
||||||
|
// Unfortunately we rely on some client-side classes in this code. We just load in the client side data provider
|
||||||
|
// and invoke that.
|
||||||
|
try {
|
||||||
|
Class.forName("dan200.computercraft.data.client.ClientDataProviders")
|
||||||
|
.getMethod("add", GeneratorSink.class).invoke(null, generator);
|
||||||
|
} catch (ReflectiveOperationException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GeneratorSink {
|
public interface GeneratorSink {
|
||||||
<T extends DataProvider> T add(DataProvider.Factory<T> factory);
|
<T extends DataProvider> T add(DataProvider.Factory<T> factory);
|
||||||
|
|
||||||
|
<T> void addFromCodec(String name, PackType type, String directory, Codec<T> codec, Consumer<BiConsumer<ResourceLocation, T>> output);
|
||||||
|
|
||||||
void lootTable(List<SubProviderEntry> tables);
|
void lootTable(List<SubProviderEntry> tables);
|
||||||
|
|
||||||
TagsProvider<Block> blockTags(Consumer<TagProvider.TagConsumer<Block>> tags);
|
TagsProvider<Block> blockTags(Consumer<TagProvider.TagConsumer<Block>> tags);
|
||||||
|
@@ -6,6 +6,7 @@ package dan200.computercraft.data;
|
|||||||
|
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import dan200.computercraft.api.ComputerCraftAPI;
|
import dan200.computercraft.api.ComputerCraftAPI;
|
||||||
|
import dan200.computercraft.api.ComputerCraftTags;
|
||||||
import dan200.computercraft.api.pocket.PocketUpgradeDataProvider;
|
import dan200.computercraft.api.pocket.PocketUpgradeDataProvider;
|
||||||
import dan200.computercraft.api.turtle.TurtleUpgradeDataProvider;
|
import dan200.computercraft.api.turtle.TurtleUpgradeDataProvider;
|
||||||
import dan200.computercraft.api.upgrades.UpgradeBase;
|
import dan200.computercraft.api.upgrades.UpgradeBase;
|
||||||
@@ -20,6 +21,7 @@ import dan200.computercraft.shared.platform.RegistryWrappers;
|
|||||||
import net.minecraft.data.CachedOutput;
|
import net.minecraft.data.CachedOutput;
|
||||||
import net.minecraft.data.DataProvider;
|
import net.minecraft.data.DataProvider;
|
||||||
import net.minecraft.data.PackOutput;
|
import net.minecraft.data.PackOutput;
|
||||||
|
import net.minecraft.tags.TagKey;
|
||||||
import net.minecraft.world.item.Item;
|
import net.minecraft.world.item.Item;
|
||||||
import net.minecraft.world.level.block.Block;
|
import net.minecraft.world.level.block.Block;
|
||||||
|
|
||||||
@@ -97,6 +99,12 @@ public final class LanguageProvider implements DataProvider {
|
|||||||
add(ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get(), "Advanced Pocket Computer");
|
add(ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get(), "Advanced Pocket Computer");
|
||||||
add(ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get().getDescriptionId() + ".upgraded", "Advanced %s Pocket Computer");
|
add(ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get().getDescriptionId() + ".upgraded", "Advanced %s Pocket Computer");
|
||||||
|
|
||||||
|
// Tags (for EMI)
|
||||||
|
add(ComputerCraftTags.Items.COMPUTER, "Computers");
|
||||||
|
add(ComputerCraftTags.Items.TURTLE, "Turtles");
|
||||||
|
add(ComputerCraftTags.Items.WIRED_MODEM, "Wired modems");
|
||||||
|
add(ComputerCraftTags.Items.MONITOR, "Monitors");
|
||||||
|
|
||||||
// Turtle/pocket upgrades
|
// Turtle/pocket upgrades
|
||||||
add("upgrade.minecraft.diamond_sword.adjective", "Melee");
|
add("upgrade.minecraft.diamond_sword.adjective", "Melee");
|
||||||
add("upgrade.minecraft.diamond_shovel.adjective", "Digging");
|
add("upgrade.minecraft.diamond_shovel.adjective", "Digging");
|
||||||
@@ -150,9 +158,6 @@ public final class LanguageProvider implements DataProvider {
|
|||||||
add("commands.computercraft.track.dump.desc", "Dump the latest results of computer tracking.");
|
add("commands.computercraft.track.dump.desc", "Dump the latest results of computer tracking.");
|
||||||
add("commands.computercraft.track.dump.no_timings", "No timings available");
|
add("commands.computercraft.track.dump.no_timings", "No timings available");
|
||||||
add("commands.computercraft.track.dump.computer", "Computer");
|
add("commands.computercraft.track.dump.computer", "Computer");
|
||||||
add("commands.computercraft.reload.synopsis", "Reload the ComputerCraft config file");
|
|
||||||
add("commands.computercraft.reload.desc", "Reload the ComputerCraft config file");
|
|
||||||
add("commands.computercraft.reload.done", "Reloaded config");
|
|
||||||
add("commands.computercraft.queue.synopsis", "Send a computer_command event to a command computer");
|
add("commands.computercraft.queue.synopsis", "Send a computer_command event to a command computer");
|
||||||
add("commands.computercraft.queue.desc", "Send a computer_command event to a command computer, passing through the additional arguments. This is mostly designed for map makers, acting as a more computer-friendly version of /trigger. Any player can run the command, which would most likely be done through a text component's click event.");
|
add("commands.computercraft.queue.desc", "Send a computer_command event to a command computer, passing through the additional arguments. This is mostly designed for map makers, acting as a more computer-friendly version of /trigger. Any player can run the command, which would most likely be done through a text component's click event.");
|
||||||
|
|
||||||
@@ -214,6 +219,7 @@ public final class LanguageProvider implements DataProvider {
|
|||||||
addConfigEntry(ConfigSpec.defaultComputerSettings, "Default Computer settings");
|
addConfigEntry(ConfigSpec.defaultComputerSettings, "Default Computer settings");
|
||||||
addConfigEntry(ConfigSpec.logComputerErrors, "Log computer errors");
|
addConfigEntry(ConfigSpec.logComputerErrors, "Log computer errors");
|
||||||
addConfigEntry(ConfigSpec.commandRequireCreative, "Command computers require creative");
|
addConfigEntry(ConfigSpec.commandRequireCreative, "Command computers require creative");
|
||||||
|
addConfigEntry(ConfigSpec.disabledGenericMethods, "Disabled generic methods");
|
||||||
|
|
||||||
addConfigGroup(ConfigSpec.serverSpec, "execution", "Execution");
|
addConfigGroup(ConfigSpec.serverSpec, "execution", "Execution");
|
||||||
addConfigEntry(ConfigSpec.computerThreads, "Computer threads");
|
addConfigEntry(ConfigSpec.computerThreads, "Computer threads");
|
||||||
@@ -277,8 +283,8 @@ public final class LanguageProvider implements DataProvider {
|
|||||||
turtleUpgrades.getGeneratedUpgrades().stream().map(UpgradeBase::getUnlocalisedAdjective),
|
turtleUpgrades.getGeneratedUpgrades().stream().map(UpgradeBase::getUnlocalisedAdjective),
|
||||||
pocketUpgrades.getGeneratedUpgrades().stream().map(UpgradeBase::getUnlocalisedAdjective),
|
pocketUpgrades.getGeneratedUpgrades().stream().map(UpgradeBase::getUnlocalisedAdjective),
|
||||||
Metric.metrics().values().stream().map(x -> AggregatedMetric.TRANSLATION_PREFIX + x.name() + ".name"),
|
Metric.metrics().values().stream().map(x -> AggregatedMetric.TRANSLATION_PREFIX + x.name() + ".name"),
|
||||||
getConfigEntries(ConfigSpec.serverSpec).map(ConfigFile.Entry::translationKey),
|
ConfigSpec.serverSpec.entries().map(ConfigFile.Entry::translationKey),
|
||||||
getConfigEntries(ConfigSpec.clientSpec).map(ConfigFile.Entry::translationKey)
|
ConfigSpec.clientSpec.entries().map(ConfigFile.Entry::translationKey)
|
||||||
).flatMap(x -> x);
|
).flatMap(x -> x);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,6 +304,10 @@ public final class LanguageProvider implements DataProvider {
|
|||||||
add(AggregatedMetric.TRANSLATION_PREFIX + metric.name() + ".name", text);
|
add(AggregatedMetric.TRANSLATION_PREFIX + metric.name() + ".name", text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void add(TagKey<Item> tag, String text) {
|
||||||
|
add("tag.item." + tag.location().getNamespace() + "." + tag.location().getPath(), text);
|
||||||
|
}
|
||||||
|
|
||||||
private void addConfigGroup(ConfigFile spec, String path, String text) {
|
private void addConfigGroup(ConfigFile spec, String path, String text) {
|
||||||
var entry = spec.getEntry(path);
|
var entry = spec.getEntry(path);
|
||||||
if (!(entry instanceof ConfigFile.Group)) throw new IllegalArgumentException("Cannot find group " + path);
|
if (!(entry instanceof ConfigFile.Group)) throw new IllegalArgumentException("Cannot find group " + path);
|
||||||
@@ -308,16 +318,4 @@ public final class LanguageProvider implements DataProvider {
|
|||||||
add(value.translationKey(), text);
|
add(value.translationKey(), text);
|
||||||
add(value.translationKey() + ".tooltip", value.comment());
|
add(value.translationKey() + ".tooltip", value.comment());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Stream<ConfigFile.Entry> getConfigEntries(ConfigFile spec) {
|
|
||||||
return spec.entries().flatMap(LanguageProvider::getConfigEntries);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Stream<ConfigFile.Entry> getConfigEntries(ConfigFile.Entry entry) {
|
|
||||||
if (entry instanceof ConfigFile.Value<?>) return Stream.of(entry);
|
|
||||||
if (entry instanceof ConfigFile.Group group) {
|
|
||||||
return Stream.concat(Stream.of(entry), group.children().flatMap(LanguageProvider::getConfigEntries));
|
|
||||||
}
|
|
||||||
throw new IllegalStateException("Invalid config entry " + entry);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -23,7 +23,7 @@ import net.minecraft.world.level.storage.loot.entries.LootItem;
|
|||||||
import net.minecraft.world.level.storage.loot.entries.LootPoolEntryContainer;
|
import net.minecraft.world.level.storage.loot.entries.LootPoolEntryContainer;
|
||||||
import net.minecraft.world.level.storage.loot.functions.CopyNameFunction;
|
import net.minecraft.world.level.storage.loot.functions.CopyNameFunction;
|
||||||
import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets;
|
import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets;
|
||||||
import net.minecraft.world.level.storage.loot.predicates.AnyOfCondition;
|
import net.minecraft.world.level.storage.loot.predicates.AlternativeLootItemCondition;
|
||||||
import net.minecraft.world.level.storage.loot.predicates.ExplosionCondition;
|
import net.minecraft.world.level.storage.loot.predicates.ExplosionCondition;
|
||||||
import net.minecraft.world.level.storage.loot.predicates.LootItemBlockStatePropertyCondition;
|
import net.minecraft.world.level.storage.loot.predicates.LootItemBlockStatePropertyCondition;
|
||||||
import net.minecraft.world.level.storage.loot.predicates.LootItemCondition;
|
import net.minecraft.world.level.storage.loot.predicates.LootItemCondition;
|
||||||
@@ -79,7 +79,7 @@ class LootTableProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void registerGeneric(BiConsumer<ResourceLocation, LootTable.Builder> add) {
|
private static void registerGeneric(BiConsumer<ResourceLocation, LootTable.Builder> add) {
|
||||||
add.accept(CommonHooks.TREASURE_DISK_LOOT, LootTable.lootTable());
|
add.accept(CommonHooks.LOOT_TREASURE_DISK, LootTable.lootTable());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void selfDrop(BiConsumer<ResourceLocation, LootTable.Builder> add, Supplier<? extends Block> wrapper) {
|
private static void selfDrop(BiConsumer<ResourceLocation, LootTable.Builder> add, Supplier<? extends Block> wrapper) {
|
||||||
@@ -98,7 +98,7 @@ class LootTableProvider {
|
|||||||
blockDrop(
|
blockDrop(
|
||||||
add, block,
|
add, block,
|
||||||
DynamicLoot.dynamicEntry(new ResourceLocation(ComputerCraftAPI.MOD_ID, "computer")),
|
DynamicLoot.dynamicEntry(new ResourceLocation(ComputerCraftAPI.MOD_ID, "computer")),
|
||||||
AnyOfCondition.anyOf(
|
AlternativeLootItemCondition.alternative(
|
||||||
BlockNamedEntityLootCondition.BUILDER,
|
BlockNamedEntityLootCondition.BUILDER,
|
||||||
HasComputerIdLootCondition.BUILDER,
|
HasComputerIdLootCondition.BUILDER,
|
||||||
PlayerCreativeLootCondition.BUILDER.invert()
|
PlayerCreativeLootCondition.BUILDER.invert()
|
||||||
|
@@ -8,6 +8,7 @@ import com.google.gson.JsonObject;
|
|||||||
import dan200.computercraft.api.ComputerCraftAPI;
|
import dan200.computercraft.api.ComputerCraftAPI;
|
||||||
import dan200.computercraft.api.pocket.PocketUpgradeDataProvider;
|
import dan200.computercraft.api.pocket.PocketUpgradeDataProvider;
|
||||||
import dan200.computercraft.api.turtle.TurtleUpgradeDataProvider;
|
import dan200.computercraft.api.turtle.TurtleUpgradeDataProvider;
|
||||||
|
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||||
import dan200.computercraft.core.util.Colour;
|
import dan200.computercraft.core.util.Colour;
|
||||||
import dan200.computercraft.shared.ModRegistry;
|
import dan200.computercraft.shared.ModRegistry;
|
||||||
import dan200.computercraft.shared.common.IColouredItem;
|
import dan200.computercraft.shared.common.IColouredItem;
|
||||||
@@ -110,7 +111,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
|||||||
var nameId = turtleItem.getFamily().name().toLowerCase(Locale.ROOT);
|
var nameId = turtleItem.getFamily().name().toLowerCase(Locale.ROOT);
|
||||||
|
|
||||||
for (var upgrade : turtleUpgrades.getGeneratedUpgrades()) {
|
for (var upgrade : turtleUpgrades.getGeneratedUpgrades()) {
|
||||||
var result = turtleItem.create(-1, null, -1, null, upgrade, -1, null);
|
var result = turtleItem.create(-1, null, -1, null, UpgradeData.ofDefault(upgrade), -1, null);
|
||||||
ShapedRecipeBuilder
|
ShapedRecipeBuilder
|
||||||
.shaped(RecipeCategory.REDSTONE, result.getItem())
|
.shaped(RecipeCategory.REDSTONE, result.getItem())
|
||||||
.group(String.format("%s:turtle_%s", ComputerCraftAPI.MOD_ID, nameId))
|
.group(String.format("%s:turtle_%s", ComputerCraftAPI.MOD_ID, nameId))
|
||||||
@@ -146,7 +147,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
|||||||
var nameId = pocket.getFamily().name().toLowerCase(Locale.ROOT);
|
var nameId = pocket.getFamily().name().toLowerCase(Locale.ROOT);
|
||||||
|
|
||||||
for (var upgrade : pocketUpgrades.getGeneratedUpgrades()) {
|
for (var upgrade : pocketUpgrades.getGeneratedUpgrades()) {
|
||||||
var result = pocket.create(-1, null, -1, upgrade);
|
var result = pocket.create(-1, null, -1, UpgradeData.ofDefault(upgrade));
|
||||||
ShapedRecipeBuilder
|
ShapedRecipeBuilder
|
||||||
.shaped(RecipeCategory.REDSTONE, result.getItem())
|
.shaped(RecipeCategory.REDSTONE, result.getItem())
|
||||||
.group(String.format("%s:pocket_%s", ComputerCraftAPI.MOD_ID, nameId))
|
.group(String.format("%s:pocket_%s", ComputerCraftAPI.MOD_ID, nameId))
|
||||||
|
@@ -19,8 +19,6 @@ import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
|
|||||||
import dan200.computercraft.api.redstone.BundledRedstoneProvider;
|
import dan200.computercraft.api.redstone.BundledRedstoneProvider;
|
||||||
import dan200.computercraft.api.turtle.TurtleRefuelHandler;
|
import dan200.computercraft.api.turtle.TurtleRefuelHandler;
|
||||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
|
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
|
||||||
import dan200.computercraft.core.apis.ApiFactories;
|
|
||||||
import dan200.computercraft.core.asm.GenericMethod;
|
|
||||||
import dan200.computercraft.core.filesystem.WritableFileMount;
|
import dan200.computercraft.core.filesystem.WritableFileMount;
|
||||||
import dan200.computercraft.impl.detail.DetailRegistryImpl;
|
import dan200.computercraft.impl.detail.DetailRegistryImpl;
|
||||||
import dan200.computercraft.impl.network.wired.WiredNodeImpl;
|
import dan200.computercraft.impl.network.wired.WiredNodeImpl;
|
||||||
@@ -79,7 +77,7 @@ public abstract class AbstractComputerCraftAPI implements ComputerCraftAPIServic
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void registerGenericSource(GenericSource source) {
|
public final void registerGenericSource(GenericSource source) {
|
||||||
GenericMethod.register(source);
|
GenericSources.register(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
package dan200.computercraft.core.apis;
|
package dan200.computercraft.impl;
|
||||||
|
|
||||||
import dan200.computercraft.api.lua.ILuaAPIFactory;
|
import dan200.computercraft.api.lua.ILuaAPIFactory;
|
||||||
|
|
||||||
@@ -11,19 +11,24 @@ import java.util.Collections;
|
|||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The global factory for {@link ILuaAPIFactory}s.
|
||||||
|
*
|
||||||
|
* @see dan200.computercraft.core.ComputerContext.Builder#apiFactories(Collection)
|
||||||
|
* @see dan200.computercraft.api.ComputerCraftAPI#registerAPIFactory(ILuaAPIFactory)
|
||||||
|
*/
|
||||||
public final class ApiFactories {
|
public final class ApiFactories {
|
||||||
private ApiFactories() {
|
private ApiFactories() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Collection<ILuaAPIFactory> factories = new LinkedHashSet<>();
|
private static final Collection<ILuaAPIFactory> factories = new LinkedHashSet<>();
|
||||||
private static final Collection<ILuaAPIFactory> factoriesView = Collections.unmodifiableCollection(factories);
|
|
||||||
|
|
||||||
public static synchronized void register(ILuaAPIFactory factory) {
|
static synchronized void register(ILuaAPIFactory factory) {
|
||||||
Objects.requireNonNull(factory, "provider cannot be null");
|
Objects.requireNonNull(factory, "provider cannot be null");
|
||||||
factories.add(factory);
|
factories.add(factory);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Iterable<ILuaAPIFactory> getAll() {
|
public static Collection<ILuaAPIFactory> getAll() {
|
||||||
return factoriesView;
|
return Collections.unmodifiableCollection(factories);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -0,0 +1,41 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.impl;
|
||||||
|
|
||||||
|
import dan200.computercraft.api.lua.GenericSource;
|
||||||
|
import dan200.computercraft.core.asm.GenericMethod;
|
||||||
|
import dan200.computercraft.shared.config.ConfigSpec;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The global registry for {@link GenericSource}s.
|
||||||
|
*
|
||||||
|
* @see dan200.computercraft.core.ComputerContext.Builder#genericMethods(Collection)
|
||||||
|
* @see dan200.computercraft.api.ComputerCraftAPI#registerGenericSource(GenericSource)
|
||||||
|
*/
|
||||||
|
public final class GenericSources {
|
||||||
|
private GenericSources() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Collection<GenericSource> sources = new LinkedHashSet<>();
|
||||||
|
|
||||||
|
static synchronized void register(GenericSource source) {
|
||||||
|
Objects.requireNonNull(source, "provider cannot be null");
|
||||||
|
sources.add(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Collection<GenericMethod> getAllMethods() {
|
||||||
|
var disabledMethods = Set.copyOf(ConfigSpec.disabledGenericMethods.get());
|
||||||
|
return sources.stream()
|
||||||
|
.filter(x -> !disabledMethods.contains(x.id()))
|
||||||
|
.flatMap(GenericMethod::getMethods)
|
||||||
|
.filter(x -> !disabledMethods.contains(x.id()))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
@@ -7,6 +7,7 @@ package dan200.computercraft.impl;
|
|||||||
import com.google.gson.*;
|
import com.google.gson.*;
|
||||||
import dan200.computercraft.api.ComputerCraftAPI;
|
import dan200.computercraft.api.ComputerCraftAPI;
|
||||||
import dan200.computercraft.api.upgrades.UpgradeBase;
|
import dan200.computercraft.api.upgrades.UpgradeBase;
|
||||||
|
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||||
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
|
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
|
||||||
import dan200.computercraft.shared.platform.PlatformHelper;
|
import dan200.computercraft.shared.platform.PlatformHelper;
|
||||||
import net.minecraft.core.Registry;
|
import net.minecraft.core.Registry;
|
||||||
@@ -74,13 +75,13 @@ public class UpgradeManager<R extends UpgradeSerialiser<? extends T>, T extends
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public T get(ItemStack stack) {
|
public UpgradeData<T> get(ItemStack stack) {
|
||||||
if (stack.isEmpty()) return null;
|
if (stack.isEmpty()) return null;
|
||||||
|
|
||||||
for (var wrapper : current.values()) {
|
for (var wrapper : current.values()) {
|
||||||
var craftingStack = wrapper.upgrade().getCraftingItem();
|
var craftingStack = wrapper.upgrade().getCraftingItem();
|
||||||
if (!craftingStack.isEmpty() && craftingStack.getItem() == stack.getItem() && wrapper.upgrade().isItemSuitable(stack)) {
|
if (!craftingStack.isEmpty() && craftingStack.getItem() == stack.getItem() && wrapper.upgrade().isItemSuitable(stack)) {
|
||||||
return wrapper.upgrade();
|
return UpgradeData.of(wrapper.upgrade, wrapper.upgrade.getUpgradeData(stack));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -23,7 +23,13 @@ import net.minecraft.world.entity.Entity;
|
|||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
import net.minecraft.world.level.chunk.LevelChunk;
|
import net.minecraft.world.level.chunk.LevelChunk;
|
||||||
import net.minecraft.world.level.storage.loot.BuiltInLootTables;
|
import net.minecraft.world.level.storage.loot.BuiltInLootTables;
|
||||||
|
import net.minecraft.world.level.storage.loot.LootPool;
|
||||||
|
import net.minecraft.world.level.storage.loot.entries.LootTableReference;
|
||||||
|
import net.minecraft.world.level.storage.loot.providers.number.ConstantValue;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
@@ -69,9 +75,9 @@ public final class CommonHooks {
|
|||||||
MonitorWatcher.onWatch(chunk, player);
|
MonitorWatcher.onWatch(chunk, player);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final ResourceLocation TREASURE_DISK_LOOT = new ResourceLocation(ComputerCraftAPI.MOD_ID, "treasure_disk");
|
public static final ResourceLocation LOOT_TREASURE_DISK = new ResourceLocation(ComputerCraftAPI.MOD_ID, "treasure_disk");
|
||||||
|
|
||||||
public static final Set<ResourceLocation> TREASURE_DISK_LOOT_TABLES = Set.of(
|
private static final Set<ResourceLocation> TABLES = new HashSet<>(Arrays.asList(
|
||||||
BuiltInLootTables.SIMPLE_DUNGEON,
|
BuiltInLootTables.SIMPLE_DUNGEON,
|
||||||
BuiltInLootTables.ABANDONED_MINESHAFT,
|
BuiltInLootTables.ABANDONED_MINESHAFT,
|
||||||
BuiltInLootTables.STRONGHOLD_CORRIDOR,
|
BuiltInLootTables.STRONGHOLD_CORRIDOR,
|
||||||
@@ -82,7 +88,16 @@ public final class CommonHooks {
|
|||||||
BuiltInLootTables.IGLOO_CHEST,
|
BuiltInLootTables.IGLOO_CHEST,
|
||||||
BuiltInLootTables.WOODLAND_MANSION,
|
BuiltInLootTables.WOODLAND_MANSION,
|
||||||
BuiltInLootTables.VILLAGE_CARTOGRAPHER
|
BuiltInLootTables.VILLAGE_CARTOGRAPHER
|
||||||
);
|
));
|
||||||
|
|
||||||
|
|
||||||
|
public static @Nullable LootPool.Builder getExtraLootPool(ResourceLocation lootTable) {
|
||||||
|
if (!lootTable.getNamespace().equals("minecraft") || !TABLES.contains(lootTable)) return null;
|
||||||
|
|
||||||
|
return LootPool.lootPool()
|
||||||
|
.add(LootTableReference.lootTableReference(LOOT_TREASURE_DISK))
|
||||||
|
.setRolls(ConstantValue.exactly(1));
|
||||||
|
}
|
||||||
|
|
||||||
public static void onDatapackReload(BiConsumer<String, PreparableReloadListener> addReload) {
|
public static void onDatapackReload(BiConsumer<String, PreparableReloadListener> addReload) {
|
||||||
addReload.accept("mounts", ResourceMount.RELOAD_LISTENER);
|
addReload.accept("mounts", ResourceMount.RELOAD_LISTENER);
|
||||||
|
@@ -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;
|
||||||
@@ -93,7 +94,7 @@ import net.minecraft.world.level.block.entity.BlockEntity;
|
|||||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||||
import net.minecraft.world.level.block.state.BlockState;
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
import net.minecraft.world.level.material.MapColor;
|
import net.minecraft.world.level.material.Material;
|
||||||
import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType;
|
import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType;
|
||||||
|
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
@@ -112,7 +113,7 @@ public final class ModRegistry {
|
|||||||
static final RegistrationHelper<Block> REGISTRY = PlatformHelper.get().createRegistrationHelper(Registries.BLOCK);
|
static final RegistrationHelper<Block> REGISTRY = PlatformHelper.get().createRegistrationHelper(Registries.BLOCK);
|
||||||
|
|
||||||
private static BlockBehaviour.Properties properties() {
|
private static BlockBehaviour.Properties properties() {
|
||||||
return BlockBehaviour.Properties.of().strength(2);
|
return BlockBehaviour.Properties.of(Material.STONE).strength(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BlockBehaviour.Properties computerProperties() {
|
private static BlockBehaviour.Properties computerProperties() {
|
||||||
@@ -122,17 +123,17 @@ public final class ModRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static BlockBehaviour.Properties turtleProperties() {
|
private static BlockBehaviour.Properties turtleProperties() {
|
||||||
return BlockBehaviour.Properties.of().strength(2.5f);
|
return BlockBehaviour.Properties.of(Material.STONE).strength(2.5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BlockBehaviour.Properties modemProperties() {
|
private static BlockBehaviour.Properties modemProperties() {
|
||||||
return BlockBehaviour.Properties.of().strength(1.5f);
|
return BlockBehaviour.Properties.of(Material.STONE).strength(1.5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final RegistryEntry<ComputerBlock<ComputerBlockEntity>> COMPUTER_NORMAL = REGISTRY.register("computer_normal",
|
public static final RegistryEntry<ComputerBlock<ComputerBlockEntity>> COMPUTER_NORMAL = REGISTRY.register("computer_normal",
|
||||||
() -> new ComputerBlock<>(computerProperties().mapColor(MapColor.STONE), ComputerFamily.NORMAL, BlockEntities.COMPUTER_NORMAL));
|
() -> new ComputerBlock<>(computerProperties(), ComputerFamily.NORMAL, BlockEntities.COMPUTER_NORMAL));
|
||||||
public static final RegistryEntry<ComputerBlock<ComputerBlockEntity>> COMPUTER_ADVANCED = REGISTRY.register("computer_advanced",
|
public static final RegistryEntry<ComputerBlock<ComputerBlockEntity>> COMPUTER_ADVANCED = REGISTRY.register("computer_advanced",
|
||||||
() -> new ComputerBlock<>(computerProperties().mapColor(MapColor.GOLD), ComputerFamily.ADVANCED, BlockEntities.COMPUTER_ADVANCED));
|
() -> new ComputerBlock<>(computerProperties(), ComputerFamily.ADVANCED, BlockEntities.COMPUTER_ADVANCED));
|
||||||
|
|
||||||
public static final RegistryEntry<ComputerBlock<CommandComputerBlockEntity>> COMPUTER_COMMAND = REGISTRY.register("computer_command", () -> new ComputerBlock<>(
|
public static final RegistryEntry<ComputerBlock<CommandComputerBlockEntity>> COMPUTER_COMMAND = REGISTRY.register("computer_command", () -> new ComputerBlock<>(
|
||||||
computerProperties().strength(-1, 6000000.0F),
|
computerProperties().strength(-1, 6000000.0F),
|
||||||
@@ -140,27 +141,27 @@ public final class ModRegistry {
|
|||||||
));
|
));
|
||||||
|
|
||||||
public static final RegistryEntry<TurtleBlock> TURTLE_NORMAL = REGISTRY.register("turtle_normal",
|
public static final RegistryEntry<TurtleBlock> TURTLE_NORMAL = REGISTRY.register("turtle_normal",
|
||||||
() -> new TurtleBlock(turtleProperties().mapColor(MapColor.STONE), ComputerFamily.NORMAL, BlockEntities.TURTLE_NORMAL));
|
() -> new TurtleBlock(turtleProperties(), ComputerFamily.NORMAL, BlockEntities.TURTLE_NORMAL));
|
||||||
public static final RegistryEntry<TurtleBlock> TURTLE_ADVANCED = REGISTRY.register("turtle_advanced",
|
public static final RegistryEntry<TurtleBlock> TURTLE_ADVANCED = REGISTRY.register("turtle_advanced",
|
||||||
() -> new TurtleBlock(turtleProperties().mapColor(MapColor.GOLD), ComputerFamily.ADVANCED, BlockEntities.TURTLE_ADVANCED));
|
() -> new TurtleBlock(turtleProperties(), ComputerFamily.ADVANCED, BlockEntities.TURTLE_ADVANCED));
|
||||||
|
|
||||||
public static final RegistryEntry<SpeakerBlock> SPEAKER = REGISTRY.register("speaker", () -> new SpeakerBlock(properties().mapColor(MapColor.STONE)));
|
public static final RegistryEntry<SpeakerBlock> SPEAKER = REGISTRY.register("speaker", () -> new SpeakerBlock(properties()));
|
||||||
public static final RegistryEntry<DiskDriveBlock> DISK_DRIVE = REGISTRY.register("disk_drive", () -> new DiskDriveBlock(properties().mapColor(MapColor.STONE)));
|
public static final RegistryEntry<DiskDriveBlock> DISK_DRIVE = REGISTRY.register("disk_drive", () -> new DiskDriveBlock(properties()));
|
||||||
public static final RegistryEntry<PrinterBlock> PRINTER = REGISTRY.register("printer", () -> new PrinterBlock(properties().mapColor(MapColor.STONE)));
|
public static final RegistryEntry<PrinterBlock> PRINTER = REGISTRY.register("printer", () -> new PrinterBlock(properties()));
|
||||||
|
|
||||||
public static final RegistryEntry<MonitorBlock> MONITOR_NORMAL = REGISTRY.register("monitor_normal",
|
public static final RegistryEntry<MonitorBlock> MONITOR_NORMAL = REGISTRY.register("monitor_normal",
|
||||||
() -> new MonitorBlock(properties().mapColor(MapColor.STONE), BlockEntities.MONITOR_NORMAL));
|
() -> new MonitorBlock(properties(), BlockEntities.MONITOR_NORMAL));
|
||||||
public static final RegistryEntry<MonitorBlock> MONITOR_ADVANCED = REGISTRY.register("monitor_advanced",
|
public static final RegistryEntry<MonitorBlock> MONITOR_ADVANCED = REGISTRY.register("monitor_advanced",
|
||||||
() -> new MonitorBlock(properties().mapColor(MapColor.GOLD), BlockEntities.MONITOR_ADVANCED));
|
() -> new MonitorBlock(properties(), BlockEntities.MONITOR_ADVANCED));
|
||||||
|
|
||||||
public static final RegistryEntry<WirelessModemBlock> WIRELESS_MODEM_NORMAL = REGISTRY.register("wireless_modem_normal",
|
public static final RegistryEntry<WirelessModemBlock> WIRELESS_MODEM_NORMAL = REGISTRY.register("wireless_modem_normal",
|
||||||
() -> new WirelessModemBlock(properties().mapColor(MapColor.STONE), BlockEntities.WIRELESS_MODEM_NORMAL));
|
() -> new WirelessModemBlock(properties(), BlockEntities.WIRELESS_MODEM_NORMAL));
|
||||||
public static final RegistryEntry<WirelessModemBlock> WIRELESS_MODEM_ADVANCED = REGISTRY.register("wireless_modem_advanced",
|
public static final RegistryEntry<WirelessModemBlock> WIRELESS_MODEM_ADVANCED = REGISTRY.register("wireless_modem_advanced",
|
||||||
() -> new WirelessModemBlock(properties().mapColor(MapColor.GOLD), BlockEntities.WIRELESS_MODEM_ADVANCED));
|
() -> new WirelessModemBlock(properties(), BlockEntities.WIRELESS_MODEM_ADVANCED));
|
||||||
|
|
||||||
public static final RegistryEntry<WiredModemFullBlock> WIRED_MODEM_FULL = REGISTRY.register("wired_modem_full",
|
public static final RegistryEntry<WiredModemFullBlock> WIRED_MODEM_FULL = REGISTRY.register("wired_modem_full",
|
||||||
() -> new WiredModemFullBlock(modemProperties().mapColor(MapColor.STONE)));
|
() -> new WiredModemFullBlock(modemProperties()));
|
||||||
public static final RegistryEntry<CableBlock> CABLE = REGISTRY.register("cable", () -> new CableBlock(modemProperties().mapColor(MapColor.STONE)));
|
public static final RegistryEntry<CableBlock> CABLE = REGISTRY.register("cable", () -> new CableBlock(modemProperties()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class BlockEntities {
|
public static class BlockEntities {
|
||||||
@@ -365,11 +366,50 @@ public final class ModRegistry {
|
|||||||
public static final RegistryEntry<ImpostorShapelessRecipe.Serializer> IMPOSTOR_SHAPELESS = REGISTRY.register("impostor_shapeless", ImpostorShapelessRecipe.Serializer::new);
|
public static final RegistryEntry<ImpostorShapelessRecipe.Serializer> IMPOSTOR_SHAPELESS = REGISTRY.register("impostor_shapeless", ImpostorShapelessRecipe.Serializer::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
static class CreativeTabs {
|
/**
|
||||||
static final RegistrationHelper<CreativeModeTab> REGISTRY = PlatformHelper.get().createRegistrationHelper(Registries.CREATIVE_MODE_TAB);
|
* Register any objects which don't have to be done on the main thread.
|
||||||
|
*/
|
||||||
|
public static void register() {
|
||||||
|
Blocks.REGISTRY.register();
|
||||||
|
BlockEntities.REGISTRY.register();
|
||||||
|
Items.REGISTRY.register();
|
||||||
|
TurtleSerialisers.REGISTRY.register();
|
||||||
|
PocketUpgradeSerialisers.REGISTRY.register();
|
||||||
|
Menus.REGISTRY.register();
|
||||||
|
ArgumentTypes.REGISTRY.register();
|
||||||
|
LootItemConditionTypes.REGISTRY.register();
|
||||||
|
RecipeSerializers.REGISTRY.register();
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
// Register bundled power providers
|
||||||
private static final RegistryEntry<CreativeModeTab> TAB = REGISTRY.register("tab", () -> PlatformHelper.get().newCreativeModeTab()
|
ComputerCraftAPI.registerBundledRedstoneProvider(new DefaultBundledRedstoneProvider());
|
||||||
|
ComputerCraftAPI.registerRefuelHandler(new FurnaceRefuelHandler());
|
||||||
|
ComputerCraftAPI.registerMediaProvider(stack -> {
|
||||||
|
var item = stack.getItem();
|
||||||
|
if (item instanceof IMedia media) return media;
|
||||||
|
if (item instanceof RecordItem) return RecordMedia.INSTANCE;
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
VanillaDetailRegistries.ITEM_STACK.addProvider(ItemDetails::fill);
|
||||||
|
VanillaDetailRegistries.BLOCK_IN_WORLD.addProvider(BlockDetails::fill);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register any objects which must be done on the main thread.
|
||||||
|
*/
|
||||||
|
public static void registerMainThread() {
|
||||||
|
CauldronInteraction.WATER.put(ModRegistry.Items.TURTLE_NORMAL.get(), TurtleItem.CAULDRON_INTERACTION);
|
||||||
|
CauldronInteraction.WATER.put(ModRegistry.Items.TURTLE_ADVANCED.get(), TurtleItem.CAULDRON_INTERACTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure a {@link CreativeModeTab.Builder} to contain all of ComputerCraft's items.
|
||||||
|
*
|
||||||
|
* @param builder The builder to configure.
|
||||||
|
* @return The same building, for calling {@link CreativeModeTab.Builder#build()} on.
|
||||||
|
*/
|
||||||
|
public static CreativeModeTab.Builder registerCreativeTab(CreativeModeTab.Builder builder) {
|
||||||
|
return builder
|
||||||
.icon(() -> new ItemStack(Items.COMPUTER_NORMAL.get()))
|
.icon(() -> new ItemStack(Items.COMPUTER_NORMAL.get()))
|
||||||
.title(Component.translatable("itemGroup.computercraft"))
|
.title(Component.translatable("itemGroup.computercraft"))
|
||||||
.displayItems((context, out) -> {
|
.displayItems((context, out) -> {
|
||||||
@@ -401,56 +441,18 @@ public final class ModRegistry {
|
|||||||
for (var colour = 0; colour < 16; colour++) {
|
for (var colour = 0; colour < 16; colour++) {
|
||||||
out.accept(DiskItem.createFromIDAndColour(-1, null, Colour.VALUES[colour].getHex()));
|
out.accept(DiskItem.createFromIDAndColour(-1, null, Colour.VALUES[colour].getHex()));
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register any objects which don't have to be done on the main thread.
|
|
||||||
*/
|
|
||||||
public static void register() {
|
|
||||||
Blocks.REGISTRY.register();
|
|
||||||
BlockEntities.REGISTRY.register();
|
|
||||||
Items.REGISTRY.register();
|
|
||||||
TurtleSerialisers.REGISTRY.register();
|
|
||||||
PocketUpgradeSerialisers.REGISTRY.register();
|
|
||||||
Menus.REGISTRY.register();
|
|
||||||
ArgumentTypes.REGISTRY.register();
|
|
||||||
LootItemConditionTypes.REGISTRY.register();
|
|
||||||
RecipeSerializers.REGISTRY.register();
|
|
||||||
CreativeTabs.REGISTRY.register();
|
|
||||||
|
|
||||||
// Register bundled power providers
|
|
||||||
ComputerCraftAPI.registerBundledRedstoneProvider(new DefaultBundledRedstoneProvider());
|
|
||||||
ComputerCraftAPI.registerRefuelHandler(new FurnaceRefuelHandler());
|
|
||||||
ComputerCraftAPI.registerMediaProvider(stack -> {
|
|
||||||
var item = stack.getItem();
|
|
||||||
if (item instanceof IMedia media) return media;
|
|
||||||
if (item instanceof RecordItem) return RecordMedia.INSTANCE;
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
|
|
||||||
VanillaDetailRegistries.ITEM_STACK.addProvider(ItemDetails::fill);
|
|
||||||
VanillaDetailRegistries.BLOCK_IN_WORLD.addProvider(BlockDetails::fill);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register any objects which must be done on the main thread.
|
|
||||||
*/
|
|
||||||
public static void registerMainThread() {
|
|
||||||
CauldronInteraction.WATER.put(ModRegistry.Items.TURTLE_NORMAL.get(), TurtleItem.CAULDRON_INTERACTION);
|
|
||||||
CauldronInteraction.WATER.put(ModRegistry.Items.TURTLE_ADVANCED.get(), TurtleItem.CAULDRON_INTERACTION);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
@@ -124,9 +127,7 @@ public final class CommandComputerCraft {
|
|||||||
if (computer.isOn()) shutdown++;
|
if (computer.isOn()) shutdown++;
|
||||||
computer.shutdown();
|
computer.shutdown();
|
||||||
}
|
}
|
||||||
|
context.getSource().sendSuccess(Component.translatable("commands.computercraft.shutdown.done", shutdown, computers.size()), false);
|
||||||
var didShutdown = shutdown;
|
|
||||||
context.getSource().sendSuccess(() -> Component.translatable("commands.computercraft.shutdown.done", didShutdown, computers.size()), false);
|
|
||||||
return shutdown;
|
return shutdown;
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@@ -140,9 +141,7 @@ public final class CommandComputerCraft {
|
|||||||
if (!computer.isOn()) on++;
|
if (!computer.isOn()) on++;
|
||||||
computer.turnOn();
|
computer.turnOn();
|
||||||
}
|
}
|
||||||
|
context.getSource().sendSuccess(Component.translatable("commands.computercraft.turn_on.done", on, computers.size()), false);
|
||||||
var didOn = on;
|
|
||||||
context.getSource().sendSuccess(() -> Component.translatable("commands.computercraft.turn_on.done", didOn, computers.size()), false);
|
|
||||||
return on;
|
return on;
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@@ -173,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");
|
||||||
@@ -217,7 +219,7 @@ public final class CommandComputerCraft {
|
|||||||
getMetricsInstance(context.getSource()).start();
|
getMetricsInstance(context.getSource()).start();
|
||||||
|
|
||||||
var stopCommand = "/computercraft track stop";
|
var stopCommand = "/computercraft track stop";
|
||||||
context.getSource().sendSuccess(() -> Component.translatable(
|
context.getSource().sendSuccess(Component.translatable(
|
||||||
"commands.computercraft.track.start.stop",
|
"commands.computercraft.track.start.stop",
|
||||||
link(text(stopCommand), stopCommand, Component.translatable("commands.computercraft.track.stop.action"))
|
link(text(stopCommand), stopCommand, Component.translatable("commands.computercraft.track.stop.action"))
|
||||||
), false);
|
), false);
|
||||||
|
@@ -49,6 +49,29 @@ public enum UserLevel implements Predicate<CommandSourceStack> {
|
|||||||
return source.hasPermission(toLevel());
|
return source.hasPermission(toLevel());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Take the union of two {@link UserLevel}s.
|
||||||
|
* <p>
|
||||||
|
* This satisfies the property that for all sources {@code s}, {@code a.test(s) || b.test(s) == (a ∪ b).test(s)}.
|
||||||
|
*
|
||||||
|
* @param left The first user level to take the union of.
|
||||||
|
* @param right The second user level to take the union of.
|
||||||
|
* @return The union of two levels.
|
||||||
|
*/
|
||||||
|
public static UserLevel union(UserLevel left, UserLevel right) {
|
||||||
|
if (left == right) return left;
|
||||||
|
|
||||||
|
// x ∪ ANYONE = ANYONE
|
||||||
|
if (left == ANYONE || right == ANYONE) return ANYONE;
|
||||||
|
|
||||||
|
// x ∪ OWNER = OWNER
|
||||||
|
if (left == OWNER) return right;
|
||||||
|
if (right == OWNER) return left;
|
||||||
|
|
||||||
|
// At this point, we have x != y and x, y ∈ { OP, OWNER_OP }.
|
||||||
|
return OWNER_OP;
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean isOwner(CommandSourceStack source) {
|
private static boolean isOwner(CommandSourceStack source) {
|
||||||
var server = source.getServer();
|
var server = source.getServer();
|
||||||
var sender = source.getEntity();
|
var sender = source.getEntity();
|
||||||
|
@@ -44,12 +44,12 @@ public class ArgumentUtils {
|
|||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private static <A extends ArgumentType<?>, T extends ArgumentTypeInfo.Template<A>> void serializeToNetwork(FriendlyByteBuf buffer, ArgumentTypeInfo<A, T> type, ArgumentTypeInfo.Template<A> template) {
|
private static <A extends ArgumentType<?>, T extends ArgumentTypeInfo.Template<A>> void serializeToNetwork(FriendlyByteBuf buffer, ArgumentTypeInfo<A, T> type, ArgumentTypeInfo.Template<A> template) {
|
||||||
RegistryWrappers.writeId(buffer, RegistryWrappers.COMMAND_ARGUMENT_TYPES, type);
|
buffer.writeId(RegistryWrappers.COMMAND_ARGUMENT_TYPES, type);
|
||||||
type.serializeToNetwork((T) template, buffer);
|
type.serializeToNetwork((T) template, buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ArgumentTypeInfo.Template<?> deserialize(FriendlyByteBuf buffer) {
|
public static ArgumentTypeInfo.Template<?> deserialize(FriendlyByteBuf buffer) {
|
||||||
var type = RegistryWrappers.readId(buffer, RegistryWrappers.COMMAND_ARGUMENT_TYPES);
|
var type = buffer.readById(RegistryWrappers.COMMAND_ARGUMENT_TYPES);
|
||||||
Objects.requireNonNull(type, "Unknown argument type");
|
Objects.requireNonNull(type, "Unknown argument type");
|
||||||
return type.deserializeFromNetwork(buffer);
|
return type.deserializeFromNetwork(buffer);
|
||||||
}
|
}
|
||||||
|
@@ -18,7 +18,6 @@ import net.minecraft.commands.CommandBuildContext;
|
|||||||
import net.minecraft.commands.CommandSourceStack;
|
import net.minecraft.commands.CommandSourceStack;
|
||||||
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
|
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
@@ -159,14 +158,14 @@ public final class ComputersArgumentType implements ArgumentType<ComputersArgume
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ComputersArgumentType.Template unpack(@NotNull ComputersArgumentType argumentType) {
|
public ComputersArgumentType.Template unpack(ComputersArgumentType argumentType) {
|
||||||
return new ComputersArgumentType.Template(this, argumentType.requireSome);
|
return new ComputersArgumentType.Template(this, argumentType.requireSome);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public record Template(Info info, boolean requireSome) implements ArgumentTypeInfo.Template<ComputersArgumentType> {
|
public record Template(Info info, boolean requireSome) implements ArgumentTypeInfo.Template<ComputersArgumentType> {
|
||||||
@Override
|
@Override
|
||||||
public ComputersArgumentType instantiate(@NotNull CommandBuildContext context) {
|
public ComputersArgumentType instantiate(CommandBuildContext context) {
|
||||||
return requireSome ? SOME : MANY;
|
return requireSome ? SOME : MANY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -17,7 +17,6 @@ import net.minecraft.commands.synchronization.ArgumentTypeInfo;
|
|||||||
import net.minecraft.commands.synchronization.ArgumentTypeInfos;
|
import net.minecraft.commands.synchronization.ArgumentTypeInfos;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@@ -144,7 +143,7 @@ public final class RepeatArgumentType<T, U> implements ArgumentType<List<T>> {
|
|||||||
) implements ArgumentTypeInfo.Template<RepeatArgumentType<?, ?>> {
|
) implements ArgumentTypeInfo.Template<RepeatArgumentType<?, ?>> {
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||||
public RepeatArgumentType<?, ?> instantiate(@NotNull CommandBuildContext commandBuildContext) {
|
public RepeatArgumentType<?, ?> instantiate(CommandBuildContext commandBuildContext) {
|
||||||
var child = child().instantiate(commandBuildContext);
|
var child = child().instantiate(commandBuildContext);
|
||||||
return flatten ? RepeatArgumentType.someFlat((ArgumentType) child, some()) : RepeatArgumentType.some(child, some());
|
return flatten ? RepeatArgumentType.someFlat((ArgumentType) child, some()) : RepeatArgumentType.some(child, some());
|
||||||
}
|
}
|
||||||
|
@@ -48,11 +48,15 @@ public class CommandBuilder<S> implements CommandNodeBuilder<S, Command<S>> {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CommandBuilder<S> arg(String name, ArgumentType<?> type) {
|
public CommandBuilder<S> arg(ArgumentBuilder<S, ?> arg) {
|
||||||
args.add(RequiredArgumentBuilder.argument(name, type));
|
args.add(arg);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CommandBuilder<S> arg(String name, ArgumentType<?> type) {
|
||||||
|
return arg(RequiredArgumentBuilder.argument(name, type));
|
||||||
|
}
|
||||||
|
|
||||||
public <T> CommandNodeBuilder<S, ArgCommand<S, List<T>>> argManyValue(String name, ArgumentType<T> type, List<T> empty) {
|
public <T> CommandNodeBuilder<S, ArgCommand<S, List<T>>> argManyValue(String name, ArgumentType<T> type, List<T> empty) {
|
||||||
return argMany(name, type, () -> empty);
|
return argMany(name, type, () -> empty);
|
||||||
}
|
}
|
||||||
@@ -74,7 +78,7 @@ public class CommandBuilder<S> implements CommandNodeBuilder<S, Command<S>> {
|
|||||||
|
|
||||||
return command -> {
|
return command -> {
|
||||||
// The node for no arguments
|
// The node for no arguments
|
||||||
var tail = tail(ctx -> command.run(ctx, empty.get()));
|
var tail = setupTail(ctx -> command.run(ctx, empty.get()));
|
||||||
|
|
||||||
// The node for one or more arguments
|
// The node for one or more arguments
|
||||||
ArgumentBuilder<S, ?> moreArg = RequiredArgumentBuilder
|
ArgumentBuilder<S, ?> moreArg = RequiredArgumentBuilder
|
||||||
@@ -83,7 +87,7 @@ public class CommandBuilder<S> implements CommandNodeBuilder<S, Command<S>> {
|
|||||||
|
|
||||||
// Chain all of them together!
|
// Chain all of them together!
|
||||||
tail.then(moreArg);
|
tail.then(moreArg);
|
||||||
return link(tail);
|
return buildTail(tail);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,20 +98,16 @@ public class CommandBuilder<S> implements CommandNodeBuilder<S, Command<S>> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandNode<S> executes(Command<S> command) {
|
public CommandNode<S> executes(Command<S> command) {
|
||||||
if (args.isEmpty()) throw new IllegalStateException("Cannot have empty arg chain builder");
|
return buildTail(setupTail(command));
|
||||||
|
|
||||||
return link(tail(command));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ArgumentBuilder<S, ?> tail(Command<S> command) {
|
private ArgumentBuilder<S, ?> setupTail(Command<S> command) {
|
||||||
var defaultTail = args.get(args.size() - 1);
|
return args.get(args.size() - 1).executes(command);
|
||||||
defaultTail.executes(command);
|
|
||||||
if (requires != null) defaultTail.requires(requires);
|
|
||||||
return defaultTail;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private CommandNode<S> link(ArgumentBuilder<S, ?> tail) {
|
private CommandNode<S> buildTail(ArgumentBuilder<S, ?> tail) {
|
||||||
for (var i = args.size() - 2; i >= 0; i--) tail = args.get(i).then(tail);
|
for (var i = args.size() - 2; i >= 0; i--) tail = args.get(i).then(tail);
|
||||||
|
if (requires != null) tail.requires(requires);
|
||||||
return tail.build();
|
return tail.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,6 +10,7 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
|||||||
import com.mojang.brigadier.context.CommandContext;
|
import com.mojang.brigadier.context.CommandContext;
|
||||||
import com.mojang.brigadier.tree.CommandNode;
|
import com.mojang.brigadier.tree.CommandNode;
|
||||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||||
|
import dan200.computercraft.shared.command.UserLevel;
|
||||||
import net.minecraft.ChatFormatting;
|
import net.minecraft.ChatFormatting;
|
||||||
import net.minecraft.commands.CommandSourceStack;
|
import net.minecraft.commands.CommandSourceStack;
|
||||||
import net.minecraft.network.chat.ClickEvent;
|
import net.minecraft.network.chat.ClickEvent;
|
||||||
@@ -18,6 +19,8 @@ import net.minecraft.network.chat.Component;
|
|||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static dan200.computercraft.core.util.Nullability.assertNonNull;
|
import static dan200.computercraft.core.util.Nullability.assertNonNull;
|
||||||
import static dan200.computercraft.shared.command.text.ChatHelpers.coloured;
|
import static dan200.computercraft.shared.command.text.ChatHelpers.coloured;
|
||||||
@@ -37,6 +40,29 @@ public final class HelpingArgumentBuilder extends LiteralArgumentBuilder<Command
|
|||||||
return new HelpingArgumentBuilder(literal);
|
return new HelpingArgumentBuilder(literal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LiteralArgumentBuilder<CommandSourceStack> requires(Predicate<CommandSourceStack> requirement) {
|
||||||
|
throw new IllegalStateException("Cannot use requires on a HelpingArgumentBuilder");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Predicate<CommandSourceStack> getRequirement() {
|
||||||
|
// The requirement of this node is the union of all child's requirements.
|
||||||
|
var requirements = Stream.concat(
|
||||||
|
children.stream().map(ArgumentBuilder::getRequirement),
|
||||||
|
getArguments().stream().map(CommandNode::getRequirement)
|
||||||
|
).toList();
|
||||||
|
|
||||||
|
// If all requirements are a UserLevel, take the union of those instead.
|
||||||
|
var userLevel = UserLevel.OWNER;
|
||||||
|
for (var requirement : requirements) {
|
||||||
|
if (!(requirement instanceof UserLevel level)) return x -> requirements.stream().anyMatch(y -> y.test(x));
|
||||||
|
userLevel = UserLevel.union(userLevel, level);
|
||||||
|
}
|
||||||
|
|
||||||
|
return userLevel;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LiteralArgumentBuilder<CommandSourceStack> executes(final Command<CommandSourceStack> command) {
|
public LiteralArgumentBuilder<CommandSourceStack> executes(final Command<CommandSourceStack> command) {
|
||||||
throw new IllegalStateException("Cannot use executes on a HelpingArgumentBuilder");
|
throw new IllegalStateException("Cannot use executes on a HelpingArgumentBuilder");
|
||||||
@@ -80,9 +106,7 @@ public final class HelpingArgumentBuilder extends LiteralArgumentBuilder<Command
|
|||||||
helpCommand.node = node;
|
helpCommand.node = node;
|
||||||
|
|
||||||
// Set up a /... help command
|
// Set up a /... help command
|
||||||
var helpNode = LiteralArgumentBuilder.<CommandSourceStack>literal("help")
|
var helpNode = LiteralArgumentBuilder.<CommandSourceStack>literal("help").executes(helpCommand);
|
||||||
.requires(x -> getArguments().stream().anyMatch(y -> y.getRequirement().test(x)))
|
|
||||||
.executes(helpCommand);
|
|
||||||
|
|
||||||
// Add all normal command children to this and the help node
|
// Add all normal command children to this and the help node
|
||||||
for (var child : getArguments()) {
|
for (var child : getArguments()) {
|
||||||
@@ -129,14 +153,14 @@ public final class HelpingArgumentBuilder extends LiteralArgumentBuilder<Command
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int run(CommandContext<CommandSourceStack> context) {
|
public int run(CommandContext<CommandSourceStack> context) {
|
||||||
context.getSource().sendSuccess(() -> getHelp(context, assertNonNull(node), id, command), false);
|
context.getSource().sendSuccess(getHelp(context, assertNonNull(node), id, command), false);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Command<CommandSourceStack> helpForChild(CommandNode<CommandSourceStack> node, String id, String command) {
|
private static Command<CommandSourceStack> helpForChild(CommandNode<CommandSourceStack> node, String id, String command) {
|
||||||
return context -> {
|
return context -> {
|
||||||
context.getSource().sendSuccess(() -> getHelp(context, node, id + "." + node.getName().replace('-', '_'), command + " " + node.getName()), false);
|
context.getSource().sendSuccess(getHelp(context, node, id + "." + node.getName().replace('-', '_'), command + " " + node.getName()), false);
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -37,6 +37,6 @@ public class ServerTableFormatter implements TableFormatter {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeLine(String label, Component component) {
|
public void writeLine(String label, Component component) {
|
||||||
source.sendSuccess(() -> component, false);
|
source.sendSuccess(component, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -32,7 +32,7 @@ import net.minecraft.world.level.block.entity.BlockEntity;
|
|||||||
import net.minecraft.world.level.block.entity.BlockEntityTicker;
|
import net.minecraft.world.level.block.entity.BlockEntityTicker;
|
||||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||||
import net.minecraft.world.level.block.state.BlockState;
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
import net.minecraft.world.level.storage.loot.LootParams;
|
import net.minecraft.world.level.storage.loot.LootContext;
|
||||||
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
|
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
|
||||||
import net.minecraft.world.phys.BlockHitResult;
|
import net.minecraft.world.phys.BlockHitResult;
|
||||||
import net.minecraft.world.phys.Vec3;
|
import net.minecraft.world.phys.Vec3;
|
||||||
@@ -138,12 +138,13 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit
|
|||||||
|
|
||||||
var tile = world.getBlockEntity(pos);
|
var tile = world.getBlockEntity(pos);
|
||||||
if (tile instanceof AbstractComputerBlockEntity computer) {
|
if (tile instanceof AbstractComputerBlockEntity computer) {
|
||||||
var context = new LootParams.Builder(serverWorld)
|
var context = new LootContext.Builder(serverWorld)
|
||||||
|
.withRandom(world.random)
|
||||||
.withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(pos))
|
.withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(pos))
|
||||||
.withParameter(LootContextParams.TOOL, player.getMainHandItem())
|
.withParameter(LootContextParams.TOOL, player.getMainHandItem())
|
||||||
.withParameter(LootContextParams.THIS_ENTITY, player)
|
.withParameter(LootContextParams.THIS_ENTITY, player)
|
||||||
.withParameter(LootContextParams.BLOCK_ENTITY, tile)
|
.withParameter(LootContextParams.BLOCK_ENTITY, tile)
|
||||||
.withDynamicDrop(DROP, out -> out.accept(getItem(computer)));
|
.withDynamicDrop(DROP, (ctx, out) -> out.accept(getItem(computer)));
|
||||||
for (var item : state.getDrops(context)) {
|
for (var item : state.getDrops(context)) {
|
||||||
popResource(world, pos, item);
|
popResource(world, pos, item);
|
||||||
}
|
}
|
||||||
|
@@ -199,6 +199,11 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
|
|||||||
return localSide;
|
return localSide;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateRedstoneInputs(ServerComputer computer) {
|
||||||
|
var pos = getBlockPos();
|
||||||
|
for (var dir : DirectionUtil.FACINGS) updateRedstoneInput(computer, dir, pos.relative(dir));
|
||||||
|
}
|
||||||
|
|
||||||
private void updateRedstoneInput(ServerComputer computer, Direction dir, BlockPos targetPos) {
|
private void updateRedstoneInput(ServerComputer computer, Direction dir, BlockPos targetPos) {
|
||||||
var offsetSide = dir.getOpposite();
|
var offsetSide = dir.getOpposite();
|
||||||
var localDir = remapToLocalSide(dir);
|
var localDir = remapToLocalSide(dir);
|
||||||
@@ -254,8 +259,7 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
|
|||||||
|
|
||||||
// If the position is not any adjacent one, update all inputs. This is pretty terrible, but some redstone mods
|
// If the position is not any adjacent one, update all inputs. This is pretty terrible, but some redstone mods
|
||||||
// handle this incorrectly.
|
// handle this incorrectly.
|
||||||
var pos = getBlockPos();
|
updateRedstoneInputs(computer);
|
||||||
for (var dir : DirectionUtil.FACINGS) updateRedstoneInput(computer, dir, pos.relative(dir));
|
|
||||||
invalidSides = (1 << 6) - 1; // Mark all peripherals as dirty.
|
invalidSides = (1 << 6) - 1; // Mark all peripherals as dirty.
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,9 +268,10 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
|
|||||||
*/
|
*/
|
||||||
public void updateOutput() {
|
public void updateOutput() {
|
||||||
BlockEntityHelpers.updateBlock(this);
|
BlockEntityHelpers.updateBlock(this);
|
||||||
for (var dir : DirectionUtil.FACINGS) {
|
for (var dir : DirectionUtil.FACINGS) RedstoneUtil.propagateRedstoneOutput(getLevel(), getBlockPos(), dir);
|
||||||
RedstoneUtil.propagateRedstoneOutput(getLevel(), getBlockPos(), dir);
|
|
||||||
}
|
var computer = getServerComputer();
|
||||||
|
if (computer != null) updateRedstoneInputs(computer);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract ServerComputer createComputer(int id);
|
protected abstract ServerComputer createComputer(int id);
|
||||||
|
@@ -9,13 +9,16 @@ import dan200.computercraft.api.ComputerCraftAPI;
|
|||||||
import dan200.computercraft.api.filesystem.Mount;
|
import dan200.computercraft.api.filesystem.Mount;
|
||||||
import dan200.computercraft.api.network.PacketNetwork;
|
import dan200.computercraft.api.network.PacketNetwork;
|
||||||
import dan200.computercraft.core.ComputerContext;
|
import dan200.computercraft.core.ComputerContext;
|
||||||
import dan200.computercraft.core.computer.ComputerThread;
|
|
||||||
import dan200.computercraft.core.computer.GlobalEnvironment;
|
import dan200.computercraft.core.computer.GlobalEnvironment;
|
||||||
import dan200.computercraft.core.computer.mainthread.MainThread;
|
import dan200.computercraft.core.computer.mainthread.MainThread;
|
||||||
import dan200.computercraft.core.computer.mainthread.MainThreadConfig;
|
import dan200.computercraft.core.computer.mainthread.MainThreadConfig;
|
||||||
import dan200.computercraft.core.lua.CobaltLuaMachine;
|
import dan200.computercraft.core.lua.CobaltLuaMachine;
|
||||||
import dan200.computercraft.core.lua.ILuaMachine;
|
import dan200.computercraft.core.lua.ILuaMachine;
|
||||||
|
import dan200.computercraft.core.methods.MethodSupplier;
|
||||||
|
import dan200.computercraft.core.methods.PeripheralMethod;
|
||||||
import dan200.computercraft.impl.AbstractComputerCraftAPI;
|
import dan200.computercraft.impl.AbstractComputerCraftAPI;
|
||||||
|
import dan200.computercraft.impl.ApiFactories;
|
||||||
|
import dan200.computercraft.impl.GenericSources;
|
||||||
import dan200.computercraft.shared.CommonHooks;
|
import dan200.computercraft.shared.CommonHooks;
|
||||||
import dan200.computercraft.shared.computer.metrics.GlobalMetrics;
|
import dan200.computercraft.shared.computer.metrics.GlobalMetrics;
|
||||||
import dan200.computercraft.shared.config.ConfigSpec;
|
import dan200.computercraft.shared.config.ConfigSpec;
|
||||||
@@ -67,11 +70,13 @@ public final class ServerContext {
|
|||||||
this.server = server;
|
this.server = server;
|
||||||
storageDir = server.getWorldPath(FOLDER);
|
storageDir = server.getWorldPath(FOLDER);
|
||||||
mainThread = new MainThread(mainThreadConfig);
|
mainThread = new MainThread(mainThreadConfig);
|
||||||
context = new ComputerContext(
|
context = ComputerContext.builder(new Environment(server))
|
||||||
new Environment(server),
|
.computerThreads(ConfigSpec.computerThreads.get())
|
||||||
new ComputerThread(ConfigSpec.computerThreads.get()),
|
.mainThreadScheduler(mainThread)
|
||||||
mainThread, luaMachine
|
.luaFactory(luaMachine)
|
||||||
);
|
.apiFactories(ApiFactories.getAll())
|
||||||
|
.genericMethods(GenericSources.getAllMethods())
|
||||||
|
.build();
|
||||||
idAssigner = new IDAssigner(storageDir.resolve("ids.json"));
|
idAssigner = new IDAssigner(storageDir.resolve("ids.json"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,6 +138,16 @@ public final class ServerContext {
|
|||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the {@link MethodSupplier} used to find methods on peripherals.
|
||||||
|
*
|
||||||
|
* @return The {@link PeripheralMethod} method supplier.
|
||||||
|
* @see ComputerContext#peripheralMethods()
|
||||||
|
*/
|
||||||
|
public MethodSupplier<PeripheralMethod> peripheralMethods() {
|
||||||
|
return context.peripheralMethods();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tick all components of this server context. This should <em>NOT</em> be called outside of {@link CommonHooks}.
|
* Tick all components of this server context. This should <em>NOT</em> be called outside of {@link CommonHooks}.
|
||||||
*/
|
*/
|
||||||
|
@@ -7,7 +7,7 @@ package dan200.computercraft.shared.computer.upload;
|
|||||||
import dan200.computercraft.api.lua.LuaFunction;
|
import dan200.computercraft.api.lua.LuaFunction;
|
||||||
import dan200.computercraft.core.apis.handles.BinaryReadableHandle;
|
import dan200.computercraft.core.apis.handles.BinaryReadableHandle;
|
||||||
import dan200.computercraft.core.apis.handles.ByteBufferChannel;
|
import dan200.computercraft.core.apis.handles.ByteBufferChannel;
|
||||||
import dan200.computercraft.core.asm.ObjectSource;
|
import dan200.computercraft.core.methods.ObjectSource;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@@ -4,21 +4,19 @@
|
|||||||
|
|
||||||
package dan200.computercraft.shared.config;
|
package dan200.computercraft.shared.config;
|
||||||
|
|
||||||
|
import com.electronwill.nightconfig.core.CommentedConfig;
|
||||||
import com.electronwill.nightconfig.core.Config;
|
import com.electronwill.nightconfig.core.Config;
|
||||||
import com.electronwill.nightconfig.core.InMemoryCommentedFormat;
|
import com.electronwill.nightconfig.core.InMemoryCommentedFormat;
|
||||||
import com.electronwill.nightconfig.core.UnmodifiableConfig;
|
import com.electronwill.nightconfig.core.UnmodifiableConfig;
|
||||||
import dan200.computercraft.core.apis.http.options.Action;
|
import dan200.computercraft.core.apis.http.options.Action;
|
||||||
import dan200.computercraft.core.apis.http.options.AddressRule;
|
import dan200.computercraft.core.apis.http.options.AddressRule;
|
||||||
|
import dan200.computercraft.core.apis.http.options.InvalidRuleException;
|
||||||
import dan200.computercraft.core.apis.http.options.PartialOptions;
|
import dan200.computercraft.core.apis.http.options.PartialOptions;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import java.util.*;
|
||||||
import java.util.Locale;
|
import java.util.function.Consumer;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.OptionalInt;
|
|
||||||
import java.util.OptionalLong;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses, checks and generates {@link Config}s for {@link AddressRule}.
|
* Parses, checks and generates {@link Config}s for {@link AddressRule}.
|
||||||
@@ -26,49 +24,65 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
class AddressRuleConfig {
|
class AddressRuleConfig {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(AddressRuleConfig.class);
|
private static final Logger LOG = LoggerFactory.getLogger(AddressRuleConfig.class);
|
||||||
|
|
||||||
public static UnmodifiableConfig makeRule(String host, Action action) {
|
private static final AddressRule REJECT_ALL = AddressRule.parse("*", OptionalInt.empty(), Action.DENY.toPartial());
|
||||||
var config = InMemoryCommentedFormat.defaultInstance().createConfig(ConcurrentHashMap::new);
|
|
||||||
config.add("host", host);
|
|
||||||
config.add("action", action.name().toLowerCase(Locale.ROOT));
|
|
||||||
|
|
||||||
if (host.equals("*") && action == Action.ALLOW) {
|
public static List<UnmodifiableConfig> defaultRules() {
|
||||||
config.setComment("max_download", """
|
return List.of(
|
||||||
The maximum size (in bytes) that a computer can download in a single request.
|
makeRule(config -> {
|
||||||
Note that responses may receive more data than allowed, but this data will not
|
config.setComment("host", """
|
||||||
be returned to the client.""");
|
The magic "$private" host matches all private address ranges, such as localhost and 192.168.0.0/16.
|
||||||
config.set("max_download", AddressRule.MAX_DOWNLOAD);
|
This rule prevents computers accessing internal services, and is strongly recommended.""");
|
||||||
|
config.add("host", "$private");
|
||||||
|
|
||||||
config.setComment("max_upload", """
|
config.setComment("action", "Deny all requests to private IP addresses.");
|
||||||
The maximum size (in bytes) that a computer can upload in a single request. This
|
config.add("action", Action.DENY.name().toLowerCase(Locale.ROOT));
|
||||||
includes headers and POST text.""");
|
}),
|
||||||
config.set("max_upload", AddressRule.MAX_UPLOAD);
|
makeRule(config -> {
|
||||||
|
config.setComment("host", """
|
||||||
|
The wildcard "*" rule matches all remaining hosts.""");
|
||||||
|
config.add("host", "*");
|
||||||
|
|
||||||
config.setComment("max_websocket_message", "The maximum size (in bytes) that a computer can send or receive in one websocket packet.");
|
config.setComment("action", "Allow all non-denied hosts.");
|
||||||
config.set("max_websocket_message", AddressRule.WEBSOCKET_MESSAGE);
|
config.add("action", Action.ALLOW.name().toLowerCase(Locale.ROOT));
|
||||||
|
|
||||||
config.setComment("use_proxy", "Enable use of the HTTP/SOCKS proxy if it is configured.");
|
config.setComment("max_download", """
|
||||||
config.set("use_proxy", false);
|
The maximum size (in bytes) that a computer can download in a single request.
|
||||||
}
|
Note that responses may receive more data than allowed, but this data will not
|
||||||
|
be returned to the client.""");
|
||||||
|
config.set("max_download", AddressRule.MAX_DOWNLOAD);
|
||||||
|
|
||||||
|
config.setComment("max_upload", """
|
||||||
|
The maximum size (in bytes) that a computer can upload in a single request. This
|
||||||
|
includes headers and POST text.""");
|
||||||
|
config.set("max_upload", AddressRule.MAX_UPLOAD);
|
||||||
|
|
||||||
|
config.setComment("max_websocket_message", "The maximum size (in bytes) that a computer can send or receive in one websocket packet.");
|
||||||
|
config.set("max_websocket_message", AddressRule.WEBSOCKET_MESSAGE);
|
||||||
|
|
||||||
|
config.setComment("use_proxy", "Enable use of the HTTP/SOCKS proxy if it is configured.");
|
||||||
|
config.set("use_proxy", false);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static UnmodifiableConfig makeRule(Consumer<CommentedConfig> setup) {
|
||||||
|
var config = InMemoryCommentedFormat.defaultInstance().createConfig(LinkedHashMap::new);
|
||||||
|
setup.accept(config);
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean checkRule(UnmodifiableConfig builder) {
|
public static AddressRule parseRule(UnmodifiableConfig builder) {
|
||||||
var hostObj = get(builder, "host", String.class).orElse(null);
|
try {
|
||||||
var port = unboxOptInt(get(builder, "port", Number.class));
|
return doParseRule(builder);
|
||||||
return hostObj != null && checkEnum(builder, "action", Action.class)
|
} catch (InvalidRuleException e) {
|
||||||
&& check(builder, "port", Number.class)
|
LOG.error("Malformed HTTP rule: {} HTTP will NOT work until this is fixed.", e.getMessage());
|
||||||
&& check(builder, "max_upload", Number.class)
|
return REJECT_ALL;
|
||||||
&& check(builder, "max_download", Number.class)
|
}
|
||||||
&& check(builder, "websocket_message", Number.class)
|
|
||||||
&& check(builder, "use_proxy", Boolean.class)
|
|
||||||
&& AddressRule.parse(hostObj, port, PartialOptions.DEFAULT) != null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
public static AddressRule doParseRule(UnmodifiableConfig builder) {
|
||||||
public static AddressRule parseRule(UnmodifiableConfig builder) {
|
|
||||||
var hostObj = get(builder, "host", String.class).orElse(null);
|
var hostObj = get(builder, "host", String.class).orElse(null);
|
||||||
if (hostObj == null) return null;
|
if (hostObj == null) throw new InvalidRuleException("No 'host' specified");
|
||||||
|
|
||||||
var action = getEnum(builder, "action", Action.class).orElse(null);
|
var action = getEnum(builder, "action", Action.class).orElse(null);
|
||||||
var port = unboxOptInt(get(builder, "port", Number.class));
|
var port = unboxOptInt(get(builder, "port", Number.class));
|
||||||
@@ -88,38 +102,19 @@ class AddressRuleConfig {
|
|||||||
return AddressRule.parse(hostObj, port, options);
|
return AddressRule.parse(hostObj, port, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> boolean check(UnmodifiableConfig config, String field, Class<T> klass) {
|
|
||||||
var value = config.get(field);
|
|
||||||
if (value == null || klass.isInstance(value)) return true;
|
|
||||||
|
|
||||||
LOG.warn("HTTP rule's {} is not a {}.", field, klass.getSimpleName());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static <T extends Enum<T>> boolean checkEnum(UnmodifiableConfig config, String field, Class<T> klass) {
|
|
||||||
var value = config.get(field);
|
|
||||||
if (value == null) return true;
|
|
||||||
|
|
||||||
if (!(value instanceof String)) {
|
|
||||||
LOG.warn("HTTP rule's {} is not a string", field);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parseEnum(klass, (String) value) == null) {
|
|
||||||
LOG.warn("HTTP rule's {} is not a known option", field);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static <T> Optional<T> get(UnmodifiableConfig config, String field, Class<T> klass) {
|
private static <T> Optional<T> get(UnmodifiableConfig config, String field, Class<T> klass) {
|
||||||
var value = config.get(field);
|
var value = config.get(field);
|
||||||
return klass.isInstance(value) ? Optional.of(klass.cast(value)) : Optional.empty();
|
if (value == null) return Optional.empty();
|
||||||
|
if (klass.isInstance(value)) return Optional.of(klass.cast(value));
|
||||||
|
|
||||||
|
throw new InvalidRuleException(String.format(
|
||||||
|
"Field '%s' should be a '%s' but is a %s.",
|
||||||
|
field, klass.getSimpleName(), value.getClass().getSimpleName()
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T extends Enum<T>> Optional<T> getEnum(UnmodifiableConfig config, String field, Class<T> klass) {
|
private static <T extends Enum<T>> Optional<T> getEnum(UnmodifiableConfig config, String field, Class<T> klass) {
|
||||||
return get(config, field, String.class).map(x -> parseEnum(klass, x));
|
return get(config, field, String.class).map(x -> parseEnum(field, klass, x));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static OptionalLong unboxOptLong(Optional<? extends Number> value) {
|
private static OptionalLong unboxOptLong(Optional<? extends Number> value) {
|
||||||
@@ -130,11 +125,14 @@ class AddressRuleConfig {
|
|||||||
return value.map(Number::intValue).map(OptionalInt::of).orElse(OptionalInt.empty());
|
return value.map(Number::intValue).map(OptionalInt::of).orElse(OptionalInt.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
private static <T extends Enum<T>> T parseEnum(String field, Class<T> klass, String x) {
|
||||||
private static <T extends Enum<T>> T parseEnum(Class<T> klass, String x) {
|
|
||||||
for (var value : klass.getEnumConstants()) {
|
for (var value : klass.getEnumConstants()) {
|
||||||
if (value.name().equalsIgnoreCase(x)) return value;
|
if (value.name().equalsIgnoreCase(x)) return value;
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
|
throw new InvalidRuleException(String.format(
|
||||||
|
"Field '%s' should be one of %s, but is '%s'.",
|
||||||
|
field, Arrays.stream(klass.getEnumConstants()).map(Enum::name).toList(), x
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -54,12 +54,6 @@ public interface ConfigFile {
|
|||||||
* A group of config entries.
|
* A group of config entries.
|
||||||
*/
|
*/
|
||||||
non-sealed interface Group extends Entry {
|
non-sealed interface Group extends Entry {
|
||||||
/**
|
|
||||||
* Get all entries in this group.
|
|
||||||
*
|
|
||||||
* @return All child entries.
|
|
||||||
*/
|
|
||||||
Stream<Entry> children();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -9,7 +9,6 @@ import dan200.computercraft.api.ComputerCraftAPI;
|
|||||||
import dan200.computercraft.core.CoreConfig;
|
import dan200.computercraft.core.CoreConfig;
|
||||||
import dan200.computercraft.core.Logging;
|
import dan200.computercraft.core.Logging;
|
||||||
import dan200.computercraft.core.apis.http.NetworkUtils;
|
import dan200.computercraft.core.apis.http.NetworkUtils;
|
||||||
import dan200.computercraft.core.apis.http.options.Action;
|
|
||||||
import dan200.computercraft.core.apis.http.options.ProxyType;
|
import dan200.computercraft.core.apis.http.options.ProxyType;
|
||||||
import dan200.computercraft.core.computer.mainthread.MainThreadConfig;
|
import dan200.computercraft.core.computer.mainthread.MainThreadConfig;
|
||||||
import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer;
|
import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer;
|
||||||
@@ -20,9 +19,7 @@ import org.apache.logging.log4j.core.filter.MarkerFilter;
|
|||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public final class ConfigSpec {
|
public final class ConfigSpec {
|
||||||
@@ -38,6 +35,7 @@ public final class ConfigSpec {
|
|||||||
public static final ConfigFile.Value<Boolean> logComputerErrors;
|
public static final ConfigFile.Value<Boolean> logComputerErrors;
|
||||||
public static final ConfigFile.Value<Boolean> commandRequireCreative;
|
public static final ConfigFile.Value<Boolean> commandRequireCreative;
|
||||||
public static final ConfigFile.Value<Integer> uploadMaxSize;
|
public static final ConfigFile.Value<Integer> uploadMaxSize;
|
||||||
|
public static final ConfigFile.Value<List<? extends String>> disabledGenericMethods;
|
||||||
|
|
||||||
public static final ConfigFile.Value<Integer> computerThreads;
|
public static final ConfigFile.Value<Integer> computerThreads;
|
||||||
public static final ConfigFile.Value<Integer> maxMainGlobalTime;
|
public static final ConfigFile.Value<Integer> maxMainGlobalTime;
|
||||||
@@ -142,6 +140,19 @@ public final class ConfigSpec {
|
|||||||
Require players to be in creative mode and be opped in order to interact with
|
Require players to be in creative mode and be opped in order to interact with
|
||||||
command computers. This is the default behaviour for vanilla's Command blocks.""")
|
command computers. This is the default behaviour for vanilla's Command blocks.""")
|
||||||
.define("command_require_creative", Config.commandRequireCreative);
|
.define("command_require_creative", Config.commandRequireCreative);
|
||||||
|
|
||||||
|
disabledGenericMethods = builder
|
||||||
|
.comment("""
|
||||||
|
A list of generic methods or method sources to disable. Generic methods are
|
||||||
|
methods added to a block/block entity when there is no explicit peripheral
|
||||||
|
provider. This includes inventory methods (i.e. inventory.getItemDetail,
|
||||||
|
inventory.pushItems), and (if on Forge), the fluid_storage and energy_storage
|
||||||
|
methods.
|
||||||
|
Methods in this list can either be a whole group of methods (computercraft:inventory)
|
||||||
|
or a single method (computercraft:inventory#pushItems).
|
||||||
|
""")
|
||||||
|
.worldRestart()
|
||||||
|
.defineList("disabled_generic_methods", List.of(), x -> x instanceof String);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -182,9 +193,9 @@ public final class ConfigSpec {
|
|||||||
|
|
||||||
httpEnabled = builder
|
httpEnabled = builder
|
||||||
.comment("""
|
.comment("""
|
||||||
Enable the "http" API on Computers. This also disables the "pastebin" and "wget"
|
Enable the "http" API on Computers. Disabling this also disables the "pastebin" and
|
||||||
programs, that many users rely on. It's recommended to leave this on and use the
|
"wget" programs, that many users rely on. It's recommended to leave this on and use
|
||||||
"rules" config option to impose more fine-grained control.""")
|
the "rules" config option to impose more fine-grained control.""")
|
||||||
.define("enabled", CoreConfig.httpEnabled);
|
.define("enabled", CoreConfig.httpEnabled);
|
||||||
|
|
||||||
httpWebsocketEnabled = builder
|
httpWebsocketEnabled = builder
|
||||||
@@ -194,16 +205,23 @@ public final class ConfigSpec {
|
|||||||
httpRules = builder
|
httpRules = builder
|
||||||
.comment("""
|
.comment("""
|
||||||
A list of rules which control behaviour of the "http" API for specific domains or
|
A list of rules which control behaviour of the "http" API for specific domains or
|
||||||
IPs. Each rule is an item with a 'host' to match against, and a series of
|
IPs. Each rule matches against a hostname and an optional port, and then sets several
|
||||||
properties. Rules are evaluated in order, meaning earlier rules override later
|
properties for the request. Rules are evaluated in order, meaning earlier rules override
|
||||||
ones.
|
later ones.
|
||||||
The host may be a domain name ("pastebin.com"), wildcard ("*.pastebin.com") or
|
|
||||||
CIDR notation ("127.0.0.0/8").
|
Valid properties:
|
||||||
If no rules, the domain is blocked.""")
|
- "host" (required): The domain or IP address this rule matches. This may be a domain name
|
||||||
.defineList("rules", Arrays.asList(
|
("pastebin.com"), wildcard ("*.pastebin.com") or CIDR notation ("127.0.0.0/8").
|
||||||
AddressRuleConfig.makeRule("$private", Action.DENY),
|
- "port" (optional): Only match requests for a specific port, such as 80 or 443.
|
||||||
AddressRuleConfig.makeRule("*", Action.ALLOW)
|
|
||||||
), x -> x instanceof UnmodifiableConfig && AddressRuleConfig.checkRule((UnmodifiableConfig) x));
|
- "action" (optional): Whether to allow or deny this request.
|
||||||
|
- "max_download" (optional): The maximum size (in bytes) that a computer can download in this
|
||||||
|
request.
|
||||||
|
- "max_upload" (optional): The maximum size (in bytes) that a computer can upload in a this request.
|
||||||
|
- "max_websocket_message" (optional): The maximum size (in bytes) that a computer can send or
|
||||||
|
receive in one websocket packet.
|
||||||
|
- "use_proxy" (optional): Enable use of the HTTP/SOCKS proxy if it is configured.""")
|
||||||
|
.defineList("rules", AddressRuleConfig.defaultRules(), x -> x instanceof UnmodifiableConfig);
|
||||||
|
|
||||||
httpMaxRequests = builder
|
httpMaxRequests = builder
|
||||||
.comment("""
|
.comment("""
|
||||||
@@ -395,8 +413,8 @@ public final class ConfigSpec {
|
|||||||
// HTTP
|
// HTTP
|
||||||
CoreConfig.httpEnabled = httpEnabled.get();
|
CoreConfig.httpEnabled = httpEnabled.get();
|
||||||
CoreConfig.httpWebsocketEnabled = httpWebsocketEnabled.get();
|
CoreConfig.httpWebsocketEnabled = httpWebsocketEnabled.get();
|
||||||
CoreConfig.httpRules = httpRules.get().stream()
|
|
||||||
.map(AddressRuleConfig::parseRule).filter(Objects::nonNull).toList();
|
CoreConfig.httpRules = httpRules.get().stream().map(AddressRuleConfig::parseRule).toList();
|
||||||
|
|
||||||
CoreConfig.httpMaxRequests = httpMaxRequests.get();
|
CoreConfig.httpMaxRequests = httpMaxRequests.get();
|
||||||
CoreConfig.httpMaxWebsockets = httpMaxWebsockets.get();
|
CoreConfig.httpMaxWebsockets = httpMaxWebsockets.get();
|
||||||
|
@@ -7,7 +7,6 @@ package dan200.computercraft.shared.container;
|
|||||||
import net.minecraft.core.NonNullList;
|
import net.minecraft.core.NonNullList;
|
||||||
import net.minecraft.world.Container;
|
import net.minecraft.world.Container;
|
||||||
import net.minecraft.world.ContainerHelper;
|
import net.minecraft.world.ContainerHelper;
|
||||||
import net.minecraft.world.entity.player.Player;
|
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -16,24 +15,6 @@ import net.minecraft.world.item.ItemStack;
|
|||||||
public interface BasicContainer extends Container {
|
public interface BasicContainer extends Container {
|
||||||
NonNullList<ItemStack> getContents();
|
NonNullList<ItemStack> getContents();
|
||||||
|
|
||||||
@Override
|
|
||||||
default int getMaxStackSize() {
|
|
||||||
return 64;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
default void startOpen(Player player) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
default void stopOpen(Player player) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
default boolean canPlaceItem(int slot, ItemStack stack) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default int getContainerSize() {
|
default int getContainerSize() {
|
||||||
return getContents().size();
|
return getContents().size();
|
||||||
|
@@ -5,6 +5,7 @@
|
|||||||
package dan200.computercraft.shared.integration;
|
package dan200.computercraft.shared.integration;
|
||||||
|
|
||||||
import dan200.computercraft.api.ComputerCraftAPI;
|
import dan200.computercraft.api.ComputerCraftAPI;
|
||||||
|
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||||
import dan200.computercraft.impl.PocketUpgrades;
|
import dan200.computercraft.impl.PocketUpgrades;
|
||||||
import dan200.computercraft.impl.TurtleUpgrades;
|
import dan200.computercraft.impl.TurtleUpgrades;
|
||||||
import dan200.computercraft.shared.ModRegistry;
|
import dan200.computercraft.shared.ModRegistry;
|
||||||
@@ -56,14 +57,14 @@ public final class RecipeModHelpers {
|
|||||||
for (var turtleSupplier : TURTLES) {
|
for (var turtleSupplier : TURTLES) {
|
||||||
var turtle = turtleSupplier.get();
|
var turtle = turtleSupplier.get();
|
||||||
for (var upgrade : TurtleUpgrades.instance().getUpgrades()) {
|
for (var upgrade : TurtleUpgrades.instance().getUpgrades()) {
|
||||||
upgradeItems.add(turtle.create(-1, null, -1, null, upgrade, 0, null));
|
upgradeItems.add(turtle.create(-1, null, -1, null, UpgradeData.ofDefault(upgrade), 0, null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var pocketSupplier : POCKET_COMPUTERS) {
|
for (var pocketSupplier : POCKET_COMPUTERS) {
|
||||||
var pocket = pocketSupplier.get();
|
var pocket = pocketSupplier.get();
|
||||||
for (var upgrade : PocketUpgrades.instance().getUpgrades()) {
|
for (var upgrade : PocketUpgrades.instance().getUpgrades()) {
|
||||||
upgradeItems.add(pocket.create(-1, null, -1, upgrade));
|
upgradeItems.add(pocket.create(-1, null, -1, UpgradeData.ofDefault(upgrade)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -9,6 +9,7 @@ import dan200.computercraft.api.pocket.IPocketUpgrade;
|
|||||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||||
import dan200.computercraft.api.turtle.TurtleSide;
|
import dan200.computercraft.api.turtle.TurtleSide;
|
||||||
import dan200.computercraft.api.upgrades.UpgradeBase;
|
import dan200.computercraft.api.upgrades.UpgradeBase;
|
||||||
|
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||||
import dan200.computercraft.impl.PocketUpgrades;
|
import dan200.computercraft.impl.PocketUpgrades;
|
||||||
import dan200.computercraft.impl.TurtleUpgrades;
|
import dan200.computercraft.impl.TurtleUpgrades;
|
||||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
||||||
@@ -111,20 +112,22 @@ public class UpgradeRecipeGenerator<T> {
|
|||||||
|
|
||||||
if (stack.getItem() instanceof TurtleItem item) {
|
if (stack.getItem() instanceof TurtleItem item) {
|
||||||
// Suggest possible upgrades which can be applied to this turtle
|
// Suggest possible upgrades which can be applied to this turtle
|
||||||
var left = item.getUpgrade(stack, TurtleSide.LEFT);
|
var left = item.getUpgradeWithData(stack, TurtleSide.LEFT);
|
||||||
var right = item.getUpgrade(stack, TurtleSide.RIGHT);
|
var right = item.getUpgradeWithData(stack, TurtleSide.RIGHT);
|
||||||
if (left != null && right != null) return Collections.emptyList();
|
if (left != null && right != null) return Collections.emptyList();
|
||||||
|
|
||||||
List<T> recipes = new ArrayList<>();
|
List<T> recipes = new ArrayList<>();
|
||||||
var ingredient = Ingredient.of(stack);
|
var ingredient = Ingredient.of(stack);
|
||||||
for (var upgrade : turtleUpgrades) {
|
for (var upgrade : turtleUpgrades) {
|
||||||
|
if (upgrade.turtle == null) throw new NullPointerException();
|
||||||
|
|
||||||
// The turtle is facing towards us, so upgrades on the left are actually crafted on the right.
|
// The turtle is facing towards us, so upgrades on the left are actually crafted on the right.
|
||||||
if (left == null) {
|
if (left == null) {
|
||||||
recipes.add(turtle(ingredient, upgrade.ingredient, turtleWith(stack, upgrade.turtle, right)));
|
recipes.add(turtle(ingredient, upgrade.ingredient, turtleWith(stack, UpgradeData.ofDefault(upgrade.turtle), right)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (right == null) {
|
if (right == null) {
|
||||||
recipes.add(turtle(upgrade.ingredient, ingredient, turtleWith(stack, left, upgrade.turtle)));
|
recipes.add(turtle(upgrade.ingredient, ingredient, turtleWith(stack, left, UpgradeData.ofDefault(upgrade.turtle))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +140,8 @@ public class UpgradeRecipeGenerator<T> {
|
|||||||
List<T> recipes = new ArrayList<>();
|
List<T> recipes = new ArrayList<>();
|
||||||
var ingredient = Ingredient.of(stack);
|
var ingredient = Ingredient.of(stack);
|
||||||
for (var upgrade : pocketUpgrades) {
|
for (var upgrade : pocketUpgrades) {
|
||||||
recipes.add(pocket(upgrade.ingredient, ingredient, pocketWith(stack, upgrade.pocket)));
|
if (upgrade.pocket == null) throw new NullPointerException();
|
||||||
|
recipes.add(pocket(upgrade.ingredient, ingredient, pocketWith(stack, UpgradeData.ofDefault(upgrade.pocket))));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Collections.unmodifiableList(recipes);
|
return Collections.unmodifiableList(recipes);
|
||||||
@@ -180,21 +184,21 @@ public class UpgradeRecipeGenerator<T> {
|
|||||||
if (stack.getItem() instanceof TurtleItem item) {
|
if (stack.getItem() instanceof TurtleItem item) {
|
||||||
List<T> recipes = new ArrayList<>(0);
|
List<T> recipes = new ArrayList<>(0);
|
||||||
|
|
||||||
var left = item.getUpgrade(stack, TurtleSide.LEFT);
|
var left = item.getUpgradeWithData(stack, TurtleSide.LEFT);
|
||||||
var right = item.getUpgrade(stack, TurtleSide.RIGHT);
|
var right = item.getUpgradeWithData(stack, TurtleSide.RIGHT);
|
||||||
|
|
||||||
// The turtle is facing towards us, so upgrades on the left are actually crafted on the right.
|
// The turtle is facing towards us, so upgrades on the left are actually crafted on the right.
|
||||||
if (left != null) {
|
if (left != null) {
|
||||||
recipes.add(turtle(
|
recipes.add(turtle(
|
||||||
Ingredient.of(turtleWith(stack, null, right)),
|
Ingredient.of(turtleWith(stack, null, right)),
|
||||||
Ingredient.of(left.getCraftingItem()),
|
Ingredient.of(left.getUpgradeItem()),
|
||||||
stack
|
stack
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (right != null) {
|
if (right != null) {
|
||||||
recipes.add(turtle(
|
recipes.add(turtle(
|
||||||
Ingredient.of(right.getCraftingItem()),
|
Ingredient.of(right.getUpgradeItem()),
|
||||||
Ingredient.of(turtleWith(stack, left, null)),
|
Ingredient.of(turtleWith(stack, left, null)),
|
||||||
stack
|
stack
|
||||||
));
|
));
|
||||||
@@ -204,9 +208,9 @@ public class UpgradeRecipeGenerator<T> {
|
|||||||
} else if (stack.getItem() instanceof PocketComputerItem) {
|
} else if (stack.getItem() instanceof PocketComputerItem) {
|
||||||
List<T> recipes = new ArrayList<>(0);
|
List<T> recipes = new ArrayList<>(0);
|
||||||
|
|
||||||
var back = PocketComputerItem.getUpgrade(stack);
|
var back = PocketComputerItem.getUpgradeWithData(stack);
|
||||||
if (back != null) {
|
if (back != null) {
|
||||||
recipes.add(pocket(Ingredient.of(back.getCraftingItem()), Ingredient.of(pocketWith(stack, null)), stack));
|
recipes.add(pocket(Ingredient.of(back.getUpgradeItem()), Ingredient.of(pocketWith(stack, null)), stack));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Collections.unmodifiableList(recipes);
|
return Collections.unmodifiableList(recipes);
|
||||||
@@ -215,7 +219,7 @@ public class UpgradeRecipeGenerator<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ItemStack turtleWith(ItemStack stack, @Nullable ITurtleUpgrade left, @Nullable ITurtleUpgrade right) {
|
private static ItemStack turtleWith(ItemStack stack, @Nullable UpgradeData<ITurtleUpgrade> left, @Nullable UpgradeData<ITurtleUpgrade> right) {
|
||||||
var item = (TurtleItem) stack.getItem();
|
var item = (TurtleItem) stack.getItem();
|
||||||
return item.create(
|
return item.create(
|
||||||
item.getComputerID(stack), item.getLabel(stack), item.getColour(stack),
|
item.getComputerID(stack), item.getLabel(stack), item.getColour(stack),
|
||||||
@@ -223,7 +227,7 @@ public class UpgradeRecipeGenerator<T> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ItemStack pocketWith(ItemStack stack, @Nullable IPocketUpgrade back) {
|
private static ItemStack pocketWith(ItemStack stack, @Nullable UpgradeData<IPocketUpgrade> back) {
|
||||||
var item = (PocketComputerItem) stack.getItem();
|
var item = (PocketComputerItem) stack.getItem();
|
||||||
return item.create(
|
return item.create(
|
||||||
item.getComputerID(stack), item.getLabel(stack), item.getColour(stack), back
|
item.getComputerID(stack), item.getLabel(stack), item.getColour(stack), back
|
||||||
@@ -272,7 +276,7 @@ public class UpgradeRecipeGenerator<T> {
|
|||||||
recipes.add(turtle(
|
recipes.add(turtle(
|
||||||
ingredient, // Right upgrade, recipe on left
|
ingredient, // Right upgrade, recipe on left
|
||||||
Ingredient.of(turtleItem.create(-1, null, -1, null, null, 0, null)),
|
Ingredient.of(turtleItem.create(-1, null, -1, null, null, 0, null)),
|
||||||
turtleItem.create(-1, null, -1, null, turtle, 0, null)
|
turtleItem.create(-1, null, -1, null, UpgradeData.ofDefault(turtle), 0, null)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -283,7 +287,7 @@ public class UpgradeRecipeGenerator<T> {
|
|||||||
recipes.add(pocket(
|
recipes.add(pocket(
|
||||||
ingredient,
|
ingredient,
|
||||||
Ingredient.of(pocketItem.create(-1, null, -1, null)),
|
Ingredient.of(pocketItem.create(-1, null, -1, null)),
|
||||||
pocketItem.create(-1, null, -1, pocket)
|
pocketItem.create(-1, null, -1, UpgradeData.ofDefault(pocket))
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -12,19 +12,23 @@ import dan200.computercraft.api.peripheral.IComputerAccess;
|
|||||||
import dan200.computercraft.api.peripheral.IDynamicPeripheral;
|
import dan200.computercraft.api.peripheral.IDynamicPeripheral;
|
||||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||||
import dan200.computercraft.shared.platform.RegistryWrappers;
|
import dan200.computercraft.shared.platform.RegistryWrappers;
|
||||||
|
import net.minecraft.core.Direction;
|
||||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
class GenericPeripheral implements IDynamicPeripheral {
|
public final class GenericPeripheral implements IDynamicPeripheral {
|
||||||
|
private final BlockEntity tile;
|
||||||
|
private final Direction side;
|
||||||
|
|
||||||
private final String type;
|
private final String type;
|
||||||
private final Set<String> additionalTypes;
|
private final Set<String> additionalTypes;
|
||||||
private final BlockEntity tile;
|
|
||||||
private final List<SaturatedMethod> methods;
|
private final List<SaturatedMethod> methods;
|
||||||
|
|
||||||
GenericPeripheral(BlockEntity tile, @Nullable String name, Set<String> additionalTypes, List<SaturatedMethod> methods) {
|
GenericPeripheral(BlockEntity tile, Direction side, @Nullable String name, Set<String> additionalTypes, List<SaturatedMethod> methods) {
|
||||||
|
this.side = side;
|
||||||
var type = RegistryWrappers.BLOCK_ENTITY_TYPES.getKey(tile.getType());
|
var type = RegistryWrappers.BLOCK_ENTITY_TYPES.getKey(tile.getType());
|
||||||
this.tile = tile;
|
this.tile = tile;
|
||||||
this.type = name != null ? name : type.toString();
|
this.type = name != null ? name : type.toString();
|
||||||
@@ -32,6 +36,10 @@ class GenericPeripheral implements IDynamicPeripheral {
|
|||||||
this.methods = methods;
|
this.methods = methods;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Direction side() {
|
||||||
|
return side;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String[] getMethodNames() {
|
public String[] getMethodNames() {
|
||||||
var names = new String[methods.size()];
|
var names = new String[methods.size()];
|
||||||
@@ -54,7 +62,6 @@ class GenericPeripheral implements IDynamicPeripheral {
|
|||||||
return additionalTypes;
|
return additionalTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
@Override
|
||||||
public Object getTarget() {
|
public Object getTarget() {
|
||||||
return tile;
|
return tile;
|
||||||
|
@@ -0,0 +1,63 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.shared.peripheral.generic;
|
||||||
|
|
||||||
|
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||||
|
import dan200.computercraft.api.peripheral.PeripheralType;
|
||||||
|
import dan200.computercraft.core.methods.MethodSupplier;
|
||||||
|
import dan200.computercraft.core.methods.NamedMethod;
|
||||||
|
import dan200.computercraft.core.methods.PeripheralMethod;
|
||||||
|
import dan200.computercraft.shared.computer.core.ServerContext;
|
||||||
|
import net.minecraft.core.Direction;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A builder for a {@link GenericPeripheral}.
|
||||||
|
* <p>
|
||||||
|
* This handles building a list of {@linkplain SaturatedMethod methods} and computing the appropriate
|
||||||
|
* {@link PeripheralType} from the {@linkplain NamedMethod#genericType() methods' peripheral types}.
|
||||||
|
* <p>
|
||||||
|
* See the platform-specific peripheral providers for the usage of this.
|
||||||
|
*/
|
||||||
|
final class GenericPeripheralBuilder {
|
||||||
|
private final MethodSupplier<PeripheralMethod> peripheralMethods;
|
||||||
|
|
||||||
|
private @Nullable String name;
|
||||||
|
private final Set<String> additionalTypes = new HashSet<>(0);
|
||||||
|
private final ArrayList<SaturatedMethod> methods = new ArrayList<>();
|
||||||
|
|
||||||
|
GenericPeripheralBuilder(MinecraftServer server) {
|
||||||
|
peripheralMethods = ServerContext.get(server).peripheralMethods();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
IPeripheral toPeripheral(BlockEntity blockEntity, Direction side) {
|
||||||
|
if (methods.isEmpty()) return null;
|
||||||
|
|
||||||
|
methods.trimToSize();
|
||||||
|
return new GenericPeripheral(blockEntity, side, name, additionalTypes, methods);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean addMethods(Object target) {
|
||||||
|
return peripheralMethods.forEachSelfMethod(target, (name, method, info) -> {
|
||||||
|
methods.add(new SaturatedMethod(target, name, method));
|
||||||
|
|
||||||
|
// If we have a peripheral type, use it. Always pick the smallest one, so it's consistent (assuming mods
|
||||||
|
// don't change).
|
||||||
|
var type = info == null ? null : info.genericType();
|
||||||
|
if (type != null && type.getPrimaryType() != null) {
|
||||||
|
var primaryType = type.getPrimaryType();
|
||||||
|
if (this.name == null || this.name.compareTo(primaryType) > 0) this.name = primaryType;
|
||||||
|
}
|
||||||
|
if (type != null) additionalTypes.addAll(type.getAdditionalTypes());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -9,18 +9,20 @@ import dan200.computercraft.api.lua.ILuaContext;
|
|||||||
import dan200.computercraft.api.lua.LuaException;
|
import dan200.computercraft.api.lua.LuaException;
|
||||||
import dan200.computercraft.api.lua.MethodResult;
|
import dan200.computercraft.api.lua.MethodResult;
|
||||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||||
import dan200.computercraft.core.asm.NamedMethod;
|
import dan200.computercraft.core.methods.PeripheralMethod;
|
||||||
import dan200.computercraft.core.asm.PeripheralMethod;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link PeripheralMethod} along with the method's target.
|
||||||
|
*/
|
||||||
final class SaturatedMethod {
|
final class SaturatedMethod {
|
||||||
private final Object target;
|
private final Object target;
|
||||||
private final String name;
|
private final String name;
|
||||||
private final PeripheralMethod method;
|
private final PeripheralMethod method;
|
||||||
|
|
||||||
SaturatedMethod(Object target, NamedMethod<PeripheralMethod> method) {
|
SaturatedMethod(Object target, String name, PeripheralMethod method) {
|
||||||
this.target = target;
|
this.target = target;
|
||||||
name = method.getName();
|
this.name = name;
|
||||||
this.method = method.getMethod();
|
this.method = method;
|
||||||
}
|
}
|
||||||
|
|
||||||
MethodResult apply(ILuaContext context, IComputerAccess computer, IArguments args) throws LuaException {
|
MethodResult apply(ILuaContext context, IComputerAccess computer, IArguments args) throws LuaException {
|
||||||
|
@@ -16,10 +16,12 @@ import dan200.computercraft.api.peripheral.IPeripheral;
|
|||||||
import dan200.computercraft.api.peripheral.NotAttachedException;
|
import dan200.computercraft.api.peripheral.NotAttachedException;
|
||||||
import dan200.computercraft.api.peripheral.WorkMonitor;
|
import dan200.computercraft.api.peripheral.WorkMonitor;
|
||||||
import dan200.computercraft.core.apis.PeripheralAPI;
|
import dan200.computercraft.core.apis.PeripheralAPI;
|
||||||
import dan200.computercraft.core.asm.PeripheralMethod;
|
import dan200.computercraft.core.methods.PeripheralMethod;
|
||||||
import dan200.computercraft.core.util.LuaUtil;
|
import dan200.computercraft.core.util.LuaUtil;
|
||||||
|
import dan200.computercraft.shared.computer.core.ServerContext;
|
||||||
import dan200.computercraft.shared.peripheral.modem.ModemPeripheral;
|
import dan200.computercraft.shared.peripheral.modem.ModemPeripheral;
|
||||||
import dan200.computercraft.shared.peripheral.modem.ModemState;
|
import dan200.computercraft.shared.peripheral.modem.ModemState;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
import net.minecraft.world.level.Level;
|
import net.minecraft.world.level.Level;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -284,7 +286,8 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements Wi
|
|||||||
|
|
||||||
private void attachPeripheralImpl(IComputerAccess computer, ConcurrentMap<String, RemotePeripheralWrapper> peripherals, String periphName, IPeripheral peripheral) {
|
private void attachPeripheralImpl(IComputerAccess computer, ConcurrentMap<String, RemotePeripheralWrapper> peripherals, String periphName, IPeripheral peripheral) {
|
||||||
if (!peripherals.containsKey(periphName) && !periphName.equals(getLocalPeripheral().getConnectedName())) {
|
if (!peripherals.containsKey(periphName) && !periphName.equals(getLocalPeripheral().getConnectedName())) {
|
||||||
var wrapper = new RemotePeripheralWrapper(modem, peripheral, computer, periphName);
|
var methods = ServerContext.get(((ServerLevel) getLevel()).getServer()).peripheralMethods().getSelfMethods(peripheral);
|
||||||
|
var wrapper = new RemotePeripheralWrapper(modem, peripheral, computer, periphName, methods);
|
||||||
peripherals.put(periphName, wrapper);
|
peripherals.put(periphName, wrapper);
|
||||||
wrapper.attach();
|
wrapper.attach();
|
||||||
}
|
}
|
||||||
@@ -314,7 +317,7 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements Wi
|
|||||||
private volatile boolean attached;
|
private volatile boolean attached;
|
||||||
private final Set<String> mounts = new HashSet<>();
|
private final Set<String> mounts = new HashSet<>();
|
||||||
|
|
||||||
RemotePeripheralWrapper(WiredModemElement element, IPeripheral peripheral, IComputerAccess computer, String name) {
|
RemotePeripheralWrapper(WiredModemElement element, IPeripheral peripheral, IComputerAccess computer, String name, Map<String, PeripheralMethod> methods) {
|
||||||
this.element = element;
|
this.element = element;
|
||||||
this.peripheral = peripheral;
|
this.peripheral = peripheral;
|
||||||
this.computer = computer;
|
this.computer = computer;
|
||||||
@@ -322,7 +325,7 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements Wi
|
|||||||
|
|
||||||
type = Objects.requireNonNull(peripheral.getType(), "Peripheral type cannot be null");
|
type = Objects.requireNonNull(peripheral.getType(), "Peripheral type cannot be null");
|
||||||
additionalTypes = peripheral.getAdditionalTypes();
|
additionalTypes = peripheral.getAdditionalTypes();
|
||||||
methodMap = PeripheralAPI.getMethods(peripheral);
|
methodMap = methods;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void attach() {
|
public void attach() {
|
||||||
|
@@ -190,7 +190,7 @@ public abstract class SpeakerPeripheral implements IPeripheral {
|
|||||||
* The speaker supports [all of Minecraft's noteblock instruments](https://minecraft.fandom.com/wiki/Note_Block#Instruments).
|
* The speaker supports [all of Minecraft's noteblock instruments](https://minecraft.fandom.com/wiki/Note_Block#Instruments).
|
||||||
* These are:
|
* These are:
|
||||||
* <p>
|
* <p>
|
||||||
* {@code "harp"}, {@code "basedrum"}, {@code "snare"}, {@code "hat"}, {@code "bass"}, @code "flute"},
|
* {@code "harp"}, {@code "basedrum"}, {@code "snare"}, {@code "hat"}, {@code "bass"}, {@code "flute"},
|
||||||
* {@code "bell"}, {@code "guitar"}, {@code "chime"}, {@code "xylophone"}, {@code "iron_xylophone"},
|
* {@code "bell"}, {@code "guitar"}, {@code "chime"}, {@code "xylophone"}, {@code "iron_xylophone"},
|
||||||
* {@code "cow_bell"}, {@code "didgeridoo"}, {@code "bit"}, {@code "banjo"} and {@code "pling"}.
|
* {@code "cow_bell"}, {@code "didgeridoo"}, {@code "bit"}, {@code "banjo"} and {@code "pling"}.
|
||||||
*
|
*
|
||||||
|
@@ -19,7 +19,7 @@ public record SpeakerPosition(@Nullable Level level, Vec3 position, @Nullable En
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static SpeakerPosition of(Entity entity) {
|
public static SpeakerPosition of(Entity entity) {
|
||||||
return new SpeakerPosition(entity.level(), entity.getEyePosition(1), entity);
|
return new SpeakerPosition(entity.level, entity.getEyePosition(1), entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean withinDistance(SpeakerPosition other, double distanceSq) {
|
public boolean withinDistance(SpeakerPosition other, double distanceSq) {
|
||||||
|
@@ -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;
|
||||||
|
}
|
@@ -33,7 +33,6 @@ import net.minecraft.world.entity.player.Player;
|
|||||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||||
import net.minecraft.world.inventory.CraftingContainer;
|
import net.minecraft.world.inventory.CraftingContainer;
|
||||||
import net.minecraft.world.inventory.MenuType;
|
import net.minecraft.world.inventory.MenuType;
|
||||||
import net.minecraft.world.item.CreativeModeTab;
|
|
||||||
import net.minecraft.world.item.DyeColor;
|
import net.minecraft.world.item.DyeColor;
|
||||||
import net.minecraft.world.item.Item;
|
import net.minecraft.world.item.Item;
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
@@ -69,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.
|
||||||
*
|
*
|
||||||
@@ -273,13 +279,6 @@ public interface PlatformHelper extends dan200.computercraft.impl.PlatformHelper
|
|||||||
*/
|
*/
|
||||||
int getBurnTime(ItemStack stack);
|
int getBurnTime(ItemStack stack);
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a builder for a new creative tab.
|
|
||||||
*
|
|
||||||
* @return The creative tab builder.
|
|
||||||
*/
|
|
||||||
CreativeModeTab.Builder newCreativeModeTab();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the "container" item to be returned after crafting. For instance, crafting with a lava bucket should return
|
* Get the "container" item to be returned after crafting. For instance, crafting with a lava bucket should return
|
||||||
* an empty bucket.
|
* an empty bucket.
|
||||||
|
@@ -5,6 +5,7 @@
|
|||||||
package dan200.computercraft.shared.platform;
|
package dan200.computercraft.shared.platform;
|
||||||
|
|
||||||
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
|
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
|
||||||
|
import net.minecraft.core.IdMap;
|
||||||
import net.minecraft.core.Registry;
|
import net.minecraft.core.Registry;
|
||||||
import net.minecraft.core.registries.Registries;
|
import net.minecraft.core.registries.Registries;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
@@ -36,9 +37,7 @@ public final class RegistryWrappers {
|
|||||||
public static final RegistryWrapper<RecipeSerializer<?>> RECIPE_SERIALIZERS = PlatformHelper.get().wrap(Registries.RECIPE_SERIALIZER);
|
public static final RegistryWrapper<RecipeSerializer<?>> RECIPE_SERIALIZERS = PlatformHelper.get().wrap(Registries.RECIPE_SERIALIZER);
|
||||||
public static final RegistryWrapper<MenuType<?>> MENU = PlatformHelper.get().wrap(Registries.MENU);
|
public static final RegistryWrapper<MenuType<?>> MENU = PlatformHelper.get().wrap(Registries.MENU);
|
||||||
|
|
||||||
public interface RegistryWrapper<T> extends Iterable<T> {
|
public interface RegistryWrapper<T> extends IdMap<T> {
|
||||||
int getId(T object);
|
|
||||||
|
|
||||||
ResourceLocation getKey(T object);
|
ResourceLocation getKey(T object);
|
||||||
|
|
||||||
T get(ResourceLocation location);
|
T get(ResourceLocation location);
|
||||||
@@ -46,8 +45,6 @@ public final class RegistryWrappers {
|
|||||||
@Nullable
|
@Nullable
|
||||||
T tryGet(ResourceLocation location);
|
T tryGet(ResourceLocation location);
|
||||||
|
|
||||||
T get(int id);
|
|
||||||
|
|
||||||
default Stream<T> stream() {
|
default Stream<T> stream() {
|
||||||
return StreamSupport.stream(spliterator(), false);
|
return StreamSupport.stream(spliterator(), false);
|
||||||
}
|
}
|
||||||
@@ -56,15 +53,6 @@ public final class RegistryWrappers {
|
|||||||
private RegistryWrappers() {
|
private RegistryWrappers() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <K> void writeId(FriendlyByteBuf buf, RegistryWrapper<K> registry, K object) {
|
|
||||||
buf.writeVarInt(registry.getId(object));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <K> K readId(FriendlyByteBuf buf, RegistryWrapper<K> registry) {
|
|
||||||
var id = buf.readVarInt();
|
|
||||||
return registry.get(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <K> void writeKey(FriendlyByteBuf buf, RegistryWrapper<K> registry, K object) {
|
public static <K> void writeKey(FriendlyByteBuf buf, RegistryWrapper<K> registry, K object) {
|
||||||
buf.writeResourceLocation(registry.getKey(object));
|
buf.writeResourceLocation(registry.getKey(object));
|
||||||
}
|
}
|
||||||
|
@@ -7,6 +7,7 @@ package dan200.computercraft.shared.pocket.apis;
|
|||||||
import dan200.computercraft.api.lua.ILuaAPI;
|
import dan200.computercraft.api.lua.ILuaAPI;
|
||||||
import dan200.computercraft.api.lua.LuaFunction;
|
import dan200.computercraft.api.lua.LuaFunction;
|
||||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
|
import dan200.computercraft.api.pocket.IPocketUpgrade;
|
||||||
|
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||||
import dan200.computercraft.impl.PocketUpgrades;
|
import dan200.computercraft.impl.PocketUpgrades;
|
||||||
import dan200.computercraft.shared.pocket.core.PocketServerComputer;
|
import dan200.computercraft.shared.pocket.core.PocketServerComputer;
|
||||||
import net.minecraft.core.NonNullList;
|
import net.minecraft.core.NonNullList;
|
||||||
@@ -14,6 +15,7 @@ import net.minecraft.world.entity.player.Player;
|
|||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Control the current pocket computer, adding or removing upgrades.
|
* Control the current pocket computer, adding or removing upgrades.
|
||||||
@@ -68,7 +70,7 @@ public class PocketAPI implements ILuaAPI {
|
|||||||
if (newUpgrade == null) return new Object[]{ false, "Cannot find a valid upgrade" };
|
if (newUpgrade == null) return new Object[]{ false, "Cannot find a valid upgrade" };
|
||||||
|
|
||||||
// Remove the current upgrade
|
// Remove the current upgrade
|
||||||
if (previousUpgrade != null) storeItem(player, previousUpgrade.getCraftingItem().copy());
|
if (previousUpgrade != null) storeItem(player, previousUpgrade.getUpgradeItem());
|
||||||
|
|
||||||
// Set the new upgrade
|
// Set the new upgrade
|
||||||
computer.setUpgrade(newUpgrade);
|
computer.setUpgrade(newUpgrade);
|
||||||
@@ -93,7 +95,7 @@ public class PocketAPI implements ILuaAPI {
|
|||||||
|
|
||||||
computer.setUpgrade(null);
|
computer.setUpgrade(null);
|
||||||
|
|
||||||
storeItem(player, previousUpgrade.getCraftingItem().copy());
|
storeItem(player, previousUpgrade.getUpgradeItem());
|
||||||
|
|
||||||
return new Object[]{ true };
|
return new Object[]{ true };
|
||||||
}
|
}
|
||||||
@@ -105,13 +107,13 @@ public class PocketAPI implements ILuaAPI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static @Nullable IPocketUpgrade findUpgrade(NonNullList<ItemStack> inv, int start, @Nullable IPocketUpgrade previous) {
|
private static @Nullable UpgradeData<IPocketUpgrade> findUpgrade(NonNullList<ItemStack> inv, int start, @Nullable UpgradeData<IPocketUpgrade> previous) {
|
||||||
for (var i = 0; i < inv.size(); i++) {
|
for (var i = 0; i < inv.size(); i++) {
|
||||||
var invStack = inv.get((i + start) % inv.size());
|
var invStack = inv.get((i + start) % inv.size());
|
||||||
if (!invStack.isEmpty()) {
|
if (!invStack.isEmpty()) {
|
||||||
var newUpgrade = PocketUpgrades.instance().get(invStack);
|
var newUpgrade = PocketUpgrades.instance().get(invStack);
|
||||||
|
|
||||||
if (newUpgrade != null && newUpgrade != previous) {
|
if (newUpgrade != null && !Objects.equals(newUpgrade, previous)) {
|
||||||
// Consume an item from this stack and exit the loop
|
// Consume an item from this stack and exit the loop
|
||||||
invStack = invStack.copy();
|
invStack = invStack.copy();
|
||||||
invStack.shrink(1);
|
invStack.shrink(1);
|
||||||
|
@@ -7,6 +7,7 @@ package dan200.computercraft.shared.pocket.core;
|
|||||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||||
import dan200.computercraft.api.pocket.IPocketAccess;
|
import dan200.computercraft.api.pocket.IPocketAccess;
|
||||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
|
import dan200.computercraft.api.pocket.IPocketUpgrade;
|
||||||
|
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||||
import dan200.computercraft.core.computer.ComputerSide;
|
import dan200.computercraft.core.computer.ComputerSide;
|
||||||
import dan200.computercraft.shared.common.IColouredItem;
|
import dan200.computercraft.shared.common.IColouredItem;
|
||||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||||
@@ -104,12 +105,13 @@ public class PocketServerComputer extends ServerComputer implements IPocketAcces
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated(forRemoval = true)
|
||||||
public Map<ResourceLocation, IPeripheral> getUpgrades() {
|
public Map<ResourceLocation, IPeripheral> getUpgrades() {
|
||||||
return upgrade == null ? Collections.emptyMap() : Collections.singletonMap(upgrade.getUpgradeID(), getPeripheral(ComputerSide.BACK));
|
return upgrade == null ? Collections.emptyMap() : Collections.singletonMap(upgrade.getUpgradeID(), getPeripheral(ComputerSide.BACK));
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable IPocketUpgrade getUpgrade() {
|
public @Nullable UpgradeData<IPocketUpgrade> getUpgrade() {
|
||||||
return upgrade;
|
return upgrade == null ? null : UpgradeData.of(upgrade, getUpgradeNBTData());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -119,13 +121,11 @@ public class PocketServerComputer extends ServerComputer implements IPocketAcces
|
|||||||
*
|
*
|
||||||
* @param upgrade The new upgrade to set it to, may be {@code null}.
|
* @param upgrade The new upgrade to set it to, may be {@code null}.
|
||||||
*/
|
*/
|
||||||
public void setUpgrade(@Nullable IPocketUpgrade upgrade) {
|
public void setUpgrade(@Nullable UpgradeData<IPocketUpgrade> upgrade) {
|
||||||
if (this.upgrade == upgrade) return;
|
|
||||||
|
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
PocketComputerItem.setUpgrade(stack, upgrade);
|
PocketComputerItem.setUpgrade(stack, upgrade);
|
||||||
updateUpgradeNBTData();
|
updateUpgradeNBTData();
|
||||||
this.upgrade = upgrade;
|
this.upgrade = upgrade == null ? null : upgrade.upgrade();
|
||||||
invalidatePeripheral();
|
invalidatePeripheral();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,7 +153,7 @@ public class PocketServerComputer extends ServerComputer implements IPocketAcces
|
|||||||
super.tickServer();
|
super.tickServer();
|
||||||
|
|
||||||
// Find any players which have gone missing and remove them from the tracking list.
|
// Find any players which have gone missing and remove them from the tracking list.
|
||||||
tracking.removeIf(player -> !player.isAlive() || player.level() != getLevel());
|
tracking.removeIf(player -> !player.isAlive() || player.level != getLevel());
|
||||||
|
|
||||||
// And now find any new players, add them to the tracking list, and broadcast state where appropriate.
|
// And now find any new players, add them to the tracking list, and broadcast state where appropriate.
|
||||||
var sendState = hasOutputChanged() || lightChanged;
|
var sendState = hasOutputChanged() || lightChanged;
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user