--- a/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java
+++ b/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java
@@ -20,9 +_,32 @@
     @Override
     protected ItemStack execute(BlockSource blockSource, ItemStack item) {
         ServerLevel serverLevel = blockSource.level();
+
+        // CraftBukkit start
+        org.bukkit.block.Block bukkitBlock = org.bukkit.craftbukkit.block.CraftBlock.at(serverLevel, blockSource.pos());
+        org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item); // Paper - ignore stack size on damageable items
+        org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0));
+        if (!DispenserBlock.eventFired) {
+            serverLevel.getCraftServer().getPluginManager().callEvent(event);
+        }
+
+        if (event.isCancelled()) {
+            return item;
+        }
+
+        if (!event.getItem().equals(craftItem)) {
+            // Chain to handler for new item
+            ItemStack eventStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem());
+            DispenseItemBehavior dispenseBehavior = DispenserBlock.getDispenseBehavior(blockSource, eventStack); // Paper - Fix NPE with equippable and items without behavior
+            if (dispenseBehavior != DispenseItemBehavior.NOOP && dispenseBehavior != this) {
+                dispenseBehavior.dispense(blockSource, eventStack);
+                return item;
+            }
+        }
+        // CraftBukkit end
         if (!serverLevel.isClientSide()) {
             BlockPos blockPos = blockSource.pos().relative(blockSource.state().getValue(DispenserBlock.FACING));
-            this.setSuccess(tryShearBeehive(serverLevel, blockPos) || tryShearLivingEntity(serverLevel, blockPos, item));
+            this.setSuccess(tryShearBeehive(serverLevel, blockPos) || tryShearLivingEntity(serverLevel, blockPos, item, bukkitBlock, craftItem)); // CraftBukkit
             if (this.isSuccess()) {
                 item.hurtAndBreak(1, serverLevel, null, item1 -> {});
             }
@@ -50,10 +_,18 @@
         return false;
     }
 
-    private static boolean tryShearLivingEntity(ServerLevel level, BlockPos pos, ItemStack stack) {
+    private static boolean tryShearLivingEntity(ServerLevel level, BlockPos pos, ItemStack stack, org.bukkit.block.Block bukkitBlock, org.bukkit.craftbukkit.inventory.CraftItemStack craftItem) { // CraftBukkit - add args
         for (LivingEntity livingEntity : level.getEntitiesOfClass(LivingEntity.class, new AABB(pos), EntitySelector.NO_SPECTATORS)) {
             if (livingEntity instanceof Shearable shearable && shearable.readyForShearing()) {
-                shearable.shear(level, SoundSource.BLOCKS, stack);
+                // CraftBukkit start
+                // Paper start - Add drops to shear events
+                org.bukkit.event.block.BlockShearEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockShearEntityEvent(livingEntity, bukkitBlock, craftItem, shearable.generateDefaultDrops(level, stack));
+                if (event.isCancelled()) {
+                    // Paper end - Add drops to shear events
+                    continue;
+                }
+                // CraftBukkit end
+                shearable.shear(level, SoundSource.BLOCKS, stack, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops())); // Paper - Add drops to shear events
                 level.gameEvent(null, GameEvent.SHEAR, pos);
                 return true;
             }
