--- a/net/minecraft/world/level/block/state/BlockBehaviour.java
+++ b/net/minecraft/world/level/block/state/BlockBehaviour.java
@@ -89,7 +_,7 @@
     protected static final Direction[] UPDATE_SHAPE_ORDER = new Direction[]{
         Direction.WEST, Direction.EAST, Direction.NORTH, Direction.SOUTH, Direction.DOWN, Direction.UP
     };
-    protected final boolean hasCollision;
+    public final boolean hasCollision;
     protected final float explosionResistance;
     protected final boolean isRandomlyTicking;
     protected final SoundType soundType;
@@ -168,13 +_,20 @@
     }
 
     protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean movedByPiston) {
+        org.spigotmc.AsyncCatcher.catchOp("block onPlace"); // Spigot
     }
 
     protected void affectNeighborsAfterRemoval(BlockState state, ServerLevel level, BlockPos pos, boolean movedByPiston) {
     }
 
+    // CraftBukkit start
+    protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean movedByPiston, @Nullable net.minecraft.world.item.context.UseOnContext context) {
+        this.onPlace(state, level, pos, oldState, movedByPiston);
+    }
+    // CraftBukkit end
+
     protected void onExplosionHit(BlockState state, ServerLevel level, BlockPos pos, Explosion explosion, BiConsumer<ItemStack, BlockPos> dropConsumer) {
-        if (!state.isAir() && explosion.getBlockInteraction() != Explosion.BlockInteraction.TRIGGER_BLOCK) {
+        if (!state.isAir() && explosion.getBlockInteraction() != Explosion.BlockInteraction.TRIGGER_BLOCK && state.isDestroyable()) { // Paper - Protect Bedrock and End Portal/Frames from being destroyed
             Block block = state.getBlock();
             boolean flag = explosion.getIndirectSourceEntity() instanceof Player;
             if (block.dropFromExplosion(explosion)) {
@@ -184,8 +_,10 @@
                     .withParameter(LootContextParams.TOOL, ItemStack.EMPTY)
                     .withOptionalParameter(LootContextParams.BLOCK_ENTITY, blockEntity)
                     .withOptionalParameter(LootContextParams.THIS_ENTITY, explosion.getDirectSourceEntity());
-                if (explosion.getBlockInteraction() == Explosion.BlockInteraction.DESTROY_WITH_DECAY) {
-                    builder.withParameter(LootContextParams.EXPLOSION_RADIUS, explosion.radius());
+                // CraftBukkit start - add yield
+                if (explosion instanceof net.minecraft.world.level.ServerExplosion serverExplosion && serverExplosion.yield < 1.0F) {
+                    builder.withParameter(LootContextParams.EXPLOSION_RADIUS, 1.0F / serverExplosion.yield);
+                    // CraftBukkit end
                 }
 
                 state.spawnAfterBreak(level, pos, ItemStack.EMPTY, flag);
@@ -253,7 +_,7 @@
     }
 
     protected boolean canBeReplaced(BlockState state, BlockPlaceContext useContext) {
-        return state.canBeReplaced() && (useContext.getItemInHand().isEmpty() || !useContext.getItemInHand().is(this.asItem()));
+        return state.canBeReplaced() && (useContext.getItemInHand().isEmpty() || !useContext.getItemInHand().is(this.asItem())) && (state.isDestroyable() || (useContext.getPlayer() != null && useContext.getPlayer().getAbilities().instabuild)); // Paper - Protect Bedrock and End Portal/Frames from being destroyed
     }
 
     protected boolean canBeReplaced(BlockState state, Fluid fluid) {
@@ -296,7 +_,7 @@
     }
 
     @Nullable
-    protected MenuProvider getMenuProvider(BlockState state, Level level, BlockPos pos) {
+    public MenuProvider getMenuProvider(BlockState state, Level level, BlockPos pos) {
         return null;
     }
 
@@ -404,7 +_,7 @@
         return this.properties.destroyTime;
     }
 
-    public abstract static class BlockStateBase extends StateHolder<Block, BlockState> {
+    public abstract static class BlockStateBase extends StateHolder<Block, BlockState> implements ca.spottedleaf.moonrise.patches.starlight.blockstate.StarlightAbstractBlockState, ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState { // Paper - rewrite chunk system // Paper - optimise collisions
         private static final Direction[] DIRECTIONS = Direction.values();
         private static final VoxelShape[] EMPTY_OCCLUSION_SHAPES = Util.make(new VoxelShape[DIRECTIONS.length], shape -> Arrays.fill(shape, Shapes.empty()));
         private static final VoxelShape[] FULL_BLOCK_OCCLUSION_SHAPES = Util.make(
@@ -420,7 +_,7 @@
         private boolean legacySolid;
         private final PushReaction pushReaction;
         private final MapColor mapColor;
-        private final float destroySpeed;
+        public final float destroySpeed;
         private final boolean requiresCorrectToolForDrops;
         private final boolean canOcclude;
         private final BlockBehaviour.StatePredicate isRedstoneConductor;
@@ -443,6 +_,76 @@
         private boolean propagatesSkylightDown;
         private int lightBlock;
 
+        // Paper start - rewrite chunk system
+        private boolean isConditionallyFullOpaque;
+
+        @Override
+        public final boolean starlight$isConditionallyFullOpaque() {
+            return this.isConditionallyFullOpaque;
+        }
+        // Paper end - rewrite chunk system
+        // Paper start - optimise collisions
+        private static final int RANDOM_OFFSET = 704237939;
+        private static final Direction[] DIRECTIONS_CACHED = Direction.values();
+        private static final java.util.concurrent.atomic.AtomicInteger ID_GENERATOR = new java.util.concurrent.atomic.AtomicInteger();
+        private final int id1 = it.unimi.dsi.fastutil.HashCommon.murmurHash3(it.unimi.dsi.fastutil.HashCommon.murmurHash3(ID_GENERATOR.getAndIncrement() + RANDOM_OFFSET) + RANDOM_OFFSET);
+        private final int id2 = it.unimi.dsi.fastutil.HashCommon.murmurHash3(it.unimi.dsi.fastutil.HashCommon.murmurHash3(ID_GENERATOR.getAndIncrement() + RANDOM_OFFSET) + RANDOM_OFFSET);
+        private boolean occludesFullBlock;
+        private boolean emptyCollisionShape;
+        private boolean emptyConstantCollisionShape;
+        private VoxelShape constantCollisionShape;
+
+        private static void initCaches(final VoxelShape shape, final boolean neighbours) {
+            ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)shape).moonrise$isFullBlock();
+            ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)shape).moonrise$occludesFullBlock();
+            shape.toAabbs();
+            if (!shape.isEmpty()) {
+                shape.bounds();
+            }
+            if (neighbours) {
+                for (final Direction direction : DIRECTIONS_CACHED) {
+                    initCaches(((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)shape).moonrise$getFaceShapeClamped(direction), false);
+                    initCaches(shape.getFaceShape(direction), false);
+                }
+            }
+        }
+
+        @Override
+        public final boolean moonrise$hasCache() {
+            return this.cache != null;
+        }
+
+        @Override
+        public final boolean moonrise$occludesFullBlock() {
+            return this.occludesFullBlock;
+        }
+
+        @Override
+        public final boolean moonrise$emptyCollisionShape() {
+            return this.emptyCollisionShape;
+        }
+
+        @Override
+        public final boolean moonrise$emptyContextCollisionShape() {
+            return this.emptyConstantCollisionShape;
+        }
+
+        @Override
+        public final int moonrise$uniqueId1() {
+            return this.id1;
+        }
+
+        @Override
+        public final int moonrise$uniqueId2() {
+            return this.id2;
+        }
+
+        @Override
+        public final VoxelShape moonrise$getConstantContextCollisionShape() {
+            return this.constantCollisionShape;
+        }
+        // Paper end - optimise collisions
+
         protected BlockStateBase(Block owner, Reference2ObjectArrayMap<Property<?>, Comparable<?>> values, MapCodec<BlockState> propertiesCodec) {
             super(owner, values, propertiesCodec);
             BlockBehaviour.Properties properties = owner.properties;
@@ -466,6 +_,16 @@
             this.instrument = properties.instrument;
             this.replaceable = properties.replaceable;
         }
+        // Paper start - Perf: impl cached craft block data, lazy load to fix issue with loading at the wrong time
+        @Nullable
+        private org.bukkit.craftbukkit.block.data.CraftBlockData cachedCraftBlockData;
+
+        public org.bukkit.craftbukkit.block.data.CraftBlockData createCraftBlockData() {
+            if (this.cachedCraftBlockData == null) this.cachedCraftBlockData = org.bukkit.craftbukkit.block.data.CraftBlockData.createData(this.asState());
+            return (org.bukkit.craftbukkit.block.data.CraftBlockData) this.cachedCraftBlockData.clone();
+        }
+        // Paper end - Perf: impl cached craft block data, lazy load to fix issue with loading at the wrong time
+
 
         private boolean calculateSolid() {
             if (this.owner.properties.forceSolidOn) {
@@ -485,12 +_,14 @@
             }
         }
 
+        protected boolean shapeExceedsCube = true; // Paper - moved from actual method to here
         public void initCache() {
             this.fluidState = this.owner.getFluidState(this.asState());
             this.isRandomlyTicking = this.owner.isRandomlyTicking(this.asState());
             if (!this.getBlock().hasDynamicShape()) {
                 this.cache = new BlockBehaviour.BlockStateBase.Cache(this.asState());
             }
+            this.shapeExceedsCube = this.cache == null || this.cache.largeCollisionShape; // Paper - moved from actual method to here
 
             this.legacySolid = this.calculateSolid();
             this.occlusionShape = this.canOcclude ? this.owner.getOcclusionShape(this.asState()) : Shapes.empty();
@@ -509,6 +_,41 @@
 
             this.propagatesSkylightDown = this.owner.propagatesSkylightDown(this.asState());
             this.lightBlock = this.owner.getLightBlock(this.asState());
+            // Paper start - rewrite chunk system
+            this.isConditionallyFullOpaque = this.canOcclude & this.useShapeForLightOcclusion;
+            // Paper end - rewrite chunk system
+            // Paper start - optimise collisions
+            if (this.cache != null) {
+                final VoxelShape collisionShape = this.cache.collisionShape;
+                if (this.isAir()) {
+                    this.constantCollisionShape = Shapes.empty();
+                } else {
+                    this.constantCollisionShape = null;
+                }
+                this.occludesFullBlock = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)collisionShape).moonrise$occludesFullBlock();
+                this.emptyCollisionShape = collisionShape.isEmpty();
+                this.emptyConstantCollisionShape = this.constantCollisionShape != null && this.constantCollisionShape.isEmpty();
+                // init caches
+                initCaches(collisionShape, true);
+                if (this.constantCollisionShape != null) {
+                    initCaches(this.constantCollisionShape, true);
+                }
+            } else {
+                this.occludesFullBlock = false;
+                this.emptyCollisionShape = false;
+                this.emptyConstantCollisionShape = false;
+                this.constantCollisionShape = null;
+            }
+
+            if (this.occlusionShape != null) {
+                initCaches(this.occlusionShape, true);
+            }
+            if (this.occlusionShapesByFace != null) {
+                for (final VoxelShape shape : this.occlusionShapesByFace) {
+                    initCaches(shape, true);
+                }
+            }
+            // Paper end - optimise collisions
         }
 
         public Block getBlock() {
@@ -529,6 +_,11 @@
         public boolean isSolid() {
             return this.legacySolid;
         }
+        // Paper start - Protect Bedrock and End Portal/Frames from being destroyed
+        public final boolean isDestroyable() {
+            return getBlock().isDestroyable();
+        }
+        // Paper end - Protect Bedrock and End Portal/Frames from being destroyed
 
         public boolean isValidSpawn(BlockGetter level, BlockPos pos, EntityType<?> entityType) {
             return this.getBlock().properties.isValidSpawn.test(this.asState(), level, pos, entityType);
@@ -550,19 +_,19 @@
             return this.occlusionShape;
         }
 
-        public boolean hasLargeCollisionShape() {
-            return this.cache == null || this.cache.largeCollisionShape;
+        public final boolean hasLargeCollisionShape() { // Paper
+            return this.shapeExceedsCube; // Paper - moved into shape cache init
         }
 
-        public boolean useShapeForLightOcclusion() {
+        public final boolean useShapeForLightOcclusion() { // Paper - Perf: Final for inlining
             return this.useShapeForLightOcclusion;
         }
 
-        public int getLightEmission() {
+        public final int getLightEmission() { // Paper - Perf: Final for inlining
             return this.lightEmission;
         }
 
-        public boolean isAir() {
+        public final boolean isAir() { // Paper - Perf: Final for inlining
             return this.isAir;
         }
 
@@ -632,14 +_,14 @@
         }
 
         public PushReaction getPistonPushReaction() {
-            return this.pushReaction;
+            return !this.isDestroyable() ? PushReaction.BLOCK : this.pushReaction; // Paper - Protect Bedrock and End Portal/Frames from being destroyed
         }
 
         public boolean isSolidRender() {
             return this.solidRender;
         }
 
-        public boolean canOcclude() {
+        public final boolean canOcclude() { // Paper - Perf: Final for inlining
             return this.canOcclude;
         }
 
@@ -727,7 +_,13 @@
         }
 
         public void onPlace(Level level, BlockPos pos, BlockState oldState, boolean movedByPiston) {
-            this.getBlock().onPlace(this.asState(), level, pos, oldState, movedByPiston);
+            // CraftBukkit start
+            this.onPlace(level, pos, oldState, movedByPiston, null);
+        }
+
+        public void onPlace(Level level, BlockPos pos, BlockState oldState, boolean movedByPiston, @Nullable net.minecraft.world.item.context.UseOnContext context) {
+            this.getBlock().onPlace(this.asState(), level, pos, oldState, movedByPiston, context);
+            // CraftBukkit end
         }
 
         public void affectNeighborsAfterRemoval(ServerLevel level, BlockPos pos, boolean movedByPiston) {
@@ -752,6 +_,7 @@
 
         public void spawnAfterBreak(ServerLevel level, BlockPos pos, ItemStack stack, boolean dropExperience) {
             this.getBlock().spawnAfterBreak(this.asState(), level, pos, stack, dropExperience);
+            if (dropExperience) {this.getBlock().popExperience(level, pos, this.getBlock().getExpDrop(this.asState(), level, pos, stack, true));} // Paper - Properly handle xp dropping
         }
 
         public List<ItemStack> getDrops(LootParams.Builder lootParams) {
@@ -856,11 +_,11 @@
             return this.getBlock().builtInRegistryHolder().is(block);
         }
 
-        public FluidState getFluidState() {
+        public final FluidState getFluidState() { // Paper - Perf: Final for inlining
             return this.fluidState;
         }
 
-        public boolean isRandomlyTicking() {
+        public final boolean isRandomlyTicking() { // Paper - Perf: Final for inlining
             return this.isRandomlyTicking;
         }
 
