--- a/net/minecraft/world/item/ItemStack.java
+++ b/net/minecraft/world/item/ItemStack.java
@@ -10,9 +10,10 @@
 import java.text.DecimalFormat;
 import java.text.DecimalFormatSymbols;
 import java.util.Collection;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
-import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Optional;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
@@ -24,12 +25,14 @@
 import net.minecraft.advancements.CriteriaTriggers;
 import net.minecraft.commands.arguments.blocks.BlockStateParser;
 import net.minecraft.core.BlockPos;
+import net.minecraft.core.Direction;
 import net.minecraft.core.Holder;
 import net.minecraft.core.Registry;
 import net.minecraft.core.registries.BuiltInRegistries;
 import net.minecraft.core.registries.Registries;
 import net.minecraft.nbt.CompoundTag;
 import net.minecraft.nbt.ListTag;
+import net.minecraft.nbt.NbtOps;
 import net.minecraft.nbt.Tag;
 import net.minecraft.network.chat.CommonComponents;
 import net.minecraft.network.chat.Component;
@@ -37,12 +40,11 @@
 import net.minecraft.network.chat.HoverEvent;
 import net.minecraft.network.chat.MutableComponent;
 import net.minecraft.network.chat.Style;
+import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket;
 import net.minecraft.resources.ResourceLocation;
-import net.minecraft.server.level.ServerPlayer;
-import net.minecraft.sounds.SoundEvent;
-import net.minecraft.stats.Stats;
 import net.minecraft.tags.TagKey;
 import net.minecraft.util.RandomSource;
+import net.minecraft.util.datafix.fixes.References;
 import net.minecraft.world.InteractionHand;
 import net.minecraft.world.InteractionResult;
 import net.minecraft.world.InteractionResultHolder;
@@ -56,7 +58,6 @@
 import net.minecraft.world.entity.ai.attributes.Attributes;
 import net.minecraft.world.entity.decoration.ItemFrame;
 import net.minecraft.world.entity.item.ItemEntity;
-import net.minecraft.world.entity.player.Player;
 import net.minecraft.world.flag.FeatureFlagSet;
 import net.minecraft.world.inventory.ClickAction;
 import net.minecraft.world.inventory.Slot;
@@ -68,25 +69,58 @@
 import net.minecraft.world.item.enchantment.Enchantments;
 import net.minecraft.world.level.ItemLike;
 import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.BaseEntityBlock;
 import net.minecraft.world.level.block.Block;
-import net.minecraft.world.level.block.state.BlockState;
-import net.minecraft.world.level.block.state.pattern.BlockInWorld;
 import org.slf4j.Logger;
 
+// CraftBukkit start
+import com.mojang.serialization.Dynamic;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.sounds.SoundSource;
+import net.minecraft.stats.Stats;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.level.block.JukeboxBlock;
+import net.minecraft.world.level.block.SaplingBlock;
+import net.minecraft.world.level.block.SoundType;
+import net.minecraft.world.level.block.WitherSkullBlock;
+import net.minecraft.world.level.block.entity.BlockEntity;
+import net.minecraft.world.level.block.entity.SignBlockEntity;
+import net.minecraft.world.level.block.entity.SkullBlockEntity;
+import net.minecraft.world.level.block.state.pattern.BlockInWorld;
+import org.bukkit.Location;
+import org.bukkit.TreeType;
+import org.bukkit.block.BlockState;
+import org.bukkit.craftbukkit.v1_19_R2.block.CraftBlock;
+import org.bukkit.craftbukkit.v1_19_R2.block.CraftBlockState;
+import org.bukkit.craftbukkit.v1_19_R2.inventory.CraftItemStack;
+import org.bukkit.craftbukkit.v1_19_R2.util.CraftMagicNumbers;
+import org.bukkit.entity.Player;
+import org.bukkit.event.block.BlockFertilizeEvent;
+import org.bukkit.event.player.PlayerItemDamageEvent;
+import org.bukkit.event.world.StructureGrowEvent;
+// CraftBukkit end
+
 public final class ItemStack {
+
     public static final Codec<ItemStack> CODEC = RecordCodecBuilder.create((instance) -> {
-        return instance.group(BuiltInRegistries.ITEM.byNameCodec().fieldOf("id").forGetter((stack) -> {
-            return stack.item;
-        }), Codec.INT.fieldOf("Count").forGetter((stack) -> {
-            return stack.count;
-        }), CompoundTag.CODEC.optionalFieldOf("tag").forGetter((stack) -> {
-            return Optional.ofNullable(stack.tag);
+        return instance.group(BuiltInRegistries.ITEM.byNameCodec().fieldOf("id").forGetter((itemstack) -> {
+            return itemstack.item;
+        }), Codec.INT.fieldOf("Count").forGetter((itemstack) -> {
+            return itemstack.count;
+        }), CompoundTag.CODEC.optionalFieldOf("tag").forGetter((itemstack) -> {
+            return Optional.ofNullable(itemstack.tag);
         })).apply(instance, ItemStack::new);
     });
     private static final Logger LOGGER = LogUtils.getLogger();
-    public static final ItemStack EMPTY = new ItemStack((Item)null);
-    public static final DecimalFormat ATTRIBUTE_MODIFIER_FORMAT = Util.make(new DecimalFormat("#.##"), (decimalFormat) -> {
-        decimalFormat.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.ROOT));
+    public static final ItemStack EMPTY = new ItemStack((Item) null);
+    public static final DecimalFormat ATTRIBUTE_MODIFIER_FORMAT = (DecimalFormat) Util.make(new DecimalFormat("#.##"), (decimalformat) -> {
+        decimalformat.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.ROOT));
     });
     public static final String TAG_ENCH = "Enchantments";
     public static final String TAG_DISPLAY = "display";
@@ -121,12 +155,67 @@
         return this.getItem().getTooltipImage(this);
     }
 
+    // Paper start
+    private static final java.util.Comparator<? super CompoundTag> enchantSorter = java.util.Comparator.comparing(o -> o.getString("id"));
+    private void processEnchantOrder(@Nullable CompoundTag tag) {
+        if (tag == null || !tag.contains("Enchantments", 9)) {
+            return;
+        }
+        ListTag list = tag.getList("Enchantments", 10);
+        if (list.size() < 2) {
+            return;
+        }
+        try {
+            //noinspection unchecked
+            list.sort((java.util.Comparator<? super net.minecraft.nbt.Tag>) enchantSorter); // Paper
+        } catch (Exception ignored) {}
+    }
+
+    private void processText() {
+        CompoundTag display = getTagElement("display");
+        if (display != null) {
+            if (display.contains("Name", 8)) {
+                String json = display.getString("Name");
+                if (json != null && json.contains("\u00A7")) {
+                    try {
+                        display.put("Name", convert(json));
+                    } catch (com.google.gson.JsonParseException jsonparseexception) {
+                        display.remove("Name");
+                    }
+                }
+            }
+            if (display.contains("Lore", 9)) {
+                ListTag list = display.getList("Lore", 8);
+                for (int index = 0; index < list.size(); index++) {
+                    String json = list.getString(index);
+                    if (json != null && json.contains("\u00A7")) { // Only try if it has legacy in the unparsed json
+                        try {
+                            list.set(index, convert(json));
+                        } catch (com.google.gson.JsonParseException e) {
+                            list.set(index, net.minecraft.nbt.StringTag.valueOf(org.bukkit.craftbukkit.v1_19_R2.util.CraftChatMessage.toJSON(net.minecraft.network.chat.Component.literal(""))));
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private net.minecraft.nbt.StringTag convert(String json) {
+        Component component = Component.Serializer.fromJson(json);
+        if (component.getContents() instanceof net.minecraft.network.chat.contents.LiteralContents literalContents && literalContents.text().contains("\u00A7") && component.getSiblings().isEmpty()) {
+            // Only convert if the root component is a single comp with legacy in it, don't convert already normal components
+            component = org.bukkit.craftbukkit.v1_19_R2.util.CraftChatMessage.fromString(literalContents.text())[0];
+        }
+        return net.minecraft.nbt.StringTag.valueOf(org.bukkit.craftbukkit.v1_19_R2.util.CraftChatMessage.toJSON(component));
+    }
+    // Paper end
+
     public ItemStack(ItemLike item) {
         this(item, 1);
     }
 
     public ItemStack(Holder<Item> entry) {
-        this(entry.value(), 1);
+        this((ItemLike) entry.value(), 1);
     }
 
     private ItemStack(ItemLike item, int count, Optional<CompoundTag> nbt) {
@@ -135,7 +224,7 @@
     }
 
     public ItemStack(Holder<Item> itemEntry, int count) {
-        this(itemEntry.value(), count);
+        this((ItemLike) itemEntry.value(), count);
     }
 
     public ItemStack(ItemLike item, int count) {
@@ -148,16 +237,32 @@
         this.updateEmptyCacheFlag();
     }
 
+    // Called to run this stack through the data converter to handle older storage methods and serialized items
+    public void convertStack(int version) {
+        if (0 < version && version < CraftMagicNumbers.INSTANCE.getDataVersion()) {
+            CompoundTag savedStack = new CompoundTag();
+            this.save(savedStack);
+            savedStack = (CompoundTag) MinecraftServer.getServer().fixerUpper.update(References.ITEM_STACK, new Dynamic(NbtOps.INSTANCE, savedStack), version, CraftMagicNumbers.INSTANCE.getDataVersion()).getValue();
+            this.load(savedStack);
+        }
+    }
+
     private void updateEmptyCacheFlag() {
+        if (this.emptyCacheFlag && this == ItemStack.EMPTY) throw new AssertionError("TRAP"); // CraftBukkit
         this.emptyCacheFlag = false;
         this.emptyCacheFlag = this.isEmpty();
     }
 
-    private ItemStack(CompoundTag nbt) {
-        this.item = BuiltInRegistries.ITEM.get(new ResourceLocation(nbt.getString("id")));
-        this.count = nbt.getByte("Count");
-        if (nbt.contains("tag", 10)) {
-            this.tag = nbt.getCompound("tag");
+    // CraftBukkit - break into own method
+    private void load(CompoundTag nbttagcompound) {
+        this.item = (Item) BuiltInRegistries.ITEM.get(new ResourceLocation(nbttagcompound.getString("id")));
+        this.count = nbttagcompound.getByte("Count");
+        if (nbttagcompound.contains("tag", 10)) {
+            // CraftBukkit start - make defensive copy as this data may be coming from the save thread
+            this.tag = nbttagcompound.getCompound("tag").copy();
+            // CraftBukkit end
+            this.processEnchantOrder(this.tag); // Paper
+            this.processText(); // Paper
             this.getItem().verifyTagAfterLoad(this.tag);
         }
 
@@ -165,26 +270,25 @@
             this.setDamageValue(this.getDamageValue());
         }
 
+    }
+
+    private ItemStack(CompoundTag nbt) {
+        this.load(nbt);
+        // CraftBukkit end
         this.updateEmptyCacheFlag();
     }
 
     public static ItemStack of(CompoundTag nbt) {
         try {
             return new ItemStack(nbt);
-        } catch (RuntimeException var2) {
-            LOGGER.debug("Tried to load invalid item: {}", nbt, var2);
-            return EMPTY;
+        } catch (RuntimeException runtimeexception) {
+            ItemStack.LOGGER.debug("Tried to load invalid item: {}", nbt, runtimeexception);
+            return ItemStack.EMPTY;
         }
     }
 
     public boolean isEmpty() {
-        if (this == EMPTY) {
-            return true;
-        } else if (this.getItem() != null && !this.is(Items.AIR)) {
-            return this.count <= 0;
-        } else {
-            return true;
-        }
+        return this == ItemStack.EMPTY || this.item == null || this.item == Items.AIR || this.count <= 0; // Paper
     }
 
     public boolean isItemEnabled(FeatureFlagSet enabledFeatures) {
@@ -192,11 +296,12 @@
     }
 
     public ItemStack split(int amount) {
-        int i = Math.min(amount, this.count);
-        ItemStack itemStack = this.copy();
-        itemStack.setCount(i);
-        this.shrink(i);
-        return itemStack;
+        int j = Math.min(amount, this.count);
+        ItemStack itemstack = this.copy();
+
+        itemstack.setCount(j);
+        this.shrink(j);
+        return itemstack;
     }
 
     public Item getItem() {
@@ -227,28 +332,176 @@
         return this.getItem().builtInRegistryHolder().tags();
     }
 
-    public InteractionResult useOn(UseOnContext context) {
-        Player player = context.getPlayer();
-        BlockPos blockPos = context.getClickedPos();
-        BlockInWorld blockInWorld = new BlockInWorld(context.getLevel(), blockPos, false);
-        if (player != null && !player.getAbilities().mayBuild && !this.hasAdventureModePlaceTagForBlock(context.getLevel().registryAccess().registryOrThrow(Registries.BLOCK), blockInWorld)) {
+    public InteractionResult useOn(UseOnContext itemactioncontext, InteractionHand enumhand) { // CraftBukkit - add hand
+        net.minecraft.world.entity.player.Player entityhuman = itemactioncontext.getPlayer();
+        BlockPos blockposition = itemactioncontext.getClickedPos();
+        BlockInWorld shapedetectorblock = new BlockInWorld(itemactioncontext.getLevel(), blockposition, false);
+
+        if (entityhuman != null && !entityhuman.getAbilities().mayBuild && !this.hasAdventureModePlaceTagForBlock(itemactioncontext.getLevel().registryAccess().registryOrThrow(Registries.BLOCK), shapedetectorblock)) {
             return InteractionResult.PASS;
         } else {
+            // CraftBukkit start - handle all block place event logic here
+            CompoundTag oldData = this.getTagClone();
+            int oldCount = this.getCount();
+            ServerLevel world = (ServerLevel) itemactioncontext.getLevel();
+
+            if (!(this.getItem() instanceof BucketItem/* || this.getItem() instanceof SolidBucketItem*/)) { // if not bucket // Paper - capture block states for snow buckets
+                world.captureBlockStates = true;
+                // special case bonemeal
+                if (this.getItem() == Items.BONE_MEAL) {
+                    world.captureTreeGeneration = true;
+                }
+            }
             Item item = this.getItem();
-            InteractionResult interactionResult = item.useOn(context);
-            if (player != null && interactionResult.shouldAwardStats()) {
-                player.awardStat(Stats.ITEM_USED.get(item));
+            InteractionResult enuminteractionresult = item.useOn(itemactioncontext);
+            CompoundTag newData = this.getTagClone();
+            int newCount = this.getCount();
+            this.setCount(oldCount);
+            this.setTagClone(oldData);
+            world.captureBlockStates = false;
+            if (enuminteractionresult.consumesAction() && world.captureTreeGeneration && world.capturedBlockStates.size() > 0) {
+                world.captureTreeGeneration = false;
+                Location location = new Location(world.getWorld(), blockposition.getX(), blockposition.getY(), blockposition.getZ());
+                TreeType treeType = SaplingBlock.treeType;
+                SaplingBlock.treeType = null;
+                List<BlockState> blocks = new java.util.ArrayList<>(world.capturedBlockStates.values());
+                world.capturedBlockStates.clear();
+                StructureGrowEvent structureEvent = null;
+                if (treeType != null) {
+                    boolean isBonemeal = this.getItem() == Items.BONE_MEAL;
+                    structureEvent = new StructureGrowEvent(location, treeType, isBonemeal, (Player) entityhuman.getBukkitEntity(), blocks);
+                    org.bukkit.Bukkit.getPluginManager().callEvent(structureEvent);
+                }
+
+                BlockFertilizeEvent fertilizeEvent = new BlockFertilizeEvent(CraftBlock.at(world, blockposition), (Player) entityhuman.getBukkitEntity(), blocks);
+                fertilizeEvent.setCancelled(structureEvent != null && structureEvent.isCancelled());
+                org.bukkit.Bukkit.getPluginManager().callEvent(fertilizeEvent);
+
+                if (!fertilizeEvent.isCancelled()) {
+                    // Change the stack to its new contents if it hasn't been tampered with.
+                    if (this.getCount() == oldCount && Objects.equals(this.tag, oldData)) {
+                        this.setTag(newData);
+                        this.setCount(newCount);
+                    }
+                    for (BlockState blockstate : blocks) {
+                        blockstate.update(true);
+                    }
+                }
+
+                SignItem.openSign = null; // SPIGOT-6758 - Reset on early return
+                return enuminteractionresult;
             }
+            world.captureTreeGeneration = false;
 
-            return interactionResult;
+            if (entityhuman != null && enuminteractionresult.shouldAwardStats()) {
+                org.bukkit.event.block.BlockPlaceEvent placeEvent = null;
+                List<BlockState> blocks = new java.util.ArrayList<>(world.capturedBlockStates.values());
+                world.capturedBlockStates.clear();
+                if (blocks.size() > 1) {
+                    placeEvent = org.bukkit.craftbukkit.v1_19_R2.event.CraftEventFactory.callBlockMultiPlaceEvent(world, entityhuman, enumhand, blocks, blockposition.getX(), blockposition.getY(), blockposition.getZ());
+                } else if (blocks.size() == 1 && item != Items.POWDER_SNOW_BUCKET) { // Paper - don't call event twice for snow buckets
+                    placeEvent = org.bukkit.craftbukkit.v1_19_R2.event.CraftEventFactory.callBlockPlaceEvent(world, entityhuman, enumhand, blocks.get(0), blockposition.getX(), blockposition.getY(), blockposition.getZ());
+                }
+
+                if (placeEvent != null && (placeEvent.isCancelled() || !placeEvent.canBuild())) {
+                    enuminteractionresult = InteractionResult.FAIL; // cancel placement
+                    // PAIL: Remove this when MC-99075 fixed
+                    placeEvent.getPlayer().updateInventory();
+                    world.capturedTileEntities.clear(); // Paper - clear out tile entities as chests and such will pop loot
+                    // revert back all captured blocks
+                    world.preventPoiUpdated = true; // CraftBukkit - SPIGOT-5710
+                    for (BlockState blockstate : blocks) {
+                        blockstate.update(true, false);
+                    }
+                    world.preventPoiUpdated = false;
+
+                    // Brute force all possible updates
+                    BlockPos placedPos = ((CraftBlock) placeEvent.getBlock()).getPosition();
+                    for (Direction dir : Direction.values()) {
+                        ((ServerPlayer) entityhuman).connection.send(new ClientboundBlockUpdatePacket(world, placedPos.relative(dir)));
+                    }
+                    SignItem.openSign = null; // SPIGOT-6758 - Reset on early return
+                } else {
+                    // Change the stack to its new contents if it hasn't been tampered with.
+                    if (this.getCount() == oldCount && Objects.equals(this.tag, oldData)) {
+                        this.setTag(newData);
+                        this.setCount(newCount);
+                    }
+
+                    for (Map.Entry<BlockPos, BlockEntity> e : world.capturedTileEntities.entrySet()) {
+                        world.setBlockEntity(e.getValue());
+                    }
+
+                    for (BlockState blockstate : blocks) {
+                        int updateFlag = ((CraftBlockState) blockstate).getFlag();
+                        net.minecraft.world.level.block.state.BlockState oldBlock = ((CraftBlockState) blockstate).getHandle();
+                        BlockPos newblockposition = ((CraftBlockState) blockstate).getPosition();
+                        net.minecraft.world.level.block.state.BlockState block = world.getBlockState(newblockposition);
+
+                        if (!(block.getBlock() instanceof BaseEntityBlock)) { // Containers get placed automatically
+                            block.getBlock().onPlace(block, world, newblockposition, oldBlock, true, itemactioncontext); // Paper - pass itemactioncontext
+                        }
+
+                        world.notifyAndUpdatePhysics(newblockposition, null, oldBlock, block, world.getBlockState(newblockposition), updateFlag, 512); // send null chunk as chunk.k() returns false by this point
+                    }
+
+                    // Special case juke boxes as they update their tile entity. Copied from ItemRecord.
+                    // PAIL: checkme on updates.
+                    if (this.item instanceof RecordItem) {
+                        ((JukeboxBlock) Blocks.JUKEBOX).setRecord(entityhuman, world, blockposition, world.getBlockState(blockposition), this);
+                        world.levelEvent((net.minecraft.world.entity.player.Player) null, 1010, blockposition, Item.getId(this.item));
+                        this.shrink(1);
+                        entityhuman.awardStat(Stats.PLAY_RECORD);
+                    }
+
+                    if (this.item == Items.WITHER_SKELETON_SKULL) { // Special case skulls to allow wither spawns to be cancelled
+                        BlockPos bp = blockposition;
+                        if (!world.getBlockState(blockposition).getMaterial().isReplaceable()) {
+                            if (!world.getBlockState(blockposition).getMaterial().isSolid()) {
+                                bp = null;
+                            } else {
+                                bp = bp.relative(itemactioncontext.getClickedFace());
+                            }
+                        }
+                        if (bp != null) {
+                            BlockEntity te = world.getBlockEntity(bp);
+                            if (te instanceof SkullBlockEntity) {
+                                WitherSkullBlock.checkSpawn(world, bp, (SkullBlockEntity) te);
+                            }
+                        }
+                    }
+
+                    // SPIGOT-4678
+                    if (this.item instanceof SignItem && SignItem.openSign != null) {
+                        try {
+                            entityhuman.openTextEdit((SignBlockEntity) world.getBlockEntity(SignItem.openSign));
+                        } finally {
+                            SignItem.openSign = null;
+                        }
+                    }
+
+                    // SPIGOT-1288 - play sound stripped from ItemBlock
+                    if (this.item instanceof BlockItem) {
+                        SoundType soundeffecttype = ((BlockItem) this.item).getBlock().getSoundType(null);
+                        world.playSound(entityhuman, blockposition, soundeffecttype.getPlaceSound(), SoundSource.BLOCKS, (soundeffecttype.getVolume() + 1.0F) / 2.0F, soundeffecttype.getPitch() * 0.8F);
+                    }
+
+                    entityhuman.awardStat(Stats.ITEM_USED.get(item));
+                }
+            }
+            world.capturedTileEntities.clear();
+            world.capturedBlockStates.clear();
+            // CraftBukkit end
+
+            return enuminteractionresult;
         }
     }
 
-    public float getDestroySpeed(BlockState state) {
+    public float getDestroySpeed(net.minecraft.world.level.block.state.BlockState state) {
         return this.getItem().getDestroySpeed(this, state);
     }
 
-    public InteractionResultHolder<ItemStack> use(Level world, Player user, InteractionHand hand) {
+    public InteractionResultHolder<ItemStack> use(Level world, net.minecraft.world.entity.player.Player user, InteractionHand hand) {
         return this.getItem().use(world, user, hand);
     }
 
@@ -257,9 +510,10 @@
     }
 
     public CompoundTag save(CompoundTag nbt) {
-        ResourceLocation resourceLocation = BuiltInRegistries.ITEM.getKey(this.getItem());
-        nbt.putString("id", resourceLocation == null ? "minecraft:air" : resourceLocation.toString());
-        nbt.putByte("Count", (byte)this.count);
+        ResourceLocation minecraftkey = BuiltInRegistries.ITEM.getKey(this.getItem());
+
+        nbt.putString("id", minecraftkey == null ? "minecraft:air" : minecraftkey.toString());
+        nbt.putByte("Count", (byte) this.count);
         if (this.tag != null) {
             nbt.put("tag", this.tag.copy());
         }
@@ -277,8 +531,9 @@
 
     public boolean isDamageableItem() {
         if (!this.emptyCacheFlag && this.getItem().getMaxDamage() > 0) {
-            CompoundTag compoundTag = this.getTag();
-            return compoundTag == null || !compoundTag.getBoolean("Unbreakable");
+            CompoundTag nbttagcompound = this.getTag();
+
+            return nbttagcompound == null || !nbttagcompound.getBoolean("Unbreakable");
         } else {
             return false;
         }
@@ -300,45 +555,77 @@
         return this.getItem().getMaxDamage();
     }
 
-    public boolean hurt(int amount, RandomSource random, @Nullable ServerPlayer player) {
+    public boolean hurt(int amount, RandomSource random, @Nullable LivingEntity player) { // Paper - allow any living entity instead of only ServerPlayers
         if (!this.isDamageableItem()) {
             return false;
         } else {
+            int j;
+
             if (amount > 0) {
-                int i = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.UNBREAKING, this);
-                int j = 0;
+                j = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.UNBREAKING, this);
+                int k = 0;
 
-                for(int k = 0; i > 0 && k < amount; ++k) {
-                    if (DigDurabilityEnchantment.shouldIgnoreDurabilityDrop(this, i, random)) {
-                        ++j;
+                for (int l = 0; j > 0 && l < amount; ++l) {
+                    if (DigDurabilityEnchantment.shouldIgnoreDurabilityDrop(this, j, random)) {
+                        ++k;
                     }
                 }
 
-                amount -= j;
+                int originalDamage = amount; // Paper
+                amount -= k;
+                // CraftBukkit start
+                if (player instanceof ServerPlayer serverPlayer) { // Paper
+                    PlayerItemDamageEvent event = new PlayerItemDamageEvent(serverPlayer.getBukkitEntity(), CraftItemStack.asCraftMirror(this), amount, originalDamage); // Paper
+                    event.getPlayer().getServer().getPluginManager().callEvent(event);
+
+                    if (amount != event.getDamage() || event.isCancelled()) {
+                        event.getPlayer().updateInventory();
+                    }
+                    if (event.isCancelled()) {
+                        return false;
+                    }
+
+                    amount = event.getDamage();
+                    // Paper start - EntityDamageItemEvent
+                } else if (player != null) {
+                    io.papermc.paper.event.entity.EntityDamageItemEvent event = new io.papermc.paper.event.entity.EntityDamageItemEvent(player.getBukkitLivingEntity(), CraftItemStack.asCraftMirror(this), amount);
+                    if (!event.callEvent()) {
+                        return false;
+                    }
+                    amount = event.getDamage();
+                    // Paper end
+                }
+                // CraftBukkit end
                 if (amount <= 0) {
                     return false;
                 }
             }
 
-            if (player != null && amount != 0) {
-                CriteriaTriggers.ITEM_DURABILITY_CHANGED.trigger(player, this, this.getDamageValue() + amount);
+            if (player instanceof ServerPlayer serverPlayer && amount != 0) { // Paper
+                CriteriaTriggers.ITEM_DURABILITY_CHANGED.trigger(serverPlayer, this, this.getDamageValue() + amount); // Paper
             }
 
-            int l = this.getDamageValue() + amount;
-            this.setDamageValue(l);
-            return l >= this.getMaxDamage();
+            j = this.getDamageValue() + amount;
+            this.setDamageValue(j);
+            return j >= this.getMaxDamage();
         }
     }
 
     public <T extends LivingEntity> void hurtAndBreak(int amount, T entity, Consumer<T> breakCallback) {
-        if (!entity.level.isClientSide && (!(entity instanceof Player) || !((Player)entity).getAbilities().instabuild)) {
+        if (!entity.level.isClientSide && (!(entity instanceof net.minecraft.world.entity.player.Player) || !((net.minecraft.world.entity.player.Player) entity).getAbilities().instabuild)) {
             if (this.isDamageableItem()) {
-                if (this.hurt(amount, entity.getRandom(), entity instanceof ServerPlayer ? (ServerPlayer)entity : null)) {
+                if (this.hurt(amount, entity.getRandom(), entity /*instanceof ServerPlayer ? (ServerPlayer) entity : null*/)) { // Paper - pass LivingEntity for EntityItemDamageEvent
                     breakCallback.accept(entity);
                     Item item = this.getItem();
+                    // CraftBukkit start - Check for item breaking
+                    if (this.count == 1 && entity instanceof net.minecraft.world.entity.player.Player) {
+                        org.bukkit.craftbukkit.v1_19_R2.event.CraftEventFactory.callPlayerItemBreakEvent((net.minecraft.world.entity.player.Player) entity, this);
+                    }
+                    // CraftBukkit end
+
                     this.shrink(1);
-                    if (entity instanceof Player) {
-                        ((Player)entity).awardStat(Stats.ITEM_BROKEN.get(item));
+                    if (entity instanceof net.minecraft.world.entity.player.Player) {
+                        ((net.minecraft.world.entity.player.Player) entity).awardStat(Stats.ITEM_BROKEN.get(item));
                     }
 
                     this.setDamageValue(0);
@@ -360,98 +647,77 @@
         return this.item.getBarColor(this);
     }
 
-    public boolean overrideStackedOnOther(Slot slot, ClickAction clickType, Player player) {
+    public boolean overrideStackedOnOther(Slot slot, ClickAction clickType, net.minecraft.world.entity.player.Player player) {
         return this.getItem().overrideStackedOnOther(this, slot, clickType, player);
     }
 
-    public boolean overrideOtherStackedOnMe(ItemStack stack, Slot slot, ClickAction clickType, Player player, SlotAccess cursorStackReference) {
+    public boolean overrideOtherStackedOnMe(ItemStack stack, Slot slot, ClickAction clickType, net.minecraft.world.entity.player.Player player, SlotAccess cursorStackReference) {
         return this.getItem().overrideOtherStackedOnMe(this, stack, slot, clickType, player, cursorStackReference);
     }
 
-    public void hurtEnemy(LivingEntity target, Player attacker) {
+    public void hurtEnemy(LivingEntity target, net.minecraft.world.entity.player.Player attacker) {
         Item item = this.getItem();
+
         if (item.hurtEnemy(this, target, attacker)) {
             attacker.awardStat(Stats.ITEM_USED.get(item));
         }
 
     }
 
-    public void mineBlock(Level world, BlockState state, BlockPos pos, Player miner) {
+    public void mineBlock(Level world, net.minecraft.world.level.block.state.BlockState state, BlockPos pos, net.minecraft.world.entity.player.Player miner) {
         Item item = this.getItem();
+
         if (item.mineBlock(this, world, state, pos, miner)) {
             miner.awardStat(Stats.ITEM_USED.get(item));
         }
 
     }
 
-    public boolean isCorrectToolForDrops(BlockState state) {
+    public boolean isCorrectToolForDrops(net.minecraft.world.level.block.state.BlockState state) {
         return this.getItem().isCorrectToolForDrops(state);
     }
 
-    public InteractionResult interactLivingEntity(Player user, LivingEntity entity, InteractionHand hand) {
+    public InteractionResult interactLivingEntity(net.minecraft.world.entity.player.Player user, LivingEntity entity, InteractionHand hand) {
         return this.getItem().interactLivingEntity(this, user, entity, hand);
     }
 
-    public ItemStack copy() {
-        if (this.isEmpty()) {
-            return EMPTY;
+    public ItemStack copy() { return cloneItemStack(false); } // Paper
+    public ItemStack cloneItemStack(boolean origItem) { // Paper
+        if (!origItem && this.isEmpty()) { // Paper
+            return ItemStack.EMPTY;
         } else {
-            ItemStack itemStack = new ItemStack(this.getItem(), this.count);
-            itemStack.setPopTime(this.getPopTime());
+            ItemStack itemstack = new ItemStack(origItem ? this.item : this.getItem(), this.count); // Paper
+
+            itemstack.setPopTime(this.getPopTime());
             if (this.tag != null) {
-                itemStack.tag = this.tag.copy();
+                itemstack.tag = this.tag.copy();
             }
 
-            return itemStack;
+            return itemstack;
         }
     }
 
     public ItemStack copyWithCount(int count) {
-        ItemStack itemStack = this.copy();
-        itemStack.setCount(count);
-        return itemStack;
+        ItemStack itemstack = this.copy();
+
+        itemstack.setCount(count);
+        return itemstack;
     }
 
     public static boolean tagMatches(ItemStack left, ItemStack right) {
-        if (left.isEmpty() && right.isEmpty()) {
-            return true;
-        } else if (!left.isEmpty() && !right.isEmpty()) {
-            if (left.tag == null && right.tag != null) {
-                return false;
-            } else {
-                return left.tag == null || left.tag.equals(right.tag);
-            }
-        } else {
-            return false;
-        }
+        return left.isEmpty() && right.isEmpty() ? true : (!left.isEmpty() && !right.isEmpty() ? (left.tag == null && right.tag != null ? false : left.tag == null || left.tag.equals(right.tag)) : false);
     }
 
     public static boolean matches(ItemStack left, ItemStack right) {
-        if (left.isEmpty() && right.isEmpty()) {
-            return true;
-        } else {
-            return !left.isEmpty() && !right.isEmpty() ? left.matches(right) : false;
-        }
+        return left.isEmpty() && right.isEmpty() ? true : (!left.isEmpty() && !right.isEmpty() ? left.matches(right) : false);
     }
 
     private boolean matches(ItemStack stack) {
-        if (this.count != stack.count) {
-            return false;
-        } else if (!this.is(stack.getItem())) {
-            return false;
-        } else if (this.tag == null && stack.tag != null) {
-            return false;
-        } else {
-            return this.tag == null || this.tag.equals(stack.tag);
-        }
+        return this.count != stack.count ? false : (!this.is(stack.getItem()) ? false : (this.tag == null && stack.tag != null ? false : this.tag == null || this.tag.equals(stack.tag)));
     }
 
     public static boolean isSame(ItemStack left, ItemStack right) {
-        if (left == right) {
-            return true;
-        } else {
-            return !left.isEmpty() && !right.isEmpty() ? left.sameItem(right) : false;
-        }
+        return left == right ? true : (!left.isEmpty() && !right.isEmpty() ? left.sameItem(right) : false);
     }
 
     public boolean sameItem(ItemStack stack) {
@@ -459,14 +725,13 @@
     }
 
     public static boolean isSameItemSameTags(ItemStack stack, ItemStack otherStack) {
-        return stack.is(otherStack.getItem()) && tagMatches(stack, otherStack);
+        return stack.is(otherStack.getItem()) && ItemStack.tagMatches(stack, otherStack);
     }
 
     public String getDescriptionId() {
         return this.getItem().getDescriptionId(this);
     }
 
-    @Override
     public String toString() {
         return this.count + " " + this.getItem();
     }
@@ -482,7 +747,7 @@
 
     }
 
-    public void onCraftedBy(Level world, Player player, int amount) {
+    public void onCraftedBy(Level world, net.minecraft.world.entity.player.Player player, int amount) {
         player.awardStat(Stats.ITEM_CRAFTED.get(this.getItem()), amount);
         this.getItem().onCraftedBy(this, world, player);
     }
@@ -512,6 +777,17 @@
         return this.tag;
     }
 
+    // CraftBukkit start
+    @Nullable
+    private CompoundTag getTagClone() {
+        return this.tag == null ? null : this.tag.copy();
+    }
+
+    private void setTagClone(@Nullable CompoundTag nbtttagcompound) {
+        this.setTag(nbtttagcompound == null ? null : nbtttagcompound.copy());
+    }
+    // CraftBukkit end
+
     public CompoundTag getOrCreateTag() {
         if (this.tag == null) {
             this.setTag(new CompoundTag());
@@ -524,9 +800,10 @@
         if (this.tag != null && this.tag.contains(key, 10)) {
             return this.tag.getCompound(key);
         } else {
-            CompoundTag compoundTag = new CompoundTag();
-            this.addTagElement(key, compoundTag);
-            return compoundTag;
+            CompoundTag nbttagcompound = new CompoundTag();
+
+            this.addTagElement(key, nbttagcompound);
+            return nbttagcompound;
         }
     }
 
@@ -549,8 +826,28 @@
         return this.tag != null ? this.tag.getList("Enchantments", 10) : new ListTag();
     }
 
+    // Paper start - (this is just a good no conflict location)
+    public org.bukkit.inventory.ItemStack asBukkitMirror() {
+        return CraftItemStack.asCraftMirror(this);
+    }
+    public org.bukkit.inventory.ItemStack asBukkitCopy() {
+        return CraftItemStack.asCraftMirror(this.copy());
+    }
+    public static ItemStack fromBukkitCopy(org.bukkit.inventory.ItemStack itemstack) {
+        return CraftItemStack.asNMSCopy(itemstack);
+    }
+    private org.bukkit.craftbukkit.v1_19_R2.inventory.CraftItemStack bukkitStack;
+    public org.bukkit.inventory.ItemStack getBukkitStack() {
+        if (bukkitStack == null || bukkitStack.handle != this) {
+            bukkitStack = org.bukkit.craftbukkit.v1_19_R2.inventory.CraftItemStack.asCraftMirror(this);
+        }
+        return bukkitStack;
+    }
+    // Paper end
+
     public void setTag(@Nullable CompoundTag nbt) {
         this.tag = nbt;
+        this.processEnchantOrder(this.tag); // Paper
         if (this.getItem().canBeDepleted()) {
             this.setDamageValue(this.getDamageValue());
         }
@@ -562,17 +859,19 @@
     }
 
     public Component getHoverName() {
-        CompoundTag compoundTag = this.getTagElement("display");
-        if (compoundTag != null && compoundTag.contains("Name", 8)) {
+        CompoundTag nbttagcompound = this.getTagElement("display");
+
+        if (nbttagcompound != null && nbttagcompound.contains("Name", 8)) {
             try {
-                Component component = Component.Serializer.fromJson(compoundTag.getString("Name"));
-                if (component != null) {
-                    return component;
+                MutableComponent ichatmutablecomponent = Component.Serializer.fromJson(nbttagcompound.getString("Name"));
+
+                if (ichatmutablecomponent != null) {
+                    return ichatmutablecomponent;
                 }
 
-                compoundTag.remove("Name");
-            } catch (Exception var3) {
-                compoundTag.remove("Name");
+                nbttagcompound.remove("Name");
+            } catch (Exception exception) {
+                nbttagcompound.remove("Name");
             }
         }
 
@@ -580,21 +879,23 @@
     }
 
     public ItemStack setHoverName(@Nullable Component name) {
-        CompoundTag compoundTag = this.getOrCreateTagElement("display");
+        CompoundTag nbttagcompound = this.getOrCreateTagElement("display");
+
         if (name != null) {
-            compoundTag.putString("Name", Component.Serializer.toJson(name));
+            nbttagcompound.putString("Name", Component.Serializer.toJson(name));
         } else {
-            compoundTag.remove("Name");
+            nbttagcompound.remove("Name");
         }
 
         return this;
     }
 
     public void resetHoverName() {
-        CompoundTag compoundTag = this.getTagElement("display");
-        if (compoundTag != null) {
-            compoundTag.remove("Name");
-            if (compoundTag.isEmpty()) {
+        CompoundTag nbttagcompound = this.getTagElement("display");
+
+        if (nbttagcompound != null) {
+            nbttagcompound.remove("Name");
+            if (nbttagcompound.isEmpty()) {
                 this.removeTagKey("display");
             }
         }
@@ -606,104 +907,124 @@
     }
 
     public boolean hasCustomHoverName() {
-        CompoundTag compoundTag = this.getTagElement("display");
-        return compoundTag != null && compoundTag.contains("Name", 8);
+        CompoundTag nbttagcompound = this.getTagElement("display");
+
+        return nbttagcompound != null && nbttagcompound.contains("Name", 8);
     }
 
-    public List<Component> getTooltipLines(@Nullable Player player, TooltipFlag context) {
+    public List<Component> getTooltipLines(@Nullable net.minecraft.world.entity.player.Player player, TooltipFlag context) {
         List<Component> list = Lists.newArrayList();
-        MutableComponent mutableComponent = Component.empty().append(this.getHoverName()).withStyle(this.getRarity().color);
+        MutableComponent ichatmutablecomponent = Component.empty().append(this.getHoverName()).withStyle(this.getRarity().color);
+
         if (this.hasCustomHoverName()) {
-            mutableComponent.withStyle(ChatFormatting.ITALIC);
+            ichatmutablecomponent.withStyle(ChatFormatting.ITALIC);
         }
 
-        list.add(mutableComponent);
+        list.add(ichatmutablecomponent);
         if (!context.isAdvanced() && !this.hasCustomHoverName() && this.is(Items.FILLED_MAP)) {
             Integer integer = MapItem.getMapId(this);
+
             if (integer != null) {
                 list.add(Component.literal("#" + integer).withStyle(ChatFormatting.GRAY));
             }
         }
 
         int i = this.getHideFlags();
-        if (shouldShowInTooltip(i, ItemStack.TooltipPart.ADDITIONAL)) {
+
+        if (ItemStack.shouldShowInTooltip(i, ItemStack.TooltipPart.ADDITIONAL)) {
             this.getItem().appendHoverText(this, player == null ? null : player.level, list, context);
         }
 
+        int j;
+
         if (this.hasTag()) {
-            if (shouldShowInTooltip(i, ItemStack.TooltipPart.ENCHANTMENTS)) {
-                appendEnchantmentNames(list, this.getEnchantmentTags());
+            if (ItemStack.shouldShowInTooltip(i, ItemStack.TooltipPart.ENCHANTMENTS)) {
+                ItemStack.appendEnchantmentNames(list, this.getEnchantmentTags());
             }
 
             if (this.tag.contains("display", 10)) {
-                CompoundTag compoundTag = this.tag.getCompound("display");
-                if (shouldShowInTooltip(i, ItemStack.TooltipPart.DYE) && compoundTag.contains("color", 99)) {
+                CompoundTag nbttagcompound = this.tag.getCompound("display");
+
+                if (ItemStack.shouldShowInTooltip(i, ItemStack.TooltipPart.DYE) && nbttagcompound.contains("color", 99)) {
                     if (context.isAdvanced()) {
-                        list.add(Component.translatable("item.color", String.format(Locale.ROOT, "#%06X", compoundTag.getInt("color"))).withStyle(ChatFormatting.GRAY));
+                        list.add(Component.translatable("item.color", String.format(Locale.ROOT, "#%06X", nbttagcompound.getInt("color"))).withStyle(ChatFormatting.GRAY));
                     } else {
                         list.add(Component.translatable("item.dyed").withStyle(ChatFormatting.GRAY, ChatFormatting.ITALIC));
                     }
                 }
 
-                if (compoundTag.getTagType("Lore") == 9) {
-                    ListTag listTag = compoundTag.getList("Lore", 8);
+                if (nbttagcompound.getTagType("Lore") == 9) {
+                    ListTag nbttaglist = nbttagcompound.getList("Lore", 8);
 
-                    for(int j = 0; j < listTag.size(); ++j) {
-                        String string = listTag.getString(j);
+                    for (j = 0; j < nbttaglist.size(); ++j) {
+                        String s = nbttaglist.getString(j);
 
                         try {
-                            MutableComponent mutableComponent2 = Component.Serializer.fromJson(string);
-                            if (mutableComponent2 != null) {
-                                list.add(ComponentUtils.mergeStyles(mutableComponent2, LORE_STYLE));
+                            MutableComponent ichatmutablecomponent1 = Component.Serializer.fromJson(s);
+
+                            if (ichatmutablecomponent1 != null) {
+                                list.add(ComponentUtils.mergeStyles(ichatmutablecomponent1, ItemStack.LORE_STYLE));
                             }
-                        } catch (Exception var19) {
-                            compoundTag.remove("Lore");
+                        } catch (Exception exception) {
+                            nbttagcompound.remove("Lore");
                         }
                     }
                 }
             }
         }
 
-        if (shouldShowInTooltip(i, ItemStack.TooltipPart.MODIFIERS)) {
-            for(EquipmentSlot equipmentSlot : EquipmentSlot.values()) {
-                Multimap<Attribute, AttributeModifier> multimap = this.getAttributeModifiers(equipmentSlot);
+        int k;
+
+        if (ItemStack.shouldShowInTooltip(i, ItemStack.TooltipPart.MODIFIERS)) {
+            EquipmentSlot[] aenumitemslot = EquipmentSlot.values();
+
+            k = aenumitemslot.length;
+
+            for (j = 0; j < k; ++j) {
+                EquipmentSlot enumitemslot = aenumitemslot[j];
+                Multimap<Attribute, AttributeModifier> multimap = this.getAttributeModifiers(enumitemslot);
+
                 if (!multimap.isEmpty()) {
                     list.add(CommonComponents.EMPTY);
-                    list.add(Component.translatable("item.modifiers." + equipmentSlot.getName()).withStyle(ChatFormatting.GRAY));
+                    list.add(Component.translatable("item.modifiers." + enumitemslot.getName()).withStyle(ChatFormatting.GRAY));
+                    Iterator iterator = multimap.entries().iterator();
+
+                    while (iterator.hasNext()) {
+                        Entry<Attribute, AttributeModifier> entry = (Entry) iterator.next();
+                        AttributeModifier attributemodifier = (AttributeModifier) entry.getValue();
+                        double d0 = attributemodifier.getAmount();
+                        boolean flag = false;
 
-                    for(Map.Entry<Attribute, AttributeModifier> entry : multimap.entries()) {
-                        AttributeModifier attributeModifier = entry.getValue();
-                        double d = attributeModifier.getAmount();
-                        boolean bl = false;
                         if (player != null) {
-                            if (attributeModifier.getId() == Item.BASE_ATTACK_DAMAGE_UUID) {
-                                d += player.getAttributeBaseValue(Attributes.ATTACK_DAMAGE);
-                                d += (double)EnchantmentHelper.getDamageBonus(this, MobType.UNDEFINED);
-                                bl = true;
-                            } else if (attributeModifier.getId() == Item.BASE_ATTACK_SPEED_UUID) {
-                                d += player.getAttributeBaseValue(Attributes.ATTACK_SPEED);
-                                bl = true;
+                            if (attributemodifier.getId() == Item.BASE_ATTACK_DAMAGE_UUID) {
+                                d0 += player.getAttributeBaseValue(Attributes.ATTACK_DAMAGE);
+                                d0 += (double) EnchantmentHelper.getDamageBonus(this, MobType.UNDEFINED);
+                                flag = true;
+                            } else if (attributemodifier.getId() == Item.BASE_ATTACK_SPEED_UUID) {
+                                d0 += player.getAttributeBaseValue(Attributes.ATTACK_SPEED);
+                                flag = true;
                             }
                         }
 
-                        double f;
-                        if (attributeModifier.getOperation() != AttributeModifier.Operation.MULTIPLY_BASE && attributeModifier.getOperation() != AttributeModifier.Operation.MULTIPLY_TOTAL) {
-                            if (entry.getKey().equals(Attributes.KNOCKBACK_RESISTANCE)) {
-                                f = d * 10.0D;
+                        double d1;
+
+                        if (attributemodifier.getOperation() != AttributeModifier.Operation.MULTIPLY_BASE && attributemodifier.getOperation() != AttributeModifier.Operation.MULTIPLY_TOTAL) {
+                            if (((Attribute) entry.getKey()).equals(Attributes.KNOCKBACK_RESISTANCE)) {
+                                d1 = d0 * 10.0D;
                             } else {
-                                f = d;
+                                d1 = d0;
                             }
                         } else {
-                            f = d * 100.0D;
+                            d1 = d0 * 100.0D;
                         }
 
-                        if (bl) {
-                            list.add(Component.literal(" ").append(Component.translatable("attribute.modifier.equals." + attributeModifier.getOperation().toValue(), ATTRIBUTE_MODIFIER_FORMAT.format(f), Component.translatable(entry.getKey().getDescriptionId()))).withStyle(ChatFormatting.DARK_GREEN));
-                        } else if (d > 0.0D) {
-                            list.add(Component.translatable("attribute.modifier.plus." + attributeModifier.getOperation().toValue(), ATTRIBUTE_MODIFIER_FORMAT.format(f), Component.translatable(entry.getKey().getDescriptionId())).withStyle(ChatFormatting.BLUE));
-                        } else if (d < 0.0D) {
-                            f *= -1.0D;
-                            list.add(Component.translatable("attribute.modifier.take." + attributeModifier.getOperation().toValue(), ATTRIBUTE_MODIFIER_FORMAT.format(f), Component.translatable(entry.getKey().getDescriptionId())).withStyle(ChatFormatting.RED));
+                        if (flag) {
+                            list.add(Component.literal(" ").append((Component) Component.translatable("attribute.modifier.equals." + attributemodifier.getOperation().toValue(), ItemStack.ATTRIBUTE_MODIFIER_FORMAT.format(d1), Component.translatable(((Attribute) entry.getKey()).getDescriptionId()))).withStyle(ChatFormatting.DARK_GREEN));
+                        } else if (d0 > 0.0D) {
+                            list.add(Component.translatable("attribute.modifier.plus." + attributemodifier.getOperation().toValue(), ItemStack.ATTRIBUTE_MODIFIER_FORMAT.format(d1), Component.translatable(((Attribute) entry.getKey()).getDescriptionId())).withStyle(ChatFormatting.BLUE));
+                        } else if (d0 < 0.0D) {
+                            d1 *= -1.0D;
+                            list.add(Component.translatable("attribute.modifier.take." + attributemodifier.getOperation().toValue(), ItemStack.ATTRIBUTE_MODIFIER_FORMAT.format(d1), Component.translatable(((Attribute) entry.getKey()).getDescriptionId())).withStyle(ChatFormatting.RED));
                         }
                     }
                 }
@@ -711,30 +1032,32 @@
         }
 
         if (this.hasTag()) {
-            if (shouldShowInTooltip(i, ItemStack.TooltipPart.UNBREAKABLE) && this.tag.getBoolean("Unbreakable")) {
+            if (ItemStack.shouldShowInTooltip(i, ItemStack.TooltipPart.UNBREAKABLE) && this.tag.getBoolean("Unbreakable")) {
                 list.add(Component.translatable("item.unbreakable").withStyle(ChatFormatting.BLUE));
             }
 
-            if (shouldShowInTooltip(i, ItemStack.TooltipPart.CAN_DESTROY) && this.tag.contains("CanDestroy", 9)) {
-                ListTag listTag2 = this.tag.getList("CanDestroy", 8);
-                if (!listTag2.isEmpty()) {
+            ListTag nbttaglist1;
+
+            if (ItemStack.shouldShowInTooltip(i, ItemStack.TooltipPart.CAN_DESTROY) && this.tag.contains("CanDestroy", 9)) {
+                nbttaglist1 = this.tag.getList("CanDestroy", 8);
+                if (!nbttaglist1.isEmpty()) {
                     list.add(CommonComponents.EMPTY);
                     list.add(Component.translatable("item.canBreak").withStyle(ChatFormatting.GRAY));
 
-                    for(int k = 0; k < listTag2.size(); ++k) {
-                        list.addAll(expandBlockState(listTag2.getString(k)));
+                    for (k = 0; k < nbttaglist1.size(); ++k) {
+                        list.addAll(ItemStack.expandBlockState(nbttaglist1.getString(k)));
                     }
                 }
             }
 
-            if (shouldShowInTooltip(i, ItemStack.TooltipPart.CAN_PLACE) && this.tag.contains("CanPlaceOn", 9)) {
-                ListTag listTag3 = this.tag.getList("CanPlaceOn", 8);
-                if (!listTag3.isEmpty()) {
+            if (ItemStack.shouldShowInTooltip(i, ItemStack.TooltipPart.CAN_PLACE) && this.tag.contains("CanPlaceOn", 9)) {
+                nbttaglist1 = this.tag.getList("CanPlaceOn", 8);
+                if (!nbttaglist1.isEmpty()) {
                     list.add(CommonComponents.EMPTY);
                     list.add(Component.translatable("item.canPlace").withStyle(ChatFormatting.GRAY));
 
-                    for(int l = 0; l < listTag3.size(); ++l) {
-                        list.addAll(expandBlockState(listTag3.getString(l)));
+                    for (k = 0; k < nbttaglist1.size(); ++k) {
+                        list.addAll(ItemStack.expandBlockState(nbttaglist1.getString(k)));
                     }
                 }
             }
@@ -752,7 +1075,7 @@
         }
 
         if (player != null && !this.getItem().isEnabled(player.getLevel().enabledFeatures())) {
-            list.add(DISABLED_ITEM_TOOLTIP);
+            list.add(ItemStack.DISABLED_ITEM_TOOLTIP);
         }
 
         return list;
@@ -767,15 +1090,17 @@
     }
 
     public void hideTooltipPart(ItemStack.TooltipPart tooltipSection) {
-        CompoundTag compoundTag = this.getOrCreateTag();
-        compoundTag.putInt("HideFlags", compoundTag.getInt("HideFlags") | tooltipSection.getMask());
+        CompoundTag nbttagcompound = this.getOrCreateTag();
+
+        nbttagcompound.putInt("HideFlags", nbttagcompound.getInt("HideFlags") | tooltipSection.getMask());
     }
 
     public static void appendEnchantmentNames(List<Component> tooltip, ListTag enchantments) {
-        for(int i = 0; i < enchantments.size(); ++i) {
-            CompoundTag compoundTag = enchantments.getCompound(i);
-            BuiltInRegistries.ENCHANTMENT.getOptional(EnchantmentHelper.getEnchantmentId(compoundTag)).ifPresent((e) -> {
-                tooltip.add(e.getFullname(EnchantmentHelper.getEnchantmentLevel(compoundTag)));
+        for (int i = 0; i < enchantments.size(); ++i) {
+            CompoundTag nbttagcompound = enchantments.getCompound(i);
+
+            BuiltInRegistries.ENCHANTMENT.getOptional(EnchantmentHelper.getEnchantmentId(nbttagcompound)).ifPresent((enchantment) -> {
+                tooltip.add(enchantment.getFullname(EnchantmentHelper.getEnchantmentLevel(nbttagcompound)));
             });
         }
 
@@ -783,15 +1108,15 @@
 
     private static Collection<Component> expandBlockState(String tag) {
         try {
-            return BlockStateParser.parseForTesting(BuiltInRegistries.BLOCK.asLookup(), tag, true).map((blockResult) -> {
-                return Lists.newArrayList(blockResult.blockState().getBlock().getName().withStyle(ChatFormatting.DARK_GRAY));
-            }, (tagResult) -> {
-                return tagResult.tag().stream().map((holder) -> {
-                    return holder.value().getName().withStyle(ChatFormatting.DARK_GRAY);
+            return (Collection) BlockStateParser.parseForTesting(BuiltInRegistries.BLOCK.asLookup(), tag, true).map((argumentblock_a) -> {
+                return Lists.newArrayList(new Component[]{argumentblock_a.blockState().getBlock().getName().withStyle(ChatFormatting.DARK_GRAY)});
+            }, (argumentblock_b) -> {
+                return (List) argumentblock_b.tag().stream().map((holder) -> {
+                    return ((Block) holder.value()).getName().withStyle(ChatFormatting.DARK_GRAY);
                 }).collect(Collectors.toList());
             });
-        } catch (CommandSyntaxException var2) {
-            return Lists.newArrayList(Component.literal("missingno").withStyle(ChatFormatting.DARK_GRAY));
+        } catch (CommandSyntaxException commandsyntaxexception) {
+            return Lists.newArrayList(new Component[]{Component.literal("missingno").withStyle(ChatFormatting.DARK_GRAY)});
         }
     }
 
@@ -804,11 +1129,7 @@
     }
 
     public boolean isEnchantable() {
-        if (!this.getItem().isEnchantable(this)) {
-            return false;
-        } else {
-            return !this.isEnchanted();
-        }
+        return !this.getItem().isEnchantable(this) ? false : !this.isEnchanted();
     }
 
     public void enchant(Enchantment enchantment, int level) {
@@ -817,16 +1138,14 @@
             this.tag.put("Enchantments", new ListTag());
         }
 
-        ListTag listTag = this.tag.getList("Enchantments", 10);
-        listTag.add(EnchantmentHelper.storeEnchantment(EnchantmentHelper.getEnchantmentId(enchantment), (byte)level));
+        ListTag nbttaglist = this.tag.getList("Enchantments", 10);
+
+        nbttaglist.add(EnchantmentHelper.storeEnchantment(EnchantmentHelper.getEnchantmentId(enchantment), (byte) level));
+        processEnchantOrder(this.tag); // Paper
     }
 
     public boolean isEnchanted() {
-        if (this.tag != null && this.tag.contains("Enchantments", 9)) {
-            return !this.tag.getList("Enchantments", 10).isEmpty();
-        } else {
-            return false;
-        }
+        return this.tag != null && this.tag.contains("Enchantments", 9) ? !this.tag.getList("Enchantments", 10).isEmpty() : false;
     }
 
     public void addTagElement(String key, Tag element) {
@@ -843,7 +1162,7 @@
 
     @Nullable
     public ItemFrame getFrame() {
-        return this.entityRepresentation instanceof ItemFrame ? (ItemFrame)this.getEntityRepresentation() : null;
+        return this.entityRepresentation instanceof ItemFrame ? (ItemFrame) this.getEntityRepresentation() : null;
     }
 
     @Nullable
@@ -856,32 +1175,42 @@
     }
 
     public void setRepairCost(int repairCost) {
+        // CraftBukkit start - remove RepairCost tag when 0 (SPIGOT-3945)
+        if (repairCost == 0) {
+            this.removeTagKey("RepairCost");
+            return;
+        }
+        // CraftBukkit end
         this.getOrCreateTag().putInt("RepairCost", repairCost);
     }
 
     public Multimap<Attribute, AttributeModifier> getAttributeModifiers(EquipmentSlot slot) {
-        Multimap<Attribute, AttributeModifier> multimap;
+        Object object;
+
         if (this.hasTag() && this.tag.contains("AttributeModifiers", 9)) {
-            multimap = HashMultimap.create();
-            ListTag listTag = this.tag.getList("AttributeModifiers", 10);
+            object = HashMultimap.create();
+            ListTag nbttaglist = this.tag.getList("AttributeModifiers", 10);
+
+            for (int i = 0; i < nbttaglist.size(); ++i) {
+                CompoundTag nbttagcompound = nbttaglist.getCompound(i);
+
+                if (!nbttagcompound.contains("Slot", 8) || nbttagcompound.getString("Slot").equals(slot.getName())) {
+                    Optional<Attribute> optional = BuiltInRegistries.ATTRIBUTE.getOptional(ResourceLocation.tryParse(nbttagcompound.getString("AttributeName")));
 
-            for(int i = 0; i < listTag.size(); ++i) {
-                CompoundTag compoundTag = listTag.getCompound(i);
-                if (!compoundTag.contains("Slot", 8) || compoundTag.getString("Slot").equals(slot.getName())) {
-                    Optional<Attribute> optional = BuiltInRegistries.ATTRIBUTE.getOptional(ResourceLocation.tryParse(compoundTag.getString("AttributeName")));
                     if (optional.isPresent()) {
-                        AttributeModifier attributeModifier = AttributeModifier.load(compoundTag);
-                        if (attributeModifier != null && attributeModifier.getId().getLeastSignificantBits() != 0L && attributeModifier.getId().getMostSignificantBits() != 0L) {
-                            multimap.put(optional.get(), attributeModifier);
+                        AttributeModifier attributemodifier = AttributeModifier.load(nbttagcompound);
+
+                        if (attributemodifier != null && attributemodifier.getId().getLeastSignificantBits() != 0L && attributemodifier.getId().getMostSignificantBits() != 0L) {
+                            ((Multimap) object).put((Attribute) optional.get(), attributemodifier);
                         }
                     }
                 }
             }
         } else {
-            multimap = this.getItem().getDefaultAttributeModifiers(slot);
+            object = this.getItem().getDefaultAttributeModifiers(slot);
         }
 
-        return multimap;
+        return (Multimap) object;
     }
 
     public void addAttributeModifier(Attribute attribute, AttributeModifier modifier, @Nullable EquipmentSlot slot) {
@@ -890,30 +1219,41 @@
             this.tag.put("AttributeModifiers", new ListTag());
         }
 
-        ListTag listTag = this.tag.getList("AttributeModifiers", 10);
-        CompoundTag compoundTag = modifier.save();
-        compoundTag.putString("AttributeName", BuiltInRegistries.ATTRIBUTE.getKey(attribute).toString());
+        ListTag nbttaglist = this.tag.getList("AttributeModifiers", 10);
+        CompoundTag nbttagcompound = modifier.save();
+
+        nbttagcompound.putString("AttributeName", BuiltInRegistries.ATTRIBUTE.getKey(attribute).toString());
         if (slot != null) {
-            compoundTag.putString("Slot", slot.getName());
+            nbttagcompound.putString("Slot", slot.getName());
         }
 
-        listTag.add(compoundTag);
+        nbttaglist.add(nbttagcompound);
+    }
+
+    // CraftBukkit start
+    @Deprecated
+    public void setItem(Item item) {
+        this.bukkitStack = null; // Paper
+        this.item = item;
     }
+    // CraftBukkit end
 
     public Component getDisplayName() {
-        MutableComponent mutableComponent = Component.empty().append(this.getHoverName());
+        MutableComponent ichatmutablecomponent = Component.empty().append(this.getHoverName());
+
         if (this.hasCustomHoverName()) {
-            mutableComponent.withStyle(ChatFormatting.ITALIC);
+            ichatmutablecomponent.withStyle(ChatFormatting.ITALIC);
         }
 
-        MutableComponent mutableComponent2 = ComponentUtils.wrapInSquareBrackets(mutableComponent);
+        MutableComponent ichatmutablecomponent1 = ComponentUtils.wrapInSquareBrackets(ichatmutablecomponent);
+
         if (!this.emptyCacheFlag) {
-            mutableComponent2.withStyle(this.getRarity().color).withStyle((style) -> {
-                return style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_ITEM, new HoverEvent.ItemStackInfo(this)));
+            ichatmutablecomponent1.withStyle(this.getRarity().color).withStyle((chatmodifier) -> {
+                return chatmodifier.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_ITEM, new HoverEvent.ItemStackInfo(this)));
             });
         }
 
-        return mutableComponent2;
+        return ichatmutablecomponent1;
     }
 
     public boolean hasAdventureModePlaceTagForBlock(Registry<Block> blockRegistry, BlockInWorld pos) {
@@ -983,16 +1323,13 @@
     }
 
     public static enum TooltipPart {
-        ENCHANTMENTS,
-        MODIFIERS,
-        UNBREAKABLE,
-        CAN_DESTROY,
-        CAN_PLACE,
-        ADDITIONAL,
-        DYE;
+
+        ENCHANTMENTS, MODIFIERS, UNBREAKABLE, CAN_DESTROY, CAN_PLACE, ADDITIONAL, DYE;
 
         private final int mask = 1 << this.ordinal();
 
+        private TooltipPart() {}
+
         public int getMask() {
             return this.mask;
         }
