From aec9dda15ac5d3aa41f88a27a52f1ab9b5a2863a Mon Sep 17 00:00:00 2001 From: booky10 Date: Sun, 28 Jul 2024 18:35:07 +0200 Subject: [PATCH] Fix de-syncs with merchant item offers Item cost uses component predicates for serialization, which aren't covered by our ItemStack component packing As the client tries to update the merchant recipe on its own (probably not intended, there is a check to prevent this in one place but not in another), this causes a de-sync in currently selected merchant offer Fixes CloudCraftProjects/Elements#16 --- ...translate-custom-item-names-and-lore.patch | 152 ++++++++++++++++-- 1 file changed, 140 insertions(+), 12 deletions(-) diff --git a/patches/server/0013-Add-option-to-translate-custom-item-names-and-lore.patch b/patches/server/0013-Add-option-to-translate-custom-item-names-and-lore.patch index c6c9931..71a5ef6 100644 --- a/patches/server/0013-Add-option-to-translate-custom-item-names-and-lore.patch +++ b/patches/server/0013-Add-option-to-translate-custom-item-names-and-lore.patch @@ -30,10 +30,10 @@ index b3402ff1789cf0e2ec661282a3ee7bad3f627041..ec8bdaa81a924e7d5eaf5c74b1c4cc21 + } } diff --git a/src/main/java/dev/booky/cloudplane/ItemUtil.java b/src/main/java/dev/booky/cloudplane/ItemUtil.java -index cc19d9e75233465cbf28f65aecfdf498d4339a63..0117a2c5a714e9c9140380a51883b7fbc48bdf1e 100644 +index cc19d9e75233465cbf28f65aecfdf498d4339a63..c5310a783a2b3f6bfa9b236ae43475d3f517a606 100644 --- a/src/main/java/dev/booky/cloudplane/ItemUtil.java +++ b/src/main/java/dev/booky/cloudplane/ItemUtil.java -@@ -1,18 +1,99 @@ +@@ -1,18 +1,198 @@ package dev.booky.cloudplane; +import com.mojang.datafixers.util.Pair; @@ -43,31 +43,35 @@ index cc19d9e75233465cbf28f65aecfdf498d4339a63..0117a2c5a714e9c9140380a51883b7fb import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; +import net.minecraft.core.component.DataComponentPatch; ++import net.minecraft.core.component.DataComponentPredicate; +import net.minecraft.core.component.DataComponentType; +import net.minecraft.core.component.DataComponents; ++import net.minecraft.core.component.TypedDataComponent; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtOps; +import net.minecraft.nbt.Tag; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.component.CustomData; ++import net.minecraft.world.item.trading.ItemCost; import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Optional; -+import java.util.stream.Stream; ++import java.util.Set; + +import static net.minecraft.core.component.DataComponents.CUSTOM_DATA; public final class ItemUtil { -+ private static final List>> SAVEABLE_COMPONENT_TYPES = Stream.of( -+ DataComponents.ITEM_NAME, DataComponents.CUSTOM_NAME, DataComponents.LORE -+ ) -+ .>>map(type -> Pair.of(BuiltInRegistries.DATA_COMPONENT_TYPE.getKey(type).toString(), type)) -+ .toList(); ++ private static final Set> SAVEABLE_COMPONENT_TYPE_SET = Set.of( ++ DataComponents.ITEM_NAME, DataComponents.CUSTOM_NAME, DataComponents.LORE ++ ); ++ private static final List>> SAVEABLE_COMPONENT_TYPES = SAVEABLE_COMPONENT_TYPE_SET.stream() ++ .>>map(type -> Pair.of(BuiltInRegistries.DATA_COMPONENT_TYPE.getKey(type).toString(), type)) ++ .toList(); + private static final String SAVEABLE_COMPONENT_TAG_NAME = "CloudPlane$SavedData"; + private ItemUtil() { @@ -86,8 +90,8 @@ index cc19d9e75233465cbf28f65aecfdf498d4339a63..0117a2c5a714e9c9140380a51883b7fb + continue; // nothing saved for this type, skip + } + Object decoded = type.getSecond().codecOrThrow() -+ .decode(NbtOps.INSTANCE, encoded) -+ .getOrThrow().getFirst(); ++ .decode(NbtOps.INSTANCE, encoded) ++ .getOrThrow().getFirst(); + stack.set((DataComponentType) type.getSecond(), decoded); + } + // generally unsafe to do, but this probably won't cause any issues at this place @@ -124,11 +128,106 @@ index cc19d9e75233465cbf28f65aecfdf498d4339a63..0117a2c5a714e9c9140380a51883b7fb + + // copy patch map, prevents modification of original item stack + Reference2ObjectMap, Optional> newPatch = -+ new Reference2ObjectArrayMap<>(patch.size() + 1); ++ new Reference2ObjectArrayMap<>(patch.size() + 1); + newPatch.putAll(patch); + newPatch.put(CUSTOM_DATA, Optional.of(CustomData.of(tag))); + return new DataComponentPatch(newPatch); + } ++ ++ @SuppressWarnings("unchecked") // not unchecked ++ public static ItemCost unpackPatchSaves(ItemCost cost) { ++ List> components = cost.components().expectedComponents; ++ CustomData customData = null; ++ for (TypedDataComponent component : components) { ++ if (component.type() == CUSTOM_DATA) { ++ customData = (CustomData) component.value(); ++ break; ++ } ++ } ++ if (customData == null || customData.isEmpty() ++ || !customData.contains(SAVEABLE_COMPONENT_TAG_NAME)) { ++ return cost; // nothing to unpack ++ } ++ CompoundTag savedTag = customData.getUnsafe().getCompound(SAVEABLE_COMPONENT_TAG_NAME); ++ components = new ArrayList<>(components); ++ crying: ++ for (Pair> type : SAVEABLE_COMPONENT_TYPES) { ++ Tag encoded = savedTag.get(type.getFirst()); ++ if (encoded == null) { ++ continue; // nothing saved for this type, skip ++ } ++ Object decoded = type.getSecond().codecOrThrow() ++ .decode(NbtOps.INSTANCE, encoded) ++ .getOrThrow().getFirst(); ++ TypedDataComponent newComponent = new TypedDataComponent<>( ++ (DataComponentType) type.getSecond(), decoded); ++ ++ for (int i = 0; i < components.size(); i++) { ++ TypedDataComponent component = components.get(i); ++ if (component.type() == type) { ++ components.set(i, newComponent); ++ continue crying; ++ } ++ } ++ components.add(newComponent); ++ } ++ // generally unsafe to do, but this probably won't cause any issues at this place ++ customData.getUnsafe().remove(SAVEABLE_COMPONENT_TAG_NAME); ++ cost.components().expectedComponents = components; ++ return cost; ++ } ++ ++ public static ItemCost packPatchSaves(ItemCost cost) { ++ List> components = cost.components().expectedComponents; ++ if (components == null || components.isEmpty()) { ++ return cost; // no expected components, skip complex logic ++ } ++ ++ int existingCustomDataIndex = -1; ++ CustomData existingCustomData = null; ++ CompoundTag savedTag = null; // lazy-loaded ++ for (int i = 0; i < components.size(); i++) { ++ TypedDataComponent component = components.get(i); ++ if (component.type() == CUSTOM_DATA) { ++ existingCustomDataIndex = i; ++ existingCustomData = (CustomData) component.value(); ++ continue; ++ } ++ if (!SAVEABLE_COMPONENT_TYPE_SET.contains(component.type())) { ++ continue; // doesn't need to be saved ++ } ++ if (savedTag == null) { ++ savedTag = new CompoundTag(); ++ } ++ Tag encoded = component.encodeValue(NbtOps.INSTANCE).getOrThrow(); ++ String key = BuiltInRegistries.DATA_COMPONENT_TYPE.getKey(component.type()).toString(); ++ savedTag.put(key, encoded); ++ } ++ if (savedTag == null) { // nothing found to be saved, return original ++ return cost; ++ } ++ ++ // add saved data to custom data component ++ CompoundTag tag = (existingCustomData == null ? CustomData.EMPTY : existingCustomData).copyTag(); ++ tag.put(SAVEABLE_COMPONENT_TAG_NAME, savedTag); ++ ++ // create new item cost predicate with packed data ++ TypedDataComponent newComponent = new TypedDataComponent<>( ++ CUSTOM_DATA, CustomData.of(tag)); ++ if (existingCustomDataIndex != -1) { // replace existing ++ components = new ArrayList<>(components); ++ components.set(existingCustomDataIndex, newComponent); ++ } else { // add new ++ List> prevComponents = components; ++ components = new ArrayList<>(components.size() + 1); ++ components.addAll(prevComponents); ++ components.add(newComponent); ++ } ++ ++ // components isn't made immutable here, but doesn't matter ++ DataComponentPredicate componentsPredicate = new DataComponentPredicate(components); ++ return new ItemCost(cost.item(), cost.count(), componentsPredicate, cost.itemStack()); ++ } + private static List inlineComponent(Component component) { component = component.compact(); @@ -146,6 +245,22 @@ index b8977749d35dd7343021425f477445bec470d46b..c94e5ed9d61f1586a08ce1ec6ea032eb this.map = changedComponents; } +diff --git a/src/main/java/net/minecraft/core/component/DataComponentPredicate.java b/src/main/java/net/minecraft/core/component/DataComponentPredicate.java +index 97204a0ff57691d71d7a9879e5ff7246da7af23f..8a1cd7c10cc4777e909bd6a6e4a4caf6e97a623c 100644 +--- a/src/main/java/net/minecraft/core/component/DataComponentPredicate.java ++++ b/src/main/java/net/minecraft/core/component/DataComponentPredicate.java +@@ -24,9 +24,9 @@ public final class DataComponentPredicate implements Predicate + .apply(ByteBufCodecs.list()) + .map(DataComponentPredicate::new, predicate -> predicate.expectedComponents); + public static final DataComponentPredicate EMPTY = new DataComponentPredicate(List.of()); +- private final List> expectedComponents; ++ public List> expectedComponents; // CloudPlane - private-f -> public + +- DataComponentPredicate(List> components) { ++ public DataComponentPredicate(List> components) { // CloudPlane - private -> public + this.expectedComponents = components; + } + diff --git a/src/main/java/net/minecraft/core/component/PatchedDataComponentMap.java b/src/main/java/net/minecraft/core/component/PatchedDataComponentMap.java index 22da75d8197de29a150c9eade7994deecae53a10..c026a6edf497c86be3b3f1b511bb285701173a88 100644 --- a/src/main/java/net/minecraft/core/component/PatchedDataComponentMap.java @@ -160,7 +275,7 @@ index 22da75d8197de29a150c9eade7994deecae53a10..c026a6edf497c86be3b3f1b511bb2857 public PatchedDataComponentMap(DataComponentMap baseComponents) { diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index 58c38bc4361ddf24716f326b0c6fc626d434756e..24d035a40c675ce55893169c74101a9c392cac44 100644 +index f8589837070039b4911a9532b92fa959c7af6352..c6082e43c5f5ab4fc58726e481341990d45cb7e2 100644 --- a/src/main/java/net/minecraft/world/item/ItemStack.java +++ b/src/main/java/net/minecraft/world/item/ItemStack.java @@ -171,6 +171,11 @@ public final class ItemStack implements DataComponentHolder { @@ -250,3 +365,16 @@ index e4bc72ffd982a06a71bfa1c20f4e66e13cf3234a..cda383fb63889177a96e85f41151dce9 .map(ItemLore::new, ItemLore::lines); // CloudPlane end - split lore lines +diff --git a/src/main/java/net/minecraft/world/item/trading/ItemCost.java b/src/main/java/net/minecraft/world/item/trading/ItemCost.java +index 5b365afe523c6f07f156a594737dd58b60ab3d7f..b9cbd081c84904c7701f05b70dd73f16cc15e74d 100644 +--- a/src/main/java/net/minecraft/world/item/trading/ItemCost.java ++++ b/src/main/java/net/minecraft/world/item/trading/ItemCost.java +@@ -33,7 +33,7 @@ public record ItemCost(Holder item, int count, DataComponentPredicate comp + DataComponentPredicate.STREAM_CODEC, + ItemCost::components, + ItemCost::new +- ); ++ ).map(dev.booky.cloudplane.ItemUtil::unpackPatchSaves, dev.booky.cloudplane.ItemUtil::packPatchSaves); // CloudPlane - item translations; fix merchant offers de-sync + public static final StreamCodec> OPTIONAL_STREAM_CODEC = STREAM_CODEC.apply(ByteBufCodecs::optional); + + public ItemCost(ItemLike item) {