Compare commits

...

6 Commits

Author SHA1 Message Date
Jonathan Coates 01407544c9
Update to 1.20.5 (#1793)
- Switch most network code to use StreamCodec
 - Turtle/pocket computer upgrades now use DataComponentPatch instead of
   raw NBT.
2024-04-25 20:32:48 +00:00
Jonathan Coates bd2fd9d4c8
Merge branch 'mc-1.20.x' into mc-1.20.y 2024-04-25 18:23:04 +01:00
Jonathan Coates 00e2e2bd2d
Switch to vanilla's stillValid implementation 2024-04-25 18:21:23 +01:00
Jonathan Coates 7c1f40031b
Some cleanup to network messages
- Use enums for key and mouse actions, rather than integer ids.
 - Change TerminalState to always contain a terminal. We now make
   TerminalState nullable when we want to skip sending anything.
2024-04-25 18:19:34 +01:00
Jonathan Coates 929debd382
Don't build the webside on Windows/Mac
It seems to stall on Mac, and unlike Windows, I don't have access to a
machine to debug it :/.
2024-04-24 21:49:49 +01:00
Jonathan Coates 4980b7355d
Don't share CharsetDecoders across threads
Fixes #1803
2024-04-24 21:19:30 +01:00
400 changed files with 3336 additions and 4020 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
@ -97,7 +97,7 @@ jobs:
- name: ⚒️ Build
run: |
./gradlew --configure-on-demand :core:assemble :web:assemble
./gradlew --configure-on-demand :core:assemble
- name: 🧪 Run tests
run: |

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -10,12 +10,10 @@
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import net.minecraft.Util;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.core.component.DataComponentPatch;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import java.util.Objects;
/**
* Common functionality between {@link ITurtleUpgrade} and {@link IPocketUpgrade}.
*/
@ -56,8 +54,8 @@ public interface UpgradeBase {
/**
* Returns the item stack representing a currently equipped turtle upgrade.
* <p>
* While upgrades can store upgrade data ({@link ITurtleAccess#getUpgradeNBTData(TurtleSide)} and
* {@link IPocketAccess#getUpgradeNBTData()}}, by default this data is discarded when an upgrade is unequipped,
* While upgrades can store upgrade data ({@link ITurtleAccess#getUpgradeData(TurtleSide)} and
* {@link IPocketAccess#getUpgradeData()}}, by default this data is discarded when an upgrade is unequipped,
* and the original item stack is returned.
* <p>
* By overriding this method, you can create a new {@link ItemStack} which contains enough data to
@ -69,24 +67,24 @@ public interface UpgradeBase {
* @param upgradeData The current upgrade data. This should <strong>NOT</strong> be mutated.
* @return The item stack returned when unequipping.
*/
default ItemStack getUpgradeItem(CompoundTag upgradeData) {
default ItemStack getUpgradeItem(DataComponentPatch upgradeData) {
return getCraftingItem();
}
/**
* Extract upgrade data from an {@link ItemStack}.
* <p>
* This upgrade data will be available with {@link ITurtleAccess#getUpgradeNBTData(TurtleSide)} or
* {@link IPocketAccess#getUpgradeNBTData()}.
* This upgrade data will be available with {@link ITurtleAccess#getUpgradeData(TurtleSide)} or
* {@link IPocketAccess#getUpgradeData()}.
* <p>
* This should be an inverse to {@link #getUpgradeItem(CompoundTag)}.
* This should be an inverse to {@link #getUpgradeItem(DataComponentPatch)}.
*
* @param stack The stack that was equipped by the turtle or pocket computer. This will have the same item as
* {@link #getCraftingItem()}.
* @return The upgrade data that should be set on the turtle or pocket computer.
*/
default CompoundTag getUpgradeData(ItemStack stack) {
return new CompoundTag();
default DataComponentPatch getUpgradeData(ItemStack stack) {
return DataComponentPatch.EMPTY;
}
/**
@ -96,26 +94,15 @@ default CompoundTag getUpgradeData(ItemStack stack) {
* 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 @@
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.core.component.DataComponentPatch;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.Contract;
import javax.annotation.Nullable;
/**
* An upgrade (i.e. a {@link ITurtleUpgrade}) and its current upgrade data.
* <p>
* <strong>IMPORTANT:</strong> The {@link #data()} in an upgrade data is often a reference to the original upgrade data.
* Be careful to take a {@linkplain #copy() defensive copy} if you plan to use the data in this upgrade.
*
* @param upgrade The current upgrade.
* @param data The upgrade's data.
* @param <T> The type of upgrade, either {@link ITurtleUpgrade} or {@link IPocketUpgrade}.
*/
public record UpgradeData<T extends UpgradeBase>(T upgrade, CompoundTag data) {
public record UpgradeData<T extends UpgradeBase>(T upgrade, DataComponentPatch data) {
/**
* A utility method to construct a new {@link UpgradeData} instance.
*
@ -31,7 +25,7 @@ public record UpgradeData<T extends UpgradeBase>(T upgrade, CompoundTag data) {
* @param <T> The type of upgrade.
* @return The new {@link UpgradeData} instance.
*/
public static <T extends UpgradeBase> UpgradeData<T> of(T upgrade, CompoundTag data) {
public static <T extends UpgradeBase> UpgradeData<T> of(T upgrade, DataComponentPatch data) {
return new UpgradeData<>(upgrade, data);
}
@ -47,19 +41,7 @@ public static <T extends UpgradeBase> UpgradeData<T> ofDefault(T upgrade) {
}
/**
* Take a copy of a (possibly {@code null}) {@link UpgradeData} instance.
*
* @param upgrade The copied upgrade data.
* @param <T> The type of upgrade.
* @return The newly created upgrade data.
*/
@Contract("!null -> !null; null -> null")
public static <T extends UpgradeBase> @Nullable UpgradeData<T> copyOf(@Nullable UpgradeData<T> upgrade) {
return upgrade == null ? null : upgrade.copy();
}
/**
* Get the {@linkplain UpgradeBase#getUpgradeItem(CompoundTag) upgrade item} for this upgrade.
* Get the {@linkplain UpgradeBase#getUpgradeItem(DataComponentPatch) upgrade item} for this upgrade.
* <p>
* This returns a defensive copy of the item, to prevent accidental mutation of the upgrade data or original
* {@linkplain UpgradeBase#getCraftingItem() upgrade stack}.
@ -69,14 +51,4 @@ public static <T extends UpgradeBase> UpgradeData<T> ofDefault(T upgrade) {
public ItemStack getUpgradeItem() {
return upgrade.getUpgradeItem(data).copy();
}
/**
* Take a copy of this {@link UpgradeData}. This returns a new instance with the same upgrade and a fresh copy of
* the upgrade data.
*
* @return A copy of the current instance.
*/
public UpgradeData<T> copy() {
return new UpgradeData<>(upgrade(), data().copy());
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,7 +6,9 @@
import com.mojang.blaze3d.vertex.Tesselator;
import dan200.computercraft.core.terminal.TextBuffer;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.common.HeldItemMenu;
import dan200.computercraft.shared.media.items.PrintoutData;
import dan200.computercraft.shared.media.items.PrintoutItem;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
@ -35,16 +37,17 @@ public PrintoutScreen(HeldItemMenu container, Inventory player, Component title)
imageHeight = Y_SIZE;
var text = PrintoutItem.getText(container.getStack());
this.text = new TextBuffer[text.length];
for (var i = 0; i < this.text.length; i++) this.text[i] = new TextBuffer(text[i]);
var colours = PrintoutItem.getColours(container.getStack());
this.colours = new TextBuffer[colours.length];
for (var i = 0; i < this.colours.length; i++) this.colours[i] = new TextBuffer(colours[i]);
var printout = container.getStack().getOrDefault(ModRegistry.DataComponents.PRINTOUT.get(), PrintoutData.EMPTY);
this.text = new TextBuffer[printout.lines().size()];
this.colours = new TextBuffer[printout.lines().size()];
for (var i = 0; i < this.text.length; i++) {
var line = printout.lines().get(i);
this.text[i] = new TextBuffer(line.text());
this.colours[i] = new TextBuffer(line.foreground());
}
page = 0;
pages = Math.max(this.text.length / PrintoutItem.LINES_PER_PAGE, 1);
pages = Math.max(this.text.length / PrintoutData.LINES_PER_PAGE, 1);
book = ((PrintoutItem) container.getStack().getItem()).getType() == PrintoutItem.Type.BOOK;
}
@ -89,7 +92,7 @@ protected void renderBg(GuiGraphics graphics, float partialTicks, int mouseX, in
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 void register(EmiRegistry registry) {
registry.setDefaultComparison(ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get(), pocketComparison);
}
private static final Comparison turtleComparison = compareStacks((left, right) ->
left.getItem() instanceof TurtleItem turtle
&& turtle.getUpgrade(left, TurtleSide.LEFT) == turtle.getUpgrade(right, TurtleSide.LEFT)
&& turtle.getUpgrade(left, TurtleSide.RIGHT) == turtle.getUpgrade(right, TurtleSide.RIGHT));
private static final Comparison turtleComparison = compareStacks((left, right)
-> TurtleItem.getUpgrade(left, TurtleSide.LEFT) == TurtleItem.getUpgrade(right, TurtleSide.LEFT)
&& TurtleItem.getUpgrade(left, TurtleSide.RIGHT) == TurtleItem.getUpgrade(right, TurtleSide.RIGHT));
private static final Comparison pocketComparison = compareStacks((left, right) ->
left.getItem() instanceof PocketComputerItem && PocketComputerItem.getUpgrade(left) == PocketComputerItem.getUpgrade(right));
private static final Comparison pocketComparison = compareStacks((left, right) -> PocketComputerItem.getUpgrade(left) == PocketComputerItem.getUpgrade(right));
private static Comparison compareStacks(BiPredicate<ItemStack, ItemStack> test) {
return Comparison.of((left, right) -> {

View File

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

View File

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

View File

@ -49,7 +49,7 @@ public void handleComputerTerminal(int containerId, TerminalState terminal) {
}
@Override
public void handleMonitorData(BlockPos pos, TerminalState terminal) {
public void handleMonitorData(BlockPos pos, @Nullable TerminalState terminal) {
var player = Minecraft.getInstance().player;
if (player == null) return;
@ -67,7 +67,7 @@ public void handlePlayRecord(BlockPos pos, @Nullable SoundEvent sound, @Nullable
}
@Override
public void handlePocketComputerData(UUID instanceId, ComputerState state, int lightState, TerminalState terminal) {
public void handlePocketComputerData(UUID instanceId, ComputerState state, int lightState, @Nullable TerminalState terminal) {
ClientPocketComputers.setState(instanceId, state, lightState, terminal);
}

View File

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

View File

@ -4,11 +4,11 @@
package dan200.computercraft.client.pocket;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.computer.core.ComputerState;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.terminal.TerminalState;
import dan200.computercraft.shared.network.client.PocketComputerDataMessage;
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
import net.minecraft.world.item.ItemStack;
import javax.annotation.Nullable;
@ -43,7 +43,7 @@ public static void remove(UUID id) {
* @param lightColour The current colour of the modem light.
* @param terminalData The current terminal contents.
*/
public static void setState(UUID instanceId, ComputerState state, int lightColour, TerminalState terminalData) {
public static void setState(UUID instanceId, ComputerState state, int lightColour, @Nullable TerminalState terminalData) {
var computer = instances.get(instanceId);
if (computer == null) {
instances.put(instanceId, new PocketComputerData(state, lightColour, terminalData));
@ -53,7 +53,7 @@ public static void setState(UUID instanceId, ComputerState state, int lightColou
}
public static @Nullable PocketComputerData get(ItemStack stack) {
var id = PocketComputerItem.getInstanceID(stack);
return id == null ? null : instances.get(id);
var id = stack.get(ModRegistry.DataComponents.COMPUTER.get());
return id == null ? null : instances.get(id.instance());
}
}

View File

@ -26,10 +26,10 @@ public final class PocketComputerData {
private ComputerState state;
private int lightColour;
PocketComputerData(ComputerState state, int lightColour, TerminalState terminalData) {
PocketComputerData(ComputerState state, int lightColour, @Nullable TerminalState terminalData) {
this.state = state;
this.lightColour = lightColour;
if (terminalData.hasTerminal()) terminal = terminalData.create();
if (terminalData != null) terminal = terminalData.create();
}
public int getLightState() {
@ -44,11 +44,11 @@ public ComputerState getState() {
return state;
}
void setState(ComputerState state, int lightColour, TerminalState terminalData) {
void setState(ComputerState state, int lightColour, @Nullable TerminalState terminalData) {
this.state = state;
this.lightColour = lightColour;
if (terminalData.hasTerminal()) {
if (terminalData != null) {
if (terminal == null) {
terminal = terminalData.create();
} else {

View File

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

View File

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

View File

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

View File

@ -9,11 +9,14 @@
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 static void drawText(PoseStack transform, MultiBufferSource bufferSource,
}
}
public static void drawText(PoseStack transform, MultiBufferSource bufferSource, int x, int y, int start, int light, String[] text, String[] colours) {
public static void drawText(PoseStack transform, MultiBufferSource bufferSource, int x, int y, int start, int light, List<PrintoutData.Line> lines) {
var buffer = bufferSource.getBuffer(RenderTypes.PRINTOUT_TEXT);
var emitter = FixedWidthFontRenderer.toVertexConsumer(transform, buffer);
for (var line = 0; line < LINES_PER_PAGE && line < text.length; line++) {
for (var line = 0; line < LINES_PER_PAGE && line < lines.size(); line++) {
var lineContents = lines.get(start + line);
FixedWidthFontRenderer.drawString(emitter,
x, y + line * FONT_HEIGHT,
new TextBuffer(text[start + line]), new TextBuffer(colours[start + line]),
new TextBuffer(lineContents.text()), new TextBuffer(lineContents.foreground()),
Palette.DEFAULT, light
);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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.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 @@ public static List<SubProviderEntry> getTables() {
);
}
private static void registerBlocks(BiConsumer<ResourceLocation, LootTable.Builder> add) {
private static void registerBlocks(HolderLookup.Provider registries, BiConsumer<ResourceKey<LootTable>, LootTable.Builder> add) {
namedBlockDrop(add, ModRegistry.Blocks.DISK_DRIVE);
selfDrop(add, ModRegistry.Blocks.MONITOR_NORMAL);
selfDrop(add, ModRegistry.Blocks.MONITOR_ADVANCED);
@ -78,15 +78,15 @@ private static void registerBlocks(BiConsumer<ResourceLocation, LootTable.Builde
));
}
private static void registerGeneric(BiConsumer<ResourceLocation, LootTable.Builder> add) {
private static void registerGeneric(HolderLookup.Provider registries, BiConsumer<ResourceKey<LootTable>, LootTable.Builder> add) {
add.accept(CommonHooks.TREASURE_DISK_LOOT, LootTable.lootTable());
}
private static void selfDrop(BiConsumer<ResourceLocation, LootTable.Builder> add, Supplier<? extends Block> wrapper) {
private static void selfDrop(BiConsumer<ResourceKey<LootTable>, LootTable.Builder> add, Supplier<? extends Block> wrapper) {
blockDrop(add, wrapper, LootItem.lootTableItem(wrapper.get()), ExplosionCondition.survivesExplosion());
}
private static void namedBlockDrop(BiConsumer<ResourceLocation, LootTable.Builder> add, Supplier<? extends Block> wrapper) {
private static void namedBlockDrop(BiConsumer<ResourceKey<LootTable>, LootTable.Builder> add, Supplier<? extends Block> wrapper) {
blockDrop(
add, wrapper,
LootItem.lootTableItem(wrapper.get()).apply(CopyNameFunction.copyName(CopyNameFunction.NameSource.BLOCK_ENTITY)),
@ -94,10 +94,10 @@ private static void namedBlockDrop(BiConsumer<ResourceLocation, LootTable.Builde
);
}
private static void computerDrop(BiConsumer<ResourceLocation, LootTable.Builder> add, Supplier<? extends Block> block) {
private static void computerDrop(BiConsumer<ResourceKey<LootTable>, LootTable.Builder> add, Supplier<? extends Block> block) {
blockDrop(
add, block,
DynamicLoot.dynamicEntry(new ResourceLocation(ComputerCraftAPI.MOD_ID, "computer")),
LootItem.lootTableItem(block.get()).apply(CopyComponentsFunction.copyComponents(CopyComponentsFunction.Source.BLOCK_ENTITY)),
AnyOfCondition.anyOf(
BlockNamedEntityLootCondition.BUILDER,
HasComputerIdLootCondition.BUILDER,
@ -107,7 +107,7 @@ private static void computerDrop(BiConsumer<ResourceLocation, LootTable.Builder>
}
private static void blockDrop(
BiConsumer<ResourceLocation, LootTable.Builder> add, Supplier<? extends Block> wrapper,
BiConsumer<ResourceKey<LootTable>, LootTable.Builder> add, Supplier<? extends Block> wrapper,
LootPoolEntryContainer.Builder<?> drop,
LootItemCondition.Builder condition
) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,57 @@
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.mixin;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import com.mojang.datafixers.DSL;
import com.mojang.datafixers.schemas.Schema;
import com.mojang.datafixers.types.templates.TypeTemplate;
import com.mojang.datafixers.util.Pair;
import dan200.computercraft.impl.UpgradeManager;
import dan200.computercraft.shared.ModRegistry.DataComponents;
import net.minecraft.util.datafix.fixes.References;
import net.minecraft.util.datafix.schemas.V3818_3;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import java.util.Arrays;
import java.util.stream.Stream;
/**
* Add our custom data components to the datafixer system.
*
* @see UpgradeManager#codec()
* @see DataComponents#POCKET_UPGRADE
* @see DataComponents#LEFT_TURTLE_UPGRADE
* @see DataComponents#RIGHT_TURTLE_UPGRADE
* @see ItemStackComponentizationFixMixin
*/
@Mixin(V3818_3.class)
class V3818_3Mixin {
@ModifyReturnValue(
method = "method_57277",
at = @At("TAIL")
)
@SuppressWarnings("UnusedMethod")
private static TypeTemplate addExtraTypes(TypeTemplate type, Schema schema) {
// Create a codec for UpgradeData
var upgradeData = DSL.optionalFields("components", References.DATA_COMPONENTS.in(schema));
return extraOptionalFields(type,
Pair.of("computercraft: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.peripheral.monitor.MonitorWatcher;
import dan200.computercraft.shared.util.DropConsumer;
import dan200.computercraft.shared.util.TickScheduler;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
@ -28,7 +29,8 @@
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.storage.loot.BuiltInLootTables;
import net.minecraft.world.level.storage.loot.LootPool;
import net.minecraft.world.level.storage.loot.entries.LootTableReference;
import net.minecraft.world.level.storage.loot.LootTable;
import net.minecraft.world.level.storage.loot.entries.NestedLootTable;
import net.minecraft.world.level.storage.loot.providers.number.ConstantValue;
import javax.annotation.Nullable;
@ -92,9 +94,9 @@ public static void onChunkTicketLevelChanged(ServerLevel level, long chunkPos, i
TickScheduler.onChunkTicketChanged(level, chunkPos, oldLevel, newLevel);
}
public static final ResourceLocation TREASURE_DISK_LOOT = new ResourceLocation(ComputerCraftAPI.MOD_ID, "treasure_disk");
public static final ResourceKey<LootTable> TREASURE_DISK_LOOT = ResourceKey.create(Registries.LOOT_TABLE, new ResourceLocation(ComputerCraftAPI.MOD_ID, "treasure_disk"));
private static final Set<ResourceLocation> TREASURE_DISK_LOOT_TABLES = Set.of(
private static final Set<ResourceKey<LootTable>> TREASURE_DISK_LOOT_TABLES = Set.of(
BuiltInLootTables.SIMPLE_DUNGEON,
BuiltInLootTables.ABANDONED_MINESHAFT,
BuiltInLootTables.STRONGHOLD_CORRIDOR,
@ -107,13 +109,13 @@ public static void onChunkTicketLevelChanged(ServerLevel level, long chunkPos, i
BuiltInLootTables.VILLAGE_CARTOGRAPHER
);
public static @Nullable LootPool.Builder getExtraLootPool(ResourceLocation lootTable) {
if (!lootTable.getNamespace().equals("minecraft") || !TREASURE_DISK_LOOT_TABLES.contains(lootTable)) {
public static @Nullable LootPool.Builder getExtraLootPool(ResourceKey<LootTable> lootTable) {
if (!TREASURE_DISK_LOOT_TABLES.contains(lootTable)) {
return null;
}
return LootPool.lootPool()
.add(LootTableReference.lootTableReference(TREASURE_DISK_LOOT))
.add(NestedLootTable.lootTableReference(TREASURE_DISK_LOOT))
.setRolls(ConstantValue.exactly(1));
}

View File

@ -6,6 +6,7 @@
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.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.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.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.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.MapColor;
import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType;
import java.util.function.BiFunction;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
/**
* Registers ComputerCraft's registry entries and additional objects, such as {@link CauldronInteraction}s and
@ -174,8 +178,8 @@ private static BlockBehaviour.Properties modemProperties() {
public static class BlockEntities {
static final RegistrationHelper<BlockEntityType<?>> REGISTRY = PlatformHelper.get().createRegistrationHelper(Registries.BLOCK_ENTITY_TYPE);
private static <T extends BlockEntity> RegistryEntry<BlockEntityType<T>> ofBlock(RegistryEntry<? extends Block> block, BiFunction<BlockPos, BlockState, T> factory) {
return REGISTRY.register(block.id().getPath(), () -> PlatformHelper.get().createBlockEntityType(factory, block.get()));
private static <T extends BlockEntity> RegistryEntry<BlockEntityType<T>> ofBlock(RegistryEntry<? extends Block> block, BlockEntityType.BlockEntitySupplier<T> factory) {
return REGISTRY.register(block.id().getPath(), () -> BlockEntityType.Builder.of(factory, block.get()).build(null));
}
public static final RegistryEntry<BlockEntityType<MonitorBlockEntity>> MONITOR_NORMAL =
@ -262,6 +266,114 @@ private static <B extends Block, I extends Item> RegistryEntry<I> ofBlock(Regist
() -> new CableBlockItem.WiredModem(Blocks.CABLE.get(), properties()));
}
public static final class DataComponents {
static final RegistrationHelper<DataComponentType<?>> REGISTRY = PlatformHelper.get().createRegistrationHelper(Registries.DATA_COMPONENT_TYPE);
private static <T> RegistryEntry<DataComponentType<T>> register(String name, UnaryOperator<DataComponentType.Builder<T>> unaryOperator) {
return REGISTRY.register(name, () -> unaryOperator.apply(DataComponentType.builder()).build());
}
/**
* 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 static class Menus {
static final RegistrationHelper<MenuType<?>> REGISTRY = PlatformHelper.get().createRegistrationHelper(Registries.MENU);
public static final RegistryEntry<MenuType<ComputerMenuWithoutInventory>> COMPUTER = REGISTRY.register("computer",
() -> ContainerData.toType(ComputerContainerData::new, (id, inv, data) -> new ComputerMenuWithoutInventory(Menus.COMPUTER.get(), id, inv, data)));
() -> ContainerData.toType(ComputerContainerData.STREAM_CODEC, (id, inv, data) -> new ComputerMenuWithoutInventory(Menus.COMPUTER.get(), id, inv, data)));
public static final RegistryEntry<MenuType<ComputerMenuWithoutInventory>> POCKET_COMPUTER = REGISTRY.register("pocket_computer",
() -> ContainerData.toType(ComputerContainerData::new, (id, inv, data) -> new ComputerMenuWithoutInventory(Menus.POCKET_COMPUTER.get(), id, inv, data)));
() -> ContainerData.toType(ComputerContainerData.STREAM_CODEC, (id, inv, data) -> new ComputerMenuWithoutInventory(Menus.POCKET_COMPUTER.get(), id, inv, data)));
public static final RegistryEntry<MenuType<ComputerMenuWithoutInventory>> POCKET_COMPUTER_NO_TERM = REGISTRY.register("pocket_computer_no_term",
() -> ContainerData.toType(ComputerContainerData::new, (id, inv, data) -> new ComputerMenuWithoutInventory(Menus.POCKET_COMPUTER_NO_TERM.get(), id, inv, data)));
() -> ContainerData.toType(ComputerContainerData.STREAM_CODEC, (id, inv, data) -> new ComputerMenuWithoutInventory(Menus.POCKET_COMPUTER_NO_TERM.get(), id, inv, data)));
public static final RegistryEntry<MenuType<TurtleMenu>> TURTLE = REGISTRY.register("turtle",
() -> ContainerData.toType(ComputerContainerData::new, TurtleMenu::ofMenuData));
() -> ContainerData.toType(ComputerContainerData.STREAM_CODEC, TurtleMenu::ofMenuData));
public static final RegistryEntry<MenuType<DiskDriveMenu>> DISK_DRIVE = REGISTRY.register("disk_drive",
() -> new MenuType<>(DiskDriveMenu::new, FeatureFlags.VANILLA_SET));
@ -311,12 +423,12 @@ public static class Menus {
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 static class LootItemConditionTypes {
static final RegistrationHelper<LootItemConditionType> REGISTRY = PlatformHelper.get().createRegistrationHelper(Registries.LOOT_CONDITION_TYPE);
public static final RegistryEntry<LootItemConditionType> BLOCK_NAMED = REGISTRY.register("block_named",
() -> new LootItemConditionType(Codec.unit(BlockNamedEntityLootCondition.INSTANCE)));
() -> new LootItemConditionType(MapCodec.unit(BlockNamedEntityLootCondition.INSTANCE)));
public static final RegistryEntry<LootItemConditionType> PLAYER_CREATIVE = REGISTRY.register("player_creative",
() -> new LootItemConditionType(Codec.unit(PlayerCreativeLootCondition.INSTANCE)));
() -> new LootItemConditionType(MapCodec.unit(PlayerCreativeLootCondition.INSTANCE)));
public static final RegistryEntry<LootItemConditionType> HAS_ID = REGISTRY.register("has_id",
() -> new LootItemConditionType(Codec.unit(HasComputerIdLootCondition.INSTANCE)));
() -> new LootItemConditionType(MapCodec.unit(HasComputerIdLootCondition.INSTANCE)));
}
public static class RecipeSerializers {
@ -362,21 +474,17 @@ private static <T extends CustomRecipe> RegistryEntry<SimpleCraftingRecipeSerial
return REGISTRY.register(name, () -> new SimpleCraftingRecipeSerializer<>(factory));
}
public static final RegistryEntry<RecipeSerializer<CustomShapedRecipe>> SHAPED = REGISTRY.register("shaped", () -> CustomShapedRecipe.serialiser(CustomShapedRecipe::new));
public static final RegistryEntry<RecipeSerializer<CustomShapelessRecipe>> SHAPELESS = REGISTRY.register("shapeless", () -> CustomShapelessRecipe.serialiser(CustomShapelessRecipe::new));
public static final RegistryEntry<RecipeSerializer<ImpostorShapedRecipe>> IMPOSTOR_SHAPED = REGISTRY.register("impostor_shaped", () -> CustomShapedRecipe.serialiser(ImpostorShapedRecipe::new));
public static final RegistryEntry<RecipeSerializer<ImpostorShapelessRecipe>> IMPOSTOR_SHAPELESS = REGISTRY.register("impostor_shapeless", () -> CustomShapelessRecipe.serialiser(ImpostorShapelessRecipe::new));
public static final RegistryEntry<SimpleCraftingRecipeSerializer<ColourableRecipe>> DYEABLE_ITEM = simple("colour", ColourableRecipe::new);
public static final RegistryEntry<SimpleCraftingRecipeSerializer<ClearColourRecipe>> DYEABLE_ITEM_CLEAR = simple("clear_colour", ClearColourRecipe::new);
public static final RegistryEntry<RecipeSerializer<TurtleRecipe>> TURTLE = REGISTRY.register("turtle", () -> TurtleRecipe.validatingSerialiser(TurtleRecipe::of));
public static final RegistryEntry<SimpleCraftingRecipeSerializer<TurtleUpgradeRecipe>> TURTLE_UPGRADE = simple("turtle_upgrade", TurtleUpgradeRecipe::new);
public static final RegistryEntry<RecipeSerializer<TurtleOverlayRecipe>> TURTLE_OVERLAY = REGISTRY.register("turtle_overlay", TurtleOverlayRecipe.Serialiser::new);
public static final RegistryEntry<RecipeSerializer<TurtleOverlayRecipe>> TURTLE_OVERLAY = REGISTRY.register("turtle_overlay", TurtleOverlayRecipe::serialiser);
public static final RegistryEntry<SimpleCraftingRecipeSerializer<PocketComputerUpgradeRecipe>> POCKET_COMPUTER_UPGRADE = simple("pocket_computer_upgrade", PocketComputerUpgradeRecipe::new);
public static final RegistryEntry<SimpleCraftingRecipeSerializer<PrintoutRecipe>> PRINTOUT = simple("printout", PrintoutRecipe::new);
public static final RegistryEntry<SimpleCraftingRecipeSerializer<DiskRecipe>> DISK = simple("disk", DiskRecipe::new);
public static final RegistryEntry<RecipeSerializer<ComputerUpgradeRecipe>> COMPUTER_UPGRADE = REGISTRY.register("computer_upgrade", () -> CustomShapedRecipe.validatingSerialiser(ComputerUpgradeRecipe::of));
public static final RegistryEntry<RecipeSerializer<ComputerConvertRecipe>> COMPUTER_CONVERT = REGISTRY.register("computer_convert", () -> CustomShapedRecipe.serialiser(ComputerConvertRecipe::new));
}
public static class Permissions {
@ -425,7 +533,7 @@ static class CreativeTabs {
out.accept(Items.DISK_DRIVE.get());
for (var colour = 0; colour < 16; colour++) {
out.accept(DiskItem.createFromIDAndColour(-1, null, Colour.VALUES[colour].getHex()));
out.accept(DataComponentUtil.createStack(Items.DISK.get(), net.minecraft.core.component.DataComponents.DYED_COLOR, new DyedItemColor(Colour.VALUES[colour].getHex(), false)));
}
})
.build());
@ -438,6 +546,7 @@ public static void register() {
Blocks.REGISTRY.register();
BlockEntities.REGISTRY.register();
Items.REGISTRY.register();
DataComponents.REGISTRY.register();
TurtleSerialisers.REGISTRY.register();
PocketUpgradeSerialisers.REGISTRY.register();
Menus.REGISTRY.register();
@ -470,14 +579,14 @@ public static void registerMainThread() {
}
private static void addTurtle(CreativeModeTab.Output out, TurtleItem turtle) {
out.accept(turtle.create(-1, null, -1, null, null, 0, null));
out.accept(new ItemStack(turtle));
TurtleUpgrades.getVanillaUpgrades()
.map(x -> turtle.create(-1, null, -1, null, UpgradeData.ofDefault(x), 0, null))
.map(x -> DataComponentUtil.createStack(turtle, DataComponents.RIGHT_TURTLE_UPGRADE.get(), UpgradeData.ofDefault(x)))
.forEach(out::accept);
}
private static void addPocket(CreativeModeTab.Output out, PocketComputerItem pocket) {
out.accept(pocket.create(-1, null, -1, null));
PocketUpgrades.getVanillaUpgrades().map(x -> pocket.create(-1, null, -1, UpgradeData.ofDefault(x))).forEach(out::accept);
out.accept(new ItemStack(pocket));
PocketUpgrades.getVanillaUpgrades().map(x -> DataComponentUtil.createStack(pocket, DataComponents.POCKET_UPGRADE.get(), UpgradeData.ofDefault(x))).forEach(out::accept);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,19 +7,14 @@
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.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 @@ protected AbstractComputerBlock(Properties settings, RegistryEntry<BlockEntityTy
}
@Override
@Deprecated
public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean isMoving) {
protected void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean isMoving) {
super.onPlace(state, world, pos, oldState, isMoving);
var tile = world.getBlockEntity(pos);
@ -61,14 +53,12 @@ public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldS
}
@Override
@Deprecated
public boolean isSignalSource(BlockState state) {
protected boolean isSignalSource(BlockState state) {
return true;
}
@Override
@Deprecated
public int getDirectSignal(BlockState state, BlockGetter world, BlockPos pos, Direction incomingSide) {
protected int getDirectSignal(BlockState state, BlockGetter world, BlockPos pos, Direction incomingSide) {
var entity = world.getBlockEntity(pos);
if (!(entity instanceof AbstractComputerBlockEntity computerEntity)) return 0;
@ -79,11 +69,14 @@ public int getDirectSignal(BlockState state, BlockGetter world, BlockPos pos, Di
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 int getBundledRedstoneOutput(Level world, BlockPos pos, Direction side) {
}
@Override
@Deprecated
public ItemStack getCloneItemStack(LevelReader world, BlockPos pos, BlockState state) {
var tile = world.getBlockEntity(pos);
if (tile instanceof AbstractComputerBlockEntity computer) {
@ -113,55 +105,21 @@ public ItemStack getCloneItemStack(LevelReader world, BlockPos pos, BlockState s
@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 InteractionResult use(BlockState state, Level level, BlockPos pos, Player
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 final void onNeighborChange(BlockState state, LevelReader world, BlockPos
}
@Override
@Deprecated
public BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor level, BlockPos pos, BlockPos neighborPos) {
protected BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor level, BlockPos pos, BlockPos neighborPos) {
var be = level.getBlockEntity(pos);
if (be instanceof AbstractComputerBlockEntity computer) computer.neighbourShapeChanged(direction);
@ -200,8 +156,7 @@ public BlockState updateShape(BlockState state, Direction direction, BlockState
@Nullable
@Override
@Deprecated
public MenuProvider getMenuProvider(BlockState state, Level level, BlockPos pos) {
protected MenuProvider getMenuProvider(BlockState state, Level level, BlockPos pos) {
return level.getBlockEntity(pos) instanceof AbstractComputerBlockEntity computer ? computer : null;
}

View File

@ -9,21 +9,23 @@
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;
import net.minecraft.world.Container;
import net.minecraft.world.LockCode;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.Nameable;
@ -75,13 +77,13 @@ public void setRemoved() {
unload();
}
protected double getInteractRange() {
return BlockEntityHelpers.DEFAULT_INTERACT_RANGE;
protected float getInteractRange() {
return Container.DEFAULT_DISTANCE_BUFFER;
}
public boolean isUsable(Player player) {
return BaseContainerBlockEntity.canUnlock(player, lockCode, getDisplayName())
&& BlockEntityHelpers.isUsable(this, player, getInteractRange());
&& Container.stillValidBlockEntity(this, player, getInteractRange());
}
protected void serverTick() {
@ -136,7 +138,7 @@ protected void serverTick() {
protected abstract void updateBlockState(ComputerState newState);
@Override
public void saveAdditional(CompoundTag nbt) {
public void saveAdditional(CompoundTag nbt, HolderLookup.Provider registries) {
// Save ID, label and power state
if (computerID >= 0) nbt.putInt(NBT_ID, computerID);
if (label != null) nbt.putString(NBT_LABEL, label);
@ -144,20 +146,20 @@ public void saveAdditional(CompoundTag nbt) {
lockCode.addToTag(nbt);
super.saveAdditional(nbt);
super.saveAdditional(nbt, registries);
}
@Override
public final void load(CompoundTag nbt) {
super.load(nbt);
public final void loadAdditional(CompoundTag nbt, HolderLookup.Provider registries) {
super.loadAdditional(nbt, registries);
if (level != null && level.isClientSide) {
loadClient(nbt);
loadClient(nbt, registries);
} else {
loadServer(nbt);
loadServer(nbt, registries);
}
}
protected void loadServer(CompoundTag nbt) {
protected void loadServer(CompoundTag nbt, HolderLookup.Provider registries) {
// Load ID, label and power state
computerID = nbt.contains(NBT_ID) ? nbt.getInt(NBT_ID) : -1;
label = nbt.contains(NBT_LABEL) ? nbt.getString(NBT_LABEL) : null;
@ -166,6 +168,31 @@ protected void loadServer(CompoundTag nbt) {
lockCode = LockCode.fromTag(nbt);
}
@Override
protected void applyImplicitComponents(DataComponentInput component) {
super.applyImplicitComponents(component);
label = DataComponentUtil.getCustomName(component.get(DataComponents.CUSTOM_NAME));
computerID = 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;
}
@ -369,15 +396,15 @@ public final ClientboundBlockEntityDataPacket getUpdatePacket() {
}
@Override
public CompoundTag getUpdateTag() {
public CompoundTag getUpdateTag(HolderLookup.Provider registries) {
// We need this for pick block on the client side.
var nbt = super.getUpdateTag();
var nbt = super.getUpdateTag(registries);
if (label != null) nbt.putString(NBT_LABEL, label);
if (computerID >= 0) nbt.putInt(NBT_ID, computerID);
return nbt;
}
protected void loadClient(CompoundTag nbt) {
protected void loadClient(CompoundTag nbt, HolderLookup.Provider registries) {
label = nbt.contains(NBT_LABEL) ? nbt.getString(NBT_LABEL) : null;
computerID = nbt.contains(NBT_ID) ? nbt.getInt(NBT_ID) : -1;
}

View File

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

View File

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

View File

@ -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,8 @@
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;
@ -18,71 +20,54 @@
* 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;
@Nullable
private final ByteBuf buffer;
public TerminalState(@Nullable NetworkedTerminal terminal) {
if (terminal == null) {
colour = false;
width = height = 0;
buffer = null;
} else {
colour = terminal.isColour();
width = terminal.getWidth();
height = terminal.getHeight();
public TerminalState(NetworkedTerminal terminal) {
colour = terminal.isColour();
width = terminal.getWidth();
height = terminal.getHeight();
var buf = buffer = Unpooled.buffer();
terminal.write(new FriendlyByteBuf(buf));
}
var buf = buffer = Unpooled.buffer();
terminal.write(new FriendlyByteBuf(buf));
}
public TerminalState(FriendlyByteBuf buf) {
@Contract("null -> null; !null -> !null")
public static @Nullable TerminalState create(@Nullable NetworkedTerminal terminal) {
return terminal == null ? null : new TerminalState(terminal);
}
private TerminalState(FriendlyByteBuf buf) {
colour = buf.readBoolean();
width = buf.readVarInt();
height = buf.readVarInt();
if (buf.readBoolean()) {
width = buf.readVarInt();
height = buf.readVarInt();
var length = buf.readVarInt();
buffer = buf.readBytes(length);
} else {
width = height = 0;
buffer = null;
}
var length = buf.readVarInt();
buffer = buf.readBytes(length);
}
public void write(FriendlyByteBuf buf) {
private void write(FriendlyByteBuf buf) {
buf.writeBoolean(colour);
buf.writeBoolean(buffer != null);
if (buffer != null) {
buf.writeVarInt(width);
buf.writeVarInt(height);
buf.writeVarInt(buffer.readableBytes());
buf.writeBytes(buffer, buffer.readerIndex(), buffer.readableBytes());
}
}
public boolean hasTerminal() {
return buffer != null;
buf.writeVarInt(width);
buf.writeVarInt(height);
buf.writeVarInt(buffer.readableBytes());
buf.writeBytes(buffer, buffer.readerIndex(), buffer.readableBytes());
}
public int size() {
return buffer == null ? 0 : buffer.readableBytes();
return buffer.readableBytes();
}
public void apply(NetworkedTerminal terminal) {
if (buffer == null) throw new NullPointerException("buffer");
terminal.resize(width, height);
terminal.read(new FriendlyByteBuf(buffer));
}
public NetworkedTerminal create() {
if (buffer == null) throw new NullPointerException("Terminal does not exist");
var terminal = new NetworkedTerminal(width, height, colour);
terminal.read(new FriendlyByteBuf(buffer));
return terminal;

View File

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

View File

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

View File

@ -27,7 +27,7 @@ private PlayerCreativeLootCondition() {
@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 static void fill(Map<? super String, Object> data, ItemStack stack) {
data.put("tags", DetailHelpers.getTags(stack.getTags()));
data.put("itemGroups", getItemGroups(stack));
var tag = stack.getTag();
if (tag != null && tag.contains("display", Tag.TAG_COMPOUND)) {
var displayTag = tag.getCompound("display");
if (displayTag.contains("Lore", Tag.TAG_LIST)) {
var loreTag = displayTag.getList("Lore", Tag.TAG_STRING);
data.put("lore", loreTag.stream()
.map(ItemDetails::parseTextComponent)
.filter(Objects::nonNull)
.map(Component::getString)
.toList());
}
}
var lore = stack.get(DataComponents.LORE);
if (lore != null) data.put("lore", lore.lines().stream().map(Component::getString).toList());
/*
* Used to hide some data from ItemStack tooltip.
* @see https://minecraft.wiki/w/Tutorials/Command_NBT_tags
* @see ItemStack#getTooltip
*/
var hideFlags = tag != null ? tag.getInt("HideFlags") : 0;
var enchants = getAllEnchants(stack, hideFlags);
var enchants = getAllEnchants(stack);
if (!enchants.isEmpty()) data.put("enchantments", enchants);
if (tag != null && tag.getBoolean("Unbreakable") && (hideFlags & 4) == 0) {
data.put("unbreakable", true);
}
}
@Nullable
private static Component parseTextComponent(Tag x) {
try {
return Component.Serializer.fromJson(x.getAsString());
} catch (JsonParseException e) {
return null;
}
var unbreakable = stack.get(DataComponents.UNBREAKABLE);
if (unbreakable != null && unbreakable.showInTooltip()) data.put("unbreakable", true);
}
/**
@ -107,26 +85,13 @@ private static List<Map<String, Object>> getItemGroups(ItemStack stack) {
/**
* Retrieve all visible enchantments from given stack. Try to follow all tooltip rules : order and visibility.
*
* @param stack Stack to analyse
* @param hideFlags An int used as bit field to provide visibility rules.
* @param stack Stack to analyse
* @return A filled list that contain all visible enchantments.
*/
private static List<Map<String, Object>> getAllEnchants(ItemStack stack, int hideFlags) {
private static List<Map<String, Object>> getAllEnchants(ItemStack stack) {
var enchants = new ArrayList<Map<String, Object>>(0);
if (stack.getItem() instanceof EnchantedBookItem && (hideFlags & 32) == 0) {
addEnchantments(EnchantedBookItem.getEnchantments(stack), enchants);
}
if (stack.isEnchanted() && (hideFlags & 1) == 0) {
/*
* Mimic the EnchantmentHelper.getEnchantments(ItemStack stack) behavior without special case for Enchanted book.
* I'll do that to have the same data than ones displayed in tooltip.
* @see EnchantmentHelper.getEnchantments(ItemStack stack)
*/
addEnchantments(stack.getEnchantmentTags(), enchants);
}
addEnchantments(stack.get(DataComponents.STORED_ENCHANTMENTS), enchants);
addEnchantments(stack.get(DataComponents.ENCHANTMENTS), enchants);
return enchants;
}
@ -138,18 +103,18 @@ private static List<Map<String, Object>> getAllEnchants(ItemStack stack, int hid
* @see EnchantmentHelper
*/
@SuppressWarnings("NonApiType")
private static void addEnchantments(ListTag rawEnchants, ArrayList<Map<String, Object>> enchants) {
if (rawEnchants.isEmpty()) return;
private static void addEnchantments(@Nullable ItemEnchantments rawEnchants, ArrayList<Map<String, Object>> enchants) {
if (rawEnchants == null || rawEnchants.isEmpty()) return;
enchants.ensureCapacity(enchants.size() + rawEnchants.size());
for (var entry : EnchantmentHelper.deserializeEnchantments(rawEnchants).entrySet()) {
for (var entry : rawEnchants.entrySet()) {
var enchantment = entry.getKey();
var level = entry.getValue();
var level = entry.getIntValue();
var enchant = new HashMap<String, Object>(3);
enchant.put("name", DetailHelpers.getId(BuiltInRegistries.ENCHANTMENT, enchantment));
enchant.put("name", DetailHelpers.getId(BuiltInRegistries.ENCHANTMENT, enchantment.value()));
enchant.put("level", level);
enchant.put("displayName", enchantment.getFullname(level).getString());
enchant.put("displayName", enchantment.value().getFullname(level).getString());
enchants.add(enchant);
}
}

View File

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

View File

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

View File

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

View File

@ -10,43 +10,34 @@
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 boolean doesSneakBypassUse(ItemStack stack, LevelReader world, BlockPos p
@Override
public @Nullable String getLabel(ItemStack stack) {
return stack.hasCustomHoverName() ? stack.getHoverName().getString() : null;
var label = stack.get(DataComponents.CUSTOM_NAME);
return label != null ? label.getString() : null;
}
@Override
public boolean setLabel(ItemStack stack, @Nullable String label) {
if (label != null) {
stack.setHoverName(Component.literal(label));
} else {
stack.resetHoverName();
}
stack.set(DataComponents.CUSTOM_NAME, label != null ? Component.literal(label) : null);
return true;
}
@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.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 PrintoutItem(Properties settings, Type type) {
}
@Override
public void appendHoverText(ItemStack stack, @Nullable Level world, List<Component> list, TooltipFlag options) {
public void appendHoverText(ItemStack stack, TooltipContext context, List<Component> list, TooltipFlag options) {
var title = getTitle(stack);
if (title != null && !title.isEmpty()) list.add(Component.literal(title));
if (!title.isEmpty()) list.add(Component.literal(title));
}
@Override
@ -58,70 +48,17 @@ public InteractionResultHolder<ItemStack> use(Level world, Player player, Intera
return new InteractionResultHolder<>(InteractionResult.sidedSuccess(world.isClientSide), player.getItemInHand(hand));
}
private ItemStack createFromTitleAndText(@Nullable String title, @Nullable String[] text, @Nullable String[] colours) {
var stack = new ItemStack(this);
// Build NBT
if (title != null) stack.getOrCreateTag().putString(NBT_TITLE, title);
if (text != null) {
var tag = stack.getOrCreateTag();
tag.putInt(NBT_PAGES, text.length / LINES_PER_PAGE);
for (var i = 0; i < text.length; i++) {
if (text[i] != null) tag.putString(NBT_LINE_TEXT + i, text[i]);
}
}
if (colours != null) {
var tag = stack.getOrCreateTag();
for (var i = 0; i < colours.length; i++) {
if (colours[i] != null) tag.putString(NBT_LINE_COLOUR + i, colours[i]);
}
}
return stack;
}
public static ItemStack createSingleFromTitleAndText(@Nullable String title, @Nullable String[] text, @Nullable String[] colours) {
return ModRegistry.Items.PRINTED_PAGE.get().createFromTitleAndText(title, text, colours);
}
public static ItemStack createMultipleFromTitleAndText(@Nullable String title, @Nullable String[] text, @Nullable String[] colours) {
return ModRegistry.Items.PRINTED_PAGES.get().createFromTitleAndText(title, text, colours);
}
public static ItemStack createBookFromTitleAndText(@Nullable String title, @Nullable String[] text, @Nullable String[] colours) {
return ModRegistry.Items.PRINTED_BOOK.get().createFromTitleAndText(title, text, colours);
}
public Type getType() {
return type;
}
public static String getTitle(ItemStack stack) {
var nbt = stack.getTag();
return nbt != null && nbt.contains(NBT_TITLE) ? nbt.getString(NBT_TITLE) : "";
var nbt = stack.get(ModRegistry.DataComponents.PRINTOUT.get());
return nbt == null ? "" : nbt.title();
}
public static int getPageCount(ItemStack stack) {
var nbt = stack.getTag();
return nbt != null && nbt.contains(NBT_PAGES) ? nbt.getInt(NBT_PAGES) : 1;
}
public static String[] getText(ItemStack stack) {
return getLines(stack, NBT_LINE_TEXT);
}
public static String[] getColours(ItemStack stack) {
return getLines(stack, NBT_LINE_COLOUR);
}
private static String[] getLines(ItemStack stack, String prefix) {
var nbt = stack.getTag();
var numLines = getPageCount(stack) * LINES_PER_PAGE;
var lines = new String[numLines];
for (var i = 0; i < lines.length; i++) {
lines[i] = nbt != null ? nbt.getString(prefix + i) : "";
}
return lines;
var nbt = stack.get(ModRegistry.DataComponents.PRINTOUT.get());
return nbt == null ? 1 : nbt.pages();
}
}

View File

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

View File

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

View File

@ -5,9 +5,11 @@
package dan200.computercraft.shared.media.recipes;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.media.items.PrintoutData;
import dan200.computercraft.shared.media.items.PrintoutItem;
import dan200.computercraft.shared.platform.PlatformHelper;
import net.minecraft.core.RegistryAccess;
import dan200.computercraft.shared.util.DataComponentUtil;
import net.minecraft.core.HolderLookup;
import net.minecraft.world.inventory.CraftingContainer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
@ -17,6 +19,8 @@
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.level.Level;
import java.util.List;
public final class PrintoutRecipe extends CustomRecipe {
private final Ingredient leather;
private final Ingredient string;
@ -35,8 +39,8 @@ public boolean canCraftInDimensions(int x, int y) {
}
@Override
public ItemStack getResultItem(RegistryAccess registryAccess) {
return PrintoutItem.createMultipleFromTitleAndText(null, null, null);
public ItemStack getResultItem(HolderLookup.Provider registryAccess) {
return new ItemStack(ModRegistry.Items.PRINTED_PAGES.get());
}
@Override
@ -45,7 +49,7 @@ public boolean matches(CraftingContainer inventory, Level world) {
}
@Override
public ItemStack assemble(CraftingContainer inventory, RegistryAccess registryAccess) {
public ItemStack assemble(CraftingContainer inventory, HolderLookup.Provider registryAccess) {
// See if we match the recipe, and extract the input disk ID and dye colour
var numPages = 0;
var numPrintouts = 0;
@ -82,43 +86,30 @@ public ItemStack assemble(CraftingContainer inventory, RegistryAccess registryAc
}
// Build some pages with what was passed in
if (numPages <= PrintoutItem.MAX_PAGES && stringFound && printoutFound && numPrintouts >= (leatherFound ? 1 : 2)) {
if (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 @@
import dan200.computercraft.shared.network.client.ClientNetworkContext;
import dan200.computercraft.shared.network.server.ServerNetworkContext;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
/**
* The base interface for any message which will be sent to the client or server.
@ -16,23 +15,7 @@
* @see ClientNetworkContext
* @see ServerNetworkContext
*/
public interface NetworkMessage<T> {
/**
* Get the type of this message.
*
* @return The type of this message.
*/
MessageType<?> type();
/**
* Write this packet to a buffer.
* <p>
* This may be called on any thread, so this should be a pure operation.
*
* @param buf The buffer to write data to.
*/
void write(FriendlyByteBuf buf);
public interface NetworkMessage<T> extends CustomPacketPayload {
/**
* Handle this {@link NetworkMessage}.
*

View File

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

View File

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

View File

@ -26,11 +26,11 @@ public interface ClientNetworkContext {
void handleComputerTerminal(int containerId, TerminalState terminal);
void handleMonitorData(BlockPos pos, TerminalState terminal);
void handleMonitorData(BlockPos pos, @Nullable TerminalState terminal);
void handlePlayRecord(BlockPos pos, @Nullable SoundEvent sound, @Nullable String name);
void handlePocketComputerData(UUID instanceId, ComputerState state, int lightState, TerminalState terminal);
void handlePocketComputerData(UUID instanceId, ComputerState state, int lightState, @Nullable TerminalState terminal);
void handlePocketComputerDeleted(UUID instanceId);

View File

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

View File

@ -5,40 +5,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 java.util.Optional;
public class MonitorClientMessage implements NetworkMessage<ClientNetworkContext> {
private final BlockPos pos;
private final TerminalState state;
public MonitorClientMessage(BlockPos pos, TerminalState state) {
this.pos = pos;
this.state = state;
}
public MonitorClientMessage(FriendlyByteBuf buf) {
pos = buf.readBlockPos();
state = new TerminalState(buf);
}
@Override
public void write(FriendlyByteBuf buf) {
buf.writeBlockPos(pos);
state.write(buf);
}
/**
* Update the terminal contents of a monitor.
*
* @param pos The position of the origin monitor.
* @param terminal The current monitor terminal.
*/
public record MonitorClientMessage(
BlockPos pos, 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.terminal.NetworkedTerminal;
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 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 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() : new TerminalState((NetworkedTerminal) null);
}
public PocketComputerDataMessage(FriendlyByteBuf buf) {
clientId = buf.readUUID();
state = buf.readEnum(ComputerState.class);
lightState = buf.readVarInt();
terminal = new TerminalState(buf);
}
@Override
public void write(FriendlyByteBuf buf) {
buf.writeUUID(clientId);
buf.writeEnum(state);
buf.writeVarInt(lightState);
terminal.write(buf);
this(
computer.getInstanceUUID(),
computer.getState(),
computer.getLight(),
sendTerminal ? 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 void handle(ClientNetworkContext context) {
}
@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 @@
* <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 void handle(ClientNetworkContext context) {
}
@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 @@
* <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 void handle(ClientNetworkContext context) {
}
@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 @@
* <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 void handle(ClientNetworkContext context) {
}
@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 @@
* <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 void handle(ClientNetworkContext context) {
}
@Override
public MessageType<SpeakerStopClientMessage> type() {
public CustomPacketPayload.Type<SpeakerStopClientMessage> type() {
return NetworkMessages.SPEAKER_STOP;
}
}

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