Uploaded image for project: 'Spigot'
  1. Spigot
  2. SPIGOT-6716

Comparisons of enchanted book items sometimes fail due to the non-deterministic reordering of their stored enchantments

XMLWordPrintable

    • This server is running CraftBukkit version dev-Spigot-b166a49-18027d0 (MC: 1.17.1) (Implementing API version 1.17.1-R0.1-SNAPSHOT)
    • Yes

      There are cases in which CraftMetaEnchantedBook uses a non-ordered HashMap to store the enchantments of an enchanted book. Any NMS enchanted book ItemStack that has its stored enchantments in an order that does not match this non-deterministic order of the HashMap is prone to cause item comparison issues (CraftItemStack#equals, CraftItemStack#isSimilar) once the item stack is converted to a Bukkit ItemStack and then back to a NMS / Craft ItemStack.

      Example reproduction plugin:

      package de.blablubbabc.test2;
      
      import org.bukkit.Bukkit;
      import org.bukkit.Material;
      import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftItemStack;
      import org.bukkit.entity.Player;
      import org.bukkit.event.EventHandler;
      import org.bukkit.event.Listener;
      import org.bukkit.event.block.Action;
      import org.bukkit.event.player.PlayerInteractEvent;
      import org.bukkit.inventory.EquipmentSlot;
      import org.bukkit.inventory.ItemStack;
      import org.bukkit.inventory.PlayerInventory;
      import org.bukkit.plugin.Plugin;import net.minecraft.nbt.NBTTagCompound;
      
      public class TestItemStackSerialization2 implements Listener {
      
          private Plugin plugin;
      
          public TestItemStackSerialization2(Plugin plugin) {
              this.plugin = plugin;
              Bukkit.getPluginManager().registerEvents(this, plugin);
          }
      
          @EventHandler
          public void onInteract(PlayerInteractEvent event) {
              if (event.getAction() != Action.RIGHT_CLICK_AIR) return;
              if (event.getHand() != EquipmentSlot.HAND) return;
      
              ItemStack item = event.getItem();
              if (item == null || item.getType() == Material.AIR) return;
              if (item.getType() != Material.ENCHANTED_BOOK) return;
      
              Player player = event.getPlayer();
              PlayerInventory inventory = player.getInventory();
              plugin.getLogger().info("Item in hand: " + getItemSNBT(item));
              ItemStack bukkitCopy = new ItemStack(item.getType(), item.getAmount());
              bukkitCopy.setItemMeta(item.getItemMeta());
              plugin.getLogger().info("Bukkit copy: " + getItemSNBT(bukkitCopy));
              inventory.setItemInOffHand(bukkitCopy);
              ItemStack offhand = inventory.getItemInOffHand();
              plugin.getLogger().info("Bukkit copy in off hand: " + getItemSNBT(offhand));
              plugin.getLogger().info("Similar to offhand item? " + item.isSimilar(offhand));
          }
      
          private static String getItemSNBT(ItemStack itemStack) {
              if (itemStack == null) return null;
              net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.asNMSCopy(itemStack);
              NBTTagCompound itemNBT = nmsItem.save(new NBTTagCompound());
              return itemNBT.toString();
          }
      }

      Create an enchanted book item in-game with a particular enchantment order:

      give @s minecraft:enchanted_book{StoredEnchantments:[{lvl:4s,id:"minecraft:bane_of_arthropods"},{lvl:3s,id:"minecraft:piercing"},{lvl:3s,id:"minecraft:power"}]} 1

      Hold the item in your hand and right-click.
      Output:

      [18:12:56] [Server thread/INFO]: [@: Gave 1 [Enchanted Book] to blablubbabc]
      [18:12:58] [Server thread/INFO]: [TestPlugin] Item in hand: {Count:1b,id:"minecraft:enchanted_book",tag:{StoredEnchantments:[{id:"minecraft:bane_of_arthropods",lvl:4s},{id:"minecraft:piercing",lvl:3s},{id:"minecraft:power",lvl:3s}]}}
      [18:12:58] [Server thread/INFO]: [TestPlugin] Bukkit copy: {Count:1b,id:"minecraft:enchanted_book",tag:{StoredEnchantments:[{id:"minecraft:bane_of_arthropods",lvl:4s},{id:"minecraft:power",lvl:3s},{id:"minecraft:piercing",lvl:3s}]}}
      [18:12:58] [Server thread/INFO]: [TestPlugin] Bukkit copy in off hand: {Count:1b,id:"minecraft:enchanted_book",tag:{StoredEnchantments:[{id:"minecraft:bane_of_arthropods",lvl:4s},{id:"minecraft:power",lvl:3s},{id:"minecraft:piercing",lvl:3s}]}}
      [18:12:58] [Server thread/INFO]: [TestPlugin] Similar to offhand item? false

      This conversion between Craft and Bukkit ItemStack can also occur in other situations, implicitly. Once converted to a Bukkit ItemStack, the enchantments are reordered. When converted back to a Minecraft ItemStack, this new enchantment order is preserved. Once two CraftItemStacks with differently ordered stored enchantments are compared, they are considered non-equal.

            Unassigned Unassigned
            blablubbabc blablubbabc
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

              Created:
              Updated:
              Resolved: