--- a/net/minecraft/world/level/block/state/BlockBehaviour.java
+++ b/net/minecraft/world/level/block/state/BlockBehaviour.java
@@ -83,9 +83,8 @@
 import net.minecraft.world.phys.shapes.VoxelShape;
 
 public abstract class BlockBehaviour implements FeatureElement {
-    protected static final Direction[] UPDATE_SHAPE_ORDER = new Direction[]{
-        Direction.WEST, Direction.EAST, Direction.NORTH, Direction.SOUTH, Direction.DOWN, Direction.UP
-    };
+
+    protected static final Direction[] UPDATE_SHAPE_ORDER = new Direction[]{Direction.WEST, Direction.EAST, Direction.NORTH, Direction.SOUTH, Direction.DOWN, Direction.UP};
     public final boolean hasCollision;
     protected final float explosionResistance;
     protected final boolean isRandomlyTicking;
@@ -124,11 +123,12 @@
     }
 
     public static <B extends Block> MapCodec<B> simpleCodec(Function<BlockBehaviour.Properties, B> blockFromSettings) {
-        return RecordCodecBuilder.mapCodec(instance -> instance.group(propertiesCodec()).apply(instance, blockFromSettings));
+        return RecordCodecBuilder.mapCodec((instance) -> {
+            return instance.group(BlockBehaviour.propertiesCodec()).apply(instance, blockFromSettings);
+        });
     }
 
-    protected void updateIndirectNeighbourShapes(BlockState state, LevelAccessor world, BlockPos pos, int flags, int maxUpdateDepth) {
-    }
+    protected void updateIndirectNeighbourShapes(BlockState state, LevelAccessor world, BlockPos pos, int flags, int maxUpdateDepth) {}
 
     protected boolean isPathfindable(BlockState state, PathComputationType type) {
         switch (type) {
@@ -155,32 +155,44 @@
         DebugPackets.sendNeighborsUpdatePacket(world, pos);
     }
 
+    // Paper start - UseOnContext param
+    protected void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify, net.minecraft.world.item.context.UseOnContext context) {
+        this.onPlace(state, world, pos, oldState, notify);
+    }
+    // Paper end - UseOnContext param
+
     protected void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) {
+        org.spigotmc.AsyncCatcher.catchOp("block onPlace"); // Spigot
     }
 
     protected void onRemove(BlockState state, Level world, BlockPos pos, BlockState newState, boolean moved) {
+        org.spigotmc.AsyncCatcher.catchOp("block remove"); // Spigot
         if (state.hasBlockEntity() && !state.is(newState.getBlock())) {
             world.removeBlockEntity(pos);
         }
+
     }
 
     protected void onExplosionHit(BlockState state, Level world, BlockPos pos, Explosion explosion, BiConsumer<ItemStack, BlockPos> stackMerger) {
-        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 bl = explosion.getIndirectSourceEntity() instanceof Player;
-            if (block.dropFromExplosion(explosion) && world instanceof ServerLevel serverLevel) {
-                BlockEntity blockEntity = state.hasBlockEntity() ? world.getBlockEntity(pos) : null;
-                LootParams.Builder builder = new LootParams.Builder(serverLevel)
-                    .withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(pos))
-                    .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());
+            boolean flag = explosion.getIndirectSourceEntity() instanceof Player;
+
+            if (block.dropFromExplosion(explosion) && world instanceof ServerLevel) {
+                ServerLevel worldserver = (ServerLevel) world;
+                BlockEntity tileentity = state.hasBlockEntity() ? world.getBlockEntity(pos) : null;
+                LootParams.Builder lootparams_a = (new LootParams.Builder(worldserver)).withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(pos)).withParameter(LootContextParams.TOOL, ItemStack.EMPTY).withOptionalParameter(LootContextParams.BLOCK_ENTITY, tileentity).withOptionalParameter(LootContextParams.THIS_ENTITY, explosion.getDirectSourceEntity());
+
+                // CraftBukkit start - add yield
+                if (explosion.yield < 1.0F) {
+                    lootparams_a.withParameter(LootContextParams.EXPLOSION_RADIUS, 1.0F / explosion.yield);
+                    // CraftBukkit end
                 }
 
-                state.spawnAfterBreak(serverLevel, pos, ItemStack.EMPTY, bl);
-                state.getDrops(builder).forEach(stack -> stackMerger.accept(stack, pos));
+                state.spawnAfterBreak(worldserver, pos, ItemStack.EMPTY, flag);
+                state.getDrops(lootparams_a).forEach((itemstack) -> {
+                    stackMerger.accept(itemstack, pos);
+                });
             }
 
             world.setBlock(pos, Blocks.AIR.defaultBlockState(), 3);
@@ -192,9 +204,7 @@
         return InteractionResult.PASS;
     }
 
-    protected ItemInteractionResult useItemOn(
-        ItemStack stack, BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit
-    ) {
+    protected ItemInteractionResult useItemOn(ItemStack stack, BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) {
         return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION;
     }
 
@@ -244,7 +254,7 @@
     }
 
     protected boolean canBeReplaced(BlockState state, BlockPlaceContext context) {
-        return state.canBeReplaced() && (context.getItemInHand().isEmpty() || !context.getItemInHand().is(this.asItem()));
+        return state.canBeReplaced() && (context.getItemInHand().isEmpty() || !context.getItemInHand().is(this.asItem())) && (state.isDestroyable() || (context.getPlayer() != null && context.getPlayer().getAbilities().instabuild)); // Paper - Protect Bedrock and End Portal/Frames from being destroyed
     }
 
     protected boolean canBeReplaced(BlockState state, Fluid fluid) {
@@ -252,14 +262,16 @@
     }
 
     protected List<ItemStack> getDrops(BlockState state, LootParams.Builder builder) {
-        ResourceKey<LootTable> resourceKey = this.getLootTable();
-        if (resourceKey == BuiltInLootTables.EMPTY) {
+        ResourceKey<LootTable> resourcekey = this.getLootTable();
+
+        if (resourcekey == BuiltInLootTables.EMPTY) {
             return Collections.emptyList();
         } else {
-            LootParams lootParams = builder.withParameter(LootContextParams.BLOCK_STATE, state).create(LootContextParamSets.BLOCK);
-            ServerLevel serverLevel = lootParams.getLevel();
-            LootTable lootTable = serverLevel.getServer().reloadableRegistries().getLootTable(resourceKey);
-            return lootTable.getRandomItems(lootParams);
+            LootParams lootparams = builder.withParameter(LootContextParams.BLOCK_STATE, state).create(LootContextParamSets.BLOCK);
+            ServerLevel worldserver = lootparams.getLevel();
+            LootTable loottable = worldserver.getServer().reloadableRegistries().getLootTable(resourcekey);
+
+            return loottable.getRandomItems(lootparams);
         }
     }
 
@@ -280,11 +292,7 @@
     }
 
     protected int getLightBlock(BlockState state, BlockGetter world, BlockPos pos) {
-        if (state.isSolidRender(world, pos)) {
-            return world.getMaxLightLevel();
-        } else {
-            return state.propagatesSkylightDown(world, pos) ? 0 : 1;
-        }
+        return state.isSolidRender(world, pos) ? world.getMaxLightLevel() : (state.propagatesSkylightDown(world, pos) ? 0 : 1);
     }
 
     @Nullable
@@ -324,34 +332,31 @@
         return this.getCollisionShape(state, world, pos, context);
     }
 
-    protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
-    }
+    protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {}
 
-    protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
-    }
+    protected void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {}
 
     protected float getDestroyProgress(BlockState state, Player player, BlockGetter world, BlockPos pos) {
         float f = state.getDestroySpeed(world, pos);
+
         if (f == -1.0F) {
             return 0.0F;
         } else {
             int i = player.hasCorrectToolForDrops(state) ? 30 : 100;
-            return player.getDestroySpeed(state) / f / (float)i;
+
+            return player.getDestroySpeed(state) / f / (float) i;
         }
     }
 
-    protected void spawnAfterBreak(BlockState state, ServerLevel world, BlockPos pos, ItemStack tool, boolean dropExperience) {
-    }
+    protected void spawnAfterBreak(BlockState state, ServerLevel world, BlockPos pos, ItemStack tool, boolean dropExperience) {}
 
-    protected void attack(BlockState state, Level world, BlockPos pos, Player player) {
-    }
+    protected void attack(BlockState state, Level world, BlockPos pos, Player player) {}
 
     protected int getSignal(BlockState state, BlockGetter world, BlockPos pos, Direction direction) {
         return 0;
     }
 
-    protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
-    }
+    protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {}
 
     protected int getDirectSignal(BlockState state, BlockGetter world, BlockPos pos, Direction direction) {
         return 0;
@@ -359,15 +364,15 @@
 
     public final ResourceKey<LootTable> getLootTable() {
         if (this.drops == null) {
-            ResourceLocation resourceLocation = BuiltInRegistries.BLOCK.getKey(this.asBlock());
-            this.drops = ResourceKey.create(Registries.LOOT_TABLE, resourceLocation.withPrefix("blocks/"));
+            ResourceLocation minecraftkey = BuiltInRegistries.BLOCK.getKey(this.asBlock());
+
+            this.drops = ResourceKey.create(Registries.LOOT_TABLE, minecraftkey.withPrefix("blocks/"));
         }
 
         return this.drops;
     }
 
-    protected void onProjectileHit(Level world, BlockState state, BlockHitResult hit, Projectile projectile) {
-    }
+    protected void onProjectileHit(Level world, BlockState state, BlockHitResult hit, Projectile projectile) {}
 
     protected boolean propagatesSkylightDown(BlockState state, BlockGetter world, BlockPos pos) {
         return !Block.isShapeFullBlock(state.getShape(world, pos)) && state.getFluidState().isEmpty();
@@ -386,20 +391,382 @@
     protected abstract Block asBlock();
 
     public MapColor defaultMapColor() {
-        return this.properties.mapColor.apply(this.asBlock().defaultBlockState());
+        return (MapColor) this.properties.mapColor.apply(this.asBlock().defaultBlockState());
     }
 
     public float defaultDestroyTime() {
         return this.properties.destroyTime;
     }
 
+    public static class Properties {
+
+        public static final Codec<BlockBehaviour.Properties> CODEC = Codec.unit(() -> {
+            return Properties.of();
+        });
+        Function<BlockState, MapColor> mapColor = (iblockdata) -> {
+            return MapColor.NONE;
+        };
+        boolean hasCollision = true;
+        SoundType soundType;
+        ToIntFunction<BlockState> lightEmission;
+        float explosionResistance;
+        float destroyTime;
+        boolean requiresCorrectToolForDrops;
+        boolean isRandomlyTicking;
+        float friction;
+        float speedFactor;
+        float jumpFactor;
+        ResourceKey<LootTable> drops;
+        boolean canOcclude;
+        boolean isAir;
+        boolean ignitedByLava;
+        /** @deprecated */
+        @Deprecated
+        boolean liquid;
+        /** @deprecated */
+        @Deprecated
+        boolean forceSolidOff;
+        boolean forceSolidOn;
+        PushReaction pushReaction;
+        boolean spawnTerrainParticles;
+        NoteBlockInstrument instrument;
+        boolean replaceable;
+        BlockBehaviour.StateArgumentPredicate<EntityType<?>> isValidSpawn;
+        BlockBehaviour.StatePredicate isRedstoneConductor;
+        BlockBehaviour.StatePredicate isSuffocating;
+        BlockBehaviour.StatePredicate isViewBlocking;
+        BlockBehaviour.StatePredicate hasPostProcess;
+        BlockBehaviour.StatePredicate emissiveRendering;
+        boolean dynamicShape;
+        FeatureFlagSet requiredFeatures;
+        Optional<BlockBehaviour.OffsetFunction> offsetFunction;
+
+        private Properties() {
+            this.soundType = SoundType.STONE;
+            this.lightEmission = (iblockdata) -> {
+                return 0;
+            };
+            this.friction = 0.6F;
+            this.speedFactor = 1.0F;
+            this.jumpFactor = 1.0F;
+            this.canOcclude = true;
+            this.pushReaction = PushReaction.NORMAL;
+            this.spawnTerrainParticles = true;
+            this.instrument = NoteBlockInstrument.HARP;
+            this.isValidSpawn = (iblockdata, iblockaccess, blockposition, entitytypes) -> {
+                return iblockdata.isFaceSturdy(iblockaccess, blockposition, Direction.UP) && iblockdata.getLightEmission() < 14;
+            };
+            this.isRedstoneConductor = (iblockdata, iblockaccess, blockposition) -> {
+                return iblockdata.isCollisionShapeFullBlock(iblockaccess, blockposition);
+            };
+            this.isSuffocating = (iblockdata, iblockaccess, blockposition) -> {
+                return iblockdata.blocksMotion() && iblockdata.isCollisionShapeFullBlock(iblockaccess, blockposition);
+            };
+            this.isViewBlocking = this.isSuffocating;
+            this.hasPostProcess = (iblockdata, iblockaccess, blockposition) -> {
+                return false;
+            };
+            this.emissiveRendering = (iblockdata, iblockaccess, blockposition) -> {
+                return false;
+            };
+            this.requiredFeatures = FeatureFlags.VANILLA_SET;
+            this.offsetFunction = Optional.empty();
+        }
+
+        public static BlockBehaviour.Properties of() {
+            return new BlockBehaviour.Properties();
+        }
+
+        public static BlockBehaviour.Properties ofFullCopy(BlockBehaviour block) {
+            BlockBehaviour.Properties blockbase_info = Properties.ofLegacyCopy(block);
+            BlockBehaviour.Properties blockbase_info1 = block.properties;
+
+            blockbase_info.jumpFactor = blockbase_info1.jumpFactor;
+            blockbase_info.isRedstoneConductor = blockbase_info1.isRedstoneConductor;
+            blockbase_info.isValidSpawn = blockbase_info1.isValidSpawn;
+            blockbase_info.hasPostProcess = blockbase_info1.hasPostProcess;
+            blockbase_info.isSuffocating = blockbase_info1.isSuffocating;
+            blockbase_info.isViewBlocking = blockbase_info1.isViewBlocking;
+            blockbase_info.drops = blockbase_info1.drops;
+            return blockbase_info;
+        }
+
+        /** @deprecated */
+        @Deprecated
+        public static BlockBehaviour.Properties ofLegacyCopy(BlockBehaviour block) {
+            BlockBehaviour.Properties blockbase_info = new BlockBehaviour.Properties();
+            BlockBehaviour.Properties blockbase_info1 = block.properties;
+
+            blockbase_info.destroyTime = blockbase_info1.destroyTime;
+            blockbase_info.explosionResistance = blockbase_info1.explosionResistance;
+            blockbase_info.hasCollision = blockbase_info1.hasCollision;
+            blockbase_info.isRandomlyTicking = blockbase_info1.isRandomlyTicking;
+            blockbase_info.lightEmission = blockbase_info1.lightEmission;
+            blockbase_info.mapColor = blockbase_info1.mapColor;
+            blockbase_info.soundType = blockbase_info1.soundType;
+            blockbase_info.friction = blockbase_info1.friction;
+            blockbase_info.speedFactor = blockbase_info1.speedFactor;
+            blockbase_info.dynamicShape = blockbase_info1.dynamicShape;
+            blockbase_info.canOcclude = blockbase_info1.canOcclude;
+            blockbase_info.isAir = blockbase_info1.isAir;
+            blockbase_info.ignitedByLava = blockbase_info1.ignitedByLava;
+            blockbase_info.liquid = blockbase_info1.liquid;
+            blockbase_info.forceSolidOff = blockbase_info1.forceSolidOff;
+            blockbase_info.forceSolidOn = blockbase_info1.forceSolidOn;
+            blockbase_info.pushReaction = blockbase_info1.pushReaction;
+            blockbase_info.requiresCorrectToolForDrops = blockbase_info1.requiresCorrectToolForDrops;
+            blockbase_info.offsetFunction = blockbase_info1.offsetFunction;
+            blockbase_info.spawnTerrainParticles = blockbase_info1.spawnTerrainParticles;
+            blockbase_info.requiredFeatures = blockbase_info1.requiredFeatures;
+            blockbase_info.emissiveRendering = blockbase_info1.emissiveRendering;
+            blockbase_info.instrument = blockbase_info1.instrument;
+            blockbase_info.replaceable = blockbase_info1.replaceable;
+            return blockbase_info;
+        }
+
+        public BlockBehaviour.Properties mapColor(DyeColor color) {
+            this.mapColor = (iblockdata) -> {
+                return color.getMapColor();
+            };
+            return this;
+        }
+
+        public BlockBehaviour.Properties mapColor(MapColor color) {
+            this.mapColor = (iblockdata) -> {
+                return color;
+            };
+            return this;
+        }
+
+        public BlockBehaviour.Properties mapColor(Function<BlockState, MapColor> mapColorProvider) {
+            this.mapColor = mapColorProvider;
+            return this;
+        }
+
+        public BlockBehaviour.Properties noCollission() {
+            this.hasCollision = false;
+            this.canOcclude = false;
+            return this;
+        }
+
+        public BlockBehaviour.Properties noOcclusion() {
+            this.canOcclude = false;
+            return this;
+        }
+
+        public BlockBehaviour.Properties friction(float slipperiness) {
+            this.friction = slipperiness;
+            return this;
+        }
+
+        public BlockBehaviour.Properties speedFactor(float velocityMultiplier) {
+            this.speedFactor = velocityMultiplier;
+            return this;
+        }
+
+        public BlockBehaviour.Properties jumpFactor(float jumpVelocityMultiplier) {
+            this.jumpFactor = jumpVelocityMultiplier;
+            return this;
+        }
+
+        public BlockBehaviour.Properties sound(SoundType soundGroup) {
+            this.soundType = soundGroup;
+            return this;
+        }
+
+        public BlockBehaviour.Properties lightLevel(ToIntFunction<BlockState> luminance) {
+            this.lightEmission = luminance;
+            return this;
+        }
+
+        public BlockBehaviour.Properties strength(float hardness, float resistance) {
+            return this.destroyTime(hardness).explosionResistance(resistance);
+        }
+
+        public BlockBehaviour.Properties instabreak() {
+            return this.strength(0.0F);
+        }
+
+        public BlockBehaviour.Properties strength(float strength) {
+            this.strength(strength, strength);
+            return this;
+        }
+
+        public BlockBehaviour.Properties randomTicks() {
+            this.isRandomlyTicking = true;
+            return this;
+        }
+
+        public BlockBehaviour.Properties dynamicShape() {
+            this.dynamicShape = true;
+            return this;
+        }
+
+        public BlockBehaviour.Properties noLootTable() {
+            this.drops = BuiltInLootTables.EMPTY;
+            return this;
+        }
+
+        public BlockBehaviour.Properties dropsLike(Block source) {
+            this.drops = source.getLootTable();
+            return this;
+        }
+
+        public BlockBehaviour.Properties ignitedByLava() {
+            this.ignitedByLava = true;
+            return this;
+        }
+
+        public BlockBehaviour.Properties liquid() {
+            this.liquid = true;
+            return this;
+        }
+
+        public BlockBehaviour.Properties forceSolidOn() {
+            this.forceSolidOn = true;
+            return this;
+        }
+
+        /** @deprecated */
+        @Deprecated
+        public BlockBehaviour.Properties forceSolidOff() {
+            this.forceSolidOff = true;
+            return this;
+        }
+
+        public BlockBehaviour.Properties pushReaction(PushReaction pistonBehavior) {
+            this.pushReaction = pistonBehavior;
+            return this;
+        }
+
+        public BlockBehaviour.Properties air() {
+            this.isAir = true;
+            return this;
+        }
+
+        public BlockBehaviour.Properties isValidSpawn(BlockBehaviour.StateArgumentPredicate<EntityType<?>> predicate) {
+            this.isValidSpawn = predicate;
+            return this;
+        }
+
+        public BlockBehaviour.Properties isRedstoneConductor(BlockBehaviour.StatePredicate predicate) {
+            this.isRedstoneConductor = predicate;
+            return this;
+        }
+
+        public BlockBehaviour.Properties isSuffocating(BlockBehaviour.StatePredicate predicate) {
+            this.isSuffocating = predicate;
+            return this;
+        }
+
+        public BlockBehaviour.Properties isViewBlocking(BlockBehaviour.StatePredicate predicate) {
+            this.isViewBlocking = predicate;
+            return this;
+        }
+
+        public BlockBehaviour.Properties hasPostProcess(BlockBehaviour.StatePredicate predicate) {
+            this.hasPostProcess = predicate;
+            return this;
+        }
+
+        public BlockBehaviour.Properties emissiveRendering(BlockBehaviour.StatePredicate predicate) {
+            this.emissiveRendering = predicate;
+            return this;
+        }
+
+        public BlockBehaviour.Properties requiresCorrectToolForDrops() {
+            this.requiresCorrectToolForDrops = true;
+            return this;
+        }
+
+        public BlockBehaviour.Properties destroyTime(float hardness) {
+            this.destroyTime = hardness;
+            return this;
+        }
+
+        public BlockBehaviour.Properties explosionResistance(float resistance) {
+            this.explosionResistance = Math.max(0.0F, resistance);
+            return this;
+        }
+
+        public BlockBehaviour.Properties offsetType(BlockBehaviour.OffsetType offsetType) {
+            switch (offsetType.ordinal()) {
+                case 1:
+                    this.offsetFunction = Optional.of((iblockdata, iblockaccess, blockposition) -> {
+                        Block block = iblockdata.getBlock();
+                        long i = Mth.getSeed(blockposition.getX(), 0, blockposition.getZ());
+                        float f = block.getMaxHorizontalOffset();
+                        double d0 = Mth.clamp(((double) ((float) (i & 15L) / 15.0F) - 0.5D) * 0.5D, (double) (-f), (double) f);
+                        double d1 = Mth.clamp(((double) ((float) (i >> 8 & 15L) / 15.0F) - 0.5D) * 0.5D, (double) (-f), (double) f);
+
+                        return new Vec3(d0, 0.0D, d1);
+                    });
+                    break;
+                case 2:
+                    this.offsetFunction = Optional.of((iblockdata, iblockaccess, blockposition) -> {
+                        Block block = iblockdata.getBlock();
+                        long i = Mth.getSeed(blockposition.getX(), 0, blockposition.getZ());
+                        double d0 = ((double) ((float) (i >> 4 & 15L) / 15.0F) - 1.0D) * (double) block.getMaxVerticalOffset();
+                        float f = block.getMaxHorizontalOffset();
+                        double d1 = Mth.clamp(((double) ((float) (i & 15L) / 15.0F) - 0.5D) * 0.5D, (double) (-f), (double) f);
+                        double d2 = Mth.clamp(((double) ((float) (i >> 8 & 15L) / 15.0F) - 0.5D) * 0.5D, (double) (-f), (double) f);
+
+                        return new Vec3(d1, d0, d2);
+                    });
+                    break;
+                default:
+                    this.offsetFunction = Optional.empty();
+            }
+
+            return this;
+        }
+
+        public BlockBehaviour.Properties noTerrainParticles() {
+            this.spawnTerrainParticles = false;
+            return this;
+        }
+
+        public BlockBehaviour.Properties requiredFeatures(FeatureFlag... features) {
+            this.requiredFeatures = FeatureFlags.REGISTRY.subset(features);
+            return this;
+        }
+
+        public BlockBehaviour.Properties instrument(NoteBlockInstrument instrument) {
+            this.instrument = instrument;
+            return this;
+        }
+
+        public BlockBehaviour.Properties replaceable() {
+            this.replaceable = true;
+            return this;
+        }
+    }
+
+    public interface StateArgumentPredicate<A> {
+
+        boolean test(BlockState state, BlockGetter world, BlockPos pos, A type);
+    }
+
+    public interface OffsetFunction {
+
+        Vec3 evaluate(BlockState state, BlockGetter world, BlockPos pos);
+    }
+
+    public interface StatePredicate {
+
+        boolean test(BlockState state, BlockGetter world, BlockPos pos);
+    }
+
     public abstract static class BlockStateBase extends StateHolder<Block, BlockState> {
+
         private final int lightEmission;
         private final boolean useShapeForLightOcclusion;
         private final boolean isAir;
         private final boolean ignitedByLava;
+        /** @deprecated */
         @Deprecated
         private final boolean liquid;
+        /** @deprecated */
         @Deprecated
         private boolean legacySolid;
         private final PushReaction pushReaction;
@@ -418,80 +785,206 @@
         private final boolean replaceable;
         @Nullable
         protected BlockBehaviour.BlockStateBase.Cache cache;
-        private FluidState fluidState = Fluids.EMPTY.defaultFluidState();
+        private FluidState fluidState;
         private boolean isRandomlyTicking;
 
         protected BlockStateBase(Block block, Reference2ObjectArrayMap<Property<?>, Comparable<?>> propertyMap, MapCodec<BlockState> codec) {
             super(block, propertyMap, codec);
-            BlockBehaviour.Properties properties = block.properties;
-            this.lightEmission = properties.lightEmission.applyAsInt(this.asState());
+            this.fluidState = Fluids.EMPTY.defaultFluidState();
+            BlockBehaviour.Properties blockbase_info = block.properties;
+
+            this.lightEmission = blockbase_info.lightEmission.applyAsInt(this.asState());
             this.useShapeForLightOcclusion = block.useShapeForLightOcclusion(this.asState());
-            this.isAir = properties.isAir;
-            this.ignitedByLava = properties.ignitedByLava;
-            this.liquid = properties.liquid;
-            this.pushReaction = properties.pushReaction;
-            this.mapColor = properties.mapColor.apply(this.asState());
-            this.destroySpeed = properties.destroyTime;
-            this.requiresCorrectToolForDrops = properties.requiresCorrectToolForDrops;
-            this.canOcclude = properties.canOcclude;
-            this.isRedstoneConductor = properties.isRedstoneConductor;
-            this.isSuffocating = properties.isSuffocating;
-            this.isViewBlocking = properties.isViewBlocking;
-            this.hasPostProcess = properties.hasPostProcess;
-            this.emissiveRendering = properties.emissiveRendering;
-            this.offsetFunction = properties.offsetFunction;
-            this.spawnTerrainParticles = properties.spawnTerrainParticles;
-            this.instrument = properties.instrument;
-            this.replaceable = properties.replaceable;
+            this.isAir = blockbase_info.isAir;
+            this.ignitedByLava = blockbase_info.ignitedByLava;
+            this.liquid = blockbase_info.liquid;
+            this.pushReaction = blockbase_info.pushReaction;
+            this.mapColor = (MapColor) blockbase_info.mapColor.apply(this.asState());
+            this.destroySpeed = blockbase_info.destroyTime;
+            this.requiresCorrectToolForDrops = blockbase_info.requiresCorrectToolForDrops;
+            this.canOcclude = blockbase_info.canOcclude;
+            this.isRedstoneConductor = blockbase_info.isRedstoneConductor;
+            this.isSuffocating = blockbase_info.isSuffocating;
+            this.isViewBlocking = blockbase_info.isViewBlocking;
+            this.hasPostProcess = blockbase_info.hasPostProcess;
+            this.emissiveRendering = blockbase_info.emissiveRendering;
+            this.offsetFunction = blockbase_info.offsetFunction;
+            this.spawnTerrainParticles = blockbase_info.spawnTerrainParticles;
+            this.instrument = blockbase_info.instrument;
+            this.replaceable = blockbase_info.replaceable;
+            this.conditionallyFullOpaque = this.canOcclude & this.useShapeForLightOcclusion; // Paper
+            // Paper start - optimise collisions
+            this.id1 = it.unimi.dsi.fastutil.HashCommon.murmurHash3(it.unimi.dsi.fastutil.HashCommon.murmurHash3(ID_GENERATOR.getAndIncrement() + RANDOM_OFFSET) + RANDOM_OFFSET);
+            this.id2 = it.unimi.dsi.fastutil.HashCommon.murmurHash3(it.unimi.dsi.fastutil.HashCommon.murmurHash3(ID_GENERATOR.getAndIncrement() + RANDOM_OFFSET) + RANDOM_OFFSET);
+            // Paper end - optimise collisions
         }
+        // Paper start - Perf: impl cached craft block data, lazy load to fix issue with loading at the wrong time
+        private org.bukkit.craftbukkit.block.data.CraftBlockData cachedCraftBlockData;
+
+        public org.bukkit.craftbukkit.block.data.CraftBlockData createCraftBlockData() {
+            if (cachedCraftBlockData == null) cachedCraftBlockData = org.bukkit.craftbukkit.block.data.CraftBlockData.createData(asState());
+            return (org.bukkit.craftbukkit.block.data.CraftBlockData) 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) {
+            if (((Block) this.owner).properties.forceSolidOn) {
                 return true;
-            } else if (this.owner.properties.forceSolidOff) {
+            } else if (((Block) this.owner).properties.forceSolidOff) {
                 return false;
             } else if (this.cache == null) {
                 return false;
             } else {
-                VoxelShape voxelShape = this.cache.collisionShape;
-                if (voxelShape.isEmpty()) {
+                VoxelShape voxelshape = this.cache.collisionShape;
+
+                if (voxelshape.isEmpty()) {
                     return false;
                 } else {
-                    AABB aABB = voxelShape.bounds();
-                    return aABB.getSize() >= 0.7291666666666666 || aABB.getYsize() >= 1.0;
+                    AABB axisalignedbb = voxelshape.bounds();
+
+                    return axisalignedbb.getSize() >= 0.7291666666666666D ? true : axisalignedbb.getYsize() >= 1.0D;
                 }
             }
         }
 
+        // Paper start
+        protected boolean shapeExceedsCube = true;
+        public final boolean shapeExceedsCube() {
+            return this.shapeExceedsCube;
+        }
+        // Paper end
+        // Paper start - starlight
+        protected int opacityIfCached = -1;
+        // ret -1 if opacity is dynamic, or -1 if the block is conditionally full opaque, else return opacity in [0, 15]
+        public final int getOpacityIfCached() {
+            return this.opacityIfCached;
+        }
+
+        protected final boolean conditionallyFullOpaque;
+        public final boolean isConditionallyFullOpaque() {
+            return this.conditionallyFullOpaque;
+        }
+        // Paper end - starlight
+        // 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, id2;
+        private boolean occludesFullBlock;
+        private boolean emptyCollisionShape;
+        private VoxelShape constantCollisionShape;
+        private AABB constantAABBCollision;
+        private static void initCaches(final VoxelShape shape) {
+            shape.isFullBlock();
+            shape.occludesFullBlock();
+            shape.toAabbs();
+            if (!shape.isEmpty()) {
+                shape.bounds();
+            }
+        }
+
+        public final boolean hasCache() {
+            return this.cache != null;
+        }
+
+        public final boolean occludesFullBlock() {
+            return this.occludesFullBlock;
+        }
+
+        public final boolean emptyCollisionShape() {
+            return this.emptyCollisionShape;
+        }
+
+        public final int uniqueId1() {
+            return this.id1;
+        }
+
+        public final int uniqueId2() {
+            return this.id2;
+        }
+
+        public final VoxelShape getConstantCollisionShape() {
+            return this.constantCollisionShape;
+        }
+
+        public final AABB getConstantCollisionAABB() {
+            return this.constantAABBCollision;
+        }
+        // Paper end - optimise collisions
+
         public void initCache() {
-            this.fluidState = this.owner.getFluidState(this.asState());
-            this.isRandomlyTicking = this.owner.isRandomlyTicking(this.asState());
+            this.fluidState = ((Block) this.owner).getFluidState(this.asState());
+            this.isRandomlyTicking = ((Block) 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.opacityIfCached = this.cache == null || this.isConditionallyFullOpaque() ? -1 : this.cache.lightBlock; // Paper - starlight - cache opacity for light
 
             this.legacySolid = this.calculateSolid();
+            // Paper start - optimise collisions
+            if (this.cache != null) {
+                final VoxelShape collisionShape = this.cache.collisionShape;
+                try {
+                    this.constantCollisionShape = this.getCollisionShape(null, null, null);
+                    this.constantAABBCollision = this.constantCollisionShape == null ? null : this.constantCollisionShape.getSingleAABBRepresentation();
+                } catch (final Throwable throwable) {
+                    this.constantCollisionShape = null;
+                    this.constantAABBCollision = null;
+                }
+                this.occludesFullBlock = collisionShape.occludesFullBlock();
+                this.emptyCollisionShape = collisionShape.isEmpty();
+                // init caches
+                initCaches(collisionShape);
+                if (collisionShape != Shapes.empty() && collisionShape != Shapes.block()) {
+                    for (final Direction direction : DIRECTIONS_CACHED) {
+                        // initialise the directional face shape cache as well
+                        final VoxelShape shape = Shapes.getFaceShape(collisionShape, direction);
+                        initCaches(shape);
+                    }
+                }
+                if (this.cache.occlusionShapes != null) {
+                    for (final VoxelShape shape : this.cache.occlusionShapes) {
+                        initCaches(shape);
+                    }
+                }
+            } else {
+                this.occludesFullBlock = false;
+                this.emptyCollisionShape = false;
+                this.constantCollisionShape = null;
+                this.constantAABBCollision = null;
+            }
+            // Paper end - optimise collisions
         }
 
         public Block getBlock() {
-            return this.owner;
+            return (Block) this.owner;
         }
 
         public Holder<Block> getBlockHolder() {
-            return this.owner.builtInRegistryHolder();
+            return ((Block) this.owner).builtInRegistryHolder();
         }
 
+        /** @deprecated */
         @Deprecated
         public boolean blocksMotion() {
             Block block = this.getBlock();
+
             return block != Blocks.COBWEB && block != Blocks.BAMBOO_SAPLING && this.isSolid();
         }
 
+        /** @deprecated */
         @Deprecated
         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 world, BlockPos pos, EntityType<?> type) {
             return this.getBlock().properties.isValidSpawn.test(this.asState(), world, pos, type);
         }
@@ -505,28 +998,26 @@
         }
 
         public VoxelShape getFaceOcclusionShape(BlockGetter world, BlockPos pos, Direction direction) {
-            return this.cache != null && this.cache.occlusionShapes != null
-                ? this.cache.occlusionShapes[direction.ordinal()]
-                : Shapes.getFaceShape(this.getOcclusionShape(world, pos), direction);
+            return this.cache != null && this.cache.occlusionShapes != null ? this.cache.occlusionShapes[direction.ordinal()] : Shapes.getFaceShape(this.getOcclusionShape(world, pos), direction);
         }
 
         public VoxelShape getOcclusionShape(BlockGetter world, BlockPos pos) {
             return this.getBlock().getOcclusionShape(this.asState(), world, pos);
         }
 
-        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;
         }
 
@@ -534,6 +1025,7 @@
             return this.ignitedByLava;
         }
 
+        /** @deprecated */
         @Deprecated
         public boolean liquid() {
             return this.liquid;
@@ -596,19 +1088,20 @@
         }
 
         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(BlockGetter world, BlockPos pos) {
             if (this.cache != null) {
                 return this.cache.solidRender;
             } else {
-                BlockState blockState = this.asState();
-                return blockState.canOcclude() && Block.isShapeFullBlock(blockState.getOcclusionShape(world, pos));
+                BlockState iblockdata = this.asState();
+
+                return iblockdata.canOcclude() ? Block.isShapeFullBlock(iblockdata.getOcclusionShape(world, pos)) : false;
             }
         }
 
-        public boolean canOcclude() {
+        public final boolean canOcclude() { // Paper - Perf: Final for inlining
             return this.canOcclude;
         }
 
@@ -653,7 +1146,9 @@
         }
 
         public Vec3 getOffset(BlockGetter world, BlockPos pos) {
-            return this.offsetFunction.<Vec3>map(offsetter -> offsetter.evaluate(this.asState(), world, pos)).orElse(Vec3.ZERO);
+            return (Vec3) this.offsetFunction.map((blockbase_b) -> {
+                return blockbase_b.evaluate(this.asState(), world, pos);
+            }).orElse(Vec3.ZERO);
         }
 
         public boolean hasOffsetFunction() {
@@ -673,12 +1168,17 @@
         }
 
         public final void updateNeighbourShapes(LevelAccessor world, BlockPos pos, int flags, int maxUpdateDepth) {
-            BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
+            BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos();
+            Direction[] aenumdirection = BlockBehaviour.UPDATE_SHAPE_ORDER;
+            int k = aenumdirection.length;
 
-            for (Direction direction : BlockBehaviour.UPDATE_SHAPE_ORDER) {
-                mutableBlockPos.setWithOffset(pos, direction);
-                world.neighborShapeChanged(direction.getOpposite(), this.asState(), mutableBlockPos, pos, flags, maxUpdateDepth);
+            for (int l = 0; l < k; ++l) {
+                Direction enumdirection = aenumdirection[l];
+
+                blockposition_mutableblockposition.setWithOffset(pos, enumdirection);
+                world.neighborShapeChanged(enumdirection.getOpposite(), this.asState(), blockposition_mutableblockposition, pos, flags, maxUpdateDepth);
             }
+
         }
 
         public final void updateIndirectNeighbourShapes(LevelAccessor world, BlockPos pos, int flags) {
@@ -689,6 +1189,12 @@
             this.getBlock().updateIndirectNeighbourShapes(this.asState(), world, pos, flags, maxUpdateDepth);
         }
 
+        // Paper start
+        public void onPlace(Level world, BlockPos pos, BlockState state, boolean notify, net.minecraft.world.item.context.UseOnContext context) {
+            this.getBlock().onPlace(this.asState(), world, pos, state, notify, context);
+        }
+        // Paper end
+
         public void onPlace(Level world, BlockPos pos, BlockState state, boolean notify) {
             this.getBlock().onPlace(this.asState(), world, pos, state, notify);
         }
@@ -715,6 +1221,7 @@
 
         public void spawnAfterBreak(ServerLevel world, BlockPos pos, ItemStack tool, boolean dropExperience) {
             this.getBlock().spawnAfterBreak(this.asState(), world, pos, tool, dropExperience);
+            if (dropExperience) {getBlock().popExperience(world, pos, this.getBlock().getExpDrop(asState(), world, pos, tool, true));} // Paper - Properly handle xp dropping
         }
 
         public List<ItemStack> getDrops(LootParams.Builder builder) {
@@ -787,7 +1294,7 @@
         }
 
         public boolean is(Holder<Block> blockEntry) {
-            return this.is(blockEntry.value());
+            return this.is((Block) blockEntry.value());
         }
 
         public Stream<TagKey<Block>> getTags() {
@@ -800,7 +1307,7 @@
 
         @Nullable
         public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level world, BlockEntityType<T> blockEntityType) {
-            return this.getBlock() instanceof EntityBlock ? ((EntityBlock)this.getBlock()).getTicker(world, this.asState(), blockEntityType) : null;
+            return this.getBlock() instanceof EntityBlock ? ((EntityBlock) this.getBlock()).getTicker(world, this.asState(), blockEntityType) : null;
         }
 
         public boolean is(Block block) {
@@ -811,11 +1318,11 @@
             return this.getBlock().builtInRegistryHolder().is(key);
         }
 
-        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;
         }
 
@@ -857,7 +1364,8 @@
             return this.instrument;
         }
 
-        static final class Cache {
+        private static final class Cache {
+
             private static final Direction[] DIRECTIONS = Direction.values();
             private static final int SUPPORT_TYPE_COUNT = SupportType.values().length;
             protected final boolean solidRender;
@@ -872,39 +1380,48 @@
 
             Cache(BlockState state) {
                 Block block = state.getBlock();
+
                 this.solidRender = state.isSolidRender(EmptyBlockGetter.INSTANCE, BlockPos.ZERO);
                 this.propagatesSkylightDown = block.propagatesSkylightDown(state, EmptyBlockGetter.INSTANCE, BlockPos.ZERO);
                 this.lightBlock = block.getLightBlock(state, EmptyBlockGetter.INSTANCE, BlockPos.ZERO);
+                int i;
+
                 if (!state.canOcclude()) {
                     this.occlusionShapes = null;
                 } else {
-                    this.occlusionShapes = new VoxelShape[DIRECTIONS.length];
-                    VoxelShape voxelShape = block.getOcclusionShape(state, EmptyBlockGetter.INSTANCE, BlockPos.ZERO);
+                    this.occlusionShapes = new VoxelShape[BlockBehaviour.BlockStateBase.Cache.DIRECTIONS.length];
+                    VoxelShape voxelshape = block.getOcclusionShape(state, EmptyBlockGetter.INSTANCE, BlockPos.ZERO);
+                    Direction[] aenumdirection = BlockBehaviour.BlockStateBase.Cache.DIRECTIONS;
 
-                    for (Direction direction : DIRECTIONS) {
-                        this.occlusionShapes[direction.ordinal()] = Shapes.getFaceShape(voxelShape, direction);
+                    i = aenumdirection.length;
+
+                    for (int j = 0; j < i; ++j) {
+                        Direction enumdirection = aenumdirection[j];
+
+                        this.occlusionShapes[enumdirection.ordinal()] = Shapes.getFaceShape(voxelshape, enumdirection);
                     }
                 }
 
                 this.collisionShape = block.getCollisionShape(state, EmptyBlockGetter.INSTANCE, BlockPos.ZERO, CollisionContext.empty());
                 if (!this.collisionShape.isEmpty() && state.hasOffsetFunction()) {
-                    throw new IllegalStateException(
-                        String.format(
-                            Locale.ROOT,
-                            "%s has a collision shape and an offset type, but is not marked as dynamicShape in its properties.",
-                            BuiltInRegistries.BLOCK.getKey(block)
-                        )
-                    );
+                    throw new IllegalStateException(String.format(Locale.ROOT, "%s has a collision shape and an offset type, but is not marked as dynamicShape in its properties.", BuiltInRegistries.BLOCK.getKey(block)));
                 } else {
-                    this.largeCollisionShape = Arrays.stream(Direction.Axis.values())
-                        .anyMatch(axis -> this.collisionShape.min(axis) < 0.0 || this.collisionShape.max(axis) > 1.0);
-                    this.faceSturdy = new boolean[DIRECTIONS.length * SUPPORT_TYPE_COUNT];
+                    this.largeCollisionShape = Arrays.stream(Direction.Axis.values()).anyMatch((enumdirection_enumaxis) -> {
+                        return this.collisionShape.min(enumdirection_enumaxis) < 0.0D || this.collisionShape.max(enumdirection_enumaxis) > 1.0D;
+                    });
+                    this.faceSturdy = new boolean[BlockBehaviour.BlockStateBase.Cache.DIRECTIONS.length * BlockBehaviour.BlockStateBase.Cache.SUPPORT_TYPE_COUNT];
+                    Direction[] aenumdirection1 = BlockBehaviour.BlockStateBase.Cache.DIRECTIONS;
+                    int k = aenumdirection1.length;
 
-                    for (Direction direction2 : DIRECTIONS) {
-                        for (SupportType supportType : SupportType.values()) {
-                            this.faceSturdy[getFaceSupportIndex(direction2, supportType)] = supportType.isSupporting(
-                                state, EmptyBlockGetter.INSTANCE, BlockPos.ZERO, direction2
-                            );
+                    for (i = 0; i < k; ++i) {
+                        Direction enumdirection1 = aenumdirection1[i];
+                        SupportType[] aenumblocksupport = SupportType.values();
+                        int l = aenumblocksupport.length;
+
+                        for (int i1 = 0; i1 < l; ++i1) {
+                            SupportType enumblocksupport = aenumblocksupport[i1];
+
+                            this.faceSturdy[Cache.getFaceSupportIndex(enumdirection1, enumblocksupport)] = enumblocksupport.isSupporting(state, EmptyBlockGetter.INSTANCE, BlockPos.ZERO, enumdirection1);
                         }
                     }
 
@@ -913,329 +1430,19 @@
             }
 
             public boolean isFaceSturdy(Direction direction, SupportType shapeType) {
-                return this.faceSturdy[getFaceSupportIndex(direction, shapeType)];
+                return this.faceSturdy[Cache.getFaceSupportIndex(direction, shapeType)];
             }
 
             private static int getFaceSupportIndex(Direction direction, SupportType shapeType) {
-                return direction.ordinal() * SUPPORT_TYPE_COUNT + shapeType.ordinal();
+                return direction.ordinal() * BlockBehaviour.BlockStateBase.Cache.SUPPORT_TYPE_COUNT + shapeType.ordinal();
             }
         }
     }
 
-    public interface OffsetFunction {
-        Vec3 evaluate(BlockState state, BlockGetter world, BlockPos pos);
-    }
-
     public static enum OffsetType {
-        NONE,
-        XZ,
-        XYZ;
-    }
-
-    public static class Properties {
-        public static final Codec<BlockBehaviour.Properties> CODEC = Codec.unit(() -> of());
-        Function<BlockState, MapColor> mapColor = state -> MapColor.NONE;
-        boolean hasCollision = true;
-        SoundType soundType = SoundType.STONE;
-        ToIntFunction<BlockState> lightEmission = state -> 0;
-        float explosionResistance;
-        float destroyTime;
-        boolean requiresCorrectToolForDrops;
-        boolean isRandomlyTicking;
-        float friction = 0.6F;
-        float speedFactor = 1.0F;
-        float jumpFactor = 1.0F;
-        ResourceKey<LootTable> drops;
-        boolean canOcclude = true;
-        boolean isAir;
-        boolean ignitedByLava;
-        @Deprecated
-        boolean liquid;
-        @Deprecated
-        boolean forceSolidOff;
-        boolean forceSolidOn;
-        PushReaction pushReaction = PushReaction.NORMAL;
-        boolean spawnTerrainParticles = true;
-        NoteBlockInstrument instrument = NoteBlockInstrument.HARP;
-        boolean replaceable;
-        BlockBehaviour.StateArgumentPredicate<EntityType<?>> isValidSpawn = (state, world, pos, type) -> state.isFaceSturdy(world, pos, Direction.UP)
-                && state.getLightEmission() < 14;
-        BlockBehaviour.StatePredicate isRedstoneConductor = (state, world, pos) -> state.isCollisionShapeFullBlock(world, pos);
-        BlockBehaviour.StatePredicate isSuffocating = (state, world, pos) -> state.blocksMotion() && state.isCollisionShapeFullBlock(world, pos);
-        BlockBehaviour.StatePredicate isViewBlocking = this.isSuffocating;
-        BlockBehaviour.StatePredicate hasPostProcess = (state, world, pos) -> false;
-        BlockBehaviour.StatePredicate emissiveRendering = (state, world, pos) -> false;
-        boolean dynamicShape;
-        FeatureFlagSet requiredFeatures = FeatureFlags.VANILLA_SET;
-        Optional<BlockBehaviour.OffsetFunction> offsetFunction = Optional.empty();
 
-        private Properties() {
-        }
-
-        public static BlockBehaviour.Properties of() {
-            return new BlockBehaviour.Properties();
-        }
-
-        public static BlockBehaviour.Properties ofFullCopy(BlockBehaviour block) {
-            BlockBehaviour.Properties properties = ofLegacyCopy(block);
-            BlockBehaviour.Properties properties2 = block.properties;
-            properties.jumpFactor = properties2.jumpFactor;
-            properties.isRedstoneConductor = properties2.isRedstoneConductor;
-            properties.isValidSpawn = properties2.isValidSpawn;
-            properties.hasPostProcess = properties2.hasPostProcess;
-            properties.isSuffocating = properties2.isSuffocating;
-            properties.isViewBlocking = properties2.isViewBlocking;
-            properties.drops = properties2.drops;
-            return properties;
-        }
-
-        @Deprecated
-        public static BlockBehaviour.Properties ofLegacyCopy(BlockBehaviour block) {
-            BlockBehaviour.Properties properties = new BlockBehaviour.Properties();
-            BlockBehaviour.Properties properties2 = block.properties;
-            properties.destroyTime = properties2.destroyTime;
-            properties.explosionResistance = properties2.explosionResistance;
-            properties.hasCollision = properties2.hasCollision;
-            properties.isRandomlyTicking = properties2.isRandomlyTicking;
-            properties.lightEmission = properties2.lightEmission;
-            properties.mapColor = properties2.mapColor;
-            properties.soundType = properties2.soundType;
-            properties.friction = properties2.friction;
-            properties.speedFactor = properties2.speedFactor;
-            properties.dynamicShape = properties2.dynamicShape;
-            properties.canOcclude = properties2.canOcclude;
-            properties.isAir = properties2.isAir;
-            properties.ignitedByLava = properties2.ignitedByLava;
-            properties.liquid = properties2.liquid;
-            properties.forceSolidOff = properties2.forceSolidOff;
-            properties.forceSolidOn = properties2.forceSolidOn;
-            properties.pushReaction = properties2.pushReaction;
-            properties.requiresCorrectToolForDrops = properties2.requiresCorrectToolForDrops;
-            properties.offsetFunction = properties2.offsetFunction;
-            properties.spawnTerrainParticles = properties2.spawnTerrainParticles;
-            properties.requiredFeatures = properties2.requiredFeatures;
-            properties.emissiveRendering = properties2.emissiveRendering;
-            properties.instrument = properties2.instrument;
-            properties.replaceable = properties2.replaceable;
-            return properties;
-        }
-
-        public BlockBehaviour.Properties mapColor(DyeColor color) {
-            this.mapColor = state -> color.getMapColor();
-            return this;
-        }
-
-        public BlockBehaviour.Properties mapColor(MapColor color) {
-            this.mapColor = state -> color;
-            return this;
-        }
-
-        public BlockBehaviour.Properties mapColor(Function<BlockState, MapColor> mapColorProvider) {
-            this.mapColor = mapColorProvider;
-            return this;
-        }
-
-        public BlockBehaviour.Properties noCollission() {
-            this.hasCollision = false;
-            this.canOcclude = false;
-            return this;
-        }
-
-        public BlockBehaviour.Properties noOcclusion() {
-            this.canOcclude = false;
-            return this;
-        }
-
-        public BlockBehaviour.Properties friction(float slipperiness) {
-            this.friction = slipperiness;
-            return this;
-        }
-
-        public BlockBehaviour.Properties speedFactor(float velocityMultiplier) {
-            this.speedFactor = velocityMultiplier;
-            return this;
-        }
-
-        public BlockBehaviour.Properties jumpFactor(float jumpVelocityMultiplier) {
-            this.jumpFactor = jumpVelocityMultiplier;
-            return this;
-        }
-
-        public BlockBehaviour.Properties sound(SoundType soundGroup) {
-            this.soundType = soundGroup;
-            return this;
-        }
-
-        public BlockBehaviour.Properties lightLevel(ToIntFunction<BlockState> luminance) {
-            this.lightEmission = luminance;
-            return this;
-        }
-
-        public BlockBehaviour.Properties strength(float hardness, float resistance) {
-            return this.destroyTime(hardness).explosionResistance(resistance);
-        }
-
-        public BlockBehaviour.Properties instabreak() {
-            return this.strength(0.0F);
-        }
-
-        public BlockBehaviour.Properties strength(float strength) {
-            this.strength(strength, strength);
-            return this;
-        }
-
-        public BlockBehaviour.Properties randomTicks() {
-            this.isRandomlyTicking = true;
-            return this;
-        }
-
-        public BlockBehaviour.Properties dynamicShape() {
-            this.dynamicShape = true;
-            return this;
-        }
-
-        public BlockBehaviour.Properties noLootTable() {
-            this.drops = BuiltInLootTables.EMPTY;
-            return this;
-        }
-
-        public BlockBehaviour.Properties dropsLike(Block source) {
-            this.drops = source.getLootTable();
-            return this;
-        }
-
-        public BlockBehaviour.Properties ignitedByLava() {
-            this.ignitedByLava = true;
-            return this;
-        }
-
-        public BlockBehaviour.Properties liquid() {
-            this.liquid = true;
-            return this;
-        }
-
-        public BlockBehaviour.Properties forceSolidOn() {
-            this.forceSolidOn = true;
-            return this;
-        }
-
-        @Deprecated
-        public BlockBehaviour.Properties forceSolidOff() {
-            this.forceSolidOff = true;
-            return this;
-        }
-
-        public BlockBehaviour.Properties pushReaction(PushReaction pistonBehavior) {
-            this.pushReaction = pistonBehavior;
-            return this;
-        }
-
-        public BlockBehaviour.Properties air() {
-            this.isAir = true;
-            return this;
-        }
-
-        public BlockBehaviour.Properties isValidSpawn(BlockBehaviour.StateArgumentPredicate<EntityType<?>> predicate) {
-            this.isValidSpawn = predicate;
-            return this;
-        }
-
-        public BlockBehaviour.Properties isRedstoneConductor(BlockBehaviour.StatePredicate predicate) {
-            this.isRedstoneConductor = predicate;
-            return this;
-        }
-
-        public BlockBehaviour.Properties isSuffocating(BlockBehaviour.StatePredicate predicate) {
-            this.isSuffocating = predicate;
-            return this;
-        }
-
-        public BlockBehaviour.Properties isViewBlocking(BlockBehaviour.StatePredicate predicate) {
-            this.isViewBlocking = predicate;
-            return this;
-        }
-
-        public BlockBehaviour.Properties hasPostProcess(BlockBehaviour.StatePredicate predicate) {
-            this.hasPostProcess = predicate;
-            return this;
-        }
-
-        public BlockBehaviour.Properties emissiveRendering(BlockBehaviour.StatePredicate predicate) {
-            this.emissiveRendering = predicate;
-            return this;
-        }
-
-        public BlockBehaviour.Properties requiresCorrectToolForDrops() {
-            this.requiresCorrectToolForDrops = true;
-            return this;
-        }
-
-        public BlockBehaviour.Properties destroyTime(float hardness) {
-            this.destroyTime = hardness;
-            return this;
-        }
-
-        public BlockBehaviour.Properties explosionResistance(float resistance) {
-            this.explosionResistance = Math.max(0.0F, resistance);
-            return this;
-        }
-
-        public BlockBehaviour.Properties offsetType(BlockBehaviour.OffsetType offsetType) {
-            switch (offsetType) {
-                case XZ:
-                    this.offsetFunction = Optional.of((state, world, pos) -> {
-                        Block block = state.getBlock();
-                        long l = Mth.getSeed(pos.getX(), 0, pos.getZ());
-                        float f = block.getMaxHorizontalOffset();
-                        double d = Mth.clamp(((double)((float)(l & 15L) / 15.0F) - 0.5) * 0.5, (double)(-f), (double)f);
-                        double e = Mth.clamp(((double)((float)(l >> 8 & 15L) / 15.0F) - 0.5) * 0.5, (double)(-f), (double)f);
-                        return new Vec3(d, 0.0, e);
-                    });
-                    break;
-                case XYZ:
-                    this.offsetFunction = Optional.of((state, world, pos) -> {
-                        Block block = state.getBlock();
-                        long l = Mth.getSeed(pos.getX(), 0, pos.getZ());
-                        double d = ((double)((float)(l >> 4 & 15L) / 15.0F) - 1.0) * (double)block.getMaxVerticalOffset();
-                        float f = block.getMaxHorizontalOffset();
-                        double e = Mth.clamp(((double)((float)(l & 15L) / 15.0F) - 0.5) * 0.5, (double)(-f), (double)f);
-                        double g = Mth.clamp(((double)((float)(l >> 8 & 15L) / 15.0F) - 0.5) * 0.5, (double)(-f), (double)f);
-                        return new Vec3(e, d, g);
-                    });
-                    break;
-                default:
-                    this.offsetFunction = Optional.empty();
-            }
-
-            return this;
-        }
-
-        public BlockBehaviour.Properties noTerrainParticles() {
-            this.spawnTerrainParticles = false;
-            return this;
-        }
-
-        public BlockBehaviour.Properties requiredFeatures(FeatureFlag... features) {
-            this.requiredFeatures = FeatureFlags.REGISTRY.subset(features);
-            return this;
-        }
-
-        public BlockBehaviour.Properties instrument(NoteBlockInstrument instrument) {
-            this.instrument = instrument;
-            return this;
-        }
-
-        public BlockBehaviour.Properties replaceable() {
-            this.replaceable = true;
-            return this;
-        }
-    }
-
-    public interface StateArgumentPredicate<A> {
-        boolean test(BlockState state, BlockGetter world, BlockPos pos, A type);
-    }
+        NONE, XZ, XYZ;
 
-    public interface StatePredicate {
-        boolean test(BlockState state, BlockGetter world, BlockPos pos);
+        private OffsetType() {}
     }
 }
