1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-11-10 20:09:58 +00:00

Update to 1.20.5 (#1793)

- Switch most network code to use StreamCodec
 - Turtle/pocket computer upgrades now use DataComponentPatch instead of
   raw NBT.
This commit is contained in:
Jonathan Coates 2024-04-25 21:32:48 +01:00 committed by GitHub
parent bd2fd9d4c8
commit 01407544c9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
393 changed files with 3276 additions and 3915 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
@ -87,7 +87,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

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

@ -78,8 +78,16 @@ dependencies {
// Configure default JavaCompile tasks with our arguments.
sourceSets.all {
tasks.named(compileJavaTaskName, JavaCompile::class.java) {
options.compilerArgs.addAll(
listOf(
"-Xlint",
// Processing just gives us "No processor claimed any of these annotations", so skip that!
options.compilerArgs.addAll(listOf("-Xlint", "-Xlint:-processing"))
"-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 org.jetbrains.kotlin.gradle.tasks.KotlinCompile
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 @@ abstract class CCTweakedExtension(
* 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 @@ class CCTweakedPlugin : Plugin<Project> {
}
companion object {
val JAVA_VERSION = JavaLanguageVersion.of(17)
val JAVA_VERSION = JavaLanguageVersion.of(21)
}
}

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

@ -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.3"
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"
@ -66,7 +67,7 @@ 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"
@ -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" }
@ -179,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"]

View File

@ -9,7 +9,7 @@ import dan200.computercraft.api.turtle.ITurtleAccess;
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 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
}
/**
* 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 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
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.ITurtleUpgrade;
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 @@ final class TurtleUpgradeModellers {
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 @@ package dan200.computercraft.api;
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 class ComputerCraftTags {
*/
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 class ComputerCraftTags {
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.UpgradeBase;
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 @@ public interface ITurtleAccess {
* @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 @@ public interface ITurtleAccess {
/**
* 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.api.upgrades.UpgradeSerialiser;
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 @@ public interface ITurtleUpgrade extends UpgradeBase {
* @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.ComputerCraftTags;
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 abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITur
/**
* 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.ITurtleAccess;
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;
}
/**
@ -96,26 +94,15 @@ public interface UpgradeBase {
* the original stack. In order to prevent people losing items with enchantments (or
* repairing items with non-0 damage), we impose additional checks on the item.
* <p>
* The default check requires that any non-capability NBT is exactly the same as the
* crafting item, but this may be relaxed for your upgrade.
* <p>
* This is based on {@code net.neoforged.common.crafting.StrictNBTIngredient}'s check.
* The default check requires that any NBT is exactly the same as the crafting item,
* but this may be relaxed for your upgrade.
*
* @param stack The stack to check. This is guaranteed to be non-empty and have the same item as
* {@link #getCraftingItem()}.
* @return If this stack may be used to equip this upgrade.
*/
default boolean isItemSuitable(ItemStack stack) {
var crafting = getCraftingItem();
// 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();
return tag.equals(craftingTag);
return ItemStack.isSameItemSameComponents(getCraftingItem(), stack);
}
/**

View File

@ -6,23 +6,17 @@ package dan200.computercraft.api.upgrades;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.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 record UpgradeData<T extends UpgradeBase>(T upgrade, CompoundTag data) {
}
/**
* 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 record UpgradeData<T extends UpgradeBase>(T upgrade, CompoundTag data) {
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.api.turtle.TurtleUpgradeDataProvider;
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 @@ package dan200.computercraft.impl.upgrades;
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 final class SerialiserWithCraftingItem<T extends UpgradeBase> implements
}
@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 @@ package dan200.computercraft.impl.upgrades;
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 final class SimpleSerialiser<T extends UpgradeBase> implements UpgradeSer
}
@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.client.turtle.TurtleUpgradeModellers;
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.network.chat.Component;
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 final class ClientRegistry {
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 final class ClientRegistry {
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 final class ClientRegistry {
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 class ClientTableFormatter implements TableFormatter {
var tag = createTag(table.getId());
if (chat.allMessages.removeIf(guiMessage -> guiMessage.tag() != null && Objects.equals(guiMessage.tag().logTag(), tag.logTag()))) {
chat.refreshTrimmedMessage();
chat.rescaleChat();
}
TableFormatter.super.display(table);

View File

@ -6,7 +6,9 @@ package dan200.computercraft.client.gui;
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,16 +37,17 @@ public class PrintoutScreen extends AbstractContainerScreen<HeldItemMenu> {
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);
pages = Math.max(this.text.length / PrintoutData.LINES_PER_PAGE, 1);
book = ((PrintoutItem) container.getStack().getItem()).getType() == PrintoutItem.Type.BOOK;
}
@ -89,7 +92,7 @@ public class PrintoutScreen extends AbstractContainerScreen<HeldItemMenu> {
var renderer = MultiBufferSource.immediate(Tesselator.getInstance().getBuilder());
drawBorder(graphics.pose(), renderer, leftPos, topPos, 0, page, pages, book, FULL_BRIGHT_LIGHTMAP);
drawText(graphics.pose(), renderer, leftPos + X_TEXT_MARGIN, topPos + Y_TEXT_MARGIN, PrintoutItem.LINES_PER_PAGE * page, FULL_BRIGHT_LIGHTMAP, text, colours);
drawText(graphics.pose(), renderer, leftPos + X_TEXT_MARGIN, topPos + Y_TEXT_MARGIN, PrintoutData.LINES_PER_PAGE * page, FULL_BRIGHT_LIGHTMAP, text, colours);
renderer.endBatch();
graphics.pose().popPose();

View File

@ -27,13 +27,11 @@ public class EMIComputerCraft implements EmiPlugin {
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.platform.ClientPlatformHelper;
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 @@ public final class TurtleModelParts<T> {
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 final class TurtleModelParts<T> {
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 @@ public final class 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 @@ public interface ClientPlatformHelper extends dan200.computercraft.impl.client.C
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 final class ClientPocketComputers {
}
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 final class CableHighlightRenderer {
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 final class CableHighlightRenderer {
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.config.Config;
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 @@ public final class PocketItemRenderer extends ItemMapLikeRenderer {
// 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 @@ package dan200.computercraft.client.render;
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;
@ -15,8 +17,8 @@ import net.minecraft.world.item.ItemStack;
import static dan200.computercraft.client.render.PrintoutRenderer.*;
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_HEIGHT;
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_WIDTH;
import static dan200.computercraft.shared.media.items.PrintoutItem.LINES_PER_PAGE;
import static dan200.computercraft.shared.media.items.PrintoutItem.LINE_MAX_LENGTH;
import static dan200.computercraft.shared.media.items.PrintoutData.LINES_PER_PAGE;
import static dan200.computercraft.shared.media.items.PrintoutData.LINE_LENGTH;
/**
* Emulates map and item-frame rendering for printouts.
@ -37,8 +39,6 @@ public final class PrintoutItemRenderer extends ItemMapLikeRenderer {
}
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,10 +50,12 @@ public final class PrintoutItemRenderer extends ItemMapLikeRenderer {
}
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;
double width = LINE_LENGTH * FONT_WIDTH + X_TEXT_MARGIN * 2;
double height = LINES_PER_PAGE * FONT_HEIGHT + Y_TEXT_MARGIN * 2;
// Non-books will be left aligned
@ -75,9 +77,6 @@ public final class PrintoutItemRenderer extends ItemMapLikeRenderer {
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,11 +9,14 @@ import com.mojang.blaze3d.vertex.VertexConsumer;
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;
import static dan200.computercraft.shared.media.items.PrintoutData.LINES_PER_PAGE;
/**
* Renders printed pages or books, either for a GUI ({@link dan200.computercraft.client.gui.PrintoutScreen}) or
@ -69,13 +72,14 @@ public final class PrintoutRenderer {
}
}
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 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl
});
}
// 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 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl
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 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl
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.MultiBufferSource;
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 final class MonitorHighlightRenderer {
// 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 final class MonitorHighlightRenderer {
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.TransformedModel;
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 class TurtleModemModeller implements TurtleUpgradeModeller<TurtleModem> {
}
@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.RegistryHelper;
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 final class TurtleUpgradeModellers {
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.item.Item;
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 @@ public final class 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 final class DataProviders {
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

@ -4,7 +4,6 @@
package dan200.computercraft.data;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.shared.CommonHooks;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.data.BlockNamedEntityLootCondition;
@ -13,14 +12,15 @@ import dan200.computercraft.shared.data.PlayerCreativeLootCondition;
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.ResourceLocation;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.storage.loot.LootPool;
import net.minecraft.world.level.storage.loot.LootTable;
import net.minecraft.world.level.storage.loot.entries.DynamicLoot;
import net.minecraft.world.level.storage.loot.entries.LootItem;
import net.minecraft.world.level.storage.loot.entries.LootPoolEntryContainer;
import net.minecraft.world.level.storage.loot.functions.CopyComponentsFunction;
import net.minecraft.world.level.storage.loot.functions.CopyNameFunction;
import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets;
import net.minecraft.world.level.storage.loot.predicates.AnyOfCondition;
@ -41,7 +41,7 @@ class LootTableProvider {
);
}
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 +78,15 @@ class LootTableProvider {
));
}
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,10 +94,10 @@ class LootTableProvider {
);
}
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")),
LootItem.lootTableItem(block.get()).apply(CopyComponentsFunction.copyComponents(CopyComponentsFunction.Source.BLOCK_ENTITY)),
AnyOfCondition.anyOf(
BlockNamedEntityLootCondition.BUILDER,
HasComputerIdLootCondition.BUILDER,
@ -107,7 +107,7 @@ class LootTableProvider {
}
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.impl.RegistryHelper;
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.RecipeCategory;
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 net.minecraft.world.level.block.Blocks;
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 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
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 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
*/
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 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
*/
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 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
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 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
.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 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
.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 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
.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 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
.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 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
.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 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
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 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
}
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 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
}
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 @@ package dan200.computercraft.data;
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 @@ class TagProvider {
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 @@ package dan200.computercraft.data.recipe;
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 abstract class AbstractRecipeBuilder<S extends AbstractRecipeBuilder<S, O
* @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.tags.TagKey;
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 final class ShapelessSpecBuilder extends AbstractRecipeBuilder<ShapelessS
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 class UpgradeManager<T extends UpgradeBase> extends SimpleJsonResourceRel
}
@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 class UpgradeManager<T extends UpgradeBase> extends SimpleJsonResourceRel
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 @@ public class UpgradeManager<T extends UpgradeBase> extends SimpleJsonResourceRel
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 @@ public class UpgradeManager<T extends UpgradeBase> extends SimpleJsonResourceRel
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 @@ package dan200.computercraft.mixin;
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 @@ class V1460Mixin {
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:pocket_upgrade", upgradeData),
Pair.of("computercraft:left_turtle_upgrade", upgradeData),
Pair.of("computercraft:right_turtle_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.computer.metrics.ComputerMBean;
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.item.ItemStack;
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 final class CommonHooks {
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 final class CommonHooks {
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,7 @@ package dan200.computercraft.shared;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.serialization.Codec;
import com.mojang.serialization.MapCodec;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.detail.DetailProvider;
import dan200.computercraft.api.detail.VanillaDetailRegistries;
@ -32,9 +33,11 @@ import dan200.computercraft.shared.computer.blocks.ComputerBlockEntity;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.inventory.ComputerMenuWithoutInventory;
import dan200.computercraft.shared.computer.inventory.ViewComputerMenu;
import dan200.computercraft.shared.computer.items.AbstractComputerItem;
import dan200.computercraft.shared.computer.items.CommandComputerItem;
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 +45,7 @@ import dan200.computercraft.shared.data.PlayerCreativeLootCondition;
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 +81,23 @@ import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
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 dan200.computercraft.shared.util.NonNegativeId;
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.Block;
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 @@ public final class ModRegistry {
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,114 @@ public final class ModRegistry {
() -> 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());
}
/**
* The id of a computer.
*
* @see AbstractComputerItem
* @see PocketComputerItem
*/
public static final RegistryEntry<DataComponentType<NonNegativeId>> COMPUTER_ID = register("computer_id", b -> b
.persistent(NonNegativeId.CODEC).networkSynchronized(NonNegativeId.STREAM_CODEC)
);
/**
* The left upgrade of a turtle.
*
* @see TurtleItem
*/
public static final RegistryEntry<DataComponentType<UpgradeData<ITurtleUpgrade>>> LEFT_TURTLE_UPGRADE = register("left_turtle_upgrade", b -> b
.persistent(TurtleUpgrades.instance().codec()).networkSynchronized(TurtleUpgrades.instance().streamCodec())
);
/**
* The right upgrade of a turtle.
*
* @see TurtleItem
*/
public static final RegistryEntry<DataComponentType<UpgradeData<ITurtleUpgrade>>> RIGHT_TURTLE_UPGRADE = register("right_turtle_upgrade", b -> b
.persistent(TurtleUpgrades.instance().codec()).networkSynchronized(TurtleUpgrades.instance().streamCodec())
);
/**
* The fuel level of a turtle.
*/
public static final RegistryEntry<DataComponentType<Integer>> FUEL = register("fuel", b -> b
.persistent(Codec.INT).networkSynchronized(ByteBufCodecs.VAR_INT)
);
/**
* The overlay on a turtle.
*/
public static final RegistryEntry<DataComponentType<ResourceLocation>> OVERLAY = register("overlay", b -> b
.persistent(ResourceLocation.CODEC).networkSynchronized(ResourceLocation.STREAM_CODEC)
);
/**
* The back upgrade of a pocket computer.
*
* @see PocketComputerItem
*/
public static final RegistryEntry<DataComponentType<UpgradeData<IPocketUpgrade>>> POCKET_UPGRADE = register("pocket_upgrade", b -> b
.persistent(PocketUpgrades.instance().codec()).networkSynchronized(PocketUpgrades.instance().streamCodec())
);
/**
* A reference to the currently running {@link dan200.computercraft.shared.computer.core.ServerComputer}.
*
* @see ServerComputerReference
* @see PocketComputerItem
*/
public static final RegistryEntry<DataComponentType<ServerComputerReference>> COMPUTER = register("computer", b -> b
.persistent(ServerComputerReference.CODEC).networkSynchronized(ServerComputerReference.STREAM_CODEC)
);
/**
* Whether this item is currently on.
*
* @see PocketComputerItem
* @see TurtleModem
*/
public static final RegistryEntry<DataComponentType<Boolean>> ON = register("on", b -> b
.persistent(Codec.BOOL).networkSynchronized(ByteBufCodecs.BOOL)
);
/**
* Information about a treasure disk's mount.
*
* @see TreasureDiskItem
* @see TreasureDisk
*/
public static final RegistryEntry<DataComponentType<TreasureDisk>> TREASURE_DISK = register("treasure_disk", b -> b
.persistent(TreasureDisk.CODEC).networkSynchronized(TreasureDisk.STREAM_CODEC)
);
/**
* The id of a disk.
*
* @see DiskItem
*/
public static final RegistryEntry<DataComponentType<NonNegativeId>> DISK_ID = register("disk_id", b -> b
.persistent(NonNegativeId.CODEC).networkSynchronized(NonNegativeId.STREAM_CODEC)
);
/**
* The contents of a printed page/printed pages.
*
* @see PrintoutItem
* @see PrintoutData
*/
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 +404,16 @@ public final class ModRegistry {
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 +423,12 @@ public final class ModRegistry {
public static final RegistryEntry<MenuType<HeldItemMenu>> PRINTOUT = REGISTRY.register("printout",
() -> ContainerData.toType(
HeldItemContainerData::new,
(id, inventory, data) -> new HeldItemMenu(Menus.PRINTOUT.get(), id, inventory.player, data.getHand())
HeldItemContainerData.STREAM_CODEC,
(id, inventory, data) -> new HeldItemMenu(Menus.PRINTOUT.get(), id, inventory.player, data.hand())
));
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 +458,13 @@ public final class ModRegistry {
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 +474,17 @@ public final class ModRegistry {
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 +533,7 @@ public final class ModRegistry {
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 +546,7 @@ public final class ModRegistry {
Blocks.REGISTRY.register();
BlockEntities.REGISTRY.register();
Items.REGISTRY.register();
DataComponents.REGISTRY.register();
TurtleSerialisers.REGISTRY.register();
PocketUpgradeSerialisers.REGISTRY.register();
Menus.REGISTRY.register();
@ -470,14 +579,14 @@ public final class ModRegistry {
}
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

@ -260,7 +260,7 @@ public final class CommandComputerCraft {
*/
private static int view(CommandSourceStack source, ServerComputer computer) throws CommandSyntaxException {
var player = source.getPlayerOrException();
new ComputerContainerData(computer, ItemStack.EMPTY).open(player, new MenuProvider() {
new ComputerContainerData(computer, new ItemStack(ModRegistry.Items.COMPUTER_NORMAL.get())).open(player, new MenuProvider() {
@Override
public Component getDisplayName() {
return Component.translatable("gui.computercraft.view_computer");

View File

@ -14,8 +14,6 @@ import net.minecraft.core.registries.BuiltInRegistries;
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 class ArgumentUtils {
@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 com.mojang.brigadier.suggestion.SuggestionsBuilder;
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 final class RepeatArgumentType<T, U> implements ArgumentType<List<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 final class RepeatArgumentType<T, U> implements ArgumentType<List<T>> {
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 net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.world.Container;
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 @@ import net.minecraft.world.level.block.state.BlockState;
/**
* 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 @@ public abstract class AbstractContainerBlockEntity extends BaseContainerBlockEnt
protected final Component getDefaultName() {
return Component.translatable(getBlockState().getBlock().getDescriptionId());
}
@Override
public boolean stillValid(Player player) {
return Container.stillValidBlockEntity(this, player);
}
}

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.item.crafting.RecipeSerializer;
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 final class ClearColourRecipe extends CustomRecipe {
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 final class ClearColourRecipe extends CustomRecipe {
}
@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 final class ColourableRecipe extends CustomRecipe {
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 final class ColourableRecipe extends CustomRecipe {
}
@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 final class ColourableRecipe extends CustomRecipe {
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 final class ColourableRecipe extends CustomRecipe {
}
}
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 @@ package dan200.computercraft.shared.common;
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.BlockStateProperties;
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 abstract class HorizontalContainerBlock extends BaseEntityBlock {
}
@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 abstract class HorizontalContainerBlock extends BaseEntityBlock {
}
@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 abstract class HorizontalContainerBlock extends BaseEntityBlock {
}
@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 @@ public class CommandAPI implements ILuaAPI {
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,19 +7,14 @@ package dan200.computercraft.shared.computer.blocks;
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;
import net.minecraft.core.Direction;
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;
@ -27,16 +22,14 @@ import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.HorizontalDirectionalBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.storage.loot.LootParams;
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import javax.annotation.Nullable;
@ -52,8 +45,7 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit
}
@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);
@ -61,14 +53,12 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit
}
@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;
@ -79,11 +69,14 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit
return computer.getRedstoneOutput(localSide);
}
protected abstract ItemStack getItem(AbstractComputerBlockEntity tile);
private 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);
}
@ -100,7 +93,6 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit
}
@Override
@Deprecated
public ItemStack getCloneItemStack(LevelReader world, BlockPos pos, BlockState state) {
var tile = world.getBlockEntity(pos);
if (tile instanceof AbstractComputerBlockEntity computer) {
@ -113,55 +105,21 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit
@Override
public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity tile, ItemStack tool) {
// Don't drop blocks here - see onBlockHarvested.
player.awardStat(Stats.BLOCK_MINED.get(this));
player.causeFoodExhaustion(0.005F);
// Use the same trick as DoublePlantBlock, to skip dropping items. See playerWillDestroy.
super.playerDestroy(world, player, pos, Blocks.AIR.defaultBlockState(), tile, tool);
}
@Override
public BlockState playerWillDestroy(Level world, BlockPos pos, BlockState state, Player player) {
var result = super.playerWillDestroy(world, pos, state, player);
if (!(world instanceof ServerLevel serverWorld)) return result;
// We drop the item here instead of doing it in the harvest method, as we should
// drop computers for creative players too.
Block.dropResources(state, world, pos, world.getBlockEntity(pos), player, player.getMainHandItem());
var tile = world.getBlockEntity(pos);
if (tile instanceof AbstractComputerBlockEntity computer) {
var context = new LootParams.Builder(serverWorld)
.withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(pos))
.withParameter(LootContextParams.TOOL, player.getMainHandItem())
.withParameter(LootContextParams.THIS_ENTITY, player)
.withParameter(LootContextParams.BLOCK_ENTITY, tile)
.withDynamicDrop(DROP, out -> out.accept(getItem(computer)));
for (var item : state.getDrops(context)) {
popResource(world, pos, item);
}
state.spawnAfterBreak(serverWorld, pos, player.getMainHandItem(), true);
}
return result;
return super.playerWillDestroy(world, pos, state, player);
}
@Override
public void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) {
super.setPlacedBy(world, pos, state, placer, stack);
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);
}
}
@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 (!player.isCrouching() && level.getBlockEntity(pos) instanceof AbstractComputerBlockEntity computer) {
// Regular right click to activate computer
if (!level.isClientSide && computer.isUsable(player)) {
@ -173,12 +131,11 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit
return InteractionResult.sidedSuccess(level.isClientSide);
}
return super.use(state, level, pos, player, hand, hit);
return super.useWithoutItem(state, level, pos, player, hit);
}
@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);
}
@ -190,8 +147,7 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit
}
@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);
@ -200,8 +156,7 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit
@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,18 +9,19 @@ import dan200.computercraft.api.ComputerCraftAPI;
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.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;
@ -76,8 +77,8 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
unload();
}
protected int getInteractRange() {
return Container.DEFAULT_DISTANCE_LIMIT;
protected float getInteractRange() {
return Container.DEFAULT_DISTANCE_BUFFER;
}
public boolean isUsable(Player player) {
@ -137,7 +138,7 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
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);
@ -145,20 +146,20 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
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;
@ -167,6 +168,31 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
lockCode = LockCode.fromTag(nbt);
}
@Override
protected void applyImplicitComponents(DataComponentInput component) {
super.applyImplicitComponents(component);
label = DataComponentUtil.getCustomName(component.get(DataComponents.CUSTOM_NAME));
computerID = NonNegativeId.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(), NonNegativeId.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;
}
@ -370,15 +396,15 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
}
@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 @@ package dan200.computercraft.shared.computer.blocks;
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 @@ public class ComputerBlock<T extends ComputerBlockEntity> extends AbstractComput
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 @@ package dan200.computercraft.shared.computer.items;
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 abstract class AbstractComputerItem extends BlockItem implements ICompute
@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

@ -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("instance").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 @@ package dan200.computercraft.shared.computer.terminal;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import org.jetbrains.annotations.Contract;
import javax.annotation.Nullable;
@ -19,6 +20,8 @@ import javax.annotation.Nullable;
* 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;
@ -38,7 +41,7 @@ public class TerminalState {
return terminal == null ? null : new TerminalState(terminal);
}
public TerminalState(FriendlyByteBuf buf) {
private TerminalState(FriendlyByteBuf buf) {
colour = buf.readBoolean();
width = buf.readVarInt();
height = buf.readVarInt();
@ -47,7 +50,7 @@ public class TerminalState {
buffer = buf.readBytes(length);
}
public void write(FriendlyByteBuf buf) {
private void write(FriendlyByteBuf buf) {
buf.writeBoolean(colour);
buf.writeVarInt(width);
buf.writeVarInt(height);

View File

@ -10,19 +10,19 @@ import net.minecraft.world.ContainerHelper;
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 @@ public interface BasicContainer extends Container {
@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 net.minecraft.world.item.ItemStack;
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

@ -27,7 +27,7 @@ public final class PlayerCreativeLootCondition implements LootItemCondition {
@Override
public boolean test(LootContext lootContext) {
var entity = lootContext.getParamOrNull(LootContextParams.THIS_ENTITY);
return entity instanceof Player player && player.getAbilities().instabuild;
return entity instanceof Player player && player.isCreative();
}
@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 class ItemDetails {
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);
}
/**
@ -108,25 +86,12 @@ public class ItemDetails {
* 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.
* @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 @@ public class ItemDetails {
* @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.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.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
@ -54,14 +55,14 @@ public final class RecipeModHelpers {
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.UpgradeBase;
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 class UpgradeRecipeGenerator<T> {
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 class UpgradeRecipeGenerator<T> {
*/
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 class UpgradeRecipeGenerator<T> {
}
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 @@ public class UpgradeRecipeGenerator<T> {
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 @@ public class UpgradeRecipeGenerator<T> {
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 class JEIComputerCraft implements IModPlugin {
* 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 class JEIComputerCraft implements IModPlugin {
* 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 class JEIComputerCraft implements IModPlugin {
/**
* 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,43 +10,34 @@ import dan200.computercraft.api.filesystem.Mount;
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 dan200.computercraft.shared.util.NonNegativeId;
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) {
list.add(Component.translatable("gui.computercraft.tooltip.disk_id", id)
var id = stack.get(ModRegistry.DataComponents.DISK_ID.get());
if (id != null) {
list.add(Component.translatable("gui.computercraft.tooltip.disk_id", id.id())
.withStyle(ChatFormatting.GRAY));
}
}
@ -59,41 +50,27 @@ public class DiskItem extends Item implements IMedia, IColouredItem {
@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;
}
@Override
public @Nullable Mount createDataMount(ItemStack stack, ServerLevel level) {
var diskID = getDiskID(stack);
if (diskID < 0) {
diskID = ComputerCraftAPI.createUniqueNumberedSaveDir(level.getServer(), "disk");
setDiskID(stack, diskID);
}
var diskID = NonNegativeId.getOrCreate(level.getServer(), stack, ModRegistry.DataComponents.DISK_ID.get(), "disk");
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;
return NonNegativeId.getId(stack.get(ModRegistry.DataComponents.DISK_ID.get()));
}
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,104 @@
// 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;
/**
* The contents of a printout.
*
* @param title The title of this printout.
* @param lines A list of lines for this printout.
* @see PrintoutItem
* @see dan200.computercraft.shared.ModRegistry.DataComponents#PRINTOUT
*/
public record PrintoutData(String title, List<Line> lines) {
public static final int LINE_LENGTH = 25;
public static final int LINES_PER_PAGE = 21;
public static final int MAX_PAGES = 16;
/**
* An empty printout. This has no title, and is a single page of empty lines.
*/
public static final PrintoutData EMPTY;
static {
var lines = new Line[LINES_PER_PAGE];
Arrays.fill(lines, Line.EMPTY);
EMPTY = new PrintoutData("", List.of(lines));
}
private static final Codec<String> LINE_TEXT = Codec.STRING.validate(x -> x.length() == LINE_LENGTH
? DataResult.success(x)
: DataResult.error(() -> "Expected string of length " + LINE_LENGTH));
private static final Codec<Line> LINE_CODEC = RecordCodecBuilder.<Line>create(s -> s.group(
LINE_TEXT.fieldOf("text").forGetter(Line::text),
LINE_TEXT.fieldOf("foreground").forGetter(Line::foreground)
).apply(s, Line::new));
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, MAX_PAGES * LINES_PER_PAGE)
.validate(PrintoutData::validateLines)
.fieldOf("lines").forGetter(PrintoutData::lines)
).apply(s, PrintoutData::new));
public static final StreamCodec<ByteBuf, PrintoutData> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.STRING_UTF8, PrintoutData::title,
LINE_STREAM_CODEC.apply(ByteBufCodecs.list(MAX_PAGES * 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(" ".repeat(LINE_LENGTH), "f".repeat(LINE_LENGTH));
public Line {
if (text.length() != LINE_LENGTH) throw new IllegalArgumentException("text is of wrong length");
if (foreground.length() != LINE_LENGTH) throw new IllegalArgumentException("foreground is of wrong length");
}
}
public PrintoutData {
validateLines(lines).getOrThrow(IllegalArgumentException::new);
}
private static DataResult<List<Line>> validateLines(List<Line> lines) {
if (lines.isEmpty()) return DataResult.error(() -> "Expected non-empty list of lines");
if ((lines.size() % LINES_PER_PAGE) != 0) return DataResult.error(() -> "Not enough lines for a page");
if (lines.size() > LINES_PER_PAGE * MAX_PAGES) return DataResult.error(() -> "Too many pages");
return DataResult.success(lines);
}
/**
* Get the number of pages in this printout.
*
* @return The number of pages.
*/
public int pages() {
return Math.ceilDiv(lines.size(), LINES_PER_PAGE);
}
}

View File

@ -17,19 +17,9 @@ 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 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;
public enum Type {
PAGE,
PAGES,
@ -44,9 +34,9 @@ public class PrintoutItem extends Item {
}
@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 +48,17 @@ public class PrintoutItem extends Item {
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,38 @@
// 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;
/**
* Stores information about a {@linkplain TreasureDiskItem treasure disk's} mount.
*
* @param name The name/title of the disk.
* @param path The subpath to the resource
* @see ModRegistry.DataComponents#TREASURE_DISK
*/
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.ComputerCraftAPI;
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.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.level.LevelReader;
import javax.annotation.Nullable;
@ -26,18 +24,13 @@ import java.io.IOException;
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 class TreasureDiskItem extends Item implements IMedia {
@Override
public String getLabel(ItemStack stack) {
return getTitle(stack);
return TreasureDisk.getTitle(stack);
}
@Override
@ -55,7 +48,10 @@ public class TreasureDiskItem extends Item implements IMedia {
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 class TreasureDiskItem extends Item implements IMedia {
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 @@ package dan200.computercraft.shared.media.recipes;
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 class DiskRecipe extends CustomRecipe {
}
@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 class DiskRecipe extends CustomRecipe {
}
}
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 class DiskRecipe extends CustomRecipe {
}
@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.Ingredient;
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 final class PrintoutRecipe extends CustomRecipe {
}
@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 final class PrintoutRecipe extends CustomRecipe {
}
@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;
@ -82,43 +86,30 @@ public final class PrintoutRecipe extends CustomRecipe {
}
// Build some pages with what was passed in
if (numPages <= PrintoutItem.MAX_PAGES && stringFound && printoutFound && numPrintouts >= (leatherFound ? 1 : 2)) {
if (numPages <= PrintoutData.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 * PrintoutData.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++;
for (var pageLine = 0; pageLine < PrintoutData.LINES_PER_PAGE; pageLine++) {
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 @@ package dan200.computercraft.shared.network;
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 @@ import net.minecraft.network.FriendlyByteBuf;
* @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.api.ComputerCraftAPI;
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 @@ public final class NetworkMessages {
*
* @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 final class NetworkMessages {
*
* @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 class ChatTableClientMessage implements NetworkMessage<ClientNetworkConte
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 class ChatTableClientMessage implements NetworkMessage<ClientNetworkConte
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 class ChatTableClientMessage implements NetworkMessage<ClientNetworkConte
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 class ChatTableClientMessage implements NetworkMessage<ClientNetworkConte
}
@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 class ComputerTerminalClientMessage implements NetworkMessage<ClientNetwo
}
@Override
public MessageType<ComputerTerminalClientMessage> type() {
public CustomPacketPayload.Type<ComputerTerminalClientMessage> type() {
return NetworkMessages.COMPUTER_TERMINAL;
}
}

View File

@ -5,41 +5,38 @@
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.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import javax.annotation.Nullable;
import java.util.Optional;
public class MonitorClientMessage implements NetworkMessage<ClientNetworkContext> {
private final BlockPos pos;
private final @Nullable TerminalState state;
public MonitorClientMessage(BlockPos pos, @Nullable TerminalState state) {
this.pos = pos;
this.state = state;
}
public MonitorClientMessage(FriendlyByteBuf buf) {
pos = buf.readBlockPos();
state = buf.readNullable(TerminalState::new);
}
@Override
public void write(FriendlyByteBuf buf) {
buf.writeBlockPos(pos);
buf.writeNullable(state, (b, t) -> t.write(b));
}
/**
* 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, Optional<TerminalState> terminal
) implements NetworkMessage<ClientNetworkContext> {
public static final StreamCodec<RegistryFriendlyByteBuf, MonitorClientMessage> STREAM_CODEC = StreamCodec.composite(
BlockPos.STREAM_CODEC, MonitorClientMessage::pos,
ByteBufCodecs.optional(TerminalState.STREAM_CODEC), MonitorClientMessage::terminal,
MonitorClientMessage::new
);
@Override
public void handle(ClientNetworkContext context) {
context.handleMonitorData(pos, state);
context.handleMonitorData(pos, terminal.orElse(null));
}
@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.TerminalState;
import dan200.computercraft.shared.network.MessageType;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.NetworkMessages;
import dan200.computercraft.shared.network.codec.MoreStreamCodecs;
import dan200.computercraft.shared.pocket.core.PocketServerComputer;
import net.minecraft.network.FriendlyByteBuf;
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 javax.annotation.Nullable;
import java.util.Optional;
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 @Nullable TerminalState terminal;
public record PocketComputerDataMessage(
UUID id, ComputerState state, int lightState, Optional<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,
ByteBufCodecs.optional(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() : null;
}
public PocketComputerDataMessage(FriendlyByteBuf buf) {
clientId = buf.readUUID();
state = buf.readEnum(ComputerState.class);
lightState = buf.readVarInt();
terminal = buf.readNullable(TerminalState::new);
}
@Override
public void write(FriendlyByteBuf buf) {
buf.writeUUID(clientId);
buf.writeEnum(state);
buf.writeVarInt(lightState);
buf.writeNullable(terminal, (b, t) -> t.write(b));
this(
computer.getInstanceUUID(),
computer.getState(),
computer.getLight(),
sendTerminal ? Optional.of(computer.getTerminalState()) : Optional.empty()
);
}
@Override
public void handle(ClientNetworkContext context) {
context.handlePocketComputerData(clientId, state, lightState, terminal);
context.handlePocketComputerData(id, state, lightState, terminal.orElse(null));
}
@Override
public MessageType<PocketComputerDataMessage> type() {
public CustomPacketPayload.Type<PocketComputerDataMessage> type() {
return NetworkMessages.POCKET_COMPUTER_DATA;
}
}

View File

@ -4,29 +4,24 @@
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 net.minecraft.network.FriendlyByteBuf;
import net.minecraft.core.UUIDUtil;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import java.util.UUID;
public class PocketComputerDeletedClientMessage implements NetworkMessage<ClientNetworkContext> {
private final UUID instanceId;
public PocketComputerDeletedClientMessage(UUID instanceId) {
this.instanceId = instanceId;
}
public PocketComputerDeletedClientMessage(FriendlyByteBuf buffer) {
instanceId = buffer.readUUID();
}
@Override
public void write(FriendlyByteBuf buf) {
buf.writeUUID(instanceId);
}
/**
* Delete any client-side pocket computer state.
*
* @param instanceId The pocket computer's instance id.
*/
public record PocketComputerDeletedClientMessage(UUID instanceId) implements NetworkMessage<ClientNetworkContext> {
public static final StreamCodec<RegistryFriendlyByteBuf, PocketComputerDeletedClientMessage> STREAM_CODEC = UUIDUtil.STREAM_CODEC
.map(PocketComputerDeletedClientMessage::new, PocketComputerDeletedClientMessage::instanceId)
.cast();
@Override
public void handle(ClientNetworkContext context) {
@ -34,7 +29,7 @@ public class PocketComputerDeletedClientMessage implements NetworkMessage<Client
}
@Override
public MessageType<PocketComputerDeletedClientMessage> type() {
public CustomPacketPayload.Type<PocketComputerDeletedClientMessage> type() {
return NetworkMessages.POCKET_COMPUTER_DELETED;
}
}

View File

@ -4,13 +4,17 @@
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.speaker.EncodedAudio;
import dan200.computercraft.shared.peripheral.speaker.SpeakerBlockEntity;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPeripheral;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
import net.minecraft.network.FriendlyByteBuf;
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;
@ -19,34 +23,28 @@ import java.util.UUID;
* <p>
* Used by speakers to play sounds.
*
* @param source The {@linkplain SpeakerPeripheral#getSource() id} of the speaker playing audio.
* @param pos The position of the speaker.
* @param content The audio to play.
* @param volume The volume to play the audio at.
* @see SpeakerBlockEntity
*/
public class SpeakerAudioClientMessage implements NetworkMessage<ClientNetworkContext> {
private final UUID source;
private final SpeakerPosition.Message pos;
private final EncodedAudio content;
private final float volume;
public record SpeakerAudioClientMessage(
UUID source,
SpeakerPosition.Message pos,
EncodedAudio content,
float volume
) implements NetworkMessage<ClientNetworkContext> {
public static final StreamCodec<RegistryFriendlyByteBuf, SpeakerAudioClientMessage> STREAM_CODEC = StreamCodec.composite(
UUIDUtil.STREAM_CODEC, SpeakerAudioClientMessage::source,
SpeakerPosition.Message.STREAM_CODEC, SpeakerAudioClientMessage::pos,
EncodedAudio.STREAM_CODEC, SpeakerAudioClientMessage::content,
ByteBufCodecs.FLOAT, SpeakerAudioClientMessage::volume,
SpeakerAudioClientMessage::new
);
public SpeakerAudioClientMessage(UUID source, SpeakerPosition pos, float volume, EncodedAudio content) {
this.source = source;
this.pos = pos.asMessage();
this.content = content;
this.volume = volume;
}
public SpeakerAudioClientMessage(FriendlyByteBuf buf) {
source = buf.readUUID();
pos = SpeakerPosition.Message.read(buf);
volume = buf.readFloat();
content = EncodedAudio.read(buf);
}
@Override
public void write(FriendlyByteBuf buf) {
buf.writeUUID(source);
pos.write(buf);
buf.writeFloat(volume);
content.write(buf);
this(source, pos.asMessage(), content, volume);
}
@Override
@ -55,7 +53,7 @@ public class SpeakerAudioClientMessage implements NetworkMessage<ClientNetworkCo
}
@Override
public MessageType<SpeakerAudioClientMessage> type() {
public CustomPacketPayload.Type<SpeakerAudioClientMessage> type() {
return NetworkMessages.SPEAKER_AUDIO;
}
}

View File

@ -4,12 +4,15 @@
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.speaker.SpeakerBlockEntity;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPeripheral;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.core.UUIDUtil;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import java.util.UUID;
@ -18,26 +21,21 @@ import java.util.UUID;
* <p>
* Used by speakers to play sounds.
*
* @param source The {@linkplain SpeakerPeripheral#getSource() id} of the speaker playing audio.
* @param pos The new position of the speaker.
* @see SpeakerBlockEntity
*/
public class SpeakerMoveClientMessage implements NetworkMessage<ClientNetworkContext> {
private final UUID source;
private final SpeakerPosition.Message pos;
public record SpeakerMoveClientMessage(
UUID source, SpeakerPosition.Message pos
) implements NetworkMessage<ClientNetworkContext> {
public static final StreamCodec<RegistryFriendlyByteBuf, SpeakerMoveClientMessage> STREAM_CODEC = StreamCodec.composite(
UUIDUtil.STREAM_CODEC, SpeakerMoveClientMessage::source,
SpeakerPosition.Message.STREAM_CODEC, SpeakerMoveClientMessage::pos,
SpeakerMoveClientMessage::new
);
public SpeakerMoveClientMessage(UUID source, SpeakerPosition pos) {
this.source = source;
this.pos = pos.asMessage();
}
public SpeakerMoveClientMessage(FriendlyByteBuf buf) {
source = buf.readUUID();
pos = SpeakerPosition.Message.read(buf);
}
@Override
public void write(FriendlyByteBuf buf) {
buf.writeUUID(source);
pos.write(buf);
this(source, pos.asMessage());
}
@Override
@ -46,7 +44,7 @@ public class SpeakerMoveClientMessage implements NetworkMessage<ClientNetworkCon
}
@Override
public MessageType<SpeakerMoveClientMessage> type() {
public CustomPacketPayload.Type<SpeakerMoveClientMessage> type() {
return NetworkMessages.SPEAKER_MOVE;
}
}

View File

@ -4,12 +4,16 @@
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.speaker.SpeakerBlockEntity;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPeripheral;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
import net.minecraft.network.FriendlyByteBuf;
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 net.minecraft.resources.ResourceLocation;
import java.util.UUID;
@ -19,38 +23,31 @@ import java.util.UUID;
* <p>
* Used by speakers to play sounds.
*
* @param source The {@linkplain SpeakerPeripheral#getSource() id} of the speaker playing audio.
* @param pos The position of the speaker.
* @param sound The sound to play.
* @param volume The volume to play the sound at.
* @param pitch The pitch to play the sound at.
* @see SpeakerBlockEntity
*/
public class SpeakerPlayClientMessage implements NetworkMessage<ClientNetworkContext> {
private final UUID source;
private final SpeakerPosition.Message pos;
private final ResourceLocation sound;
private final float volume;
private final float pitch;
public record SpeakerPlayClientMessage(
UUID source,
SpeakerPosition.Message pos,
ResourceLocation sound,
float volume,
float pitch
) implements NetworkMessage<ClientNetworkContext> {
public static final StreamCodec<RegistryFriendlyByteBuf, SpeakerPlayClientMessage> STREAM_CODEC = StreamCodec.composite(
UUIDUtil.STREAM_CODEC, SpeakerPlayClientMessage::source,
SpeakerPosition.Message.STREAM_CODEC, SpeakerPlayClientMessage::pos,
ResourceLocation.STREAM_CODEC, SpeakerPlayClientMessage::sound,
ByteBufCodecs.FLOAT, SpeakerPlayClientMessage::volume,
ByteBufCodecs.FLOAT, SpeakerPlayClientMessage::pitch,
SpeakerPlayClientMessage::new
);
public SpeakerPlayClientMessage(UUID source, SpeakerPosition pos, ResourceLocation sound, float volume, float pitch) {
this.source = source;
this.pos = pos.asMessage();
this.sound = sound;
this.volume = volume;
this.pitch = pitch;
}
public SpeakerPlayClientMessage(FriendlyByteBuf buf) {
source = buf.readUUID();
pos = SpeakerPosition.Message.read(buf);
sound = buf.readResourceLocation();
volume = buf.readFloat();
pitch = buf.readFloat();
}
@Override
public void write(FriendlyByteBuf buf) {
buf.writeUUID(source);
pos.write(buf);
buf.writeResourceLocation(sound);
buf.writeFloat(volume);
buf.writeFloat(pitch);
this(source, pos.asMessage(), sound, volume, pitch);
}
@Override
@ -59,7 +56,7 @@ public class SpeakerPlayClientMessage implements NetworkMessage<ClientNetworkCon
}
@Override
public MessageType<SpeakerPlayClientMessage> type() {
public CustomPacketPayload.Type<SpeakerPlayClientMessage> type() {
return NetworkMessages.SPEAKER_PLAY;
}
}

View File

@ -4,11 +4,14 @@
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.speaker.SpeakerBlockEntity;
import net.minecraft.network.FriendlyByteBuf;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPeripheral;
import net.minecraft.core.UUIDUtil;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import java.util.UUID;
@ -17,23 +20,13 @@ import java.util.UUID;
* <p>
* Called when a speaker is broken.
*
* @param source The {@linkplain SpeakerPeripheral#getSource() id} of the speaker playing audio.
* @see SpeakerBlockEntity
*/
public class SpeakerStopClientMessage implements NetworkMessage<ClientNetworkContext> {
private final UUID source;
public SpeakerStopClientMessage(UUID source) {
this.source = source;
}
public SpeakerStopClientMessage(FriendlyByteBuf buf) {
source = buf.readUUID();
}
@Override
public void write(FriendlyByteBuf buf) {
buf.writeUUID(source);
}
public record SpeakerStopClientMessage(UUID source) implements NetworkMessage<ClientNetworkContext> {
public static final StreamCodec<RegistryFriendlyByteBuf, SpeakerStopClientMessage> STREAM_CODEC = UUIDUtil.STREAM_CODEC
.map(SpeakerStopClientMessage::new, SpeakerStopClientMessage::source)
.cast();
@Override
public void handle(ClientNetworkContext context) {
@ -41,7 +34,7 @@ public class SpeakerStopClientMessage implements NetworkMessage<ClientNetworkCon
}
@Override
public MessageType<SpeakerStopClientMessage> type() {
public CustomPacketPayload.Type<SpeakerStopClientMessage> type() {
return NetworkMessages.SPEAKER_STOP;
}
}

View File

@ -12,11 +12,12 @@ import dan200.computercraft.impl.PocketUpgrades;
import dan200.computercraft.impl.RegistryHelper;
import dan200.computercraft.impl.TurtleUpgrades;
import dan200.computercraft.impl.UpgradeManager;
import dan200.computercraft.shared.network.MessageType;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.NetworkMessages;
import net.minecraft.core.Registry;
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.ResourceKey;
import net.minecraft.resources.ResourceLocation;
@ -27,34 +28,36 @@ import java.util.Map;
* Syncs turtle and pocket upgrades to the client.
*/
public final class UpgradesLoadedMessage implements NetworkMessage<ClientNetworkContext> {
private final Map<String, UpgradeManager.UpgradeWrapper<ITurtleUpgrade>> turtleUpgrades;
private final Map<String, UpgradeManager.UpgradeWrapper<IPocketUpgrade>> pocketUpgrades;
public static final StreamCodec<RegistryFriendlyByteBuf, UpgradesLoadedMessage> STREAM_CODEC = StreamCodec.ofMember(UpgradesLoadedMessage::write, UpgradesLoadedMessage::new);
private final Map<ResourceLocation, UpgradeManager.UpgradeWrapper<ITurtleUpgrade>> turtleUpgrades;
private final Map<ResourceLocation, UpgradeManager.UpgradeWrapper<IPocketUpgrade>> pocketUpgrades;
public UpgradesLoadedMessage() {
turtleUpgrades = TurtleUpgrades.instance().getUpgradeWrappers();
pocketUpgrades = PocketUpgrades.instance().getUpgradeWrappers();
}
public UpgradesLoadedMessage(FriendlyByteBuf buf) {
private UpgradesLoadedMessage(RegistryFriendlyByteBuf buf) {
turtleUpgrades = fromBytes(buf, ITurtleUpgrade.serialiserRegistryKey());
pocketUpgrades = fromBytes(buf, IPocketUpgrade.serialiserRegistryKey());
}
private <T extends UpgradeBase> Map<String, UpgradeManager.UpgradeWrapper<T>> fromBytes(
FriendlyByteBuf buf, ResourceKey<Registry<UpgradeSerialiser<? extends T>>> registryKey
private <T extends UpgradeBase> Map<ResourceLocation, UpgradeManager.UpgradeWrapper<T>> fromBytes(
RegistryFriendlyByteBuf buf, ResourceKey<Registry<UpgradeSerialiser<? extends T>>> registryKey
) {
var registry = RegistryHelper.getRegistry(registryKey);
var size = buf.readVarInt();
Map<String, UpgradeManager.UpgradeWrapper<T>> upgrades = new HashMap<>(size);
Map<ResourceLocation, UpgradeManager.UpgradeWrapper<T>> upgrades = new HashMap<>(size);
for (var i = 0; i < size; i++) {
var id = buf.readUtf();
var id = buf.readResourceLocation();
var serialiserId = buf.readResourceLocation();
var serialiser = registry.get(serialiserId);
if (serialiser == null) throw new IllegalStateException("Unknown serialiser " + serialiserId);
var upgrade = serialiser.fromNetwork(new ResourceLocation(id), buf);
var upgrade = serialiser.fromNetwork(id, buf);
var modId = buf.readUtf();
upgrades.put(id, new UpgradeManager.UpgradeWrapper<T>(id, upgrade, serialiser, modId));
@ -63,20 +66,19 @@ public final class UpgradesLoadedMessage implements NetworkMessage<ClientNetwork
return upgrades;
}
@Override
public void write(FriendlyByteBuf buf) {
private void write(RegistryFriendlyByteBuf buf) {
toBytes(buf, ITurtleUpgrade.serialiserRegistryKey(), turtleUpgrades);
toBytes(buf, IPocketUpgrade.serialiserRegistryKey(), pocketUpgrades);
}
private <T extends UpgradeBase> void toBytes(
FriendlyByteBuf buf, ResourceKey<Registry<UpgradeSerialiser<? extends T>>> registryKey, Map<String, UpgradeManager.UpgradeWrapper<T>> upgrades
RegistryFriendlyByteBuf buf, ResourceKey<Registry<UpgradeSerialiser<? extends T>>> registryKey, Map<ResourceLocation, UpgradeManager.UpgradeWrapper<T>> upgrades
) {
var registry = RegistryHelper.getRegistry(registryKey);
buf.writeVarInt(upgrades.size());
for (var entry : upgrades.entrySet()) {
buf.writeUtf(entry.getKey());
buf.writeResourceLocation(entry.getKey());
var serialiser = entry.getValue().serialiser();
@SuppressWarnings("unchecked")
@ -96,7 +98,7 @@ public final class UpgradesLoadedMessage implements NetworkMessage<ClientNetwork
}
@Override
public MessageType<UpgradesLoadedMessage> type() {
public CustomPacketPayload.Type<UpgradesLoadedMessage> type() {
return NetworkMessages.UPGRADES_LOADED;
}
}

View File

@ -4,60 +4,57 @@
package dan200.computercraft.shared.network.client;
import dan200.computercraft.core.util.Nullability;
import dan200.computercraft.shared.computer.upload.UploadResult;
import dan200.computercraft.shared.network.MessageType;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.NetworkMessages;
import net.minecraft.network.FriendlyByteBuf;
import dan200.computercraft.shared.network.codec.MoreStreamCodecs;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.ComponentSerialization;
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;
import javax.annotation.Nullable;
import java.util.Optional;
public final class UploadResultMessage implements NetworkMessage<ClientNetworkContext> {
public static final StreamCodec<RegistryFriendlyByteBuf, UploadResultMessage> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.VAR_INT, x -> x.containerId,
MoreStreamCodecs.ofEnum(UploadResult.class), x -> x.result,
ComponentSerialization.OPTIONAL_STREAM_CODEC, x -> x.errorMessage,
UploadResultMessage::new
);
public class UploadResultMessage implements NetworkMessage<ClientNetworkContext> {
private final int containerId;
private final UploadResult result;
private final @Nullable Component errorMessage;
private final Optional<Component> errorMessage;
private UploadResultMessage(AbstractContainerMenu container, UploadResult result, @Nullable Component errorMessage) {
containerId = container.containerId;
private UploadResultMessage(int containerId, UploadResult result, Optional<Component> errorMessage) {
this.containerId = containerId;
this.result = result;
this.errorMessage = errorMessage;
}
public static UploadResultMessage queued(AbstractContainerMenu container) {
return new UploadResultMessage(container, UploadResult.QUEUED, null);
return new UploadResultMessage(container.containerId, UploadResult.QUEUED, Optional.empty());
}
public static UploadResultMessage consumed(AbstractContainerMenu container) {
return new UploadResultMessage(container, UploadResult.CONSUMED, null);
return new UploadResultMessage(container.containerId, UploadResult.CONSUMED, Optional.empty());
}
public static UploadResultMessage error(AbstractContainerMenu container, Component errorMessage) {
return new UploadResultMessage(container, UploadResult.ERROR, errorMessage);
}
public UploadResultMessage(FriendlyByteBuf buf) {
containerId = buf.readVarInt();
result = buf.readEnum(UploadResult.class);
errorMessage = result == UploadResult.ERROR ? buf.readComponent() : null;
}
@Override
public void write(FriendlyByteBuf buf) {
buf.writeVarInt(containerId);
buf.writeEnum(result);
if (result == UploadResult.ERROR) buf.writeComponent(Nullability.assertNonNull(errorMessage));
return new UploadResultMessage(container.containerId, UploadResult.ERROR, Optional.of(errorMessage));
}
@Override
public void handle(ClientNetworkContext context) {
context.handleUploadResult(containerId, result, errorMessage);
context.handleUploadResult(containerId, result, errorMessage.orElse(null));
}
@Override
public MessageType<UploadResultMessage> type() {
public CustomPacketPayload.Type<UploadResultMessage> type() {
return NetworkMessages.UPLOAD_RESULT;
}
}

View File

@ -0,0 +1,107 @@
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.network.codec;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.DecoderException;
import net.minecraft.core.NonNullList;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.VarInt;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.world.phys.Vec3;
import java.nio.ByteBuffer;
import java.util.OptionalInt;
/**
* Additional {@link StreamCodec}s.
*
* @see ByteBufCodecs
*/
public class MoreStreamCodecs {
public static <B extends FriendlyByteBuf, C extends Enum<C>> StreamCodec<B, C> ofEnum(Class<C> klass) {
return new StreamCodec<>() {
@Override
public C decode(B buffer) {
return buffer.readEnum(klass);
}
@Override
public void encode(B buffer, C value) {
buffer.writeEnum(value);
}
};
}
public static <B extends FriendlyByteBuf, C> StreamCodec<B, NonNullList<C>> nonNullList(StreamCodec<B, C> codec, C empty) {
return new StreamCodec<>() {
@Override
public NonNullList<C> decode(B buffer) {
var count = buffer.readVarInt();
var result = NonNullList.withSize(count, empty);
for (var i = 0; i < result.size(); i++) result.set(i, codec.decode(buffer));
return result;
}
@Override
public void encode(B buffer, NonNullList<C> list) {
var count = buffer.writeVarInt(list.size());
for (var entry : list) codec.encode(buffer, entry);
}
};
}
public static final StreamCodec<ByteBuf, Vec3> VEC3 = StreamCodec.composite(
ByteBufCodecs.DOUBLE, Vec3::x,
ByteBufCodecs.DOUBLE, Vec3::y,
ByteBufCodecs.DOUBLE, Vec3::z,
Vec3::new
);
/**
* A codec for {@link OptionalInt}. This uses the same wire format as {@link ByteBufCodecs#optional(StreamCodec)}
* and {@link ByteBufCodecs#VAR_INT}.
*/
public static final StreamCodec<ByteBuf, OptionalInt> OPTIONAL_INT = new StreamCodec<>() {
@Override
public OptionalInt decode(ByteBuf buf) {
return buf.readBoolean() ? OptionalInt.of(ByteBufCodecs.VAR_INT.decode(buf)) : OptionalInt.empty();
}
@Override
public void encode(ByteBuf buf, OptionalInt optional) {
if (optional.isPresent()) {
buf.writeBoolean(true);
ByteBufCodecs.VAR_INT.encode(buf, optional.getAsInt());
} else {
buf.writeBoolean(false);
}
}
};
/**
* Equivalent to {@link ByteBufCodecs#BYTE_ARRAY}, but into an immutable {@link ByteBuffer}.
*/
public static final StreamCodec<ByteBuf, ByteBuffer> BYTE_BUFFER = new StreamCodec<>() {
@Override
public ByteBuffer decode(ByteBuf buf) {
var toRead = VarInt.read(buf);
if (toRead > buf.readableBytes()) {
throw new DecoderException("ByteArray with size " + toRead + " is bigger than allowed");
}
var bytes = new byte[toRead];
buf.readBytes(bytes);
return ByteBuffer.wrap(bytes).asReadOnlyBuffer();
}
@Override
public void encode(ByteBuf buf, ByteBuffer buffer) {
VarInt.write(buf, buffer.remaining());
buf.writeBytes(buffer.duplicate());
}
};
}

View File

@ -8,55 +8,37 @@ import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.terminal.TerminalState;
import dan200.computercraft.shared.config.Config;
import net.minecraft.network.FriendlyByteBuf;
import dan200.computercraft.shared.network.codec.MoreStreamCodecs;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.world.item.ItemStack;
public class ComputerContainerData implements ContainerData {
private final ComputerFamily family;
private final TerminalState terminal;
private final ItemStack displayStack;
private final int uploadMaxSize;
/**
* The data required to open a computer container.
*
* @param family The computer family.
* @param terminal The initial terminal contents.
* @param displayStack The stack associated with this menu. This may be displayed on the client.
* @param uploadMaxSize The maximum size of a file upload.
*/
public record ComputerContainerData(
ComputerFamily family, TerminalState terminal, ItemStack displayStack, int uploadMaxSize
) implements ContainerData {
public static final StreamCodec<RegistryFriendlyByteBuf, ComputerContainerData> STREAM_CODEC = StreamCodec.composite(
MoreStreamCodecs.ofEnum(ComputerFamily.class), ComputerContainerData::family,
TerminalState.STREAM_CODEC, ComputerContainerData::terminal,
ItemStack.OPTIONAL_STREAM_CODEC, ComputerContainerData::displayStack,
ByteBufCodecs.VAR_INT, ComputerContainerData::uploadMaxSize,
ComputerContainerData::new
);
public ComputerContainerData(ServerComputer computer, ItemStack displayStack) {
family = computer.getFamily();
terminal = computer.getTerminalState();
this.displayStack = displayStack;
uploadMaxSize = Config.uploadMaxSize;
}
public ComputerContainerData(FriendlyByteBuf buf) {
family = buf.readEnum(ComputerFamily.class);
terminal = new TerminalState(buf);
displayStack = buf.readItem();
uploadMaxSize = buf.readInt();
this(computer.getFamily(), computer.getTerminalState(), displayStack, Config.uploadMaxSize);
}
@Override
public void toBytes(FriendlyByteBuf buf) {
buf.writeEnum(family);
terminal.write(buf);
buf.writeItem(displayStack);
buf.writeInt(uploadMaxSize);
}
public ComputerFamily family() {
return family;
}
public TerminalState terminal() {
return terminal;
}
/**
* Get a stack associated with this menu. This may be displayed on the client.
*
* @return The stack associated with this menu.
*/
public ItemStack displayStack() {
return displayStack;
}
public int uploadMaxSize() {
return uploadMaxSize;
public void toBytes(RegistryFriendlyByteBuf buf) {
STREAM_CODEC.encode(buf, this);
}
}

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