@@ -12,12 +12,61 @@ import net.minecraft.registry.Registries
1212import java.util.concurrent.ConcurrentHashMap
1313import stackabletools.config.ConfigManager
1414import stackabletools.config.StackingCategory
15+ import net.minecraft.nbt.NbtCompound
16+ import net.minecraft.item.Items
1517
1618object StackableToolsUtils {
1719
1820 private val stackableCache = ConcurrentHashMap <String , Boolean >()
1921 private var lastConfigHash = 0
2022
23+ /* *
24+ * Compares two ItemStack NBT tags, ignoring cosmetic differences like custom names.
25+ * Enchantments are compared as sets (order-insensitive).
26+ */
27+ private fun areNBTsEquivalent (nbt1 : NbtCompound ? , nbt2 : NbtCompound ? ): Boolean {
28+ // Both null = equivalent
29+ if (nbt1 == null && nbt2 == null ) return true
30+ // One null, one not = not equivalent
31+ if (nbt1 == null || nbt2 == null ) return false
32+
33+ // Extract enchantment lists
34+ val enc1 = nbt1.getList(" Enchantments" , 10 ) // 10 = NbtElement.COMPOUND_TYPE
35+ val enc2 = nbt2.getList(" Enchantments" , 10 )
36+
37+ // Compare enchantments as sets (order-insensitive)
38+ if (enc1.size != enc2.size) return false
39+
40+ val enc1Set = mutableSetOf<String >()
41+ for (i in 0 until enc1.size) {
42+ enc1Set.add(enc1.getCompound(i).toString())
43+ }
44+
45+ val enc2Set = mutableSetOf<String >()
46+ for (i in 0 until enc2.size) {
47+ enc2Set.add(enc2.getCompound(i).toString())
48+ }
49+
50+ if (enc1Set != enc2Set) return false
51+
52+ // Check that there are no other conflicting NBT tags (ignore display/cosmetic ones)
53+ val cosmetics = setOf (" display" , " HideFlags" , " CustomModelData" , " Damage" )
54+
55+ val relevantKeys1 = nbt1.keys.filter { it !in cosmetics }
56+ val relevantKeys2 = nbt2.keys.filter { it !in cosmetics }
57+
58+ if (relevantKeys1.toSet() != relevantKeys2.toSet()) return false
59+
60+ // Compare non-cosmetic keys
61+ for (key in relevantKeys1) {
62+ val v1 = nbt1.get(key)?.toString()
63+ val v2 = nbt2.get(key)?.toString()
64+ if (v1 != v2) return false
65+ }
66+
67+ return true
68+ }
69+
2170 /* *
2271 * Checks if two item stacks can be merged (same item, same durability, same NBT).
2372 * @param a First item stack.
@@ -36,9 +85,9 @@ object StackableToolsUtils {
3685 // For other items, check exact durability
3786 if (a.damage != b.damage) return false
3887
39- // CRUCIAL RULE: Check that enchantments (NBT) are STRICTLY identical
40- // ItemStack.canCombine already checks for NBT equality (enchantments, custom names, etc.)
41- if (! ItemStack .canCombine(a , b)) return false
88+ // Compare NBT tags: enchantments must match (order-insensitive),
89+ // but ignore cosmetic differences like custom names
90+ if (! areNBTsEquivalent(a.nbt , b.nbt )) return false
4291
4392 return true
4493 }
0 commit comments