Skip to content

Commit

Permalink
Fix de-syncs with merchant item offers
Browse files Browse the repository at this point in the history
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
  • Loading branch information
booky10 committed Jul 28, 2024
1 parent 965f54a commit aec9dda
Showing 1 changed file with 140 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<Pair<String, DataComponentType<?>>> SAVEABLE_COMPONENT_TYPES = Stream.of(
+ DataComponents.ITEM_NAME, DataComponents.CUSTOM_NAME, DataComponents.LORE
+ )
+ .<Pair<String, DataComponentType<?>>>map(type -> Pair.of(BuiltInRegistries.DATA_COMPONENT_TYPE.getKey(type).toString(), type))
+ .toList();
+ private static final Set<DataComponentType<?>> SAVEABLE_COMPONENT_TYPE_SET = Set.of(
+ DataComponents.ITEM_NAME, DataComponents.CUSTOM_NAME, DataComponents.LORE
+ );
+ private static final List<Pair<String, DataComponentType<?>>> SAVEABLE_COMPONENT_TYPES = SAVEABLE_COMPONENT_TYPE_SET.stream()
+ .<Pair<String, DataComponentType<?>>>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() {
Expand All @@ -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<Object>) type.getSecond(), decoded);
+ }
+ // generally unsafe to do, but this probably won't cause any issues at this place
Expand Down Expand Up @@ -124,11 +128,106 @@ index cc19d9e75233465cbf28f65aecfdf498d4339a63..0117a2c5a714e9c9140380a51883b7fb
+
+ // copy patch map, prevents modification of original item stack
+ Reference2ObjectMap<DataComponentType<?>, 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<TypedDataComponent<?>> 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<String, DataComponentType<?>> 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<Object> newComponent = new TypedDataComponent<>(
+ (DataComponentType<Object>) 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<TypedDataComponent<?>> 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<CustomData> 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<TypedDataComponent<?>> 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<Component> inlineComponent(Component component) {
component = component.compact();
Expand All @@ -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<DataComponentMap>
.apply(ByteBufCodecs.list())
.map(DataComponentPredicate::new, predicate -> predicate.expectedComponents);
public static final DataComponentPredicate EMPTY = new DataComponentPredicate(List.of());
- private final List<TypedDataComponent<?>> expectedComponents;
+ public List<TypedDataComponent<?>> expectedComponents; // CloudPlane - private-f -> public

- DataComponentPredicate(List<TypedDataComponent<?>> components) {
+ public DataComponentPredicate(List<TypedDataComponent<?>> 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
Expand All @@ -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 {
Expand Down Expand Up @@ -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> 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<RegistryFriendlyByteBuf, Optional<ItemCost>> OPTIONAL_STREAM_CODEC = STREAM_CODEC.apply(ByteBufCodecs::optional);

public ItemCost(ItemLike item) {

0 comments on commit aec9dda

Please sign in to comment.