1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-10-20 00:17:38 +00:00

Compare commits

..

2 Commits

Author SHA1 Message Date
Jonathan Coates
1ece2aa23b Merge branch 'mc-1.19.x' into mc-1.20.x 2023-06-10 09:07:41 +01:00
Jonathan Coates
ff1e5f6823 Update to 1.20
- Use GuiGraphics for rendering UI elements. Almost definitely some
   z-fighting issues slipped in here.

 - Use Forge's loot modifier system for handling treasure disks. I have
   mixed feelings about this - it's a nice system, but also is far less
   efficient than the previous approach.

 - Regenerate data. This is the brunt of the commit, but nothing
   especially interesting here.
2023-06-08 09:52:00 +01:00
480 changed files with 2878 additions and 6700 deletions

View File

@@ -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,45 +27,39 @@ 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 - name: Build with Gradle
run: ./gradlew assemble || ./gradlew assemble run: |
./gradlew assemble || ./gradlew assemble
./gradlew downloadAssets || ./gradlew downloadAssets
./gradlew build
- name: 💡 Lint - name: Run client tests
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: 🧪 Parse test reports - name: Prepare Jars
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

View File

@@ -4,7 +4,6 @@ on:
push: push:
branches: branches:
- mc-1.19.x - mc-1.19.x
- mc-1.20.x
jobs: jobs:
make_doc: make_doc:

View File

@@ -6,7 +6,6 @@ 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/*
@@ -48,7 +47,6 @@ 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

View File

@@ -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 with helping out. provides an introduction as to how to get started in 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 installed: - Make sure you've got the following software instealled:
- 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 may be useful to write a test to make sure your code works as expected. When making larger changes, it's 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."

View File

@@ -4,12 +4,7 @@ SPDX-FileCopyrightText: 2017 The CC: Tweaked Developers
SPDX-License-Identifier: MPL-2.0 SPDX-License-Identifier: MPL-2.0
--> -->
<picture> # ![CC: Tweaked](doc/logo.png)
<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>
[![Current build status](https://github.com/cc-tweaked/CC-Tweaked/workflows/Build/badge.svg)](https://github.com/cc-tweaked/CC-Tweaked/actions "Current build status") [![Current build status](https://github.com/cc-tweaked/CC-Tweaked/workflows/Build/badge.svg)](https://github.com/cc-tweaked/CC-Tweaked/actions "Current build status")
[![Download CC: Tweaked on CurseForge](https://img.shields.io/static/v1?label=Download&message=CC:%20Tweaked&color=E04E14&logoColor=E04E14&logo=CurseForge)][CurseForge] [![Download CC: Tweaked on CurseForge](https://img.shields.io/static/v1?label=Download&message=CC:%20Tweaked&color=E04E14&logoColor=E04E14&logo=CurseForge)][CurseForge]
[![Download CC: Tweaked on Modrinth](https://img.shields.io/static/v1?label=Download&color=00AF5C&logoColor=00AF5C&logo=Modrinth&message=CC:%20Tweaked)][Modrinth] [![Download CC: Tweaked on Modrinth](https://img.shields.io/static/v1?label=Download&color=00AF5C&logoColor=00AF5C&logo=Modrinth&message=CC:%20Tweaked)][Modrinth]
@@ -49,7 +44,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:$cctVersion") compileOnly("cc.tweaked:cc-tweaked-$mcVersion-common-api")
// Forge Gradle // Forge Gradle
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-core-api:$cctVersion") compileOnly("cc.tweaked:cc-tweaked-$mcVersion-core-api:$cctVersion")
@@ -62,19 +57,6 @@ 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.

View File

@@ -10,9 +10,8 @@ import cc.tweaked.gradle.IdeaRunConfigurations
import cc.tweaked.gradle.MinecraftConfigurations import cc.tweaked.gradle.MinecraftConfigurations
plugins { plugins {
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("cc-tweaked.java-convention")
id("net.minecraftforge.gradle")
id("org.parchmentmc.librarian.forgegradle") id("org.parchmentmc.librarian.forgegradle")
} }

View File

@@ -37,35 +37,20 @@ java {
repositories { repositories {
mavenCentral() mavenCentral()
maven("https://squiddev.cc/maven") {
val mainMaven = maven("https://squiddev.cc/maven") {
name = "SquidDev" name = "SquidDev"
content { content {
// Until https://github.com/SpongePowered/Mixin/pull/593 is merged includeGroup("org.squiddev")
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") includeGroup("cc.tweaked")
includeModule("org.squiddev", "Cobalt")
// Things we mirror // Things we mirror
includeGroup("dev.architectury") includeGroup("dev.architectury")
includeGroup("dev.emi")
includeGroup("maven.modrinth") includeGroup("maven.modrinth")
includeGroup("me.shedaniel.cloth")
includeGroup("me.shedaniel") includeGroup("me.shedaniel")
includeGroup("me.shedaniel.cloth")
includeGroup("mezz.jei") includeGroup("mezz.jei")
includeModule("com.terraformersmc", "modmenu") includeModule("com.terraformersmc", "modmenu")
// Until https://github.com/SpongePowered/Mixin/pull/593 is merged
includeModule("org.spongepowered", "mixin")
} }
} }
} }
@@ -119,7 +104,6 @@ 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 {

View File

@@ -1,26 +0,0 @@
// 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),
)
}

View File

@@ -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").lowercase() val osName = System.getProperty("os.name").toLowerCase()
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").lowercase() val osArch = System.getProperty("os.arch").toLowerCase()
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"

View File

@@ -4,7 +4,6 @@
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
@@ -33,14 +32,11 @@ 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() = renderdoc || project.hasProperty("clientDebug") val clientDebug get() = project.hasProperty("clientDebug")
/** /**
* When [false], tests will not run under a framebuffer. * When [false], tests will not run under a framebuffer.
@@ -54,25 +50,6 @@ 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.
*/ */
@@ -84,7 +61,10 @@ 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))
} }
/** /**

View File

@@ -1,51 +0,0 @@
// 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))
}

View File

@@ -13,15 +13,6 @@ 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):

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -10,7 +10,7 @@ kotlin.jvm.target.validation.mode=error
# Mod properties # Mod properties
isUnstable=false isUnstable=false
modVersion=1.107.0 modVersion=1.105.0
# Minecraft properties: We want to configure this here so we can read it in settings.gradle # Minecraft properties: We want to configure this here so we can read it in settings.gradle
mcVersion=1.19.4 mcVersion=1.20

View File

@@ -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.86.1+1.19.4" fabric-api = "0.83.0+1.20"
fabric-loader = "0.14.21" fabric-loader = "0.14.21"
forge = "45.0.42" forge = "46.0.1"
forgeSpi = "6.0.0" forgeSpi = "6.0.0"
mixin = "0.8.5" mixin = "0.8.5"
parchment = "2023.06.26" parchment = "2023.03.12"
parchmentMc = "1.19.4" parchmentMc = "1.19.3"
# 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.1" cobalt = "0.7.0"
cobalt-next = "0.7.2" # Not a real version, used to constrain the version we accept. cobalt-next = "0.7.1" # 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,7 +33,6 @@ 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"
@@ -54,16 +53,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.3.7" fabric-loom = "1.1.10"
forgeGradle = "6.0.8" forgeGradle = "5.1.+"
githubRelease = "2.2.12" githubRelease = "2.2.12"
ideaExt = "1.1.6" ideaExt = "1.1.6"
illuaminate = "0.1.0-28-ga7efd71" illuaminate = "0.1.0-24-gdb28902"
librarian = "1.+" librarian = "1.+"
minotaur = "2.+" minotaur = "2.+"
mixinGradle = "0.7.+" mixinGradle = "0.7.+"
nullAway = "0.9.9" nullAway = "0.9.9"
quiltflower = "1.10.0" quiltflower = "1.8.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"
@@ -93,7 +92,6 @@ 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" }
@@ -152,10 +150,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 = ["jei-forge"] externalMods-forge-runtime = []
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 = ["jei-fabric", "modmenu"] externalMods-fabric-runtime = []
# Testing # Testing
test = ["junit-jupiter-api", "junit-jupiter-params", "hamcrest", "jqwik-api"] test = ["junit-jupiter-api", "junit-jupiter-params", "hamcrest", "jqwik-api"]

Binary file not shown.

View File

@@ -1,6 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

19
gradlew vendored
View File

@@ -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/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # https://github.com/gradle/gradle/blob/master/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,11 +80,14 @@ do
esac esac
done done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# 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
@@ -140,16 +143,12 @@ 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
@@ -194,10 +193,6 @@ 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
View File

@@ -26,7 +26,6 @@ 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%

View File

@@ -11,13 +11,9 @@ 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}.
@@ -32,45 +28,15 @@ 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, unless * @param turtle Access to the turtle that the upgrade resides on. This will be null when getting item models!
* {@link #getModel(ITurtleUpgrade, CompoundTag, TurtleSide)} is overriden.
* @param side Which side of the turtle (left or right) the upgrade resides on. * @param side Which side of the turtle (left or right) the upgrade resides on.
* @return The model that you wish to be used to render your upgrade. * @return The model that you wish to be used to render your upgrade.
*/ */
TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side); TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side);
/** /**
* Obtain the model to be used when rendering a turtle peripheral. * A basic {@link TurtleUpgradeModeller} which renders using the upgrade's {@linkplain ITurtleUpgrade#getCraftingItem()
* <p> * crafting item}.
* 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.
@@ -80,7 +46,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.UPGRADE_ITEM; return (TurtleUpgradeModeller<T>) TurtleUpgradeModellers.FLAT_ITEM;
} }
/** /**
@@ -92,8 +58,7 @@ 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) {
// TODO(1.21.0): Remove this. return (upgrade, turtle, side) -> TransformedModel.of(side == TurtleSide.LEFT ? left : right);
return sided((ResourceLocation) left, right);
} }
/** /**
@@ -105,16 +70,6 @@ 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 new TurtleUpgradeModeller<>() { return (upgrade, turtle, side) -> TransformedModel.of(side == TurtleSide.LEFT ? left : right);
@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);
}
};
} }
} }

View File

@@ -6,20 +6,13 @@ 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.4065f); private static final Transformation leftTransform = getMatrixFor(-0.40625f);
private static final Transformation rightTransform = getMatrixFor(0.4065f); private static final Transformation rightTransform = getMatrixFor(0.40625f);
private static Transformation getMatrixFor(float offset) { private static Transformation getMatrixFor(float offset) {
var matrix = new Matrix4f(); var matrix = new Matrix4f();
@@ -33,23 +26,6 @@ class TurtleUpgradeModellers {
return new Transformation(matrix); return new Transformation(matrix);
} }
static final TurtleUpgradeModeller<ITurtleUpgrade> UPGRADE_ITEM = new UpgradeItemModeller(); static final TurtleUpgradeModeller<ITurtleUpgrade> FLAT_ITEM = (upgrade, turtle, side) ->
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);
}
}
} }

View File

@@ -5,7 +5,6 @@
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;
@@ -25,15 +24,6 @@ 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;

View File

@@ -5,11 +5,9 @@
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;
@@ -71,8 +69,6 @@ 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();
@@ -84,10 +80,7 @@ public interface IPocketAccess {
void updateUpgradeNBTData(); void updateUpgradeNBTData();
/** /**
* Remove the current peripheral and create a new one. * Remove the current peripheral and create a new one. You may wish to do this if the methods available change.
* <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();
@@ -95,8 +88,6 @@ 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();
} }

View File

@@ -4,7 +4,6 @@
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;
@@ -30,14 +29,6 @@ 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.
* *

View File

@@ -8,15 +8,11 @@ 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;
@@ -71,29 +67,6 @@ 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.
* *
@@ -248,51 +221,23 @@ public interface ITurtleAccess {
void playAnimation(TurtleAnimation animation); void playAnimation(TurtleAnimation animation);
/** /**
* Returns the upgrade on the specified side of the turtle, if there is one. * Returns the turtle 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 #getUpgradeWithData(TurtleSide) * @see #setUpgrade(TurtleSide, ITurtleUpgrade)
* @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)}
*/ */
@Deprecated void setUpgrade(TurtleSide side, @Nullable ITurtleUpgrade upgrade);
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.
@@ -312,8 +257,6 @@ 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);

View File

@@ -7,7 +7,6 @@ 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;
@@ -80,17 +79,4 @@ 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;
}
} }

View File

@@ -1,49 +0,0 @@
// 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;
}
}

View File

@@ -13,10 +13,8 @@ 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;
@@ -63,8 +61,6 @@ 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;
@@ -108,28 +104,6 @@ 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
@@ -158,10 +132,6 @@ 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());
}
})); }));
} }
} }

View File

@@ -4,7 +4,6 @@
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;
@@ -65,14 +64,6 @@ 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.
* *

View File

@@ -4,14 +4,10 @@
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;
@@ -54,42 +50,6 @@ 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>

View File

@@ -1,82 +0,0 @@
// 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());
}
}

View File

@@ -23,7 +23,10 @@ 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.*; import java.util.ArrayList;
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;
@@ -101,7 +104,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 CompletableFuture<?> run(CachedOutput cache) { public final 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<>();
@@ -124,7 +127,7 @@ public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends Upgra
} }
}); });
this.upgrades = Collections.unmodifiableList(upgrades); this.upgrades = upgrades;
return Util.sequenceFailFast(futures); return Util.sequenceFailFast(futures);
} }
@@ -163,21 +166,5 @@ 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);
});
}
} }
} }

View File

@@ -4,8 +4,6 @@
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;
@@ -65,15 +63,6 @@ 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;

View File

@@ -7,7 +7,6 @@ import cc.tweaked.gradle.clientClasses
import cc.tweaked.gradle.commonClasses import cc.tweaked.gradle.commonClasses
plugins { plugins {
id("cc-tweaked.publishing")
id("cc-tweaked.vanilla") id("cc-tweaked.vanilla")
id("cc-tweaked.gametest") id("cc-tweaked.gametest")
} }
@@ -26,7 +25,6 @@ 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)

View File

@@ -13,7 +13,6 @@ 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;
@@ -108,6 +107,24 @@ 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",
@@ -116,7 +133,6 @@ 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) {

View File

@@ -4,7 +4,6 @@
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;
@@ -19,6 +18,7 @@ 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(PoseStack stack, int mouseX, int mouseY, float partialTicks) { public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
renderBackground(stack); renderBackground(graphics);
super.render(stack, mouseX, mouseY, partialTicks); super.render(graphics, mouseX, mouseY, partialTicks);
renderTooltip(stack, mouseX, mouseY); renderTooltip(graphics, mouseX, mouseY);
} }
@Override @Override
@@ -147,7 +147,7 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
@Override @Override
protected void renderLabels(PoseStack transform, int mouseX, int mouseY) { protected void renderLabels(GuiGraphics graphics, int mouseX, int mouseY) {
// Skip rendering labels. // Skip rendering labels.
} }

View File

@@ -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,13 +36,14 @@ public final class ComputerScreen<T extends AbstractComputerMenu> extends Abstra
} }
@Override @Override
public void renderBg(PoseStack stack, float partialTicks, int mouseX, int mouseY) { public void renderBg(GuiGraphics graphics, 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(
stack.last().pose(), ComputerBorderRenderer.getTexture(family), terminal.getX(), terminal.getY(), graphics.pose().last().pose(), texture, terminal.getX(), terminal.getY(),
FULL_BRIGHT_LIGHTMAP, terminal.getWidth(), terminal.getHeight() FULL_BRIGHT_LIGHTMAP, terminal.getWidth(), terminal.getHeight()
); );
ComputerSidebar.renderBackground(stack, leftPos, topPos + sidebarYOffset); ComputerSidebar.renderBackground(graphics, texture, leftPos, topPos + sidebarYOffset);
} }
} }

View File

@@ -4,9 +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.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;
@@ -23,16 +22,14 @@ public class DiskDriveScreen extends AbstractContainerScreen<DiskDriveMenu> {
} }
@Override @Override
protected void renderBg(PoseStack transform, float partialTicks, int mouseX, int mouseY) { protected void renderBg(GuiGraphics graphics, float partialTicks, int mouseX, int mouseY) {
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); graphics.blit(BACKGROUND, leftPos, topPos, 0, 0, imageWidth, imageHeight);
RenderSystem.setShaderTexture(0, BACKGROUND);
blit(transform, leftPos, topPos, 0, 0, imageWidth, imageHeight);
} }
@Override @Override
public void render(PoseStack transform, int mouseX, int mouseY, float partialTicks) { public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
renderBackground(transform); renderBackground(graphics);
super.render(transform, mouseX, mouseY, partialTicks); super.render(graphics, mouseX, mouseY, partialTicks);
renderTooltip(transform, mouseX, mouseY); renderTooltip(graphics, mouseX, mouseY);
} }
} }

View File

@@ -4,10 +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 net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiComponent; import net.minecraft.client.gui.GuiGraphics;
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;
@@ -73,55 +71,52 @@ public class ItemToast implements Toast {
} }
@Override @Override
public Visibility render(PoseStack transform, ToastComponent component, long time) { public Visibility render(GuiGraphics graphics, 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) {
GuiComponent.blit(transform, 0, 0, 0, 64, width, height()); graphics.blit(TEXTURE, 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(transform, component, width, 0, 0, 28); renderBackgroundRow(graphics, width, 0, 0, 28);
for (var i = 28; i < height - bottom; i += 10) { for (var i = 28; i < height - bottom; i += 10) {
renderBackgroundRow(transform, component, width, 16, i, Math.min(16, height - i - bottom)); renderBackgroundRow(graphics, width, 16, i, Math.min(16, height - i - bottom));
} }
renderBackgroundRow(transform, component, width, 32 - bottom, height - bottom, bottom); renderBackgroundRow(graphics, 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;
component.getMinecraft().getItemRenderer().renderAndDecorateFakeItem(transform, stack, MARGIN, MARGIN + height() / 2 - IMAGE_SIZE); graphics.renderFakeItem(stack, MARGIN, MARGIN + height() / 2 - IMAGE_SIZE);
} }
component.getMinecraft().font.draw(transform, title, textX, MARGIN, 0xff500050); graphics.drawString(component.getMinecraft().font, title, textX, MARGIN, 0xff500050);
for (var i = 0; i < message.size(); ++i) { for (var i = 0; i < message.size(); ++i) {
component.getMinecraft().font.draw(transform, message.get(i), textX, (float) (LINE_SPACING + (i + 1) * LINE_SPACING), 0xff000000); graphics.drawString(component.getMinecraft().font, message.get(i), textX, 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(PoseStack transform, ToastComponent component, int x, int u, int y, int height) { private static void renderBackgroundRow(GuiGraphics graphics, 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);
GuiComponent.blit(transform, 0, y, 0, 32 + u, leftOffset, height); graphics.blit(TEXTURE, 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) {
GuiComponent.blit(transform, k, y, 32, 32 + u, Math.min(64, x - k - rightOffset), height); graphics.blit(TEXTURE, k, y, 32, 32 + u, Math.min(64, x - k - rightOffset), height);
} }
GuiComponent.blit(transform, x - rightOffset, y, 160 - rightOffset, 32 + u, rightOffset, height); graphics.blit(TEXTURE, x - rightOffset, y, 160 - rightOffset, 32 + u, rightOffset, height);
} }
} }

View File

@@ -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() {
passEvents = true; // Pass mouse vents through to the game's mouse handler. // FIXME: 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(PoseStack transform, int mouseX, int mouseY, float partialTicks) { public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
super.render(transform, mouseX, mouseY, partialTicks); super.render(graphics, 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.0f; var y = 10;
for (var line : lines) { for (var line : lines) {
font.drawShadow(transform, line, (float) ((width / 2) - (minecraft.font.width(line) / 2)), y, 0xFFFFFF); graphics.drawString(font, line, (width / 2) - (minecraft.font.width(line) / 2), y, 0xFFFFFF, true);
y += 9.0f; y += 9;
} }
} }
} }

View File

@@ -4,9 +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 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;
@@ -86,20 +85,19 @@ public final class OptionScreen extends Screen {
} }
@Override @Override
public void render(PoseStack transform, int mouseX, int mouseY, float partialTicks) { public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
renderBackground(transform); renderBackground(graphics);
// Render the actual texture. // Render the actual texture.
RenderSystem.setShaderTexture(0, BACKGROUND); graphics.blit(BACKGROUND, x, y, 0, 0, innerWidth, PADDING);
blit(transform, x, y, 0, 0, innerWidth, PADDING); graphics.blit(BACKGROUND,
blit(transform,
x, y + PADDING, 0, PADDING, innerWidth, innerHeight - PADDING * 2, x, y + PADDING, 0, PADDING, innerWidth, innerHeight - PADDING * 2,
innerWidth, PADDING innerWidth, PADDING
); );
blit(transform, x, y + innerHeight - PADDING, 0, 256 - PADDING, innerWidth, PADDING); graphics.blit(BACKGROUND, x, y + innerHeight - PADDING, 0, 256 - PADDING, innerWidth, PADDING);
assertNonNull(messageRenderer).renderLeftAlignedNoShadow(transform, x + PADDING, y + PADDING, FONT_HEIGHT, 0x404040); assertNonNull(messageRenderer).renderLeftAlignedNoShadow(graphics, x + PADDING, y + PADDING, FONT_HEIGHT, 0x404040);
super.render(transform, mouseX, mouseY, partialTicks); super.render(graphics, mouseX, mouseY, partialTicks);
} }
@Override @Override

View File

@@ -4,9 +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.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;
@@ -23,18 +22,16 @@ public class PrinterScreen extends AbstractContainerScreen<PrinterMenu> {
} }
@Override @Override
protected void renderBg(PoseStack transform, float partialTicks, int mouseX, int mouseY) { protected void renderBg(GuiGraphics graphics, float partialTicks, int mouseX, int mouseY) {
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); graphics.blit(BACKGROUND, leftPos, topPos, 0, 0, imageWidth, imageHeight);
RenderSystem.setShaderTexture(0, BACKGROUND);
blit(transform, leftPos, topPos, 0, 0, imageWidth, imageHeight);
if (getMenu().isPrinting()) blit(transform, leftPos + 34, topPos + 21, 176, 0, 25, 45); if (getMenu().isPrinting()) graphics.blit(BACKGROUND, leftPos + 34, topPos + 21, 176, 0, 25, 45);
} }
@Override @Override
public void render(PoseStack stack, int mouseX, int mouseY, float partialTicks) { public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
renderBackground(stack); renderBackground(graphics);
super.render(stack, mouseX, mouseY, partialTicks); super.render(graphics, mouseX, mouseY, partialTicks);
renderTooltip(stack, mouseX, mouseY); renderTooltip(graphics, mouseX, mouseY);
} }
} }

View File

@@ -4,12 +4,11 @@
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;
@@ -83,30 +82,27 @@ public class PrintoutScreen extends AbstractContainerScreen<HeldItemMenu> {
} }
@Override @Override
protected void renderBg(PoseStack transform, float partialTicks, int mouseX, int mouseY) { protected void renderBg(GuiGraphics graphics, 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(transform, renderer, leftPos, topPos, 0, page, pages, book, FULL_BRIGHT_LIGHTMAP); drawBorder(graphics.pose(), renderer, leftPos, topPos, 0, page, pages, book, FULL_BRIGHT_LIGHTMAP);
drawText(transform, renderer, leftPos + X_TEXT_MARGIN, topPos + Y_TEXT_MARGIN, PrintoutItem.LINES_PER_PAGE * page, FULL_BRIGHT_LIGHTMAP, text, colours); drawText(graphics.pose(), 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(PoseStack stack, int mouseX, int mouseY, float partialTicks) { public void render(GuiGraphics graphics, 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.
stack.pushPose(); graphics.pose().pushPose();
stack.translate(0, 0, -1); graphics.pose().translate(0, 0, -1);
renderBackground(stack); renderBackground(graphics);
stack.popPose(); graphics.pose().popPose();
super.render(stack, mouseX, mouseY, partialTicks); super.render(graphics, mouseX, mouseY, partialTicks);
} }
@Override @Override
protected void renderLabels(PoseStack transform, int mouseX, int mouseY) { protected void renderLabels(GuiGraphics graphics, int mouseX, int mouseY) {
// Skip rendering labels. // Skip rendering labels.
} }
} }

View File

@@ -4,8 +4,6 @@
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;
@@ -13,6 +11,7 @@ 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;
@@ -26,11 +25,9 @@ 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 = 278; private static final int TEX_WIDTH = 254;
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);
@@ -44,23 +41,21 @@ public class TurtleScreen extends AbstractComputerScreen<TurtleMenu> {
} }
@Override @Override
protected void renderBg(PoseStack transform, float partialTicks, int mouseX, int mouseY) { protected void renderBg(GuiGraphics graphics, float partialTicks, int mouseX, int mouseY) {
var advanced = family == ComputerFamily.ADVANCED; var advanced = family == ComputerFamily.ADVANCED;
RenderSystem.setShaderTexture(0, advanced ? BACKGROUND_ADVANCED : BACKGROUND_NORMAL); var texture = advanced ? BACKGROUND_ADVANCED : BACKGROUND_NORMAL;
blit(transform, leftPos + AbstractComputerMenu.SIDEBAR_WIDTH, topPos, 0, 0, 0, TEX_WIDTH, TEX_HEIGHT, FULL_TEX_SIZE, FULL_TEX_SIZE); graphics.blit(texture, leftPos + AbstractComputerMenu.SIDEBAR_WIDTH, topPos, 0, 0, TEX_WIDTH, TEX_HEIGHT);
// 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;
blit(transform, graphics.blit(texture,
leftPos + TURTLE_START_X - 2 + slotX * 18, topPos + PLAYER_START_Y - 2 + slotY * 18, 0, leftPos + TURTLE_START_X - 2 + slotX * 18, topPos + PLAYER_START_Y - 2 + slotY * 18,
0, 217, 24, 24, FULL_TEX_SIZE, FULL_TEX_SIZE 0, 217, 24, 24
); );
} }
RenderSystem.setShaderTexture(0, advanced ? ComputerBorderRenderer.BACKGROUND_ADVANCED : ComputerBorderRenderer.BACKGROUND_NORMAL); ComputerSidebar.renderBackground(graphics, ComputerBorderRenderer.getTexture(family), leftPos, topPos + sidebarYOffset);
ComputerSidebar.renderBackground(transform, leftPos, topPos + sidebarYOffset);
} }
} }

View File

@@ -4,14 +4,13 @@
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;
@@ -68,19 +67,19 @@ public final class ComputerSidebar {
)); ));
} }
public static void renderBackground(PoseStack transform, int x, int y) { public static void renderBackground(GuiGraphics graphics, ResourceLocation texture, int x, int y) {
Screen.blit(transform, graphics.blit(texture,
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
); );
Screen.blit(transform, graphics.blit(texture,
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
); );
Screen.blit(transform, graphics.blit(texture,
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
); );

View File

@@ -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,23 +57,25 @@ public class DynamicImageButton extends Button {
} }
@Override @Override
public void renderWidget(PoseStack stack, int mouseX, int mouseY, float partialTicks) { public void renderWidget(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
RenderSystem.setShaderTexture(0, texture); RenderSystem.enableBlend();
RenderSystem.disableDepthTest(); RenderSystem.enableDepthTest();
var yTex = yTexStart; var yTex = yTexStart;
if (isHoveredOrFocused()) yTex += yDiffTex; if (isHoveredOrFocused()) yTex += yDiffTex;
blit(stack, getX(), getY(), xTexStart.getAsInt(), yTex, width, height, textureWidth, textureHeight); graphics.blit(texture, getX(), getY(), xTexStart.getAsInt(), yTex, width, height, textureWidth, textureHeight);
RenderSystem.enableDepthTest();
} }
@Override @Override
public void render(PoseStack stack, int mouseX, int mouseY, float partialTicks) { public Component getMessage() {
var message = this.message.get(); return message.get().message;
setMessage(message.message()); }
setTooltip(message.tooltip());
super.render(stack, mouseX, mouseY, partialTicks); @Override
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) {

View File

@@ -4,7 +4,6 @@
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;
@@ -12,6 +11,7 @@ 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(PoseStack transform, int mouseX, int mouseY, float partialTicks) { public void renderWidget(GuiGraphics graphics, 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(transform, bufferSource.getBuffer(RenderTypes.TERMINAL)); var emitter = FixedWidthFontRenderer.toVertexConsumer(graphics.pose(), bufferSource.getBuffer(RenderTypes.TERMINAL));
FixedWidthFontRenderer.drawTerminal( FixedWidthFontRenderer.drawTerminal(
emitter, emitter,

View File

@@ -1,44 +0,0 @@
// 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);
});
}
}

View File

@@ -1,103 +0,0 @@
// 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);
}
}

View File

@@ -4,13 +4,11 @@
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;
@@ -23,17 +21,15 @@ 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<T> { public final class TurtleModelParts {
private static final Transformation identity, flip; private static final Transformation identity, flip;
static { static {
@@ -46,67 +42,33 @@ public final class TurtleModelParts<T> {
flip = new Transformation(stack.last().pose()); flip = new Transformation(stack.last().pose());
} }
private record Combination( public record Combination(
boolean colour, boolean colour,
@Nullable UpgradeData<ITurtleUpgrade> leftUpgrade, @Nullable ITurtleUpgrade leftUpgrade,
@Nullable UpgradeData<ITurtleUpgrade> rightUpgrade, @Nullable 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 = CacheBuilder.newBuilder() private final Map<TransformedModel, BakedModel> transformCache = new HashMap<>();
.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 T getModel(ItemStack stack) { public Combination getCombination(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)) {
@@ -114,8 +76,8 @@ public final class TurtleModelParts<T> {
} }
var colour = turtle.getColour(stack); var colour = turtle.getColour(stack);
var leftUpgrade = turtle.getUpgradeWithData(stack, TurtleSide.LEFT); var leftUpgrade = turtle.getUpgrade(stack, TurtleSide.LEFT);
var rightUpgrade = turtle.getUpgradeWithData(stack, TurtleSide.RIGHT); var rightUpgrade = turtle.getUpgrade(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"));
@@ -123,7 +85,7 @@ public final class TurtleModelParts<T> {
return new Combination(colour != -1, leftUpgrade, rightUpgrade, overlay, christmas, flip); return new Combination(colour != -1, leftUpgrade, rightUpgrade, overlay, christmas, flip);
} }
private List<BakedModel> buildModel(Combination combo) { public 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();
@@ -135,20 +97,19 @@ public final class TurtleModelParts<T> {
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) {
addUpgrade(parts, transformation, TurtleSide.LEFT, combo.leftUpgrade()); var model = TurtleUpgradeModellers.getModel(combo.leftUpgrade(), null, TurtleSide.LEFT);
addUpgrade(parts, transformation, TurtleSide.RIGHT, combo.rightUpgrade()); parts.add(transform(model.getModel(), transformation.compose(model.getMatrix())));
}
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;
} }
private void addUpgrade(List<BakedModel> parts, Transformation transformation, TurtleSide side, @Nullable UpgradeData<ITurtleUpgrade> upgrade) { public BakedModel transform(BakedModel model, Transformation transformation) {
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);
} }

View File

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

View File

@@ -4,13 +4,8 @@
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() {
@@ -23,16 +18,4 @@ 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);
} }

View File

@@ -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 {
public static final ResourceLocation BACKGROUND_NORMAL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "textures/gui/corners_normal.png"); private static final ResourceLocation BACKGROUND_NORMAL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "textures/gui/corners_normal.png");
public static final ResourceLocation BACKGROUND_ADVANCED = new ResourceLocation(ComputerCraftAPI.MOD_ID, "textures/gui/corners_advanced.png"); private static final ResourceLocation BACKGROUND_ADVANCED = new ResourceLocation(ComputerCraftAPI.MOD_ID, "textures/gui/corners_advanced.png");
public static final ResourceLocation BACKGROUND_COMMAND = new ResourceLocation(ComputerCraftAPI.MOD_ID, "textures/gui/corners_command.png"); private 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");
/** /**

View File

@@ -1,103 +0,0 @@
// 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
);
}
}
}

View File

@@ -5,6 +5,7 @@
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;
@@ -13,20 +14,25 @@ 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");
@@ -34,6 +40,8 @@ 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;
@@ -99,22 +107,23 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
var family = turtle.getFamily(); var family = turtle.getFamily();
var overlay = turtle.getOverlay(); var overlay = turtle.getOverlay();
renderModel(transform, buffers, lightmapCoord, overlayLight, getTurtleModel(family, colour != -1), colour == -1 ? null : new int[]{ colour }); var buffer = buffers.getBuffer(Sheets.translucentCullBlockSheet());
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, buffers, lightmapCoord, overlayLight, overlayModel, null); renderModel(transform, buffer, lightmapCoord, overlayLight, overlayModel, null);
} }
// Render the upgrades // Render the upgrades
renderUpgrade(transform, buffers, lightmapCoord, overlayLight, turtle, TurtleSide.LEFT, partialTicks); renderUpgrade(transform, buffer, lightmapCoord, overlayLight, turtle, TurtleSide.LEFT, partialTicks);
renderUpgrade(transform, buffers, lightmapCoord, overlayLight, turtle, TurtleSide.RIGHT, partialTicks); renderUpgrade(transform, buffer, lightmapCoord, overlayLight, turtle, TurtleSide.RIGHT, partialTicks);
transform.popPose(); transform.popPose();
} }
private void renderUpgrade(PoseStack transform, MultiBufferSource buffers, int lightmapCoord, int overlayLight, TurtleBlockEntity turtle, TurtleSide side, float f) { private void renderUpgrade(PoseStack transform, VertexConsumer renderer, 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();
@@ -125,33 +134,46 @@ 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);
applyTransformation(transform, model.getMatrix()); pushPoseFromTransformation(transform, model.getMatrix());
renderModel(transform, buffers, lightmapCoord, overlayLight, model.getModel(), null); renderModel(transform, renderer, lightmapCoord, overlayLight, model.getModel(), null);
transform.popPose();
transform.popPose(); transform.popPose();
} }
private void renderModel(PoseStack transform, MultiBufferSource buffers, int lightmapCoord, int overlayLight, ResourceLocation modelLocation, @Nullable int[] tints) { private void renderModel(PoseStack transform, VertexConsumer renderer, 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, buffers, lightmapCoord, overlayLight, ClientPlatformHelper.get().getModel(modelManager, modelLocation), tints); renderModel(transform, renderer, lightmapCoord, overlayLight, ClientPlatformHelper.get().getModel(modelManager, modelLocation), tints);
} }
/** private void renderModel(PoseStack transform, VertexConsumer renderer, int lightmapCoord, int overlayLight, BakedModel model, @Nullable int[] tints) {
* Render a block model. random.setSeed(0);
* renderQuads(transform, renderer, lightmapCoord, overlayLight, model.getQuads(null, null, random), tints);
* @param transform The current matrix stack. for (var facing : DirectionUtil.FACINGS) {
* @param renderer The buffer to write to. renderQuads(transform, renderer, lightmapCoord, overlayLight, model.getQuads(null, facing, random), tints);
* @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 applyTransformation(PoseStack stack, Transformation transformation) { private static void renderQuads(PoseStack transform, VertexConsumer buffer, int lightmapCoord, int overlayLight, List<BakedQuad> quads, @Nullable int[] tints) {
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());

View File

@@ -7,10 +7,7 @@ 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.PoseStack; import com.mojang.blaze3d.vertex.*;
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;
@@ -170,7 +167,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, 0, 0, 0); RenderTypes.MONITOR_TBO.end(buffer, VertexSorting.DISTANCE_TO_ORIGIN);
} }
case VBO -> { case VBO -> {
var backgroundBuffer = assertNonNull(renderState.backgroundBuffer); var backgroundBuffer = assertNonNull(renderState.backgroundBuffer);

View File

@@ -25,6 +25,7 @@ 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.

View File

@@ -13,9 +13,6 @@ 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.
*/ */
@@ -51,9 +48,4 @@ 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);
}
} }

View File

@@ -14,13 +14,11 @@ 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.
@@ -54,18 +52,12 @@ public final class TurtleUpgradeModellers {
} }
} }
public static TransformedModel getModel(ITurtleUpgrade upgrade, ITurtleAccess access, TurtleSide side) { public static TransformedModel getModel(ITurtleUpgrade upgrade, @Nullable 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;
@@ -73,8 +65,4 @@ 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());
}
} }

View File

@@ -1,34 +0,0 @@
// 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())
));
});
}
}

View File

@@ -37,14 +37,6 @@ 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(),
@@ -150,18 +142,11 @@ 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 -> switch (state) { .with(createModelDispatch(ComputerBlock.STATE, state -> ModelTemplates.CUBE_ORIENTABLE.createWithSuffix(
case OFF -> ModelTemplates.CUBE_ORIENTABLE.createWithSuffix( block, "_" + state.getSerializedName(),
block, "_" + state.getSerializedName(), TextureMapping.orientableCube(block).put(TextureSlot.FRONT, getBlockTexture(block, "_front" + state.getTexture())),
TextureMapping.orientableCube(block), generators.modelOutput
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"));
} }

View File

@@ -4,20 +4,16 @@
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;
/** /**
@@ -41,22 +37,11 @@ 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);
}
} }
public interface GeneratorSink { 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);

View File

@@ -6,7 +6,6 @@ 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;
@@ -21,7 +20,6 @@ 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;
@@ -99,12 +97,6 @@ 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");
@@ -158,6 +150,9 @@ 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.");
@@ -219,7 +214,6 @@ 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");
@@ -283,8 +277,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"),
ConfigSpec.serverSpec.entries().map(ConfigFile.Entry::translationKey), getConfigEntries(ConfigSpec.serverSpec).map(ConfigFile.Entry::translationKey),
ConfigSpec.clientSpec.entries().map(ConfigFile.Entry::translationKey) getConfigEntries(ConfigSpec.clientSpec).map(ConfigFile.Entry::translationKey)
).flatMap(x -> x); ).flatMap(x -> x);
} }
@@ -304,10 +298,6 @@ 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);
@@ -318,4 +308,16 @@ 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);
}
} }

View File

@@ -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.AlternativeLootItemCondition; import net.minecraft.world.level.storage.loot.predicates.AnyOfCondition;
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.LOOT_TREASURE_DISK, LootTable.lootTable()); add.accept(CommonHooks.TREASURE_DISK_LOOT, 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")),
AlternativeLootItemCondition.alternative( AnyOfCondition.anyOf(
BlockNamedEntityLootCondition.BUILDER, BlockNamedEntityLootCondition.BUILDER,
HasComputerIdLootCondition.BUILDER, HasComputerIdLootCondition.BUILDER,
PlayerCreativeLootCondition.BUILDER.invert() PlayerCreativeLootCondition.BUILDER.invert()

View File

@@ -8,7 +8,6 @@ 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;
@@ -111,7 +110,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, UpgradeData.ofDefault(upgrade), -1, null); var result = turtleItem.create(-1, null, -1, null, 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))
@@ -147,7 +146,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, UpgradeData.ofDefault(upgrade)); var result = pocket.create(-1, null, -1, 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))

View File

@@ -19,6 +19,8 @@ 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;
@@ -77,7 +79,7 @@ public abstract class AbstractComputerCraftAPI implements ComputerCraftAPIServic
@Override @Override
public final void registerGenericSource(GenericSource source) { public final void registerGenericSource(GenericSource source) {
GenericSources.register(source); GenericMethod.register(source);
} }
@Override @Override

View File

@@ -1,41 +0,0 @@
// 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();
}
}

View File

@@ -7,7 +7,6 @@ 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;
@@ -75,13 +74,13 @@ public class UpgradeManager<R extends UpgradeSerialiser<? extends T>, T extends
} }
@Nullable @Nullable
public UpgradeData<T> get(ItemStack stack) { public 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 UpgradeData.of(wrapper.upgrade, wrapper.upgrade.getUpgradeData(stack)); return wrapper.upgrade();
} }
} }

View File

@@ -23,13 +23,7 @@ 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;
@@ -75,9 +69,9 @@ public final class CommonHooks {
MonitorWatcher.onWatch(chunk, player); MonitorWatcher.onWatch(chunk, player);
} }
public static final ResourceLocation LOOT_TREASURE_DISK = new ResourceLocation(ComputerCraftAPI.MOD_ID, "treasure_disk"); public static final ResourceLocation TREASURE_DISK_LOOT = new ResourceLocation(ComputerCraftAPI.MOD_ID, "treasure_disk");
private static final Set<ResourceLocation> TABLES = new HashSet<>(Arrays.asList( public static final Set<ResourceLocation> TREASURE_DISK_LOOT_TABLES = Set.of(
BuiltInLootTables.SIMPLE_DUNGEON, BuiltInLootTables.SIMPLE_DUNGEON,
BuiltInLootTables.ABANDONED_MINESHAFT, BuiltInLootTables.ABANDONED_MINESHAFT,
BuiltInLootTables.STRONGHOLD_CORRIDOR, BuiltInLootTables.STRONGHOLD_CORRIDOR,
@@ -88,16 +82,7 @@ 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);

View File

@@ -11,7 +11,6 @@ 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;
@@ -94,7 +93,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.Material; import net.minecraft.world.level.material.MapColor;
import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType; import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType;
import java.util.function.BiFunction; import java.util.function.BiFunction;
@@ -113,7 +112,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(Material.STONE).strength(2); return BlockBehaviour.Properties.of().strength(2);
} }
private static BlockBehaviour.Properties computerProperties() { private static BlockBehaviour.Properties computerProperties() {
@@ -123,17 +122,17 @@ public final class ModRegistry {
} }
private static BlockBehaviour.Properties turtleProperties() { private static BlockBehaviour.Properties turtleProperties() {
return BlockBehaviour.Properties.of(Material.STONE).strength(2.5f); return BlockBehaviour.Properties.of().strength(2.5f);
} }
private static BlockBehaviour.Properties modemProperties() { private static BlockBehaviour.Properties modemProperties() {
return BlockBehaviour.Properties.of(Material.STONE).strength(1.5f); return BlockBehaviour.Properties.of().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(), ComputerFamily.NORMAL, BlockEntities.COMPUTER_NORMAL)); () -> new ComputerBlock<>(computerProperties().mapColor(MapColor.STONE), 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(), ComputerFamily.ADVANCED, BlockEntities.COMPUTER_ADVANCED)); () -> new ComputerBlock<>(computerProperties().mapColor(MapColor.GOLD), 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),
@@ -141,27 +140,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(), ComputerFamily.NORMAL, BlockEntities.TURTLE_NORMAL)); () -> new TurtleBlock(turtleProperties().mapColor(MapColor.STONE), 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(), ComputerFamily.ADVANCED, BlockEntities.TURTLE_ADVANCED)); () -> new TurtleBlock(turtleProperties().mapColor(MapColor.GOLD), ComputerFamily.ADVANCED, BlockEntities.TURTLE_ADVANCED));
public static final RegistryEntry<SpeakerBlock> SPEAKER = REGISTRY.register("speaker", () -> new SpeakerBlock(properties())); public static final RegistryEntry<SpeakerBlock> SPEAKER = REGISTRY.register("speaker", () -> new SpeakerBlock(properties().mapColor(MapColor.STONE)));
public static final RegistryEntry<DiskDriveBlock> DISK_DRIVE = REGISTRY.register("disk_drive", () -> new DiskDriveBlock(properties())); public static final RegistryEntry<DiskDriveBlock> DISK_DRIVE = REGISTRY.register("disk_drive", () -> new DiskDriveBlock(properties().mapColor(MapColor.STONE)));
public static final RegistryEntry<PrinterBlock> PRINTER = REGISTRY.register("printer", () -> new PrinterBlock(properties())); public static final RegistryEntry<PrinterBlock> PRINTER = REGISTRY.register("printer", () -> new PrinterBlock(properties().mapColor(MapColor.STONE)));
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(), BlockEntities.MONITOR_NORMAL)); () -> new MonitorBlock(properties().mapColor(MapColor.STONE), 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(), BlockEntities.MONITOR_ADVANCED)); () -> new MonitorBlock(properties().mapColor(MapColor.GOLD), 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(), BlockEntities.WIRELESS_MODEM_NORMAL)); () -> new WirelessModemBlock(properties().mapColor(MapColor.STONE), 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(), BlockEntities.WIRELESS_MODEM_ADVANCED)); () -> new WirelessModemBlock(properties().mapColor(MapColor.GOLD), 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())); () -> new WiredModemFullBlock(modemProperties().mapColor(MapColor.STONE)));
public static final RegistryEntry<CableBlock> CABLE = REGISTRY.register("cable", () -> new CableBlock(modemProperties())); public static final RegistryEntry<CableBlock> CABLE = REGISTRY.register("cable", () -> new CableBlock(modemProperties().mapColor(MapColor.STONE)));
} }
public static class BlockEntities { public static class BlockEntities {
@@ -366,50 +365,11 @@ 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 {
* Register any objects which don't have to be done on the main thread. static final RegistrationHelper<CreativeModeTab> REGISTRY = PlatformHelper.get().createRegistrationHelper(Registries.CREATIVE_MODE_TAB);
*/
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();
// Register bundled power providers @SuppressWarnings("unused")
ComputerCraftAPI.registerBundledRedstoneProvider(new DefaultBundledRedstoneProvider()); private static final RegistryEntry<CreativeModeTab> TAB = REGISTRY.register("tab", () -> PlatformHelper.get().newCreativeModeTab()
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) -> {
@@ -441,18 +401,56 @@ 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, UpgradeData.ofDefault(x), 0, null)) .map(x -> turtle.create(-1, null, -1, null, 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, UpgradeData.ofDefault(x))).forEach(out::accept); PocketUpgrades.getVanillaUpgrades().map(x -> pocket.create(-1, null, -1, x)).forEach(out::accept);
} }
} }

View File

@@ -6,12 +6,9 @@ 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;
@@ -127,7 +124,9 @@ 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;
})) }))
@@ -141,7 +140,9 @@ 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;
})) }))
@@ -172,10 +173,7 @@ public final class CommandComputerCraft {
.then(command("queue") .then(command("queue")
.requires(UserLevel.ANYONE) .requires(UserLevel.ANYONE)
.arg( .arg("computer", manyComputers())
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");
@@ -219,7 +217,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);

View File

@@ -49,29 +49,6 @@ 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();

View File

@@ -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) {
buffer.writeId(RegistryWrappers.COMMAND_ARGUMENT_TYPES, type); RegistryWrappers.writeId(buffer, 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 = buffer.readById(RegistryWrappers.COMMAND_ARGUMENT_TYPES); var type = RegistryWrappers.readId(buffer, RegistryWrappers.COMMAND_ARGUMENT_TYPES);
Objects.requireNonNull(type, "Unknown argument type"); Objects.requireNonNull(type, "Unknown argument type");
return type.deserializeFromNetwork(buffer); return type.deserializeFromNetwork(buffer);
} }

View File

@@ -18,6 +18,7 @@ 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;
@@ -158,14 +159,14 @@ public final class ComputersArgumentType implements ArgumentType<ComputersArgume
} }
@Override @Override
public ComputersArgumentType.Template unpack(ComputersArgumentType argumentType) { public ComputersArgumentType.Template unpack(@NotNull 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(CommandBuildContext context) { public ComputersArgumentType instantiate(@NotNull CommandBuildContext context) {
return requireSome ? SOME : MANY; return requireSome ? SOME : MANY;
} }

View File

@@ -17,6 +17,7 @@ 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;
@@ -143,7 +144,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(CommandBuildContext commandBuildContext) { public RepeatArgumentType<?, ?> instantiate(@NotNull 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());
} }

View File

@@ -48,13 +48,9 @@ public class CommandBuilder<S> implements CommandNodeBuilder<S, Command<S>> {
return this; return this;
} }
public CommandBuilder<S> arg(ArgumentBuilder<S, ?> arg) {
args.add(arg);
return this;
}
public CommandBuilder<S> arg(String name, ArgumentType<?> type) { public CommandBuilder<S> arg(String name, ArgumentType<?> type) {
return arg(RequiredArgumentBuilder.argument(name, type)); args.add(RequiredArgumentBuilder.argument(name, type));
return this;
} }
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) {
@@ -78,7 +74,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 = setupTail(ctx -> command.run(ctx, empty.get())); var tail = tail(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
@@ -87,7 +83,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 buildTail(tail); return link(tail);
}; };
} }
@@ -98,16 +94,20 @@ 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) {
return buildTail(setupTail(command)); if (args.isEmpty()) throw new IllegalStateException("Cannot have empty arg chain builder");
return link(tail(command));
} }
private ArgumentBuilder<S, ?> setupTail(Command<S> command) { private ArgumentBuilder<S, ?> tail(Command<S> command) {
return args.get(args.size() - 1).executes(command); var defaultTail = args.get(args.size() - 1);
defaultTail.executes(command);
if (requires != null) defaultTail.requires(requires);
return defaultTail;
} }
private CommandNode<S> buildTail(ArgumentBuilder<S, ?> tail) { private CommandNode<S> link(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();
} }
} }

View File

@@ -10,7 +10,6 @@ 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;
@@ -19,8 +18,6 @@ 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;
@@ -40,29 +37,6 @@ 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");
@@ -106,7 +80,9 @@ 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").executes(helpCommand); var helpNode = LiteralArgumentBuilder.<CommandSourceStack>literal("help")
.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()) {
@@ -153,14 +129,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;
}; };
} }

View File

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

View File

@@ -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.LootContext; import net.minecraft.world.level.storage.loot.LootParams;
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,13 +138,12 @@ 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 LootContext.Builder(serverWorld) var context = new LootParams.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, (ctx, out) -> out.accept(getItem(computer))); .withDynamicDrop(DROP, 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);
} }

View File

@@ -199,11 +199,6 @@ 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);
@@ -259,7 +254,8 @@ 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.
updateRedstoneInputs(computer); var pos = getBlockPos();
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.
} }
@@ -268,10 +264,9 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
*/ */
public void updateOutput() { public void updateOutput() {
BlockEntityHelpers.updateBlock(this); BlockEntityHelpers.updateBlock(this);
for (var dir : DirectionUtil.FACINGS) RedstoneUtil.propagateRedstoneOutput(getLevel(), getBlockPos(), dir); for (var dir : DirectionUtil.FACINGS) {
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);

View File

@@ -9,16 +9,13 @@ 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;
@@ -70,13 +67,11 @@ 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 = ComputerContext.builder(new Environment(server)) context = new ComputerContext(
.computerThreads(ConfigSpec.computerThreads.get()) new Environment(server),
.mainThreadScheduler(mainThread) new ComputerThread(ConfigSpec.computerThreads.get()),
.luaFactory(luaMachine) mainThread, luaMachine
.apiFactories(ApiFactories.getAll()) );
.genericMethods(GenericSources.getAllMethods())
.build();
idAssigner = new IDAssigner(storageDir.resolve("ids.json")); idAssigner = new IDAssigner(storageDir.resolve("ids.json"));
} }
@@ -138,16 +133,6 @@ 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}.
*/ */

View File

@@ -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.methods.ObjectSource; import dan200.computercraft.core.asm.ObjectSource;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Collections; import java.util.Collections;

View File

@@ -4,19 +4,21 @@
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 java.util.*; import javax.annotation.Nullable;
import java.util.function.Consumer; import java.util.Locale;
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}.
@@ -24,65 +26,49 @@ import java.util.function.Consumer;
class AddressRuleConfig { class AddressRuleConfig {
private static final Logger LOG = LoggerFactory.getLogger(AddressRuleConfig.class); private static final Logger LOG = LoggerFactory.getLogger(AddressRuleConfig.class);
private static final AddressRule REJECT_ALL = AddressRule.parse("*", OptionalInt.empty(), Action.DENY.toPartial()); public static UnmodifiableConfig makeRule(String host, Action action) {
var config = InMemoryCommentedFormat.defaultInstance().createConfig(ConcurrentHashMap::new);
config.add("host", host);
config.add("action", action.name().toLowerCase(Locale.ROOT));
public static List<UnmodifiableConfig> defaultRules() { if (host.equals("*") && action == Action.ALLOW) {
return List.of( config.setComment("max_download", """
makeRule(config -> { The maximum size (in bytes) that a computer can download in a single request.
config.setComment("host", """ Note that responses may receive more data than allowed, but this data will not
The magic "$private" host matches all private address ranges, such as localhost and 192.168.0.0/16. be returned to the client.""");
This rule prevents computers accessing internal services, and is strongly recommended."""); config.set("max_download", AddressRule.MAX_DOWNLOAD);
config.add("host", "$private");
config.setComment("action", "Deny all requests to private IP addresses."); config.setComment("max_upload", """
config.add("action", Action.DENY.name().toLowerCase(Locale.ROOT)); The maximum size (in bytes) that a computer can upload in a single request. This
}), includes headers and POST text.""");
makeRule(config -> { config.set("max_upload", AddressRule.MAX_UPLOAD);
config.setComment("host", """
The wildcard "*" rule matches all remaining hosts.""");
config.add("host", "*");
config.setComment("action", "Allow all non-denied hosts."); config.setComment("max_websocket_message", "The maximum size (in bytes) that a computer can send or receive in one websocket packet.");
config.add("action", Action.ALLOW.name().toLowerCase(Locale.ROOT)); config.set("max_websocket_message", AddressRule.WEBSOCKET_MESSAGE);
config.setComment("max_download", """ config.setComment("use_proxy", "Enable use of the HTTP/SOCKS proxy if it is configured.");
The maximum size (in bytes) that a computer can download in a single request. config.set("use_proxy", false);
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 AddressRule parseRule(UnmodifiableConfig builder) { public static boolean checkRule(UnmodifiableConfig builder) {
try { var hostObj = get(builder, "host", String.class).orElse(null);
return doParseRule(builder); var port = unboxOptInt(get(builder, "port", Number.class));
} catch (InvalidRuleException e) { return hostObj != null && checkEnum(builder, "action", Action.class)
LOG.error("Malformed HTTP rule: {} HTTP will NOT work until this is fixed.", e.getMessage()); && check(builder, "port", Number.class)
return REJECT_ALL; && check(builder, "max_upload", Number.class)
} && 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;
} }
public static AddressRule doParseRule(UnmodifiableConfig builder) { @Nullable
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) throw new InvalidRuleException("No 'host' specified"); if (hostObj == null) return null;
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));
@@ -102,19 +88,38 @@ 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);
if (value == null) return Optional.empty(); return klass.isInstance(value) ? Optional.of(klass.cast(value)) : 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(field, klass, x)); return get(config, field, String.class).map(x -> parseEnum(klass, x));
} }
private static OptionalLong unboxOptLong(Optional<? extends Number> value) { private static OptionalLong unboxOptLong(Optional<? extends Number> value) {
@@ -125,14 +130,11 @@ class AddressRuleConfig {
return value.map(Number::intValue).map(OptionalInt::of).orElse(OptionalInt.empty()); return value.map(Number::intValue).map(OptionalInt::of).orElse(OptionalInt.empty());
} }
private static <T extends Enum<T>> T parseEnum(String field, Class<T> klass, String x) { @Nullable
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
));
} }
} }

View File

@@ -54,6 +54,12 @@ 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();
} }
/** /**

View File

@@ -9,6 +9,7 @@ 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;
@@ -19,7 +20,9 @@ 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 {
@@ -35,7 +38,6 @@ 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;
@@ -140,19 +142,6 @@ 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);
} }
{ {
@@ -193,9 +182,9 @@ public final class ConfigSpec {
httpEnabled = builder httpEnabled = builder
.comment(""" .comment("""
Enable the "http" API on Computers. Disabling this also disables the "pastebin" and Enable the "http" API on Computers. This also disables the "pastebin" and "wget"
"wget" programs, that many users rely on. It's recommended to leave this on and use programs, that many users rely on. It's recommended to leave this on and use the
the "rules" config option to impose more fine-grained control.""") "rules" config option to impose more fine-grained control.""")
.define("enabled", CoreConfig.httpEnabled); .define("enabled", CoreConfig.httpEnabled);
httpWebsocketEnabled = builder httpWebsocketEnabled = builder
@@ -205,23 +194,16 @@ 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 matches against a hostname and an optional port, and then sets several IPs. Each rule is an item with a 'host' to match against, and a series of
properties for the request. Rules are evaluated in order, meaning earlier rules override properties. Rules are evaluated in order, meaning earlier rules override later
later ones. ones.
The host may be a domain name ("pastebin.com"), wildcard ("*.pastebin.com") or
Valid properties: CIDR notation ("127.0.0.0/8").
- "host" (required): The domain or IP address this rule matches. This may be a domain name If no rules, the domain is blocked.""")
("pastebin.com"), wildcard ("*.pastebin.com") or CIDR notation ("127.0.0.0/8"). .defineList("rules", Arrays.asList(
- "port" (optional): Only match requests for a specific port, such as 80 or 443. AddressRuleConfig.makeRule("$private", Action.DENY),
AddressRuleConfig.makeRule("*", Action.ALLOW)
- "action" (optional): Whether to allow or deny this request. ), x -> x instanceof UnmodifiableConfig && AddressRuleConfig.checkRule((UnmodifiableConfig) x));
- "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("""
@@ -413,8 +395,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()
CoreConfig.httpRules = httpRules.get().stream().map(AddressRuleConfig::parseRule).toList(); .map(AddressRuleConfig::parseRule).filter(Objects::nonNull).toList();
CoreConfig.httpMaxRequests = httpMaxRequests.get(); CoreConfig.httpMaxRequests = httpMaxRequests.get();
CoreConfig.httpMaxWebsockets = httpMaxWebsockets.get(); CoreConfig.httpMaxWebsockets = httpMaxWebsockets.get();

View File

@@ -7,6 +7,7 @@ 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;
/** /**
@@ -15,6 +16,24 @@ 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();

View File

@@ -5,7 +5,6 @@
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;
@@ -57,14 +56,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, UpgradeData.ofDefault(upgrade), 0, null)); upgradeItems.add(turtle.create(-1, null, -1, null, 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, UpgradeData.ofDefault(upgrade))); upgradeItems.add(pocket.create(-1, null, -1, upgrade));
} }
} }

View File

@@ -9,7 +9,6 @@ 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;
@@ -112,22 +111,20 @@ 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.getUpgradeWithData(stack, TurtleSide.LEFT); var left = item.getUpgrade(stack, TurtleSide.LEFT);
var right = item.getUpgradeWithData(stack, TurtleSide.RIGHT); var right = item.getUpgrade(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, UpgradeData.ofDefault(upgrade.turtle), right))); recipes.add(turtle(ingredient, upgrade.ingredient, turtleWith(stack, upgrade.turtle, right)));
} }
if (right == null) { if (right == null) {
recipes.add(turtle(upgrade.ingredient, ingredient, turtleWith(stack, left, UpgradeData.ofDefault(upgrade.turtle)))); recipes.add(turtle(upgrade.ingredient, ingredient, turtleWith(stack, left, upgrade.turtle)));
} }
} }
@@ -140,8 +137,7 @@ 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) {
if (upgrade.pocket == null) throw new NullPointerException(); recipes.add(pocket(upgrade.ingredient, ingredient, pocketWith(stack, upgrade.pocket)));
recipes.add(pocket(upgrade.ingredient, ingredient, pocketWith(stack, UpgradeData.ofDefault(upgrade.pocket))));
} }
return Collections.unmodifiableList(recipes); return Collections.unmodifiableList(recipes);
@@ -184,21 +180,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.getUpgradeWithData(stack, TurtleSide.LEFT); var left = item.getUpgrade(stack, TurtleSide.LEFT);
var right = item.getUpgradeWithData(stack, TurtleSide.RIGHT); var right = item.getUpgrade(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.getUpgradeItem()), Ingredient.of(left.getCraftingItem()),
stack stack
)); ));
} }
if (right != null) { if (right != null) {
recipes.add(turtle( recipes.add(turtle(
Ingredient.of(right.getUpgradeItem()), Ingredient.of(right.getCraftingItem()),
Ingredient.of(turtleWith(stack, left, null)), Ingredient.of(turtleWith(stack, left, null)),
stack stack
)); ));
@@ -208,9 +204,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.getUpgradeWithData(stack); var back = PocketComputerItem.getUpgrade(stack);
if (back != null) { if (back != null) {
recipes.add(pocket(Ingredient.of(back.getUpgradeItem()), Ingredient.of(pocketWith(stack, null)), stack)); recipes.add(pocket(Ingredient.of(back.getCraftingItem()), Ingredient.of(pocketWith(stack, null)), stack));
} }
return Collections.unmodifiableList(recipes); return Collections.unmodifiableList(recipes);
@@ -219,7 +215,7 @@ public class UpgradeRecipeGenerator<T> {
} }
} }
private static ItemStack turtleWith(ItemStack stack, @Nullable UpgradeData<ITurtleUpgrade> left, @Nullable UpgradeData<ITurtleUpgrade> right) { private static ItemStack turtleWith(ItemStack stack, @Nullable ITurtleUpgrade left, @Nullable 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),
@@ -227,7 +223,7 @@ public class UpgradeRecipeGenerator<T> {
); );
} }
private static ItemStack pocketWith(ItemStack stack, @Nullable UpgradeData<IPocketUpgrade> back) { private static ItemStack pocketWith(ItemStack stack, @Nullable 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
@@ -276,7 +272,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, UpgradeData.ofDefault(turtle), 0, null) turtleItem.create(-1, null, -1, null, turtle, 0, null)
)); ));
} }
} }
@@ -287,7 +283,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, UpgradeData.ofDefault(pocket)) pocketItem.create(-1, null, -1, pocket)
)); ));
} }
} }

View File

@@ -12,23 +12,19 @@ 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;
public final class GenericPeripheral implements IDynamicPeripheral { 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, Direction side, @Nullable String name, Set<String> additionalTypes, List<SaturatedMethod> methods) { GenericPeripheral(BlockEntity tile, @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();
@@ -36,10 +32,6 @@ public final 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()];
@@ -62,6 +54,7 @@ public final class GenericPeripheral implements IDynamicPeripheral {
return additionalTypes; return additionalTypes;
} }
@Nullable
@Override @Override
public Object getTarget() { public Object getTarget() {
return tile; return tile;

View File

@@ -1,63 +0,0 @@
// 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());
});
}
}

View File

@@ -9,20 +9,18 @@ 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.methods.PeripheralMethod; import dan200.computercraft.core.asm.NamedMethod;
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, String name, PeripheralMethod method) { SaturatedMethod(Object target, NamedMethod<PeripheralMethod> method) {
this.target = target; this.target = target;
this.name = name; name = method.getName();
this.method = method; this.method = method.getMethod();
} }
MethodResult apply(ILuaContext context, IComputerAccess computer, IArguments args) throws LuaException { MethodResult apply(ILuaContext context, IComputerAccess computer, IArguments args) throws LuaException {

View File

@@ -16,12 +16,10 @@ 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.methods.PeripheralMethod; import dan200.computercraft.core.asm.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;
@@ -286,8 +284,7 @@ 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 methods = ServerContext.get(((ServerLevel) getLevel()).getServer()).peripheralMethods().getSelfMethods(peripheral); var wrapper = new RemotePeripheralWrapper(modem, peripheral, computer, periphName);
var wrapper = new RemotePeripheralWrapper(modem, peripheral, computer, periphName, methods);
peripherals.put(periphName, wrapper); peripherals.put(periphName, wrapper);
wrapper.attach(); wrapper.attach();
} }
@@ -317,7 +314,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, Map<String, PeripheralMethod> methods) { RemotePeripheralWrapper(WiredModemElement element, IPeripheral peripheral, IComputerAccess computer, String name) {
this.element = element; this.element = element;
this.peripheral = peripheral; this.peripheral = peripheral;
this.computer = computer; this.computer = computer;
@@ -325,7 +322,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 = methods; methodMap = PeripheralAPI.getMethods(peripheral);
} }
public void attach() { public void attach() {

View File

@@ -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"}.
* *

View File

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

View File

@@ -1,31 +0,0 @@
// 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;
}

View File

@@ -33,6 +33,7 @@ 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;
@@ -68,13 +69,6 @@ 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.
* *
@@ -279,6 +273,13 @@ 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.

View File

@@ -5,7 +5,6 @@
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;
@@ -37,7 +36,9 @@ 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 IdMap<T> { public interface RegistryWrapper<T> extends Iterable<T> {
int getId(T object);
ResourceLocation getKey(T object); ResourceLocation getKey(T object);
T get(ResourceLocation location); T get(ResourceLocation location);
@@ -45,6 +46,8 @@ 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);
} }
@@ -53,6 +56,15 @@ 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));
} }

View File

@@ -7,7 +7,6 @@ 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;
@@ -15,7 +14,6 @@ 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.
@@ -70,7 +68,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.getUpgradeItem()); if (previousUpgrade != null) storeItem(player, previousUpgrade.getCraftingItem().copy());
// Set the new upgrade // Set the new upgrade
computer.setUpgrade(newUpgrade); computer.setUpgrade(newUpgrade);
@@ -95,7 +93,7 @@ public class PocketAPI implements ILuaAPI {
computer.setUpgrade(null); computer.setUpgrade(null);
storeItem(player, previousUpgrade.getUpgradeItem()); storeItem(player, previousUpgrade.getCraftingItem().copy());
return new Object[]{ true }; return new Object[]{ true };
} }
@@ -107,13 +105,13 @@ public class PocketAPI implements ILuaAPI {
} }
} }
private static @Nullable UpgradeData<IPocketUpgrade> findUpgrade(NonNullList<ItemStack> inv, int start, @Nullable UpgradeData<IPocketUpgrade> previous) { private static @Nullable IPocketUpgrade findUpgrade(NonNullList<ItemStack> inv, int start, @Nullable 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 && !Objects.equals(newUpgrade, previous)) { if (newUpgrade != null && 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);

View File

@@ -7,7 +7,6 @@ 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;
@@ -105,13 +104,12 @@ 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 UpgradeData<IPocketUpgrade> getUpgrade() { public @Nullable IPocketUpgrade getUpgrade() {
return upgrade == null ? null : UpgradeData.of(upgrade, getUpgradeNBTData()); return upgrade;
} }
/** /**
@@ -121,11 +119,13 @@ 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 UpgradeData<IPocketUpgrade> upgrade) { public void setUpgrade(@Nullable IPocketUpgrade upgrade) {
if (this.upgrade == upgrade) return;
synchronized (this) { synchronized (this) {
PocketComputerItem.setUpgrade(stack, upgrade); PocketComputerItem.setUpgrade(stack, upgrade);
updateUpgradeNBTData(); updateUpgradeNBTData();
this.upgrade = upgrade == null ? null : upgrade.upgrade(); this.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;

View File

@@ -48,7 +48,7 @@ public class PocketComputerMenuProvider implements MenuProvider {
isTypingOnly ? ModRegistry.Menus.POCKET_COMPUTER_NO_TERM.get() : ModRegistry.Menus.POCKET_COMPUTER.get(), id, inventory, isTypingOnly ? ModRegistry.Menus.POCKET_COMPUTER_NO_TERM.get() : ModRegistry.Menus.POCKET_COMPUTER.get(), id, inventory,
p -> { p -> {
var stack = p.getItemInHand(hand); var stack = p.getItemInHand(hand);
return stack.getItem() == item && PocketComputerItem.getServerComputer(assertNonNull(entity.level.getServer()), stack) == computer; return stack.getItem() == item && PocketComputerItem.getServerComputer(assertNonNull(entity.level().getServer()), stack) == computer;
}, },
computer, item.getFamily() computer, item.getFamily()
); );

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