--- a/net/minecraft/world/level/block/Block.java
+++ b/net/minecraft/world/level/block/Block.java
@@ -7,6 +7,7 @@
 import com.mojang.logging.LogUtils;
 import com.mojang.serialization.MapCodec;
 import it.unimi.dsi.fastutil.objects.Object2ByteLinkedOpenHashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.function.Function;
 import java.util.function.Supplier;
@@ -63,19 +64,16 @@
 import org.slf4j.Logger;
 
 public class Block extends BlockBehaviour implements ItemLike {
+
     public static final MapCodec<Block> CODEC = simpleCodec(Block::new);
     private static final Logger LOGGER = LogUtils.getLogger();
-    private final Holder.Reference<Block> builtInRegistryHolder = BuiltInRegistries.BLOCK.createIntrusiveHolder(this);
+    private final Holder.Reference<Block> builtInRegistryHolder;
     public static final IdMapper<BlockState> BLOCK_STATE_REGISTRY = new IdMapper<>();
-    private static final LoadingCache<VoxelShape, Boolean> SHAPE_FULL_BLOCK_CACHE = CacheBuilder.newBuilder()
-        .maximumSize(512L)
-        .weakKeys()
-        .build(new CacheLoader<VoxelShape, Boolean>() {
-            @Override
-            public Boolean load(VoxelShape voxelShape) {
-                return !Shapes.joinIsNotEmpty(Shapes.block(), voxelShape, BooleanOp.NOT_SAME);
-            }
-        });
+    private static final LoadingCache<VoxelShape, Boolean> SHAPE_FULL_BLOCK_CACHE = CacheBuilder.newBuilder().maximumSize(512L).weakKeys().build(new CacheLoader<VoxelShape, Boolean>() {
+        public Boolean load(VoxelShape voxelshape) {
+            return !Shapes.joinIsNotEmpty(Shapes.block(), voxelshape, BooleanOp.NOT_SAME);
+        }
+    });
     public static final int UPDATE_NEIGHBORS = 1;
     public static final int UPDATE_CLIENTS = 2;
     public static final int UPDATE_INVISIBLE = 4;
@@ -91,56 +89,81 @@
     public static final int UPDATE_LIMIT = 512;
     protected final StateDefinition<Block, BlockState> stateDefinition;
     private BlockState defaultBlockState;
+    // Paper start
+    public final boolean isDestroyable() {
+        return io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits ||
+            this != Blocks.BEDROCK &&
+            this != Blocks.END_PORTAL_FRAME &&
+            this != Blocks.END_PORTAL &&
+            this != Blocks.END_GATEWAY &&
+            this != Blocks.COMMAND_BLOCK &&
+            this != Blocks.REPEATING_COMMAND_BLOCK &&
+            this != Blocks.CHAIN_COMMAND_BLOCK &&
+            this != Blocks.BARRIER &&
+            this != Blocks.STRUCTURE_BLOCK &&
+            this != Blocks.JIGSAW;
+    }
+    public co.aikar.timings.Timing timing;
+    public co.aikar.timings.Timing getTiming() {
+        if (timing == null) {
+            timing = co.aikar.timings.MinecraftTimings.getBlockTiming(this);
+        }
+        return timing;
+    }
+    // Paper end
     @Nullable
     private String descriptionId;
     @Nullable
     private Item item;
     private static final int CACHE_SIZE = 2048;
-    private static final ThreadLocal<Object2ByteLinkedOpenHashMap<Block.BlockStatePairKey>> OCCLUSION_CACHE = ThreadLocal.withInitial(
-        () -> {
-            Object2ByteLinkedOpenHashMap<Block.BlockStatePairKey> object2ByteLinkedOpenHashMap = new Object2ByteLinkedOpenHashMap<Block.BlockStatePairKey>(
-                2048, 0.25F
-            ) {
-                protected void rehash(int i) {
-                }
-            };
-            object2ByteLinkedOpenHashMap.defaultReturnValue((byte)127);
-            return object2ByteLinkedOpenHashMap;
-        }
-    );
+    private static final ThreadLocal<Object2ByteLinkedOpenHashMap<Block.BlockStatePairKey>> OCCLUSION_CACHE = ThreadLocal.withInitial(() -> {
+        Object2ByteLinkedOpenHashMap<Block.BlockStatePairKey> object2bytelinkedopenhashmap = new Object2ByteLinkedOpenHashMap<Block.BlockStatePairKey>(2048, 0.25F) {
+            protected void rehash(int i) {}
+        };
+
+        object2bytelinkedopenhashmap.defaultReturnValue((byte) 127);
+        return object2bytelinkedopenhashmap;
+    });
 
     @Override
     protected MapCodec<? extends Block> codec() {
-        return CODEC;
+        return Block.CODEC;
     }
 
     public static int getId(@Nullable BlockState state) {
         if (state == null) {
             return 0;
         } else {
-            int i = BLOCK_STATE_REGISTRY.getId(state);
+            int i = Block.BLOCK_STATE_REGISTRY.getId(state);
+
             return i == -1 ? 0 : i;
         }
     }
 
     public static BlockState stateById(int stateId) {
-        BlockState blockState = BLOCK_STATE_REGISTRY.byId(stateId);
-        return blockState == null ? Blocks.AIR.defaultBlockState() : blockState;
+        BlockState iblockdata = (BlockState) Block.BLOCK_STATE_REGISTRY.byId(stateId);
+
+        return iblockdata == null ? Blocks.AIR.defaultBlockState() : iblockdata;
     }
 
     public static Block byItem(@Nullable Item item) {
-        return item instanceof BlockItem ? ((BlockItem)item).getBlock() : Blocks.AIR;
+        return item instanceof BlockItem ? ((BlockItem) item).getBlock() : Blocks.AIR;
     }
 
     public static BlockState pushEntitiesUp(BlockState from, BlockState to, LevelAccessor world, BlockPos pos) {
-        VoxelShape voxelShape = Shapes.joinUnoptimized(from.getCollisionShape(world, pos), to.getCollisionShape(world, pos), BooleanOp.ONLY_SECOND)
-            .move((double)pos.getX(), (double)pos.getY(), (double)pos.getZ());
-        if (voxelShape.isEmpty()) {
+        VoxelShape voxelshape = Shapes.joinUnoptimized(from.getCollisionShape(world, pos), to.getCollisionShape(world, pos), BooleanOp.ONLY_SECOND).move((double) pos.getX(), (double) pos.getY(), (double) pos.getZ());
+
+        if (voxelshape.isEmpty()) {
             return to;
         } else {
-            for (Entity entity : world.getEntities(null, voxelShape.bounds())) {
-                double d = Shapes.collide(Direction.Axis.Y, entity.getBoundingBox().move(0.0, 1.0, 0.0), List.of(voxelShape), -1.0);
-                entity.teleportRelative(0.0, 1.0 + d, 0.0);
+            List<Entity> list = world.getEntities((Entity) null, voxelshape.bounds());
+            Iterator iterator = list.iterator();
+
+            while (iterator.hasNext()) {
+                Entity entity = (Entity) iterator.next();
+                double d0 = Shapes.collide(Direction.Axis.Y, entity.getBoundingBox().move(0.0D, 1.0D, 0.0D), List.of(voxelshape), -1.0D);
+
+                entity.teleportRelative(0.0D, 1.0D + d0, 0.0D);
             }
 
             return to;
@@ -148,84 +171,91 @@
     }
 
     public static VoxelShape box(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
-        return Shapes.box(minX / 16.0, minY / 16.0, minZ / 16.0, maxX / 16.0, maxY / 16.0, maxZ / 16.0);
+        return Shapes.box(minX / 16.0D, minY / 16.0D, minZ / 16.0D, maxX / 16.0D, maxY / 16.0D, maxZ / 16.0D);
     }
 
     public static BlockState updateFromNeighbourShapes(BlockState state, LevelAccessor world, BlockPos pos) {
-        BlockState blockState = state;
-        BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
+        BlockState iblockdata1 = state;
+        BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos();
+        Direction[] aenumdirection = Block.UPDATE_SHAPE_ORDER;
+        int i = aenumdirection.length;
 
-        for (Direction direction : UPDATE_SHAPE_ORDER) {
-            mutableBlockPos.setWithOffset(pos, direction);
-            blockState = blockState.updateShape(direction, world.getBlockState(mutableBlockPos), world, pos, mutableBlockPos);
+        for (int j = 0; j < i; ++j) {
+            Direction enumdirection = aenumdirection[j];
+
+            blockposition_mutableblockposition.setWithOffset(pos, enumdirection);
+            iblockdata1 = iblockdata1.updateShape(enumdirection, world.getBlockState(blockposition_mutableblockposition), world, pos, blockposition_mutableblockposition);
         }
 
-        return blockState;
+        return iblockdata1;
     }
 
     public static void updateOrDestroy(BlockState state, BlockState newState, LevelAccessor world, BlockPos pos, int flags) {
-        updateOrDestroy(state, newState, world, pos, flags, 512);
+        Block.updateOrDestroy(state, newState, world, pos, flags, 512);
     }
 
     public static void updateOrDestroy(BlockState state, BlockState newState, LevelAccessor world, BlockPos pos, int flags, int maxUpdateDepth) {
         if (newState != state) {
             if (newState.isAir()) {
                 if (!world.isClientSide()) {
-                    world.destroyBlock(pos, (flags & 32) == 0, null, maxUpdateDepth);
+                    world.destroyBlock(pos, (flags & 32) == 0, (Entity) null, maxUpdateDepth);
                 }
             } else {
                 world.setBlock(pos, newState, flags & -33, maxUpdateDepth);
             }
         }
+
     }
 
     public Block(BlockBehaviour.Properties settings) {
         super(settings);
-        StateDefinition.Builder<Block, BlockState> builder = new StateDefinition.Builder<>(this);
-        this.createBlockStateDefinition(builder);
-        this.stateDefinition = builder.create(Block::defaultBlockState, BlockState::new);
-        this.registerDefaultState(this.stateDefinition.any());
+        this.builtInRegistryHolder = BuiltInRegistries.BLOCK.createIntrusiveHolder(this);
+        StateDefinition.Builder<Block, BlockState> blockstatelist_a = new StateDefinition.Builder<>(this);
+
+        this.createBlockStateDefinition(blockstatelist_a);
+        this.stateDefinition = blockstatelist_a.create(Block::defaultBlockState, BlockState::new);
+        this.registerDefaultState((BlockState) this.stateDefinition.any());
         if (SharedConstants.IS_RUNNING_IN_IDE) {
-            String string = this.getClass().getSimpleName();
-            if (!string.endsWith("Block")) {
-                LOGGER.error("Block classes should end with Block and {} doesn't.", string);
+            String s = this.getClass().getSimpleName();
+
+            if (!s.endsWith("Block")) {
+                Block.LOGGER.error("Block classes should end with Block and {} doesn't.", s);
             }
         }
+
     }
 
     public static boolean isExceptionForConnection(BlockState state) {
-        return state.getBlock() instanceof LeavesBlock
-            || state.is(Blocks.BARRIER)
-            || state.is(Blocks.CARVED_PUMPKIN)
-            || state.is(Blocks.JACK_O_LANTERN)
-            || state.is(Blocks.MELON)
-            || state.is(Blocks.PUMPKIN)
-            || state.is(BlockTags.SHULKER_BOXES);
+        return state.getBlock() instanceof LeavesBlock || state.is(Blocks.BARRIER) || state.is(Blocks.CARVED_PUMPKIN) || state.is(Blocks.JACK_O_LANTERN) || state.is(Blocks.MELON) || state.is(Blocks.PUMPKIN) || state.is(BlockTags.SHULKER_BOXES);
     }
 
     public static boolean shouldRenderFace(BlockState state, BlockGetter world, BlockPos pos, Direction side, BlockPos otherPos) {
-        BlockState blockState = world.getBlockState(otherPos);
-        if (state.skipRendering(blockState, side)) {
+        BlockState iblockdata1 = world.getBlockState(otherPos);
+
+        if (state.skipRendering(iblockdata1, side)) {
             return false;
-        } else if (blockState.canOcclude()) {
-            Block.BlockStatePairKey blockStatePairKey = new Block.BlockStatePairKey(state, blockState, side);
-            Object2ByteLinkedOpenHashMap<Block.BlockStatePairKey> object2ByteLinkedOpenHashMap = OCCLUSION_CACHE.get();
-            byte b = object2ByteLinkedOpenHashMap.getAndMoveToFirst(blockStatePairKey);
-            if (b != 127) {
-                return b != 0;
+        } else if (iblockdata1.canOcclude()) {
+            Block.BlockStatePairKey block_a = new Block.BlockStatePairKey(state, iblockdata1, side);
+            Object2ByteLinkedOpenHashMap<Block.BlockStatePairKey> object2bytelinkedopenhashmap = (Object2ByteLinkedOpenHashMap) Block.OCCLUSION_CACHE.get();
+            byte b0 = object2bytelinkedopenhashmap.getAndMoveToFirst(block_a);
+
+            if (b0 != 127) {
+                return b0 != 0;
             } else {
-                VoxelShape voxelShape = state.getFaceOcclusionShape(world, pos, side);
-                if (voxelShape.isEmpty()) {
+                VoxelShape voxelshape = state.getFaceOcclusionShape(world, pos, side);
+
+                if (voxelshape.isEmpty()) {
                     return true;
                 } else {
-                    VoxelShape voxelShape2 = blockState.getFaceOcclusionShape(world, otherPos, side.getOpposite());
-                    boolean bl = Shapes.joinIsNotEmpty(voxelShape, voxelShape2, BooleanOp.ONLY_FIRST);
-                    if (object2ByteLinkedOpenHashMap.size() == 2048) {
-                        object2ByteLinkedOpenHashMap.removeLastByte();
+                    VoxelShape voxelshape1 = iblockdata1.getFaceOcclusionShape(world, otherPos, side.getOpposite());
+                    boolean flag = Shapes.joinIsNotEmpty(voxelshape, voxelshape1, BooleanOp.ONLY_FIRST);
+
+                    if (object2bytelinkedopenhashmap.size() == 2048) {
+                        object2bytelinkedopenhashmap.removeLastByte();
                     }
 
-                    object2ByteLinkedOpenHashMap.putAndMoveToFirst(blockStatePairKey, (byte)(bl ? 1 : 0));
-                    return bl;
+                    object2bytelinkedopenhashmap.putAndMoveToFirst(block_a, (byte) (flag ? 1 : 0));
+                    return flag;
                 }
             }
         } else {
@@ -238,125 +268,177 @@
     }
 
     public static boolean canSupportCenter(LevelReader world, BlockPos pos, Direction side) {
-        BlockState blockState = world.getBlockState(pos);
-        return (side != Direction.DOWN || !blockState.is(BlockTags.UNSTABLE_BOTTOM_CENTER)) && blockState.isFaceSturdy(world, pos, side, SupportType.CENTER);
+        BlockState iblockdata = world.getBlockState(pos);
+
+        return side == Direction.DOWN && iblockdata.is(BlockTags.UNSTABLE_BOTTOM_CENTER) ? false : iblockdata.isFaceSturdy(world, pos, side, SupportType.CENTER);
     }
 
     public static boolean isFaceFull(VoxelShape shape, Direction side) {
-        VoxelShape voxelShape = shape.getFaceShape(side);
-        return isShapeFullBlock(voxelShape);
+        VoxelShape voxelshape1 = shape.getFaceShape(side);
+
+        return Block.isShapeFullBlock(voxelshape1);
     }
 
     public static boolean isShapeFullBlock(VoxelShape shape) {
-        return SHAPE_FULL_BLOCK_CACHE.getUnchecked(shape);
+        return shape.isFullBlock(); // Paper - optimise collisions
     }
 
-    public void animateTick(BlockState state, Level world, BlockPos pos, RandomSource random) {
-    }
+    public void animateTick(BlockState state, Level world, BlockPos pos, RandomSource random) {}
 
-    public void destroy(LevelAccessor world, BlockPos pos, BlockState state) {
-    }
+    public void destroy(LevelAccessor world, BlockPos pos, BlockState state) {}
 
     public static List<ItemStack> getDrops(BlockState state, ServerLevel world, BlockPos pos, @Nullable BlockEntity blockEntity) {
-        LootParams.Builder builder = new LootParams.Builder(world)
-            .withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(pos))
-            .withParameter(LootContextParams.TOOL, ItemStack.EMPTY)
-            .withOptionalParameter(LootContextParams.BLOCK_ENTITY, blockEntity);
-        return state.getDrops(builder);
+        LootParams.Builder lootparams_a = (new LootParams.Builder(world)).withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(pos)).withParameter(LootContextParams.TOOL, ItemStack.EMPTY).withOptionalParameter(LootContextParams.BLOCK_ENTITY, blockEntity);
+
+        return state.getDrops(lootparams_a);
     }
 
-    public static List<ItemStack> getDrops(
-        BlockState state, ServerLevel world, BlockPos pos, @Nullable BlockEntity blockEntity, @Nullable Entity entity, ItemStack stack
-    ) {
-        LootParams.Builder builder = new LootParams.Builder(world)
-            .withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(pos))
-            .withParameter(LootContextParams.TOOL, stack)
-            .withOptionalParameter(LootContextParams.THIS_ENTITY, entity)
-            .withOptionalParameter(LootContextParams.BLOCK_ENTITY, blockEntity);
-        return state.getDrops(builder);
+    public static List<ItemStack> getDrops(BlockState state, ServerLevel world, BlockPos pos, @Nullable BlockEntity blockEntity, @Nullable Entity entity, ItemStack stack) {
+        LootParams.Builder lootparams_a = (new LootParams.Builder(world)).withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(pos)).withParameter(LootContextParams.TOOL, stack).withOptionalParameter(LootContextParams.THIS_ENTITY, entity).withOptionalParameter(LootContextParams.BLOCK_ENTITY, blockEntity);
+
+        return state.getDrops(lootparams_a);
     }
 
     public static void dropResources(BlockState state, Level world, BlockPos pos) {
         if (world instanceof ServerLevel) {
-            getDrops(state, (ServerLevel)world, pos, null).forEach(stack -> popResource(world, pos, stack));
-            state.spawnAfterBreak((ServerLevel)world, pos, ItemStack.EMPTY, true);
+            Block.getDrops(state, (ServerLevel) world, pos, (BlockEntity) null).forEach((itemstack) -> {
+                Block.popResource(world, pos, itemstack);
+            });
+            state.spawnAfterBreak((ServerLevel) world, pos, ItemStack.EMPTY, true);
         }
+
     }
 
     public static void dropResources(BlockState state, LevelAccessor world, BlockPos pos, @Nullable BlockEntity blockEntity) {
         if (world instanceof ServerLevel) {
-            getDrops(state, (ServerLevel)world, pos, blockEntity).forEach(stack -> popResource((ServerLevel)world, pos, stack));
-            state.spawnAfterBreak((ServerLevel)world, pos, ItemStack.EMPTY, true);
+            Block.getDrops(state, (ServerLevel) world, pos, blockEntity).forEach((itemstack) -> {
+                Block.popResource((ServerLevel) world, pos, itemstack);
+            });
+            state.spawnAfterBreak((ServerLevel) world, pos, ItemStack.EMPTY, true);
         }
+
     }
 
+    // Paper start - Add BlockBreakBlockEvent
+    public static boolean dropResources(BlockState state, LevelAccessor levelAccessor, BlockPos pos, @Nullable BlockEntity blockEntity, BlockPos source) {
+        if (levelAccessor instanceof ServerLevel serverLevel) {
+            List<org.bukkit.inventory.ItemStack> items = new java.util.ArrayList<>();
+            for (ItemStack drop : Block.getDrops(state, serverLevel, pos, blockEntity)) {
+                items.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(drop));
+            }
+            Block block = state.getBlock(); // Paper - Properly handle xp dropping
+            io.papermc.paper.event.block.BlockBreakBlockEvent event = new io.papermc.paper.event.block.BlockBreakBlockEvent(org.bukkit.craftbukkit.block.CraftBlock.at(levelAccessor, pos), org.bukkit.craftbukkit.block.CraftBlock.at(levelAccessor, source), items);
+            event.setExpToDrop(block.getExpDrop(state, serverLevel, pos, net.minecraft.world.item.ItemStack.EMPTY, true)); // Paper - Properly handle xp dropping
+            event.callEvent();
+            for (org.bukkit.inventory.ItemStack drop : event.getDrops()) {
+                popResource(serverLevel, pos, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(drop));
+            }
+            state.spawnAfterBreak(serverLevel, pos, ItemStack.EMPTY, false); // Paper - Properly handle xp dropping
+            block.popExperience(serverLevel, pos, event.getExpToDrop()); // Paper - Properly handle xp dropping
+        }
+        return true;
+    }
+    // Paper end - Add BlockBreakBlockEvent
+
     public static void dropResources(BlockState state, Level world, BlockPos pos, @Nullable BlockEntity blockEntity, @Nullable Entity entity, ItemStack tool) {
+    // Paper start - Properly handle xp dropping
+        dropResources(state, world, pos, blockEntity, entity, tool, true);
+    }
+    public static void dropResources(BlockState state, Level world, BlockPos pos, @Nullable BlockEntity blockEntity, @Nullable Entity entity, ItemStack tool, boolean dropExperience) {
+    // Paper end - Properly handle xp dropping
         if (world instanceof ServerLevel) {
-            getDrops(state, (ServerLevel)world, pos, blockEntity, entity, tool).forEach(stack -> popResource(world, pos, stack));
-            state.spawnAfterBreak((ServerLevel)world, pos, tool, true);
+            Block.getDrops(state, (ServerLevel) world, pos, blockEntity, entity, tool).forEach((itemstack1) -> {
+                Block.popResource(world, pos, itemstack1);
+            });
+            state.spawnAfterBreak((ServerLevel) world, pos, tool, dropExperience); // Paper - Properly handle xp dropping
         }
+
     }
 
     public static void popResource(Level world, BlockPos pos, ItemStack stack) {
-        double d = (double)EntityType.ITEM.getHeight() / 2.0;
-        double e = (double)pos.getX() + 0.5 + Mth.nextDouble(world.random, -0.25, 0.25);
-        double f = (double)pos.getY() + 0.5 + Mth.nextDouble(world.random, -0.25, 0.25) - d;
-        double g = (double)pos.getZ() + 0.5 + Mth.nextDouble(world.random, -0.25, 0.25);
-        popResource(world, () -> new ItemEntity(world, e, f, g, stack), stack);
+        double d0 = (double) EntityType.ITEM.getHeight() / 2.0D;
+        double d1 = (double) pos.getX() + 0.5D + Mth.nextDouble(world.random, -0.25D, 0.25D);
+        double d2 = (double) pos.getY() + 0.5D + Mth.nextDouble(world.random, -0.25D, 0.25D) - d0;
+        double d3 = (double) pos.getZ() + 0.5D + Mth.nextDouble(world.random, -0.25D, 0.25D);
+
+        Block.popResource(world, () -> {
+            return new ItemEntity(world, d1, d2, d3, stack);
+        }, stack);
     }
 
     public static void popResourceFromFace(Level world, BlockPos pos, Direction direction, ItemStack stack) {
         int i = direction.getStepX();
         int j = direction.getStepY();
         int k = direction.getStepZ();
-        double d = (double)EntityType.ITEM.getWidth() / 2.0;
-        double e = (double)EntityType.ITEM.getHeight() / 2.0;
-        double f = (double)pos.getX() + 0.5 + (i == 0 ? Mth.nextDouble(world.random, -0.25, 0.25) : (double)i * (0.5 + d));
-        double g = (double)pos.getY() + 0.5 + (j == 0 ? Mth.nextDouble(world.random, -0.25, 0.25) : (double)j * (0.5 + e)) - e;
-        double h = (double)pos.getZ() + 0.5 + (k == 0 ? Mth.nextDouble(world.random, -0.25, 0.25) : (double)k * (0.5 + d));
-        double l = i == 0 ? Mth.nextDouble(world.random, -0.1, 0.1) : (double)i * 0.1;
-        double m = j == 0 ? Mth.nextDouble(world.random, 0.0, 0.1) : (double)j * 0.1 + 0.1;
-        double n = k == 0 ? Mth.nextDouble(world.random, -0.1, 0.1) : (double)k * 0.1;
-        popResource(world, () -> new ItemEntity(world, f, g, h, stack, l, m, n), stack);
+        double d0 = (double) EntityType.ITEM.getWidth() / 2.0D;
+        double d1 = (double) EntityType.ITEM.getHeight() / 2.0D;
+        double d2 = (double) pos.getX() + 0.5D + (i == 0 ? Mth.nextDouble(world.random, -0.25D, 0.25D) : (double) i * (0.5D + d0));
+        double d3 = (double) pos.getY() + 0.5D + (j == 0 ? Mth.nextDouble(world.random, -0.25D, 0.25D) : (double) j * (0.5D + d1)) - d1;
+        double d4 = (double) pos.getZ() + 0.5D + (k == 0 ? Mth.nextDouble(world.random, -0.25D, 0.25D) : (double) k * (0.5D + d0));
+        double d5 = i == 0 ? Mth.nextDouble(world.random, -0.1D, 0.1D) : (double) i * 0.1D;
+        double d6 = j == 0 ? Mth.nextDouble(world.random, 0.0D, 0.1D) : (double) j * 0.1D + 0.1D;
+        double d7 = k == 0 ? Mth.nextDouble(world.random, -0.1D, 0.1D) : (double) k * 0.1D;
+
+        Block.popResource(world, () -> {
+            return new ItemEntity(world, d2, d3, d4, stack, d5, d6, d7);
+        }, stack);
     }
 
     private static void popResource(Level world, Supplier<ItemEntity> itemEntitySupplier, ItemStack stack) {
         if (!world.isClientSide && !stack.isEmpty() && world.getGameRules().getBoolean(GameRules.RULE_DOBLOCKDROPS)) {
-            ItemEntity itemEntity = itemEntitySupplier.get();
-            itemEntity.setDefaultPickUpDelay();
-            world.addFreshEntity(itemEntity);
+            ItemEntity entityitem = (ItemEntity) itemEntitySupplier.get();
+
+            entityitem.setDefaultPickUpDelay();
+            // CraftBukkit start
+            if (world.captureDrops != null) {
+                world.captureDrops.add(entityitem);
+            } else {
+                world.addFreshEntity(entityitem);
+            }
+            // CraftBukkit end
         }
     }
 
     public void popExperience(ServerLevel world, BlockPos pos, int size) {
+        // Paper start - add entity parameter
+        popExperience(world, pos, size, null);
+    }
+    public void popExperience(ServerLevel world, BlockPos pos, int size, net.minecraft.world.entity.Entity entity) {
+        // Paper end - add entity parameter
         if (world.getGameRules().getBoolean(GameRules.RULE_DOBLOCKDROPS)) {
-            ExperienceOrb.award(world, Vec3.atCenterOf(pos), size);
+            ExperienceOrb.award(world, Vec3.atCenterOf(pos), size, org.bukkit.entity.ExperienceOrb.SpawnReason.BLOCK_BREAK, entity); // Paper
         }
+
     }
 
     public float getExplosionResistance() {
         return this.explosionResistance;
     }
 
-    public void wasExploded(Level world, BlockPos pos, Explosion explosion) {
-    }
+    public void wasExploded(Level world, BlockPos pos, Explosion explosion) {}
 
-    public void stepOn(Level world, BlockPos pos, BlockState state, Entity entity) {
-    }
+    public void stepOn(Level world, BlockPos pos, BlockState state, Entity entity) {}
 
     @Nullable
     public BlockState getStateForPlacement(BlockPlaceContext ctx) {
         return this.defaultBlockState();
     }
 
+    @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper - fix drops not preventing stats/food exhaustion
     public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool) {
+        // Paper start - fix drops not preventing stats/food exhaustion
+        this.playerDestroy(world, player, pos, state, blockEntity, tool, true, true);
+    }
+    public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool, boolean includeDrops, boolean dropExp) {
+        // Paper end - fix drops not preventing stats/food exhaustion
         player.awardStat(Stats.BLOCK_MINED.get(this));
-        player.causeFoodExhaustion(0.005F);
-        dropResources(state, world, pos, blockEntity, player, tool);
+        player.causeFoodExhaustion(0.005F, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.BLOCK_MINED); // CraftBukkit - EntityExhaustionEvent
+        if (includeDrops) { // Paper - fix drops not preventing stats/food exhaustion
+        Block.dropResources(state, world, pos, blockEntity, player, tool, dropExp); // Paper - Properly handle xp dropping
+        } // Paper - fix drops not preventing stats/food exhaustion
     }
 
-    public void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack itemStack) {
-    }
+    public void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack itemStack) {}
 
     public boolean isPossibleToRespawnInThis(BlockState state) {
         return !state.isSolid() && !state.liquid();
@@ -379,7 +461,7 @@
     }
 
     public void updateEntityAfterFallOn(BlockGetter world, Entity entity) {
-        entity.setDeltaMovement(entity.getDeltaMovement().multiply(1.0, 0.0, 1.0));
+        entity.setDeltaMovement(entity.getDeltaMovement().multiply(1.0D, 0.0D, 1.0D));
     }
 
     public ItemStack getCloneItemStack(LevelReader world, BlockPos pos, BlockState state) {
@@ -399,7 +481,7 @@
     }
 
     protected void spawnDestroyParticles(Level world, Player player, BlockPos pos, BlockState state) {
-        world.levelEvent(player, 2001, pos, getId(state));
+        world.levelEvent(player, 2001, pos, Block.getId(state));
     }
 
     public BlockState playerWillDestroy(Level world, BlockPos pos, BlockState state, Player player) {
@@ -408,19 +490,17 @@
             PiglinAi.angerNearbyPiglins(player, false);
         }
 
-        world.gameEvent(GameEvent.BLOCK_DESTROY, pos, GameEvent.Context.of(player, state));
+        world.gameEvent((Holder) GameEvent.BLOCK_DESTROY, pos, GameEvent.Context.of(player, state));
         return state;
     }
 
-    public void handlePrecipitation(BlockState state, Level world, BlockPos pos, Biome.Precipitation precipitation) {
-    }
+    public void handlePrecipitation(BlockState state, Level world, BlockPos pos, Biome.Precipitation precipitation) {}
 
     public boolean dropFromExplosion(Explosion explosion) {
         return true;
     }
 
-    protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
-    }
+    protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {}
 
     public StateDefinition<Block, BlockState> getStateDefinition() {
         return this.stateDefinition;
@@ -435,19 +515,22 @@
     }
 
     public final BlockState withPropertiesOf(BlockState state) {
-        BlockState blockState = this.defaultBlockState();
+        BlockState iblockdata1 = this.defaultBlockState();
+        Iterator iterator = state.getBlock().getStateDefinition().getProperties().iterator();
 
-        for (Property<?> property : state.getBlock().getStateDefinition().getProperties()) {
-            if (blockState.hasProperty(property)) {
-                blockState = copyProperty(state, blockState, property);
+        while (iterator.hasNext()) {
+            Property<?> iblockstate = (Property) iterator.next();
+
+            if (iblockdata1.hasProperty(iblockstate)) {
+                iblockdata1 = Block.copyProperty(state, iblockdata1, iblockstate);
             }
         }
 
-        return blockState;
+        return iblockdata1;
     }
 
     private static <T extends Comparable<T>> BlockState copyProperty(BlockState source, BlockState target, Property<T> property) {
-        return target.setValue(property, source.getValue(property));
+        return (BlockState) target.setValue(property, source.getValue(property));
     }
 
     @Override
@@ -463,13 +546,11 @@
         return this.dynamicShape;
     }
 
-    @Override
     public String toString() {
-        return "Block{" + BuiltInRegistries.BLOCK.getKey(this) + "}";
+        return "Block{" + String.valueOf(BuiltInRegistries.BLOCK.getKey(this)) + "}";
     }
 
-    public void appendHoverText(ItemStack stack, Item.TooltipContext context, List<Component> tooltip, TooltipFlag options) {
-    }
+    public void appendHoverText(ItemStack stack, Item.TooltipContext context, List<Component> tooltip, TooltipFlag options) {}
 
     @Override
     protected Block asBlock() {
@@ -477,24 +558,48 @@
     }
 
     protected ImmutableMap<BlockState, VoxelShape> getShapeForEachState(Function<BlockState, VoxelShape> stateToShape) {
-        return this.stateDefinition.getPossibleStates().stream().collect(ImmutableMap.toImmutableMap(Function.identity(), stateToShape));
+        return (ImmutableMap) this.stateDefinition.getPossibleStates().stream().collect(ImmutableMap.toImmutableMap(Function.identity(), stateToShape));
     }
 
+    /** @deprecated */
     @Deprecated
     public Holder.Reference<Block> builtInRegistryHolder() {
         return this.builtInRegistryHolder;
     }
 
-    protected void tryDropExperience(ServerLevel world, BlockPos pos, ItemStack tool, IntProvider experience) {
-        if (EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, tool) == 0) {
-            int i = experience.sample(world.random);
+    // CraftBukkit start
+    protected int tryDropExperience(ServerLevel worldserver, BlockPos blockposition, ItemStack itemstack, IntProvider intprovider) {
+        if (EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, itemstack) == 0) {
+            int i = intprovider.sample(worldserver.random);
+
             if (i > 0) {
-                this.popExperience(world, pos, i);
+                // this.popExperience(worldserver, blockposition, i);
+                return i;
             }
         }
+
+        return 0;
+    }
+
+    public int getExpDrop(BlockState iblockdata, ServerLevel worldserver, BlockPos blockposition, ItemStack itemstack, boolean flag) {
+        return 0;
+    }
+    // CraftBukkit end
+
+    // Spigot start
+    public static float range(float min, float value, float max) {
+        if (value < min) {
+            return min;
+        }
+        if (value > max) {
+            return max;
+        }
+        return value;
     }
+    // Spigot end
 
     public static final class BlockStatePairKey {
+
         private final BlockState first;
         private final BlockState second;
         private final Direction direction;
@@ -505,20 +610,24 @@
             this.direction = facing;
         }
 
-        @Override
         public boolean equals(Object object) {
-            return this == object
-                || object instanceof Block.BlockStatePairKey blockStatePairKey
-                    && this.first == blockStatePairKey.first
-                    && this.second == blockStatePairKey.second
-                    && this.direction == blockStatePairKey.direction;
+            if (this == object) {
+                return true;
+            } else if (!(object instanceof Block.BlockStatePairKey)) {
+                return false;
+            } else {
+                Block.BlockStatePairKey block_a = (Block.BlockStatePairKey) object;
+
+                return this.first == block_a.first && this.second == block_a.second && this.direction == block_a.direction;
+            }
         }
 
-        @Override
         public int hashCode() {
             int i = this.first.hashCode();
+
             i = 31 * i + this.second.hashCode();
-            return 31 * i + this.direction.hashCode();
+            i = 31 * i + this.direction.hashCode();
+            return i;
         }
     }
 }
