--- a/net/minecraft/world/level/block/entity/BrushableBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/BrushableBlockEntity.java
@@ -46,12 +_,12 @@
     private int brushCount;
     private long brushCountResetsAtTick;
     private long coolDownEndsAtTick;
-    private ItemStack item = ItemStack.EMPTY;
+    public ItemStack item = ItemStack.EMPTY;
     @Nullable
     private Direction hitDirection;
     @Nullable
-    private ResourceKey<LootTable> lootTable;
-    private long lootTableSeed;
+    public ResourceKey<LootTable> lootTable;
+    public long lootTableSeed;
 
     public BrushableBlockEntity(BlockPos pos, BlockState blockState) {
         super(BlockEntityType.BRUSHABLE_BLOCK, pos, blockState);
@@ -67,9 +_,26 @@
             return false;
         } else {
             this.coolDownEndsAtTick = startTick + 10L;
+            // Paper start - EntityChangeBlockEvent
+            // The vanilla logic here is *so* backwards, we'd be moving basically *all* following calls down.
+            // Instead, compute vanilla ourselves up here and just replace the below usages with our computed values for a free diff-on-change.
+            final int currentCompletionStage = this.getCompletionState();
+            final boolean enoughBrushesToBreak = ++this.brushCount >= REQUIRED_BRUSHES_TO_BREAK;
+            final int nextCompletionStage = this.getCompletionState();
+            final boolean differentCompletionStages = currentCompletionStage != nextCompletionStage;
+            final BlockState nextBrokenBlockState = this.getBlockState().setValue(BlockStateProperties.DUSTED, nextCompletionStage);
+            if (enoughBrushesToBreak || differentCompletionStages) {
+                if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(
+                    brusher, this.worldPosition, enoughBrushesToBreak ? computeTurnsTo().defaultBlockState() : nextBrokenBlockState
+                )) {
+                    brushCount--;
+                    return false;
+                }
+            }
+            // Paper end - EntityChangeBlockEvent
             this.unpackLootTable(level, brusher, stack);
-            int completionState = this.getCompletionState();
-            if (++this.brushCount >= 10) {
+            int completionState = currentCompletionStage; // Paper - EntityChangeBlockEvent - use precomputed - diff on change
+            if (enoughBrushesToBreak) { // Paper - EntityChangeBlockEvent - use precomputed - diff on change
                 this.brushingCompleted(level, brusher, stack);
                 return true;
             } else {
@@ -77,7 +_,7 @@
                 int completionState1 = this.getCompletionState();
                 if (completionState != completionState1) {
                     BlockState blockState = this.getBlockState();
-                    BlockState blockState1 = blockState.setValue(BlockStateProperties.DUSTED, completionState1);
+                    BlockState blockState1 = nextBrokenBlockState; // Paper - EntityChangeBlockEvent - use precomputed - diff on change
                     level.setBlock(this.getBlockPos(), blockState1, 3);
                 }
 
@@ -118,6 +_,11 @@
         this.dropContent(level, brusher, stack);
         BlockState blockState = this.getBlockState();
         level.levelEvent(3008, this.getBlockPos(), Block.getId(blockState));
+    // Paper start - EntityChangeEvent - extract result block logic
+        this.brushingCompleteUpdateBlock(this.computeTurnsTo());
+    }
+    private Block computeTurnsTo() {
+    // Paper end - EntityChangeEvent - extract result block logic
         Block turnsInto;
         if (this.getBlockState().getBlock() instanceof BrushableBlock brushableBlock) {
             turnsInto = brushableBlock.getTurnsInto();
@@ -125,6 +_,11 @@
             turnsInto = Blocks.AIR;
         }
 
+    // Paper start - EntityChangeEvent - extract result block logic
+        return turnsInto;
+    }
+    public void brushingCompleteUpdateBlock(final Block turnsInto) {
+    // Paper end - EntityChangeEvent - extract result block logic
         level.setBlock(this.worldPosition, turnsInto.defaultBlockState(), 3);
     }
 
@@ -141,7 +_,12 @@
             double d5 = blockPos.getZ() + 0.5 * d1 + d2;
             ItemEntity itemEntity = new ItemEntity(level, d3, d4, d5, this.item.split(level.random.nextInt(21) + 10));
             itemEntity.setDeltaMovement(Vec3.ZERO);
-            level.addFreshEntity(itemEntity);
+            // CraftBukkit start
+            if (brusher instanceof final ServerPlayer serverPlayer) {
+                org.bukkit.block.Block bblock = org.bukkit.craftbukkit.block.CraftBlock.at(this.level, this.worldPosition);
+                org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDropItemEvent(bblock, bblock.getState(), serverPlayer, java.util.List.of(itemEntity));
+            }
+            // CraftBukkit end
             this.item = ItemStack.EMPTY;
         }
     }
