--- a/net/minecraft/world/item/ItemStack.java
+++ b/net/minecraft/world/item/ItemStack.java
@@ -96,18 +96,52 @@
 import org.apache.commons.lang3.mutable.MutableBoolean;
 import org.slf4j.Logger;
 
+// CraftBukkit start
+import java.util.Map;
+import java.util.Objects;
+import net.minecraft.core.EnumDirection;
+import net.minecraft.nbt.DynamicOpsNBT;
+import net.minecraft.network.protocol.game.PacketPlayOutBlockChange;
+import net.minecraft.server.level.WorldServer;
+import net.minecraft.sounds.SoundCategory;
+import net.minecraft.world.level.block.BlockBed;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.level.block.BlockSapling;
+import net.minecraft.world.level.block.BlockSign;
+import net.minecraft.world.level.block.BlockTileEntity;
+import net.minecraft.world.level.block.BlockWitherSkull;
+import net.minecraft.world.level.block.SoundEffectType;
+import net.minecraft.world.level.block.entity.TileEntity;
+import net.minecraft.world.level.block.entity.TileEntityJukeBox;
+import net.minecraft.world.level.block.entity.TileEntitySign;
+import net.minecraft.world.level.block.entity.TileEntitySkull;
+import net.minecraft.world.level.gameevent.GameEvent;
+import org.bukkit.Location;
+import org.bukkit.TreeType;
+import org.bukkit.block.BlockState;
+import org.bukkit.craftbukkit.block.CapturedBlockState;
+import org.bukkit.craftbukkit.block.CraftBlock;
+import org.bukkit.craftbukkit.block.CraftBlockState;
+import org.bukkit.craftbukkit.inventory.CraftItemStack;
+import org.bukkit.craftbukkit.util.CraftLocation;
+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 implements DataComponentHolder {
 
     private static final List<IChatBaseComponent> OP_NBT_WARNING = List.of(IChatBaseComponent.translatable("item.op_warning.line1").withStyle(EnumChatFormat.RED, EnumChatFormat.BOLD), IChatBaseComponent.translatable("item.op_warning.line2").withStyle(EnumChatFormat.RED), IChatBaseComponent.translatable("item.op_warning.line3").withStyle(EnumChatFormat.RED));
     public static final Codec<ItemStack> CODEC = Codec.lazyInitialized(() -> {
-        return RecordCodecBuilder.create((instance) -> {
+        return RecordCodecBuilder.<ItemStack>create((instance) -> { // CraftBukkit - decompile error
             return instance.group(Item.CODEC.fieldOf("id").forGetter(ItemStack::getItemHolder), ExtraCodecs.intRange(1, 99).fieldOf("count").orElse(1).forGetter(ItemStack::getCount), DataComponentPatch.CODEC.optionalFieldOf("components", DataComponentPatch.EMPTY).forGetter((itemstack) -> {
                 return itemstack.components.asPatch();
             })).apply(instance, ItemStack::new);
         });
     });
     public static final Codec<ItemStack> SINGLE_ITEM_CODEC = Codec.lazyInitialized(() -> {
-        return RecordCodecBuilder.create((instance) -> {
+        return RecordCodecBuilder.<ItemStack>create((instance) -> { // CraftBukkit - decompile error
             return instance.group(Item.CODEC.fieldOf("id").forGetter(ItemStack::getItemHolder), DataComponentPatch.CODEC.optionalFieldOf("components", DataComponentPatch.EMPTY).forGetter((itemstack) -> {
                 return itemstack.components.asPatch();
             })).apply(instance, (holder, datacomponentpatch) -> {
@@ -132,19 +166,25 @@
             if (i <= 0) {
                 return ItemStack.EMPTY;
             } else {
-                Holder<Item> holder = (Holder) null.ITEM_STREAM_CODEC.decode(registryfriendlybytebuf);
+                Holder<Item> holder = (Holder) ITEM_STREAM_CODEC.decode(registryfriendlybytebuf); // CraftBukkit - decompile error
                 DataComponentPatch datacomponentpatch = (DataComponentPatch) DataComponentPatch.STREAM_CODEC.decode(registryfriendlybytebuf);
 
-                return new ItemStack(holder, i, datacomponentpatch);
+                // CraftBukkit start
+                ItemStack itemstack = new ItemStack(holder, i, datacomponentpatch);
+                if (!datacomponentpatch.isEmpty()) {
+                    CraftItemStack.setItemMeta(itemstack, CraftItemStack.getItemMeta(itemstack));
+                }
+                return itemstack;
+                // CraftBukkit end
             }
         }
 
         public void encode(RegistryFriendlyByteBuf registryfriendlybytebuf, ItemStack itemstack) {
-            if (itemstack.isEmpty()) {
+            if (itemstack.isEmpty() || itemstack.getItem() == null) { // CraftBukkit - NPE fix itemstack.getItem()
                 registryfriendlybytebuf.writeVarInt(0);
             } else {
                 registryfriendlybytebuf.writeVarInt(itemstack.getCount());
-                null.ITEM_STREAM_CODEC.encode(registryfriendlybytebuf, itemstack.getItemHolder());
+                ITEM_STREAM_CODEC.encode(registryfriendlybytebuf, itemstack.getItemHolder()); // CraftBukkit - decompile error
                 DataComponentPatch.STREAM_CODEC.encode(registryfriendlybytebuf, itemstack.components.asPatch());
             }
         }
@@ -187,7 +227,7 @@
 
         return dataresult.isError() ? dataresult.map((unit) -> {
             return itemstack;
-        }) : (itemstack.getCount() > itemstack.getMaxStackSize() ? DataResult.error(() -> {
+        }) : (itemstack.getCount() > itemstack.getMaxStackSize() ? DataResult.<ItemStack>error(() -> { // CraftBukkit - decompile error
             int i = itemstack.getCount();
 
             return "Item stack with stack size of " + i + " was larger than maximum: " + itemstack.getMaxStackSize();
@@ -294,8 +334,9 @@
                 j = itemstack.getMaxStackSize();
             } while (i <= j);
 
+            int finalI = i, finalJ = j; // CraftBukkit - decompile error
             return DataResult.error(() -> {
-                return "Item stack with count of " + i + " was larger than maximum: " + j;
+                return "Item stack with count of " + finalI + " was larger than maximum: " + finalJ; // CraftBukkit - decompile error
             });
         }
     }
@@ -377,15 +418,173 @@
             return EnumInteractionResult.PASS;
         } else {
             Item item = this.getItem();
-            EnumInteractionResult enuminteractionresult = item.useOn(itemactioncontext);
+            // CraftBukkit start - handle all block place event logic here
+            DataComponentPatch oldData = this.components.asPatch();
+            int oldCount = this.getCount();
+            WorldServer world = (WorldServer) itemactioncontext.getLevel();
+
+            if (!(item instanceof ItemBucket || item instanceof SolidBucketItem)) { // if not bucket
+                world.captureBlockStates = true;
+                // special case bonemeal
+                if (item == Items.BONE_MEAL) {
+                    world.captureTreeGeneration = true;
+                }
+            }
+            EnumInteractionResult enuminteractionresult;
+            try {
+                enuminteractionresult = item.useOn(itemactioncontext);
+            } finally {
+                world.captureBlockStates = false;
+            }
+            DataComponentPatch newData = this.components.asPatch();
+            int newCount = this.getCount();
+            this.setCount(oldCount);
+            this.restorePatch(oldData);
+            if (enuminteractionresult.consumesAction() && world.captureTreeGeneration && world.capturedBlockStates.size() > 0) {
+                world.captureTreeGeneration = false;
+                Location location = CraftLocation.toBukkit(blockposition, world.getWorld());
+                TreeType treeType = BlockSapling.treeType;
+                BlockSapling.treeType = null;
+                List<CraftBlockState> blocks = new java.util.ArrayList<>(world.capturedBlockStates.values());
+                world.capturedBlockStates.clear();
+                StructureGrowEvent structureEvent = null;
+                if (treeType != null) {
+                    boolean isBonemeal = getItem() == Items.BONE_MEAL;
+                    structureEvent = new StructureGrowEvent(location, treeType, isBonemeal, (Player) entityhuman.getBukkitEntity(), (List< BlockState>) (List<? extends BlockState>) blocks);
+                    org.bukkit.Bukkit.getPluginManager().callEvent(structureEvent);
+                }
+
+                BlockFertilizeEvent fertilizeEvent = new BlockFertilizeEvent(CraftBlock.at(world, blockposition), (Player) entityhuman.getBukkitEntity(), (List< BlockState>) (List<? extends 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(), oldData)) {
+                        this.restorePatch(newData);
+                        this.setCount(newCount);
+                    }
+                    for (CraftBlockState blockstate : blocks) {
+                        // SPIGOT-7572 - Move fix for SPIGOT-7248 to CapturedBlockState, to allow bees in bee nest
+                        CapturedBlockState.setBlockState(blockstate);
+                    }
+                    entityhuman.awardStat(StatisticList.ITEM_USED.get(item)); // SPIGOT-7236 - award stat
+                }
+
+                ItemSign.openSign = null; // SPIGOT-6758 - Reset on early return
+                return enuminteractionresult;
+            }
+            world.captureTreeGeneration = false;
 
             if (entityhuman != null && enuminteractionresult instanceof EnumInteractionResult.d) {
                 EnumInteractionResult.d enuminteractionresult_d = (EnumInteractionResult.d) enuminteractionresult;
 
                 if (enuminteractionresult_d.wasItemInteraction()) {
-                    entityhuman.awardStat(StatisticList.ITEM_USED.get(item));
+                    EnumHand enumhand = itemactioncontext.getHand();
+                    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.event.CraftEventFactory.callBlockMultiPlaceEvent(world, entityhuman, enumhand, blocks, blockposition.getX(), blockposition.getY(), blockposition.getZ());
+                    } else if (blocks.size() == 1) {
+                        placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEvent(world, entityhuman, enumhand, blocks.get(0), blockposition.getX(), blockposition.getY(), blockposition.getZ());
+                    }
+
+                    if (placeEvent != null && (placeEvent.isCancelled() || !placeEvent.canBuild())) {
+                        enuminteractionresult = EnumInteractionResult.FAIL; // cancel placement
+                        // PAIL: Remove this when MC-99075 fixed
+                        placeEvent.getPlayer().updateInventory();
+                        // 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
+                        BlockPosition placedPos = ((CraftBlock) placeEvent.getBlock()).getPosition();
+                        for (EnumDirection dir : EnumDirection.values()) {
+                            ((EntityPlayer) entityhuman).connection.send(new PacketPlayOutBlockChange(world, placedPos.relative(dir)));
+                        }
+                        ItemSign.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(), oldData)) {
+                            this.restorePatch(newData);
+                            this.setCount(newCount);
+                        }
+
+                        for (Map.Entry<BlockPosition, TileEntity> e : world.capturedTileEntities.entrySet()) {
+                            world.setBlockEntity(e.getValue());
+                        }
+
+                        for (BlockState blockstate : blocks) {
+                            int updateFlag = ((CraftBlockState) blockstate).getFlag();
+                            IBlockData oldBlock = ((CraftBlockState) blockstate).getHandle();
+                            BlockPosition newblockposition = ((CraftBlockState) blockstate).getPosition();
+                            IBlockData block = world.getBlockState(newblockposition);
+
+                            if (!(block.getBlock() instanceof BlockTileEntity)) { // Containers get placed automatically
+                                block.onPlace(world, newblockposition, oldBlock, true, itemactioncontext);
+                            }
+
+                            world.notifyAndUpdatePhysics(newblockposition, null, oldBlock, block, world.getBlockState(newblockposition), updateFlag, 512); // 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
+                            BlockPosition bp = blockposition;
+                            if (!world.getBlockState(blockposition).canBeReplaced()) {
+                                if (!world.getBlockState(blockposition).isSolid()) {
+                                    bp = null;
+                                } else {
+                                    bp = bp.relative(itemactioncontext.getClickedFace());
+                                }
+                            }
+                            if (bp != null) {
+                                TileEntity te = world.getBlockEntity(bp);
+                                if (te instanceof TileEntitySkull) {
+                                    BlockWitherSkull.checkSpawn(world, bp, (TileEntitySkull) te);
+                                }
+                            }
+                        }
+
+                        // SPIGOT-4678
+                        if (this.item instanceof ItemSign && ItemSign.openSign != null) {
+                            try {
+                                if (world.getBlockEntity(ItemSign.openSign) instanceof TileEntitySign tileentitysign) {
+                                    if (world.getBlockState(ItemSign.openSign).getBlock() instanceof BlockSign blocksign) {
+                                        blocksign.openTextEdit(entityhuman, tileentitysign, true, org.bukkit.event.player.PlayerSignOpenEvent.Cause.PLACE); // Craftbukkit
+                                    }
+                                }
+                            } finally {
+                                ItemSign.openSign = null;
+                            }
+                        }
+
+                        // SPIGOT-7315: Moved from BlockBed#setPlacedBy
+                        if (placeEvent != null && this.item instanceof ItemBed) {
+                            BlockPosition position = ((CraftBlock) placeEvent.getBlock()).getPosition();
+                            IBlockData blockData = world.getBlockState(position);
+
+                            if (blockData.getBlock() instanceof BlockBed) {
+                                world.blockUpdated(position, Blocks.AIR);
+                                blockData.updateNeighbourShapes(world, position, 3);
+                            }
+                        }
+
+                        // SPIGOT-1288 - play sound stripped from ItemBlock
+                        if (this.item instanceof ItemBlock) {
+                            SoundEffectType soundeffecttype = ((ItemBlock) this.item).getBlock().defaultBlockState().getSoundType(); // TODO: not strictly correct, however currently only affects decorated pots
+                            world.playSound(entityhuman, blockposition, soundeffecttype.getPlaceSound(), SoundCategory.BLOCKS, (soundeffecttype.getVolume() + 1.0F) / 2.0F, soundeffecttype.getPitch() * 0.8F);
+                        }
+
+                        entityhuman.awardStat(StatisticList.ITEM_USED.get(item));
+                    }
                 }
             }
+            world.capturedTileEntities.clear();
+            world.capturedBlockStates.clear();
+            // CraftBukkit end
 
             return enuminteractionresult;
         }
@@ -492,6 +691,21 @@
 
     public void hurtAndBreak(int i, WorldServer worldserver, @Nullable EntityPlayer entityplayer, Consumer<Item> consumer) {
         int j = this.processDurabilityChange(i, worldserver, entityplayer);
+        // CraftBukkit start
+        if (entityplayer != null) {
+            PlayerItemDamageEvent event = new PlayerItemDamageEvent(entityplayer.getBukkitEntity(), CraftItemStack.asCraftMirror(this), j);
+            event.getPlayer().getServer().getPluginManager().callEvent(event);
+
+            if (j != event.getDamage() || event.isCancelled()) {
+                event.getPlayer().updateInventory();
+            }
+            if (event.isCancelled()) {
+                return;
+            }
+
+            j = event.getDamage();
+        }
+        // CraftBukkit end
 
         if (j != 0) {
             this.applyDamage(this.getDamageValue() + j, entityplayer, consumer);
@@ -511,6 +725,11 @@
         this.setDamageValue(i);
         if (this.isBroken()) {
             Item item = this.getItem();
+            // CraftBukkit start - Check for item breaking
+            if (this.count == 1 && entityplayer != null) {
+                org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemBreakEvent(entityplayer, this);
+            }
+            // CraftBukkit end
 
             this.shrink(1);
             consumer.accept(item);
@@ -770,6 +989,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<? super T> datacomponenttype, @Nullable T t0) {
         return this.components.set(datacomponenttype, t0);
@@ -858,7 +1083,7 @@
     }
 
     private <T extends TooltipProvider> void addToTooltip(DataComponentType<T> datacomponenttype, Item.b item_b, Consumer<IChatBaseComponent> consumer, TooltipFlag tooltipflag) {
-        T t0 = (TooltipProvider) this.get(datacomponenttype);
+        T t0 = (T) this.get(datacomponenttype); // CraftBukkit - decompile error
 
         if (t0 != null) {
             t0.addToTooltip(item_b, consumer, tooltipflag);
@@ -1091,6 +1316,13 @@
         EnchantmentManager.forEachModifier(this, enumitemslot, biconsumer);
     }
 
+    // CraftBukkit start
+    @Deprecated
+    public void setItem(Item item) {
+        this.item = item;
+    }
+    // CraftBukkit end
+
     public IChatBaseComponent getDisplayName() {
         IChatMutableComponent ichatmutablecomponent = IChatBaseComponent.empty().append(this.getHoverName());
 
@@ -1153,7 +1385,7 @@
     }
 
     public void consume(int i, @Nullable EntityLiving entityliving) {
-        if (entityliving == null || !entityliving.hasInfiniteMaterials()) {
+        if ((entityliving == null || !entityliving.hasInfiniteMaterials()) && this != ItemStack.EMPTY) { // CraftBukkit
             this.shrink(i);
         }
 
