Compare commits

...

9 Commits

Author SHA1 Message Date
Jonathan Coates 61431ac52d
Update to 1.20.5 2024-04-24 21:15:48 +01:00
Jonathan Coates 5c457950d8
Merge branch 'mc-1.20.x' into mc-1.20.y 2024-04-24 20:30:59 +01:00
Jonathan Coates 925092add3
Fix build on Windows
- Force encoding to UTF-8
 - Fix npm not being found on the path
 - Test building common/web on OSX and Windows
2024-04-24 17:55:48 +01:00
Jonathan Coates 550296edc5
Fix typo in speaker docs
Closes #1799
2024-04-21 09:45:02 +01:00
Jonathan Coates 0771c4891b
Various Gradle tweaks
- Update Gradle to 8.7
 - Configure IntelliJ to build internally, rather than delgating to
   Gradle. We've seen some weird issues with using delegated builds, so
   best avoided.
 - Remove gitpod config. This has been broken for a while (used Java 16
   rather than 17) and nobody noticed, so I suspect nobody uses this.
2024-04-19 18:14:51 +01:00
Jonathan Coates 776fa00b94
Update to latest TeaVM
- Add the core TeaVM jar to the runtime the classpath, to ensure
   various runtime classes are present.
 - Fix computer initialisation errors not being displayed on the screen.
   The terminal was set to the default 0x0 size when logging the error,
   and so never displayed anything!
2024-04-17 21:57:11 +01:00
Jonathan Coates 03bb279206
Move computer right click code to the block
Rather than handling right clicks within the block entity code, we now
handle it within the block. Turtles now handle the nametagging
behaviour themselves, rather than overriding canNameWithTag.
2024-04-17 15:01:50 +01:00
Weblate fabd77132d Translations for Czech
Co-authored-by: Patriik <apatriik0@gmail.com>
2024-04-09 08:54:05 +00:00
Jonathan Coates 95be0a25bf
Update Cobalt to 0.9.3
- Fix some errors missing source positions
 - Poll interrupted state when parsing Lua
2024-04-08 12:18:21 +01:00
403 changed files with 3206 additions and 3877 deletions

View File

@ -14,7 +14,7 @@ jobs:
- name: 📥 Set up Java
uses: actions/setup-java@v4
with:
java-version: 17
java-version: 21
distribution: 'temurin'
- name: 📥 Setup Gradle
@ -58,13 +58,13 @@ jobs:
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
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: CC-Tweaked
path: ./jars
- name: 📤 Upload coverage
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
build-core:
strategy:
@ -81,24 +81,28 @@ jobs:
runs-on: ${{ matrix.uses }}
steps:
- name: Clone repository
- name: 📥 Clone repository
uses: actions/checkout@v4
- name: Set up Java
- name: 📥 Set up Java
uses: actions/setup-java@v4
with:
java-version: 17
java-version: 21
distribution: 'temurin'
- name: Setup Gradle
- name: 📥 Setup Gradle
uses: gradle/actions/setup-gradle@v3
with:
cache-read-only: ${{ !startsWith(github.ref, 'refs/heads/mc-') }}
- name: Run tests
- name: ⚒️ Build
run: |
./gradlew --configure-on-demand :core:assemble :web:assemble
- name: 🧪 Run tests
run: |
./gradlew --configure-on-demand :core:test
- name: Parse test reports
- name: 🧪 Parse test reports
run: python3 ./tools/parse-reports.py
if: ${{ failure() }}

View File

@ -18,7 +18,7 @@ jobs:
- name: Set up Java
uses: actions/setup-java@v4
with:
java-version: 17
java-version: 21
distribution: 'temurin'
- name: Setup Gradle

View File

@ -1,26 +0,0 @@
# SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
#
# SPDX-License-Identifier: MPL-2.0
image:
file: config/gitpod/Dockerfile
ports:
- port: 25565
onOpen: notify
vscode:
extensions:
- eamodio.gitlens
- github.vscode-pull-request-github
- ms-azuretools.vscode-docker
- redhat.java
- richardwillis.vscode-gradle
- vscjava.vscode-java-debug
- vscode.github
tasks:
- name: Setup pre-commit hool
init: pre-commit install --allow-missing-config
- name: Install npm packages
init: npm ci

View File

@ -29,9 +29,9 @@ ## Setting up a development environment
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:
- Java Development Kit (JDK) installed. This can be downloaded from [Adoptium].
- Java Development Kit (JDK). This can be downloaded from [Adoptium].
- [Git](https://git-scm.com/).
- If you want to work on documentation, [NodeJS][node].
- [NodeJS][node].
- Download CC: Tweaked's source code:
```

View File

@ -5,9 +5,8 @@
import cc.tweaked.gradle.JUnitExt
import net.fabricmc.loom.api.LoomGradleExtensionAPI
import net.fabricmc.loom.util.gradle.SourceSetHelper
import org.jetbrains.gradle.ext.compiler
import org.jetbrains.gradle.ext.runConfigurations
import org.jetbrains.gradle.ext.settings
import org.jetbrains.gradle.ext.*
import org.jetbrains.gradle.ext.Application
plugins {
publishing
@ -86,6 +85,19 @@ idea.project.settings.runConfigurations {
moduleName = "${idea.project.name}.forge.test"
packageName = ""
}
register<Application>("Standalone") {
moduleName = "${idea.project.name}.standalone.main"
mainClass = "cc.tweaked.standalone.Main"
programParameters = "--resources=projects/core/src/main/resources --term=80x30 --allow-local-domains"
}
}
// Build with the IntelliJ, rather than through Gradle. This may require setting the "Compiler Output" option in
// "Project Structure".
idea.project.settings.delegateActions {
delegateBuildRunToGradle = false
testRunner = ActionDelegationConfig.TestRunner.PLATFORM
}
idea.project.settings.compiler.javac {

View File

@ -78,8 +78,16 @@ dependencies {
// Configure default JavaCompile tasks with our arguments.
sourceSets.all {
tasks.named(compileJavaTaskName, JavaCompile::class.java) {
// Processing just gives us "No processor claimed any of these annotations", so skip that!
options.compilerArgs.addAll(listOf("-Xlint", "-Xlint:-processing"))
options.compilerArgs.addAll(
listOf(
"-Xlint",
// Processing just gives us "No processor claimed any of these annotations", so skip that!
"-Xlint:-processing",
// We violate this pattern too often for it to be a helpful warning. Something to improve one day!
"-Xlint:-this-escape",
),
)
options.errorprone {
check("InvalidBlockTag", CheckSeverity.OFF) // Broken by @cc.xyz
@ -148,7 +156,7 @@ tasks.javadoc {
options {
val stdOptions = this as StandardJavadocDocletOptions
stdOptions.addBooleanOption("Xdoclint:all,-missing", true)
stdOptions.links("https://docs.oracle.com/en/java/javase/17/docs/api/")
stdOptions.links("https://docs.oracle.com/en/java/javase/21/docs/api/")
}
}

View File

@ -35,7 +35,6 @@
import java.io.File
import java.io.IOException
import java.net.URI
import java.net.URL
import java.util.regex.Pattern
abstract class CCTweakedExtension(
@ -226,12 +225,12 @@ fun <T> jacoco(task: NamedDomainObjectProvider<T>) where T : Task, T : JavaForkO
* where possible.
*/
fun downloadFile(label: String, url: String): File {
val url = URL(url)
val url = URI(url)
val path = File(url.path)
project.repositories.ivy {
name = label
setUrl(URI(url.protocol, url.userInfo, url.host, url.port, path.parent, null, null))
setUrl(URI(url.scheme, url.userInfo, url.host, url.port, path.parent, null, null))
patternLayout {
artifact("[artifact].[ext]")
}

View File

@ -42,6 +42,6 @@ private fun extendIdea(project: Project) {
}
companion object {
val JAVA_VERSION = JavaLanguageVersion.of(17)
val JAVA_VERSION = JavaLanguageVersion.of(21)
}
}

View File

@ -46,7 +46,7 @@ abstract class NpmInstall : DefaultTask() {
@TaskAction
fun install() {
project.exec {
commandLine("npm", "ci")
commandLine(ProcessHelpers.getExecutable("npm"), "ci")
workingDir = projectRoot.get().asFile
}
}
@ -59,6 +59,6 @@ fun install() {
abstract class NpxExecToDir : ExecToDir() {
init {
dependsOn(NpmInstall.TASK_NAME)
executable = "npx"
executable = ProcessHelpers.getExecutable("npx")
}
}

View File

@ -9,6 +9,7 @@
import java.io.BufferedReader
import java.io.File
import java.io.InputStreamReader
import java.nio.charset.StandardCharsets
internal object ProcessHelpers {
fun startProcess(vararg command: String): Process {
@ -34,7 +35,7 @@ fun captureLines(vararg command: String): List<String> {
val process = startProcess(*command)
process.outputStream.close()
val out = BufferedReader(InputStreamReader(process.inputStream)).use { reader ->
val out = BufferedReader(InputStreamReader(process.inputStream, StandardCharsets.UTF_8)).use { reader ->
reader.lines().filter { it.isNotEmpty() }.toList()
}
ProcessGroovyMethods.closeStreams(process)
@ -46,6 +47,28 @@ fun onPath(name: String): Boolean {
val path = System.getenv("PATH") ?: return false
return path.splitToSequence(File.pathSeparator).any { File(it, name).exists() }
}
/**
* Search for an executable on the `PATH` if required.
*
* [Process]/[ProcessBuilder] does not handle all executable file extensions on Windows (such as `.com). When on
* Windows, this function searches `PATH` and `PATHEXT` for an executable matching [name].
*/
fun getExecutable(name: String): String {
if (!System.getProperty("os.name").lowercase().contains("windows")) return name
val path = (System.getenv("PATH") ?: return name).split(File.pathSeparator)
val pathExt = (System.getenv("PATHEXT") ?: return name).split(File.pathSeparator)
for (pathEntry in path) {
for (ext in pathExt) {
val resolved = File(pathEntry, name + ext)
if (resolved.exists()) return resolved.getAbsolutePath()
}
}
return name
}
}
internal fun Process.waitForOrThrow(message: String) {

View File

@ -22,4 +22,7 @@ SPDX-License-Identifier: MPL-2.0
<!-- Allow underscores in our test classes. -->
<suppress checks="MethodName" files=".*(Contract|Test).java" />
<!-- Allow underscores in Mixin classes -->
<suppress checks="TypeName" files=".*[\\/]mixin[\\/].*.java" />
</suppressions>

View File

@ -1,12 +0,0 @@
# SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
#
# SPDX-License-Identifier: MPL-2.0
FROM gitpod/workspace-base
USER gitpod
RUN sudo apt-get -q update \
&& sudo apt-get install -yq openjdk-16-jdk python3-pip npm \
&& sudo pip3 install pre-commit \
&& sudo update-java-alternatives --set java-1.16.0-openjdk-amd64

View File

@ -131,7 +131,7 @@ ## Storing audio
First, we require the dfpwm module and call [`cc.audio.dfpwm.make_decoder`] to construct a new decoder. This decoder
accepts blocks of DFPWM data and converts it to a list of 8-bit amplitudes, which we can then play with our speaker.
As mentioned above, [`speaker.playAudio`] accepts at most 128×1024 samples in one go. DFPMW uses a single bit for each
As mentioned above, [`speaker.playAudio`] accepts at most 128×1024 samples in one go. DFPWM uses a single bit for each
sample, which means we want to process our audio in chunks of 16×1024 bytes (16KiB). In order to do this, we use
[`io.lines`], which provides a nice way to loop over chunks of a file. You can of course just use [`fs.open`] and
[`fs.ReadHandle.read`] if you prefer.

View File

@ -2,7 +2,7 @@
#
# SPDX-License-Identifier: MPL-2.0
org.gradle.jvmargs=-Xmx3G
org.gradle.jvmargs=-Xmx3G -Dfile.encoding=UTF-8
org.gradle.parallel=true
kotlin.stdlib.default.dependency=false
@ -13,4 +13,4 @@ isUnstable=true
modVersion=1.110.2
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
mcVersion=1.20.4
mcVersion=1.20.5

View File

@ -7,26 +7,26 @@
# Minecraft
# 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
fabric-api = "0.93.1+1.20.4"
fabric-loader = "0.15.3"
neoForge = "20.4.210"
fabric-api = "0.97.6+1.20.5"
fabric-loader = "0.15.10"
neoForge = "20.5.0-beta"
neoForgeSpi = "8.0.1"
mixin = "0.8.5"
parchment = "2023.12.31"
parchmentMc = "1.20.3"
yarn = "1.20.4+build.3"
parchment = "2024.04.14"
parchmentMc = "1.20.4"
yarn = "1.20.5+build.1"
# Core dependencies (these versions are tied to the version Minecraft uses)
fastutil = "8.5.12"
guava = "32.1.2-jre"
netty = "4.1.97.Final"
slf4j = "2.0.7"
slf4j = "2.0.9"
# Core dependencies (independent of Minecraft)
asm = "9.6"
autoService = "1.1.1"
checkerFramework = "3.42.0"
cobalt = "0.9.2"
cobalt = { strictly = "0.9.3" }
commonsCli = "1.6.0"
jetbrainsAnnotations = "24.1.0"
jsr305 = "3.0.2"
@ -46,6 +46,7 @@ oculus = "1.2.5"
rei = "14.0.688"
rubidium = "0.6.1"
sodium = "mc1.20-0.4.10"
mixinExtra = "0.3.5"
# Testing
hamcrest = "2.2"
@ -59,19 +60,19 @@ checkstyle = "10.14.1"
curseForgeGradle = "1.1.18"
errorProne-core = "2.23.0"
errorProne-plugin = "3.1.0"
fabric-loom = "1.5.7"
fabric-loom = "1.6.7"
githubRelease = "2.5.2"
gradleVersions = "0.50.0"
ideaExt = "1.1.7"
illuaminate = "0.1.0-71-g378d86e"
lwjgl = "3.3.3"
minotaur = "2.8.7"
neoGradle = "7.0.100"
neoGradle = "7.0.107"
nullAway = "0.10.25"
spotless = "6.23.3"
taskTree = "2.1.1"
teavm = "0.10.0-SQUID.3"
vanillaExtract = "0.1.2"
teavm = "0.10.0-SQUID.4"
vanillaExtract = "0.1.3"
versionCatalogUpdate = "0.8.1"
[libraries]
@ -109,6 +110,7 @@ jei-api = { module = "mezz.jei:jei-1.20.4-common-api", version.ref = "jei" }
jei-fabric = { module = "mezz.jei:jei-1.20.4-fabric", version.ref = "jei" }
jei-forge = { module = "mezz.jei:jei-1.20.4-forge", version.ref = "jei" }
mixin = { module = "org.spongepowered:mixin", version.ref = "mixin" }
mixinExtra = { module = "io.github.llamalad7:mixinextras-common", version.ref = "mixinExtra" }
modmenu = { module = "com.terraformersmc:modmenu", version.ref = "modmenu" }
moreRed = { module = "commoble.morered:morered-1.20.1", version.ref = "moreRed" }
oculus = { module = "maven.modrinth:oculus", version.ref = "oculus" }
@ -152,6 +154,7 @@ neoGradle-userdev = { module = "net.neoforged.gradle:userdev", version.ref = "ne
nullAway = { module = "com.uber.nullaway:nullaway", version.ref = "nullAway" }
spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" }
teavm-classlib = { module = "org.teavm:teavm-classlib", version.ref = "teavm" }
teavm-core = { module = "org.teavm:teavm-core", version.ref = "teavm" }
teavm-jso = { module = "org.teavm:teavm-jso", version.ref = "teavm" }
teavm-jso-apis = { module = "org.teavm:teavm-jso-apis", version.ref = "teavm" }
teavm-jso-impl = { module = "org.teavm:teavm-jso-impl", version.ref = "teavm" }
@ -178,7 +181,7 @@ externalMods-common = ["jei-api", "nightConfig-core", "nightConfig-toml"]
externalMods-forge-compile = ["moreRed", "oculus", "jei-api"]
externalMods-forge-runtime = []
externalMods-fabric-compile = ["fabricPermissions", "iris", "jei-api", "rei-api", "rei-builtin"]
externalMods-fabric-runtime = ["jei-fabric", "modmenu"]
externalMods-fabric-runtime = [] # ["jei-fabric", "modmenu"]
# Testing
test = ["junit-jupiter-api", "junit-jupiter-params", "hamcrest", "jqwik-api"]

Binary file not shown.

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

View File

@ -9,7 +9,7 @@
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.core.component.DataComponentPatch;
import net.minecraft.resources.ResourceLocation;
import javax.annotation.Nullable;
@ -38,7 +38,7 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
* @param data Upgrade data instance for current turtle side.
* @return The model that you wish to be used to render your upgrade.
*/
TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, CompoundTag data);
TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, DataComponentPatch data);
/**
* Get a list of models that this turtle modeller depends on.
@ -55,7 +55,7 @@ default Collection<ResourceLocation> getDependencies() {
}
/**
* A basic {@link TurtleUpgradeModeller} which renders using the upgrade's {@linkplain ITurtleUpgrade#getUpgradeItem(CompoundTag)}
* A basic {@link TurtleUpgradeModeller} which renders using the upgrade's {@linkplain ITurtleUpgrade#getUpgradeItem(DataComponentPatch)}
* upgrade item}.
* <p>
* This uses appropriate transformations for "flat" items, namely those extending the {@literal minecraft:item/generated}
@ -80,7 +80,7 @@ static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> flatItem() {
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ResourceLocation left, ResourceLocation right) {
return new TurtleUpgradeModeller<>() {
@Override
public TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, CompoundTag data) {
public TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, DataComponentPatch data) {
return TransformedModel.of(side == TurtleSide.LEFT ? left : right);
}

View File

@ -11,7 +11,7 @@
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.core.component.DataComponentPatch;
import org.joml.Matrix4f;
import javax.annotation.Nullable;
@ -36,7 +36,7 @@ private static Transformation getMatrixFor(float offset) {
private static final class UpgradeItemModeller implements TurtleUpgradeModeller<ITurtleUpgrade> {
@Override
public TransformedModel getModel(ITurtleUpgrade upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, CompoundTag data) {
public TransformedModel getModel(ITurtleUpgrade upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, DataComponentPatch data) {
var stack = upgrade.getUpgradeItem(data);
var model = Minecraft.getInstance().getItemRenderer().getItemModelShaper().getItemModel(stack);
if (stack.hasFoil()) model = ClientPlatformHelper.get().createdFoiledModel(model);

View File

@ -6,10 +6,12 @@
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.ItemTags;
import net.minecraft.tags.TagKey;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
@ -35,6 +37,14 @@ public static class Items {
*/
public static final TagKey<Item> TURTLE_CAN_PLACE = make("turtle_can_place");
/**
* Items which can be dyed.
* <p>
* This is similar to {@link ItemTags#DYEABLE}, but allows cleaning the item with a sponge, rather than in a
* cauldron.
*/
public static final TagKey<Item> DYEABLE = make("dyeable");
private static TagKey<Item> make(String name) {
return TagKey.create(Registries.ITEM, new ResourceLocation(ComputerCraftAPI.MOD_ID, name));
}
@ -75,8 +85,8 @@ public static class Blocks {
public static final TagKey<Block> TURTLE_HOE_BREAKABLE = make("turtle_hoe_harvestable");
/**
* Block which can be {@linkplain BlockState#use(Level, Player, InteractionHand, BlockHitResult) used} when
* calling {@code turtle.place()}.
* Block which can be {@linkplain BlockState#useItemOn(ItemStack, Level, Player, InteractionHand, BlockHitResult) used}
* when calling {@code turtle.place()}.
*/
public static final TagKey<Block> TURTLE_CAN_USE = make("turtle_can_use");

View File

@ -5,7 +5,7 @@
package dan200.computercraft.api.pocket;
import dan200.computercraft.api.upgrades.UpgradeBase;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.core.component.DataComponentPatch;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.ItemStack;
@ -67,18 +67,19 @@ public interface IPocketAccess {
* This is persisted between computer reboots and chunk loads.
*
* @return The upgrade's NBT.
* @see #updateUpgradeNBTData()
* @see UpgradeBase#getUpgradeItem(CompoundTag)
* @see #setUpgradeData(DataComponentPatch)
* @see UpgradeBase#getUpgradeItem(DataComponentPatch)
* @see UpgradeBase#getUpgradeData(ItemStack)
*/
CompoundTag getUpgradeNBTData();
DataComponentPatch getUpgradeData();
/**
* Mark the upgrade-specific NBT as dirty.
* Update the upgrade-specific data.
*
* @see #getUpgradeNBTData()
* @param data The new upgrade data.
* @see #getUpgradeData()
*/
void updateUpgradeNBTData();
void setUpgradeData(DataComponentPatch data);
/**
* Remove the current peripheral and create a new one.

View File

@ -12,7 +12,7 @@
import dan200.computercraft.api.upgrades.UpgradeData;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.core.component.DataComponentPatch;
import net.minecraft.world.Container;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
@ -229,37 +229,22 @@ public interface ITurtleAccess {
* @param side The side to get the upgrade from.
* @return The upgrade on the specified side of the turtle, if there is one.
* @see #getUpgradeWithData(TurtleSide)
* @see #setUpgradeWithData(TurtleSide, UpgradeData)
* @see #setUpgrade(TurtleSide, UpgradeData)
*/
@Nullable
ITurtleUpgrade getUpgrade(TurtleSide side);
/**
* Returns the upgrade on the specified side of the turtle, along with its {@linkplain #getUpgradeNBTData(TurtleSide)
* Returns the upgrade on the specified side of the turtle, along with its {@linkplain #getUpgradeData(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)
* @see #setUpgrade(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.
*
* @param side The side to set the upgrade on.
* @param upgrade The upgrade to set, may be {@code null} to clear.
* @see #getUpgrade(TurtleSide)
* @deprecated Use {@link #setUpgradeWithData(TurtleSide, UpgradeData)}
*/
@Deprecated
default void setUpgrade(TurtleSide side, @Nullable ITurtleUpgrade upgrade) {
setUpgradeWithData(side, upgrade == null ? null : UpgradeData.ofDefault(upgrade));
}
@Nullable
UpgradeData<ITurtleUpgrade> getUpgradeWithData(TurtleSide side);
/**
* Set the upgrade for a given side and its upgrade data.
@ -268,7 +253,7 @@ default void setUpgrade(TurtleSide side, @Nullable ITurtleUpgrade upgrade) {
* @param upgrade The upgrade to set, may be {@code null} to clear.
* @see #getUpgradeWithData(TurtleSide)
*/
void setUpgradeWithData(TurtleSide side, @Nullable UpgradeData<ITurtleUpgrade> upgrade);
void setUpgrade(TurtleSide side, @Nullable UpgradeData<ITurtleUpgrade> upgrade);
/**
* Returns the peripheral created by the upgrade on the specified side of the turtle, if there is one.
@ -282,23 +267,23 @@ default void setUpgrade(TurtleSide side, @Nullable ITurtleUpgrade upgrade) {
/**
* Get an upgrade-specific NBT compound, which can be used to store arbitrary data.
* <p>
* This will be persisted across turtle restarts and chunk loads, as well as being synced to the client. You must
* call {@link #updateUpgradeNBTData(TurtleSide)} after modifying it.
* This will be persisted across turtle restarts and chunk loads, as well as being synced to the client. You can
* call {@link #setUpgrade(TurtleSide, UpgradeData)} to modify it.
*
* @param side The side to get the upgrade data for.
* @return The upgrade-specific data.
* @see #updateUpgradeNBTData(TurtleSide)
* @see UpgradeBase#getUpgradeItem(CompoundTag)
* @see #setUpgradeData(TurtleSide, DataComponentPatch)
* @see UpgradeBase#getUpgradeItem(DataComponentPatch)
* @see UpgradeBase#getUpgradeData(ItemStack)
*/
CompoundTag getUpgradeNBTData(TurtleSide side);
DataComponentPatch getUpgradeData(TurtleSide side);
/**
* Mark the upgrade-specific data as dirty on a specific side. This is required for the data to be synced to the
* client and persisted.
* Update the upgrade-specific data.
*
* @param side The side to mark dirty.
* @see #updateUpgradeNBTData(TurtleSide)
* @param side The side to set the upgrade data for.
* @param data The new upgrade data.
* @see #getUpgradeData(TurtleSide)
*/
void updateUpgradeNBTData(TurtleSide side);
void setUpgradeData(TurtleSide side, DataComponentPatch data);
}

View File

@ -10,7 +10,7 @@
import dan200.computercraft.impl.ComputerCraftAPIService;
import net.minecraft.core.Direction;
import net.minecraft.core.Registry;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.core.component.DataComponentPatch;
import net.minecraft.resources.ResourceKey;
import javax.annotation.Nullable;
@ -133,7 +133,7 @@ default void update(ITurtleAccess turtle, TurtleSide side) {
* @param upgradeData Data that currently stored for this upgrade
* @return Filtered version of this data.
*/
default CompoundTag getPersistedData(CompoundTag upgradeData) {
default DataComponentPatch getPersistedData(DataComponentPatch upgradeData) {
return upgradeData;
}
}

View File

@ -4,8 +4,8 @@
package dan200.computercraft.api.turtle;
import net.minecraft.core.component.DataComponents;
import net.minecraft.util.StringRepresentable;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.item.ItemStack;
/**
@ -21,7 +21,7 @@ public enum TurtleToolDurability implements StringRepresentable {
/**
* The equipped tool consumes durability if it is {@linkplain ItemStack#isEnchanted() enchanted} or has
* {@linkplain ItemStack#getAttributeModifiers(EquipmentSlot) custom attribute modifiers}.
* {@linkplain DataComponents#ATTRIBUTE_MODIFIERS custom attribute modifiers}.
*/
WHEN_ENCHANTED("when_enchanted"),

View File

@ -9,12 +9,12 @@
import dan200.computercraft.api.upgrades.UpgradeDataProvider;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import dan200.computercraft.impl.RegistryHelper;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.data.DataGenerator;
import net.minecraft.data.PackOutput;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
@ -111,7 +111,7 @@ public ToolBuilder damageMultiplier(float damageMultiplier) {
/**
* Indicate that this upgrade allows items which have been {@linkplain ItemStack#isEnchanted() enchanted} or have
* {@linkplain ItemStack#getAttributeModifiers(EquipmentSlot) custom attribute modifiers}.
* {@linkplain DataComponents#ATTRIBUTE_MODIFIERS custom attribute modifiers}.
*
* @return The tool builder, for further use.
*/

View File

@ -10,12 +10,10 @@
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import net.minecraft.Util;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.core.component.DataComponentPatch;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import java.util.Objects;
/**
* Common functionality between {@link ITurtleUpgrade} and {@link IPocketUpgrade}.
*/
@ -56,8 +54,8 @@ public interface UpgradeBase {
/**
* 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,
* While upgrades can store upgrade data ({@link ITurtleAccess#getUpgradeData(TurtleSide)} and
* {@link IPocketAccess#getUpgradeData()}}, 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
@ -69,24 +67,24 @@ public interface UpgradeBase {
* @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) {
default ItemStack getUpgradeItem(DataComponentPatch 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()}.
* This upgrade data will be available with {@link ITurtleAccess#getUpgradeData(TurtleSide)} or
* {@link IPocketAccess#getUpgradeData()}.
* <p>
* This should be an inverse to {@link #getUpgradeItem(CompoundTag)}.
* This should be an inverse to {@link #getUpgradeItem(DataComponentPatch)}.
*
* @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();
default DataComponentPatch getUpgradeData(ItemStack stack) {
return DataComponentPatch.EMPTY;
}
/**
@ -110,11 +108,9 @@ default boolean isItemSuitable(ItemStack stack) {
// A more expanded form of ItemStack.areShareTagsEqual, but allowing an empty tag to be equal to a
// null one.
var tag = stack.getTag();
var craftingTag = crafting.getTag();
if (tag == craftingTag) return true;
if (tag == null) return Objects.requireNonNull(craftingTag).isEmpty();
if (craftingTag == null) return tag.isEmpty();
// TODO(1.20.5): Something better than this
var tag = stack.getComponents();
var craftingTag = crafting.getComponents();
return tag.equals(craftingTag);
}

View File

@ -6,23 +6,17 @@
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.core.component.DataComponentPatch;
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) {
public record UpgradeData<T extends UpgradeBase>(T upgrade, DataComponentPatch data) {
/**
* A utility method to construct a new {@link UpgradeData} instance.
*
@ -31,7 +25,7 @@ public record UpgradeData<T extends UpgradeBase>(T upgrade, CompoundTag 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) {
public static <T extends UpgradeBase> UpgradeData<T> of(T upgrade, DataComponentPatch data) {
return new UpgradeData<>(upgrade, data);
}
@ -47,19 +41,7 @@ public static <T extends UpgradeBase> UpgradeData<T> ofDefault(T upgrade) {
}
/**
* 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.
* Get the {@linkplain UpgradeBase#getUpgradeItem(DataComponentPatch) 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}.
@ -69,14 +51,4 @@ public static <T extends UpgradeBase> UpgradeData<T> ofDefault(T upgrade) {
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

@ -12,7 +12,7 @@
import dan200.computercraft.impl.upgrades.SerialiserWithCraftingItem;
import dan200.computercraft.impl.upgrades.SimpleSerialiser;
import net.minecraft.core.Registry;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.RecipeSerializer;
@ -58,7 +58,7 @@ public interface UpgradeSerialiser<T extends UpgradeBase> {
* @param buffer The buffer object to read this upgrade from.
* @return The constructed upgrade, with a {@link UpgradeBase#getUpgradeID()} equal to {@code id}.
*/
T fromNetwork(ResourceLocation id, FriendlyByteBuf buffer);
T fromNetwork(ResourceLocation id, RegistryFriendlyByteBuf buffer);
/**
* Write this upgrade to a network packet, to be sent to the client.
@ -66,7 +66,7 @@ public interface UpgradeSerialiser<T extends UpgradeBase> {
* @param buffer The buffer object to write this upgrade to
* @param upgrade The upgrade to write.
*/
void toNetwork(FriendlyByteBuf buffer, T upgrade);
void toNetwork(RegistryFriendlyByteBuf buffer, T upgrade);
/**
* Create an upgrade serialiser for a simple upgrade. This is similar to a {@link SimpleCraftingRecipeSerializer},

View File

@ -7,7 +7,7 @@
import com.google.gson.JsonObject;
import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.item.ItemStack;
@ -37,13 +37,13 @@ public T fromJson(ResourceLocation id, JsonObject object) {
}
@Override
public T fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) {
var item = buffer.readItem();
public T fromNetwork(ResourceLocation id, RegistryFriendlyByteBuf buffer) {
var item = ItemStack.STREAM_CODEC.decode(buffer);
return factory.apply(id, item);
}
@Override
public void toNetwork(FriendlyByteBuf buffer, T upgrade) {
buffer.writeItem(upgrade.getCraftingItem());
public void toNetwork(RegistryFriendlyByteBuf buffer, T upgrade) {
ItemStack.STREAM_CODEC.encode(buffer, upgrade.getCraftingItem());
}
}

View File

@ -7,7 +7,7 @@
import com.google.gson.JsonObject;
import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.ApiStatus;
@ -34,11 +34,11 @@ public T fromJson(ResourceLocation id, JsonObject object) {
}
@Override
public T fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) {
public T fromNetwork(ResourceLocation id, RegistryFriendlyByteBuf buffer) {
return constructor.apply(id);
}
@Override
public void toNetwork(FriendlyByteBuf buffer, T upgrade) {
public void toNetwork(RegistryFriendlyByteBuf buffer, T upgrade) {
}
}

View File

@ -23,7 +23,7 @@ configurations {
}
repositories {
maven("https://maven.minecraftforge.net/") {
maven("https://maven.neoforged.net/") {
content {
includeModule("org.spongepowered", "mixin")
}
@ -37,6 +37,7 @@ dependencies {
clientImplementation(clientClasses(project(":common-api")))
compileOnly(libs.mixin)
compileOnly(libs.mixinExtra)
compileOnly(libs.bundles.externalMods.common)
clientCompileOnly(variantOf(libs.emi) { classifier("api") })

View File

@ -21,13 +21,11 @@
import dan200.computercraft.core.util.Colour;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.command.CommandComputerCraft;
import dan200.computercraft.shared.common.IColouredItem;
import dan200.computercraft.shared.computer.core.ComputerState;
import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
import dan200.computercraft.shared.computer.inventory.ViewComputerMenu;
import dan200.computercraft.shared.media.items.DiskItem;
import dan200.computercraft.shared.media.items.TreasureDiskItem;
import net.minecraft.Util;
import net.minecraft.client.Minecraft;
import net.minecraft.client.color.item.ItemColor;
@ -44,11 +42,13 @@
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.PreparableReloadListener;
import net.minecraft.server.packs.resources.ResourceProvider;
import net.minecraft.util.FastColor;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.component.DyedItemColor;
import net.minecraft.world.level.ItemLike;
import javax.annotation.Nullable;
@ -94,7 +94,7 @@ public static void registerMainThread(RegisterItemProperty itemProperties) {
ModRegistry.Items.POCKET_COMPUTER_NORMAL, ModRegistry.Items.POCKET_COMPUTER_ADVANCED
);
registerItemProperty(itemProperties, "coloured",
(stack, world, player, random) -> IColouredItem.getColourBasic(stack) != -1 ? 1 : 0,
(stack, world, player, random) -> DyedItemColor.getOrDefault(stack, -1) != -1 ? 1 : 0,
ModRegistry.Items.POCKET_COMPUTER_NORMAL, ModRegistry.Items.POCKET_COMPUTER_ADVANCED
);
}
@ -162,12 +162,12 @@ public static void registerExtraModels(Consumer<ResourceLocation> register) {
public static void registerItemColours(BiConsumer<ItemColor, ItemLike> register) {
register.accept(
(stack, layer) -> layer == 1 ? ((DiskItem) stack.getItem()).getColour(stack) : 0xFFFFFF,
(stack, layer) -> layer == 1 ? DiskItem.getColour(stack) : -1,
ModRegistry.Items.DISK.get()
);
register.accept(
(stack, layer) -> layer == 1 ? TreasureDiskItem.getColour(stack) : 0xFFFFFF,
(stack, layer) -> layer == 1 ? DyedItemColor.getOrDefault(stack, Colour.BLUE.getARGB()) : -1,
ModRegistry.Items.TREASURE_DISK.get()
);
@ -180,17 +180,17 @@ public static void registerItemColours(BiConsumer<ItemColor, ItemLike> register)
private static int getPocketColour(ItemStack stack, int layer) {
return switch (layer) {
default -> 0xFFFFFF;
case 1 -> IColouredItem.getColourBasic(stack); // Frame colour
default -> -1;
case 1 -> DyedItemColor.getOrDefault(stack, -1); // Frame colour
case 2 -> { // Light colour
var computer = ClientPocketComputers.get(stack);
yield computer == null || computer.getLightState() == -1 ? Colour.BLACK.getHex() : computer.getLightState();
yield computer == null || computer.getLightState() == -1 ? Colour.BLACK.getARGB() : FastColor.ARGB32.opaque(computer.getLightState());
}
};
}
private static int getTurtleColour(ItemStack stack, int layer) {
return layer == 0 ? ((IColouredItem) stack.getItem()).getColour(stack) : 0xFFFFFF;
return layer == 0 ? DyedItemColor.getOrDefault(stack, -1) : -1;
}
public static void registerShaders(ResourceProvider resources, BiConsumer<ShaderInstance, Consumer<ShaderInstance>> load) throws IOException {

View File

@ -75,7 +75,7 @@ public void display(TableBuilder table) {
var tag = createTag(table.getId());
if (chat.allMessages.removeIf(guiMessage -> guiMessage.tag() != null && Objects.equals(guiMessage.tag().logTag(), tag.logTag()))) {
chat.refreshTrimmedMessage();
chat.resetChatScroll();
}
TableFormatter.super.display(table);

View File

@ -49,31 +49,31 @@ public void queueEvent(String event, @Nullable Object[] arguments) {
@Override
public void keyDown(int key, boolean repeat) {
ClientNetworking.sendToServer(new KeyEventServerMessage(menu, repeat ? KeyEventServerMessage.TYPE_REPEAT : KeyEventServerMessage.TYPE_DOWN, key));
ClientNetworking.sendToServer(new KeyEventServerMessage(menu, repeat ? KeyEventServerMessage.Action.REPEAT : KeyEventServerMessage.Action.DOWN, key));
}
@Override
public void keyUp(int key) {
ClientNetworking.sendToServer(new KeyEventServerMessage(menu, KeyEventServerMessage.TYPE_UP, key));
ClientNetworking.sendToServer(new KeyEventServerMessage(menu, KeyEventServerMessage.Action.UP, key));
}
@Override
public void mouseClick(int button, int x, int y) {
ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_CLICK, button, x, y));
ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.Action.CLICK, button, x, y));
}
@Override
public void mouseUp(int button, int x, int y) {
ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_UP, button, x, y));
ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.Action.UP, button, x, y));
}
@Override
public void mouseDrag(int button, int x, int y) {
ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_DRAG, button, x, y));
ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.Action.DRAG, button, x, y));
}
@Override
public void mouseScroll(int direction, int x, int y) {
ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_SCROLL, direction, x, y));
ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.Action.SCROLL, direction, x, y));
}
}

View File

@ -6,7 +6,9 @@
import com.mojang.blaze3d.vertex.Tesselator;
import dan200.computercraft.core.terminal.TextBuffer;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.common.HeldItemMenu;
import dan200.computercraft.shared.media.items.PrintoutData;
import dan200.computercraft.shared.media.items.PrintoutItem;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
@ -35,13 +37,14 @@ public PrintoutScreen(HeldItemMenu container, Inventory player, Component title)
imageHeight = Y_SIZE;
var text = PrintoutItem.getText(container.getStack());
this.text = new TextBuffer[text.length];
for (var i = 0; i < this.text.length; i++) this.text[i] = new TextBuffer(text[i]);
var colours = PrintoutItem.getColours(container.getStack());
this.colours = new TextBuffer[colours.length];
for (var i = 0; i < this.colours.length; i++) this.colours[i] = new TextBuffer(colours[i]);
var printout = container.getStack().getOrDefault(ModRegistry.DataComponents.PRINTOUT.get(), PrintoutData.EMPTY);
this.text = new TextBuffer[printout.lines().size()];
this.colours = new TextBuffer[printout.lines().size()];
for (var i = 0; i < this.text.length; i++) {
var line = printout.lines().get(i);
this.text[i] = new TextBuffer(line.text());
this.colours[i] = new TextBuffer(line.foreground());
}
page = 0;
pages = Math.max(this.text.length / PrintoutItem.LINES_PER_PAGE, 1);

View File

@ -27,13 +27,11 @@ public void register(EmiRegistry registry) {
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 turtleComparison = compareStacks((left, right)
-> TurtleItem.getUpgrade(left, TurtleSide.LEFT) == TurtleItem.getUpgrade(right, TurtleSide.LEFT)
&& TurtleItem.getUpgrade(left, TurtleSide.RIGHT) == TurtleItem.getUpgrade(right, TurtleSide.RIGHT));
private static final Comparison pocketComparison = compareStacks((left, right) ->
left.getItem() instanceof PocketComputerItem && PocketComputerItem.getUpgrade(left) == PocketComputerItem.getUpgrade(right));
private static final Comparison pocketComparison = compareStacks((left, right) -> PocketComputerItem.getUpgrade(left) == PocketComputerItem.getUpgrade(right));
private static Comparison compareStacks(BiPredicate<ItemStack, ItemStack> test) {
return Comparison.of((left, right) -> {

View File

@ -15,9 +15,11 @@
import dan200.computercraft.client.render.TurtleBlockEntityRenderer;
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
import dan200.computercraft.shared.turtle.items.TurtleItem;
import dan200.computercraft.shared.util.DataComponentUtil;
import dan200.computercraft.shared.util.Holiday;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.core.component.DataComponents;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
@ -54,13 +56,6 @@ private record Combination(
boolean christmas,
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;
@ -96,31 +91,18 @@ public TurtleModelParts(BakedModel familyModel, BakedModel colourModel, ModelTra
public T getModel(ItemStack stack) {
var combination = getCombination(stack);
var existing = modelCache.get(combination);
if (existing != null) return existing;
// Take a defensive copy of the upgrade data, and add it to the cache.
var newCombination = combination.copy();
var newModel = buildModel.apply(newCombination);
modelCache.put(newCombination, newModel);
return newModel;
return modelCache.computeIfAbsent(combination, buildModel);
}
private Combination getCombination(ItemStack stack) {
var christmas = Holiday.getCurrent() == Holiday.CHRISTMAS;
if (!(stack.getItem() instanceof TurtleItem turtle)) {
return new Combination(false, null, null, null, christmas, false);
}
var colour = turtle.getColour(stack);
var leftUpgrade = turtle.getUpgradeWithData(stack, TurtleSide.LEFT);
var rightUpgrade = turtle.getUpgradeWithData(stack, TurtleSide.RIGHT);
var overlay = turtle.getOverlay(stack);
var label = turtle.getLabel(stack);
var leftUpgrade = TurtleItem.getUpgradeWithData(stack, TurtleSide.LEFT);
var rightUpgrade = TurtleItem.getUpgradeWithData(stack, TurtleSide.RIGHT);
var overlay = TurtleItem.getOverlay(stack);
var label = DataComponentUtil.getCustomName(stack);
var flip = label != null && (label.equals("Dinnerbone") || label.equals("Grumm"));
return new Combination(colour != -1, leftUpgrade, rightUpgrade, overlay, christmas, flip);
return new Combination(stack.has(DataComponents.DYED_COLOR), leftUpgrade, rightUpgrade, overlay, christmas, flip);
}
private List<BakedModel> buildModel(Combination combo) {

View File

@ -4,10 +4,10 @@
package dan200.computercraft.client.network;
import dan200.computercraft.client.platform.ClientPlatformHelper;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.server.ServerNetworkContext;
import net.minecraft.client.Minecraft;
import net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket;
/**
* Methods for sending packets from clients to the server.
@ -23,6 +23,6 @@ private ClientNetworking() {
*/
public static void sendToServer(NetworkMessage<ServerNetworkContext> message) {
var connection = Minecraft.getInstance().getConnection();
if (connection != null) connection.send(ClientPlatformHelper.get().createPacket(message));
if (connection != null) connection.send(new ServerboundCustomPayloadPacket(message));
}
}

View File

@ -5,13 +5,9 @@
package dan200.computercraft.client.platform;
import com.mojang.blaze3d.vertex.PoseStack;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.server.ServerNetworkContext;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.core.BlockPos;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.common.ServerCommonPacketListener;
import net.minecraft.sounds.SoundEvent;
import javax.annotation.Nullable;
@ -21,14 +17,6 @@ static ClientPlatformHelper get() {
return (ClientPlatformHelper) dan200.computercraft.impl.client.ClientPlatformHelper.get();
}
/**
* Convert a serverbound {@link NetworkMessage} to a Minecraft {@link Packet}.
*
* @param message The messsge to convert.
* @return The converted message.
*/
Packet<ServerCommonPacketListener> createPacket(NetworkMessage<ServerNetworkContext> message);
/**
* Render a {@link BakedModel}, using any loader-specific hooks.
*

View File

@ -4,11 +4,11 @@
package dan200.computercraft.client.pocket;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.computer.core.ComputerState;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.terminal.TerminalState;
import dan200.computercraft.shared.network.client.PocketComputerDataMessage;
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
import net.minecraft.world.item.ItemStack;
import javax.annotation.Nullable;
@ -53,7 +53,7 @@ public static void setState(UUID instanceId, ComputerState state, int lightColou
}
public static @Nullable PocketComputerData get(ItemStack stack) {
var id = PocketComputerItem.getInstanceID(stack);
return id == null ? null : instances.get(id);
var id = stack.get(ModRegistry.DataComponents.COMPUTER.get());
return id == null ? null : instances.get(id.instance());
}
}

View File

@ -51,7 +51,6 @@ public static boolean drawHighlight(PoseStack transform, MultiBufferSource buffe
var buffer = bufferSource.getBuffer(RenderType.lines());
var matrix4f = transform.last().pose();
var normal = transform.last().normal();
// TODO: Can we just accesstransformer out LevelRenderer.renderShape?
shape.forAllEdges((x1, y1, z1, x2, y2, z2) -> {
var xDelta = (float) (x2 - x1);
@ -65,12 +64,12 @@ public static boolean drawHighlight(PoseStack transform, MultiBufferSource buffe
buffer
.vertex(matrix4f, (float) (x1 + xOffset), (float) (y1 + yOffset), (float) (z1 + zOffset))
.color(0, 0, 0, 0.4f)
.normal(normal, xDelta, yDelta, zDelta)
.normal(transform.last(), xDelta, yDelta, zDelta)
.endVertex();
buffer
.vertex(matrix4f, (float) (x2 + xOffset), (float) (y2 + yOffset), (float) (z2 + zOffset))
.color(0, 0, 0, 0.4f)
.normal(normal, xDelta, yDelta, zDelta)
.normal(transform.last(), xDelta, yDelta, zDelta)
.endVertex();
});

View File

@ -15,6 +15,7 @@
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.component.DyedItemColor;
import org.joml.Matrix4f;
import static dan200.computercraft.client.render.ComputerBorderRenderer.*;
@ -61,7 +62,7 @@ protected void renderItem(PoseStack transform, MultiBufferSource bufferSource, I
// Render the main frame
var item = (PocketComputerItem) stack.getItem();
var family = item.getFamily();
var frameColour = item.getColour(stack);
var frameColour = DyedItemColor.getOrDefault(stack, -1);
var matrix = transform.last().pose();
renderFrame(matrix, bufferSource, family, frameColour, light, width, height);

View File

@ -6,6 +6,8 @@
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Axis;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.media.items.PrintoutData;
import dan200.computercraft.shared.media.items.PrintoutItem;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.world.entity.EntityType;
@ -37,8 +39,6 @@ protected void renderItem(PoseStack transform, MultiBufferSource render, ItemSta
}
public static void onRenderInFrame(PoseStack transform, MultiBufferSource render, ItemFrame frame, ItemStack stack, int packedLight) {
if (!(stack.getItem() instanceof PrintoutItem)) return;
// Move a little bit forward to ensure we're not clipping with the frame
transform.translate(0.0f, 0.0f, -0.001f);
transform.mulPose(Axis.ZP.rotationDegrees(180f));
@ -50,7 +50,9 @@ public static void onRenderInFrame(PoseStack transform, MultiBufferSource render
}
private static void drawPrintout(PoseStack transform, MultiBufferSource render, ItemStack stack, int light) {
var pages = PrintoutItem.getPageCount(stack);
var pageData = stack.getOrDefault(ModRegistry.DataComponents.PRINTOUT.get(), PrintoutData.EMPTY);
var pages = pageData.pages();
var book = ((PrintoutItem) stack.getItem()).getType() == PrintoutItem.Type.BOOK;
double width = LINE_MAX_LENGTH * FONT_WIDTH + X_TEXT_MARGIN * 2;
@ -75,9 +77,6 @@ private static void drawPrintout(PoseStack transform, MultiBufferSource render,
transform.translate((max - width) / 2.0, (max - height) / 2.0, 0.0);
drawBorder(transform, render, 0, 0, -0.01f, 0, pages, book, light);
drawText(
transform, render, X_TEXT_MARGIN, Y_TEXT_MARGIN, 0, light,
PrintoutItem.getText(stack), PrintoutItem.getColours(stack)
);
drawText(transform, render, X_TEXT_MARGIN, Y_TEXT_MARGIN, 0, light, pageData.lines());
}
}

View File

@ -9,9 +9,12 @@
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
import dan200.computercraft.core.terminal.Palette;
import dan200.computercraft.core.terminal.TextBuffer;
import dan200.computercraft.shared.media.items.PrintoutData;
import net.minecraft.client.renderer.MultiBufferSource;
import org.joml.Matrix4f;
import java.util.List;
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_HEIGHT;
import static dan200.computercraft.shared.media.items.PrintoutItem.LINES_PER_PAGE;
@ -69,13 +72,14 @@ public static void drawText(PoseStack transform, MultiBufferSource bufferSource,
}
}
public static void drawText(PoseStack transform, MultiBufferSource bufferSource, int x, int y, int start, int light, String[] text, String[] colours) {
public static void drawText(PoseStack transform, MultiBufferSource bufferSource, int x, int y, int start, int light, List<PrintoutData.Line> lines) {
var buffer = bufferSource.getBuffer(RenderTypes.PRINTOUT_TEXT);
var emitter = FixedWidthFontRenderer.toVertexConsumer(transform, buffer);
for (var line = 0; line < LINES_PER_PAGE && line < text.length; line++) {
for (var line = 0; line < LINES_PER_PAGE && line < lines.size(); line++) {
var lineContents = lines.get(start + line);
FixedWidthFontRenderer.drawString(emitter,
x, y + line * FONT_HEIGHT,
new TextBuffer(text[start + line]), new TextBuffer(colours[start + line]),
new TextBuffer(lineContents.text()), new TextBuffer(lineContents.foreground()),
Palette.DEFAULT, light
);
}

View File

@ -191,17 +191,23 @@ private static void renderTerminal(
});
}
// Our VBO doesn't transform its vertices with the provided pose stack, which means that the inverse view
// rotation matrix gives entirely wrong numbers for fog distances. We just set it to the identity which
// gives a good enough approximation.
var oldInverseRotation = RenderSystem.getInverseViewRotationMatrix();
RenderSystem.setInverseViewRotationMatrix(IDENTITY_NORMAL);
// Our VBO renders coordinates in monitor-space rather than world space. A full sized monitor (8x6) will
// use positions from (0, 0) to (164*FONT_WIDTH, 81*FONT_HEIGHT) = (984, 729). This is far outside the
// normal render distance (~200), and the edges of the monitor fade out due to fog.
// There's not really a good way around this, at least without using a custom render type (which the VBO
// renderer is trying to avoid!). Instead, we just disable fog entirely by setting the fog start to an
// absurdly high value.
var oldFogStart = RenderSystem.getShaderFogStart();
RenderSystem.setShaderFogStart(1e4f);
RenderTypes.TERMINAL.setupRenderState();
// Compose the existing model view matrix with our transformation matrix.
var modelView = new Matrix4f(RenderSystem.getModelViewMatrix()).mul(matrix);
// Render background geometry
backgroundBuffer.bind();
backgroundBuffer.drawWithShader(matrix, RenderSystem.getProjectionMatrix(), RenderTypes.getTerminalShader());
backgroundBuffer.drawWithShader(modelView, RenderSystem.getProjectionMatrix(), RenderTypes.getTerminalShader());
// Render foreground geometry with glPolygonOffset enabled.
RenderSystem.polygonOffset(-1.0f, -10.0f);
@ -209,7 +215,7 @@ private static void renderTerminal(
foregroundBuffer.bind();
foregroundBuffer.drawWithShader(
matrix, RenderSystem.getProjectionMatrix(), RenderTypes.getTerminalShader(),
modelView, RenderSystem.getProjectionMatrix(), RenderTypes.getTerminalShader(),
// As mentioned in the above comment, render the extra cursor quad if it is visible this frame. Each
// // quad has an index count of 6.
FixedWidthFontRenderer.isCursorVisible(terminal) && FrameInfo.getGlobalCursorBlink()
@ -222,7 +228,7 @@ private static void renderTerminal(
RenderTypes.TERMINAL.clearRenderState();
VertexBuffer.unbind();
RenderSystem.setInverseViewRotationMatrix(oldInverseRotation);
RenderSystem.setShaderFogStart(oldFogStart);
}
case BEST -> throw new IllegalStateException("Impossible: Should never use BEST renderer");
}

View File

@ -12,7 +12,6 @@
import net.minecraft.client.renderer.RenderType;
import net.minecraft.core.Direction;
import net.minecraft.world.phys.BlockHitResult;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import java.util.EnumSet;
@ -53,7 +52,7 @@ public static boolean drawHighlight(PoseStack transformStack, MultiBufferSource
// I wish I could think of a better way to do this
var buffer = bufferSource.getBuffer(RenderType.lines());
var transform = transformStack.last().pose();
var normal = transformStack.last().normal();
var normal = transformStack.last();
if (faces.contains(NORTH) || faces.contains(WEST)) line(buffer, transform, normal, 0, 0, 0, UP);
if (faces.contains(SOUTH) || faces.contains(WEST)) line(buffer, transform, normal, 0, 0, 1, UP);
if (faces.contains(NORTH) || faces.contains(EAST)) line(buffer, transform, normal, 1, 0, 0, UP);
@ -71,7 +70,7 @@ public static boolean drawHighlight(PoseStack transformStack, MultiBufferSource
return true;
}
private static void line(VertexConsumer buffer, Matrix4f transform, Matrix3f normal, float x, float y, float z, Direction direction) {
private static void line(VertexConsumer buffer, Matrix4f transform, PoseStack.Pose normal, float x, float y, float z, Direction direction) {
buffer
.vertex(transform, x, y, z)
.color(0, 0, 0, 0.4f)

View File

@ -9,8 +9,9 @@
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.turtle.upgrades.TurtleModem;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.core.component.DataComponentPatch;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.Nullable;
@ -41,12 +42,9 @@ public TurtleModemModeller(boolean advanced) {
}
@Override
public TransformedModel getModel(TurtleModem upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, CompoundTag data) {
var active = false;
if (turtle != null) {
var turtleNBT = turtle.getUpgradeNBTData(side);
active = turtleNBT.contains("active") && turtleNBT.getBoolean("active");
}
public TransformedModel getModel(TurtleModem upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, DataComponentPatch data) {
var component = data.get(ModRegistry.DataComponents.ON.get());
var active = component != null && component.isPresent() && component.get();
return side == TurtleSide.LEFT
? TransformedModel.of(active ? leftOnModel : leftOffModel)

View File

@ -15,7 +15,7 @@
import dan200.computercraft.impl.TurtleUpgrades;
import dan200.computercraft.impl.UpgradeManager;
import net.minecraft.client.Minecraft;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.core.component.DataComponentPatch;
import net.minecraft.resources.ResourceLocation;
import java.util.Map;
@ -60,10 +60,10 @@ public static <T extends ITurtleUpgrade> void register(UpgradeSerialiser<T> seri
public static TransformedModel getModel(ITurtleUpgrade upgrade, ITurtleAccess access, TurtleSide side) {
@SuppressWarnings("unchecked")
var modeller = (TurtleUpgradeModeller<ITurtleUpgrade>) modelCache.computeIfAbsent(upgrade, TurtleUpgradeModellers::getModeller);
return modeller.getModel(upgrade, access, side, access.getUpgradeNBTData(side));
return modeller.getModel(upgrade, access, side, access.getUpgradeData(side));
}
public static TransformedModel getModel(ITurtleUpgrade upgrade, CompoundTag data, TurtleSide side) {
public static TransformedModel getModel(ITurtleUpgrade upgrade, DataComponentPatch data, TurtleSide side) {
@SuppressWarnings("unchecked")
var modeller = (TurtleUpgradeModeller<ITurtleUpgrade>) modelCache.computeIfAbsent(upgrade, TurtleUpgradeModellers::getModeller);
return modeller.getModel(upgrade, null, side, data);

View File

@ -5,6 +5,7 @@
package dan200.computercraft.data;
import com.mojang.serialization.Codec;
import net.minecraft.core.HolderLookup;
import net.minecraft.data.DataProvider;
import net.minecraft.data.PackOutput;
import net.minecraft.data.loot.LootTableProvider.SubProviderEntry;
@ -17,7 +18,9 @@
import net.minecraft.world.level.block.Block;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
/**
@ -31,7 +34,7 @@ private DataProviders() {
public static void add(GeneratorSink generator) {
var turtleUpgrades = generator.add(TurtleUpgradeProvider::new);
var pocketUpgrades = generator.add(PocketUpgradeProvider::new);
generator.add(out -> new RecipeProvider(out, turtleUpgrades, pocketUpgrades));
generator.add((out, registries) -> new RecipeProvider(out, registries, turtleUpgrades, pocketUpgrades));
var blockTags = generator.blockTags(TagProvider::blockTags);
generator.itemTags(TagProvider::itemTags, blockTags);
@ -55,6 +58,8 @@ public static void add(GeneratorSink generator) {
public interface GeneratorSink {
<T extends DataProvider> T add(DataProvider.Factory<T> factory);
<T extends DataProvider> T add(BiFunction<PackOutput, CompletableFuture<HolderLookup.Provider>, T> factory);
<T> void addFromCodec(String name, PackType type, String directory, Codec<T> codec, Consumer<BiConsumer<ResourceLocation, T>> output);
void lootTable(List<SubProviderEntry> tables);

View File

@ -13,7 +13,9 @@
import dan200.computercraft.shared.peripheral.modem.wired.CableBlock;
import dan200.computercraft.shared.peripheral.modem.wired.CableModemVariant;
import net.minecraft.advancements.critereon.StatePropertiesPredicate;
import net.minecraft.core.HolderLookup;
import net.minecraft.data.loot.LootTableProvider.SubProviderEntry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.storage.loot.LootPool;
@ -41,7 +43,7 @@ public static List<SubProviderEntry> getTables() {
);
}
private static void registerBlocks(BiConsumer<ResourceLocation, LootTable.Builder> add) {
private static void registerBlocks(HolderLookup.Provider registries, BiConsumer<ResourceKey<LootTable>, LootTable.Builder> add) {
namedBlockDrop(add, ModRegistry.Blocks.DISK_DRIVE);
selfDrop(add, ModRegistry.Blocks.MONITOR_NORMAL);
selfDrop(add, ModRegistry.Blocks.MONITOR_ADVANCED);
@ -78,15 +80,15 @@ private static void registerBlocks(BiConsumer<ResourceLocation, LootTable.Builde
));
}
private static void registerGeneric(BiConsumer<ResourceLocation, LootTable.Builder> add) {
private static void registerGeneric(HolderLookup.Provider registries, BiConsumer<ResourceKey<LootTable>, LootTable.Builder> add) {
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<ResourceKey<LootTable>, LootTable.Builder> add, Supplier<? extends Block> wrapper) {
blockDrop(add, wrapper, LootItem.lootTableItem(wrapper.get()), ExplosionCondition.survivesExplosion());
}
private static void namedBlockDrop(BiConsumer<ResourceLocation, LootTable.Builder> add, Supplier<? extends Block> wrapper) {
private static void namedBlockDrop(BiConsumer<ResourceKey<LootTable>, LootTable.Builder> add, Supplier<? extends Block> wrapper) {
blockDrop(
add, wrapper,
LootItem.lootTableItem(wrapper.get()).apply(CopyNameFunction.copyName(CopyNameFunction.NameSource.BLOCK_ENTITY)),
@ -94,7 +96,7 @@ private static void namedBlockDrop(BiConsumer<ResourceLocation, LootTable.Builde
);
}
private static void computerDrop(BiConsumer<ResourceLocation, LootTable.Builder> add, Supplier<? extends Block> block) {
private static void computerDrop(BiConsumer<ResourceKey<LootTable>, LootTable.Builder> add, Supplier<? extends Block> block) {
blockDrop(
add, block,
DynamicLoot.dynamicEntry(new ResourceLocation(ComputerCraftAPI.MOD_ID, "computer")),
@ -107,7 +109,7 @@ private static void computerDrop(BiConsumer<ResourceLocation, LootTable.Builder>
}
private static void blockDrop(
BiConsumer<ResourceLocation, LootTable.Builder> add, Supplier<? extends Block> wrapper,
BiConsumer<ResourceKey<LootTable>, LootTable.Builder> add, Supplier<? extends Block> wrapper,
LootPoolEntryContainer.Builder<?> drop,
LootItemCondition.Builder condition
) {

View File

@ -5,7 +5,6 @@
package dan200.computercraft.data;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.mojang.authlib.GameProfile;
import com.mojang.serialization.JsonOps;
import dan200.computercraft.api.ComputerCraftAPI;
@ -19,26 +18,25 @@
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.common.ClearColourRecipe;
import dan200.computercraft.shared.common.ColourableRecipe;
import dan200.computercraft.shared.computer.recipe.ComputerUpgradeRecipe;
import dan200.computercraft.shared.media.items.DiskItem;
import dan200.computercraft.shared.computer.recipe.ComputerConvertRecipe;
import dan200.computercraft.shared.media.recipes.DiskRecipe;
import dan200.computercraft.shared.media.recipes.PrintoutRecipe;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.platform.RecipeIngredients;
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
import dan200.computercraft.shared.pocket.recipes.PocketComputerUpgradeRecipe;
import dan200.computercraft.shared.recipe.CustomShapelessRecipe;
import dan200.computercraft.shared.recipe.ImpostorShapedRecipe;
import dan200.computercraft.shared.recipe.ImpostorShapelessRecipe;
import dan200.computercraft.shared.turtle.items.TurtleItem;
import dan200.computercraft.shared.turtle.recipes.TurtleOverlayRecipe;
import dan200.computercraft.shared.turtle.recipes.TurtleRecipe;
import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe;
import dan200.computercraft.shared.util.ColourUtils;
import net.minecraft.Util;
import dan200.computercraft.shared.util.DataComponentUtil;
import net.minecraft.advancements.Criterion;
import net.minecraft.advancements.critereon.InventoryChangeTrigger;
import net.minecraft.advancements.critereon.ItemPredicate;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.data.PackOutput;
@ -46,12 +44,13 @@
import net.minecraft.data.recipes.RecipeOutput;
import net.minecraft.data.recipes.ShapedRecipeBuilder;
import net.minecraft.data.recipes.ShapelessRecipeBuilder;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.ItemTags;
import net.minecraft.tags.TagKey;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.item.*;
import net.minecraft.world.item.component.DyedItemColor;
import net.minecraft.world.item.component.ResolvableProfile;
import net.minecraft.world.item.crafting.CraftingBookCategory;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.crafting.Recipe;
@ -60,6 +59,7 @@
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import static dan200.computercraft.api.ComputerCraftTags.Items.COMPUTER;
@ -70,8 +70,8 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
private final TurtleUpgradeDataProvider turtleUpgrades;
private final PocketUpgradeDataProvider pocketUpgrades;
RecipeProvider(PackOutput output, TurtleUpgradeDataProvider turtleUpgrades, PocketUpgradeDataProvider pocketUpgrades) {
super(output);
RecipeProvider(PackOutput output, CompletableFuture<HolderLookup.Provider> registries, TurtleUpgradeDataProvider turtleUpgrades, PocketUpgradeDataProvider pocketUpgrades) {
super(output, registries);
this.turtleUpgrades = turtleUpgrades;
this.pocketUpgrades = pocketUpgrades;
}
@ -100,7 +100,7 @@ public void buildRecipes(RecipeOutput add) {
private void diskColours(RecipeOutput output) {
for (var colour : Colour.VALUES) {
ShapelessSpecBuilder
.shapeless(RecipeCategory.REDSTONE, DiskItem.createFromIDAndColour(-1, null, colour.getHex()))
.shapeless(RecipeCategory.REDSTONE, DataComponentUtil.createStack(ModRegistry.Items.DISK.get(), DataComponents.DYED_COLOR, new DyedItemColor(colour.getHex(), false)))
.requires(ingredients.redstone())
.requires(Items.PAPER)
.requires(DyeItem.byColor(ofColour(colour)))
@ -122,17 +122,16 @@ private static List<TurtleItem> turtleItems() {
*/
private void turtleUpgrades(RecipeOutput add) {
for (var turtleItem : turtleItems()) {
var base = turtleItem.create(-1, null, -1, null, null, 0, null);
var name = RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, turtleItem);
for (var upgrade : turtleUpgrades.getGeneratedUpgrades()) {
ShapedSpecBuilder
.shaped(RecipeCategory.REDSTONE, turtleItem.create(-1, null, -1, null, UpgradeData.ofDefault(upgrade), -1, null))
.shaped(RecipeCategory.REDSTONE, DataComponentUtil.createStack(turtleItem, ModRegistry.DataComponents.RIGHT_TURTLE_UPGRADE.get(), UpgradeData.ofDefault(upgrade)))
.group(name.toString())
.pattern("#T")
.define('T', base.getItem())
.define('T', turtleItem)
.define('#', upgrade.getCraftingItem().getItem())
.unlockedBy("has_items", inventoryChange(base.getItem(), upgrade.getCraftingItem().getItem()))
.unlockedBy("has_items", inventoryChange(turtleItem, upgrade.getCraftingItem().getItem()))
.build(ImpostorShapedRecipe::new)
.save(
add,
@ -153,18 +152,17 @@ private static List<PocketComputerItem> pocketComputerItems() {
*/
private void pocketUpgrades(RecipeOutput add) {
for (var pocket : pocketComputerItems()) {
var base = pocket.create(-1, null, -1, null);
var name = RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, pocket).withPath(x -> x.replace("pocket_computer_", "pocket_"));
for (var upgrade : pocketUpgrades.getGeneratedUpgrades()) {
ShapedSpecBuilder
.shaped(RecipeCategory.REDSTONE, pocket.create(-1, null, -1, UpgradeData.ofDefault(upgrade)))
.shaped(RecipeCategory.REDSTONE, DataComponentUtil.createStack(pocket, ModRegistry.DataComponents.POCKET_UPGRADE.get(), UpgradeData.ofDefault(upgrade)))
.group(name.toString())
.pattern("#")
.pattern("P")
.define('P', base.getItem())
.define('P', pocket)
.define('#', upgrade.getCraftingItem().getItem())
.unlockedBy("has_items", inventoryChange(base.getItem(), upgrade.getCraftingItem().getItem()))
.unlockedBy("has_items", inventoryChange(pocket, upgrade.getCraftingItem().getItem()))
.build(ImpostorShapedRecipe::new)
.save(
add,
@ -197,15 +195,14 @@ private void turtleOverlays(RecipeOutput add) {
private void turtleOverlay(RecipeOutput add, String overlay, Consumer<ShapelessSpecBuilder> build) {
for (var turtleItem : turtleItems()) {
var base = turtleItem.create(-1, null, -1, null, null, 0, null);
var name = RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, turtleItem);
var builder = ShapelessSpecBuilder.shapeless(RecipeCategory.REDSTONE, base)
var builder = ShapelessSpecBuilder.shapeless(RecipeCategory.REDSTONE, new ItemStack(turtleItem))
.group(name.withSuffix("_overlay").toString())
.unlockedBy("has_turtle", inventoryChange(base.getItem()));
.unlockedBy("has_turtle", inventoryChange(turtleItem));
build.accept(builder);
builder
.requires(base.getItem())
.requires(turtleItem)
.build(s -> new TurtleOverlayRecipe(s, new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/" + overlay)))
.save(add, name.withSuffix("_overlays/" + overlay));
}
@ -254,7 +251,7 @@ private void basicRecipes(RecipeOutput add) {
.define('#', ingredients.goldIngot())
.define('C', ModRegistry.Items.COMPUTER_NORMAL.get())
.unlockedBy("has_components", inventoryChange(itemPredicate(ModRegistry.Items.COMPUTER_NORMAL.get()), itemPredicate(ingredients.goldIngot())))
.build(ComputerUpgradeRecipe::new)
.build(ComputerConvertRecipe::new)
.save(add, new ResourceLocation(ComputerCraftAPI.MOD_ID, "computer_advanced_upgrade"));
ShapedRecipeBuilder
@ -277,7 +274,7 @@ private void basicRecipes(RecipeOutput add) {
.define('C', ModRegistry.Items.COMPUTER_NORMAL.get())
.define('I', ingredients.woodenChest())
.unlockedBy("has_computer", inventoryChange(ModRegistry.Items.COMPUTER_NORMAL.get()))
.buildOrThrow(TurtleRecipe::of)
.build(ComputerConvertRecipe::new)
.save(add);
ShapedSpecBuilder
@ -289,7 +286,7 @@ private void basicRecipes(RecipeOutput add) {
.define('C', ModRegistry.Items.COMPUTER_ADVANCED.get())
.define('I', ingredients.woodenChest())
.unlockedBy("has_computer", inventoryChange(ModRegistry.Items.COMPUTER_NORMAL.get()))
.buildOrThrow(TurtleRecipe::of)
.build(ComputerConvertRecipe::new)
.save(add);
ShapedSpecBuilder
@ -301,7 +298,7 @@ private void basicRecipes(RecipeOutput add) {
.define('C', ModRegistry.Items.TURTLE_NORMAL.get())
.define('B', ingredients.goldBlock())
.unlockedBy("has_components", inventoryChange(itemPredicate(ModRegistry.Items.TURTLE_NORMAL.get()), itemPredicate(ingredients.goldIngot())))
.build(ComputerUpgradeRecipe::new)
.build(ComputerConvertRecipe::new)
.save(add, new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_advanced_upgrade"));
ShapedRecipeBuilder
@ -366,7 +363,7 @@ private void basicRecipes(RecipeOutput add) {
.define('#', ingredients.goldIngot())
.define('C', ModRegistry.Items.POCKET_COMPUTER_NORMAL.get())
.unlockedBy("has_components", inventoryChange(itemPredicate(ModRegistry.Items.POCKET_COMPUTER_NORMAL.get()), itemPredicate(ingredients.goldIngot())))
.build(ComputerUpgradeRecipe::new)
.build(ComputerConvertRecipe::new)
.save(add, new ResourceLocation(ComputerCraftAPI.MOD_ID, "pocket_computer_advanced_upgrade"));
ShapedRecipeBuilder
@ -436,18 +433,18 @@ private void basicRecipes(RecipeOutput add) {
ShapelessSpecBuilder
.shapeless(RecipeCategory.DECORATIONS, playerHead("Cloudhunter", "6d074736-b1e9-4378-a99b-bd8777821c9c"))
.requires(ingredients.head())
.requires(ItemTags.SKULLS)
.requires(ModRegistry.Items.MONITOR_NORMAL.get())
.unlockedBy("has_monitor", inventoryChange(ModRegistry.Items.MONITOR_NORMAL.get()))
.build(CustomShapelessRecipe::new)
.build()
.save(add, new ResourceLocation(ComputerCraftAPI.MOD_ID, "skull_cloudy"));
ShapelessSpecBuilder
.shapeless(RecipeCategory.DECORATIONS, playerHead("dan200", "f3c8d69b-0776-4512-8434-d1b2165909eb"))
.requires(ingredients.head())
.requires(ItemTags.SKULLS)
.requires(ModRegistry.Items.COMPUTER_ADVANCED.get())
.unlockedBy("has_computer", inventoryChange(ModRegistry.Items.COMPUTER_ADVANCED.get()))
.build(CustomShapelessRecipe::new)
.build()
.save(add, new ResourceLocation(ComputerCraftAPI.MOD_ID, "skull_dan200"));
ShapelessSpecBuilder
@ -493,11 +490,11 @@ private static ItemPredicate itemPredicate(TagKey<Item> item) {
}
private static ItemPredicate itemPredicate(Ingredient ingredient) {
var json = Util.getOrThrow(Ingredient.CODEC_NONEMPTY.encodeStart(JsonOps.INSTANCE, ingredient), JsonParseException::new);
var json = Ingredient.CODEC_NONEMPTY.encodeStart(JsonOps.INSTANCE, ingredient).getOrThrow();
if (!(json instanceof JsonObject object)) throw new IllegalStateException("Unknown ingredient " + json);
if (object.has("item")) {
var item = Util.getOrThrow(ItemStack.ITEM_WITH_COUNT_CODEC.parse(JsonOps.INSTANCE, object), JsonParseException::new);
var item = ItemStack.SIMPLE_ITEM_CODEC.parse(JsonOps.INSTANCE, object).getOrThrow();
return itemPredicate(item.getItem());
} else if (object.has("tag")) {
return itemPredicate(TagKey.create(Registries.ITEM, new ResourceLocation(GsonHelper.getAsString(object, "tag"))));
@ -507,10 +504,7 @@ private static ItemPredicate itemPredicate(Ingredient ingredient) {
}
private static ItemStack playerHead(String name, String uuid) {
var item = new ItemStack(Items.PLAYER_HEAD);
var owner = NbtUtils.writeGameProfile(new CompoundTag(), new GameProfile(UUID.fromString(uuid), name));
item.getOrCreateTag().put(PlayerHeadItem.TAG_SKULL_OWNER, owner);
return item;
return DataComponentUtil.createStack(Items.PLAYER_HEAD, DataComponents.PROFILE, new ResolvableProfile(new GameProfile(UUID.fromString(uuid), name)));
}
private static void addSpecial(RecipeOutput add, Recipe<?> recipe) {

View File

@ -7,8 +7,8 @@
import dan200.computercraft.api.ComputerCraftTags;
import dan200.computercraft.impl.RegistryHelper;
import dan200.computercraft.shared.ModRegistry;
import net.minecraft.core.Registry;
import dan200.computercraft.shared.integration.ExternalModTags;
import net.minecraft.core.Registry;
import net.minecraft.data.tags.ItemTagsProvider;
import net.minecraft.data.tags.TagsProvider;
import net.minecraft.tags.BlockTags;
@ -97,6 +97,10 @@ public static void itemTags(ItemTagConsumer tags) {
tags.tag(ComputerCraftTags.Items.WIRED_MODEM).add(ModRegistry.Items.WIRED_MODEM.get(), ModRegistry.Items.WIRED_MODEM_FULL.get());
tags.copy(ComputerCraftTags.Blocks.MONITOR, ComputerCraftTags.Items.MONITOR);
tags.tag(ComputerCraftTags.Items.DYEABLE)
.addTag(ComputerCraftTags.Items.TURTLE)
.add(ModRegistry.Items.DISK.get(), ModRegistry.Items.POCKET_COMPUTER_NORMAL.get(), ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get());
tags.tag(ItemTags.PIGLIN_LOVED).add(
ModRegistry.Items.COMPUTER_ADVANCED.get(), ModRegistry.Items.TURTLE_ADVANCED.get(),
ModRegistry.Items.WIRELESS_MODEM_ADVANCED.get(), ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get(),

View File

@ -6,7 +6,6 @@
import com.mojang.serialization.DataResult;
import dan200.computercraft.shared.recipe.RecipeProperties;
import net.minecraft.Util;
import net.minecraft.advancements.AdvancementRequirements;
import net.minecraft.advancements.AdvancementRewards;
import net.minecraft.advancements.Criterion;
@ -90,7 +89,7 @@ public final FinishedRecipe build(Function<O, Recipe<?>> factory) {
* @return The "built" recipe.
*/
public final FinishedRecipe buildOrThrow(Function<O, DataResult<? extends Recipe<?>>> factory) {
return build(s -> Util.getOrThrow(factory.apply(s), IllegalStateException::new));
return build(s -> factory.apply(s).getOrThrow());
}
@SuppressWarnings("unchecked")

View File

@ -13,6 +13,7 @@
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.crafting.ShapelessRecipe;
import net.minecraft.world.level.ItemLike;
/**
@ -58,4 +59,8 @@ public ShapelessSpecBuilder requires(TagKey<Item> item) {
protected ShapelessRecipeSpec build(RecipeProperties properties) {
return new ShapelessRecipeSpec(properties, ingredients, result);
}
public FinishedRecipe build() {
return build(spec -> new ShapelessRecipe(spec.properties().group(), spec.properties().category(), spec.result(), spec.ingredients()));
}
}

View File

@ -5,12 +5,19 @@
package dan200.computercraft.impl;
import com.google.gson.*;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import dan200.computercraft.shared.platform.PlatformHelper;
import io.netty.buffer.ByteBuf;
import net.minecraft.core.Registry;
import net.minecraft.core.component.DataComponentPatch;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
@ -40,16 +47,46 @@ public class UpgradeManager<T extends UpgradeBase> extends SimpleJsonResourceRel
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
public record UpgradeWrapper<T extends UpgradeBase>(
String id, T upgrade, UpgradeSerialiser<? extends T> serialiser, String modId
ResourceLocation id, T upgrade, UpgradeSerialiser<? extends T> serialiser, String modId
) {
}
private final String kind;
private final ResourceKey<Registry<UpgradeSerialiser<? extends T>>> registry;
private Map<String, UpgradeWrapper<T>> current = Map.of();
private Map<ResourceLocation, UpgradeWrapper<T>> current = Map.of();
private Map<T, UpgradeWrapper<T>> currentWrappers = Map.of();
private final Codec<T> upgradeCodec = ResourceLocation.CODEC.flatXmap(
x -> {
var upgrade = get(x);
return upgrade == null ? DataResult.error(() -> "Unknown upgrade " + x) : DataResult.success(upgrade);
},
x -> DataResult.success(x.getUpgradeID())
);
private final Codec<UpgradeData<T>> fullCodec = RecordCodecBuilder.create(i -> i.group(
upgradeCodec.fieldOf("id").forGetter(UpgradeData::upgrade),
DataComponentPatch.CODEC.optionalFieldOf("components", DataComponentPatch.EMPTY).forGetter(UpgradeData::data)
).apply(i, UpgradeData::new));
private final Codec<UpgradeData<T>> codec = Codec.withAlternative(fullCodec, upgradeCodec, UpgradeData::ofDefault);
private final StreamCodec<ByteBuf, T> upgradeStreamCodec = ResourceLocation.STREAM_CODEC.map(
x -> {
var upgrade = get(x);
if (upgrade == null) throw new IllegalStateException("Unknown upgrade " + x);
return upgrade;
},
UpgradeBase::getUpgradeID
);
private final StreamCodec<RegistryFriendlyByteBuf, UpgradeData<T>> streamCodec = StreamCodec.composite(
upgradeStreamCodec, UpgradeData::upgrade,
DataComponentPatch.STREAM_CODEC, UpgradeData::data,
UpgradeData::new
);
public UpgradeManager(String kind, String path, ResourceKey<Registry<UpgradeSerialiser<? extends T>>> registry) {
super(GSON, path);
this.kind = kind;
@ -57,7 +94,7 @@ public UpgradeManager(String kind, String path, ResourceKey<Registry<UpgradeSeri
}
@Nullable
public T get(String id) {
public T get(ResourceLocation id) {
var wrapper = current.get(id);
return wrapper == null ? null : wrapper.upgrade();
}
@ -91,14 +128,22 @@ public Collection<T> getUpgrades() {
return currentWrappers.keySet();
}
public Map<String, UpgradeWrapper<T>> getUpgradeWrappers() {
public Map<ResourceLocation, UpgradeWrapper<T>> getUpgradeWrappers() {
return current;
}
public Codec<UpgradeData<T>> codec() {
return codec;
}
public StreamCodec<RegistryFriendlyByteBuf, UpgradeData<T>> streamCodec() {
return streamCodec;
}
@Override
protected void apply(Map<ResourceLocation, JsonElement> upgrades, ResourceManager manager, ProfilerFiller profiler) {
var registry = RegistryHelper.getRegistry(this.registry);
Map<String, UpgradeWrapper<T>> newUpgrades = new HashMap<>();
Map<ResourceLocation, UpgradeWrapper<T>> newUpgrades = new HashMap<>();
for (var element : upgrades.entrySet()) {
try {
loadUpgrade(registry, newUpgrades, element.getKey(), element.getValue());
@ -112,7 +157,7 @@ protected void apply(Map<ResourceLocation, JsonElement> upgrades, ResourceManage
LOG.info("Loaded {} {}s", current.size(), kind);
}
private void loadUpgrade(Registry<UpgradeSerialiser<? extends T>> registry, Map<String, UpgradeWrapper<T>> current, ResourceLocation id, JsonElement json) {
private void loadUpgrade(Registry<UpgradeSerialiser<? extends T>> registry, Map<ResourceLocation, UpgradeWrapper<T>> current, ResourceLocation id, JsonElement json) {
var root = GsonHelper.convertToJsonObject(json, "top element");
if (!PlatformHelper.get().shouldLoadResource(root)) return;
@ -130,11 +175,11 @@ private void loadUpgrade(Registry<UpgradeSerialiser<? extends T>> registry, Map<
throw new IllegalArgumentException("Upgrade " + id + " from " + serialiser + " was incorrectly given id " + upgrade.getUpgradeID());
}
var result = new UpgradeWrapper<T>(id.toString(), upgrade, serialiser, modId);
var result = new UpgradeWrapper<T>(id, upgrade, serialiser, modId);
current.put(result.id(), result);
}
public void loadFromNetwork(Map<String, UpgradeWrapper<T>> newUpgrades) {
public void loadFromNetwork(Map<ResourceLocation, UpgradeWrapper<T>> newUpgrades) {
current = Collections.unmodifiableMap(newUpgrades);
currentWrappers = newUpgrades.values().stream().collect(Collectors.toUnmodifiableMap(UpgradeWrapper::upgrade, x -> x));
}

View File

@ -0,0 +1,49 @@
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.mixin;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import com.mojang.datafixers.DataFix;
import com.mojang.datafixers.TypeRewriteRule;
import com.mojang.datafixers.schemas.Schema;
import com.mojang.serialization.Dynamic;
import dan200.computercraft.shared.util.ComponentizationFixers;
import net.minecraft.util.datafix.fixes.ItemStackComponentizationFix;
import net.minecraft.util.datafix.fixes.References;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/**
* Migrates CC's item NBT to use components.
*
* @see V3818_3Mixin
* @see ComponentizationFixers
*/
@Mixin(ItemStackComponentizationFix.class)
abstract class ItemStackComponentizationFixMixin extends DataFix {
@SuppressWarnings("UnusedMethod")
private ItemStackComponentizationFixMixin(Schema outputSchema, boolean changesType) {
super(outputSchema, changesType);
}
@Inject(method = "fixItemStack", at = @At("TAIL"))
@SuppressWarnings("UnusedMethod")
private static void fixItemStack(ItemStackComponentizationFix.ItemStackData data, Dynamic<?> ops, CallbackInfo ci) {
ComponentizationFixers.fixItemComponents(data, ops);
}
@ModifyReturnValue(method = "makeRule", at = @At("RETURN"), remap = false)
@SuppressWarnings("UnusedMethod")
private TypeRewriteRule wrapMakeRule(TypeRewriteRule existing) {
return TypeRewriteRule.seq(existing, fixTypeEverywhereTyped(
"Turtle upgrade componentization",
getInputSchema().getType(References.BLOCK_ENTITY),
getOutputSchema().getType(References.BLOCK_ENTITY),
ComponentizationFixers.makeBlockEntityRewrites(getInputSchema(), getOutputSchema())
));
}
}

View File

@ -7,16 +7,17 @@
import com.mojang.datafixers.DSL;
import com.mojang.datafixers.schemas.Schema;
import com.mojang.datafixers.types.templates.TypeTemplate;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveBlockEntity;
import dan200.computercraft.shared.peripheral.printer.PrinterBlockEntity;
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
import net.minecraft.util.datafix.fixes.References;
import net.minecraft.util.datafix.schemas.NamespacedSchema;
import net.minecraft.util.datafix.schemas.V1460;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Map;
@ -37,16 +38,34 @@ private void registerBlockEntities(Schema schema, CallbackInfoReturnable<Map<Str
var map = ci.getReturnValue();
// Basic inventories
registerInventory(schema, map, ModRegistry.BlockEntities.TURTLE_NORMAL.id().toString());
registerInventory(schema, map, ModRegistry.BlockEntities.TURTLE_ADVANCED.id().toString());
registerInventory(schema, map, ModRegistry.BlockEntities.PRINTER.id().toString());
registerTurtle(schema, map, "computercraft:turtle_normal");
registerTurtle(schema, map, "computercraft:turtle_advanced");
registerInventory(schema, map, "computercraft:printer");
// Disk drives contain a single item
schema.register(map, ModRegistry.BlockEntities.DISK_DRIVE.id().toString(), () -> DSL.optionalFields(
schema.register(map, "computercraft:disk_drive", () -> DSL.optionalFields(
"Item", References.ITEM_STACK.in(schema)
));
}
private static TypeTemplate upgradeData(Schema schema) {
return DSL.or(
// Pre-1.20.5 we just use the upgrade ID.
DSL.constType(NamespacedSchema.namespacedString()),
// In newer versions this is represented as a component.
DSL.optionalFields("components", References.DATA_COMPONENTS.in(schema))
);
}
@Unique
private static void registerTurtle(Schema schema, Map<String, Supplier<TypeTemplate>> map, String name) {
schema.register(map, name, () -> DSL.optionalFields(
"LeftUpgrade", upgradeData(schema),
"RightUpgrade", upgradeData(schema),
"Items", DSL.list(References.ITEM_STACK.in(schema))
));
}
@Shadow
protected static void registerInventory(Schema schema, Map<String, Supplier<TypeTemplate>> map, String name) {
}

View File

@ -0,0 +1,57 @@
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.mixin;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import com.mojang.datafixers.DSL;
import com.mojang.datafixers.schemas.Schema;
import com.mojang.datafixers.types.templates.TypeTemplate;
import com.mojang.datafixers.util.Pair;
import dan200.computercraft.impl.UpgradeManager;
import dan200.computercraft.shared.ModRegistry.DataComponents;
import net.minecraft.util.datafix.fixes.References;
import net.minecraft.util.datafix.schemas.V3818_3;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import java.util.Arrays;
import java.util.stream.Stream;
/**
* Add our custom data components to the datafixer system.
*
* @see UpgradeManager#codec()
* @see DataComponents#POCKET_UPGRADE
* @see DataComponents#LEFT_TURTLE_UPGRADE
* @see DataComponents#RIGHT_TURTLE_UPGRADE
* @see ItemStackComponentizationFixMixin
*/
@Mixin(V3818_3.class)
class V3818_3Mixin {
@ModifyReturnValue(
method = "method_57277",
at = @At("TAIL")
)
@SuppressWarnings("UnusedMethod")
private static TypeTemplate addExtraTypes(TypeTemplate type, Schema schema) {
// Create a codec for UpgradeData
var upgradeData = DSL.optionalFields("components", References.DATA_COMPONENTS.in(schema));
return extraOptionalFields(type,
Pair.of("computercraft:upgrade", upgradeData),
Pair.of("computercraft:left_upgrade", upgradeData),
Pair.of("computercraft:right_upgrade", upgradeData)
);
}
@SafeVarargs
@SuppressWarnings("varargs")
private static TypeTemplate extraOptionalFields(TypeTemplate base, Pair<String, TypeTemplate>... fields) {
return DSL.and(Stream.concat(
Arrays.stream(fields).map(entry -> DSL.optional(DSL.field(entry.getFirst(), entry.getSecond()))),
Stream.of(base)
).toList());
}
}

View File

@ -14,6 +14,7 @@
import dan200.computercraft.shared.peripheral.monitor.MonitorWatcher;
import dan200.computercraft.shared.util.DropConsumer;
import dan200.computercraft.shared.util.TickScheduler;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
@ -28,7 +29,8 @@
import net.minecraft.world.level.chunk.LevelChunk;
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.LootTable;
import net.minecraft.world.level.storage.loot.entries.NestedLootTable;
import net.minecraft.world.level.storage.loot.providers.number.ConstantValue;
import javax.annotation.Nullable;
@ -92,9 +94,9 @@ public static void onChunkTicketLevelChanged(ServerLevel level, long chunkPos, i
TickScheduler.onChunkTicketChanged(level, chunkPos, oldLevel, newLevel);
}
public static final ResourceLocation TREASURE_DISK_LOOT = new ResourceLocation(ComputerCraftAPI.MOD_ID, "treasure_disk");
public static final ResourceKey<LootTable> TREASURE_DISK_LOOT = ResourceKey.create(Registries.LOOT_TABLE, new ResourceLocation(ComputerCraftAPI.MOD_ID, "treasure_disk"));
private static final Set<ResourceLocation> TREASURE_DISK_LOOT_TABLES = Set.of(
private static final Set<ResourceKey<LootTable>> TREASURE_DISK_LOOT_TABLES = Set.of(
BuiltInLootTables.SIMPLE_DUNGEON,
BuiltInLootTables.ABANDONED_MINESHAFT,
BuiltInLootTables.STRONGHOLD_CORRIDOR,
@ -107,13 +109,13 @@ public static void onChunkTicketLevelChanged(ServerLevel level, long chunkPos, i
BuiltInLootTables.VILLAGE_CARTOGRAPHER
);
public static @Nullable LootPool.Builder getExtraLootPool(ResourceLocation lootTable) {
if (!lootTable.getNamespace().equals("minecraft") || !TREASURE_DISK_LOOT_TABLES.contains(lootTable)) {
public static @Nullable LootPool.Builder getExtraLootPool(ResourceKey<LootTable> lootTable) {
if (!TREASURE_DISK_LOOT_TABLES.contains(lootTable)) {
return null;
}
return LootPool.lootPool()
.add(LootTableReference.lootTableReference(TREASURE_DISK_LOOT))
.add(NestedLootTable.lootTableReference(TREASURE_DISK_LOOT))
.setRolls(ConstantValue.exactly(1));
}

View File

@ -6,6 +6,8 @@
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import com.mojang.serialization.MapCodec;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.detail.DetailProvider;
import dan200.computercraft.api.detail.VanillaDetailRegistries;
@ -33,8 +35,10 @@
import dan200.computercraft.shared.computer.inventory.ComputerMenuWithoutInventory;
import dan200.computercraft.shared.computer.inventory.ViewComputerMenu;
import dan200.computercraft.shared.computer.items.CommandComputerItem;
import dan200.computercraft.shared.computer.items.ComputerId;
import dan200.computercraft.shared.computer.items.ComputerItem;
import dan200.computercraft.shared.computer.recipe.ComputerUpgradeRecipe;
import dan200.computercraft.shared.computer.items.ServerComputerReference;
import dan200.computercraft.shared.computer.recipe.ComputerConvertRecipe;
import dan200.computercraft.shared.config.Config;
import dan200.computercraft.shared.data.BlockNamedEntityLootCondition;
import dan200.computercraft.shared.data.HasComputerIdLootCondition;
@ -42,10 +46,7 @@
import dan200.computercraft.shared.details.BlockDetails;
import dan200.computercraft.shared.details.ItemDetails;
import dan200.computercraft.shared.integration.PermissionRegistry;
import dan200.computercraft.shared.media.items.DiskItem;
import dan200.computercraft.shared.media.items.PrintoutItem;
import dan200.computercraft.shared.media.items.RecordMedia;
import dan200.computercraft.shared.media.items.TreasureDiskItem;
import dan200.computercraft.shared.media.items.*;
import dan200.computercraft.shared.media.recipes.DiskRecipe;
import dan200.computercraft.shared.media.recipes.PrintoutRecipe;
import dan200.computercraft.shared.network.container.ComputerContainerData;
@ -81,19 +82,22 @@
import dan200.computercraft.shared.turtle.inventory.TurtleMenu;
import dan200.computercraft.shared.turtle.items.TurtleItem;
import dan200.computercraft.shared.turtle.recipes.TurtleOverlayRecipe;
import dan200.computercraft.shared.turtle.recipes.TurtleRecipe;
import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe;
import dan200.computercraft.shared.turtle.upgrades.*;
import dan200.computercraft.shared.util.DataComponentUtil;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
import net.minecraft.commands.synchronization.SingletonArgumentInfo;
import net.minecraft.core.BlockPos;
import net.minecraft.core.cauldron.CauldronInteraction;
import net.minecraft.core.component.DataComponentType;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.chat.Component;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.flag.FeatureFlags;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.*;
import net.minecraft.world.item.component.DyedItemColor;
import net.minecraft.world.item.crafting.CustomRecipe;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer;
@ -101,12 +105,12 @@
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.MapColor;
import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType;
import java.util.function.BiFunction;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
/**
* Registers ComputerCraft's registry entries and additional objects, such as {@link CauldronInteraction}s and
@ -174,8 +178,8 @@ private static BlockBehaviour.Properties modemProperties() {
public static class BlockEntities {
static final RegistrationHelper<BlockEntityType<?>> REGISTRY = PlatformHelper.get().createRegistrationHelper(Registries.BLOCK_ENTITY_TYPE);
private static <T extends BlockEntity> RegistryEntry<BlockEntityType<T>> ofBlock(RegistryEntry<? extends Block> block, BiFunction<BlockPos, BlockState, T> factory) {
return REGISTRY.register(block.id().getPath(), () -> PlatformHelper.get().createBlockEntityType(factory, block.get()));
private static <T extends BlockEntity> RegistryEntry<BlockEntityType<T>> ofBlock(RegistryEntry<? extends Block> block, BlockEntityType.BlockEntitySupplier<T> factory) {
return REGISTRY.register(block.id().getPath(), () -> BlockEntityType.Builder.of(factory, block.get()).build(null));
}
public static final RegistryEntry<BlockEntityType<MonitorBlockEntity>> MONITOR_NORMAL =
@ -262,6 +266,60 @@ private static <B extends Block, I extends Item> RegistryEntry<I> ofBlock(Regist
() -> new CableBlockItem.WiredModem(Blocks.CABLE.get(), properties()));
}
public static final class DataComponents {
static final RegistrationHelper<DataComponentType<?>> REGISTRY = PlatformHelper.get().createRegistrationHelper(Registries.DATA_COMPONENT_TYPE);
private static <T> RegistryEntry<DataComponentType<T>> register(String name, UnaryOperator<DataComponentType.Builder<T>> unaryOperator) {
return REGISTRY.register(name, () -> unaryOperator.apply(DataComponentType.builder()).build());
}
public static final RegistryEntry<DataComponentType<TreasureDisk>> TREASURE_DISK = register("treasure_disk", b -> b
.persistent(TreasureDisk.CODEC).networkSynchronized(TreasureDisk.STREAM_CODEC)
);
public static final RegistryEntry<DataComponentType<Integer>> DISK = register("disk", b -> b
// TODO: Replace with a normal object.
.persistent(Codec.INT.validate(x -> x < 0 ? DataResult.error(() -> "Disk ID must be >= 0") : DataResult.success(x)))
.networkSynchronized(ByteBufCodecs.INT)
);
public static final RegistryEntry<DataComponentType<ComputerId>> COMPUTER_ID = register("computer_id", b -> b
.persistent(ComputerId.CODEC).networkSynchronized(ComputerId.STREAM_CODEC)
);
public static final RegistryEntry<DataComponentType<UpgradeData<IPocketUpgrade>>> POCKET_UPGRADE = register("upgrade", b -> b
.persistent(PocketUpgrades.instance().codec()).networkSynchronized(PocketUpgrades.instance().streamCodec())
);
public static final RegistryEntry<DataComponentType<UpgradeData<ITurtleUpgrade>>> LEFT_TURTLE_UPGRADE = register("left_upgrade", b -> b
.persistent(TurtleUpgrades.instance().codec()).networkSynchronized(TurtleUpgrades.instance().streamCodec())
);
public static final RegistryEntry<DataComponentType<UpgradeData<ITurtleUpgrade>>> RIGHT_TURTLE_UPGRADE = register("right_upgrade", b -> b
.persistent(TurtleUpgrades.instance().codec()).networkSynchronized(TurtleUpgrades.instance().streamCodec())
);
public static final RegistryEntry<DataComponentType<ServerComputerReference>> COMPUTER = register("computer", b -> b
.persistent(ServerComputerReference.CODEC).networkSynchronized(ServerComputerReference.STREAM_CODEC)
);
public static final RegistryEntry<DataComponentType<Boolean>> ON = register("on", b -> b
.persistent(Codec.BOOL).networkSynchronized(ByteBufCodecs.BOOL)
);
public static final RegistryEntry<DataComponentType<Integer>> FUEL = register("fuel", b -> b
.persistent(Codec.INT).networkSynchronized(ByteBufCodecs.VAR_INT)
);
public static final RegistryEntry<DataComponentType<ResourceLocation>> OVERLAY = register("overlay", b -> b
.persistent(ResourceLocation.CODEC).networkSynchronized(ResourceLocation.STREAM_CODEC)
);
public static final RegistryEntry<DataComponentType<PrintoutData>> PRINTOUT = register("printout", b -> b
.persistent(PrintoutData.CODEC).networkSynchronized(PrintoutData.STREAM_CODEC)
);
}
public static class TurtleSerialisers {
static final RegistrationHelper<UpgradeSerialiser<? extends ITurtleUpgrade>> REGISTRY = PlatformHelper.get().createRegistrationHelper(ITurtleUpgrade.serialiserRegistryKey());
@ -292,16 +350,16 @@ public static class Menus {
static final RegistrationHelper<MenuType<?>> REGISTRY = PlatformHelper.get().createRegistrationHelper(Registries.MENU);
public static final RegistryEntry<MenuType<ComputerMenuWithoutInventory>> COMPUTER = REGISTRY.register("computer",
() -> ContainerData.toType(ComputerContainerData::new, (id, inv, data) -> new ComputerMenuWithoutInventory(Menus.COMPUTER.get(), id, inv, data)));
() -> ContainerData.toType(ComputerContainerData.STREAM_CODEC, (id, inv, data) -> new ComputerMenuWithoutInventory(Menus.COMPUTER.get(), id, inv, data)));
public static final RegistryEntry<MenuType<ComputerMenuWithoutInventory>> POCKET_COMPUTER = REGISTRY.register("pocket_computer",
() -> ContainerData.toType(ComputerContainerData::new, (id, inv, data) -> new ComputerMenuWithoutInventory(Menus.POCKET_COMPUTER.get(), id, inv, data)));
() -> ContainerData.toType(ComputerContainerData.STREAM_CODEC, (id, inv, data) -> new ComputerMenuWithoutInventory(Menus.POCKET_COMPUTER.get(), id, inv, data)));
public static final RegistryEntry<MenuType<ComputerMenuWithoutInventory>> POCKET_COMPUTER_NO_TERM = REGISTRY.register("pocket_computer_no_term",
() -> ContainerData.toType(ComputerContainerData::new, (id, inv, data) -> new ComputerMenuWithoutInventory(Menus.POCKET_COMPUTER_NO_TERM.get(), id, inv, data)));
() -> ContainerData.toType(ComputerContainerData.STREAM_CODEC, (id, inv, data) -> new ComputerMenuWithoutInventory(Menus.POCKET_COMPUTER_NO_TERM.get(), id, inv, data)));
public static final RegistryEntry<MenuType<TurtleMenu>> TURTLE = REGISTRY.register("turtle",
() -> ContainerData.toType(ComputerContainerData::new, TurtleMenu::ofMenuData));
() -> ContainerData.toType(ComputerContainerData.STREAM_CODEC, TurtleMenu::ofMenuData));
public static final RegistryEntry<MenuType<DiskDriveMenu>> DISK_DRIVE = REGISTRY.register("disk_drive",
() -> new MenuType<>(DiskDriveMenu::new, FeatureFlags.VANILLA_SET));
@ -311,12 +369,12 @@ public static class Menus {
public static final RegistryEntry<MenuType<HeldItemMenu>> PRINTOUT = REGISTRY.register("printout",
() -> ContainerData.toType(
HeldItemContainerData::new,
HeldItemContainerData.STREAM_CODEC,
(id, inventory, data) -> new HeldItemMenu(Menus.PRINTOUT.get(), id, inventory.player, data.getHand())
));
public static final RegistryEntry<MenuType<ViewComputerMenu>> VIEW_COMPUTER = REGISTRY.register("view_computer",
() -> ContainerData.toType(ComputerContainerData::new, ViewComputerMenu::new));
() -> ContainerData.toType(ComputerContainerData.STREAM_CODEC, ViewComputerMenu::new));
}
static class ArgumentTypes {
@ -346,13 +404,13 @@ public static class LootItemConditionTypes {
static final RegistrationHelper<LootItemConditionType> REGISTRY = PlatformHelper.get().createRegistrationHelper(Registries.LOOT_CONDITION_TYPE);
public static final RegistryEntry<LootItemConditionType> BLOCK_NAMED = REGISTRY.register("block_named",
() -> new LootItemConditionType(Codec.unit(BlockNamedEntityLootCondition.INSTANCE)));
() -> new LootItemConditionType(MapCodec.unit(BlockNamedEntityLootCondition.INSTANCE)));
public static final RegistryEntry<LootItemConditionType> PLAYER_CREATIVE = REGISTRY.register("player_creative",
() -> new LootItemConditionType(Codec.unit(PlayerCreativeLootCondition.INSTANCE)));
() -> new LootItemConditionType(MapCodec.unit(PlayerCreativeLootCondition.INSTANCE)));
public static final RegistryEntry<LootItemConditionType> HAS_ID = REGISTRY.register("has_id",
() -> new LootItemConditionType(Codec.unit(HasComputerIdLootCondition.INSTANCE)));
() -> new LootItemConditionType(MapCodec.unit(HasComputerIdLootCondition.INSTANCE)));
}
public static class RecipeSerializers {
@ -362,21 +420,17 @@ private static <T extends CustomRecipe> RegistryEntry<SimpleCraftingRecipeSerial
return REGISTRY.register(name, () -> new SimpleCraftingRecipeSerializer<>(factory));
}
public static final RegistryEntry<RecipeSerializer<CustomShapedRecipe>> SHAPED = REGISTRY.register("shaped", () -> CustomShapedRecipe.serialiser(CustomShapedRecipe::new));
public static final RegistryEntry<RecipeSerializer<CustomShapelessRecipe>> SHAPELESS = REGISTRY.register("shapeless", () -> CustomShapelessRecipe.serialiser(CustomShapelessRecipe::new));
public static final RegistryEntry<RecipeSerializer<ImpostorShapedRecipe>> IMPOSTOR_SHAPED = REGISTRY.register("impostor_shaped", () -> CustomShapedRecipe.serialiser(ImpostorShapedRecipe::new));
public static final RegistryEntry<RecipeSerializer<ImpostorShapelessRecipe>> IMPOSTOR_SHAPELESS = REGISTRY.register("impostor_shapeless", () -> CustomShapelessRecipe.serialiser(ImpostorShapelessRecipe::new));
public static final RegistryEntry<SimpleCraftingRecipeSerializer<ColourableRecipe>> DYEABLE_ITEM = simple("colour", ColourableRecipe::new);
public static final RegistryEntry<SimpleCraftingRecipeSerializer<ClearColourRecipe>> DYEABLE_ITEM_CLEAR = simple("clear_colour", ClearColourRecipe::new);
public static final RegistryEntry<RecipeSerializer<TurtleRecipe>> TURTLE = REGISTRY.register("turtle", () -> TurtleRecipe.validatingSerialiser(TurtleRecipe::of));
public static final RegistryEntry<SimpleCraftingRecipeSerializer<TurtleUpgradeRecipe>> TURTLE_UPGRADE = simple("turtle_upgrade", TurtleUpgradeRecipe::new);
public static final RegistryEntry<RecipeSerializer<TurtleOverlayRecipe>> TURTLE_OVERLAY = REGISTRY.register("turtle_overlay", TurtleOverlayRecipe.Serialiser::new);
public static final RegistryEntry<RecipeSerializer<TurtleOverlayRecipe>> TURTLE_OVERLAY = REGISTRY.register("turtle_overlay", TurtleOverlayRecipe::serialiser);
public static final RegistryEntry<SimpleCraftingRecipeSerializer<PocketComputerUpgradeRecipe>> POCKET_COMPUTER_UPGRADE = simple("pocket_computer_upgrade", PocketComputerUpgradeRecipe::new);
public static final RegistryEntry<SimpleCraftingRecipeSerializer<PrintoutRecipe>> PRINTOUT = simple("printout", PrintoutRecipe::new);
public static final RegistryEntry<SimpleCraftingRecipeSerializer<DiskRecipe>> DISK = simple("disk", DiskRecipe::new);
public static final RegistryEntry<RecipeSerializer<ComputerUpgradeRecipe>> COMPUTER_UPGRADE = REGISTRY.register("computer_upgrade", () -> CustomShapedRecipe.validatingSerialiser(ComputerUpgradeRecipe::of));
public static final RegistryEntry<RecipeSerializer<ComputerConvertRecipe>> COMPUTER_CONVERT = REGISTRY.register("computer_convert", () -> CustomShapedRecipe.serialiser(ComputerConvertRecipe::new));
}
public static class Permissions {
@ -425,7 +479,7 @@ static class CreativeTabs {
out.accept(Items.DISK_DRIVE.get());
for (var colour = 0; colour < 16; colour++) {
out.accept(DiskItem.createFromIDAndColour(-1, null, Colour.VALUES[colour].getHex()));
out.accept(DataComponentUtil.createStack(Items.DISK.get(), net.minecraft.core.component.DataComponents.DYED_COLOR, new DyedItemColor(Colour.VALUES[colour].getHex(), false)));
}
})
.build());
@ -438,6 +492,7 @@ public static void register() {
Blocks.REGISTRY.register();
BlockEntities.REGISTRY.register();
Items.REGISTRY.register();
DataComponents.REGISTRY.register();
TurtleSerialisers.REGISTRY.register();
PocketUpgradeSerialisers.REGISTRY.register();
Menus.REGISTRY.register();
@ -470,14 +525,14 @@ public static void registerMainThread() {
}
private static void addTurtle(CreativeModeTab.Output out, TurtleItem turtle) {
out.accept(turtle.create(-1, null, -1, null, null, 0, null));
out.accept(new ItemStack(turtle));
TurtleUpgrades.getVanillaUpgrades()
.map(x -> turtle.create(-1, null, -1, null, UpgradeData.ofDefault(x), 0, null))
.map(x -> DataComponentUtil.createStack(turtle, DataComponents.RIGHT_TURTLE_UPGRADE.get(), UpgradeData.ofDefault(x)))
.forEach(out::accept);
}
private static void addPocket(CreativeModeTab.Output out, PocketComputerItem pocket) {
out.accept(pocket.create(-1, null, -1, null));
PocketUpgrades.getVanillaUpgrades().map(x -> pocket.create(-1, null, -1, UpgradeData.ofDefault(x))).forEach(out::accept);
out.accept(new ItemStack(pocket));
PocketUpgrades.getVanillaUpgrades().map(x -> DataComponentUtil.createStack(pocket, DataComponents.POCKET_UPGRADE.get(), UpgradeData.ofDefault(x))).forEach(out::accept);
}
}

View File

@ -14,8 +14,6 @@
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
import java.util.Objects;
/**
* Utilities for working with arguments.
*
@ -45,13 +43,12 @@ public static <A extends ArgumentType<?>> void serializeToNetwork(FriendlyByteBu
@SuppressWarnings("unchecked")
private static <A extends ArgumentType<?>, T extends ArgumentTypeInfo.Template<A>> void serializeToNetwork(FriendlyByteBuf buffer, ArgumentTypeInfo<A, T> type, ArgumentTypeInfo.Template<A> template) {
buffer.writeId(BuiltInRegistries.COMMAND_ARGUMENT_TYPE, type);
buffer.writeVarInt(BuiltInRegistries.COMMAND_ARGUMENT_TYPE.getIdOrThrow(type));
type.serializeToNetwork((T) template, buffer);
}
public static ArgumentTypeInfo.Template<?> deserialize(FriendlyByteBuf buffer) {
var type = buffer.readById(BuiltInRegistries.COMMAND_ARGUMENT_TYPE);
Objects.requireNonNull(type, "Unknown argument type");
var type = BuiltInRegistries.COMMAND_ARGUMENT_TYPE.byIdOrThrow(buffer.readVarInt());
return type.deserializeFromNetwork(buffer);
}

View File

@ -15,8 +15,10 @@
import net.minecraft.commands.CommandBuildContext;
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
import net.minecraft.commands.synchronization.ArgumentTypeInfos;
import net.minecraft.core.RegistryAccess;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.ComponentSerialization;
import java.util.ArrayList;
import java.util.Collection;
@ -114,14 +116,14 @@ public static class Info implements ArgumentTypeInfo<RepeatArgumentType<?, ?>, T
public void serializeToNetwork(RepeatArgumentType.Template arg, FriendlyByteBuf buf) {
buf.writeBoolean(arg.flatten);
ArgumentUtils.serializeToNetwork(buf, arg.child);
buf.writeComponent(ArgumentUtils.getMessage(arg.some));
ComponentSerialization.TRUSTED_CONTEXT_FREE_STREAM_CODEC.encode(buf, ArgumentUtils.getMessage(arg.some));
}
@Override
public RepeatArgumentType.Template deserializeFromNetwork(FriendlyByteBuf buf) {
var isList = buf.readBoolean();
var child = ArgumentUtils.deserialize(buf);
var message = buf.readComponent();
var message = ComponentSerialization.TRUSTED_CONTEXT_FREE_STREAM_CODEC.decode(buf);
return new RepeatArgumentType.Template(this, child, isList, new SimpleCommandExceptionType(message));
}
@ -134,7 +136,7 @@ public RepeatArgumentType.Template unpack(RepeatArgumentType<?, ?> argumentType)
public void serializeToJson(RepeatArgumentType.Template arg, JsonObject json) {
json.addProperty("flatten", arg.flatten);
json.add("child", ArgumentUtils.serializeToJson(arg.child));
json.addProperty("error", Component.Serializer.toJson(ArgumentUtils.getMessage(arg.some)));
json.addProperty("error", Component.Serializer.toJson(ArgumentUtils.getMessage(arg.some), RegistryAccess.EMPTY));
}
}

View File

@ -4,11 +4,8 @@
package dan200.computercraft.shared.common;
import dan200.computercraft.shared.container.BasicContainer;
import dan200.computercraft.shared.util.BlockEntityHelpers;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.block.entity.BaseContainerBlockEntity;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
@ -17,7 +14,7 @@
/**
* A {@link BlockEntity} which exposes an inventory.
*/
public abstract class AbstractContainerBlockEntity extends BaseContainerBlockEntity implements BasicContainer {
public abstract class AbstractContainerBlockEntity extends BaseContainerBlockEntity {
protected AbstractContainerBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) {
super(type, pos, state);
}
@ -26,9 +23,4 @@ protected AbstractContainerBlockEntity(BlockEntityType<?> type, BlockPos pos, Bl
protected final Component getDefaultName() {
return Component.translatable(getBlockState().getBlock().getDescriptionId());
}
@Override
public boolean stillValid(Player player) {
return BlockEntityHelpers.isUsable(this, player, BlockEntityHelpers.DEFAULT_INTERACT_RANGE);
}
}

View File

@ -4,9 +4,12 @@
package dan200.computercraft.shared.common;
import dan200.computercraft.api.ComputerCraftTags;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.util.DataComponentUtil;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.NonNullList;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.component.DataComponents;
import net.minecraft.world.inventory.CraftingContainer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
@ -16,7 +19,7 @@
import net.minecraft.world.level.Level;
/**
* Craft a wet sponge with a {@linkplain IColouredItem dyable item} to remove its dye.
* Craft a wet sponge with a {@linkplain ComputerCraftTags.Items#DYEABLE dyable item} to remove its dye.
*/
public final class ClearColourRecipe extends CustomRecipe {
public ClearColourRecipe(CraftingBookCategory category) {
@ -31,9 +34,9 @@ public boolean matches(CraftingContainer inv, Level world) {
var stack = inv.getItem(i);
if (stack.isEmpty()) continue;
if (stack.getItem() instanceof IColouredItem colourable) {
if (stack.is(ComputerCraftTags.Items.DYEABLE)) {
if (hasColourable) return false;
if (colourable.getColour(stack) == -1) return false;
if (!stack.has(DataComponents.DYED_COLOR)) return false;
hasColourable = true;
} else if (stack.getItem() == Items.WET_SPONGE) {
if (hasSponge) return false;
@ -47,19 +50,17 @@ public boolean matches(CraftingContainer inv, Level world) {
}
@Override
public ItemStack assemble(CraftingContainer inv, RegistryAccess registryAccess) {
public ItemStack assemble(CraftingContainer inv, HolderLookup.Provider registryAccess) {
var colourable = ItemStack.EMPTY;
for (var i = 0; i < inv.getContainerSize(); i++) {
var stack = inv.getItem(i);
if (stack.getItem() instanceof IColouredItem) colourable = stack;
if (stack.is(ComputerCraftTags.Items.DYEABLE)) colourable = stack;
}
if (colourable.isEmpty()) return ItemStack.EMPTY;
var stack = ((IColouredItem) colourable.getItem()).withColour(colourable, -1);
stack.setCount(1);
return stack;
return DataComponentUtil.createResult(colourable, DataComponents.DYED_COLOR, null);
}
@Override

View File

@ -4,12 +4,16 @@
package dan200.computercraft.shared.common;
import dan200.computercraft.api.ComputerCraftTags;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.util.ColourTracker;
import dan200.computercraft.shared.util.ColourUtils;
import net.minecraft.core.RegistryAccess;
import dan200.computercraft.shared.util.DataComponentUtil;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.component.DataComponents;
import net.minecraft.world.inventory.CraftingContainer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.component.DyedItemColor;
import net.minecraft.world.item.crafting.CraftingBookCategory;
import net.minecraft.world.item.crafting.CustomRecipe;
import net.minecraft.world.item.crafting.RecipeSerializer;
@ -28,7 +32,7 @@ public boolean matches(CraftingContainer inv, Level world) {
var stack = inv.getItem(i);
if (stack.isEmpty()) continue;
if (stack.getItem() instanceof IColouredItem) {
if (stack.is(ComputerCraftTags.Items.DYEABLE)) {
if (hasColourable) return false;
hasColourable = true;
} else if (ColourUtils.getStackColour(stack) != null) {
@ -42,7 +46,7 @@ public boolean matches(CraftingContainer inv, Level world) {
}
@Override
public ItemStack assemble(CraftingContainer inv, RegistryAccess registryAccess) {
public ItemStack assemble(CraftingContainer inv, HolderLookup.Provider registryAccess) {
var colourable = ItemStack.EMPTY;
var tracker = new ColourTracker();
@ -52,7 +56,7 @@ public ItemStack assemble(CraftingContainer inv, RegistryAccess registryAccess)
if (stack.isEmpty()) continue;
if (stack.getItem() instanceof IColouredItem) {
if (stack.is(ComputerCraftTags.Items.DYEABLE)) {
colourable = stack;
} else {
var dye = ColourUtils.getStackColour(stack);
@ -60,11 +64,10 @@ public ItemStack assemble(CraftingContainer inv, RegistryAccess registryAccess)
}
}
if (colourable.isEmpty()) return ItemStack.EMPTY;
return colourable.isEmpty()
? ItemStack.EMPTY
: DataComponentUtil.createResult(colourable, DataComponents.DYED_COLOR, new DyedItemColor(tracker.getColour(), false));
var stack = ((IColouredItem) colourable.getItem()).withColour(colourable, tracker.getColour());
stack.setCount(1);
return stack;
}
@Override

View File

@ -6,12 +6,9 @@
import net.minecraft.core.BlockPos;
import net.minecraft.world.Containers;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BaseEntityBlock;
@ -24,7 +21,6 @@
import net.minecraft.world.level.block.state.properties.DirectionProperty;
import net.minecraft.world.phys.BlockHitResult;
import javax.annotation.Nullable;
/**
* A block which has a container and can be placed in a horizontal direction.
@ -44,20 +40,17 @@ public final BlockState getStateForPlacement(BlockPlaceContext placement) {
}
@Override
@Deprecated
public final BlockState mirror(BlockState state, Mirror mirrorIn) {
protected final BlockState mirror(BlockState state, Mirror mirrorIn) {
return state.rotate(mirrorIn.getRotation(state.getValue(FACING)));
}
@Override
@Deprecated
public final BlockState rotate(BlockState state, Rotation rot) {
protected final BlockState rotate(BlockState state, Rotation rot) {
return state.setValue(FACING, rot.rotate(state.getValue(FACING)));
}
@Override
@Deprecated
public InteractionResult use(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) {
protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hit) {
if (level.isClientSide) return InteractionResult.SUCCESS;
if (level.getBlockEntity(pos) instanceof BaseContainerBlockEntity container) {
@ -68,8 +61,7 @@ public InteractionResult use(BlockState state, Level level, BlockPos pos, Player
}
@Override
@Deprecated
public final void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) {
protected final void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) {
if (state.is(newState.getBlock())) return;
if (level.getBlockEntity(pos) instanceof BaseContainerBlockEntity container) {
@ -81,27 +73,17 @@ public final void onRemove(BlockState state, Level level, BlockPos pos, BlockSta
}
@Override
public final void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) {
if (stack.hasCustomHoverName() && world.getBlockEntity(pos) instanceof BaseContainerBlockEntity container) {
container.setCustomName(stack.getHoverName());
}
}
@Override
@Deprecated
public final boolean hasAnalogOutputSignal(BlockState pState) {
protected final boolean hasAnalogOutputSignal(BlockState pState) {
return true;
}
@Override
@Deprecated
public final int getAnalogOutputSignal(BlockState pBlockState, Level pLevel, BlockPos pPos) {
protected final int getAnalogOutputSignal(BlockState pBlockState, Level pLevel, BlockPos pPos) {
return AbstractContainerMenu.getRedstoneSignalFromBlockEntity(pLevel.getBlockEntity(pPos));
}
@Override
@Deprecated
public RenderShape getRenderShape(BlockState state) {
protected RenderShape getRenderShape(BlockState state) {
return RenderShape.MODEL;
}
}

View File

@ -1,35 +0,0 @@
// SPDX-FileCopyrightText: 2017 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.common;
import net.minecraft.world.item.ItemStack;
public interface IColouredItem {
String NBT_COLOUR = "Color";
default int getColour(ItemStack stack) {
return getColourBasic(stack);
}
default ItemStack withColour(ItemStack stack, int colour) {
var copy = stack.copy();
setColourBasic(copy, colour);
return copy;
}
static int getColourBasic(ItemStack stack) {
var tag = stack.getTag();
return tag != null && tag.contains(NBT_COLOUR) ? tag.getInt(NBT_COLOUR) : -1;
}
static void setColourBasic(ItemStack stack, int colour) {
if (colour == -1) {
var tag = stack.getTag();
if (tag != null) tag.remove(NBT_COLOUR);
} else {
stack.getOrCreateTag().putInt(NBT_COLOUR, colour);
}
}
}

View File

@ -78,7 +78,7 @@ private static final class CommandState {
var table = VanillaDetailRegistries.BLOCK_IN_WORLD.getDetails(block);
var tile = block.blockEntity();
if (tile != null) table.put("nbt", NBTUtil.toLua(tile.saveWithFullMetadata()));
if (tile != null) table.put("nbt", NBTUtil.toLua(tile.saveWithFullMetadata(world.registryAccess())));
return table;
}

View File

@ -7,7 +7,7 @@
import dan200.computercraft.annotations.ForgeOverride;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.shared.common.IBundledRedstoneBlock;
import dan200.computercraft.shared.computer.items.IComputerItem;
import dan200.computercraft.shared.network.container.ComputerContainerData;
import dan200.computercraft.shared.platform.RegistryEntry;
import dan200.computercraft.shared.util.BlockEntityHelpers;
import net.minecraft.core.BlockPos;
@ -15,10 +15,8 @@
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.stats.Stats;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.BlockGetter;
@ -51,8 +49,7 @@ protected AbstractComputerBlock(Properties settings, RegistryEntry<BlockEntityTy
}
@Override
@Deprecated
public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean isMoving) {
protected void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean isMoving) {
super.onPlace(state, world, pos, oldState, isMoving);
var tile = world.getBlockEntity(pos);
@ -60,14 +57,12 @@ public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldS
}
@Override
@Deprecated
public boolean isSignalSource(BlockState state) {
protected boolean isSignalSource(BlockState state) {
return true;
}
@Override
@Deprecated
public int getDirectSignal(BlockState state, BlockGetter world, BlockPos pos, Direction incomingSide) {
protected int getDirectSignal(BlockState state, BlockGetter world, BlockPos pos, Direction incomingSide) {
var entity = world.getBlockEntity(pos);
if (!(entity instanceof AbstractComputerBlockEntity computerEntity)) return 0;
@ -78,11 +73,14 @@ public int getDirectSignal(BlockState state, BlockGetter world, BlockPos pos, Di
return computer.getRedstoneOutput(localSide);
}
protected abstract ItemStack getItem(AbstractComputerBlockEntity tile);
protected final ItemStack getItem(AbstractComputerBlockEntity tile) {
var stack = new ItemStack(this);
stack.applyComponents(tile.collectComponents());
return stack;
}
@Override
@Deprecated
public int getSignal(BlockState state, BlockGetter world, BlockPos pos, Direction incomingSide) {
protected int getSignal(BlockState state, BlockGetter world, BlockPos pos, Direction incomingSide) {
return getDirectSignal(state, world, pos, incomingSide);
}
@ -99,7 +97,6 @@ public int getBundledRedstoneOutput(Level world, BlockPos pos, Direction side) {
}
@Override
@Deprecated
public ItemStack getCloneItemStack(LevelReader world, BlockPos pos, BlockState state) {
var tile = world.getBlockEntity(pos);
if (tile instanceof AbstractComputerBlockEntity computer) {
@ -144,29 +141,23 @@ public BlockState playerWillDestroy(Level world, BlockPos pos, BlockState state,
}
@Override
public void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) {
super.setPlacedBy(world, pos, state, placer, stack);
protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hit) {
if (!player.isCrouching() && level.getBlockEntity(pos) instanceof AbstractComputerBlockEntity computer) {
// Regular right click to activate computer
if (!level.isClientSide && computer.isUsable(player)) {
var serverComputer = computer.createServerComputer();
serverComputer.turnOn();
var tile = world.getBlockEntity(pos);
if (!world.isClientSide && tile instanceof IComputerBlockEntity computer && stack.getItem() instanceof IComputerItem item) {
var id = item.getComputerID(stack);
if (id != -1) computer.setComputerID(id);
var label = item.getLabel(stack);
if (label != null) computer.setLabel(label);
new ComputerContainerData(serverComputer, getItem(computer)).open(player, computer);
}
return InteractionResult.sidedSuccess(level.isClientSide);
}
return super.useWithoutItem(state, level, pos, player, hit);
}
@Override
@Deprecated
public final InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) {
return world.getBlockEntity(pos) instanceof AbstractComputerBlockEntity computer ? computer.use(player, hand) : InteractionResult.PASS;
}
@Override
@Deprecated
public final void neighborChanged(BlockState state, Level world, BlockPos pos, Block neighbourBlock, BlockPos neighbourPos, boolean isMoving) {
protected final void neighborChanged(BlockState state, Level world, BlockPos pos, Block neighbourBlock, BlockPos neighbourPos, boolean isMoving) {
var be = world.getBlockEntity(pos);
if (be instanceof AbstractComputerBlockEntity computer) computer.neighborChanged(neighbourPos);
}
@ -178,8 +169,7 @@ public final void onNeighborChange(BlockState state, LevelReader world, BlockPos
}
@Override
@Deprecated
public BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor level, BlockPos pos, BlockPos neighborPos) {
protected BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor level, BlockPos pos, BlockPos neighborPos) {
var be = level.getBlockEntity(pos);
if (be instanceof AbstractComputerBlockEntity computer) computer.neighbourShapeChanged(direction);
@ -188,8 +178,7 @@ public BlockState updateShape(BlockState state, Direction direction, BlockState
@Nullable
@Override
@Deprecated
public MenuProvider getMenuProvider(BlockState state, Level level, BlockPos pos) {
protected MenuProvider getMenuProvider(BlockState state, Level level, BlockPos pos) {
return level.getBlockEntity(pos) instanceof AbstractComputerBlockEntity computer ? computer : null;
}

View File

@ -9,26 +9,28 @@
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.impl.BundledRedstone;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ComputerState;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.network.container.ComputerContainerData;
import dan200.computercraft.shared.computer.items.ComputerId;
import dan200.computercraft.shared.platform.ComponentAccess;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.util.BlockEntityHelpers;
import dan200.computercraft.shared.util.DirectionUtil;
import dan200.computercraft.shared.util.IDAssigner;
import dan200.computercraft.shared.util.RedstoneUtil;
import dan200.computercraft.shared.util.*;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.component.DataComponentMap;
import net.minecraft.core.component.DataComponents;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
import net.minecraft.world.*;
import net.minecraft.world.Container;
import net.minecraft.world.LockCode;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.Nameable;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.block.entity.BaseContainerBlockEntity;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
@ -76,42 +78,13 @@ public void setRemoved() {
unload();
}
protected boolean canNameWithTag(Player player) {
return false;
}
protected double getInteractRange() {
return BlockEntityHelpers.DEFAULT_INTERACT_RANGE;
protected float getInteractRange() {
return Container.DEFAULT_DISTANCE_BUFFER;
}
public boolean isUsable(Player player) {
return BaseContainerBlockEntity.canUnlock(player, lockCode, getDisplayName())
&& BlockEntityHelpers.isUsable(this, player, getInteractRange());
}
public InteractionResult use(Player player, InteractionHand hand) {
var currentItem = player.getItemInHand(hand);
if (!currentItem.isEmpty() && currentItem.getItem() == Items.NAME_TAG && canNameWithTag(player) && currentItem.hasCustomHoverName()) {
// Label to rename computer
if (!getLevel().isClientSide) {
setLabel(currentItem.getHoverName().getString());
currentItem.shrink(1);
}
return InteractionResult.sidedSuccess(getLevel().isClientSide);
} else if (!player.isCrouching()) {
// Regular right click to activate computer
if (!getLevel().isClientSide && isUsable(player)) {
var computer = createServerComputer();
computer.turnOn();
var stack = getBlockState().getBlock() instanceof AbstractComputerBlock<?>
? ((AbstractComputerBlock<?>) getBlockState().getBlock()).getItem(this)
: ItemStack.EMPTY;
new ComputerContainerData(computer, stack).open(player, this);
}
return InteractionResult.sidedSuccess(getLevel().isClientSide);
}
return InteractionResult.PASS;
&& Container.stillValidBlockEntity(this, player, getInteractRange());
}
protected void serverTick() {
@ -166,7 +139,7 @@ protected void serverTick() {
protected abstract void updateBlockState(ComputerState newState);
@Override
public void saveAdditional(CompoundTag nbt) {
public void saveAdditional(CompoundTag nbt, HolderLookup.Provider registries) {
// Save ID, label and power state
if (computerID >= 0) nbt.putInt(NBT_ID, computerID);
if (label != null) nbt.putString(NBT_LABEL, label);
@ -174,20 +147,20 @@ public void saveAdditional(CompoundTag nbt) {
lockCode.addToTag(nbt);
super.saveAdditional(nbt);
super.saveAdditional(nbt, registries);
}
@Override
public final void load(CompoundTag nbt) {
super.load(nbt);
public final void loadAdditional(CompoundTag nbt, HolderLookup.Provider registries) {
super.loadAdditional(nbt, registries);
if (level != null && level.isClientSide) {
loadClient(nbt);
loadClient(nbt, registries);
} else {
loadServer(nbt);
loadServer(nbt, registries);
}
}
protected void loadServer(CompoundTag nbt) {
protected void loadServer(CompoundTag nbt, HolderLookup.Provider registries) {
// Load ID, label and power state
computerID = nbt.contains(NBT_ID) ? nbt.getInt(NBT_ID) : -1;
label = nbt.contains(NBT_LABEL) ? nbt.getString(NBT_LABEL) : null;
@ -196,6 +169,31 @@ protected void loadServer(CompoundTag nbt) {
lockCode = LockCode.fromTag(nbt);
}
@Override
protected void applyImplicitComponents(DataComponentInput component) {
super.applyImplicitComponents(component);
label = DataComponentUtil.getCustomName(component.get(DataComponents.CUSTOM_NAME));
computerID = ComputerId.getId(component.get(ModRegistry.DataComponents.COMPUTER_ID.get()));
lockCode = component.getOrDefault(DataComponents.LOCK, LockCode.NO_LOCK);
}
@Override
protected void collectImplicitComponents(DataComponentMap.Builder builder) {
super.collectImplicitComponents(builder);
builder.set(ModRegistry.DataComponents.COMPUTER_ID.get(), ComputerId.of(computerID));
builder.set(DataComponents.CUSTOM_NAME, label == null ? null : Component.literal(label));
if (lockCode != LockCode.NO_LOCK) builder.set(DataComponents.LOCK, lockCode);
}
@Override
@Deprecated
public void removeComponentsFromTag(CompoundTag tag) {
super.removeComponentsFromTag(tag);
tag.remove(NBT_ID);
tag.remove(NBT_LABEL);
tag.remove(LockCode.TAG_LOCK);
}
protected boolean isPeripheralBlockedOnSide(ComputerSide localSide) {
return false;
}
@ -343,7 +341,7 @@ public final void setComputerID(int id) {
if (getLevel().isClientSide || computerID == id) return;
computerID = id;
setChanged();
BlockEntityHelpers.updateBlock(this);
}
@Override
@ -353,7 +351,7 @@ public final void setLabel(@Nullable String label) {
this.label = label;
var computer = getServerComputer();
if (computer != null) computer.setLabel(label);
setChanged();
BlockEntityHelpers.updateBlock(this);
}
@Override
@ -399,15 +397,15 @@ public final ClientboundBlockEntityDataPacket getUpdatePacket() {
}
@Override
public CompoundTag getUpdateTag() {
public CompoundTag getUpdateTag(HolderLookup.Provider registries) {
// We need this for pick block on the client side.
var nbt = super.getUpdateTag();
var nbt = super.getUpdateTag(registries);
if (label != null) nbt.putString(NBT_LABEL, label);
if (computerID >= 0) nbt.putInt(NBT_ID, computerID);
return nbt;
}
protected void loadClient(CompoundTag nbt) {
protected void loadClient(CompoundTag nbt, HolderLookup.Provider registries) {
label = nbt.contains(NBT_LABEL) ? nbt.getString(NBT_LABEL) : null;
computerID = nbt.contains(NBT_ID) ? nbt.getInt(NBT_ID) : -1;
}

View File

@ -7,11 +7,9 @@
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import dan200.computercraft.shared.computer.core.ComputerState;
import dan200.computercraft.shared.computer.items.ComputerItem;
import dan200.computercraft.shared.platform.RegistryEntry;
import dan200.computercraft.shared.util.BlockCodecs;
import net.minecraft.core.Direction;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntityType;
@ -55,12 +53,4 @@ protected MapCodec<? extends ComputerBlock<?>> codec() {
public BlockState getStateForPlacement(BlockPlaceContext placement) {
return defaultBlockState().setValue(FACING, placement.getHorizontalDirection().getOpposite());
}
@Override
protected ItemStack getItem(AbstractComputerBlockEntity tile) {
if (!(tile instanceof ComputerBlockEntity computer)) return ItemStack.EMPTY;
if (!(asItem() instanceof ComputerItem item)) return ItemStack.EMPTY;
return item.create(computer.getComputerID(), computer.getLabel());
}
}

View File

@ -7,30 +7,32 @@
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.filesystem.Mount;
import dan200.computercraft.api.media.IMedia;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.computer.blocks.AbstractComputerBlock;
import dan200.computercraft.shared.config.Config;
import dan200.computercraft.shared.util.DataComponentUtil;
import net.minecraft.ChatFormatting;
import net.minecraft.core.component.DataComponents;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.Level;
import javax.annotation.Nullable;
import java.util.List;
public abstract class AbstractComputerItem extends BlockItem implements IComputerItem, IMedia {
public class AbstractComputerItem extends BlockItem implements IMedia {
public AbstractComputerItem(AbstractComputerBlock<?> block, Properties settings) {
super(block, settings);
}
@Override
public void appendHoverText(ItemStack stack, @Nullable Level world, List<Component> list, TooltipFlag options) {
if (options.isAdvanced() || getLabel(stack) == null) {
var id = getComputerID(stack);
if (id >= 0) {
list.add(Component.translatable("gui.computercraft.tooltip.computer_id", id)
public void appendHoverText(ItemStack stack, TooltipContext context, List<Component> list, TooltipFlag options) {
if (options.isAdvanced() || !stack.has(DataComponents.CUSTOM_NAME)) {
var id = stack.get(ModRegistry.DataComponents.COMPUTER_ID.get());
if (id != null) {
list.add(Component.translatable("gui.computercraft.tooltip.computer_id", id.id())
.withStyle(ChatFormatting.GRAY));
}
}
@ -38,22 +40,18 @@ public void appendHoverText(ItemStack stack, @Nullable Level world, List<Compone
@Override
public @Nullable String getLabel(ItemStack stack) {
return IComputerItem.super.getLabel(stack);
return DataComponentUtil.getCustomName(stack);
}
@Override
public boolean setLabel(ItemStack stack, @Nullable String label) {
if (label != null) {
stack.setHoverName(Component.literal(label));
} else {
stack.resetHoverName();
}
DataComponentUtil.setCustomName(stack, label);
return true;
}
@Override
public @Nullable Mount createDataMount(ItemStack stack, ServerLevel level) {
var id = getComputerID(stack);
return id >= 0 ? ComputerCraftAPI.createSaveDirMount(level.getServer(), "computer/" + id, Config.computerSpaceLimit) : null;
var id = stack.get(ModRegistry.DataComponents.COMPUTER_ID.get());
return id != null ? ComputerCraftAPI.createSaveDirMount(level.getServer(), "computer/" + id.id(), Config.computerSpaceLimit) : null;
}
}

View File

@ -0,0 +1,40 @@
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.computer.items;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import dan200.computercraft.shared.ModRegistry;
import io.netty.buffer.ByteBuf;
import net.minecraft.core.component.DataComponentHolder;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import javax.annotation.Nullable;
public record ComputerId(int id) {
public static final Codec<ComputerId> CODEC = Codec.INT.flatXmap(
x -> x < 0 ? DataResult.error(() -> "Disk ID must be >= 0") : DataResult.success(new ComputerId(x)),
x -> DataResult.success(x.id())
);
public static final StreamCodec<ByteBuf, ComputerId> STREAM_CODEC = ByteBufCodecs.INT.map(ComputerId::new, ComputerId::id);
public ComputerId {
if (id < 0) throw new IllegalArgumentException("Computer id cannot be < 0");
}
public static int getId(DataComponentHolder holder) {
return getId(holder.get(ModRegistry.DataComponents.COMPUTER_ID.get()));
}
public static int getId(@Nullable ComputerId id) {
return id == null ? -1 : id.id();
}
public static @Nullable ComputerId of(int id) {
return id >= 0 ? new ComputerId(id) : null;
}
}

View File

@ -5,28 +5,10 @@
package dan200.computercraft.shared.computer.items;
import dan200.computercraft.shared.computer.blocks.ComputerBlock;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import javax.annotation.Nullable;
public class ComputerItem extends AbstractComputerItem {
// TODO: Do we deprecate this?
public ComputerItem(ComputerBlock<?> block, Properties settings) {
super(block, settings);
}
public ItemStack create(int id, @Nullable String label) {
var result = new ItemStack(this);
if (id >= 0) result.getOrCreateTag().putInt(NBT_ID, id);
if (label != null) result.setHoverName(Component.literal(label));
return result;
}
@Override
public ItemStack changeItem(ItemStack stack, Item newItem) {
return newItem instanceof ComputerItem computer
? computer.create(getComputerID(stack), getLabel(stack))
: ItemStack.EMPTY;
}
}

View File

@ -1,35 +0,0 @@
// Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
//
// SPDX-License-Identifier: LicenseRef-CCPL
package dan200.computercraft.shared.computer.items;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import javax.annotation.Nullable;
public interface IComputerItem {
String NBT_ID = "ComputerId";
default int getComputerID(ItemStack stack) {
var nbt = stack.getTag();
return nbt != null && nbt.contains(NBT_ID) ? nbt.getInt(NBT_ID) : -1;
}
default @Nullable String getLabel(ItemStack stack) {
return stack.hasCustomHoverName() ? stack.getHoverName().getString() : null;
}
/**
* Create a new stack, changing the underlying item.
* <p>
* This should copy the computer's data to a different item of the same type (for instance, converting a normal
* computer to an advanced one).
*
* @param stack The current computer stack.
* @param newItem The new item.
* @return The new stack, possibly {@linkplain ItemStack#EMPTY empty} if {@code newItem} is of the same type.
*/
ItemStack changeItem(ItemStack stack, Item newItem);
}

View File

@ -0,0 +1,47 @@
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.computer.items;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.core.ServerComputerRegistry;
import net.minecraft.core.UUIDUtil;
import net.minecraft.core.component.DataComponentHolder;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import javax.annotation.Nullable;
import java.util.UUID;
/**
* A reference to a {@link ServerComputer}.
*
* @param session The current {@linkplain ServerComputerRegistry#getSessionID() session id}.
* @param instance The computer's {@linkplain ServerComputer#getInstanceUUID() instance id}.
*/
public record ServerComputerReference(int session, UUID instance) {
public static final Codec<ServerComputerReference> CODEC = RecordCodecBuilder.create(i -> i.group(
Codec.INT.fieldOf("session").forGetter(ServerComputerReference::session),
UUIDUtil.CODEC.fieldOf("session").forGetter(ServerComputerReference::instance)
).apply(i, ServerComputerReference::new));
public static final StreamCodec<RegistryFriendlyByteBuf, ServerComputerReference> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.VAR_INT, ServerComputerReference::session,
UUIDUtil.STREAM_CODEC, ServerComputerReference::instance,
ServerComputerReference::new
);
public @Nullable ServerComputer get(ServerComputerRegistry registry) {
return registry.get(session, this.instance());
}
public static @Nullable ServerComputer get(DataComponentHolder holder, ServerComputerRegistry registry) {
var reference = holder.get(ModRegistry.DataComponents.COMPUTER.get());
return reference == null ? null : reference.get(registry);
}
}

View File

@ -4,43 +4,50 @@
package dan200.computercraft.shared.computer.recipe;
import dan200.computercraft.shared.computer.items.IComputerItem;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.computer.items.AbstractComputerItem;
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
import dan200.computercraft.shared.recipe.CustomShapedRecipe;
import dan200.computercraft.shared.recipe.ShapedRecipeSpec;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.HolderLookup;
import net.minecraft.world.inventory.CraftingContainer;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.item.crafting.RecipeSerializer;
/**
* A recipe which converts a computer from one form into another.
*/
public abstract class ComputerConvertRecipe extends CustomShapedRecipe {
public final class ComputerConvertRecipe extends CustomShapedRecipe {
private final Item result;
public ComputerConvertRecipe(ShapedRecipeSpec recipe) {
super(recipe);
}
protected abstract ItemStack convert(IComputerItem item, ItemStack stack);
@Override
public boolean matches(CraftingContainer inventory, Level world) {
if (!super.matches(inventory, world)) return false;
for (var i = 0; i < inventory.getContainerSize(); i++) {
if (inventory.getItem(i).getItem() instanceof IComputerItem) return true;
}
return false;
this.result = recipe.result().getItem();
}
@Override
public ItemStack assemble(CraftingContainer inventory, RegistryAccess registryAccess) {
// Find our computer item and convert it.
public ItemStack assemble(CraftingContainer inventory, HolderLookup.Provider registryAccess) {
// Find our computer item and copy the components across.
for (var i = 0; i < inventory.getContainerSize(); i++) {
var stack = inventory.getItem(i);
if (stack.getItem() instanceof IComputerItem) return convert((IComputerItem) stack.getItem(), stack);
if (isComputerItem(stack.getItem())) {
var newStack = new ItemStack(result);
newStack.applyComponents(stack.getComponentsPatch());
return newStack;
}
}
return ItemStack.EMPTY;
}
@Override
public RecipeSerializer<ComputerConvertRecipe> getSerializer() {
return ModRegistry.RecipeSerializers.COMPUTER_CONVERT.get();
}
private static boolean isComputerItem(Item item) {
// TODO: Make this a little more general. Either with a tag, or a predicate on the recipe itself?
return item instanceof AbstractComputerItem || item instanceof PocketComputerItem;
}
}

View File

@ -1,46 +0,0 @@
// SPDX-FileCopyrightText: 2019 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.computer.recipe;
import com.mojang.serialization.DataResult;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.computer.items.IComputerItem;
import dan200.computercraft.shared.recipe.ShapedRecipeSpec;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.RecipeSerializer;
/**
* A recipe which "upgrades" a {@linkplain IComputerItem computer}, converting to it a new item (for instance a normal
* turtle to an advanced one).
*
* @see IComputerItem#changeItem(ItemStack, Item)
*/
public final class ComputerUpgradeRecipe extends ComputerConvertRecipe {
private final Item result;
public ComputerUpgradeRecipe(ShapedRecipeSpec recipe) {
super(recipe);
this.result = recipe.result().getItem();
}
public static DataResult<ComputerUpgradeRecipe> of(ShapedRecipeSpec recipe) {
if (!(recipe.result().getItem() instanceof IComputerItem)) {
return DataResult.error(() -> recipe.result().getItem() + " is not a computer item");
}
return DataResult.success(new ComputerUpgradeRecipe(recipe));
}
@Override
protected ItemStack convert(IComputerItem item, ItemStack stack) {
return item.changeItem(stack, result);
}
@Override
public RecipeSerializer<ComputerUpgradeRecipe> getSerializer() {
return ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get();
}
}

View File

@ -7,6 +7,7 @@
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import javax.annotation.Nullable;
@ -18,6 +19,8 @@
* states, etc...
*/
public class TerminalState {
public static final StreamCodec<FriendlyByteBuf, TerminalState> STREAM_CODEC = StreamCodec.ofMember(TerminalState::write, TerminalState::new);
private final boolean colour;
private final int width;
private final int height;
@ -27,6 +30,7 @@ public class TerminalState {
public TerminalState(@Nullable NetworkedTerminal terminal) {
if (terminal == null) {
// TODO: Make this non-nullable, and have an Optional<...> codec.
colour = false;
width = height = 0;
buffer = null;
@ -40,7 +44,7 @@ public TerminalState(@Nullable NetworkedTerminal terminal) {
}
}
public TerminalState(FriendlyByteBuf buf) {
private TerminalState(FriendlyByteBuf buf) {
colour = buf.readBoolean();
if (buf.readBoolean()) {
@ -55,7 +59,7 @@ public TerminalState(FriendlyByteBuf buf) {
}
}
public void write(FriendlyByteBuf buf) {
private void write(FriendlyByteBuf buf) {
buf.writeBoolean(colour);
buf.writeBoolean(buffer != null);

View File

@ -10,19 +10,19 @@
import net.minecraft.world.item.ItemStack;
/**
* A basic implementation of {@link Container} which operates on a {@linkplain #getContents() list of stacks}.
* A basic implementation of {@link Container} which operates on a {@linkplain #getItems() list of stacks}.
*/
public interface BasicContainer extends Container {
NonNullList<ItemStack> getContents();
NonNullList<ItemStack> getItems();
@Override
default int getContainerSize() {
return getContents().size();
return getItems().size();
}
@Override
default boolean isEmpty() {
for (var stack : getContents()) {
for (var stack : getItems()) {
if (!stack.isEmpty()) return false;
}
@ -31,27 +31,33 @@ default boolean isEmpty() {
@Override
default ItemStack getItem(int slot) {
var contents = getContents();
var contents = getItems();
return slot >= 0 && slot < contents.size() ? contents.get(slot) : ItemStack.EMPTY;
}
@Override
default ItemStack removeItemNoUpdate(int slot) {
return ContainerHelper.takeItem(getContents(), slot);
return ContainerHelper.takeItem(getItems(), slot);
}
@Override
default ItemStack removeItem(int slot, int count) {
return ContainerHelper.removeItem(getContents(), slot, count);
return ContainerHelper.removeItem(getItems(), slot, count);
}
@Override
default void setItem(int slot, ItemStack itemStack) {
getContents().set(slot, itemStack);
getItems().set(slot, itemStack);
}
@Override
default void clearContent() {
getContents().clear();
getItems().clear();
}
static void defaultSetItems(NonNullList<ItemStack> inventory, NonNullList<ItemStack> items) {
var i = 0;
for (; i < items.size(); i++) inventory.set(i, items.get(i));
for (; i < inventory.size(); i++) inventory.set(i, ItemStack.EMPTY);
}
}

View File

@ -11,7 +11,7 @@
import javax.annotation.Nullable;
/**
* A basic implementation of {@link WorldlyContainer} which operates on a {@linkplain #getContents() list of stacks}.
* A basic implementation of {@link WorldlyContainer} which operates on a {@linkplain #getItems() list of stacks}.
*/
public interface BasicWorldlyContainer extends BasicContainer, WorldlyContainer {
@Override

View File

@ -4,20 +4,23 @@
package dan200.computercraft.shared.details;
import com.google.gson.JsonParseException;
import dan200.computercraft.shared.util.NBTUtil;
import net.minecraft.core.component.DataComponentPatch;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.Tag;
import net.minecraft.nbt.NbtOps;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.CreativeModeTabs;
import net.minecraft.world.item.EnchantedBookItem;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.item.enchantment.ItemEnchantments;
import javax.annotation.Nullable;
import java.util.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Data providers for items.
@ -26,7 +29,9 @@ public class ItemDetails {
public static void fillBasic(Map<? super String, Object> data, ItemStack stack) {
data.put("name", DetailHelpers.getId(BuiltInRegistries.ITEM, stack.getItem()));
data.put("count", stack.getCount());
var hash = NBTUtil.getNBTHash(stack.getTag());
var components = stack.getComponentsPatch();
var hash = components.isEmpty() ? null : NBTUtil.getNBTHash(DataComponentPatch.CODEC.encodeStart(NbtOps.INSTANCE, components).result().orElse(null));
if (hash != null) data.put("nbt", hash);
}
@ -46,41 +51,14 @@ public static void fill(Map<? super String, Object> data, ItemStack stack) {
data.put("tags", DetailHelpers.getTags(stack.getTags()));
data.put("itemGroups", getItemGroups(stack));
var tag = stack.getTag();
if (tag != null && tag.contains("display", Tag.TAG_COMPOUND)) {
var displayTag = tag.getCompound("display");
if (displayTag.contains("Lore", Tag.TAG_LIST)) {
var loreTag = displayTag.getList("Lore", Tag.TAG_STRING);
data.put("lore", loreTag.stream()
.map(ItemDetails::parseTextComponent)
.filter(Objects::nonNull)
.map(Component::getString)
.toList());
}
}
var lore = stack.get(DataComponents.LORE);
if (lore != null) data.put("lore", lore.lines().stream().map(Component::getString).toList());
/*
* Used to hide some data from ItemStack tooltip.
* @see https://minecraft.wiki/w/Tutorials/Command_NBT_tags
* @see ItemStack#getTooltip
*/
var hideFlags = tag != null ? tag.getInt("HideFlags") : 0;
var enchants = getAllEnchants(stack, hideFlags);
var enchants = getAllEnchants(stack);
if (!enchants.isEmpty()) data.put("enchantments", enchants);
if (tag != null && tag.getBoolean("Unbreakable") && (hideFlags & 4) == 0) {
data.put("unbreakable", true);
}
}
@Nullable
private static Component parseTextComponent(Tag x) {
try {
return Component.Serializer.fromJson(x.getAsString());
} catch (JsonParseException e) {
return null;
}
var unbreakable = stack.get(DataComponents.UNBREAKABLE);
if (unbreakable != null && unbreakable.showInTooltip()) data.put("unbreakable", true);
}
/**
@ -107,26 +85,13 @@ private static List<Map<String, Object>> getItemGroups(ItemStack stack) {
/**
* Retrieve all visible enchantments from given stack. Try to follow all tooltip rules : order and visibility.
*
* @param stack Stack to analyse
* @param hideFlags An int used as bit field to provide visibility rules.
* @param stack Stack to analyse
* @return A filled list that contain all visible enchantments.
*/
private static List<Map<String, Object>> getAllEnchants(ItemStack stack, int hideFlags) {
private static List<Map<String, Object>> getAllEnchants(ItemStack stack) {
var enchants = new ArrayList<Map<String, Object>>(0);
if (stack.getItem() instanceof EnchantedBookItem && (hideFlags & 32) == 0) {
addEnchantments(EnchantedBookItem.getEnchantments(stack), enchants);
}
if (stack.isEnchanted() && (hideFlags & 1) == 0) {
/*
* Mimic the EnchantmentHelper.getEnchantments(ItemStack stack) behavior without special case for Enchanted book.
* I'll do that to have the same data than ones displayed in tooltip.
* @see EnchantmentHelper.getEnchantments(ItemStack stack)
*/
addEnchantments(stack.getEnchantmentTags(), enchants);
}
addEnchantments(stack.get(DataComponents.STORED_ENCHANTMENTS), enchants);
addEnchantments(stack.get(DataComponents.ENCHANTMENTS), enchants);
return enchants;
}
@ -138,18 +103,18 @@ private static List<Map<String, Object>> getAllEnchants(ItemStack stack, int hid
* @see EnchantmentHelper
*/
@SuppressWarnings("NonApiType")
private static void addEnchantments(ListTag rawEnchants, ArrayList<Map<String, Object>> enchants) {
if (rawEnchants.isEmpty()) return;
private static void addEnchantments(@Nullable ItemEnchantments rawEnchants, ArrayList<Map<String, Object>> enchants) {
if (rawEnchants == null || rawEnchants.isEmpty()) return;
enchants.ensureCapacity(enchants.size() + rawEnchants.size());
for (var entry : EnchantmentHelper.deserializeEnchantments(rawEnchants).entrySet()) {
for (var entry : rawEnchants.entrySet()) {
var enchantment = entry.getKey();
var level = entry.getValue();
var level = entry.getIntValue();
var enchant = new HashMap<String, Object>(3);
enchant.put("name", DetailHelpers.getId(BuiltInRegistries.ENCHANTMENT, enchantment));
enchant.put("name", DetailHelpers.getId(BuiltInRegistries.ENCHANTMENT, enchantment.value()));
enchant.put("level", level);
enchant.put("displayName", enchantment.getFullname(level).getString());
enchant.put("displayName", enchantment.value().getFullname(level).getString());
enchants.add(enchant);
}
}

View File

@ -11,6 +11,7 @@
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
import dan200.computercraft.shared.turtle.items.TurtleItem;
import dan200.computercraft.shared.util.DataComponentUtil;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
@ -54,14 +55,14 @@ public static List<ItemStack> getExtraStacks() {
for (var turtleSupplier : TURTLES) {
var turtle = turtleSupplier.get();
for (var upgrade : TurtleUpgrades.instance().getUpgrades()) {
upgradeItems.add(turtle.create(-1, null, -1, null, UpgradeData.ofDefault(upgrade), 0, null));
upgradeItems.add(DataComponentUtil.createStack(turtle, ModRegistry.DataComponents.RIGHT_TURTLE_UPGRADE.get(), UpgradeData.ofDefault(upgrade)));
}
}
for (var pocketSupplier : POCKET_COMPUTERS) {
var pocket = pocketSupplier.get();
for (var upgrade : PocketUpgrades.instance().getUpgrades()) {
upgradeItems.add(pocket.create(-1, null, -1, UpgradeData.ofDefault(upgrade)));
upgradeItems.add(DataComponentUtil.createStack(pocket, ModRegistry.DataComponents.POCKET_UPGRADE.get(), UpgradeData.ofDefault(upgrade)));
}
}

View File

@ -11,8 +11,10 @@
import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.impl.PocketUpgrades;
import dan200.computercraft.impl.TurtleUpgrades;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
import dan200.computercraft.shared.turtle.items.TurtleItem;
import dan200.computercraft.shared.util.DataComponentUtil;
import net.minecraft.core.NonNullList;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
@ -105,10 +107,10 @@ public boolean isUpgrade(ItemStack stack) {
public List<T> findRecipesWithInput(ItemStack stack) {
setupCache();
if (stack.getItem() instanceof TurtleItem item) {
if (stack.getItem() instanceof TurtleItem) {
// Suggest possible upgrades which can be applied to this turtle
var left = item.getUpgradeWithData(stack, TurtleSide.LEFT);
var right = item.getUpgradeWithData(stack, TurtleSide.RIGHT);
var left = TurtleItem.getUpgradeWithData(stack, TurtleSide.LEFT);
var right = TurtleItem.getUpgradeWithData(stack, TurtleSide.RIGHT);
if (left != null && right != null) return List.of();
List<T> recipes = new ArrayList<>();
@ -176,11 +178,11 @@ public List<T> findRecipesWithInput(ItemStack stack) {
*/
public List<T> findRecipesWithOutput(ItemStack stack) {
// Find which upgrade this item currently has, and so how we could build it.
if (stack.getItem() instanceof TurtleItem item) {
if (stack.getItem() instanceof TurtleItem) {
List<T> recipes = new ArrayList<>(0);
var left = item.getUpgradeWithData(stack, TurtleSide.LEFT);
var right = item.getUpgradeWithData(stack, TurtleSide.RIGHT);
var left = TurtleItem.getUpgradeWithData(stack, TurtleSide.LEFT);
var right = TurtleItem.getUpgradeWithData(stack, TurtleSide.RIGHT);
// The turtle is facing towards us, so upgrades on the left are actually crafted on the right.
if (left != null) {
@ -215,18 +217,16 @@ public List<T> findRecipesWithOutput(ItemStack stack) {
}
private static ItemStack turtleWith(ItemStack stack, @Nullable UpgradeData<ITurtleUpgrade> left, @Nullable UpgradeData<ITurtleUpgrade> right) {
var item = (TurtleItem) stack.getItem();
return item.create(
item.getComputerID(stack), item.getLabel(stack), item.getColour(stack),
left, right, item.getFuelLevel(stack), item.getOverlay(stack)
);
var newStack = stack.copyWithCount(1);
newStack.set(ModRegistry.DataComponents.LEFT_TURTLE_UPGRADE.get(), left);
newStack.set(ModRegistry.DataComponents.RIGHT_TURTLE_UPGRADE.get(), right);
return stack;
}
private static ItemStack pocketWith(ItemStack stack, @Nullable UpgradeData<IPocketUpgrade> back) {
var item = (PocketComputerItem) stack.getItem();
return item.create(
item.getComputerID(stack), item.getLabel(stack), item.getColour(stack), back
);
var newStack = stack.copyWithCount(1);
newStack.set(ModRegistry.DataComponents.POCKET_UPGRADE.get(), back);
return stack;
}
private T pocket(Ingredient upgrade, Ingredient pocketComputer, ItemStack result) {
@ -278,8 +278,8 @@ List<T> getRecipes() {
var turtleItem = turtleSupplier.get();
recipes.add(turtle(
ingredient, // Right upgrade, recipe on left
Ingredient.of(turtleItem.create(-1, null, -1, null, null, 0, null)),
turtleItem.create(-1, null, -1, null, UpgradeData.ofDefault(turtle), 0, null)
Ingredient.of(new ItemStack(turtleItem)),
DataComponentUtil.createStack(turtleItem, ModRegistry.DataComponents.RIGHT_TURTLE_UPGRADE.get(), UpgradeData.ofDefault(turtle))
));
}
}
@ -289,8 +289,8 @@ List<T> getRecipes() {
var pocketItem = pocketSupplier.get();
recipes.add(pocket(
ingredient,
Ingredient.of(pocketItem.create(-1, null, -1, null)),
pocketItem.create(-1, null, -1, UpgradeData.ofDefault(pocket))
Ingredient.of(pocketItem),
DataComponentUtil.createStack(pocketItem, ModRegistry.DataComponents.POCKET_UPGRADE.get(), UpgradeData.ofDefault(pocket))
));
}
}

View File

@ -70,14 +70,11 @@ public void onRuntimeAvailable(IJeiRuntime runtime) {
* Distinguishes turtles by upgrades and family.
*/
private static final IIngredientSubtypeInterpreter<ItemStack> turtleSubtype = (stack, ctx) -> {
var item = stack.getItem();
if (!(item instanceof TurtleItem turtle)) return IIngredientSubtypeInterpreter.NONE;
var name = new StringBuilder("turtle:");
// Add left and right upgrades to the identifier
var left = turtle.getUpgrade(stack, TurtleSide.LEFT);
var right = turtle.getUpgrade(stack, TurtleSide.RIGHT);
var left = TurtleItem.getUpgrade(stack, TurtleSide.LEFT);
var right = TurtleItem.getUpgrade(stack, TurtleSide.RIGHT);
if (left != null) name.append(left.getUpgradeID());
if (left != null && right != null) name.append('|');
if (right != null) name.append(right.getUpgradeID());
@ -89,9 +86,6 @@ public void onRuntimeAvailable(IJeiRuntime runtime) {
* Distinguishes pocket computers by upgrade and family.
*/
private static final IIngredientSubtypeInterpreter<ItemStack> pocketSubtype = (stack, ctx) -> {
var item = stack.getItem();
if (!(item instanceof PocketComputerItem)) return IIngredientSubtypeInterpreter.NONE;
var name = new StringBuilder("pocket:");
// Add the upgrade to the identifier
@ -104,11 +98,5 @@ public void onRuntimeAvailable(IJeiRuntime runtime) {
/**
* Distinguishes disks by colour.
*/
private static final IIngredientSubtypeInterpreter<ItemStack> diskSubtype = (stack, ctx) -> {
var item = stack.getItem();
if (!(item instanceof DiskItem disk)) return IIngredientSubtypeInterpreter.NONE;
var colour = disk.getColour(stack);
return colour == -1 ? IIngredientSubtypeInterpreter.NONE : String.format("%06x", colour);
};
private static final IIngredientSubtypeInterpreter<ItemStack> diskSubtype = (stack, ctx) -> Integer.toString(DiskItem.getColour(stack));
}

View File

@ -10,39 +10,29 @@
import dan200.computercraft.api.media.IMedia;
import dan200.computercraft.core.util.Colour;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.common.IColouredItem;
import dan200.computercraft.shared.config.Config;
import net.minecraft.ChatFormatting;
import net.minecraft.core.BlockPos;
import net.minecraft.core.component.DataComponents;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.Level;
import net.minecraft.world.item.component.DyedItemColor;
import net.minecraft.world.level.LevelReader;
import javax.annotation.Nullable;
import java.util.List;
public class DiskItem extends Item implements IMedia, IColouredItem {
private static final String NBT_ID = "DiskId";
public class DiskItem extends Item implements IMedia {
public DiskItem(Properties settings) {
super(settings);
}
public static ItemStack createFromIDAndColour(int id, @Nullable String label, int colour) {
var stack = new ItemStack(ModRegistry.Items.DISK.get());
setDiskID(stack, id);
ModRegistry.Items.DISK.get().setLabel(stack, label);
IColouredItem.setColourBasic(stack, colour);
return stack;
}
@Override
public void appendHoverText(ItemStack stack, @Nullable Level world, List<Component> list, TooltipFlag options) {
public void appendHoverText(ItemStack stack, TooltipContext context, List<Component> list, TooltipFlag options) {
if (options.isAdvanced()) {
var id = getDiskID(stack);
if (id >= 0) {
@ -59,16 +49,13 @@ public boolean doesSneakBypassUse(ItemStack stack, LevelReader world, BlockPos p
@Override
public @Nullable String getLabel(ItemStack stack) {
return stack.hasCustomHoverName() ? stack.getHoverName().getString() : null;
var label = stack.get(DataComponents.CUSTOM_NAME);
return label != null ? label.getString() : null;
}
@Override
public boolean setLabel(ItemStack stack, @Nullable String label) {
if (label != null) {
stack.setHoverName(Component.literal(label));
} else {
stack.resetHoverName();
}
stack.set(DataComponents.CUSTOM_NAME, label != null ? Component.literal(label) : null);
return true;
}
@ -77,23 +64,17 @@ public boolean setLabel(ItemStack stack, @Nullable String label) {
var diskID = getDiskID(stack);
if (diskID < 0) {
diskID = ComputerCraftAPI.createUniqueNumberedSaveDir(level.getServer(), "disk");
setDiskID(stack, diskID);
stack.set(ModRegistry.DataComponents.DISK.get(), diskID);
}
return ComputerCraftAPI.createSaveDirMount(level.getServer(), "disk/" + diskID, Config.floppySpaceLimit);
}
public static int getDiskID(ItemStack stack) {
var nbt = stack.getTag();
return nbt != null && nbt.contains(NBT_ID) ? nbt.getInt(NBT_ID) : -1;
var id = stack.get(ModRegistry.DataComponents.DISK.get());
return id != null ? id : -1;
}
private static void setDiskID(ItemStack stack, int id) {
if (id >= 0) stack.getOrCreateTag().putInt(NBT_ID, id);
}
@Override
public int getColour(ItemStack stack) {
var colour = IColouredItem.getColourBasic(stack);
return colour == -1 ? Colour.WHITE.getHex() : colour;
public static int getColour(ItemStack stack) {
return DyedItemColor.getOrDefault(stack, Colour.WHITE.getARGB());
}
}

View File

@ -0,0 +1,67 @@
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.media.items;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import dan200.computercraft.core.terminal.Terminal;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import java.util.Arrays;
import java.util.List;
public record PrintoutData(String title, List<Line> lines) {
public static final PrintoutData EMPTY;
static {
var lines = new Line[PrintoutItem.LINES_PER_PAGE];
Arrays.fill(lines, Line.EMPTY);
EMPTY = new PrintoutData("", List.of(lines));
}
private static final Codec<Line> LINE_CODEC = RecordCodecBuilder.<Line>create(s -> s.group(
Codec.STRING.fieldOf("text").forGetter(Line::text),
Codec.STRING.fieldOf("foreground").forGetter(Line::foreground)
).apply(s, Line::new)).validate(x -> x.text().length() == x.foreground().length() ? DataResult.success(x) : DataResult.error(() -> "Text and foreground are different lengths"));
private static final StreamCodec<ByteBuf, Line> LINE_STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.STRING_UTF8, Line::text,
ByteBufCodecs.STRING_UTF8, Line::foreground,
Line::new
);
public static final Codec<PrintoutData> CODEC = RecordCodecBuilder.<PrintoutData>create(s -> s.group(
Codec.STRING.optionalFieldOf("title", "").forGetter(PrintoutData::title),
LINE_CODEC.listOf(1, PrintoutItem.MAX_PAGES * PrintoutItem.LINES_PER_PAGE).fieldOf("lines").forGetter(PrintoutData::lines)
).apply(s, PrintoutData::new)).validate(x -> (x.lines().size() % PrintoutItem.LINES_PER_PAGE) == 0 ? DataResult.success(x) : DataResult.error(() -> "Not enough lines for a page."));
public static final StreamCodec<ByteBuf, PrintoutData> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.STRING_UTF8, PrintoutData::title,
LINE_STREAM_CODEC.apply(ByteBufCodecs.list(PrintoutItem.MAX_PAGES * PrintoutItem.LINES_PER_PAGE)), PrintoutData::lines,
PrintoutData::new
);
/**
* A single line on our printed pages.
*
* @param text The text for this line.
* @param foreground The foreground colour of this line, in a format equivalent to {@link Terminal#getTextColourLine(int)}.
*/
public record Line(String text, String foreground) {
public static final Line EMPTY = new Line("", "");
}
/**
* Get the number of pages in this printout.
*
* @return The number of pages.
*/
public int pages() {
return Math.ceilDiv(lines.size(), PrintoutItem.LINES_PER_PAGE);
}
}

View File

@ -17,15 +17,9 @@
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.Level;
import javax.annotation.Nullable;
import java.util.List;
public class PrintoutItem extends Item {
private static final String NBT_TITLE = "Title";
private static final String NBT_PAGES = "Pages";
private static final String NBT_LINE_TEXT = "Text";
private static final String NBT_LINE_COLOUR = "Color";
public static final int LINES_PER_PAGE = 21;
public static final int LINE_MAX_LENGTH = 25;
public static final int MAX_PAGES = 16;
@ -44,9 +38,9 @@ public PrintoutItem(Properties settings, Type type) {
}
@Override
public void appendHoverText(ItemStack stack, @Nullable Level world, List<Component> list, TooltipFlag options) {
public void appendHoverText(ItemStack stack, TooltipContext context, List<Component> list, TooltipFlag options) {
var title = getTitle(stack);
if (title != null && !title.isEmpty()) list.add(Component.literal(title));
if (!title.isEmpty()) list.add(Component.literal(title));
}
@Override
@ -58,70 +52,17 @@ public InteractionResultHolder<ItemStack> use(Level world, Player player, Intera
return new InteractionResultHolder<>(InteractionResult.sidedSuccess(world.isClientSide), player.getItemInHand(hand));
}
private ItemStack createFromTitleAndText(@Nullable String title, @Nullable String[] text, @Nullable String[] colours) {
var stack = new ItemStack(this);
// Build NBT
if (title != null) stack.getOrCreateTag().putString(NBT_TITLE, title);
if (text != null) {
var tag = stack.getOrCreateTag();
tag.putInt(NBT_PAGES, text.length / LINES_PER_PAGE);
for (var i = 0; i < text.length; i++) {
if (text[i] != null) tag.putString(NBT_LINE_TEXT + i, text[i]);
}
}
if (colours != null) {
var tag = stack.getOrCreateTag();
for (var i = 0; i < colours.length; i++) {
if (colours[i] != null) tag.putString(NBT_LINE_COLOUR + i, colours[i]);
}
}
return stack;
}
public static ItemStack createSingleFromTitleAndText(@Nullable String title, @Nullable String[] text, @Nullable String[] colours) {
return ModRegistry.Items.PRINTED_PAGE.get().createFromTitleAndText(title, text, colours);
}
public static ItemStack createMultipleFromTitleAndText(@Nullable String title, @Nullable String[] text, @Nullable String[] colours) {
return ModRegistry.Items.PRINTED_PAGES.get().createFromTitleAndText(title, text, colours);
}
public static ItemStack createBookFromTitleAndText(@Nullable String title, @Nullable String[] text, @Nullable String[] colours) {
return ModRegistry.Items.PRINTED_BOOK.get().createFromTitleAndText(title, text, colours);
}
public Type getType() {
return type;
}
public static String getTitle(ItemStack stack) {
var nbt = stack.getTag();
return nbt != null && nbt.contains(NBT_TITLE) ? nbt.getString(NBT_TITLE) : "";
var nbt = stack.get(ModRegistry.DataComponents.PRINTOUT.get());
return nbt == null ? "" : nbt.title();
}
public static int getPageCount(ItemStack stack) {
var nbt = stack.getTag();
return nbt != null && nbt.contains(NBT_PAGES) ? nbt.getInt(NBT_PAGES) : 1;
}
public static String[] getText(ItemStack stack) {
return getLines(stack, NBT_LINE_TEXT);
}
public static String[] getColours(ItemStack stack) {
return getLines(stack, NBT_LINE_COLOUR);
}
private static String[] getLines(ItemStack stack, String prefix) {
var nbt = stack.getTag();
var numLines = getPageCount(stack) * LINES_PER_PAGE;
var lines = new String[numLines];
for (var i = 0; i < lines.length; i++) {
lines[i] = nbt != null ? nbt.getString(prefix + i) : "";
}
return lines;
var nbt = stack.get(ModRegistry.DataComponents.PRINTOUT.get());
return nbt == null ? 1 : nbt.pages();
}
}

View File

@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.media.items;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import dan200.computercraft.shared.ModRegistry;
import net.minecraft.core.component.DataComponentHolder;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
/**
* Component for {@linkplain TreasureDiskItem treasure disks}.
*
* @param name The name/title of the disk.
* @param path The subpath to the resource
*/
public record TreasureDisk(String name, String path) {
public static final Codec<TreasureDisk> CODEC = RecordCodecBuilder.create(i -> i.group(
Codec.STRING.fieldOf("name").forGetter(TreasureDisk::name),
Codec.STRING.fieldOf("path").forGetter(TreasureDisk::path)
).apply(i, TreasureDisk::new));
public static final StreamCodec<FriendlyByteBuf, TreasureDisk> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.STRING_UTF8, TreasureDisk::name,
ByteBufCodecs.STRING_UTF8, TreasureDisk::path,
TreasureDisk::new
);
public static String getTitle(DataComponentHolder holder) {
var nbt = holder.get(ModRegistry.DataComponents.TREASURE_DISK.get());
return nbt != null ? nbt.name() : "'missingno' by how did you get this anyway?";
}
}

View File

@ -9,7 +9,6 @@
import dan200.computercraft.api.filesystem.Mount;
import dan200.computercraft.api.media.IMedia;
import dan200.computercraft.core.filesystem.SubMount;
import dan200.computercraft.core.util.Colour;
import dan200.computercraft.shared.ModRegistry;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
@ -18,7 +17,6 @@
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import javax.annotation.Nullable;
@ -26,18 +24,13 @@
import java.util.List;
public class TreasureDiskItem extends Item implements IMedia {
private static final String NBT_TITLE = "Title";
private static final String NBT_COLOUR = "Colour";
private static final String NBT_SUB_PATH = "SubPath";
public TreasureDiskItem(Properties settings) {
super(settings);
}
@Override
public void appendHoverText(ItemStack stack, @Nullable Level world, List<Component> list, TooltipFlag tooltipOptions) {
var label = getTitle(stack);
if (!label.isEmpty()) list.add(Component.literal(label));
public void appendHoverText(ItemStack stack, TooltipContext context, List<Component> list, TooltipFlag tooltipOptions) {
list.add(Component.literal(TreasureDisk.getTitle(stack)));
}
@ForgeOverride
@ -47,7 +40,7 @@ public boolean doesSneakBypassUse(ItemStack stack, LevelReader world, BlockPos p
@Override
public String getLabel(ItemStack stack) {
return getTitle(stack);
return TreasureDisk.getTitle(stack);
}
@Override
@ -55,7 +48,10 @@ public String getLabel(ItemStack stack) {
var rootTreasure = ComputerCraftAPI.createResourceMount(level.getServer(), "computercraft", "lua/treasure");
if (rootTreasure == null) return null;
var subPath = getSubPath(stack);
var treasureDisk = stack.get(ModRegistry.DataComponents.TREASURE_DISK.get());
if (treasureDisk == null) return null;
var subPath = treasureDisk.path();
try {
if (rootTreasure.exists(subPath)) {
return new SubMount(rootTreasure, subPath);
@ -68,37 +64,4 @@ public String getLabel(ItemStack stack) {
return null;
}
}
public static ItemStack create(String subPath, int colourIndex) {
var result = new ItemStack(ModRegistry.Items.TREASURE_DISK.get());
var nbt = result.getOrCreateTag();
nbt.putString(NBT_SUB_PATH, subPath);
var slash = subPath.indexOf('/');
if (slash >= 0) {
var author = subPath.substring(0, slash);
var title = subPath.substring(slash + 1);
nbt.putString(NBT_TITLE, "\"" + title + "\" by " + author);
} else {
nbt.putString(NBT_TITLE, "untitled");
}
nbt.putInt(NBT_COLOUR, Colour.values()[colourIndex].getHex());
return result;
}
private static String getTitle(ItemStack stack) {
var nbt = stack.getTag();
return nbt != null && nbt.contains(NBT_TITLE) ? nbt.getString(NBT_TITLE) : "'missingno' by how did you get this anyway?";
}
private static String getSubPath(ItemStack stack) {
var nbt = stack.getTag();
return nbt != null && nbt.contains(NBT_SUB_PATH) ? nbt.getString(NBT_SUB_PATH) : "dan200/alongtimeago";
}
public static int getColour(ItemStack stack) {
var nbt = stack.getTag();
return nbt != null && nbt.contains(NBT_COLOUR) ? nbt.getInt(NBT_COLOUR) : Colour.BLUE.getHex();
}
}

View File

@ -6,14 +6,16 @@
import dan200.computercraft.core.util.Colour;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.media.items.DiskItem;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.util.ColourTracker;
import dan200.computercraft.shared.util.ColourUtils;
import net.minecraft.core.RegistryAccess;
import dan200.computercraft.shared.util.DataComponentUtil;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.component.DataComponents;
import net.minecraft.world.inventory.CraftingContainer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.component.DyedItemColor;
import net.minecraft.world.item.crafting.CraftingBookCategory;
import net.minecraft.world.item.crafting.CustomRecipe;
import net.minecraft.world.item.crafting.Ingredient;
@ -53,7 +55,7 @@ public boolean matches(CraftingContainer inv, Level world) {
}
@Override
public ItemStack assemble(CraftingContainer inv, RegistryAccess registryAccess) {
public ItemStack assemble(CraftingContainer inv, HolderLookup.Provider registryAccess) {
var tracker = new ColourTracker();
for (var i = 0; i < inv.getContainerSize(); i++) {
@ -67,7 +69,7 @@ public ItemStack assemble(CraftingContainer inv, RegistryAccess registryAccess)
}
}
return DiskItem.createFromIDAndColour(-1, null, tracker.hasColour() ? tracker.getColour() : Colour.BLUE.getHex());
return DataComponentUtil.createStack(ModRegistry.Items.DISK.get(), DataComponents.DYED_COLOR, new DyedItemColor(tracker.hasColour() ? tracker.getColour() : Colour.BLUE.getHex(), false));
}
@Override
@ -76,8 +78,8 @@ public boolean canCraftInDimensions(int x, int y) {
}
@Override
public ItemStack getResultItem(RegistryAccess registryAccess) {
return DiskItem.createFromIDAndColour(-1, null, Colour.BLUE.getHex());
public ItemStack getResultItem(HolderLookup.Provider registryAccess) {
return DataComponentUtil.createStack(ModRegistry.Items.DISK.get(), DataComponents.DYED_COLOR, new DyedItemColor(Colour.BLUE.getHex(), false));
}
@Override

View File

@ -5,9 +5,11 @@
package dan200.computercraft.shared.media.recipes;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.media.items.PrintoutData;
import dan200.computercraft.shared.media.items.PrintoutItem;
import dan200.computercraft.shared.platform.PlatformHelper;
import net.minecraft.core.RegistryAccess;
import dan200.computercraft.shared.util.DataComponentUtil;
import net.minecraft.core.HolderLookup;
import net.minecraft.world.inventory.CraftingContainer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
@ -17,6 +19,8 @@
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.level.Level;
import java.util.List;
public final class PrintoutRecipe extends CustomRecipe {
private final Ingredient leather;
private final Ingredient string;
@ -35,8 +39,8 @@ public boolean canCraftInDimensions(int x, int y) {
}
@Override
public ItemStack getResultItem(RegistryAccess registryAccess) {
return PrintoutItem.createMultipleFromTitleAndText(null, null, null);
public ItemStack getResultItem(HolderLookup.Provider registryAccess) {
return new ItemStack(ModRegistry.Items.PRINTED_PAGES.get());
}
@Override
@ -45,7 +49,7 @@ public boolean matches(CraftingContainer inventory, Level world) {
}
@Override
public ItemStack assemble(CraftingContainer inventory, RegistryAccess registryAccess) {
public ItemStack assemble(CraftingContainer inventory, HolderLookup.Provider registryAccess) {
// See if we match the recipe, and extract the input disk ID and dye colour
var numPages = 0;
var numPrintouts = 0;
@ -84,41 +88,28 @@ public ItemStack assemble(CraftingContainer inventory, RegistryAccess registryAc
// Build some pages with what was passed in
if (numPages <= PrintoutItem.MAX_PAGES && stringFound && printoutFound && numPrintouts >= (leatherFound ? 1 : 2)) {
if (printouts == null) throw new IllegalStateException("Printouts must be non-null");
var text = new String[numPages * PrintoutItem.LINES_PER_PAGE];
var colours = new String[numPages * PrintoutItem.LINES_PER_PAGE];
var lines = new PrintoutData.Line[numPages * PrintoutItem.LINES_PER_PAGE];
var line = 0;
for (var printout = 0; printout < numPrintouts; printout++) {
var stack = printouts[printout];
if (stack.getItem() instanceof PrintoutItem) {
var pageText = printouts[printout].get(ModRegistry.DataComponents.PRINTOUT.get());
if (pageText != null) {
// Add a printout
var pageText = PrintoutItem.getText(printouts[printout]);
var pageColours = PrintoutItem.getColours(printouts[printout]);
for (var pageLine = 0; pageLine < pageText.length; pageLine++) {
text[line] = pageText[pageLine];
colours[line] = pageColours[pageLine];
line++;
}
for (var pageLine : pageText.lines()) lines[line++] = pageLine;
} else {
// Add a blank page
for (var pageLine = 0; pageLine < PrintoutItem.LINES_PER_PAGE; pageLine++) {
text[line] = "";
colours[line] = "";
line++;
lines[line++] = PrintoutData.Line.EMPTY;
}
}
}
String title = null;
if (printouts[0].getItem() instanceof PrintoutItem) {
title = PrintoutItem.getTitle(printouts[0]);
}
var title = PrintoutItem.getTitle(printouts[0]);
if (leatherFound) {
return PrintoutItem.createBookFromTitleAndText(title, text, colours);
} else {
return PrintoutItem.createMultipleFromTitleAndText(title, text, colours);
}
return DataComponentUtil.createStack(
leatherFound ? ModRegistry.Items.PRINTED_BOOK.get() : ModRegistry.Items.PRINTED_PAGES.get(),
ModRegistry.DataComponents.PRINTOUT.get(), new PrintoutData(title, List.of(lines))
);
}
return ItemStack.EMPTY;

View File

@ -1,28 +0,0 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.network;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
/**
* A type of message to send over the network.
* <p>
* Much like recipe or argument serialisers, each type of {@link NetworkMessage} should have a unique type associated
* with it. This holds platform-specific information about how the packet should be sent over the network.
*
* @param <T> The type of message to send
* @see NetworkMessages
* @see NetworkMessage#type()
*/
public interface MessageType<T extends NetworkMessage<?>> {
/**
* Get the id of this message type. This will be used as the custom packet channel name.
*
* @return The id of this message type.
* @see CustomPacketPayload#id()
*/
ResourceLocation id();
}

View File

@ -6,8 +6,7 @@
import dan200.computercraft.shared.network.client.ClientNetworkContext;
import dan200.computercraft.shared.network.server.ServerNetworkContext;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
/**
* The base interface for any message which will be sent to the client or server.
@ -16,23 +15,7 @@
* @see ClientNetworkContext
* @see ServerNetworkContext
*/
public interface NetworkMessage<T> {
/**
* Get the type of this message.
*
* @return The type of this message.
*/
MessageType<?> type();
/**
* Write this packet to a buffer.
* <p>
* This may be called on any thread, so this should be a pure operation.
*
* @param buf The buffer to write data to.
*/
void write(FriendlyByteBuf buf);
public interface NetworkMessage<T> extends CustomPacketPayload {
/**
* Handle this {@link NetworkMessage}.
*

View File

@ -8,59 +8,61 @@
import dan200.computercraft.shared.network.client.*;
import dan200.computercraft.shared.network.server.*;
import dan200.computercraft.shared.platform.PlatformHelper;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import java.util.*;
/**
* List of all {@link MessageType}s provided by CC: Tweaked.
* List of all {@link CustomPacketPayload.Type}s provided by CC: Tweaked.
*
* @see PlatformHelper The platform helper is used to send packets.
*/
public final class NetworkMessages {
private static final Set<String> seenChannel = new HashSet<>();
private static final List<MessageType<? extends NetworkMessage<ServerNetworkContext>>> serverMessages = new ArrayList<>();
private static final List<MessageType<? extends NetworkMessage<ClientNetworkContext>>> clientMessages = new ArrayList<>();
private static final List<CustomPacketPayload.TypeAndCodec<RegistryFriendlyByteBuf, ? extends NetworkMessage<ServerNetworkContext>>> serverMessages = new ArrayList<>();
private static final List<CustomPacketPayload.TypeAndCodec<RegistryFriendlyByteBuf, ? extends NetworkMessage<ClientNetworkContext>>> clientMessages = new ArrayList<>();
public static final MessageType<ComputerActionServerMessage> COMPUTER_ACTION = registerServerbound("computer_action", ComputerActionServerMessage::new);
public static final MessageType<QueueEventServerMessage> QUEUE_EVENT = registerServerbound("queue_event", QueueEventServerMessage::new);
public static final MessageType<KeyEventServerMessage> KEY_EVENT = registerServerbound("key_event", KeyEventServerMessage::new);
public static final MessageType<MouseEventServerMessage> MOUSE_EVENT = registerServerbound("mouse_event", MouseEventServerMessage::new);
public static final MessageType<UploadFileMessage> UPLOAD_FILE = registerServerbound("upload_file", UploadFileMessage::new);
public static final CustomPacketPayload.Type<ComputerActionServerMessage> COMPUTER_ACTION = registerServerbound("computer_action", ComputerActionServerMessage.STREAM_CODEC);
public static final CustomPacketPayload.Type<QueueEventServerMessage> QUEUE_EVENT = register(serverMessages, "queue_event", QueueEventServerMessage.STREAM_CODEC);
public static final CustomPacketPayload.Type<KeyEventServerMessage> KEY_EVENT = registerServerbound("key_event", KeyEventServerMessage.STREAM_CODEC);
public static final CustomPacketPayload.Type<MouseEventServerMessage> MOUSE_EVENT = registerServerbound("mouse_event", MouseEventServerMessage.STREAM_CODEC);
public static final CustomPacketPayload.Type<UploadFileMessage> UPLOAD_FILE = register(serverMessages, "upload_file", UploadFileMessage.STREAM_CODEC);
public static final MessageType<ChatTableClientMessage> CHAT_TABLE = registerClientbound("chat_table", ChatTableClientMessage::new);
public static final MessageType<PocketComputerDataMessage> POCKET_COMPUTER_DATA = registerClientbound("pocket_computer_data", PocketComputerDataMessage::new);
public static final MessageType<PocketComputerDeletedClientMessage> POCKET_COMPUTER_DELETED = registerClientbound("pocket_computer_deleted", PocketComputerDeletedClientMessage::new);
public static final MessageType<ComputerTerminalClientMessage> COMPUTER_TERMINAL = registerClientbound("computer_terminal", ComputerTerminalClientMessage::new);
public static final MessageType<PlayRecordClientMessage> PLAY_RECORD = registerClientbound("play_record", PlayRecordClientMessage::new);
public static final MessageType<MonitorClientMessage> MONITOR_CLIENT = registerClientbound("monitor_client", MonitorClientMessage::new);
public static final MessageType<SpeakerAudioClientMessage> SPEAKER_AUDIO = registerClientbound("speaker_audio", SpeakerAudioClientMessage::new);
public static final MessageType<SpeakerMoveClientMessage> SPEAKER_MOVE = registerClientbound("speaker_move", SpeakerMoveClientMessage::new);
public static final MessageType<SpeakerPlayClientMessage> SPEAKER_PLAY = registerClientbound("speaker_play", SpeakerPlayClientMessage::new);
public static final MessageType<SpeakerStopClientMessage> SPEAKER_STOP = registerClientbound("speaker_stop", SpeakerStopClientMessage::new);
public static final MessageType<UploadResultMessage> UPLOAD_RESULT = registerClientbound("upload_result", UploadResultMessage::new);
public static final MessageType<UpgradesLoadedMessage> UPGRADES_LOADED = registerClientbound("upgrades_loaded", UpgradesLoadedMessage::new);
public static final CustomPacketPayload.Type<ChatTableClientMessage> CHAT_TABLE = registerClientbound("chat_table", ChatTableClientMessage.STREAM_CODEC);
public static final CustomPacketPayload.Type<PocketComputerDataMessage> POCKET_COMPUTER_DATA = registerClientbound("pocket_computer_data", PocketComputerDataMessage.STREAM_CODEC);
public static final CustomPacketPayload.Type<PocketComputerDeletedClientMessage> POCKET_COMPUTER_DELETED = registerClientbound("pocket_computer_deleted", PocketComputerDeletedClientMessage.STREAM_CODEC);
public static final CustomPacketPayload.Type<ComputerTerminalClientMessage> COMPUTER_TERMINAL = registerClientbound("computer_terminal", ComputerTerminalClientMessage.STREAM_CODEC);
public static final CustomPacketPayload.Type<PlayRecordClientMessage> PLAY_RECORD = registerClientbound("play_record", PlayRecordClientMessage.STREAM_CODEC);
public static final CustomPacketPayload.Type<MonitorClientMessage> MONITOR_CLIENT = registerClientbound("monitor_client", MonitorClientMessage.STREAM_CODEC);
public static final CustomPacketPayload.Type<SpeakerAudioClientMessage> SPEAKER_AUDIO = registerClientbound("speaker_audio", SpeakerAudioClientMessage.STREAM_CODEC);
public static final CustomPacketPayload.Type<SpeakerMoveClientMessage> SPEAKER_MOVE = registerClientbound("speaker_move", SpeakerMoveClientMessage.STREAM_CODEC);
public static final CustomPacketPayload.Type<SpeakerPlayClientMessage> SPEAKER_PLAY = registerClientbound("speaker_play", SpeakerPlayClientMessage.STREAM_CODEC);
public static final CustomPacketPayload.Type<SpeakerStopClientMessage> SPEAKER_STOP = registerClientbound("speaker_stop", SpeakerStopClientMessage.STREAM_CODEC);
public static final CustomPacketPayload.Type<UploadResultMessage> UPLOAD_RESULT = registerClientbound("upload_result", UploadResultMessage.STREAM_CODEC);
public static final CustomPacketPayload.Type<UpgradesLoadedMessage> UPGRADES_LOADED = registerClientbound("upgrades_loaded", UpgradesLoadedMessage.STREAM_CODEC);
private NetworkMessages() {
}
private static <C, T extends NetworkMessage<C>> MessageType<T> register(
List<MessageType<? extends NetworkMessage<C>>> messages,
String channel, FriendlyByteBuf.Reader<T> reader
private static <C, T extends NetworkMessage<C>> CustomPacketPayload.Type<T> register(
List<CustomPacketPayload.TypeAndCodec<RegistryFriendlyByteBuf, ? extends NetworkMessage<C>>> messages,
String channel, StreamCodec<RegistryFriendlyByteBuf, T> codec
) {
if (!seenChannel.add(channel)) throw new IllegalArgumentException("Duplicate channel " + channel);
var type = PlatformHelper.get().createMessageType(new ResourceLocation(ComputerCraftAPI.MOD_ID, channel), reader);
messages.add(type);
var type = new CustomPacketPayload.Type<T>(new ResourceLocation(ComputerCraftAPI.MOD_ID, channel));
messages.add(new CustomPacketPayload.TypeAndCodec<>(type, codec));
return type;
}
private static <T extends NetworkMessage<ServerNetworkContext>> MessageType<T> registerServerbound(String id, FriendlyByteBuf.Reader<T> reader) {
return register(serverMessages, id, reader);
private static <T extends NetworkMessage<ServerNetworkContext>> CustomPacketPayload.Type<T> registerServerbound(String id, StreamCodec<RegistryFriendlyByteBuf, T> codec) {
return register(serverMessages, id, codec);
}
private static <T extends NetworkMessage<ClientNetworkContext>> MessageType<T> registerClientbound(String id, FriendlyByteBuf.Reader<T> reader) {
return register(clientMessages, id, reader);
private static <T extends NetworkMessage<ClientNetworkContext>> CustomPacketPayload.Type<T> registerClientbound(String id, StreamCodec<RegistryFriendlyByteBuf, T> codec) {
return register(clientMessages, id, codec);
}
/**
@ -68,7 +70,7 @@ private static <T extends NetworkMessage<ClientNetworkContext>> MessageType<T> r
*
* @return An unmodifiable sequence of all serverbound message types.
*/
public static Collection<MessageType<? extends NetworkMessage<ServerNetworkContext>>> getServerbound() {
public static Collection<CustomPacketPayload.TypeAndCodec<RegistryFriendlyByteBuf, ? extends NetworkMessage<ServerNetworkContext>>> getServerbound() {
return Collections.unmodifiableCollection(serverMessages);
}
@ -77,7 +79,7 @@ public static Collection<MessageType<? extends NetworkMessage<ServerNetworkConte
*
* @return An unmodifiable sequence of all clientbound message types.
*/
public static Collection<MessageType<? extends NetworkMessage<ClientNetworkContext>>> getClientbound() {
public static Collection<CustomPacketPayload.TypeAndCodec<RegistryFriendlyByteBuf, ? extends NetworkMessage<ClientNetworkContext>>> getClientbound() {
return Collections.unmodifiableCollection(clientMessages);
}
}

View File

@ -5,14 +5,18 @@
package dan200.computercraft.shared.network.client;
import dan200.computercraft.shared.command.text.TableBuilder;
import dan200.computercraft.shared.network.MessageType;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.NetworkMessages;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.ComponentSerialization;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
public class ChatTableClientMessage implements NetworkMessage<ClientNetworkContext> {
public static final StreamCodec<RegistryFriendlyByteBuf, ChatTableClientMessage> STREAM_CODEC = StreamCodec.ofMember(ChatTableClientMessage::write, ChatTableClientMessage::new);
private static final int MAX_LEN = 16;
private final TableBuilder table;
@ -21,13 +25,13 @@ public ChatTableClientMessage(TableBuilder table) {
this.table = table;
}
public ChatTableClientMessage(FriendlyByteBuf buf) {
private ChatTableClientMessage(RegistryFriendlyByteBuf buf) {
var id = buf.readUtf(MAX_LEN);
var columns = buf.readVarInt();
TableBuilder table;
if (buf.readBoolean()) {
var headers = new Component[columns];
for (var i = 0; i < columns; i++) headers[i] = buf.readComponent();
for (var i = 0; i < columns; i++) headers[i] = ComponentSerialization.STREAM_CODEC.decode(buf);
table = new TableBuilder(id, headers);
} else {
table = new TableBuilder(id);
@ -36,7 +40,7 @@ public ChatTableClientMessage(FriendlyByteBuf buf) {
var rows = buf.readVarInt();
for (var i = 0; i < rows; i++) {
var row = new Component[columns];
for (var j = 0; j < columns; j++) row[j] = buf.readComponent();
for (var j = 0; j < columns; j++) row[j] = ComponentSerialization.STREAM_CODEC.decode(buf);
table.row(row);
}
@ -44,18 +48,17 @@ public ChatTableClientMessage(FriendlyByteBuf buf) {
this.table = table;
}
@Override
public void write(FriendlyByteBuf buf) {
private void write(RegistryFriendlyByteBuf buf) {
buf.writeUtf(table.getId(), MAX_LEN);
buf.writeVarInt(table.getColumns());
buf.writeBoolean(table.getHeaders() != null);
if (table.getHeaders() != null) {
for (var header : table.getHeaders()) buf.writeComponent(header);
for (var header : table.getHeaders()) ComponentSerialization.STREAM_CODEC.encode(buf, header);
}
buf.writeVarInt(table.getRows().size());
for (var row : table.getRows()) {
for (var column : row) buf.writeComponent(column);
for (var column : row) ComponentSerialization.STREAM_CODEC.encode(buf, column);
}
buf.writeVarInt(table.getAdditional());
@ -67,7 +70,7 @@ public void handle(ClientNetworkContext context) {
}
@Override
public MessageType<ChatTableClientMessage> type() {
public CustomPacketPayload.Type<ChatTableClientMessage> type() {
return NetworkMessages.CHAT_TABLE;
}
}

View File

@ -4,32 +4,33 @@
package dan200.computercraft.shared.network.client;
import dan200.computercraft.shared.computer.menu.ComputerMenu;
import dan200.computercraft.shared.computer.terminal.TerminalState;
import dan200.computercraft.shared.network.MessageType;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.NetworkMessages;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.world.inventory.AbstractContainerMenu;
public class ComputerTerminalClientMessage implements NetworkMessage<ClientNetworkContext> {
private final int containerId;
private final TerminalState terminal;
/**
* Update the terminal for the currently opened {@link ComputerMenu}.
*
* @param containerId The currently opened container id.
* @param terminal The new terminal data.
*/
public record ComputerTerminalClientMessage(
int containerId, TerminalState terminal
) implements NetworkMessage<ClientNetworkContext> {
public static final StreamCodec<RegistryFriendlyByteBuf, ComputerTerminalClientMessage> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.VAR_INT, ComputerTerminalClientMessage::containerId,
TerminalState.STREAM_CODEC, ComputerTerminalClientMessage::terminal,
ComputerTerminalClientMessage::new
);
public ComputerTerminalClientMessage(AbstractContainerMenu menu, TerminalState terminal) {
containerId = menu.containerId;
this.terminal = terminal;
}
public ComputerTerminalClientMessage(FriendlyByteBuf buf) {
containerId = buf.readVarInt();
terminal = new TerminalState(buf);
}
@Override
public void write(FriendlyByteBuf buf) {
buf.writeVarInt(containerId);
terminal.write(buf);
this(menu.containerId, terminal);
}
@Override
@ -38,7 +39,7 @@ public void handle(ClientNetworkContext context) {
}
@Override
public MessageType<ComputerTerminalClientMessage> type() {
public CustomPacketPayload.Type<ComputerTerminalClientMessage> type() {
return NetworkMessages.COMPUTER_TERMINAL;
}
}

View File

@ -5,40 +5,34 @@
package dan200.computercraft.shared.network.client;
import dan200.computercraft.shared.computer.terminal.TerminalState;
import dan200.computercraft.shared.network.MessageType;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.NetworkMessages;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
public class MonitorClientMessage implements NetworkMessage<ClientNetworkContext> {
private final BlockPos pos;
private final TerminalState state;
public MonitorClientMessage(BlockPos pos, TerminalState state) {
this.pos = pos;
this.state = state;
}
public MonitorClientMessage(FriendlyByteBuf buf) {
pos = buf.readBlockPos();
state = new TerminalState(buf);
}
@Override
public void write(FriendlyByteBuf buf) {
buf.writeBlockPos(pos);
state.write(buf);
}
/**
* Update the terminal contents of a monitor.
*
* @param pos The position of the origin monitor.
* @param terminal The current monitor terminal.
*/
public record MonitorClientMessage(BlockPos pos,
TerminalState terminal) implements NetworkMessage<ClientNetworkContext> {
public static final StreamCodec<RegistryFriendlyByteBuf, MonitorClientMessage> STREAM_CODEC = StreamCodec.composite(
BlockPos.STREAM_CODEC, MonitorClientMessage::pos,
TerminalState.STREAM_CODEC, MonitorClientMessage::terminal,
MonitorClientMessage::new
);
@Override
public void handle(ClientNetworkContext context) {
context.handleMonitorData(pos, state);
context.handleMonitorData(pos, terminal);
}
@Override
public MessageType<MonitorClientMessage> type() {
public CustomPacketPayload.Type<MonitorClientMessage> type() {
return NetworkMessages.MONITOR_CLIENT;
}
}

View File

@ -4,60 +4,50 @@
package dan200.computercraft.shared.network.client;
import dan200.computercraft.shared.network.MessageType;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.NetworkMessages;
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveBlockEntity;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.core.Holder;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.sounds.SoundEvent;
import javax.annotation.Nullable;
import java.util.Optional;
/**
* Starts or stops a record on the client, depending on if {@link #soundEvent} is {@code null}.
* <p>
* Used by disk drives to play record items.
*
* @param pos The position of the speaker, where we should play this sound.
* @param soundEvent The sound to play, or {@link Optional#empty()} if we should stop playing.
* @param name The title of the audio to play.
* @see DiskDriveBlockEntity
*/
public class PlayRecordClientMessage implements NetworkMessage<ClientNetworkContext> {
private final BlockPos pos;
private final @Nullable String name;
private final @Nullable SoundEvent soundEvent;
public PlayRecordClientMessage(BlockPos pos, SoundEvent event, @Nullable String name) {
this.pos = pos;
this.name = name;
soundEvent = event;
}
public record PlayRecordClientMessage(
BlockPos pos, Optional<Holder<SoundEvent>> soundEvent, Optional<String> name
) implements NetworkMessage<ClientNetworkContext> {
public static final StreamCodec<RegistryFriendlyByteBuf, PlayRecordClientMessage> STREAM_CODEC = StreamCodec.composite(
BlockPos.STREAM_CODEC, PlayRecordClientMessage::pos,
ByteBufCodecs.optional(SoundEvent.STREAM_CODEC), PlayRecordClientMessage::soundEvent,
ByteBufCodecs.optional(ByteBufCodecs.STRING_UTF8), PlayRecordClientMessage::name,
PlayRecordClientMessage::new
);
public PlayRecordClientMessage(BlockPos pos) {
this.pos = pos;
name = null;
soundEvent = null;
}
public PlayRecordClientMessage(FriendlyByteBuf buf) {
pos = buf.readBlockPos();
soundEvent = buf.readNullable(SoundEvent::readFromNetwork);
name = buf.readNullable(FriendlyByteBuf::readUtf);
}
@Override
public void write(FriendlyByteBuf buf) {
buf.writeBlockPos(pos);
buf.writeNullable(soundEvent, (b, e) -> e.writeToNetwork(b));
buf.writeNullable(name, FriendlyByteBuf::writeUtf);
this(pos, Optional.empty(), Optional.empty());
}
@Override
public void handle(ClientNetworkContext context) {
context.handlePlayRecord(pos, soundEvent, name);
context.handlePlayRecord(pos, soundEvent.map(Holder::value).orElse(null), name.orElse(null));
}
@Override
public MessageType<PlayRecordClientMessage> type() {
public CustomPacketPayload.Type<PlayRecordClientMessage> type() {
return NetworkMessages.PLAY_RECORD;
}
}

View File

@ -5,54 +5,56 @@
package dan200.computercraft.shared.network.client;
import dan200.computercraft.shared.computer.core.ComputerState;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.terminal.NetworkedTerminal;
import dan200.computercraft.shared.computer.terminal.TerminalState;
import dan200.computercraft.shared.network.MessageType;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.NetworkMessages;
import dan200.computercraft.shared.pocket.core.PocketServerComputer;
import net.minecraft.network.FriendlyByteBuf;
import dan200.computercraft.shared.recipe.MoreStreamCodecs;
import net.minecraft.core.UUIDUtil;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import java.util.UUID;
/**
* Provides additional data about a client computer, such as its ID and current state.
*
* @param id The {@linkplain ServerComputer#getInstanceUUID() instance id} of the pocket computer.
* @param state Whether the computer is on, off, or blinking.
* @param lightState The colour of the light, or {@code -1} if off.
* @param terminal The computer's terminal. This may be absent, in which case the terminal will not be updated on.
*/
public class PocketComputerDataMessage implements NetworkMessage<ClientNetworkContext> {
private final UUID clientId;
private final ComputerState state;
private final int lightState;
private final TerminalState terminal;
public record PocketComputerDataMessage(
UUID id, ComputerState state, int lightState, TerminalState terminal
) implements NetworkMessage<ClientNetworkContext> {
public static final StreamCodec<RegistryFriendlyByteBuf, PocketComputerDataMessage> STREAM_CODEC = StreamCodec.composite(
UUIDUtil.STREAM_CODEC, PocketComputerDataMessage::id,
MoreStreamCodecs.ofEnum(ComputerState.class), PocketComputerDataMessage::state,
ByteBufCodecs.VAR_INT, PocketComputerDataMessage::lightState,
TerminalState.STREAM_CODEC, PocketComputerDataMessage::terminal,
PocketComputerDataMessage::new
);
public PocketComputerDataMessage(PocketServerComputer computer, boolean sendTerminal) {
clientId = computer.getInstanceUUID();
state = computer.getState();
lightState = computer.getLight();
terminal = sendTerminal ? computer.getTerminalState() : new TerminalState((NetworkedTerminal) null);
}
public PocketComputerDataMessage(FriendlyByteBuf buf) {
clientId = buf.readUUID();
state = buf.readEnum(ComputerState.class);
lightState = buf.readVarInt();
terminal = new TerminalState(buf);
}
@Override
public void write(FriendlyByteBuf buf) {
buf.writeUUID(clientId);
buf.writeEnum(state);
buf.writeVarInt(lightState);
terminal.write(buf);
this(
computer.getInstanceUUID(),
computer.getState(),
computer.getLight(),
sendTerminal ? computer.getTerminalState() : new TerminalState((NetworkedTerminal) null)
);
}
@Override
public void handle(ClientNetworkContext context) {
context.handlePocketComputerData(clientId, state, lightState, terminal);
context.handlePocketComputerData(id, state, lightState, terminal);
}
@Override
public MessageType<PocketComputerDataMessage> type() {
public CustomPacketPayload.Type<PocketComputerDataMessage> type() {
return NetworkMessages.POCKET_COMPUTER_DATA;
}
}

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