From 144ca2d06fc75b2d754e3cfa803b0ce673d7651d Mon Sep 17 00:00:00 2001 From: Pablo Herrera Date: Thu, 7 Nov 2024 00:12:13 +0100 Subject: [PATCH] Make item matching ignore modifier uuids (#1426) Signed-off-by: Pablo Herrera --- .../main/java/tc/oc/pgm/shops/ShopModule.java | 18 ++++------ .../java/tc/oc/pgm/shops/menu/Payment.java | 10 ++++-- .../tc/oc/pgm/util/xml/XMLFluentParser.java | 10 ++++++ .../platform/modern/ModernInventoryUtil.java | 23 ++++++++++++ .../platform/sportpaper/SpInventoryUtil.java | 19 ++++++++++ .../oc/pgm/util/inventory/InventoryUtils.java | 35 +++++++++++++++++++ .../tc/oc/pgm/util/material/Materials.java | 18 ++++++++-- 7 files changed, 117 insertions(+), 16 deletions(-) diff --git a/core/src/main/java/tc/oc/pgm/shops/ShopModule.java b/core/src/main/java/tc/oc/pgm/shops/ShopModule.java index d14e145968..ab6a953a7b 100644 --- a/core/src/main/java/tc/oc/pgm/shops/ShopModule.java +++ b/core/src/main/java/tc/oc/pgm/shops/ShopModule.java @@ -43,7 +43,6 @@ import tc.oc.pgm.shops.menu.Icon; import tc.oc.pgm.shops.menu.Payment; import tc.oc.pgm.util.xml.InvalidXMLException; -import tc.oc.pgm.util.xml.Node; import tc.oc.pgm.util.xml.XMLFluentParser; import tc.oc.pgm.util.xml.XMLUtils; @@ -184,20 +183,15 @@ public static List parsePayments(Element parent, XMLFluentParser parser return payments; } - public static Payment parsePayment(Element element, XMLFluentParser parser) + public static Payment parsePayment(Element el, XMLFluentParser parser) throws InvalidXMLException { - Node priceAttr = Node.fromAttr(element, "price"); - Node currencyAttr = Node.fromAttr(element, "currency"); - Node colorAttr = Node.fromAttr(element, "color"); + Integer price = parser.parseInt(el, "price").optional(0); + Material currency = price <= 0 ? null : parser.material(el, "currency").orNull(); + ChatColor color = parser.parseEnum(ChatColor.class, el, "color").optional(ChatColor.GOLD); - Integer price = XMLUtils.parseNumber(priceAttr, Integer.class, 0); - Material currency = - price <= 0 || currencyAttr == null ? null : XMLUtils.parseMaterial(currencyAttr); - ChatColor color = XMLUtils.parseChatColor(colorAttr, ChatColor.GOLD); - - ItemStack item = parser.item(element, "item").child().orNull(); + ItemStack item = parser.item(el, "item").child().orNull(); if (currency == null && item == null && price > 0) { - throw new InvalidXMLException("A 'currency' attribute or child is required", element); + throw new InvalidXMLException("A 'currency' attribute or child is required", el); } return new Payment(currency, price, color, item); diff --git a/core/src/main/java/tc/oc/pgm/shops/menu/Payment.java b/core/src/main/java/tc/oc/pgm/shops/menu/Payment.java index b67d21ed82..381000f34f 100644 --- a/core/src/main/java/tc/oc/pgm/shops/menu/Payment.java +++ b/core/src/main/java/tc/oc/pgm/shops/menu/Payment.java @@ -39,8 +39,14 @@ public ChatColor getColor() { } public boolean hasPayment(PlayerInventory inventory) { - return price <= 0 - || (item != null ? inventory.contains(item, price) : inventory.contains(currency, price)); + if (price <= 0) return true; + + int remaining = price; + for (ItemStack item : inventory.getContents()) { + if (item == null || !matches(item)) continue; + if ((remaining -= item.getAmount()) <= 0) return true; + } + return false; } public boolean matches(ItemStack item) { diff --git a/core/src/main/java/tc/oc/pgm/util/xml/XMLFluentParser.java b/core/src/main/java/tc/oc/pgm/util/xml/XMLFluentParser.java index cc26b6e439..acd207a777 100644 --- a/core/src/main/java/tc/oc/pgm/util/xml/XMLFluentParser.java +++ b/core/src/main/java/tc/oc/pgm/util/xml/XMLFluentParser.java @@ -1,6 +1,7 @@ package tc.oc.pgm.util.xml; import java.time.Duration; +import org.bukkit.Material; import org.bukkit.util.BlockVector; import org.bukkit.util.Vector; import org.jdom2.Element; @@ -93,6 +94,15 @@ public NumberBuilder number(Class cls, Element el, Stri return new NumberBuilder<>(cls, el, prop); } + public Builder.Generic material(Element el, String... prop) { + return new Builder.Generic<>(el, prop) { + @Override + protected Material parse(Node node) throws InvalidXMLException { + return XMLUtils.parseMaterial(node); + } + }; + } + public Builder.Generic vector(Element el, String... prop) { return new Builder.Generic<>(el, prop) { @Override diff --git a/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/ModernInventoryUtil.java b/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/ModernInventoryUtil.java index 4c2f4bbea4..a8cc1da4b8 100644 --- a/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/ModernInventoryUtil.java +++ b/platform/platform-modern/src/main/java/tc/oc/pgm/platform/modern/ModernInventoryUtil.java @@ -70,6 +70,29 @@ public void applyAttributeModifiers( } } + @Override + public boolean attributesEqual(ItemMeta meta1, ItemMeta meta2) { + var attributes1 = meta1.getAttributeModifiers(); + var attributes2 = meta2.getAttributeModifiers(); + if (attributes1 == null || attributes2 == null) return false; + + if (!attributes1.keySet().equals(attributes2.keySet())) return false; + + for (Attribute attr : attributes1.keySet()) { + if (modifiersDiffer(attributes1.get(attr), attributes2.get(attr))) return false; + } + return true; + } + + @Override + public void stripAttributes(ItemMeta meta) { + var attributes = meta.getAttributeModifiers(); + + if (attributes != null && !attributes.isEmpty()) { + attributes.keySet().forEach(meta::removeAttributeModifier); + } + } + @Override public EquipmentSlot getUsedHand(PlayerEvent event) { return switch (event) { diff --git a/platform/platform-sportpaper/src/main/java/tc/oc/pgm/platform/sportpaper/SpInventoryUtil.java b/platform/platform-sportpaper/src/main/java/tc/oc/pgm/platform/sportpaper/SpInventoryUtil.java index 2e52cc6e03..ce9c4ba5ba 100644 --- a/platform/platform-sportpaper/src/main/java/tc/oc/pgm/platform/sportpaper/SpInventoryUtil.java +++ b/platform/platform-sportpaper/src/main/java/tc/oc/pgm/platform/sportpaper/SpInventoryUtil.java @@ -67,6 +67,25 @@ public void applyAttributeModifiers( } } + @Override + public boolean attributesEqual(ItemMeta meta1, ItemMeta meta2) { + var attributes = meta1.getModifiedAttributes(); + if (!attributes.equals(meta2.getModifiedAttributes())) return false; + + for (String attr : attributes) { + if (modifiersDiffer(meta1.getAttributeModifiers(attr), meta2.getAttributeModifiers(attr))) + return false; + } + return true; + } + + @Override + public void stripAttributes(ItemMeta meta) { + for (String attr : meta.getModifiedAttributes()) { + meta.getAttributeModifiers(attr).clear(); + } + } + @Override public EquipmentSlot getUsedHand(PlayerEvent event) { return EquipmentSlot.HAND; diff --git a/util/src/main/java/tc/oc/pgm/util/inventory/InventoryUtils.java b/util/src/main/java/tc/oc/pgm/util/inventory/InventoryUtils.java index 34e24cc76c..5e16b2cae9 100644 --- a/util/src/main/java/tc/oc/pgm/util/inventory/InventoryUtils.java +++ b/util/src/main/java/tc/oc/pgm/util/inventory/InventoryUtils.java @@ -25,6 +25,7 @@ import org.bukkit.inventory.meta.PotionMeta; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import tc.oc.pgm.util.bukkit.BukkitUtils; import tc.oc.pgm.util.platform.Platform; @@ -188,6 +189,40 @@ default void setUnbreakable(ItemStack item, boolean unbreakable) { void applyAttributeModifiers( SetMultimap modifiers, ItemMeta meta); + boolean attributesEqual(ItemMeta meta1, ItemMeta meta2); + + default boolean modifiersDiffer( + Collection a, Collection b) { + if (a.size() != b.size()) return true; + if (a.isEmpty()) return false; + // Fast case for single modifier + if (a.size() == 1) { + var modA = a.iterator().next(); + var modB = b.iterator().next(); + return modA.getOperation() != modB.getOperation() || modA.getAmount() != modB.getAmount(); + } + + record SimpleModifier(double amount, AttributeModifier.Operation operation) + implements Comparable { + public SimpleModifier(AttributeModifier modifier) { + this(modifier.getAmount(), modifier.getOperation()); + } + + @Override + public int compareTo(@NotNull SimpleModifier o) { + int res = operation.ordinal() - o.operation.ordinal(); + if (res != 0) return res; + return Double.compare(amount, o.amount); + } + } + + var listA = a.stream().map(SimpleModifier::new).sorted().toList(); + var listB = b.stream().map(SimpleModifier::new).sorted().toList(); + return !listA.equals(listB); + } + + void stripAttributes(ItemMeta meta); + EquipmentSlot getUsedHand(PlayerEvent event); void setCanDestroy(ItemMeta itemMeta, Set materials); diff --git a/util/src/main/java/tc/oc/pgm/util/material/Materials.java b/util/src/main/java/tc/oc/pgm/util/material/Materials.java index 652e43f12f..2bc2bd3783 100644 --- a/util/src/main/java/tc/oc/pgm/util/material/Materials.java +++ b/util/src/main/java/tc/oc/pgm/util/material/Materials.java @@ -1,6 +1,7 @@ package tc.oc.pgm.util.material; import static org.bukkit.Material.*; +import static tc.oc.pgm.util.inventory.InventoryUtils.INVENTORY_UTILS; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -108,8 +109,21 @@ static boolean itemsSimilar(ItemStack first, ItemStack second, boolean skipDur) final boolean hasMeta2 = second.hasItemMeta(); if (!hasMeta1 && !hasMeta2) return true; - final ItemMeta meta1 = hasMeta1 ? first.getItemMeta() : null; - final ItemMeta meta2 = hasMeta2 ? second.getItemMeta() : null; + ItemMeta meta1 = hasMeta1 ? first.getItemMeta() : null; + ItemMeta meta2 = hasMeta2 ? second.getItemMeta() : null; + + if (hasMeta1 && hasMeta2 && meta1.hasAttributeModifiers() && meta2.hasAttributeModifiers()) { + if (INVENTORY_UTILS.attributesEqual(meta1, meta2)) { + // If attributes match, strip them not to affect the comparison. + // This is done because attributes have a random UUID in t hem that make them never equal + meta1 = meta1.clone(); + meta2 = meta2.clone(); + INVENTORY_UTILS.stripAttributes(meta1); + INVENTORY_UTILS.stripAttributes(meta2); + } else { + return false; + } + } return Bukkit.getItemFactory().equals(meta1, meta2); }