--- a/net/minecraft/world/item/ItemStack.java
+++ b/net/minecraft/world/item/ItemStack.java
@@ -164,8 +_,8 @@
     private int popTime;
     @Deprecated
     @Nullable
-    private final Item item;
-    final PatchedDataComponentMap components;
+    private Item item;
+    PatchedDataComponentMap components;
     @Nullable
     private Entity entityRepresentation;
 
@@ -198,12 +_,20 @@
 
             @Override
             public void encode(RegistryFriendlyByteBuf buffer, ItemStack value) {
-                if (value.isEmpty()) {
+                if (value.isEmpty() || value.getItem() == null) { // CraftBukkit - NPE fix itemstack.getItem()
                     buffer.writeVarInt(0);
                 } else {
-                    buffer.writeVarInt(value.getCount());
+                    buffer.writeVarInt(io.papermc.paper.util.sanitizer.ItemComponentSanitizer.sanitizeCount(io.papermc.paper.util.sanitizer.ItemObfuscationSession.currentSession(), value, value.getCount())); // Paper - potentially sanitize count
                     Item.STREAM_CODEC.encode(buffer, value.getItemHolder());
+                    // Paper start - adventure; conditionally render translatable components
+                    boolean prev = net.minecraft.network.chat.ComponentSerialization.DONT_RENDER_TRANSLATABLES.get();
+                    try (final io.papermc.paper.util.SafeAutoClosable ignored = io.papermc.paper.util.sanitizer.ItemObfuscationSession.withContext(c -> c.itemStack(value))) { // pass the itemstack as context to the obfuscation session
+                        net.minecraft.network.chat.ComponentSerialization.DONT_RENDER_TRANSLATABLES.set(true);
                     codec.encode(buffer, value.components.asPatch());
+                    } finally {
+                        net.minecraft.network.chat.ComponentSerialization.DONT_RENDER_TRANSLATABLES.set(prev);
+                    }
+                    // Paper end - adventure; conditionally render translatable components
                 }
             }
         };
@@ -373,10 +_,166 @@
             return InteractionResult.PASS;
         } else {
             Item item = this.getItem();
-            InteractionResult interactionResult = item.useOn(context);
+            // CraftBukkit start - handle all block place event logic here
+            DataComponentPatch previousPatch = this.components.asPatch();
+            int oldCount = this.getCount();
+            ServerLevel serverLevel = (ServerLevel) context.getLevel();
+
+            if (!(item instanceof BucketItem/* || item instanceof SolidBucketItem*/)) { // if not bucket // Paper - Fix cancelled powdered snow bucket placement
+                serverLevel.captureBlockStates = true;
+                // special case bonemeal
+                if (item == Items.BONE_MEAL) {
+                    serverLevel.captureTreeGeneration = true;
+                }
+            }
+            InteractionResult interactionResult;
+            try {
+                interactionResult = item.useOn(context);
+            } finally {
+                serverLevel.captureBlockStates = false;
+            }
+            DataComponentPatch newPatch = this.components.asPatch();
+            int newCount = this.getCount();
+            this.setCount(oldCount);
+            this.restorePatch(previousPatch);
+            if (interactionResult.consumesAction() && serverLevel.captureTreeGeneration && !serverLevel.capturedBlockStates.isEmpty()) {
+                serverLevel.captureTreeGeneration = false;
+                org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(clickedPos, serverLevel.getWorld());
+                org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeType;
+                net.minecraft.world.level.block.SaplingBlock.treeType = null;
+                List<org.bukkit.craftbukkit.block.CraftBlockState> blocks = new java.util.ArrayList<>(serverLevel.capturedBlockStates.values());
+                serverLevel.capturedBlockStates.clear();
+                org.bukkit.event.world.StructureGrowEvent structureEvent = null;
+                if (treeType != null) {
+                    boolean isBonemeal = this.getItem() == Items.BONE_MEAL;
+                    structureEvent = new org.bukkit.event.world.StructureGrowEvent(location, treeType, isBonemeal, (org.bukkit.entity.Player) player.getBukkitEntity(), (List<org.bukkit.block.BlockState>) (List<? extends org.bukkit.block.BlockState>) blocks);
+                    org.bukkit.Bukkit.getPluginManager().callEvent(structureEvent);
+                }
+
+                org.bukkit.event.block.BlockFertilizeEvent fertilizeEvent = new org.bukkit.event.block.BlockFertilizeEvent(org.bukkit.craftbukkit.block.CraftBlock.at(serverLevel, clickedPos), (org.bukkit.entity.Player) player.getBukkitEntity(), (List<org.bukkit.block.BlockState>) (List<? extends org.bukkit.block.BlockState>) 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.components.asPatch(), previousPatch)) {
+                        this.restorePatch(newPatch);
+                        this.setCount(newCount);
+                    }
+                    for (org.bukkit.craftbukkit.block.CraftBlockState snapshot : blocks) {
+                        // SPIGOT-7572 - Move fix for SPIGOT-7248 to CapturedBlockState, to allow bees in bee nest
+                        snapshot.place(snapshot.getFlags());
+                        serverLevel.checkCapturedTreeStateForObserverNotify(clickedPos, snapshot); // Paper - notify observers even if grow failed
+                    }
+                    player.awardStat(Stats.ITEM_USED.get(item)); // SPIGOT-7236 - award stat
+                }
+
+                SignItem.openSign = null; // SPIGOT-6758 - Reset on early return
+                return interactionResult;
+            }
+            serverLevel.captureTreeGeneration = false;
             if (player != null && interactionResult instanceof InteractionResult.Success success && success.wasItemInteraction()) {
-                player.awardStat(Stats.ITEM_USED.get(item));
+                InteractionHand hand = context.getHand();
+                org.bukkit.event.block.BlockPlaceEvent placeEvent = null;
+                List<org.bukkit.block.BlockState> blocks = new java.util.ArrayList<>(serverLevel.capturedBlockStates.values());
+                serverLevel.capturedBlockStates.clear();
+                if (blocks.size() > 1) {
+                    placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockMultiPlaceEvent(serverLevel, player, hand, blocks, clickedPos);
+                } else if (blocks.size() == 1 && item != Items.POWDER_SNOW_BUCKET) { // Paper - Fix cancelled powdered snow bucket placement
+                    placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEvent(serverLevel, player, hand, blocks.getFirst(), clickedPos);
+                }
+
+                if (placeEvent != null && (placeEvent.isCancelled() || !placeEvent.canBuild())) {
+                    interactionResult = InteractionResult.FAIL; // cancel placement
+                    // PAIL: Remove this when MC-99075 fixed
+                    player.containerMenu.sendAllDataToRemote();
+                    serverLevel.capturedTileEntities.clear(); // Paper - Allow chests to be placed with NBT data; clear out block entities as chests and such will pop loot
+                    // revert back all captured blocks
+                    for (org.bukkit.block.BlockState blockstate : blocks) {
+                        ((org.bukkit.craftbukkit.block.CraftBlockState) blockstate).revertPlace();
+                    }
+
+                    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.components.asPatch(), previousPatch)) {
+                        this.restorePatch(newPatch);
+                        this.setCount(newCount);
+                    }
+
+                    for (java.util.Map.Entry<BlockPos, net.minecraft.world.level.block.entity.BlockEntity> e : serverLevel.capturedTileEntities.entrySet()) {
+                        serverLevel.setBlockEntity(e.getValue());
+                    }
+
+                    for (org.bukkit.block.BlockState blockstate : blocks) {
+                        int updateFlags = ((org.bukkit.craftbukkit.block.CraftBlockState) blockstate).getFlags();
+                        net.minecraft.world.level.block.state.BlockState oldBlock = ((org.bukkit.craftbukkit.block.CraftBlockState) blockstate).getHandle();
+                        BlockPos newPos = ((org.bukkit.craftbukkit.block.CraftBlockState) blockstate).getPosition();
+                        net.minecraft.world.level.block.state.BlockState block = serverLevel.getBlockState(newPos);
+
+                        if (!(block.getBlock() instanceof net.minecraft.world.level.block.BaseEntityBlock)) { // Containers get placed automatically
+                            block.onPlace(serverLevel, newPos, oldBlock, true, context);
+                        }
+
+                        serverLevel.notifyAndUpdatePhysics(newPos, null, oldBlock, block, serverLevel.getBlockState(newPos), updateFlags, net.minecraft.world.level.block.Block.UPDATE_LIMIT); // send null chunk as chunk.k() returns false by this point
+                    }
+
+                    if (this.item == Items.WITHER_SKELETON_SKULL) { // Special case skulls to allow wither spawns to be cancelled
+                        BlockPos bp = clickedPos;
+                        if (!serverLevel.getBlockState(clickedPos).canBeReplaced()) {
+                            if (!serverLevel.getBlockState(clickedPos).isSolid()) {
+                                bp = null;
+                            } else {
+                                bp = bp.relative(context.getClickedFace());
+                            }
+                        }
+                        if (bp != null) {
+                            net.minecraft.world.level.block.entity.BlockEntity te = serverLevel.getBlockEntity(bp);
+                            if (te instanceof net.minecraft.world.level.block.entity.SkullBlockEntity) {
+                                net.minecraft.world.level.block.WitherSkullBlock.checkSpawn(serverLevel, bp, (net.minecraft.world.level.block.entity.SkullBlockEntity) te);
+                            }
+                        }
+                    }
+
+                    // SPIGOT-4678
+                    if (this.item instanceof SignItem && SignItem.openSign != null) {
+                        try {
+                            if (serverLevel.getBlockEntity(SignItem.openSign) instanceof net.minecraft.world.level.block.entity.SignBlockEntity blockEntity) {
+                                if (serverLevel.getBlockState(SignItem.openSign).getBlock() instanceof net.minecraft.world.level.block.SignBlock signBlock) {
+                                    signBlock.openTextEdit(player, blockEntity, true, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.PLACE); // CraftBukkit // Paper - Add PlayerOpenSignEvent
+                                }
+                            }
+                        } finally {
+                            SignItem.openSign = null;
+                        }
+                    }
+
+                    // SPIGOT-7315: Moved from BedBlock#setPlacedBy
+                    if (placeEvent != null && this.item instanceof BedItem) {
+                        BlockPos pos = ((org.bukkit.craftbukkit.block.CraftBlock) placeEvent.getBlock()).getPosition();
+                        net.minecraft.world.level.block.state.BlockState state = serverLevel.getBlockState(pos);
+
+                        if (state.getBlock() instanceof net.minecraft.world.level.block.BedBlock) {
+                            serverLevel.updateNeighborsAt(pos, net.minecraft.world.level.block.Blocks.AIR);
+                            state.updateNeighbourShapes(serverLevel, pos, 3);
+                        }
+                    }
+
+                    // SPIGOT-1288 - play sound stripped from BlockItem
+                    if (this.item instanceof BlockItem && success.paperSuccessContext().placedBlockPosition() != null) {
+                        // Paper start - Fix spigot sound playing for BlockItem ItemStacks
+                        net.minecraft.world.level.block.state.BlockState state = serverLevel.getBlockState(success.paperSuccessContext().placedBlockPosition());
+                        net.minecraft.world.level.block.SoundType soundType = state.getSoundType();
+                        // Paper end - Fix spigot sound playing for BlockItem ItemStacks
+                        serverLevel.playSound(player, clickedPos, soundType.getPlaceSound(), net.minecraft.sounds.SoundSource.BLOCKS, (soundType.getVolume() + 1.0F) / 2.0F, soundType.getPitch() * 0.8F);
+                    }
+
+                    player.awardStat(Stats.ITEM_USED.get(item));
+                }
             }
+            serverLevel.capturedTileEntities.clear();
+            serverLevel.capturedBlockStates.clear();
+            // CraftBukkit end
 
             return interactionResult;
         }
@@ -473,31 +_,71 @@
         return this.isDamageableItem() && this.getDamageValue() >= this.getMaxDamage() - 1;
     }
 
-    public void hurtAndBreak(int damage, ServerLevel level, @Nullable ServerPlayer player, Consumer<Item> onBreak) {
-        int i = this.processDurabilityChange(damage, level, player);
-        if (i != 0) {
+    public void hurtAndBreak(int damage, ServerLevel level, @Nullable LivingEntity player, Consumer<Item> onBreak) { // Paper - Add EntityDamageItemEvent
+        // Paper start - add force boolean overload
+        this.hurtAndBreak(damage, level, player, onBreak, false);
+    }
+
+    public void hurtAndBreak(int damage, ServerLevel level, @Nullable LivingEntity player, Consumer<Item> onBreak, boolean force) { // Paper - Add EntityDamageItemEvent
+        // Paper end
+        final int originalDamage = damage; // Paper - Expand PlayerItemDamageEvent
+        int i = this.processDurabilityChange(damage, level, player, force); // Paper
+        // CraftBukkit start
+        if (i > 0 && player instanceof final ServerPlayer serverPlayer) { // Paper - Add EntityDamageItemEvent - limit to positive damage and run for player
+            org.bukkit.event.player.PlayerItemDamageEvent event = new org.bukkit.event.player.PlayerItemDamageEvent(serverPlayer.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(this), i, originalDamage); // Paper - Add EntityDamageItemEvent
+            event.getPlayer().getServer().getPluginManager().callEvent(event);
+
+            if (i != event.getDamage() || event.isCancelled()) {
+                serverPlayer.containerMenu.sendAllDataToRemote();
+            }
+            if (event.isCancelled()) {
+                return;
+            }
+
+            i = event.getDamage();
+            // Paper start - Add EntityDamageItemEvent
+        } else if (i > 0 && player != null) {
+            io.papermc.paper.event.entity.EntityDamageItemEvent event = new io.papermc.paper.event.entity.EntityDamageItemEvent(player.getBukkitLivingEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(this), i);
+            if (!event.callEvent()) {
+                return;
+            }
+            i = event.getDamage();
+            // Paper end - Add EntityDamageItemEvent
+        }
+        // CraftBukkit end
+        if (i != 0) { // Paper - Add EntityDamageItemEvent - diff on change for above event ifs.
             this.applyDamage(this.getDamageValue() + i, player, onBreak);
         }
     }
 
-    private int processDurabilityChange(int damage, ServerLevel level, @Nullable ServerPlayer player) {
+    private int processDurabilityChange(int damage, ServerLevel level, @Nullable LivingEntity player) { // Paper - Add EntityDamageItemEvent
+        // Paper start - itemstack damage api
+        return processDurabilityChange(damage, level, player, false);
+    }
+    private int processDurabilityChange(int damage, ServerLevel level, @Nullable LivingEntity player, boolean force) {
+        // Paper end - itemstack damage api
         if (!this.isDamageableItem()) {
             return 0;
-        } else if (player != null && player.hasInfiniteMaterials()) {
+        } else if (player instanceof ServerPlayer && player.hasInfiniteMaterials() && !force) { // Paper - Add EntityDamageItemEvent
             return 0;
         } else {
             return damage > 0 ? EnchantmentHelper.processDurabilityChange(level, this, damage) : damage;
         }
     }
 
-    private void applyDamage(int damage, @Nullable ServerPlayer player, Consumer<Item> onBreak) {
-        if (player != null) {
-            CriteriaTriggers.ITEM_DURABILITY_CHANGED.trigger(player, this, damage);
+    private void applyDamage(int damage, @Nullable LivingEntity player, Consumer<Item> onBreak) { // Paper - Add EntityDamageItemEvent
+        if (player instanceof final ServerPlayer serverPlayer) { // Paper - Add EntityDamageItemEvent
+            CriteriaTriggers.ITEM_DURABILITY_CHANGED.trigger(serverPlayer, this, damage); // Paper - Add EntityDamageItemEvent
         }
 
         this.setDamageValue(damage);
         if (this.isBroken()) {
             Item item = this.getItem();
+            // CraftBukkit start - Check for item breaking
+            if (this.count == 1 && player instanceof final ServerPlayer serverPlayer) { // Paper - Add EntityDamageItemEvent
+                org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemBreakEvent(serverPlayer, this); // Paper - Add EntityDamageItemEvent
+            }
+            // CraftBukkit end
             this.shrink(1);
             onBreak.accept(item);
         }
@@ -510,15 +_,39 @@
                 return;
             }
 
-            int min = Math.min(this.getDamageValue() + i, this.getMaxDamage() - 1);
+            int min = Math.min(this.getDamageValue() + i, this.getMaxDamage() - 1); // Paper - Expand PlayerItemDamageEvent - diff on change as min computation is copied post event.
+
+            // Paper start - Expand PlayerItemDamageEvent
+            if (min - this.getDamageValue() > 0) {
+                org.bukkit.event.player.PlayerItemDamageEvent event = new org.bukkit.event.player.PlayerItemDamageEvent(
+                    serverPlayer.getBukkitEntity(),
+                    org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(this),
+                    min - this.getDamageValue(),
+                    damage
+                );
+                if (!event.callEvent() || event.getDamage() == 0) {
+                    return;
+                }
+
+                // Prevent breaking the item in this code path as callers may expect the item to survive
+                // (given the method name)
+                min = Math.min(this.getDamageValue() + event.getDamage(), this.getMaxDamage() - 1);
+            }
+            // Paper end - Expand PlayerItemDamageEvent
+
             this.applyDamage(min, serverPlayer, item -> {});
         }
     }
 
     public void hurtAndBreak(int amount, LivingEntity entity, EquipmentSlot slot) {
+        // Paper start - add param to skip infinite mats check
+        this.hurtAndBreak(amount, entity, slot, false);
+    }
+    public void hurtAndBreak(int amount, LivingEntity entity, EquipmentSlot slot, boolean force) {
+        // Paper end - add param to skip infinite mats check
         if (entity.level() instanceof ServerLevel serverLevel) {
             this.hurtAndBreak(
-                amount, serverLevel, entity instanceof ServerPlayer serverPlayer ? serverPlayer : null, item -> entity.onEquippedItemBroken(item, slot)
+                amount, serverLevel, entity, item -> {if (slot != null) entity.onEquippedItemBroken(item, slot); }, force // Paper - Add EntityDamageItemEvent & itemstack damage API - do not process entity related callbacks when damaging from API
             );
         }
     }
@@ -603,10 +_,16 @@
     }
 
     public ItemStack copy() {
-        if (this.isEmpty()) {
+        // Paper start - Perf: Optimize Hoppers
+        return this.copy(false);
+    }
+
+    public ItemStack copy(final boolean originalItem) {
+        if (!originalItem && this.isEmpty()) {
+            // Paper end - Perf: Optimize Hoppers
             return EMPTY;
         } else {
-            ItemStack itemStack = new ItemStack(this.getItem(), this.count, this.components.copy());
+            ItemStack itemStack = new ItemStack(originalItem ? this.item : this.getItem(), this.count, this.components.copy()); // Paper - Perf: Optimize Hoppers
             itemStack.setPopTime(this.getPopTime());
             return itemStack;
         }
@@ -732,6 +_,12 @@
         return this.getItem().useOnRelease(this);
     }
 
+    // CraftBukkit start
+    public void restorePatch(DataComponentPatch datacomponentpatch) {
+        this.components.restorePatch(datacomponentpatch);
+    }
+    // CraftBukkit end
+
     @Nullable
     public <T> T set(DataComponentType<T> component, @Nullable T value) {
         return this.components.set(component, value);
@@ -779,6 +_,28 @@
         this.getItem().verifyComponentsAfterLoad(this);
     }
 
+    // Paper start - (this is just a good no conflict location)
+    public org.bukkit.inventory.ItemStack asBukkitMirror() {
+        return org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(this);
+    }
+
+    public org.bukkit.inventory.ItemStack asBukkitCopy() {
+        return org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(this.copy());
+    }
+
+    public static ItemStack fromBukkitCopy(org.bukkit.inventory.ItemStack itemstack) {
+        return org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(itemstack);
+    }
+
+    private @Nullable org.bukkit.craftbukkit.inventory.CraftItemStack bukkitStack;
+    public org.bukkit.inventory.ItemStack getBukkitStack() {
+        if (this.bukkitStack == null || this.bukkitStack.handle != this) {
+            this.bukkitStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(this);
+        }
+        return this.bukkitStack;
+    }
+    // Paper end
+
     public Component getHoverName() {
         Component customName = this.getCustomName();
         return customName != null ? customName : this.getItemName();
@@ -1054,6 +_,19 @@
         EnchantmentHelper.forEachModifier(this, equipmentSLot, action);
     }
 
+    // CraftBukkit start
+    @Deprecated
+    public void setItem(Item item) {
+        this.bukkitStack = null; // Paper
+        this.item = item;
+        // Paper start - change base component prototype
+        final DataComponentPatch patch = this.getComponentsPatch();
+        this.components = new PatchedDataComponentMap(this.item.components());
+        this.applyComponents(patch);
+        // Paper end - change base component prototype
+    }
+    // CraftBukkit end
+
     public Component getDisplayName() {
         MutableComponent mutableComponent = Component.empty().append(this.getHoverName());
         if (this.has(DataComponents.CUSTOM_NAME)) {
@@ -1109,7 +_,7 @@
     }
 
     public void consume(int amount, @Nullable LivingEntity entity) {
-        if (entity == null || !entity.hasInfiniteMaterials()) {
+        if ((entity == null || !entity.hasInfiniteMaterials()) && this != ItemStack.EMPTY) { // CraftBukkit
             this.shrink(amount);
         }
     }
