From 1501fe29e6ff2734016b235ee9ec96b5df429683 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Wed, 2 Jul 2025 18:16:21 -0400 Subject: [PATCH] Deduplicate ingredient item values using reference equality of components This is necessary to work around Neo's changes to Holder.Reference equality Related: #577 --- .../PatchedDataComponentMapAccessor.java | 18 +++++++ .../recipe/IngredientValueDeduplicator.java | 50 ++++++++++++++++++- 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 neoforge/src/main/java/org/embeddedt/modernfix/neoforge/mixin/perf/ingredient_item_deduplication/PatchedDataComponentMapAccessor.java diff --git a/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/mixin/perf/ingredient_item_deduplication/PatchedDataComponentMapAccessor.java b/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/mixin/perf/ingredient_item_deduplication/PatchedDataComponentMapAccessor.java new file mode 100644 index 00000000..d6003877 --- /dev/null +++ b/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/mixin/perf/ingredient_item_deduplication/PatchedDataComponentMapAccessor.java @@ -0,0 +1,18 @@ +package org.embeddedt.modernfix.neoforge.mixin.perf.ingredient_item_deduplication; + +import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; +import net.minecraft.core.component.DataComponentMap; +import net.minecraft.core.component.DataComponentType; +import net.minecraft.core.component.PatchedDataComponentMap; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.Optional; + +@Mixin(PatchedDataComponentMap.class) +public interface PatchedDataComponentMapAccessor { + @Accessor("prototype") + DataComponentMap mfix$getPrototype(); + @Accessor("patch") + Reference2ObjectMap, Optional> mfix$getPatch(); +} diff --git a/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/recipe/IngredientValueDeduplicator.java b/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/recipe/IngredientValueDeduplicator.java index f5ac0a9b..25e2cebe 100644 --- a/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/recipe/IngredientValueDeduplicator.java +++ b/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/recipe/IngredientValueDeduplicator.java @@ -2,8 +2,11 @@ package org.embeddedt.modernfix.neoforge.recipe; import it.unimi.dsi.fastutil.Hash; import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet; +import it.unimi.dsi.fastutil.objects.Reference2ObjectMaps; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStackLinkedSet; import net.minecraft.world.item.crafting.Ingredient; +import org.embeddedt.modernfix.neoforge.mixin.perf.ingredient_item_deduplication.PatchedDataComponentMapAccessor; /** * @author embeddedt (original inspiration from Uncandango's AllTheLeaks mod) @@ -15,9 +18,54 @@ public class IngredientValueDeduplicator { return o == null ? 0 : ItemStackLinkedSet.TYPE_AND_TAG.hashCode(o.item()); } + private boolean areComponentsSame(ItemStack a, ItemStack b) { + // Compare using stricter logic than vanilla: require the prototype maps to be identity-equal, and require + // the values in the patch to also be identity-equal. This works around Neo allowing Holder.Reference objects + // made with the server & client registries to be considered equal. + if (a.getComponents() instanceof PatchedDataComponentMapAccessor aComps && b.getComponents() instanceof PatchedDataComponentMapAccessor bComps) { + if (aComps.mfix$getPrototype() != bComps.mfix$getPrototype()) { + return false; + } + var aPatch = aComps.mfix$getPatch(); + var bPatch = bComps.mfix$getPatch(); + if (aPatch != bPatch) { + if (aPatch.size() != bPatch.size()) { + return false; + } + for (var entry : Reference2ObjectMaps.fastIterable(aPatch)) { + var value = bPatch.get(entry.getKey()); + if (value == null) { + return false; + } + if (value.isPresent() != entry.getValue().isPresent()) { + return false; + } else if (value.isPresent() && value.get() != entry.getValue().get()) { + return false; + } + } + } + return true; + } else { + return a.getComponents() == b.getComponents(); + } + } + + private boolean areStacksSame(ItemStack a, ItemStack b) { + if (!a.is(b.getItem())) { + return false; + } + if (a.isEmpty() != b.isEmpty()) { + return false; + } + if (!areComponentsSame(a, b)) { + return false; + } + return a.getCount() == b.getCount(); + } + @Override public boolean equals(Ingredient.ItemValue a, Ingredient.ItemValue b) { - return a == b || a != null && b != null && ItemStackLinkedSet.TYPE_AND_TAG.equals(a.item(), b.item()) && a.item().getCount() == b.item().getCount(); + return a == b || a != null && b != null && areStacksSame(a.item(), b.item()); } });