[SPIGOT-6716] Comparisons of enchanted book items sometimes fail due to the non-deterministic reordering of their stored enchantments Created: 19/Aug/21  Updated: 20/Aug/21  Resolved: 20/Aug/21

Status: Resolved
Project: Spigot
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Bug Priority: Minor
Reporter: blablubbabc Assignee: Unassigned
Resolution: Fixed Votes: 0
Labels: comparison, enchantments, itemstack

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

 Description   

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.


Generated at Tue Apr 22 03:00:30 UTC 2025 using Jira 10.3.5#10030005-sha1:190c783f2bd6c69cd5accdb70f97e48812a78d14.