diff --git a/game/client/src/main/java/net/minecraft/client/gui/container/ScreenContainerAbstract.java b/game/client/src/main/java/net/minecraft/client/gui/container/ScreenContainerAbstract.java
index dcd58660a..35af4f5ae 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/container/ScreenContainerAbstract.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/container/ScreenContainerAbstract.java
@@ -262,6 +262,10 @@ public abstract class ScreenContainerAbstract extends Screen {
         if (!this.mc.thePlayer.getGamemode().hasBlockConsumption()) {
             // Duplicate items with middle mouse button
             if (buttonNum == 2) {
+                if (GameSettings.ENABLE_ITEM_DRAGGING.value) {
+                    this.itemDragHandler.mouseEvent(mx, my, buttonNum, true);
+                    return;
+                }
                 Slot slot = this.inventorySlots.getSlot(getSlotId(mx, my));
                 if (slot != null) {
                     if (slot.getItemStack() == null) {
diff --git a/game/client/src/main/java/net/minecraft/client/player/controller/PlayerController.java b/game/client/src/main/java/net/minecraft/client/player/controller/PlayerController.java
index a9a0ac06d..f3594f92a 100644
--- a/game/client/src/main/java/net/minecraft/client/player/controller/PlayerController.java
+++ b/game/client/src/main/java/net/minecraft/client/player/controller/PlayerController.java
@@ -58,6 +58,9 @@ public abstract class PlayerController {
             return;
         }
 
+        final @NotNull Block<?> mineBlock = world.getBlockType(tilePos);
+        if (!mineBlock.canCollideCheck(world.getBlockData(tilePos), false)) return;
+
         if (!player.getGamemode().hasBlockBreakingAnimation()) {
             this.swingItem(true);
 
@@ -71,7 +74,6 @@ public abstract class PlayerController {
             return;
         }
 
-        final @NotNull Block<?> mineBlock = world.getBlockType(tilePos);
         final float mineStrength = mineBlock.getStrength(world, tilePos, side, player);
 
         if (mineStrength < 1.0f) {
diff --git a/game/client/src/main/java/net/minecraft/client/render/WorldRenderer.java b/game/client/src/main/java/net/minecraft/client/render/WorldRenderer.java
index fd5b5ea7f..a7ea30810 100644
--- a/game/client/src/main/java/net/minecraft/client/render/WorldRenderer.java
+++ b/game/client/src/main/java/net/minecraft/client/render/WorldRenderer.java
@@ -22,7 +22,6 @@ import org.joml.Matrix4f;
 import org.joml.Vector3d;
 import org.joml.Vector3dc;
 import org.joml.primitives.AABBd;
-import org.joml.primitives.AABBdc;
 import org.lwjgl.input.Mouse;
 
 import net.minecraft.client.Minecraft;
@@ -47,10 +46,12 @@ import net.minecraft.client.render.tessellator.TessellatorGeneral;
 import net.minecraft.client.render.texture.stitcher.TextureRegistry;
 import net.minecraft.core.entity.Entity;
 import net.minecraft.core.entity.player.Player;
+import net.minecraft.core.item.IPlaceable;
 import net.minecraft.core.sound.SoundCategory;
 import net.minecraft.core.util.debug.Debug;
 import net.minecraft.core.util.helper.Direction;
 import net.minecraft.core.util.helper.MathHelper;
+import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.util.helper.Time;
 import net.minecraft.core.util.phys.HitResult;
 import net.minecraft.core.world.World;
@@ -127,23 +128,22 @@ public class WorldRenderer {
     }
 
     public void getMouseOver(float partialTicks) {
-        if (this.mc.activeCamera == null) {
-            return;
-        }
-        if (this.mc.currentWorld == null) {
-            return;
-        }
+        if (this.mc.activeCamera == null) return;
+        if (this.mc.currentWorld == null) return;
 
         final double blockReach = this.mc.thePlayer.getGamemode().getBlockReachDistance();
         final double entityReach = this.mc.thePlayer.getGamemode().getEntityReachDistance();
 
-        Vector3dc pos1 = this.mc.thePlayer.getPosition(partialTicks, true);
-        Vector3dc look = this.mc.thePlayer.getViewVector(partialTicks);
+        final Vector3dc pos1 = this.mc.thePlayer.getPosition(partialTicks, true);
+        final Vector3dc look = this.mc.thePlayer.getViewVector(partialTicks);
         assert look != null;
-        Vector3dc pos2 = pos1.add(look.x() * blockReach, look.y() * blockReach, look.z() * blockReach, new Vector3d());
+        final Vector3dc pos2 = new Vector3d(look).mul(blockReach).add(pos1);
+        
+        final var playerItem = this.mc.thePlayer.inventory.getCurrentItem();
+        final boolean interactsWithFluid = playerItem != null && playerItem.interactsWithFluid();
 
         // Raytrace blocks
-        this.mc.objectMouseOver = this.mc.thePlayer.rayCast(blockReach, partialTicks, false, false, true);
+        this.mc.objectMouseOver = this.mc.thePlayer.rayCast(blockReach, partialTicks, interactsWithFluid, false, true);
 //		mc.objectMouseOver = mc.currentWorld.checkBlockCollisionBetweenPoints(pos1, pos2, false, false, true);
 
         // Calculate again because block raytrace changes the values
@@ -151,28 +151,32 @@ public class WorldRenderer {
 //		look = mc.thePlayer.getViewVector(partialTicks);
 //		pos2 = pos1.add(look.x * entityReach, look.y * entityReach, look.z * entityReach);
 
-        double hitBlockDistance = 1000.0;
-        if (this.mc.objectMouseOver != null) {
-            hitBlockDistance = pos1.distance(this.mc.objectMouseOver.location);
-        }
+        final double hitBlockDistance = (this.mc.objectMouseOver != null)
+            ? pos1.distance(this.mc.objectMouseOver.location)
+            : Double.POSITIVE_INFINITY;
 
         this.pointedEntity = null;
-        float expand = 1.0f;
-        AABBd entitySearchBox = MathHelper.aabbExpand(this.mc.thePlayer.bb, look.x() * entityReach, look.y() * entityReach, look.z() * entityReach, new AABBd());
+        
+        final float expand = 1.0f;
+        final AABBd entitySearchBox = MathHelper.aabbExpand(this.mc.thePlayer.bb, look.x() * entityReach, look.y() * entityReach, look.z() * entityReach, new AABBd());
         MathHelper.aabbGrow(entitySearchBox, expand, expand, expand, entitySearchBox);
-
-        List<Entity> entities = this.mc.currentWorld.getEntitiesWithinAABBExcludingEntity(this.mc.thePlayer, entitySearchBox);
-
+        
         // Search for the closest hit entity
         double pointedEntityDistance = 0.0;
+
+        final var entities = this.mc.currentWorld.getEntitiesWithinAABBExcludingEntity(this.mc.thePlayer, entitySearchBox);
+        final AABBd hitBox = new AABBd();
         for (int i = 0; i < entities.size(); i++) {
-            Entity entity = entities.get(i);
-            if (!entity.isSelectable()) {
-                continue;
+            final Entity entity = entities.get(i);
+
+            if (playerItem != null) {
+                if (!playerItem.interactsWithEntity(entity)) continue;
+            } else {
+                if (!entity.isSelectable()) continue;
             }
 
-            float hitRadius = entity == this.mc.thePlayer.vehicle ? 0 : entity.getPickRadius();
-            AABBdc hitBox = MathHelper.aabbGrow(entity.bb, hitRadius, hitRadius, hitRadius, new AABBd());
+            final float hitRadius = entity == this.mc.thePlayer.vehicle ? 0 : entity.getPickRadius();
+            MathHelper.aabbGrow(entity.bb, hitRadius, hitRadius, hitRadius, hitBox);
 
             if (hitBox.containsPoint(pos1)) {
                 this.pointedEntity = entity;
@@ -180,12 +184,10 @@ public class WorldRenderer {
                 continue;
             }
 
-            HitResult hitResult = MathHelper.aabbClip(hitBox, pos1, pos2);
-            if (hitResult == null) {
-                continue;
-            }
+            final HitResult hitResult = MathHelper.aabbClip(hitBox, pos1, pos2);
+            if (hitResult == null) continue;
 
-            double entityDistance = pos1.distance(hitResult.location);
+            final double entityDistance = pos1.distance(hitResult.location);
             if (this.pointedEntity == null || entityDistance < pointedEntityDistance) {
                 this.pointedEntity = entity;
                 pointedEntityDistance = entityDistance;
@@ -207,7 +209,9 @@ public class WorldRenderer {
         if (!GameSettings.EASY_BRIDGE.value) return;
         if (!this.mc.thePlayer.onGround) return;
         if (Direction.getLockable(this.mc.thePlayer) != Direction.DOWN) return;
-        if (this.mc.thePlayer.inventory.getCurrentItem() == null) return;
+        final var item = this.mc.thePlayer.inventory.getCurrentItem();
+        if (item == null) return;
+        if (!(item.getItem() instanceof IPlaceable placeable)) return;
 
         this.mc.bridgePos.set(
             MathHelper.floor(this.mc.thePlayer.x),
@@ -215,7 +219,11 @@ public class WorldRenderer {
             MathHelper.floor(this.mc.thePlayer.z)
         );
 
-        this.mc.canBridge = this.mc.thePlayer.world.canPlaceInsideBlock(this.mc.bridgePos);
+        if (!placeable.canPlaceDirectlyAtPosition(
+            item, this.mc.currentWorld, this.mc.thePlayer, this.mc.bridgePos, Side.TOP, 
+            0.5, 0.5)) return;
+        
+        this.mc.canBridge = true;
     }
 
     private float getFOVModifier(float partialTick, boolean isModifiedByFOV) {
diff --git a/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererPlayer.java b/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererPlayer.java
index 271270fd2..be6db2908 100644
--- a/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererPlayer.java
+++ b/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererPlayer.java
@@ -76,7 +76,6 @@ public class MobRendererPlayer extends MobRendererBipedArmored<Player> {
             final TessellatorGeneral tessellator = GLRenderer.getTessellator();
             TextureRegistry.worldAtlas.bind();
             GLRenderer.pushFrame();
-            Lighting.disable();
 
             GLRenderer.setBlendFunc(BlendFactor.SRC_ALPHA, BlendFactor.ONE_MINUS_SRC_ALPHA);
             GLRenderer.enableState(State.BLEND);
diff --git a/game/client/src/main/java/net/minecraft/client/render/item/model/ItemModelBucket.java b/game/client/src/main/java/net/minecraft/client/render/item/model/ItemModelBucket.java
index 6bd95d81a..b54bf5588 100644
--- a/game/client/src/main/java/net/minecraft/client/render/item/model/ItemModelBucket.java
+++ b/game/client/src/main/java/net/minecraft/client/render/item/model/ItemModelBucket.java
@@ -1,5 +1,6 @@
 package net.minecraft.client.render.item.model;
 
+import net.minecraft.client.Minecraft;
 import net.minecraft.client.render.texture.stitcher.IconCoordinate;
 import net.minecraft.client.render.texture.stitcher.TextureRegistry;
 import net.minecraft.core.entity.Entity;
@@ -7,6 +8,7 @@ import net.minecraft.core.item.Item;
 import net.minecraft.core.item.ItemBucket;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.util.collection.NamespaceID;
+import net.minecraft.core.world.World;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -17,12 +19,21 @@ import java.util.Set;
 public abstract class ItemModelBucket extends ItemModelStandard {
 
     protected final Map<NamespaceID, IconCoordinate[]> bucketIcons = new HashMap<>();
+    private final Map<String, IconCoordinate[]> textureIconCache = new HashMap<>();
+
+    protected String iconNamespace;
+    protected String iconPrefix;
+    protected int iconMaxCharges;
 
     public ItemModelBucket(@NotNull Item item, boolean defaultTextureLookup) {
         super(item, defaultTextureLookup);
     }
 
     protected void initializeIcons(@NotNull String namespace, @NotNull String prefix, int maxCharges, @NotNull Set<@NotNull NamespaceID> validStates) {
+        this.iconNamespace = namespace;
+        this.iconPrefix = prefix;
+        this.iconMaxCharges = maxCharges;
+
         IconCoordinate[] emptyArray = new IconCoordinate[1];
         emptyArray[0] = TextureRegistry.getTexture(namespace + ":item/" + prefix + "/empty");
         this.bucketIcons.put(ItemBucket.STATE_EMPTY, emptyArray);
@@ -41,6 +52,17 @@ public abstract class ItemModelBucket extends ItemModelStandard {
         }
     }
 
+    private @NotNull IconCoordinate[] getChargeIcons(final @NotNull String textureStateName, final int maxCharges) {
+        return textureIconCache.computeIfAbsent(textureStateName, name -> {
+            IconCoordinate[] chargesArray = new IconCoordinate[maxCharges + 1];
+            for (int c = 1; c <= maxCharges; c++) {
+                String path = iconNamespace + ":item/" + iconPrefix + "/" + name + "_" + c;
+                chargesArray[c] = TextureRegistry.getTexture(path);
+            }
+            return chargesArray;
+        });
+    }
+
     @Override
     public @NotNull IconCoordinate getIcon(@Nullable Entity entity, @NotNull ItemStack itemStack) {
         NamespaceID state = ItemBucket.getState(itemStack);
@@ -57,7 +79,24 @@ public abstract class ItemModelBucket extends ItemModelStandard {
         }
 
         charges = Math.max(0, Math.min(charges, maxForThisBucket));
-        IconCoordinate[] iconsForState = bucketIcons.get(state);
+
+        if (ItemBucket.STATE_EMPTY.equals(state)) {
+            IconCoordinate[] emptyIcons = bucketIcons.get(ItemBucket.STATE_EMPTY);
+            if (emptyIcons != null && charges < emptyIcons.length) {
+                IconCoordinate emptyIcon = emptyIcons[charges];
+                if (emptyIcon != null) {
+                    return emptyIcon;
+                }
+            }
+            return super.getIcon(entity, itemStack);
+        }
+
+        final World world = entity != null ? entity.world : Minecraft.getMinecraft().currentWorld;
+        final String textureStateName = ItemBucket.getTextureStateName(entity, world, itemStack);
+        final String defaultTextureStateName = ItemBucket.getStateName(state);
+        IconCoordinate[] iconsForState = textureStateName.equals(defaultTextureStateName)
+            ? bucketIcons.get(state)
+            : getChargeIcons(textureStateName, maxForThisBucket);
 
         if (iconsForState == null || charges >= iconsForState.length) {
             return super.getIcon(entity, itemStack);
@@ -66,4 +105,4 @@ public abstract class ItemModelBucket extends ItemModelStandard {
         IconCoordinate icon = iconsForState[charges];
         return (icon != null) ? icon : super.getIcon(entity, itemStack);
     }
-}
\ No newline at end of file
+}
diff --git a/game/client/src/main/java/net/minecraft/client/render/item/model/ItemModelDispatcher.java b/game/client/src/main/java/net/minecraft/client/render/item/model/ItemModelDispatcher.java
index 87b3af8b0..9194db182 100644
--- a/game/client/src/main/java/net/minecraft/client/render/item/model/ItemModelDispatcher.java
+++ b/game/client/src/main/java/net/minecraft/client/render/item/model/ItemModelDispatcher.java
@@ -10,15 +10,15 @@ import net.minecraft.core.item.block.ItemBlock;
 import org.useless.dragonfly.DisplayPos;
 
 public class ItemModelDispatcher extends Dispatcher<Item, ItemModel> {
-    protected static final DisplayPos HANDHELD_FIRST_PERSON_RIGHT_HAND = new DisplayPos(1.13f/16f, 3.2f/16f, 1.13f/16f, 0, -90, 25, 0.68f, 0.68f, 0.68f);
-    protected static final DisplayPos HANDHELD_FIRST_PERSON_LEFT_HAND = new DisplayPos(1.13f/16f, 3.2f/16f, 1.13f/16f, 0, 90, -25, 0.68f, 0.68f, 0.68f);
-    protected static final DisplayPos HANDHELD_THIRD_PERSON_RIGHT_HAND = new DisplayPos(0, 4.0f/16f, 0.5f/16f, 0, -90, 55, 0.85f, 0.85f, 0.85f);
-    protected static final DisplayPos HANDHELD_THIRD_PERSON_LEFT_HAND = new DisplayPos(0, 4.0f/16f, 0.5f/16f, 0,  90, -55, 0.85f, 0.85f, 0.85f);
-
-    protected static final DisplayPos HANDHELD_ROD_FIRST_PERSON_RIGHT_HAND = new DisplayPos(0/16f, 1.6f/16f, 0.8f/16f, 0, 90, 25, 0.68f, 0.68f, 0.68f);
-    protected static final DisplayPos HANDHELD_ROD_FIRST_PERSON_LEFT_HAND = new DisplayPos(0/16f, 1.6f/16f, 0.8f/16f, 0, -90, -25, 0.68f, 0.68f, 0.68f);
-    protected static final DisplayPos HANDHELD_ROD_THIRD_PERSON_RIGHT_HAND = new DisplayPos(0, 4.0f/16f, 2.5f/16f, 0, 90, 55, 0.85f, 0.85f, 0.85f);
-    protected static final DisplayPos HANDHELD_ROD_THIRD_PERSON_LEFT_HAND = new DisplayPos(0, 4.0f/16f, 2.5f/16f, 0,  -90, -55, 0.85f, 0.85f, 0.85f);
+    public static final DisplayPos HANDHELD_FIRST_PERSON_RIGHT_HAND = new DisplayPos(1.13f/16f, 3.2f/16f, 1.13f/16f, 0, -90, 25, 0.68f, 0.68f, 0.68f);
+    public static final DisplayPos HANDHELD_FIRST_PERSON_LEFT_HAND = new DisplayPos(1.13f/16f, 3.2f/16f, 1.13f/16f, 0, 90, -25, 0.68f, 0.68f, 0.68f);
+    public static final DisplayPos HANDHELD_THIRD_PERSON_RIGHT_HAND = new DisplayPos(0, 4.0f/16f, 0.5f/16f, 0, -90, 55, 0.85f, 0.85f, 0.85f);
+    public static final DisplayPos HANDHELD_THIRD_PERSON_LEFT_HAND = new DisplayPos(0, 4.0f/16f, 0.5f/16f, 0,  90, -55, 0.85f, 0.85f, 0.85f);
+
+    public static final DisplayPos HANDHELD_ROD_FIRST_PERSON_RIGHT_HAND = new DisplayPos(0/16f, 1.6f/16f, 0.8f/16f, 0, 90, 25, 0.68f, 0.68f, 0.68f);
+    public static final DisplayPos HANDHELD_ROD_FIRST_PERSON_LEFT_HAND = new DisplayPos(0/16f, 1.6f/16f, 0.8f/16f, 0, -90, -25, 0.68f, 0.68f, 0.68f);
+    public static final DisplayPos HANDHELD_ROD_THIRD_PERSON_RIGHT_HAND = new DisplayPos(0, 4.0f/16f, 2.5f/16f, 0, 90, 55, 0.85f, 0.85f, 0.85f);
+    public static final DisplayPos HANDHELD_ROD_THIRD_PERSON_LEFT_HAND = new DisplayPos(0, 4.0f/16f, 2.5f/16f, 0,  -90, -55, 0.85f, 0.85f, 0.85f);
 
     private static ItemModelDispatcher instance = new ItemModelDispatcher();
     public static final ItemModel emptyModel = new ItemModelNone();
diff --git a/game/client/src/main/java/net/minecraft/client/render/tileentity/TileEntityRendererJarButterfly.java b/game/client/src/main/java/net/minecraft/client/render/tileentity/TileEntityRendererJarButterfly.java
index c813ce39b..0b5f21272 100644
--- a/game/client/src/main/java/net/minecraft/client/render/tileentity/TileEntityRendererJarButterfly.java
+++ b/game/client/src/main/java/net/minecraft/client/render/tileentity/TileEntityRendererJarButterfly.java
@@ -13,6 +13,6 @@ public class TileEntityRendererJarButterfly extends TileEntityRenderer<TileEntit
         boolean hanging = (meta & BlockLogicJarButterfly.MASK_HANGING) != 0;
         MobRendererButterfly.drawButterfly(tessellator, tileEntity.ticksRan, 0.70f, 0,
             x + 0.5, y + (hanging ? 6/16f : 0) - 0.125 + MathHelper.sin((tileEntity.ticksRan + partialTick)/20) * (0.0625), z + 0.5,
-            partialTick, this.renderDispatcher.textureManager, "/assets/minecraft/textures/entity/butterfly/" + tileEntity.color.name + "/0_0.png");
+            partialTick, this.renderDispatcher.textureManager, "/assets/minecraft/textures/entity/butterfly/" + tileEntity.color.name() + "/0_0.png");
     }
 }
diff --git a/game/client/src/main/java/org/useless/dragonfly/data/block/mojang/BlockModelMojangData.java b/game/client/src/main/java/org/useless/dragonfly/data/block/mojang/BlockModelMojangData.java
index 2e0aec760..76aa73368 100644
--- a/game/client/src/main/java/org/useless/dragonfly/data/block/mojang/BlockModelMojangData.java
+++ b/game/client/src/main/java/org/useless/dragonfly/data/block/mojang/BlockModelMojangData.java
@@ -129,7 +129,10 @@ public class BlockModelMojangData implements BlockModelData {
                 this.renderLayer = (parentData.renderLayer != null && renderLayer == null) ? parentData.renderLayer : renderLayer;
 
                 if (parentData.displayPositions != null) this.displayPositions.putAll(parentData.displayPositions);
-                if (displayPositions != null) this.displayPositions.putAll(displayPositions);
+                if (displayPositions != null) {
+                    this.displayPositions.putAll(displayPositions);
+                    generateOtherHandPoses(displayPositions);
+                }
                 if (parentData.textures != null) this.textures.putAll(parentData.textures);
                 if (textures != null) this.textures.putAll(textures);
                 if (parentData.particleIndices != null) this.particleIndices.putAll(parentData.particleIndices);
@@ -137,6 +140,7 @@ public class BlockModelMojangData implements BlockModelData {
             } else {
                 this.ambientOcclusion = ambientOcclusion == null ? DEFAULT_AMBIENT_OCCLUSION : ambientOcclusion;
                 this.displayPositions = displayPositions;
+                if (displayPositions != null) generateOtherHandPoses(displayPositions);
                 this.particleIndices = particleIndices;
                 this.renderLayer = renderLayer;
                 this.textures = textures;
@@ -145,12 +149,25 @@ public class BlockModelMojangData implements BlockModelData {
         } else {
             this.ambientOcclusion = ambientOcclusion == null ? DEFAULT_AMBIENT_OCCLUSION : ambientOcclusion;
             this.displayPositions = displayPositions;
+            if (displayPositions != null) generateOtherHandPoses(displayPositions);
             this.particleIndices = particleIndices;
             this.renderLayer = renderLayer;
             this.textures = textures;
             this.elements = elements;
         }
+    }
 
+    // Mojang will automatically transfer right handed overrides to the left hand, even if the parent model has an existing definition for them
+    private void generateOtherHandPoses(@NotNull Map<String, DisplayPos> overridePoses) {
+        assert this.displayPositions != null : "should only be called when this.displayPositions definitely exists!";
+        if (overridePoses.containsKey(DisplayPos.FIRST_PERSON_RIGHT_HAND) && !overridePoses.containsKey(DisplayPos.FIRST_PERSON_LEFT_HAND)) {
+            var right = overridePoses.get(DisplayPos.FIRST_PERSON_RIGHT_HAND);
+            this.displayPositions.put(DisplayPos.FIRST_PERSON_LEFT_HAND, right);
+        }
+        if (overridePoses.containsKey(DisplayPos.THIRD_PERSON_RIGHT_HAND) && !overridePoses.containsKey(DisplayPos.THIRD_PERSON_LEFT_HAND)) {
+            var right = overridePoses.get(DisplayPos.THIRD_PERSON_RIGHT_HAND);
+            this.displayPositions.put(DisplayPos.THIRD_PERSON_LEFT_HAND, right);
+        }
     }
 
     @Override
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/activator_active.json b/game/client/src/main/resources/assets/minecraft/models/block/activator_active.json
index 9c8fa7ed3..f397922f7 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/activator_active.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/activator_active.json
@@ -1,11 +1,9 @@
 {
-  "parent": "minecraft:block/cube",
+  "parent": "minecraft:block/orientable_with_bottom",
   "textures": {
-    "up": "minecraft:block/activator/top_active",
-    "down": "minecraft:block/activator/bottom_active",
-    "south": "minecraft:block/activator/side_active",
-    "north": "minecraft:block/activator/front_active",
-    "west": "minecraft:block/activator/side_active",
-    "east": "minecraft:block/activator/side_active"
+    "top": "minecraft:block/activator/top_active",
+    "bottom": "minecraft:block/activator/bottom_active",
+    "front": "minecraft:block/activator/front_active",
+    "side": "minecraft:block/activator/side_active"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/activator_idle.json b/game/client/src/main/resources/assets/minecraft/models/block/activator_idle.json
index 27baf99d8..2887658c3 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/activator_idle.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/activator_idle.json
@@ -1,11 +1,9 @@
 {
-  "parent": "minecraft:block/cube",
+  "parent": "minecraft:block/orientable_with_bottom",
   "textures": {
-    "up": "minecraft:block/activator/top",
-    "down": "minecraft:block/activator/bottom",
-    "south": "minecraft:block/activator/side",
-    "north": "minecraft:block/activator/front",
-    "west": "minecraft:block/activator/side",
-    "east": "minecraft:block/activator/side"
+    "top": "minecraft:block/activator/top",
+    "bottom": "minecraft:block/activator/bottom",
+    "front": "minecraft:block/activator/front",
+    "side": "minecraft:block/activator/side"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/chest/single/black.json b/game/client/src/main/resources/assets/minecraft/models/block/chest/single/black.json
index 868c24d60..9d451473a 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/chest/single/black.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/chest/single/black.json
@@ -1,11 +1,8 @@
 {
-  "parent": "minecraft:block/cube",
+  "parent": "minecraft:block/orientable",
   "textures": {
-    "up": "minecraft:block/chest/planks_black/top",
-    "down": "minecraft:block/chest/planks_black/top",
-    "south": "minecraft:block/chest/planks_black/side",
-    "north": "minecraft:block/chest/planks_black/front",
-    "west": "minecraft:block/chest/planks_black/side",
-    "east": "minecraft:block/chest/planks_black/side"
+    "top": "minecraft:block/chest/planks_black/top",
+    "front": "minecraft:block/chest/planks_black/front",
+    "side": "minecraft:block/chest/planks_black/side"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/chest/single/blue.json b/game/client/src/main/resources/assets/minecraft/models/block/chest/single/blue.json
index 5e36ee04e..9a2d6e77c 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/chest/single/blue.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/chest/single/blue.json
@@ -1,11 +1,8 @@
 {
-  "parent": "minecraft:block/cube",
+  "parent": "minecraft:block/orientable",
   "textures": {
-    "up": "minecraft:block/chest/planks_blue/top",
-    "down": "minecraft:block/chest/planks_blue/top",
-    "south": "minecraft:block/chest/planks_blue/side",
-    "north": "minecraft:block/chest/planks_blue/front",
-    "west": "minecraft:block/chest/planks_blue/side",
-    "east": "minecraft:block/chest/planks_blue/side"
+    "top": "minecraft:block/chest/planks_blue/top",
+    "front": "minecraft:block/chest/planks_blue/front",
+    "side": "minecraft:block/chest/planks_blue/side"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/chest/single/brown.json b/game/client/src/main/resources/assets/minecraft/models/block/chest/single/brown.json
index d64a68821..1206d0fe8 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/chest/single/brown.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/chest/single/brown.json
@@ -1,11 +1,8 @@
 {
-  "parent": "minecraft:block/cube",
+  "parent": "minecraft:block/orientable",
   "textures": {
-    "up": "minecraft:block/chest/planks_brown/top",
-    "down": "minecraft:block/chest/planks_brown/top",
-    "south": "minecraft:block/chest/planks_brown/side",
-    "north": "minecraft:block/chest/planks_brown/front",
-    "west": "minecraft:block/chest/planks_brown/side",
-    "east": "minecraft:block/chest/planks_brown/side"
+    "top": "minecraft:block/chest/planks_brown/top",
+    "front": "minecraft:block/chest/planks_brown/front",
+    "side": "minecraft:block/chest/planks_brown/side"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/chest/single/cyan.json b/game/client/src/main/resources/assets/minecraft/models/block/chest/single/cyan.json
index 17762aa8a..fe576c3f3 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/chest/single/cyan.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/chest/single/cyan.json
@@ -1,11 +1,8 @@
 {
-  "parent": "minecraft:block/cube",
+  "parent": "minecraft:block/orientable",
   "textures": {
-    "up": "minecraft:block/chest/planks_cyan/top",
-    "down": "minecraft:block/chest/planks_cyan/top",
-    "south": "minecraft:block/chest/planks_cyan/side",
-    "north": "minecraft:block/chest/planks_cyan/front",
-    "west": "minecraft:block/chest/planks_cyan/side",
-    "east": "minecraft:block/chest/planks_cyan/side"
+    "top": "minecraft:block/chest/planks_cyan/top",
+    "front": "minecraft:block/chest/planks_cyan/front",
+    "side": "minecraft:block/chest/planks_cyan/side"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/chest/single/gray.json b/game/client/src/main/resources/assets/minecraft/models/block/chest/single/gray.json
index 935596991..39bf24dad 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/chest/single/gray.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/chest/single/gray.json
@@ -1,11 +1,8 @@
 {
-  "parent": "minecraft:block/cube",
+  "parent": "minecraft:block/orientable",
   "textures": {
-    "up": "minecraft:block/chest/planks_gray/top",
-    "down": "minecraft:block/chest/planks_gray/top",
-    "south": "minecraft:block/chest/planks_gray/side",
-    "north": "minecraft:block/chest/planks_gray/front",
-    "west": "minecraft:block/chest/planks_gray/side",
-    "east": "minecraft:block/chest/planks_gray/side"
+    "top": "minecraft:block/chest/planks_gray/top",
+    "front": "minecraft:block/chest/planks_gray/front",
+    "side": "minecraft:block/chest/planks_gray/side"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/chest/single/green.json b/game/client/src/main/resources/assets/minecraft/models/block/chest/single/green.json
index ea5fd9a06..70d51df78 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/chest/single/green.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/chest/single/green.json
@@ -1,11 +1,8 @@
 {
-  "parent": "minecraft:block/cube",
+  "parent": "minecraft:block/orientable",
   "textures": {
-    "up": "minecraft:block/chest/planks_green/top",
-    "down": "minecraft:block/chest/planks_green/top",
-    "south": "minecraft:block/chest/planks_green/side",
-    "north": "minecraft:block/chest/planks_green/front",
-    "west": "minecraft:block/chest/planks_green/side",
-    "east": "minecraft:block/chest/planks_green/side"
+    "top": "minecraft:block/chest/planks_green/top",
+    "front": "minecraft:block/chest/planks_green/front",
+    "side": "minecraft:block/chest/planks_green/side"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/chest/single/lightblue.json b/game/client/src/main/resources/assets/minecraft/models/block/chest/single/lightblue.json
index fa6a3e1e2..bac6b9094 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/chest/single/lightblue.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/chest/single/lightblue.json
@@ -1,11 +1,8 @@
 {
-  "parent": "minecraft:block/cube",
+  "parent": "minecraft:block/orientable",
   "textures": {
-    "up": "minecraft:block/chest/planks_lightblue/top",
-    "down": "minecraft:block/chest/planks_lightblue/top",
-    "south": "minecraft:block/chest/planks_lightblue/side",
-    "north": "minecraft:block/chest/planks_lightblue/front",
-    "west": "minecraft:block/chest/planks_lightblue/side",
-    "east": "minecraft:block/chest/planks_lightblue/side"
+    "top": "minecraft:block/chest/planks_lightblue/top",
+    "front": "minecraft:block/chest/planks_lightblue/front",
+    "side": "minecraft:block/chest/planks_lightblue/side"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/chest/single/lime.json b/game/client/src/main/resources/assets/minecraft/models/block/chest/single/lime.json
index fc28f69b3..2d51d40e5 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/chest/single/lime.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/chest/single/lime.json
@@ -1,11 +1,8 @@
 {
-  "parent": "minecraft:block/cube",
+  "parent": "minecraft:block/orientable",
   "textures": {
-    "up": "minecraft:block/chest/planks_lime/top",
-    "down": "minecraft:block/chest/planks_lime/top",
-    "south": "minecraft:block/chest/planks_lime/side",
-    "north": "minecraft:block/chest/planks_lime/front",
-    "west": "minecraft:block/chest/planks_lime/side",
-    "east": "minecraft:block/chest/planks_lime/side"
+    "top": "minecraft:block/chest/planks_lime/top",
+    "front": "minecraft:block/chest/planks_lime/front",
+    "side": "minecraft:block/chest/planks_lime/side"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/chest/single/magenta.json b/game/client/src/main/resources/assets/minecraft/models/block/chest/single/magenta.json
index 43099184a..1ca4c294f 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/chest/single/magenta.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/chest/single/magenta.json
@@ -1,11 +1,8 @@
 {
-  "parent": "minecraft:block/cube",
+  "parent": "minecraft:block/orientable",
   "textures": {
-    "up": "minecraft:block/chest/planks_magenta/top",
-    "down": "minecraft:block/chest/planks_magenta/top",
-    "south": "minecraft:block/chest/planks_magenta/side",
-    "north": "minecraft:block/chest/planks_magenta/front",
-    "west": "minecraft:block/chest/planks_magenta/side",
-    "east": "minecraft:block/chest/planks_magenta/side"
+    "top": "minecraft:block/chest/planks_magenta/top",
+    "front": "minecraft:block/chest/planks_magenta/front",
+    "side": "minecraft:block/chest/planks_magenta/side"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/chest/single/orange.json b/game/client/src/main/resources/assets/minecraft/models/block/chest/single/orange.json
index fb945d6c4..39ab0c95d 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/chest/single/orange.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/chest/single/orange.json
@@ -1,11 +1,8 @@
 {
-  "parent": "minecraft:block/cube",
+  "parent": "minecraft:block/orientable",
   "textures": {
-    "up": "minecraft:block/chest/planks_orange/top",
-    "down": "minecraft:block/chest/planks_orange/top",
-    "south": "minecraft:block/chest/planks_orange/side",
-    "north": "minecraft:block/chest/planks_orange/front",
-    "west": "minecraft:block/chest/planks_orange/side",
-    "east": "minecraft:block/chest/planks_orange/side"
+    "top": "minecraft:block/chest/planks_orange/top",
+    "front": "minecraft:block/chest/planks_orange/front",
+    "side": "minecraft:block/chest/planks_orange/side"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/chest/single/pink.json b/game/client/src/main/resources/assets/minecraft/models/block/chest/single/pink.json
index 7b864c7e1..278750711 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/chest/single/pink.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/chest/single/pink.json
@@ -1,11 +1,8 @@
 {
-  "parent": "minecraft:block/cube",
+  "parent": "minecraft:block/orientable",
   "textures": {
-    "up": "minecraft:block/chest/planks_pink/top",
-    "down": "minecraft:block/chest/planks_pink/top",
-    "south": "minecraft:block/chest/planks_pink/side",
-    "north": "minecraft:block/chest/planks_pink/front",
-    "west": "minecraft:block/chest/planks_pink/side",
-    "east": "minecraft:block/chest/planks_pink/side"
+    "top": "minecraft:block/chest/planks_pink/top",
+    "front": "minecraft:block/chest/planks_pink/front",
+    "side": "minecraft:block/chest/planks_pink/side"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/chest/single/planks.json b/game/client/src/main/resources/assets/minecraft/models/block/chest/single/planks.json
index fe1010a1e..11122998e 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/chest/single/planks.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/chest/single/planks.json
@@ -1,11 +1,8 @@
 {
-  "parent": "minecraft:block/cube",
+  "parent": "minecraft:block/orientable",
   "textures": {
-    "up": "minecraft:block/chest/planks/top",
-    "down": "minecraft:block/chest/planks/top",
-    "south": "minecraft:block/chest/planks/side",
-    "north": "minecraft:block/chest/planks/front",
-    "west": "minecraft:block/chest/planks/side",
-    "east": "minecraft:block/chest/planks/side"
+    "top": "minecraft:block/chest/planks/top",
+    "front": "minecraft:block/chest/planks/front",
+    "side": "minecraft:block/chest/planks/side"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/chest/single/purple.json b/game/client/src/main/resources/assets/minecraft/models/block/chest/single/purple.json
index fe197824c..c2e6d865a 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/chest/single/purple.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/chest/single/purple.json
@@ -1,11 +1,8 @@
 {
-  "parent": "minecraft:block/cube",
+  "parent": "minecraft:block/orientable",
   "textures": {
-    "up": "minecraft:block/chest/planks_purple/top",
-    "down": "minecraft:block/chest/planks_purple/top",
-    "south": "minecraft:block/chest/planks_purple/side",
-    "north": "minecraft:block/chest/planks_purple/front",
-    "west": "minecraft:block/chest/planks_purple/side",
-    "east": "minecraft:block/chest/planks_purple/side"
+    "top": "minecraft:block/chest/planks_purple/top",
+    "front": "minecraft:block/chest/planks_purple/front",
+    "side": "minecraft:block/chest/planks_purple/side"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/chest/single/red.json b/game/client/src/main/resources/assets/minecraft/models/block/chest/single/red.json
index bc3dc92f3..228113f74 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/chest/single/red.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/chest/single/red.json
@@ -1,11 +1,8 @@
 {
-  "parent": "minecraft:block/cube",
+  "parent": "minecraft:block/orientable",
   "textures": {
-    "up": "minecraft:block/chest/planks_red/top",
-    "down": "minecraft:block/chest/planks_red/top",
-    "south": "minecraft:block/chest/planks_red/side",
-    "north": "minecraft:block/chest/planks_red/front",
-    "west": "minecraft:block/chest/planks_red/side",
-    "east": "minecraft:block/chest/planks_red/side"
+    "top": "minecraft:block/chest/planks_red/top",
+    "front": "minecraft:block/chest/planks_red/front",
+    "side": "minecraft:block/chest/planks_red/side"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/chest/single/silver.json b/game/client/src/main/resources/assets/minecraft/models/block/chest/single/silver.json
index 527dbbd40..a70f4ac17 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/chest/single/silver.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/chest/single/silver.json
@@ -1,11 +1,8 @@
 {
-  "parent": "minecraft:block/cube",
+  "parent": "minecraft:block/orientable",
   "textures": {
-    "up": "minecraft:block/chest/planks_silver/top",
-    "down": "minecraft:block/chest/planks_silver/top",
-    "south": "minecraft:block/chest/planks_silver/side",
-    "north": "minecraft:block/chest/planks_silver/front",
-    "west": "minecraft:block/chest/planks_silver/side",
-    "east": "minecraft:block/chest/planks_silver/side"
+    "top": "minecraft:block/chest/planks_silver/top",
+    "front": "minecraft:block/chest/planks_silver/front",
+    "side": "minecraft:block/chest/planks_silver/side"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/chest/single/white.json b/game/client/src/main/resources/assets/minecraft/models/block/chest/single/white.json
index 0aecdff66..899047d0f 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/chest/single/white.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/chest/single/white.json
@@ -1,11 +1,8 @@
 {
-  "parent": "minecraft:block/cube",
+  "parent": "minecraft:block/orientable",
   "textures": {
-    "up": "minecraft:block/chest/planks_white/top",
-    "down": "minecraft:block/chest/planks_white/top",
-    "south": "minecraft:block/chest/planks_white/side",
-    "north": "minecraft:block/chest/planks_white/front",
-    "west": "minecraft:block/chest/planks_white/side",
-    "east": "minecraft:block/chest/planks_white/side"
+    "top": "minecraft:block/chest/planks_white/top",
+    "front": "minecraft:block/chest/planks_white/front",
+    "side": "minecraft:block/chest/planks_white/side"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/chest/single/yellow.json b/game/client/src/main/resources/assets/minecraft/models/block/chest/single/yellow.json
index 73eee629b..1c7dfa7cc 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/chest/single/yellow.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/chest/single/yellow.json
@@ -1,11 +1,8 @@
 {
-  "parent": "minecraft:block/cube",
+  "parent": "minecraft:block/orientable",
   "textures": {
-    "up": "minecraft:block/chest/planks_yellow/top",
-    "down": "minecraft:block/chest/planks_yellow/top",
-    "south": "minecraft:block/chest/planks_yellow/side",
-    "north": "minecraft:block/chest/planks_yellow/front",
-    "west": "minecraft:block/chest/planks_yellow/side",
-    "east": "minecraft:block/chest/planks_yellow/side"
+    "top": "minecraft:block/chest/planks_yellow/top",
+    "front": "minecraft:block/chest/planks_yellow/front",
+    "side": "minecraft:block/chest/planks_yellow/side"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/dispenser.json b/game/client/src/main/resources/assets/minecraft/models/block/dispenser.json
index 4a4f74842..3dd380cf0 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/dispenser.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/dispenser.json
@@ -1,11 +1,9 @@
 {
-  "parent": "minecraft:block/cube",
+  "parent": "minecraft:block/orientable_with_bottom",
   "textures": {
-    "up": "minecraft:block/dispenser/top",
-    "down": "minecraft:block/dispenser/bottom",
-    "south": "minecraft:block/dispenser/side",
-    "north": "minecraft:block/dispenser/front",
-    "west": "minecraft:block/dispenser/side",
-    "east": "minecraft:block/dispenser/side"
+    "top": "minecraft:block/dispenser/top",
+    "bottom": "minecraft:block/dispenser/bottom",
+    "front": "minecraft:block/dispenser/front",
+    "side": "minecraft:block/dispenser/side"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/fencegate/template_closed.json b/game/client/src/main/resources/assets/minecraft/models/block/fencegate/template_closed.json
index b1a090fa3..82da95d1f 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/fencegate/template_closed.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/fencegate/template_closed.json
@@ -5,6 +5,11 @@
             "translation": [ 0, -1, 0],
             "scale":[ 0.8, 0.8, 0.8 ]
         },
+        "firstperson_righthand": {
+            "rotation": [ 0, -45, 0 ],
+            "translation": [ 0, 0, 0 ],
+            "scale": [ 0.40, 0.40, 0.40 ]
+        },
         "head": {
             "rotation": [ 0, 0, 0 ],
             "translation": [ 0, -3, -6],
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/furnace_blast/active.json b/game/client/src/main/resources/assets/minecraft/models/block/furnace_blast/active.json
index f76da7d24..b8b5123eb 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/furnace_blast/active.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/furnace_blast/active.json
@@ -1,11 +1,9 @@
 {
-  "parent": "minecraft:block/cube",
+  "parent": "minecraft:block/orientable_with_bottom",
   "textures": {
-    "up": "minecraft:block/furnace_blast/top",
-    "down": "minecraft:block/furnace_blast/bottom",
-    "south": "minecraft:block/furnace_blast/side",
-    "north": "minecraft:block/furnace_blast/active_front",
-    "west": "minecraft:block/furnace_blast/side",
-    "east": "minecraft:block/furnace_blast/side"
+    "top": "minecraft:block/furnace_blast/top",
+    "bottom": "minecraft:block/furnace_blast/bottom",
+    "front": "minecraft:block/furnace_blast/active_front",
+    "side": "minecraft:block/furnace_blast/side"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/furnace_blast/active_filled.json b/game/client/src/main/resources/assets/minecraft/models/block/furnace_blast/active_filled.json
index 0f54ae353..f86499152 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/furnace_blast/active_filled.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/furnace_blast/active_filled.json
@@ -1,11 +1,9 @@
 {
-  "parent": "minecraft:block/cube",
+  "parent": "minecraft:block/orientable_with_bottom",
   "textures": {
-    "up": "minecraft:block/furnace_blast/top",
-    "down": "minecraft:block/furnace_blast/bottom",
-    "south": "minecraft:block/furnace_blast/side",
-    "north": "minecraft:block/furnace_blast/active_front_filled",
-    "west": "minecraft:block/furnace_blast/side",
-    "east": "minecraft:block/furnace_blast/side"
+    "top": "minecraft:block/furnace_blast/top",
+    "bottom": "minecraft:block/furnace_blast/bottom",
+    "front": "minecraft:block/furnace_blast/active_front_filled",
+    "side": "minecraft:block/furnace_blast/side"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/furnace_blast/idle.json b/game/client/src/main/resources/assets/minecraft/models/block/furnace_blast/idle.json
index 88cf9c1c9..a23fffb37 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/furnace_blast/idle.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/furnace_blast/idle.json
@@ -1,11 +1,9 @@
 {
-  "parent": "minecraft:block/cube",
+  "parent": "minecraft:block/orientable_with_bottom",
   "textures": {
-    "up": "minecraft:block/furnace_blast/top",
-    "down": "minecraft:block/furnace_blast/bottom",
-    "south": "minecraft:block/furnace_blast/side",
-    "north": "minecraft:block/furnace_blast/idle_front",
-    "west": "minecraft:block/furnace_blast/side",
-    "east": "minecraft:block/furnace_blast/side"
+    "top": "minecraft:block/furnace_blast/top",
+    "bottom": "minecraft:block/furnace_blast/bottom",
+    "front": "minecraft:block/furnace_blast/idle_front",
+    "side": "minecraft:block/furnace_blast/side"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/furnace_blast/idle_filled.json b/game/client/src/main/resources/assets/minecraft/models/block/furnace_blast/idle_filled.json
index a97a89097..faf411a0d 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/furnace_blast/idle_filled.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/furnace_blast/idle_filled.json
@@ -1,11 +1,9 @@
 {
-  "parent": "minecraft:block/cube",
+  "parent": "minecraft:block/orientable_with_bottom",
   "textures": {
-    "up": "minecraft:block/furnace_blast/top",
-    "down": "minecraft:block/furnace_blast/bottom",
-    "south": "minecraft:block/furnace_blast/side",
-    "north": "minecraft:block/furnace_blast/idle_front_filled",
-    "west": "minecraft:block/furnace_blast/side",
-    "east": "minecraft:block/furnace_blast/side"
+    "top": "minecraft:block/furnace_blast/top",
+    "bottom": "minecraft:block/furnace_blast/bottom",
+    "front": "minecraft:block/furnace_blast/idle_front_filled",
+    "side": "minecraft:block/furnace_blast/side"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/furnace_stone/active.json b/game/client/src/main/resources/assets/minecraft/models/block/furnace_stone/active.json
index 9ac40ef37..6930e8c1c 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/furnace_stone/active.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/furnace_stone/active.json
@@ -1,11 +1,9 @@
 {
-  "parent": "minecraft:block/cube",
+  "parent": "minecraft:block/orientable_with_bottom",
   "textures": {
-    "up": "minecraft:block/furnace_stone/top",
-    "down": "minecraft:block/furnace_stone/bottom",
-    "south": "minecraft:block/furnace_stone/side",
-    "north": "minecraft:block/furnace_stone/active_front",
-    "west": "minecraft:block/furnace_stone/side",
-    "east": "minecraft:block/furnace_stone/side"
+    "top": "minecraft:block/furnace_stone/top",
+    "bottom": "minecraft:block/furnace_stone/bottom",
+    "front": "minecraft:block/furnace_stone/active_front",
+    "side": "minecraft:block/furnace_stone/side"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/furnace_stone/active_filled.json b/game/client/src/main/resources/assets/minecraft/models/block/furnace_stone/active_filled.json
index c0b1fef32..2fc7e3b0d 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/furnace_stone/active_filled.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/furnace_stone/active_filled.json
@@ -1,11 +1,9 @@
 {
-  "parent": "minecraft:block/cube",
+  "parent": "minecraft:block/orientable_with_bottom",
   "textures": {
-    "up": "minecraft:block/furnace_stone/top",
-    "down": "minecraft:block/furnace_stone/bottom",
-    "south": "minecraft:block/furnace_stone/side",
-    "north": "minecraft:block/furnace_stone/active_front_filled",
-    "west": "minecraft:block/furnace_stone/side",
-    "east": "minecraft:block/furnace_stone/side"
+    "top": "minecraft:block/furnace_stone/top",
+    "bottom": "minecraft:block/furnace_stone/bottom",
+    "front": "minecraft:block/furnace_stone/active_front_filled",
+    "side": "minecraft:block/furnace_stone/side"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/furnace_stone/idle.json b/game/client/src/main/resources/assets/minecraft/models/block/furnace_stone/idle.json
index 6e35ed373..97bdf5c6d 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/furnace_stone/idle.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/furnace_stone/idle.json
@@ -1,11 +1,9 @@
 {
-  "parent": "minecraft:block/cube",
+  "parent": "minecraft:block/orientable_with_bottom",
   "textures": {
-    "up": "minecraft:block/furnace_stone/top",
-    "down": "minecraft:block/furnace_stone/bottom",
-    "south": "minecraft:block/furnace_stone/side",
-    "north": "minecraft:block/furnace_stone/idle_front",
-    "west": "minecraft:block/furnace_stone/side",
-    "east": "minecraft:block/furnace_stone/side"
+    "top": "minecraft:block/furnace_stone/top",
+    "bottom": "minecraft:block/furnace_stone/bottom",
+    "front": "minecraft:block/furnace_stone/idle_front",
+    "side": "minecraft:block/furnace_stone/side"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/furnace_stone/idle_filled.json b/game/client/src/main/resources/assets/minecraft/models/block/furnace_stone/idle_filled.json
index 4c234def2..ded30cb66 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/furnace_stone/idle_filled.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/furnace_stone/idle_filled.json
@@ -1,11 +1,9 @@
 {
-  "parent": "minecraft:block/cube",
+  "parent": "minecraft:block/orientable_with_bottom",
   "textures": {
-    "up": "minecraft:block/furnace_stone/top",
-    "down": "minecraft:block/furnace_stone/bottom",
-    "south": "minecraft:block/furnace_stone/side",
-    "north": "minecraft:block/furnace_stone/idle_front_filled",
-    "west": "minecraft:block/furnace_stone/side",
-    "east": "minecraft:block/furnace_stone/side"
+    "top": "minecraft:block/furnace_stone/top",
+    "bottom": "minecraft:block/furnace_stone/bottom",
+    "front": "minecraft:block/furnace_stone/idle_front_filled",
+    "side": "minecraft:block/furnace_stone/side"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/motion_sensor_active.json b/game/client/src/main/resources/assets/minecraft/models/block/motion_sensor_active.json
index 688c988cd..be6bb697a 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/motion_sensor_active.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/motion_sensor_active.json
@@ -1,11 +1,8 @@
 {
-  "parent": "minecraft:block/cube",
+  "parent": "minecraft:block/orientable",
   "textures": {
-    "up": "minecraft:block/motion_sensor/side",
-    "down": "minecraft:block/motion_sensor/side",
-    "south": "minecraft:block/motion_sensor/side",
-    "north": "minecraft:block/motion_sensor/active_front",
-    "west": "minecraft:block/motion_sensor/side",
-    "east": "minecraft:block/motion_sensor/side"
+    "top": "minecraft:block/motion_sensor/side",
+    "front": "minecraft:block/motion_sensor/active_front",
+    "side": "minecraft:block/motion_sensor/side"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/motion_sensor_idle.json b/game/client/src/main/resources/assets/minecraft/models/block/motion_sensor_idle.json
index 9923dee13..2ea7de729 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/motion_sensor_idle.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/motion_sensor_idle.json
@@ -1,11 +1,8 @@
 {
-  "parent": "minecraft:block/cube",
+  "parent": "minecraft:block/orientable",
   "textures": {
-    "up": "minecraft:block/motion_sensor/side",
-    "down": "minecraft:block/motion_sensor/side",
-    "south": "minecraft:block/motion_sensor/side",
-    "north": "minecraft:block/motion_sensor/idle_front",
-    "west": "minecraft:block/motion_sensor/side",
-    "east": "minecraft:block/motion_sensor/side"
+    "top": "minecraft:block/motion_sensor/side",
+    "front": "minecraft:block/motion_sensor/idle_front",
+    "side": "minecraft:block/motion_sensor/side"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/orientable_with_bottom.json b/game/client/src/main/resources/assets/minecraft/models/block/orientable_with_bottom.json
index d03a89bdb..7c1c61bf4 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/orientable_with_bottom.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/orientable_with_bottom.json
@@ -1,19 +1,8 @@
 {
-    "parent": "block/cube",
-    "display": {
-        "firstperson_righthand": {
-            "rotation": [ 0, 135, 0 ],
-            "translation": [ 0, 0, 0 ],
-            "scale": [ 0.40, 0.40, 0.40 ]
-        }
-    },
+    "parent": "block/orientable_with_sides",
     "textures": {
-        "particle": "#front",
-        "down": "#bottom",
-        "up": "#top",
-        "north": "#front",
-        "east": "#side",
-        "south": "#side",
-        "west": "#side"
+        "right": "#side",
+        "back": "#side",
+        "left": "#side"
     }
 }
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/orientable_with_sides.json b/game/client/src/main/resources/assets/minecraft/models/block/orientable_with_sides.json
new file mode 100644
index 000000000..98f1d0fcd
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/models/block/orientable_with_sides.json
@@ -0,0 +1,19 @@
+{
+  "parent": "block/cube",
+  "display": {
+    "firstperson_righthand": {
+      "rotation": [ 0, 135, 0 ],
+      "translation": [ 0, 0, 0 ],
+      "scale": [ 0.40, 0.40, 0.40 ]
+    }
+  },
+  "textures": {
+    "particle": "#front",
+    "down": "#bottom",
+    "up": "#top",
+    "north": "#front",
+    "east": "#right",
+    "south": "#back",
+    "west": "#left"
+  }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/pumpkin_carved.json b/game/client/src/main/resources/assets/minecraft/models/block/pumpkin_carved.json
index 4aec1b871..c5788ae80 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/pumpkin_carved.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/pumpkin_carved.json
@@ -1,11 +1,9 @@
 {
-  "parent": "minecraft:block/cube",
+  "parent": "minecraft:block/orientable_with_bottom",
   "textures": {
-    "up": "minecraft:block/pumpkin_carved/top",
-    "down": "minecraft:block/pumpkin_carved/bottom",
-    "south": "minecraft:block/pumpkin_carved/side",
-    "north": "minecraft:block/pumpkin_carved/front",
-    "west": "minecraft:block/pumpkin_carved/side",
-    "east": "minecraft:block/pumpkin_carved/side"
+    "top": "minecraft:block/pumpkin_carved/top",
+    "bottom": "minecraft:block/pumpkin_carved/bottom",
+    "front": "minecraft:block/pumpkin_carved/front",
+    "side": "minecraft:block/pumpkin_carved/side"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/pumpkin_carved_lit.json b/game/client/src/main/resources/assets/minecraft/models/block/pumpkin_carved_lit.json
index 512ca88b7..b38b16c8a 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/pumpkin_carved_lit.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/pumpkin_carved_lit.json
@@ -1,11 +1,9 @@
 {
-  "parent": "minecraft:block/cube",
+  "parent": "minecraft:block/orientable_with_bottom",
   "textures": {
-    "up": "minecraft:block/pumpkin_carved_lit/top",
-    "down": "minecraft:block/pumpkin_carved_lit/bottom",
-    "south": "minecraft:block/pumpkin_carved_lit/side",
-    "north": "minecraft:block/pumpkin_carved_lit/front",
-    "west": "minecraft:block/pumpkin_carved_lit/side",
-    "east": "minecraft:block/pumpkin_carved_lit/side"
+    "top": "minecraft:block/pumpkin_carved_lit/top",
+    "bottom": "minecraft:block/pumpkin_carved_lit/bottom",
+    "front": "minecraft:block/pumpkin_carved_lit/front",
+    "side": "minecraft:block/pumpkin_carved_lit/side"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/pumpkin_redstone.json b/game/client/src/main/resources/assets/minecraft/models/block/pumpkin_redstone.json
index 3322c8129..57d5fa427 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/pumpkin_redstone.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/pumpkin_redstone.json
@@ -1,11 +1,9 @@
 {
-  "parent": "minecraft:block/cube",
+  "parent": "minecraft:block/orientable_with_bottom",
   "textures": {
-    "up": "minecraft:block/pumpkin_redstone/top",
-    "down": "minecraft:block/pumpkin_redstone/bottom",
-    "south": "minecraft:block/pumpkin_redstone/side",
-    "north": "minecraft:block/pumpkin_redstone/front",
-    "west": "minecraft:block/pumpkin_redstone/side",
-    "east": "minecraft:block/pumpkin_redstone/side"
+    "top": "minecraft:block/pumpkin_redstone/top",
+    "bottom": "minecraft:block/pumpkin_redstone/bottom",
+    "front": "minecraft:block/pumpkin_redstone/front",
+    "side": "minecraft:block/pumpkin_redstone/side"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/trommel.json b/game/client/src/main/resources/assets/minecraft/models/block/trommel.json
index c42392628..e103e4870 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/trommel.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/trommel.json
@@ -1,11 +1,18 @@
 {
-  "parent": "minecraft:block/cube_bottom_top",
+  "parent": "minecraft:block/orientable_with_sides",
+  "display": {
+    "firstperson_righthand": {
+      "rotation": [ 0, -45, 0 ],
+      "translation": [ 0, 0, 0 ],
+      "scale": [ 0.40, 0.40, 0.40 ]
+    }
+  },
   "textures": {
     "top": "minecraft:block/trommel/top",
     "bottom": "minecraft:block/trommel/bottom",
-    "south": "minecraft:block/trommel/idle_back",
-    "north": "minecraft:block/trommel/idle_front",
-    "west": "minecraft:block/trommel/side",
-    "east": "minecraft:block/trommel/side_slot"
+    "back": "minecraft:block/trommel/idle_back",
+    "front": "minecraft:block/trommel/idle_front",
+    "left": "minecraft:block/trommel/side",
+    "right": "minecraft:block/trommel/side_slot"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/trommel_active.json b/game/client/src/main/resources/assets/minecraft/models/block/trommel_active.json
index 8394df71b..7daa3ee97 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/trommel_active.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/trommel_active.json
@@ -1,11 +1,18 @@
 {
-  "parent": "minecraft:block/cube_bottom_top",
+  "parent": "minecraft:block/orientable_with_sides",
+  "display": {
+    "firstperson_righthand": {
+      "rotation": [ 0, -45, 0 ],
+      "translation": [ 0, 0, 0 ],
+      "scale": [ 0.40, 0.40, 0.40 ]
+    }
+  },
   "textures": {
     "top": "minecraft:block/trommel/top",
     "bottom": "minecraft:block/trommel/bottom",
-    "south": "minecraft:block/trommel/active_back",
-    "north": "minecraft:block/trommel/active_front",
-    "west": "minecraft:block/trommel/side",
-    "east": "minecraft:block/trommel/side_slot"
+    "back": "minecraft:block/trommel/active_back",
+    "front": "minecraft:block/trommel/active_front",
+    "left": "minecraft:block/trommel/side",
+    "right": "minecraft:block/trommel/side_slot"
   }
 }
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/textures/item/bucket_iron/waterboiling_1.png b/game/client/src/main/resources/assets/minecraft/textures/item/bucket_iron/waterboiling_1.png
new file mode 100644
index 000000000..0473a1aeb
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/item/bucket_iron/waterboiling_1.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/item/bucket_steel/waterboiling_1.png b/game/client/src/main/resources/assets/minecraft/textures/item/bucket_steel/waterboiling_1.png
new file mode 100644
index 000000000..21cbc04ae
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/item/bucket_steel/waterboiling_1.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/item/bucket_steel/waterboiling_2.png b/game/client/src/main/resources/assets/minecraft/textures/item/bucket_steel/waterboiling_2.png
new file mode 100644
index 000000000..9a1589898
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/item/bucket_steel/waterboiling_2.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/item/bucket_steel/waterboiling_3.png b/game/client/src/main/resources/assets/minecraft/textures/item/bucket_steel/waterboiling_3.png
new file mode 100644
index 000000000..3a0ac8d03
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/item/bucket_steel/waterboiling_3.png differ
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicGrowthRubyglass.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicGrowthRubyglass.java
index 1c9369df2..3045ada08 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicGrowthRubyglass.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicGrowthRubyglass.java
@@ -211,16 +211,17 @@ public class BlockLogicGrowthRubyglass extends BlockLogic {
 
 	@Override
 	public ItemStack @Nullable [] getBreakResult(@NotNull World world, @NotNull EnumDropCause dropCause, int data, @Nullable TileEntity tileEntity) {
-		if (dropCause != EnumDropCause.SILK_TOUCH) {
-			return null;
-		}
-
-		int shape = data & SHAPE_MASK;
-
-		if (shape == STATE_BOTTOM) {
-			return new ItemStack[] { new ItemStack(this.block, 2) };
-		}
-
-		return new ItemStack[] { new ItemStack(this.block, 1) };
-	}
+        if (dropCause == EnumDropCause.PICK_BLOCK) {
+            return new ItemStack[]{this.block.getDefaultStack()};
+        } else if (dropCause == EnumDropCause.SILK_TOUCH) {
+            int shape = data & SHAPE_MASK;
+
+            if (shape == STATE_BOTTOM) {
+                return new ItemStack[]{new ItemStack(this.block, 2)};
+            }
+
+            return new ItemStack[]{new ItemStack(this.block, 1)};
+        }
+        return null;
+    }
 }
\ No newline at end of file
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicJarButterfly.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicJarButterfly.java
index 5e9e63706..44a97bf18 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicJarButterfly.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicJarButterfly.java
@@ -135,7 +135,7 @@ public class BlockLogicJarButterfly extends BlockLogic implements ISupportable {
         }
         if (!world.isClientSide) {
             MobButterfly entity = new MobButterfly(world);
-            entity.setColor(this.color.id);
+            entity.setColor(this.color.id());
             entity.setPos(tilePos.x() + 0.5f + (world.rand.nextFloat() - 0.5f) * 0.5f, tilePos.y() + world.rand.nextFloat() * 0.5f, tilePos.z() + 0.5f + (world.rand.nextFloat() - 0.5f) * 0.5f);
             world.entityJoinedWorld(entity);
         }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerAsh.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerAsh.java
index b09358de3..af63cfeea 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerAsh.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerAsh.java
@@ -17,7 +17,7 @@ import org.jetbrains.annotations.Nullable;
 public class BlockLogicLayerAsh extends BlockLogicLayerSupportable
 {
 
-    public BlockLogicLayerAsh(Block<? extends BlockLogicLayerBase> block, Block<?> fullBlock, Material material)
+    public BlockLogicLayerAsh(@NotNull Block<? extends BlockLogicLayerBase> block, @NotNull Block<?> fullBlock, @NotNull Material material)
     {
         super(block, fullBlock, material);
         setBlockBounds(0.0F, 0.0F, 0.0F, 1.0F, 0.125F, 1.0F);
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerBase.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerBase.java
index a0d8de9dc..628d9c951 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerBase.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerBase.java
@@ -19,7 +19,7 @@ public abstract class BlockLogicLayerBase extends BlockLogic
     public final @Nullable Block<?> fullBlock;
     public static final int MASK_HEIGHT = 0b111;
 	
-	public BlockLogicLayerBase(@NotNull Block<? extends BlockLogicLayerBase> block, @Nullable Block<?> fullBlock, @NotNull Material material)
+	public BlockLogicLayerBase(@NotNull Block<? extends BlockLogicLayerBase> block, @NotNull Block<?> fullBlock, @NotNull Material material)
     {
         super(block, material);
         setBlockBounds(0.0F, 0.0F, 0.0F, 1.0F, 0.125F, 1.0F);
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerLeaves.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerLeaves.java
index b01bf518f..c401bb588 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerLeaves.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerLeaves.java
@@ -19,7 +19,7 @@ import java.util.Random;
 public class BlockLogicLayerLeaves extends BlockLogicLayerSupportable {
     public static final int MASK_PERMANENT = 0b1000_0000;
 
-    public BlockLogicLayerLeaves(Block<? extends BlockLogicLayerLeaves> block, Block<?> fullBlock, Material material) {
+    public BlockLogicLayerLeaves(@NotNull Block<? extends BlockLogicLayerLeaves> block, @NotNull Block<?> fullBlock, @NotNull Material material) {
         super(block, fullBlock, material);
         setBlockBounds(0.0F, 0.0F, 0.0F, 1.0F, 0.125F, 1.0F);
         block.setTicking(true);
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerSlate.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerSlate.java
index e8c2681f2..6bca23523 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerSlate.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerSlate.java
@@ -11,7 +11,7 @@ import org.jetbrains.annotations.Nullable;
 
 public class BlockLogicLayerSlate extends BlockLogicLayerBase
 {
-	public BlockLogicLayerSlate(@NotNull Block<? extends BlockLogicLayerBase> block, @Nullable Block<?> fullBlock, @NotNull Material material) {
+	public BlockLogicLayerSlate(@NotNull Block<? extends BlockLogicLayerBase> block, @NotNull Block<?> fullBlock, @NotNull Material material) {
 		super(block, fullBlock, material);
 	}
 
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerSnow.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerSnow.java
index b98471617..cf5f41b1a 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerSnow.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerSnow.java
@@ -22,7 +22,7 @@ import java.util.Random;
 
 public class BlockLogicLayerSnow extends BlockLogicLayerSupportable {
 
-    public BlockLogicLayerSnow(@NotNull Block<? extends BlockLogicLayerBase> block, @Nullable Block<?> fullBlock, @NotNull Material material) {
+    public BlockLogicLayerSnow(@NotNull Block<? extends BlockLogicLayerBase> block, @NotNull Block<?> fullBlock, @NotNull Material material) {
         super(block, fullBlock, material);
         setBlockBounds(0.0F, 0.0F, 0.0F, 1.0F, 0.125F, 1.0F);
         block.setTicking(true);
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerSupportable.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerSupportable.java
index 23d242b52..6a98f27dc 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerSupportable.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerSupportable.java
@@ -9,10 +9,9 @@ import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.pos.TilePosc;
 import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
 
 public abstract class BlockLogicLayerSupportable extends BlockLogicLayerBase implements ISupportable {
-	public BlockLogicLayerSupportable(@NotNull Block<? extends BlockLogicLayerBase> block, @Nullable Block<?> fullBlock, @NotNull Material material) {
+	public BlockLogicLayerSupportable(@NotNull Block<? extends BlockLogicLayerBase> block, @NotNull Block<?> fullBlock, @NotNull Material material) {
         super(block, fullBlock, material);
     }
 
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicThermalVent.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicThermalVent.java
index 8ba5af230..c04ab6df9 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicThermalVent.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicThermalVent.java
@@ -38,7 +38,7 @@ public class BlockLogicThermalVent extends BlockLogic {
 
         world.spawnParticle("ventsmoke", xPos, yPos, zPos, 0.0D, 0.005D, 0.0D, 1, 1000, true);
 
-        if (state >= VENTING_STATE - 1) {
+        if (hasVentBelow(world, tilePos) && state >= VENTING_STATE - 1) {
             for (int i = 0; i < 4; i++) {
                 world.spawnParticle("ventsmoke", xPos, yPos, zPos, 0.0D, 0.005D, 0.0D, 0, 1000.0, true);
                 world.spawnParticle("smoke", xPos + rand.nextFloat() - 0.5D, yPos, zPos + rand.nextFloat() - 0.5D, 0.0D, 0.005D, 0.0D, 1, 1000.0, true);
@@ -53,12 +53,30 @@ public class BlockLogicThermalVent extends BlockLogic {
         return 1;
     }
 
+    private static boolean hasVentBelow(final @NotNull World world, final @NotNull TilePosc tilePos) {
+        return Block.hasLogicClass(world.getBlockType(tilePos.down(new TilePos())), BlockLogicThermalVent.class);
+    }
+
+    @Override
+    public void onNeighborChanged(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Block<?> block) {
+        if (!hasVentBelow(world, tilePos) && getVentState(world.getBlockData(tilePos)) != DORMANT_STATE) {
+            world.setBlockDataNotify(tilePos, DORMANT_STATE);
+        }
+    }
+
     @Override
     public void updateTick(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Random rand, final boolean isRandomTick) {
         if (world.getBlockType(tilePos.up(new TilePos())).solid()) {
             return;
         }
 
+        if (!hasVentBelow(world, tilePos)) {
+            if (getVentState(world.getBlockData(tilePos)) != DORMANT_STATE) {
+                world.setBlockDataNotify(tilePos, DORMANT_STATE);
+            }
+            return;
+        }
+
         if (rand.nextInt(4) != 0) {
             int state = getVentState(world.getBlockData(tilePos));
             if (state < VENTING_STATE) {
@@ -120,7 +138,7 @@ public class BlockLogicThermalVent extends BlockLogic {
         super.onPlacedByWorld(world, tilePos);
 
         if (!world.isClientSide) {
-            world.setBlockDataNotify(tilePos, world.rand.nextInt(VENTING_STATE));
+            world.setBlockDataNotify(tilePos, hasVentBelow(world, tilePos) ? world.rand.nextInt(VENTING_STATE) : DORMANT_STATE);
         }
     }
 
diff --git a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityJarButterfly.java b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityJarButterfly.java
index 77121d8e1..968a58315 100644
--- a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityJarButterfly.java
+++ b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityJarButterfly.java
@@ -32,6 +32,6 @@ public class TileEntityJarButterfly extends TileEntity{
 
     @Override
     public void writeAdditionalData(@NotNull CompoundTag compoundTag) {
-        compoundTag.putByte("color", (byte) color.id);
+        compoundTag.putByte("color", (byte) color.id());
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/entity/ICapturable.java b/game/core/src/main/java/net/minecraft/core/entity/ICapturable.java
new file mode 100644
index 000000000..92a6c4770
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/entity/ICapturable.java
@@ -0,0 +1,25 @@
+package net.minecraft.core.entity;
+
+import org.jetbrains.annotations.NotNull;
+
+import net.minecraft.core.item.Item;
+
+public interface ICapturable {
+    boolean canBeCapturedBy(@NotNull Item item);
+    int onCaptured(int amount, boolean acceptPartial);
+    
+    default int onCaptured() { return this.onCaptured(1, true); }
+    default int onCaptured(int amount) { return this.onCaptured(amount, true); }
+
+    static interface Singular extends ICapturable {
+        void onSingularCaptured();
+
+        @Override
+        default int onCaptured(int amount, boolean acceptPartial) {
+            if (amount < 1) return 0;
+            if (!acceptPartial && amount > 1) return 0;
+            this.onSingularCaptured();
+            return 1;
+        }
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/entity/Mob.java b/game/core/src/main/java/net/minecraft/core/entity/Mob.java
index af4f33c56..363436919 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/Mob.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/Mob.java
@@ -4,6 +4,7 @@ import com.b100.utils.StringUtils;
 import com.mojang.nbt.tags.CompoundTag;
 import net.minecraft.core.Global;
 import net.minecraft.core.block.material.Materials;
+import net.minecraft.core.item.Item;
 import net.minecraft.core.item.ItemFood;
 import net.minecraft.core.util.phys.HitResult;
 import net.minecraft.core.WeightedRandomLootObject;
diff --git a/game/core/src/main/java/net/minecraft/core/entity/animal/MobButterfly.java b/game/core/src/main/java/net/minecraft/core/entity/animal/MobButterfly.java
index 98a7d3ff3..1f983e42d 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/animal/MobButterfly.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/animal/MobButterfly.java
@@ -9,10 +9,12 @@ import net.minecraft.core.block.Block;
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.block.tag.BlockTags;
 import net.minecraft.core.entity.Entity;
+import net.minecraft.core.entity.ICapturable;
 import net.minecraft.core.entity.MobFlying;
 import net.minecraft.core.entity.SkinVariantList;
 import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.item.Item;
+import net.minecraft.core.item.ItemJar;
 import net.minecraft.core.item.Items;
 import net.minecraft.core.util.helper.DamageType;
 import net.minecraft.core.util.helper.MathHelper;
@@ -22,12 +24,10 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.joml.primitives.AABBd;
 
-import java.util.Arrays;
-import java.util.HashSet;
 import java.util.Set;
 import java.util.function.Supplier;
 
-public class MobButterfly extends MobFlying implements AmbientCreature {
+public class MobButterfly extends MobFlying implements AmbientCreature, ICapturable.Singular {
     public static final Int2ObjectMap<ButterflyEntry> colorEntries = new Int2ObjectArrayMap<>();
 
     public static final int DATA_COLOR_ID = 16;
@@ -38,8 +38,8 @@ public class MobButterfly extends MobFlying implements AmbientCreature {
     public static final ButterflyEntry COLOR_SILVER;
 
     public static ButterflyEntry addEntry(int id, String name, double weight, Supplier<Item> jarItem, Biome... allowedBiomes) {
-        assert id >= 0 && id <= 0xFF : "ID is outside valid range!";
-        ButterflyEntry entry = new ButterflyEntry(id, name, weight, jarItem, allowedBiomes);
+        assert (id & ~0xFF) == 0 : "ID is outside valid range!";
+        final var entry = new ButterflyEntry(id, name, weight, jarItem, allowedBiomes);
         colorEntries.put(id, entry);
         return entry;
     }
@@ -284,45 +284,37 @@ public class MobButterfly extends MobFlying implements AmbientCreature {
         return colorEntries.get(getColor()).name + "/" + variantList.getSkinReference(this.variantJsonPath, "0", getSkinVariant());
     }
 
-    public static class ButterflyEntry {
-        public final int id;
-        public final String name;
-        /** Biomes which the color can appear, null indicates any biome*/
-        private @Nullable Set<Biome> allowedBiomes;
-        public final Supplier<Item> jarItem;
-        public final double weight;
-
-        public ButterflyEntry(int id, String name, double weight, Supplier<Item> jarItem, Biome... allowedBiomes) {
-            this.id = id;
-            this.name = name;
-            this.weight = weight;
-            this.jarItem = jarItem;
-
-            if (allowedBiomes.length > 0) {
-                this.allowedBiomes = new HashSet<>();
-                this.allowedBiomes.addAll(Arrays.asList(allowedBiomes));
-            }
+    public static record ButterflyEntry(
+        int id,
+        @NotNull String name,
+        double weight,
+        @NotNull Supplier<@NotNull Item> jarItemLazy,
+        @Nullable Set<Biome> allowedBiomes
+    ) {
+        public ButterflyEntry(
+            int id,
+            @NotNull String name,
+            double weight,
+            @NotNull Supplier<@NotNull Item> jarItemLazy,
+            Biome... allowedBiomes
+        ) {
+            this(id, name, weight, jarItemLazy, allowedBiomes.length == 0 ? null : Set.of(allowedBiomes));
         }
 
-        public ButterflyEntry addAllowedBiome(Biome biome) {
-            if (this.allowedBiomes == null) this.allowedBiomes = new HashSet<>();
-            this.allowedBiomes.add(biome);
-            return this;
+        public @NotNull Item jarItem() { return this.jarItemLazy.get(); }
+        public boolean isBiomeAllowed(Biome biome) {
+            return this.allowedBiomes == null || this.allowedBiomes.contains(biome);
         }
+    }
 
-        public ButterflyEntry removeAllowedBiome(Biome biome) {
-            if (this.allowedBiomes == null) this.allowedBiomes = new HashSet<>();
-            this.allowedBiomes.remove(biome);
-            return this;
-        }
 
-        public ButterflyEntry allowAllBiomes() {
-            this.allowedBiomes = null;
-            return this;
-        }
+    @Override
+    public boolean canBeCapturedBy(@NotNull Item item) {
+        return item instanceof ItemJar;
+    }
 
-        public boolean isBiomeAllowed(Biome biome) {
-            return this.allowedBiomes == null || this.allowedBiomes.contains(biome);
-        }
+    @Override
+    public void onSingularCaptured() {
+        this.remove();
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/entity/animal/MobFireflyCluster.java b/game/core/src/main/java/net/minecraft/core/entity/animal/MobFireflyCluster.java
index 6b05f57ac..325eec7b6 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/animal/MobFireflyCluster.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/animal/MobFireflyCluster.java
@@ -6,8 +6,11 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.block.tag.BlockTags;
 import net.minecraft.core.entity.Entity;
+import net.minecraft.core.entity.ICapturable;
 import net.minecraft.core.entity.MobFlying;
 import net.minecraft.core.enums.LightLayer;
+import net.minecraft.core.item.Item;
+import net.minecraft.core.item.ItemJar;
 import net.minecraft.core.util.helper.DamageType;
 import net.minecraft.core.util.helper.MathHelper;
 import net.minecraft.core.world.World;
@@ -20,7 +23,7 @@ import java.util.Collection;
 import java.util.List;
 
 public class MobFireflyCluster extends MobFlying
-    implements AmbientCreature {
+    implements AmbientCreature, ICapturable {
     public static final int DATA_COLOR_ID = 16;
     public static final int DATA_CLUSTER_SIZE_ID = 17;
     public static final int DATA_COUNT_ID = 18;
@@ -412,6 +415,26 @@ public class MobFireflyCluster extends MobFlying
         return true;
     }
 
+
+    @Override
+    public boolean canBeCapturedBy(@NotNull Item item) {
+        return item instanceof ItemJar;
+    }
+
+    @Override
+    public int onCaptured(int amount, boolean acceptPartial) {
+        if (amount < 1) return 0;
+
+        int count = this.getFireflyCount();
+        if (!acceptPartial && amount > count) return 0;
+        
+        count -= amount;
+        this.setFireflyCount(count);
+        if (count == 0) this.remove();
+
+        return count;
+    }
+
     public static class FireflyColor {
         public static Int2ObjectMap<FireflyColor> fireFlyColors = new Int2ObjectArrayMap<>();
         public static FireflyColor GREEN = register(new FireflyColor(0, "fireflyGreen", new Biome[]{
diff --git a/game/core/src/main/java/net/minecraft/core/entity/player/Player.java b/game/core/src/main/java/net/minecraft/core/entity/player/Player.java
index bbc257449..b28e6ac3a 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/player/Player.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/player/Player.java
@@ -59,6 +59,9 @@ import net.minecraft.core.world.chunk.ChunkCoordinates;
 import net.minecraft.core.world.chunk.provider.ChunkProvider;
 import net.minecraft.core.world.pos.TilePos;
 import net.minecraft.core.world.pos.TilePosc;
+import net.minecraft.core.world.type.WorldType;
+import net.minecraft.core.world.type.WorldTypes;
+import net.minecraft.core.world.type.tag.WorldTypeTags;
 import net.minecraft.core.world.weather.Weathers;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -1635,28 +1638,36 @@ public abstract class Player
         double oneFifthOfRadius = maxRadius * 0.2;
         double threeFifthsOfRadius = maxRadius * 0.6;
 
+        boolean isNether = player.world.getWorldType().hasTag(WorldTypeTags.NETHER);
+
         float randomizedPitch = 0.8F + (this.world.rand.nextFloat() - this.world.rand.nextFloat()) * 0.45F;
 
         if (distance <= oneFifthOfRadius) {
             this.world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS,
                 ventCoordinateX, ventCoordinateY, ventCoordinateZ,
             "tile.crumble", 20.0F, randomizedPitch);
-            this.world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS,
-                ventCoordinateX, ventCoordinateY, ventCoordinateZ,
-            "tile.deepboom", 20.0F, randomizedPitch);
+            if(isNether) {
+                this.world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS,
+                    ventCoordinateX, ventCoordinateY, ventCoordinateZ,
+                "tile.deepboom", 20.0F, randomizedPitch);
+            }
         }
         else if (distance <= threeFifthsOfRadius) {
             this.world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS,
                 ventCoordinateX, ventCoordinateY, ventCoordinateZ,
             "tile.deepcrumble", 20.0F, randomizedPitch);
-            this.world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS,
-                ventCoordinateX, ventCoordinateY, ventCoordinateZ,
-            "tile.deepquietboom", 20.0F, randomizedPitch);
+            if(isNether) {
+                this.world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS,
+                    ventCoordinateX, ventCoordinateY, ventCoordinateZ,
+                "tile.deepquietboom", 20.0F, randomizedPitch);
+            }
         }
         else {
-            this.world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS,
-                ventCoordinateX, ventCoordinateY, ventCoordinateZ,
-            "tile.deepquietboom", 20.0F, randomizedPitch);
+            if(isNether) {
+                this.world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS,
+                    ventCoordinateX, ventCoordinateY, ventCoordinateZ,
+                "tile.deepquietboom", 20.0F, randomizedPitch);
+            }
         }
     }
 
diff --git a/game/core/src/main/java/net/minecraft/core/item/IAccumulatable.java b/game/core/src/main/java/net/minecraft/core/item/IAccumulatable.java
new file mode 100644
index 000000000..37a4d2409
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/item/IAccumulatable.java
@@ -0,0 +1,181 @@
+package net.minecraft.core.item;
+
+import net.minecraft.core.block.Block;
+import net.minecraft.core.block.BlockLogic;
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.block.entity.TileEntityActivator;
+import net.minecraft.core.entity.player.Player;
+import net.minecraft.core.enums.EnumBlockSoundEffectType;
+import net.minecraft.core.util.helper.Direction;
+import net.minecraft.core.util.helper.Side;
+import net.minecraft.core.world.World;
+
+import java.util.Random;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.joml.primitives.AABBd;
+import org.joml.primitives.AABBdc;
+
+import net.minecraft.core.world.pos.TilePos;
+import net.minecraft.core.world.pos.TilePosc;
+
+public interface IAccumulatable<T extends BlockLogic> extends IPlaceable.PlaceableBlock<T> {
+    
+    boolean canAccumulateInto(
+        @NotNull ItemStack selfStack,
+        @NotNull Block<?> block, int data, @NotNull Side side, double xHit, double yHit);
+
+    /**
+     * @param result out parameter, .block and .data are by default the current block and data at position
+     */
+    @NotNull BlockDataResult getAccumulationResult(
+        @NotNull ItemStack selfStack,
+        @NotNull World world, @Nullable Player player, @NotNull TilePosc tilePos,
+        @NotNull Side side, double xHit, double yHit,
+        @NotNull BlockDataResult result);
+        
+    default void postAccumulate(
+        @NotNull ItemStack selfStack,
+        @NotNull World world, @Nullable Player player, @NotNull TilePosc tilePos,
+        @NotNull Side side, double xHit, double yHit,
+        @NotNull BlockDataResult result
+    ) {}
+
+    public static class BlockDataResult {
+        public @NotNull Block<?> block;
+        public int data;
+        public boolean doRawPlacing;
+        public boolean shouldUpdateNeighbors;
+        public boolean shouldConsumeItem;
+        public boolean shouldPlaySound;
+        public final @NotNull AABBd bounds;
+
+        public BlockDataResult() {
+            this(new AABBd().set(INVALID_BB));
+        }
+        public BlockDataResult(@NotNull AABBd bounds) {
+            this.bounds = bounds;
+            this.reset();
+        }
+    
+        public void reset() {
+            this.block = Blocks.AIR;
+            this.data = 0;
+            this.doRawPlacing = false;
+            this.shouldUpdateNeighbors = true;
+            this.shouldConsumeItem = true;
+            this.shouldPlaySound = true;
+            this.removeBounds();
+        }
+
+        public void removeBounds() {
+            this.bounds.set(INVALID_BB);
+        }
+
+        private static final @NotNull AABBdc INVALID_BB = new AABBd(
+            Double.POSITIVE_INFINITY,Double.POSITIVE_INFINITY,Double.POSITIVE_INFINITY,
+            Double.NEGATIVE_INFINITY,Double.NEGATIVE_INFINITY,Double.NEGATIVE_INFINITY);
+    }
+
+    @Override
+    default boolean placeOnBlock(
+        @NotNull ItemStack selfStack,
+        @NotNull World world, @Nullable Player player, @NotNull TilePosc blockPos,
+        @NotNull Side side, double xHit, double yHit
+    ) {
+        return this.placeOnBlockAccumulatable(true, selfStack, world, player, blockPos, side, xHit, yHit);
+    }
+
+    @Override
+    default void placeByActivator(
+        @NotNull ItemStack selfStack,
+        @NotNull World world, @NotNull TileEntityActivator activator,
+        @NotNull Random random, @NotNull TilePosc blockPos,
+        @NotNull Direction direction, double offX, double offY, double offZ
+    ) {
+        blockPos = blockPos.add(direction, new TilePos());
+        this.placeOnBlockAccumulatable(false, selfStack, world, null, blockPos, direction.opposite().side(), Double.NaN, Double.NaN);
+    }
+
+    @Override
+    default boolean placeWithoutShift(
+        @NotNull ItemStack selfStack,
+        @NotNull World world, @Nullable Player player, @NotNull TilePosc blockPos,
+        @NotNull Side side, double xHit, double yHit
+    ) {
+        return this.placeOnBlockAccumulatable(false, selfStack, world, player, blockPos, side, xHit, yHit);
+    }
+    
+    static boolean defaultPlaceWithoutShift(
+        @NotNull IAccumulatable<?> self,
+        @NotNull ItemStack selfStack,
+        @NotNull World world, @Nullable Player player, @NotNull TilePosc blockPos,
+        @NotNull Side side, double xHit, double yHit
+    ) {
+        if (!self.canPlaceDirectlyAtPosition(selfStack, world, player, blockPos, side, xHit, yHit)) return false;
+
+        final int meta = self.getPlacedData(selfStack, world, player, blockPos, side, xHit, yHit);
+        return self.placeDirectly(selfStack, world, player, blockPos, meta, side, xHit, yHit);
+    }
+
+    default boolean placeOnBlockAccumulatable(
+        boolean shift,
+        @NotNull ItemStack selfStack,
+        @NotNull World world, @Nullable Player player, @NotNull TilePosc tilePos,
+        @NotNull Side side, double xHit, double yHit
+    ) {
+        if (selfStack.stackSize <= 0) return false;
+
+        var block = world.getBlockType(tilePos);
+        var data = world.getBlockData(tilePos);
+
+        if (!this.canAccumulateInto(selfStack, block, data, side, xHit, yHit)) {
+            if (!this.shouldShiftOutOf(selfStack, world, player, tilePos, side, xHit, yHit))
+                return defaultPlaceWithoutShift(this, selfStack, world, player, tilePos, side, xHit, yHit);
+            if (!shift) return false;
+
+            tilePos = tilePos.add(side, new TilePos());
+            yHit -= side.offsetY();
+            xHit -= side.offsetX() + side.offsetZ();
+            block = world.getBlockType(tilePos);
+            data = world.getBlockData(tilePos);
+
+            if (!this.canAccumulateInto(selfStack, block, data, side, xHit, yHit))
+                return defaultPlaceWithoutShift(this, selfStack, world, player, tilePos, side, xHit, yHit);
+        }
+
+        interface ctx {
+            BlockDataResult result = new BlockDataResult();
+            AABBd queryBB = new AABBd();
+        }
+        synchronized(ctx.result) {
+            ctx.result.reset();
+            ctx.result.block = block;
+            ctx.result.data = data;
+
+            final var result = this.getAccumulationResult(selfStack, world, player, tilePos, side, xHit, yHit, ctx.result);
+
+            if (result.bounds.isValid()) {
+                ctx.queryBB.set(result.bounds).translate(tilePos.x(), tilePos.y(), tilePos.z());
+                if (!world.checkIfAABBIsClear(ctx.queryBB)) return false;
+            }
+            
+            final boolean placed = (result.doRawPlacing)
+                ? world.setBlockTypeDataRaw(tilePos, result.block, result.data)
+                : world.setBlockTypeData(tilePos, result.block, result.data);
+            if (!placed) return false;
+            
+            if (result.shouldConsumeItem) selfStack.consumeItem(player);
+
+            this.postAccumulate(selfStack, world, player, tilePos, side, xHit, yHit, result);
+            if (result.shouldUpdateNeighbors) world.notifyBlockChange(tilePos, block);
+
+            if (result.shouldPlaySound) world.playBlockSoundEffect(player,
+                tilePos.x() + 0.5F, tilePos.y() + 0.5F, tilePos.z() + 0.5F,
+                block, EnumBlockSoundEffectType.PLACE);
+
+            return true;
+        }
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/item/IPlaceable.java b/game/core/src/main/java/net/minecraft/core/item/IPlaceable.java
new file mode 100644
index 000000000..77015e3a6
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/item/IPlaceable.java
@@ -0,0 +1,162 @@
+package net.minecraft.core.item;
+
+import java.util.Random;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import net.minecraft.core.block.Block;
+import net.minecraft.core.block.BlockLogic;
+import net.minecraft.core.block.entity.TileEntityActivator;
+import net.minecraft.core.block.tag.BlockTags;
+import net.minecraft.core.entity.player.Player;
+import net.minecraft.core.enums.EnumBlockSoundEffectType;
+import net.minecraft.core.util.helper.Direction;
+import net.minecraft.core.util.helper.Side;
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.pos.TilePos;
+import net.minecraft.core.world.pos.TilePosc;
+
+public interface IPlaceable {
+    boolean shouldShiftOutOf(
+        @NotNull ItemStack selfStack, 
+        @NotNull World world, @Nullable Player player, @NotNull TilePosc blockPos,
+        @NotNull Side side, double xHit, double yHit
+    );
+
+    boolean canPlaceDirectlyAtPosition(
+        @NotNull ItemStack selfStack, 
+        @NotNull World world, @Nullable Player player, @NotNull TilePosc blockPos,
+        @NotNull Side side, double xHit, double yHit
+    );
+
+    public static boolean shouldShiftBlockOutOf(
+        @NotNull Block<?> block, @NotNull World world, @NotNull TilePosc blockPos
+    ) {
+        if (!world.canPlaceInsideBlock(blockPos)) return true;
+        return block.hasTag(BlockTags.PLACE_OVERWRITES) && 
+            block == world.getBlockType(blockPos);
+    }
+
+    public static boolean canPlaceBlockDirectlyAtPosition(
+        @NotNull Block<?> block, @NotNull World world,
+        @NotNull TilePosc blockPos, @NotNull Side side
+    ) {
+        return world.canBlockIdBePlacedAt(block.id(), blockPos, false, side);
+    }
+
+    boolean placeWithoutShift(
+        @NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player,
+        @NotNull TilePosc blockPos, @NotNull Side side, double xHit, double yHit
+    );
+
+    default boolean placeOnBlock(
+        @NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player,
+        @NotNull TilePosc blockPos, @NotNull Side side, double xHit, double yHit
+    ) {
+        if (this.shouldShiftOutOf(selfStack, world, player, blockPos, side, xHit, yHit)) blockPos = blockPos.add(side.direction(), new TilePos());
+        return this.placeWithoutShift(selfStack, world, player, blockPos, side, xHit, yHit);
+    }
+
+    default void placeByActivator(
+        @NotNull ItemStack selfStack, @NotNull World world,
+        @NotNull TileEntityActivator activator, @NotNull Random random, @NotNull TilePosc blockPos,
+        @NotNull Direction direction, double offX, double offY, double offZ
+    ) {
+        blockPos = new TilePos(blockPos).add(direction);
+        this.placeWithoutShift(selfStack, world, null, blockPos, direction.side().opposite(), 0.5f, 0.5f);
+    }
+
+    public static interface PlaceableBlock<T extends BlockLogic> extends IPlaceable {
+        @NotNull Block<T> getBlock();
+
+        @Override
+        default boolean shouldShiftOutOf(
+            @NotNull ItemStack selfStack, 
+            @NotNull World world, @Nullable Player player, @NotNull TilePosc blockPos,
+            @NotNull Side side, double xHit, double yHit
+        ) {
+            return shouldShiftBlockOutOf(this.getBlock(), world, blockPos);
+        }
+
+        @Override
+        default boolean canPlaceDirectlyAtPosition(
+            @NotNull ItemStack selfStack, 
+            @NotNull World world, @Nullable Player player, @NotNull TilePosc blockPos,
+            @NotNull Side side, double xHit, double yHit
+        ) {
+            return canPlaceBlockDirectlyAtPosition(this.getBlock(), world, blockPos, side);
+        }
+
+        default boolean placeWithMeta(
+            @NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player,
+            @NotNull TilePosc blockPos, int meta, @NotNull Side side, double xHit, double yHit
+        ) {
+            if (this.shouldShiftOutOf(selfStack, world, player, blockPos, side, xHit, yHit)) blockPos = blockPos.add(side.direction(), new TilePos());
+            if (!this.canPlaceDirectlyAtPosition(selfStack, world, player, blockPos, side, xHit, yHit)) return false;
+
+            return this.placeDirectly(selfStack, world, player, blockPos, meta, side, xHit, yHit);
+        }
+
+        default int getPlacedData(
+            @NotNull ItemStack selfStack,
+            @NotNull World world, @Nullable Player player, @NotNull TilePosc blockPos,
+            @NotNull Side side, double xHit, double yHit
+        ) {
+            return this.getBlock().getPlacedData(player, selfStack, world, blockPos, side, xHit, yHit);
+        }
+
+        @Override
+        default boolean placeWithoutShift(
+            @NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player,
+            @NotNull TilePosc blockPos, @NotNull Side side, double xHit, double yHit
+        ) {
+            if (!this.canPlaceDirectlyAtPosition(selfStack, world, player, blockPos, side, xHit, yHit)) return false;
+
+            final int meta = this.getPlacedData(selfStack, world, player, blockPos, side, xHit, yHit);
+            return this.placeDirectly(selfStack, world, player, blockPos, meta, side, xHit, yHit);
+        }
+
+        default boolean placeDirectly(
+            @NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player,
+            @NotNull TilePosc blockPos, int meta, @NotNull Side side, double xHit, double yHit
+        ) {
+            if (selfStack.stackSize <= 0) return false;
+            final var pbloc = world.getBlockType(blockPos);
+            final int pdata = world.getBlockData(blockPos);
+
+            final var selfBlock = this.getBlock();
+
+            if (!world.setBlockTypeDataRaw(blockPos, selfBlock, meta)) return false;
+
+            if (pbloc != selfBlock || pdata != meta) {
+                pbloc.onRemoved(world, blockPos, pdata);
+            }
+
+            selfStack.consumeItem(player);
+
+            if (player == null) {
+                selfBlock.onPlacedOnSide(world, blockPos, side, xHit, yHit);
+            } else {
+                selfBlock.onPlacedByMob(world, blockPos, side, player, xHit, yHit);
+            }
+
+            selfBlock.onPlacedByWorld(world, blockPos);
+
+            if (selfBlock.isEntityTile && selfStack.getData().containsKey("tileEntityData")) {
+                final var tileEntity = world.getTileEntity(blockPos);
+                if (tileEntity != null) {
+                    tileEntity.readAdditionalData(selfStack.getData().getCompound("tileEntityData"));
+                }
+            }
+
+            world.notifyBlockChange(blockPos, selfBlock);
+            world.playBlockSoundEffect(
+                player,
+                blockPos.x() + 0.5F, blockPos.y() + 0.5F, blockPos.z() + 0.5F,
+                selfBlock, EnumBlockSoundEffectType.PLACE
+            );
+            return true;
+        }
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/item/Item.java b/game/core/src/main/java/net/minecraft/core/item/Item.java
index 5434bbcdd..1ff7f6a8c 100644
--- a/game/core/src/main/java/net/minecraft/core/item/Item.java
+++ b/game/core/src/main/java/net/minecraft/core/item/Item.java
@@ -216,6 +216,14 @@ public class Item implements ITaggable<Item>, IItemConvertible {
         return this.maxDamage;
     }
 
+    public boolean interactsWithFluid(@NotNull ItemStack selfStack) {
+        return false;
+    }
+
+    public boolean interactsWithEntity(@NotNull ItemStack selfStack, @NotNull Entity entity) {
+        return entity.isSelectable();
+    }
+
     @Deprecated
     public final boolean onUseItemOnBlock(@NotNull final ItemStack selfStack, @Nullable final Player player, @NotNull final World world, final int blockX, final int blockY, final int blockZ, @NotNull final Side side, final double xPlaced, final double yPlaced) {
         return onUseOnBlock(selfStack, world, player, new TilePos(blockX, blockY, blockZ), side, xPlaced, yPlaced);
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemBed.java b/game/core/src/main/java/net/minecraft/core/item/ItemBed.java
index 04610b5b2..6387080f1 100644
--- a/game/core/src/main/java/net/minecraft/core/item/ItemBed.java
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemBed.java
@@ -3,7 +3,6 @@ package net.minecraft.core.item;
 import net.minecraft.core.block.BlockLogicBed;
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.entity.player.Player;
-import net.minecraft.core.enums.EnumBlockSoundEffectType;
 import net.minecraft.core.util.helper.Direction;
 import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.world.World;
@@ -12,47 +11,41 @@ import net.minecraft.core.world.pos.TilePosc;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-public class ItemBed extends Item {
-
+public class ItemBed extends ItemPlaceablePair {
     public ItemBed(@NotNull String name, @NotNull String namespaceId, int id) {
-        super(name, namespaceId, id);
+        super(name, namespaceId, id, Blocks.BED, Blocks.BED);
     }
 
-    @Override
-    public boolean onUseOnBlock(@NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player, @NotNull TilePosc blockPos, @NotNull Side side, double xHit, double yHit) {
-        if (selfStack.stackSize <= 0) return false;
-        TilePosc pos1 = blockPos;
-        if (!world.canPlaceInsideBlock(pos1)) {
-            pos1 = new TilePos(pos1).add(side.direction());
-        }
-        final Direction dir = player == null ? Direction.NORTH : player.getHorizontalPlacementDirection(null).opposite();
-        final TilePos pos2 = new TilePos(pos1).sub(dir);
-
-        if (!world.canPlaceInsideBlock(pos1) || !world.canPlaceInsideBlock(pos2)) return false;
-        
-        final var bed = Blocks.BED.getLogic();
-        if (!bed.isSupported(world, pos1, Side.BOTTOM)) return false;
-        if (!bed.isSupported(world, pos2, Side.BOTTOM)) return false;
-        
-        if (!world.canBlockIdBePlacedAt(Blocks.BED.id(), pos1, false, side)) return false;
-        if (!world.canBlockIdBePlacedAt(Blocks.BED.id(), pos2, false, side)) return false;
-        
-        
-        final boolean isPlaced1 = 
-            world.setBlockTypeData(pos1, Blocks.BED, dir.legacyIndex());
-        final boolean isPlaced2 = isPlaced1 &&
-            world.setBlockTypeData(pos2, Blocks.BED, dir.legacyIndex() | BlockLogicBed.IS_HEAD.set(0, 1));
-        if (!isPlaced2) {
-            if (isPlaced1) world.setBlockType(pos1, Blocks.AIR);
-            return false;
-        }
-        selfStack.consumeItem(player);
+    public static @NotNull Direction getDirection(@Nullable Player player, @NotNull Side side) {
+        return (player != null)
+            ? player.getHorizontalPlacementDirection(null).opposite()
+            : (side.isHorizontal()) ? side.direction : Direction.NORTH;
+    }
 
-        world.markBlockNeedsUpdate(pos1);
-        world.markBlockNeedsUpdate(pos2);
-        world.notifyBlocksInCapsuleOfNeighborChange(dir.opposite(), pos1, Blocks.BED);
+    @Override
+    protected @NotNull PlacementResult getPlacement(
+        @NotNull ItemStack selfStack, 
+        @NotNull World world, @Nullable Player player, @NotNull TilePosc blockPos,
+        @NotNull Side side, double xHit, double yHit,
+        @NotNull PlacementResult result
+    ) {
+        final var dir = getDirection(player, side);
+        result.dataA = result.dataB = dir.legacyIndex();
+        result.dataB = BlockLogicBed.IS_HEAD.set(result.dataB, 1);
+        dir.getOffset(result.offsetB);
+        result.offsetB.mul(-1);
+        return result;
+    }
 
-        world.playBlockSoundEffect(player, pos1.x() + 0.5F, pos1.y() + 0.5F, pos1.z() + 0.5F, Blocks.BED, EnumBlockSoundEffectType.PLACE);
-        return true;
+    @Override
+    public boolean canPlaceDirectlyAtPosition(
+        @NotNull ItemStack selfStack,
+        @NotNull World world, @Nullable Player player, @NotNull TilePosc blockPos,
+        @NotNull Side side, double xHit, double yHit
+    ) {
+        final var dir = getDirection(player, side);
+        return 
+            IPlaceable.canPlaceBlockDirectlyAtPosition(blockA, world, blockPos, side) &&
+            IPlaceable.canPlaceBlockDirectlyAtPosition(blockB, world, blockPos.sub(dir, new TilePos()), side);
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemBucket.java b/game/core/src/main/java/net/minecraft/core/item/ItemBucket.java
index 2d2785f6e..3dbc3b892 100644
--- a/game/core/src/main/java/net/minecraft/core/item/ItemBucket.java
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemBucket.java
@@ -7,6 +7,8 @@ import net.minecraft.core.block.Blocks;
 import net.minecraft.core.block.entity.TileEntityActivator;
 import net.minecraft.core.block.tag.BlockTags;
 import net.minecraft.core.data.tag.Tag;
+import net.minecraft.core.entity.Entity;
+import net.minecraft.core.entity.Mob;
 import net.minecraft.core.entity.animal.MobCow;
 import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.enums.EnumDropCause;
@@ -18,6 +20,7 @@ import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.util.phys.HitResult;
 import net.minecraft.core.world.LevelListener;
 import net.minecraft.core.world.World;
+import net.minecraft.core.world.type.tag.WorldTypeTags;
 import net.minecraft.core.world.pos.TilePos;
 import net.minecraft.core.world.pos.TilePosc;
 import org.jetbrains.annotations.NotNull;
@@ -29,6 +32,15 @@ import java.util.function.BiConsumer;
 
 public abstract class ItemBucket extends ItemFood implements IItemContainer {
 
+    /**
+     * Resolves the item texture basename for a bucket state (e.g. {@code water} vs {@code waterboiling}).
+     * Return {@code defaultName} to use the state's default texture.
+     */
+    @FunctionalInterface
+    public interface TextureStateName {
+        @NotNull String resolve(@Nullable Entity entity, @Nullable World world, @NotNull ItemStack stack, @NotNull String defaultName);
+    }
+
     public record BucketState(
             String name,
             boolean isEdible,
@@ -38,39 +50,52 @@ public abstract class ItemBucket extends ItemFood implements IItemContainer {
             @Nullable Tag<Block<?>> pickupTag,
             boolean canPlace,
             @Nullable String soundPath,
-            @Nullable BiConsumer<World, TilePos> placementAction
+            @Nullable BiConsumer<World, TilePosc> placementAction,
+            @Nullable TextureStateName textureStateName
     ) {
 
         /**
          * Constructor for Empty/Placeholder states
          */
         public BucketState(String name) {
-            this(name, false, 0, 0, null, null, false, null, null);
+            this(name, false, 0, 0, null, null, false, null, null, null);
         }
 
         /**
          * Constructor for Food/Edible Buckets (Non-placeable)
          */
         public BucketState(String name, int healAmount, int ticksPerHeal) {
-            this(name, true, healAmount, ticksPerHeal, null, null, false, null, null);
+            this(name, true, healAmount, ticksPerHeal, null, null, false, null, null, null);
         }
         /**
          * Constructor for Fluids
          */
         public BucketState(String name, Block<?> fluidBlock, Tag<Block<?>> pickupTag, String soundPath) {
-            this(name, false, 0, 0, fluidBlock, pickupTag, true, soundPath, null);
+            this(name, false, 0, 0, fluidBlock, pickupTag, true, soundPath, null, null);
+        }
+        /**
+         * Constructor for Fluids with a conditional item texture
+         */
+        public BucketState(String name, Block<?> fluidBlock, Tag<Block<?>> pickupTag, String soundPath, @Nullable TextureStateName textureStateName) {
+            this(name, false, 0, 0, fluidBlock, pickupTag, true, soundPath, null, textureStateName);
         }
         /**
          * Constructor for Fluids with custom action
          */
-        public BucketState(String name, Block<?> fluidBlock, Tag<Block<?>> pickupTag, String soundPath, BiConsumer<World, TilePos> placementAction) {
-            this(name, false, 0, 0, fluidBlock, pickupTag, true, soundPath, placementAction);
+        public BucketState(String name, Block<?> fluidBlock, Tag<Block<?>> pickupTag, String soundPath, BiConsumer<World, TilePosc> placementAction) {
+            this(name, false, 0, 0, fluidBlock, pickupTag, true, soundPath, placementAction, null);
+        }
+        /**
+         * Constructor for Fluids with custom action and conditional item texture
+         */
+        public BucketState(String name, Block<?> fluidBlock, Tag<Block<?>> pickupTag, String soundPath, BiConsumer<World, TilePosc> placementAction, @Nullable TextureStateName textureStateName) {
+            this(name, false, 0, 0, fluidBlock, pickupTag, true, soundPath, placementAction, textureStateName);
         }
         /**
          * Constructor for Edible and Placeable Buckets
          */
         public BucketState(String name, int healAmount, int ticksPerHeal, Block<?> fluidBlock, Tag<Block<?>> pickupTag, String soundPath) {
-            this(name, true, healAmount, ticksPerHeal, fluidBlock, pickupTag, true, soundPath, null);
+            this(name, true, healAmount, ticksPerHeal, fluidBlock, pickupTag, true, soundPath, null, null);
         }
     }
 
@@ -91,7 +116,8 @@ public abstract class ItemBucket extends ItemFood implements IItemContainer {
                 "water",
                 Blocks.FLUID_WATER_FLOWING,
                 BlockTags.IS_WATER,
-                "liquid.splash"
+                "liquid.splash",
+                ItemBucket::resolveWaterTextureStateName
         ));
 
         registerState(STATE_LAVA, new BucketState(
@@ -141,6 +167,28 @@ public abstract class ItemBucket extends ItemFood implements IItemContainer {
         return id.value();
     }
 
+    private static @NotNull String resolveWaterTextureStateName(final @Nullable Entity entity, final @Nullable World world, final @NotNull ItemStack stack, final @NotNull String defaultName) {
+        final World context = entity != null ? entity.world : world;
+        if (context != null && context.getWorldType().hasTag(WorldTypeTags.NETHER)) {
+            return "waterboiling";
+        }
+        return defaultName;
+    }
+
+    public static @NotNull String getTextureStateName(final @Nullable Entity entity, final @NotNull ItemStack stack) {
+        return getTextureStateName(entity, entity != null ? entity.world : null, stack);
+    }
+
+    public static @NotNull String getTextureStateName(final @Nullable Entity entity, final @Nullable World world, final @NotNull ItemStack stack) {
+        final NamespaceID stateId = getState(stack);
+        final BucketState state = getBucketState(stateId);
+        final String defaultName = getStateName(stateId);
+        if (state.textureStateName() != null) {
+            return state.textureStateName().resolve(entity, world, stack, defaultName);
+        }
+        return defaultName;
+    }
+
     public static Set<NamespaceID> getRegisteredStateIds() {
         return STATE_REGISTRY.keySet();
     }
@@ -188,49 +236,83 @@ public abstract class ItemBucket extends ItemFood implements IItemContainer {
     }
 
     @Override
-    public ItemStack onUse(@NotNull ItemStack itemStack, @NotNull World world, @NotNull Player player) {
-        NamespaceID currentStateId = getState(itemStack);
-        BucketState currentState = getBucketState(currentStateId);
+    public boolean interactsWithFluid(@NotNull ItemStack selfStack) {
+        final NamespaceID currentStateId = getState(selfStack);
+        final BucketState currentState = getBucketState(currentStateId);
+        if (!currentState.canPlace() && currentState.isEdible()) return false;
+        return STATE_EMPTY.equals(currentStateId) || getCharges(selfStack) < this.maxCharges;
+    }
 
-        if (currentState.isEdible() && !currentState.canPlace()) {
-            return handleAirClick(itemStack, world, player);
-        }
+    @Override
+    public boolean interactsWithEntity(@NotNull ItemStack selfStack, @NotNull Entity entity) {
+        if (!super.interactsWithEntity(selfStack, entity)) return false;
+        final NamespaceID currentStateId = getState(selfStack);
+        final BucketState currentState = getBucketState(currentStateId);
+        return currentState.canPlace() || !currentState.isEdible();
+    }
 
-        double reachDistance = player.getGamemode().getBlockReachDistance();
-        HitResult hitResultFluid = player.rayCast(reachDistance, 1f, true, false, false);
+    @Override
+    public boolean useOnEntity(@NotNull ItemStack selfStack, @NotNull Player player, @NotNull Mob mob) {
+        NamespaceID currentStateId = getState(selfStack);
+        BucketState currentState = getBucketState(currentStateId);
 
-        if (hitResultFluid == null) {
-            return handleAirClick(itemStack, world, player);
-        } else if (hitResultFluid instanceof HitResult.Entity) {
-            return handleEntityClick(itemStack, world, player);
-        } else if (hitResultFluid instanceof HitResult.Tile hitTileFluid) {
-            TilePosc tilePos = hitTileFluid.tilePos;
-            boolean isSourceBlock = world.getBlockData(tilePos) == 0;
+        if (currentState.isEdible() && !currentState.canPlace()) return false;
 
-            if (isSourceBlock) {
-                NamespaceID targetStateId = getFluidStateAt(world, tilePos);
+        final int oldSize = selfStack.stackSize;
+        var newStack = handleEntityClick(selfStack, mob.world, player, mob);
+        if (newStack.stackSize <= 0) newStack = null;
 
-                boolean isEmptyScoop = STATE_EMPTY.equals(currentStateId);
-                boolean isRefillScoop = currentStateId.equals(targetStateId) && getCharges(itemStack) < this.maxCharges;
+        final boolean isDifferent = oldSize != newStack.stackSize || newStack != selfStack;
+        if (isDifferent) player.inventory.setItem(player.inventory.getCurrentSlot(), newStack);
 
+        return isDifferent;
+    }
+
+    @Override
+    public boolean onUseOnBlock(@NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player, @NotNull TilePosc tilePos, @NotNull Side side, double xHit, double yHit) {
+        final NamespaceID currentStateId = getState(selfStack);
+        final BucketState currentState = getBucketState(currentStateId);
+
+        if (currentState.isEdible() && !currentState.canPlace()) return false;
+
+        final int oldSize = selfStack.stackSize;
+        @Nullable ItemStack newStack;
+
+        interact: {
+            if (world.getBlockData(tilePos) == 0) {
+                final NamespaceID targetStateId = getFluidStateAt(world, tilePos);
+    
+                final boolean isEmptyScoop = STATE_EMPTY.equals(currentStateId);
+                final boolean isRefillScoop = !isEmptyScoop &&
+                    currentStateId.equals(targetStateId) && getCharges(selfStack) < this.maxCharges;
+    
                 if (targetStateId != null && (isEmptyScoop || isRefillScoop)) {
-                    return tryPickupFluid(itemStack, world, player, tilePos, targetStateId);
+                    newStack = tryPickupFluid(selfStack, world, player, tilePos, targetStateId);
+                    break interact;
                 }
             }
-
+            
             if (currentState.canPlace()) {
-                NamespaceID aimedFluid = getFluidStateAt(world, tilePos);
-                if (currentStateId.equals(aimedFluid) && getCharges(itemStack) < this.maxCharges) {
-                    return itemStack;
+                final NamespaceID aimedFluid = getFluidStateAt(world, tilePos);
+                if (currentStateId.equals(aimedFluid) && getCharges(selfStack) < this.maxCharges) {
+                    newStack = selfStack;
+                    break interact;
                 }
 
-                HitResult hitResultNoFluid = player.rayCast(reachDistance, 1f, false, false, false);
-                if (hitResultNoFluid instanceof HitResult.Tile hitTileNoFluid) {
-                    return tryPlaceFluid(itemStack, world, player, hitTileNoFluid);
-                }
+                newStack = tryPlaceFluid(selfStack, world, player, tilePos, side);
+                break interact;
             }
+            return false;
         }
 
+        final boolean isDifferent = oldSize != newStack.stackSize || newStack != selfStack;
+        if (isDifferent) player.inventory.setItem(player.inventory.getCurrentSlot(), newStack);
+
+        return isDifferent;
+    }
+
+    @Override
+    public ItemStack onUse(@NotNull ItemStack itemStack, @NotNull World world, @NotNull Player player) {
         return handleAirClick(itemStack, world, player);
     }
 
@@ -255,50 +337,38 @@ public abstract class ItemBucket extends ItemFood implements IItemContainer {
         }
     }
 
-    protected ItemStack tryPlaceFluid(ItemStack itemStack, World world, Player player, HitResult.Tile hitTile) {
-        TilePos rayCastPos = new TilePos(hitTile.tilePos);
-        if (!world.canMineBlock(player, rayCastPos)) return itemStack;
+    protected ItemStack tryPlaceFluid(@NotNull ItemStack itemStack, @NotNull World world, @NotNull Player player, @NotNull TilePosc tilePos, @NotNull Side side) {
+        if (!world.canMineBlock(player, tilePos)) return itemStack;
+
+        final NamespaceID stateId = getState(itemStack);
+        final BucketState bucketState = getBucketState(stateId);
+        if (bucketState.fluidBlock() == null) return itemStack;
 
-        Block<?> blockAtPos = world.getBlockType(rayCastPos);
+        final var blockAtPos = world.getBlockType(tilePos);
         if (!blockAtPos.hasTag(BlockTags.PLACE_OVERWRITES) && !blockAtPos.hasTag(BlockTags.BROKEN_BY_FLUIDS)) {
-            Side side = hitTile.side;
-            rayCastPos.x += side.offsetX();
-            rayCastPos.y += side.offsetY();
-            rayCastPos.z += side.offsetZ();
+            tilePos = tilePos.add(side, new TilePos());
         }
 
-        if (rayCastPos.y < 0 || rayCastPos.y >= world.getHeightBlocks()) return itemStack;
-
-        if (world.isAirBlock(rayCastPos) || !world.getBlockMaterial(rayCastPos).isSolid()) {
-            NamespaceID stateId = getState(itemStack);
-            BucketState bucketState = getBucketState(stateId);
-
-            if (bucketState.fluidBlock() != null) {
-                if (bucketState.soundPath() != null) {
-                    world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS, rayCastPos.x + 0.5F, rayCastPos.y + 0.5F, rayCastPos.z + 0.5F, bucketState.soundPath(), 1f, 1f);
-                }
+        if (!world.canBlockIdBePlacedAt(bucketState.fluidBlock().id(), tilePos, true, side)) return itemStack;
 
-                if (bucketState.placementAction() != null) {
-                    bucketState.placementAction().accept(world, rayCastPos);
-                }
+        if (bucketState.placementAction() != null) bucketState.placementAction().accept(world, tilePos);
 
-                final Block<?> replacedBlock = world.getBlockType(rayCastPos);
-                final int replacedData = world.getBlockData(rayCastPos);
+        final Block<?> replacedBlock = world.getBlockType(tilePos);
+        final int replacedData = world.getBlockData(tilePos);
 
-                world.setBlockTypeNotify(rayCastPos, bucketState.fluidBlock());
+        if (!world.setBlockTypeNotify(tilePos, bucketState.fluidBlock())) return itemStack;
 
-                if (!(replacedBlock == Blocks.AIR)) {
-                    replacedBlock.dropWithCause(world, EnumDropCause.WORLD, rayCastPos, replacedData, null, null);
-                }
+        replacedBlock.dropWithCause(world, EnumDropCause.WORLD, tilePos, replacedData, null, null);
 
-                player.swingItem();
+        if (player.getGamemode().hasBlockConsumption()) {
+            setCharges(itemStack, getCharges(itemStack) - 1);
+            if (getCharges(itemStack) == 0) setState(itemStack, STATE_EMPTY);
+        }
 
-                if (player.getGamemode().hasBlockConsumption()) {
-                    setCharges(itemStack, getCharges(itemStack) - 1);
-                    if (getCharges(itemStack) == 0) setState(itemStack, STATE_EMPTY);
-                }
-            }
+        if (bucketState.soundPath() != null) {
+            world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS, tilePos.x() + 0.5F, tilePos.y() + 0.5F, tilePos.z() + 0.5F, bucketState.soundPath(), 1f, 1f);
         }
+
         return itemStack;
     }
 
@@ -310,7 +380,7 @@ public abstract class ItemBucket extends ItemFood implements IItemContainer {
         return itemStack;
     }
 
-    protected ItemStack handleEntityClick(ItemStack itemStack, World world, Player player) {
+    protected ItemStack handleEntityClick(@NotNull ItemStack itemStack, @NotNull World world, @NotNull Player player, @NotNull Entity mob) {
         return handleAirClick(itemStack, world, player);
     }
 
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemDoor.java b/game/core/src/main/java/net/minecraft/core/item/ItemDoor.java
index c12f48bc1..73777cce1 100644
--- a/game/core/src/main/java/net/minecraft/core/item/ItemDoor.java
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemDoor.java
@@ -18,126 +18,76 @@ import org.jetbrains.annotations.Nullable;
 
 import java.util.Random;
 
-public class ItemDoor extends Item {
-    protected final @NotNull Block<? extends BlockLogicDoor> doorBlockBottom;
-    protected final @NotNull Block<? extends BlockLogicDoor> doorBlockTop;
-
+public class ItemDoor extends ItemPlaceablePair {
     public ItemDoor(@NotNull String name, @NotNull String namespaceId, int id, @NotNull Block<? extends BlockLogicDoor> doorBlockBottom, @NotNull Block<? extends BlockLogicDoor> doorBlockTop) {
-        super(name, namespaceId, id);
-        this.maxStackSize = 64;
-        this.doorBlockBottom = doorBlockBottom;
-        this.doorBlockTop = doorBlockTop;
+        super(name, namespaceId, id, doorBlockBottom, doorBlockTop);
     }
 
     @Override
-    public boolean onUseOnBlock(@NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player, @NotNull TilePosc blockPos, @NotNull Side side, double xHit, double yHit) {
-        if (player == null) return false;
-        if (!world.canPlaceInsideBlock(blockPos)) {
-            blockPos = blockPos.add(side.direction(), new TilePos());
-        }
-
-        if (!world.canBlockIdBePlacedAt(this.doorBlockBottom.id(), blockPos, false, side)) return false;
-
-        Direction leftDir = player.getHorizontalPlacementDirection(side).rotateY(1);
-        int meta = leftDir.legacyIndex();
-
+    protected @NotNull PlacementResult getPlacement(@NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player, @NotNull TilePosc blockPos, @NotNull Side side, double xHit, double yHit, @NotNull PlacementResult result) {
         final var upperPos = blockPos.up(new TilePos());
 
-        int isSolidBlockLeft = (isSupported(world, blockPos, leftDir, false) ? 1 : 0) + (isSupported(world, upperPos, leftDir, false) ? 1 : 0);
-        int isSolidBlockRight = (isSupported(world, blockPos, leftDir.opposite(), true) ? 1 : 0) + (isSupported(world, upperPos, leftDir.opposite(), true) ? 1 : 0);
-        boolean isDoorLeft = world.getBlockType(blockPos.add(leftDir, new TilePos())) == this.doorBlockBottom || world.getBlockType(blockPos.add(leftDir, new TilePos()).up(new TilePos())) == this.doorBlockTop;
-        boolean isDoorRight = world.getBlockType(blockPos.sub(leftDir, new TilePos())) == this.doorBlockBottom || world.getBlockType(blockPos.sub(leftDir, new TilePos()).up(new TilePos())) == this.doorBlockTop;
-        boolean isMirrored = false;
+        final Direction leftDir;
+        if (player != null) leftDir = player.getHorizontalPlacementDirection(side).rotateY(1);
+        else {
+            var dir = side.direction.opposite();
+            if (!dir.isHorizontal()) dir = Direction.NORTH;
+            leftDir = dir.rotateY(1);
+        }
 
+        final int isSolidBlockLeft =
+            (isSupported(world, blockPos, leftDir, false) ? 1 : 0) +
+            (isSupported(world, upperPos, leftDir, false) ? 1 : 0);
+        final int isSolidBlockRight =
+            (isSupported(world, blockPos, leftDir.opposite(), true) ? 1 : 0) +
+            (isSupported(world, upperPos, leftDir.opposite(), true) ? 1 : 0);
+
+        final var query = new TilePos();
+        final boolean isDoorLeft = 
+            world.getBlockType(blockPos.add(leftDir, query)) == this.blockA ||
+            world.getBlockType(blockPos.add(leftDir, query).up()) == this.blockB;
+        final boolean isDoorRight = 
+            world.getBlockType(blockPos.sub(leftDir, query)) == this.blockA ||
+            world.getBlockType(blockPos.sub(leftDir, query).up()) == this.blockB;
+
+        final boolean isMirrored;
         if (isDoorLeft && !isDoorRight) {
             isMirrored = (world.getBlockData(blockPos.add(leftDir, new TilePos())) & BlockLogicDoor.MASK_HINGE) == 0;
         } else if (isSolidBlockLeft <= 0 || side.direction() != leftDir.opposite()) {
-            if (isSolidBlockRight > 0 && side.direction() == leftDir) {
-                isMirrored = true;
-            } else if (isSolidBlockRight > isSolidBlockLeft) {
-                isMirrored = true;
-            }
-        }
+            isMirrored = 
+                (isSolidBlockRight > 0 && side.direction() == leftDir) || 
+                (isSolidBlockRight > isSolidBlockLeft);
+        } else isMirrored = false;
+
+        int meta = leftDir.legacyIndex();
         if (isMirrored) {
-            meta = meta - 1 & 0b11;
-            meta |= 0b100;
-            meta |= 0b1000;
+            meta -= 1;
+            meta &= 0b0011;
+            meta |= 0b1100;
         }
         meta |= (DyeColor.colorFromItemMeta(selfStack.getMetadata()).blockMeta) << 4 & 0xF0;
 
-        if (!this.doorBlockBottom.getLogic().canPlaceAt(world, blockPos, meta)) {
-            return false;
+        if (!((BlockLogicDoor)this.blockA.getLogic()).canPlaceAt(world, blockPos, meta)) {
+            result.cannotPlace = true;
+            return result;
         }
 
-        world.setBlockTypeData(blockPos, this.doorBlockBottom, meta);
-        world.setBlockTypeData(upperPos, this.doorBlockTop, meta);
-        selfStack.consumeItem(player);
-
-        this.doorBlockBottom.onPlacedByMob(world, blockPos, side, player, xHit, yHit);
-        this.doorBlockTop.onPlacedByMob(world, upperPos, side, player, xHit, yHit);
-
-        // this is technically incorrect as both positions are updated as bottom door,
-        // but does it really matter?
-        world.notifyBlocksInCapsuleOfNeighborChange(Direction.UP, blockPos, this.doorBlockBottom);
-        world.markBlockNeedsUpdate(blockPos);
-        world.markBlockNeedsUpdate(upperPos);
-
-        world.playBlockSoundEffect(player, blockPos.x() + 0.5F, blockPos.y() + 0.5F, blockPos.z() + 0.5F, this.doorBlockBottom, EnumBlockSoundEffectType.PLACE);
-        return true;
+        result.dataA = result.dataB = meta;
+        result.offsetB.set(0, 1, 0);
+        return result;
     }
 
     public boolean isSupported(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Direction direction, boolean mirrored) {
-        ISupport support = world.getSupport(tilePos.add(direction, new TilePos()), direction.opposite().side());
-        return support.canSupport(mirrored ? PartialSupport.INSTANCE.right() : PartialSupport.INSTANCE.left(), direction.side());
+        return world
+            .getSupport(tilePos.add(direction, new TilePos()), direction.opposite().side())
+            .canSupport(mirrored ? PartialSupport.INSTANCE.right() : PartialSupport.INSTANCE.left(), direction.side());
     }
 
     @Override
-    public void onUseByActivator(@NotNull ItemStack selfStack, @NotNull World world, @NotNull TileEntityActivator activator, @NotNull Random random, @NotNull TilePosc blockPos, @NotNull Direction direction, double offX, double offY, double offZ) {
-        if (!world.canPlaceInsideBlock(blockPos)) {
-            blockPos = blockPos.add(direction, new TilePos());
-        }
-
-        if (!direction.isHorizontal()) direction = Direction.NORTH;
-        Direction lefDir = direction.rotateY(1);
-        int meta = lefDir.legacyIndex();
-
-        int isSolidBlockLeft = (isSupported(world, blockPos, lefDir, false) ? 1 : 0) + (isSupported(world, blockPos.up(new TilePos()), lefDir, false) ? 1 : 0);
-        int isSolidBlockRight = (isSupported(world, blockPos, lefDir.opposite(), true) ? 1 : 0) + (isSupported(world, blockPos.up(new TilePos()), lefDir.opposite(), true) ? 1 : 0);
-        boolean isDoorLeft = world.getBlockType(blockPos.add(lefDir, new TilePos())) == this.doorBlockBottom || world.getBlockType(blockPos.add(lefDir, new TilePos()).up(new TilePos())) == this.doorBlockTop;
-        boolean isDoorRight = world.getBlockType(blockPos.sub(lefDir, new TilePos())) == this.doorBlockBottom || world.getBlockType(blockPos.sub(lefDir, new TilePos()).up(new TilePos())) == this.doorBlockTop;
-        boolean isMirrored = false;
-
-        if (isDoorLeft && !isDoorRight) {
-            isMirrored = (world.getBlockData(blockPos.add(lefDir, new TilePos())) & BlockLogicDoor.MASK_HINGE) == 0;
-        } else if (isSolidBlockLeft <= 0 || direction != lefDir.opposite()) {
-            if (isSolidBlockRight > 0 && direction == lefDir) {
-                isMirrored = true;
-            } else if (isSolidBlockRight > isSolidBlockLeft) {
-                isMirrored = true;
-            }
-        }
-        if (isMirrored) {
-            meta = meta - 1 & 0b11;
-            meta |= 0b100;
-            meta |= 0b1000;
-        }
-        meta |= (DyeColor.colorFromItemMeta(selfStack.getMetadata()).blockMeta) << 4 & 0xF0;
-        if (!this.doorBlockBottom.getLogic().canPlaceAt(world, blockPos, meta)) {
-            return;
-        }
-
-        world.setBlockTypeData(blockPos, this.doorBlockBottom, meta);
-        world.setBlockTypeData(blockPos.up(new TilePos()), this.doorBlockTop, meta);
-        world.markBlockNeedsUpdate(blockPos);
-        world.markBlockNeedsUpdate(blockPos.up(new TilePos()));
-
-        selfStack.consumeItem(null);
-
-        this.doorBlockBottom.onPlacedOnSide(world, blockPos, direction.side(), 0.5, 0.5);
-        this.doorBlockTop.onPlacedOnSide(world, blockPos.up(new TilePos()), direction.side(), 0.5, 0.5);
-
-        world.notifyBlocksInCapsuleOfNeighborChange(Direction.UP, blockPos, this.doorBlockBottom);
-        
-        world.playBlockSoundEffect(null, blockPos.x() + 0.5F, blockPos.y() + 0.5F, blockPos.z() + 0.5F, this.doorBlockBottom, EnumBlockSoundEffectType.PLACE);
+    public boolean canPlaceDirectlyAtPosition(@NotNull ItemStack selfStack, @NotNull World world,
+            @Nullable Player player, @NotNull TilePosc blockPos, @NotNull Side side, double xHit, double yHit) {
+        return 
+            IPlaceable.canPlaceBlockDirectlyAtPosition(this.blockA, world, blockPos, side) &&
+            IPlaceable.canPlaceBlockDirectlyAtPosition(this.blockA, world, blockPos, side);
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemJar.java b/game/core/src/main/java/net/minecraft/core/item/ItemJar.java
index b2ec41d87..86da7cdc2 100644
--- a/game/core/src/main/java/net/minecraft/core/item/ItemJar.java
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemJar.java
@@ -1,20 +1,25 @@
 package net.minecraft.core.item;
 
 import net.minecraft.core.block.Blocks;
+import net.minecraft.core.block.entity.TileEntityActivator;
 import net.minecraft.core.entity.Entity;
+import net.minecraft.core.entity.ICapturable;
+import net.minecraft.core.entity.Mob;
 import net.minecraft.core.entity.animal.MobButterfly;
 import net.minecraft.core.entity.animal.MobFireflyCluster;
 import net.minecraft.core.entity.player.Player;
-import net.minecraft.core.util.helper.MathHelper;
-import net.minecraft.core.util.helper.Side;
+import net.minecraft.core.util.helper.Direction;
 import net.minecraft.core.world.World;
+import net.minecraft.core.world.pos.TilePos;
 import net.minecraft.core.world.pos.TilePosc;
+
+import java.util.List;
+import java.util.Random;
+
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.joml.primitives.AABBd;
 
-import java.util.List;
-
 public class ItemJar extends ItemPlaceable {
 
     protected ItemJar(@NotNull String name, @NotNull String namespaceId, int id) {
@@ -23,77 +28,83 @@ public class ItemJar extends ItemPlaceable {
     }
 
     @Override
-    public boolean onUseOnBlock(@NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player, @NotNull TilePosc blockPos, @NotNull Side side, double xHit, double yHit) {
-        if (player != null && player.isSneaking()) {
-            return super.onUseOnBlock(selfStack, world, player, blockPos, side, xHit, yHit);
-        }
-        if (player != null) {
-            int count = selfStack.stackSize;
-            ItemStack used = onUse(selfStack, world, player);
-            if (used != null && used.stackSize != count) {
-                return true;
-            }
-        }
-        return super.onUseOnBlock(selfStack, world, player, blockPos, side, xHit, yHit);
+    public boolean interactsWithEntity(@NotNull ItemStack selfStack, @NotNull Entity entity) {
+        return super.interactsWithEntity(selfStack, entity) || (
+            entity instanceof ICapturable pokemon && pokemon.canBeCapturedBy(this)
+        );
     }
 
     @Override
-    public @Nullable ItemStack onUse(@NotNull ItemStack selfStack, @NotNull World world, @NotNull Player player) {
-        if (player.isSneaking()) {
-            return selfStack;
-        }
-        List<Entity> list = world.getEntitiesWithinAABBExcludingEntity(player, MathHelper.aabbGrow(player.bb, 2, 2, 2, new AABBd()));
+    public boolean useOnEntity(@NotNull ItemStack selfStack, @NotNull Player player, @NotNull Mob mob) {
+        final var captured =  this.tryCaptureEntity(selfStack, player, mob);
+        if (captured == null) return false;
+        player.inventory.insertItem(captured, true);
+        if (captured.stackSize <= 0) return true;
+        player.world.dropItem(player.x, player.y + player.bbHeight, player.z, captured, 0.0);
+        return true;
+    }
 
-        for (Entity entity : list) {
-            if (entity instanceof MobFireflyCluster fireflyCluster) {
-                MobFireflyCluster.FireflyColor colour = fireflyCluster.getColor();
+    @Override
+    public void onUseByActivator(
+        @NotNull ItemStack selfStack,
+        @NotNull World world, @NotNull TileEntityActivator activator,
+        @NotNull Random random, @NotNull TilePosc blockPos,
+        @NotNull Direction direction, double offX, double offY, double offZ
+    ) {
+        final var pos = blockPos.add(direction, new TilePos());
+        final @NotNull List<Entity> entities;
 
-                boolean didFillJar;
-                if (colour == MobFireflyCluster.FireflyColor.BLUE) {
-                    didFillJar = fillJar(player, new ItemStack(Items.LANTERN_FIREFLY_BLUE, 1));
-                } else if (colour == MobFireflyCluster.FireflyColor.ORANGE) {
-                    didFillJar = fillJar(player, new ItemStack(Items.LANTERN_FIREFLY_ORANGE, 1));
-                } else if (colour == MobFireflyCluster.FireflyColor.RED) {
-                    didFillJar = fillJar(player, new ItemStack(Items.LANTERN_FIREFLY_RED, 1));
-                } else {
-                    didFillJar = fillJar(player, new ItemStack(Items.LANTERN_FIREFLY_GREEN, 1));
-                }
+        interface ctx { AABBd bb = new AABBd(); }
+        synchronized(ctx.bb) {
+            ctx.bb.setMin(0,0,0).setMax(1,1,1).translate(pos.x(), pos.y(), pos.z());
+            entities = world.getEntitiesWithinAABBExcludingEntity(null, ctx.bb);
+        }
 
-                if (!world.isClientSide && didFillJar) {
-                    fireflyCluster.setFireflyCount(fireflyCluster.getFireflyCount() - 1);
-                    if (fireflyCluster.getFireflyCount() <= 0) {
-                        fireflyCluster.remove();
-                    }
-                }
+        boolean hasEntity = false;
+        for (final var entity : entities) {
+            if (entity instanceof ICapturable pokemon && pokemon.canBeCapturedBy(this)) {
+                final var captured = this.tryCaptureEntity(selfStack, null, entity);
+                if (captured == null) continue;
+                world.dropItem(pos, captured);
+                return;
+            }
+            if (entity.isSelectable()) hasEntity = true;
+        }
+        if (hasEntity) return;
+        super.placeWithoutShift(selfStack, world, null, pos, direction.opposite().side(), 0.5, 0.5);
+    }
 
-                return selfStack;
-            } else if (entity instanceof MobButterfly butterfly) {
-                MobButterfly.ButterflyEntry color = MobButterfly.colorEntries.get(butterfly.getColor());
+    public @Nullable ItemStack tryCaptureEntity(
+        @NotNull ItemStack selfStack,
+        @Nullable Player player, @NotNull Entity entity
+    ) {
+        if (selfStack.stackSize <= 0) return null;
+        if (entity instanceof ICapturable pokemon); else return null;
+        if (!pokemon.canBeCapturedBy(this)) return null;
 
-                boolean didFillJar = fillJar(player, new ItemStack(color.jarItem.get(), 1));
+        final int count = pokemon.onCaptured();
+        if (count == 0) return null;
 
-                if (!world.isClientSide && didFillJar) {
-                    butterfly.remove();
-                }
+        final Item resultingItem;
+        if (pokemon instanceof MobFireflyCluster firefly) resultingItem = captureFirefly(firefly);
+        else if (pokemon instanceof MobButterfly butterfly) resultingItem = captureButterfly(butterfly);
+        else return null;
 
-                return selfStack;
-            }
-        }
-        return selfStack;
+        return new ItemStack(resultingItem);
     }
 
-    public static boolean fillJar(@NotNull Player player, @NotNull ItemStack itemToGive) {
-        ItemStack currentItem = player.inventory.getCurrentItem();
-        if (currentItem == null || currentItem.stackSize <= 1) {
-            player.inventory.setItem(player.inventory.getCurrentSlot(), itemToGive);
-        } else {
-            player.inventory.insertItem(itemToGive, true);
-        }
-        if (itemToGive.stackSize < 1) {
-            player.swingItem();
-            player.inventory.getCurrentItem().consumeItem(player);
-            return true;
-        }
-        return false;
+    // TODO extensibility
+    private static @NotNull Item captureFirefly(@NotNull MobFireflyCluster firefly) {
+        final var colour = firefly.getColor();
+        if (colour == MobFireflyCluster.FireflyColor.BLUE) return Items.LANTERN_FIREFLY_BLUE;
+        if (colour == MobFireflyCluster.FireflyColor.ORANGE) return Items.LANTERN_FIREFLY_ORANGE;
+        if (colour == MobFireflyCluster.FireflyColor.RED) return Items.LANTERN_FIREFLY_RED;
+        if (colour == MobFireflyCluster.FireflyColor.GREEN) return Items.LANTERN_FIREFLY_GREEN;
+        throw new AssertionError("unreachable");
+    }
+    private static @NotNull Item captureButterfly(@NotNull MobButterfly butterfly) {
+        final var color = MobButterfly.colorEntries.get(butterfly.getColor());
+        assert color != null;
+        return color.jarItem();
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemPebble.java b/game/core/src/main/java/net/minecraft/core/item/ItemPebble.java
index 2625ad6ef..c9f1cd304 100644
--- a/game/core/src/main/java/net/minecraft/core/item/ItemPebble.java
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemPebble.java
@@ -4,27 +4,20 @@ import net.minecraft.core.block.Block;
 import net.minecraft.core.block.BlockLogicOverlayPebbles;
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.block.entity.TileEntityActivator;
-import net.minecraft.core.block.tag.BlockTags;
 import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.entity.projectile.ProjectilePebble;
-import net.minecraft.core.enums.EnumBlockSoundEffectType;
 import net.minecraft.core.util.helper.Direction;
 import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.world.World;
-import net.minecraft.core.world.pos.TilePos;
 import net.minecraft.core.world.pos.TilePosc;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
-import org.joml.primitives.AABBd;
-import org.joml.primitives.AABBdc;
 
 import java.util.Random;
 
-public class ItemPebble extends Item implements IDispensable {
-
+public class ItemPebble extends Item implements IDispensable, IAccumulatable<BlockLogicOverlayPebbles> {
     public ItemPebble(String name, String namespaceId, int id) {
         super(name, namespaceId, id);
-        this.maxStackSize = 64;
     }
 
     @Override
@@ -38,87 +31,50 @@ public class ItemPebble extends Item implements IDispensable {
     }
 
     @Override
-    public boolean onUseOnBlock(@NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player, @NotNull TilePosc blockPos, @NotNull Side side, double xHit, double yHit) {
-        if (player == null) return false;
-        @NotNull Block<?> block = world.getBlockType(blockPos);
-        int meta = world.getBlockData(blockPos);
-        if (block != Blocks.OVERLAY_PEBBLES && block.hasTag(BlockTags.PLACE_OVERWRITES)) {
-            block = Blocks.AIR;
-            meta = 0;
-        }
-        if (selfStack.stackSize <= 0) {
-            return false;
-        }
-        if (blockPos.y() == world.getHeightBlocks() - 1 && Blocks.OVERLAY_PEBBLES.getMaterial().isSolid()) {
-            return false;
-        }
-
-        final var pebble = Blocks.OVERLAY_PEBBLES.getLogic();
-
-        // Check if we're placing "inside" a layer
-        if (block == Blocks.OVERLAY_PEBBLES && side == Side.TOP) {
-            final int newMeta = meta + 1;
-            if (!pebble.isSupported(world, blockPos, Side.BOTTOM)) return false;
-
-            if (newMeta < BlockLogicOverlayPebbles.MAX_PEBBLES) {
-                world.setBlockTypeDataNotify(blockPos, Blocks.OVERLAY_PEBBLES, newMeta);
-                world.playBlockSoundEffect(player, blockPos.x() + 0.5F, blockPos.y() + 0.5F, blockPos.z() + 0.5F, Blocks.OVERLAY_PEBBLES, EnumBlockSoundEffectType.PLACE);
-                selfStack.consumeItem(player);
-                return true;
-            }
-        }
-
-        // Offset position
-        if (block != Blocks.AIR) {
-            blockPos = blockPos.add(side.direction(), new TilePos());
-            block = world.getBlockType(blockPos);
-            meta = world.getBlockData(blockPos);
-        }
-
-        // Check if the block we're placing into is a layer
-        if (block == Blocks.OVERLAY_PEBBLES) {
-            final int newMeta = meta + 1;
-
-            final AABBdc bbBox = new AABBd(blockPos.x(), blockPos.y(), blockPos.z(), blockPos.x() + 1f, blockPos.y() + (2 * (newMeta + 1)) / 16.0f, blockPos.z() + 1f);
-            if (!world.checkIfAABBIsClear(bbBox) || !pebble.isSupported(world, blockPos, Side.BOTTOM)) {
-                return false;
-            }
+    public @NotNull Block<BlockLogicOverlayPebbles> getBlock() {
+        return Blocks.OVERLAY_PEBBLES;
+    }
 
-            if (newMeta < BlockLogicOverlayPebbles.MAX_PEBBLES) {
-                world.setBlockTypeDataNotify(blockPos, Blocks.OVERLAY_PEBBLES, newMeta);
-                world.playBlockSoundEffect(player, blockPos.x() + 0.5F, blockPos.y() + 0.5F, blockPos.z() + 0.5F, Blocks.OVERLAY_PEBBLES, EnumBlockSoundEffectType.PLACE);
-                selfStack.consumeItem(player);
-                return true;
-            }
+    @Override
+    public boolean onUseOnBlock(
+        @NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player,
+        @NotNull TilePosc blockPos, @NotNull Side side, double xHit, double yHit
+    ) {
+        return this.placeOnBlock(selfStack, world, player, blockPos, side, xHit, yHit);
+    }
 
-        }
+    @Override
+    public boolean canAccumulateInto(
+        @NotNull ItemStack selfStack,
+        @NotNull Block<?> block, int data, @NotNull Side side, double xHit, double yHit
+    ) {
+        return block == Blocks.OVERLAY_PEBBLES && data < BlockLogicOverlayPebbles.MAX_PEBBLES - 1;
+    }
 
-        // Regular placement, as all else fails
-        if (world.canBlockIdBePlacedAt(Blocks.OVERLAY_PEBBLES.id(), blockPos, false, side) && pebble.isSupported(world, blockPos, Side.BOTTOM)) {
-            if (world.setBlockTypeNotify(blockPos, Blocks.OVERLAY_PEBBLES)) {
-                Blocks.OVERLAY_PEBBLES.onPlacedByMob(world, blockPos, side, player, xHit, yHit);
-                world.playBlockSoundEffect(player, blockPos.x() + 0.5F, blockPos.y() + 0.5F, blockPos.z() + 0.5F, Blocks.OVERLAY_PEBBLES, EnumBlockSoundEffectType.PLACE);
-                selfStack.consumeItem(player);
-                return true;
-            }
-        }
-        return false;
+    @Override
+    public @NotNull BlockDataResult getAccumulationResult(
+        @NotNull ItemStack selfStack,
+        @NotNull World world, @Nullable Player player, @NotNull TilePosc tilePos,
+        @NotNull Side side, double xHit, double yHit,
+        @NotNull BlockDataResult result
+    ) {
+        result.data += 1;
+        return result;
     }
 
     @Override
     public void onUseByActivator(@NotNull ItemStack selfStack, @NotNull World world, @NotNull TileEntityActivator activator, @NotNull Random random, @NotNull TilePosc blockPos, @NotNull Direction direction, double offX, double offY, double offZ) {
-        ProjectilePebble projectilePebble = new ProjectilePebble(world, blockPos.x() + offX, blockPos.y() + offY, blockPos.z() + offZ);
-        projectilePebble.setHeading(direction.offsetX() * 0.6, direction.offsetY() == 0 ? 0.1 : direction.offsetY() * 0.6, direction.offsetZ() * 0.6f, 1.1F, 6F);
-        world.entityJoinedWorld(projectilePebble);
-        selfStack.stackSize -= 1;
+        if (!selfStack.consumeItem(null)) return;
+        final var pebble = new ProjectilePebble(world, blockPos.x() + offX, blockPos.y() + offY, blockPos.z() + offZ);
+        pebble.setHeading(direction.offsetX() * 0.6, direction.offsetY() == 0 ? 0.1 : direction.offsetY() * 0.6, direction.offsetZ() * 0.6f, 1.1F, 6F);
+        world.entityJoinedWorld(pebble);
     }
 
     @Override
     public void onDispensed(@NotNull ItemStack selfStack, @NotNull World world, @NotNull Random random, @NotNull Direction direction, double x, double y, double z) {
-        if (selfStack.consumeItem(null)) {
-            ProjectilePebble projectilePebble = new ProjectilePebble(world, x, y, z);
-            projectilePebble.setHeading(direction.offsetX(), direction.offsetY() + 0.1D, direction.offsetZ(), 1.1F, 6F);
-            world.entityJoinedWorld(projectilePebble);
-        }
+        if (!selfStack.consumeItem(null)) return;
+        final var pebble = new ProjectilePebble(world, x, y, z);
+        pebble.setHeading(direction.offsetX(), direction.offsetY() + 0.1D, direction.offsetZ(), 1.1F, 6F);
+        world.entityJoinedWorld(pebble);
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemPlaceable.java b/game/core/src/main/java/net/minecraft/core/item/ItemPlaceable.java
index 0d190171a..c9a939d64 100644
--- a/game/core/src/main/java/net/minecraft/core/item/ItemPlaceable.java
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemPlaceable.java
@@ -1,88 +1,51 @@
 package net.minecraft.core.item;
 
 import net.minecraft.core.block.Block;
-import net.minecraft.core.block.entity.TileEntity;
+import net.minecraft.core.block.BlockLogic;
 import net.minecraft.core.block.entity.TileEntityActivator;
-import net.minecraft.core.block.tag.BlockTags;
 import net.minecraft.core.entity.player.Player;
-import net.minecraft.core.enums.EnumBlockSoundEffectType;
-import net.minecraft.core.item.block.ItemBlock;
+import net.minecraft.core.util.collection.NamespaceID;
 import net.minecraft.core.util.helper.Direction;
 import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.world.World;
-import net.minecraft.core.world.pos.TilePos;
 import net.minecraft.core.world.pos.TilePosc;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import java.util.Random;
 
-public class ItemPlaceable extends Item {
+public class ItemPlaceable extends Item implements IPlaceable.PlaceableBlock<BlockLogic> {
 
-    public final @NotNull Block<?> blockToPlace;
+    public final @NotNull Block<?> block;
 
-    public ItemPlaceable(@NotNull String name, @NotNull String namespaceId, int id, @NotNull Block<?> blockToPlace) {
-        super(name, namespaceId, id);
+    public ItemPlaceable(@NotNull NamespaceID namespaceId, @NotNull String translationKey, int id, @NotNull Block<?> blockToPlace) {
+        super(namespaceId, translationKey, id);
 
-        this.blockToPlace = blockToPlace;
+        this.block = blockToPlace;
     }
 
-    @Override
-    public boolean onUseOnBlock(@NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player, @NotNull TilePosc blockPos, @NotNull Side side, double xHit, double yHit) {
-        if (selfStack.stackSize <= 0) {
-            return false;
-        }
-        if (ItemBlock.shouldShift(world, blockPos, this.blockToPlace)) {
-            blockPos = blockPos.add(side.direction(), new TilePos());
-        }
-        if (blockPos.y() < 0 || blockPos.y() >= world.getHeightBlocks()) {
-            return false;
-        }
-        if (!world.canBlockIdBePlacedAt(this.blockToPlace.id(), blockPos, false, side)) return false;
-
-        final var data = this.blockToPlace.getPlacedData(player, selfStack, world, blockPos, side, xHit, yHit);
-
-        final var pbloc = world.getBlockType(blockPos);
-        final int pdata = world.getBlockData(blockPos);
-
-        if (!world.setBlockTypeDataRaw(blockPos, this.blockToPlace, data)) return false;
-
-        if (pbloc != this.blockToPlace || pdata != data) {
-            pbloc.onRemoved(world, blockPos, pdata);
-        }
-
-        selfStack.consumeItem(player);
+    public ItemPlaceable(@NotNull String translationKey, @NotNull String namespaceId, int id, @NotNull Block<?> blockToPlace) {
+        super(translationKey, namespaceId, id);
 
-        if (player == null) {
-            this.blockToPlace.onPlacedOnSide(world, blockPos, side, xHit, yHit);
-        } else {
-            this.blockToPlace.onPlacedByMob(world, blockPos, side, player, xHit, yHit);
-        }
-
-        // TODO before or after tileentity?
-        this.blockToPlace.onPlacedByWorld(world, blockPos);
+        this.block = blockToPlace;
+    }
 
-        if (this.blockToPlace.isEntityTile && selfStack.getData().containsKey("tileEntityData")) {
-            TileEntity tileEntity = world.getTileEntity(blockPos);
-            if (tileEntity != null) {
-                tileEntity.readAdditionalData(selfStack.getData().getCompound("tileEntityData"));
-            }
-        }
+    @Override
+    @SuppressWarnings("unchecked")
+    public @NotNull Block<BlockLogic> getBlock() {
+        return (Block<BlockLogic>)this.block;
+    }
 
-        // notify self change after onPlacedByMob
-        // this should fix almost all bugs related to piston intercepting placement logic,
-        // with out change to individual onPlaced<_> logics.
-        world.notifyBlockChange(blockPos, this.blockToPlace);
-        world.playBlockSoundEffect(player, blockPos.x() + 0.5F, blockPos.y() + 0.5F, blockPos.z() + 0.5F, this.blockToPlace, EnumBlockSoundEffectType.PLACE);
-        return true;
+    @Override
+    public boolean onUseOnBlock(
+        @NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player,
+        @NotNull TilePosc blockPos, @NotNull Side side, double xHit, double yHit
+    ) {
+        return this.placeOnBlock(selfStack, world, player, blockPos, side, xHit, yHit);
     }
 
     @Override
     public void onUseByActivator(@NotNull ItemStack selfStack, @NotNull World world, @NotNull TileEntityActivator activator, @NotNull Random random, @NotNull TilePosc blockPos, @NotNull Direction direction, double offX, double offY, double offZ) {
-        blockPos = blockPos.add(direction, new TilePos());
-        Block<?> b = world.getBlockType(blockPos);
-        if (b == null || BlockTags.PLACE_OVERWRITES.appliesTo(b)) {
-            onUseOnBlock(selfStack, world, null, blockPos, direction.side(), 0.5, 0.5);
-        }
+        this.placeByActivator(selfStack, world, activator, random, blockPos, direction, offX, offY, offZ);
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemPlaceablePair.java b/game/core/src/main/java/net/minecraft/core/item/ItemPlaceablePair.java
new file mode 100644
index 000000000..3827f3647
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemPlaceablePair.java
@@ -0,0 +1,157 @@
+package net.minecraft.core.item;
+
+import net.minecraft.core.block.Block;
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.block.entity.TileEntityActivator;
+import net.minecraft.core.entity.player.Player;
+import net.minecraft.core.enums.EnumBlockSoundEffectType;
+import net.minecraft.core.util.collection.NamespaceID;
+import net.minecraft.core.util.helper.Direction;
+import net.minecraft.core.util.helper.Side;
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.pos.TilePos;
+import net.minecraft.core.world.pos.TilePosc;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.joml.Vector3i;
+
+import java.util.Random;
+
+public abstract class ItemPlaceablePair extends Item implements IPlaceable {
+
+    protected final @NotNull Block<?> blockA;
+    protected final @NotNull Block<?> blockB;
+
+    private static final PlacementResult resultBuffer = new PlacementResult();
+
+    public ItemPlaceablePair(
+        @NotNull NamespaceID namespaceId, @NotNull String translationKey, int id,
+        @NotNull Block<?> blockA, @NotNull Block<?> blockB
+    ) {
+        super(namespaceId, translationKey, id);
+        this.blockA = blockA;
+        this.blockB = blockB;
+    }
+    
+    public ItemPlaceablePair(
+        @NotNull String translationKey,@NotNull String namespaceId, int id,
+        @NotNull Block<?> blockA, @NotNull Block<?> blockB
+    ) {
+        super(translationKey, namespaceId, id);
+        this.blockA = blockA;
+        this.blockB = blockB;
+    }
+
+    protected static class PlacementResult {
+        public int dataA;
+        public int dataB;
+        public final @NotNull Vector3i offsetB = new Vector3i();
+        public boolean cannotPlace;
+    }
+
+    protected abstract @NotNull PlacementResult getPlacement(
+        @NotNull ItemStack selfStack,
+        @NotNull World world, @Nullable Player player, @NotNull TilePosc blockPos,
+        @NotNull Side side, double xHit, double yHit,
+        @NotNull PlacementResult result
+    );
+
+    @Override
+    public boolean shouldShiftOutOf(
+        @NotNull ItemStack selfStack, 
+        @NotNull World world, @NotNull Player player, @NotNull TilePosc blockPos,
+        @NotNull Side side, double xHit, double yHit
+    ) {
+        return IPlaceable.shouldShiftBlockOutOf(blockA, world, blockPos);
+    }
+
+    @Override
+    public boolean onUseOnBlock(
+        @NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player,
+        @NotNull TilePosc blockPos, @NotNull Side side, double xHit, double yHit
+    ) {
+        return this.placeOnBlock(selfStack, world, player, blockPos, side, xHit, yHit);
+    }
+
+    @Override
+    public void onUseByActivator(@NotNull ItemStack selfStack, @NotNull World world, @NotNull TileEntityActivator activator, @NotNull Random random, @NotNull TilePosc blockPos, @NotNull Direction direction, double offX, double offY, double offZ) {
+        this.placeByActivator(selfStack, world, activator, random, blockPos, direction, offX, offY, offZ);
+    }
+
+    @Override
+    public boolean placeWithoutShift(
+        @NotNull ItemStack selfStack, @NotNull World world,
+        @Nullable Player player, @NotNull TilePosc blockPos,
+        @NotNull Side side, double xHit, double yHit
+    ) {
+        if (selfStack.stackSize <= 0) return false;
+        if (!this.canPlaceDirectlyAtPosition(selfStack, world, player, blockPos, side, xHit, yHit)) return false;
+
+        final TilePosc posA, posB;
+        final int dataA, dataB;
+
+        synchronized (resultBuffer) {
+            resultBuffer.dataA = resultBuffer.dataB = 0;
+            resultBuffer.offsetB.set(0,0,0);
+            resultBuffer.cannotPlace = false;
+            final var result = this.getPlacement(selfStack, world, player, blockPos, side, xHit, yHit, resultBuffer);
+
+            if (result.cannotPlace) return false;
+
+            dataA = result.dataA;
+            dataB = result.dataB;
+            posA = blockPos;
+            posB = blockPos.add(result.offsetB, new TilePos());
+        }
+
+        final @Nullable Block<?> prevBlockA, prevBlockB;
+        final int prevDataA, prevDataB;
+        final boolean isAIdentical, isBIdentical;
+
+        prevBlockA = world.getBlockType(posA);
+        prevDataA = world.getBlockData(posA);
+
+        prevBlockB = world.getBlockType(posB);
+        prevDataB = world.getBlockData(posB);
+        
+        isAIdentical = prevBlockA == this.blockA && prevDataA == dataA;
+        isBIdentical = prevBlockB == this.blockB && prevDataB == dataB;
+        
+        if (!isAIdentical) world.setBlockTypeDataRaw(posA, this.blockA, dataA);
+        if (!isBIdentical) world.setBlockTypeDataRaw(posB, this.blockB, dataB);
+        if (!isAIdentical) world.markBlockNeedsUpdate(posA);
+        if (!isBIdentical) world.markBlockNeedsUpdate(posB);
+
+        if (!isAIdentical) prevBlockA.onRemoved(world, posA, prevDataA);
+        if (!isBIdentical) prevBlockB.onRemoved(world, posB, prevDataB);
+        if (player == null) {
+            if (!isAIdentical) this.blockA.onPlacedOnSide(world, posA, side, xHit, yHit);
+            if (!isBIdentical) this.blockB.onPlacedOnSide(world, posB, side, xHit, yHit);
+        } else {
+            if (!isAIdentical) this.blockA.onPlacedByMob(world, posA, side, player, xHit, yHit);
+            if (!isBIdentical) this.blockB.onPlacedByMob(world, posB, side, player, xHit, yHit);
+        }
+        if (!isAIdentical) this.blockA.onPlacedByWorld(world, posA);
+        if (!isBIdentical) this.blockB.onPlacedByWorld(world, posB);
+
+        if (this.blockA.isEntityTile && selfStack.getData().containsKey("tileEntityData")) {
+            final var tileEntity = world.getTileEntity(blockPos);
+            if (tileEntity != null) {
+                tileEntity.readAdditionalData(selfStack.getData().getCompound("tileEntityData"));
+            }
+        }
+        if (isAIdentical != isBIdentical) {
+            if (isAIdentical) world.notifyBlocksOfNeighborChange(posB, this.blockB);
+            else world.notifyBlocksOfNeighborChange(posA, this.blockA);
+        } else if (isAIdentical) {
+            assert isBIdentical;
+            world.notifyBlocksOfNeighborChanges(new TilePosc[] { posA, posB }, null);
+        }
+        world.playBlockSoundEffect(
+            player,
+            posA.x() + 0.5F, posA.y() + 0.5F, posA.z() + 0.5F,
+            this.blockA, EnumBlockSoundEffectType.PLACE
+        );
+        return true;
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemRope.java b/game/core/src/main/java/net/minecraft/core/item/ItemRope.java
index fc0c9432c..c5d2c7c3e 100644
--- a/game/core/src/main/java/net/minecraft/core/item/ItemRope.java
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemRope.java
@@ -2,63 +2,48 @@ package net.minecraft.core.item;
 
 import net.minecraft.core.block.Block;
 import net.minecraft.core.block.BlockLogicRope;
+import net.minecraft.core.block.entity.TileEntityActivator;
 import net.minecraft.core.entity.player.Player;
-import net.minecraft.core.enums.EnumBlockSoundEffectType;
+import net.minecraft.core.util.helper.Direction;
 import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.pos.TilePos;
 import net.minecraft.core.world.pos.TilePosc;
+
+import java.util.Random;
+
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 public class ItemRope extends ItemPlaceable {
-    public ItemRope(@NotNull String name, @NotNull String namespaceId, int id, @NotNull Block<?> blockToPlace) {
+    public ItemRope(@NotNull String name, @NotNull String namespaceId, int id, @NotNull Block<BlockLogicRope> blockToPlace) {
         super(name, namespaceId, id, blockToPlace);
     }
 
     @Override
     public boolean onUseOnBlock(@NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player, @NotNull TilePosc blockPos, @NotNull Side side, double xHit, double yHit) {
-        if (selfStack.stackSize <= 0) {
-            return false;
-        }
-        TilePos bp = new TilePos(blockPos);
-        boolean placeOnSide = true;
-        if (world.getBlockType(bp) == this.blockToPlace && player != null && !player.isSneaking()) {
-            placeOnSide = false;
-            TilePos tempPos = new TilePos(bp);
-            while (tempPos.y > 0) {
-                tempPos.y--;
-                if (world.canBlockIdBePlacedAt(this.blockToPlace.id(), tempPos, false, side)) {
-                    bp.y = tempPos.y;
-                    break;
-                } else if (world.getBlockType(tempPos) != this.blockToPlace) {
-                    if (side == Side.TOP) {
-                        tempPos.y++;
-                        break;
-                    }
-                    return true;
-                }
-            }
-            bp.set(tempPos);
-        }
-        if (!world.canPlaceInsideBlock(bp)) {
-            bp = bp.add(side.direction(), new TilePos());
-        }
-        if (bp.y < 0 || bp.y >= world.getHeightBlocks()) {
-            return false;
-        }
-        if (world.canBlockIdBePlacedAt(this.blockToPlace.id(), bp, false, side) && selfStack.consumeItem(player)) {
-            if (world.setBlockTypeData(bp, this.blockToPlace, placeOnSide ? BlockLogicRope.MASK_ROPE_CONNECT : 0)) {
-                if (player == null) {
-                    this.blockToPlace.onPlacedOnSide(world, bp, side, xHit, yHit);
-                } else {
-                    this.blockToPlace.onPlacedByMob(world, bp, side, player, xHit, yHit);
-                }
-                world.notifyBlockChange(bp, this.blockToPlace);
-                world.playBlockSoundEffect(player, bp.x + 0.5F, bp.y + 0.5F, bp.z + 0.5F, this.blockToPlace, EnumBlockSoundEffectType.PLACE);
-                return true;
-            }
+        if (selfStack.stackSize <= 0) return false;
+
+        final var placePos = new TilePos(blockPos);
+        final boolean placeOnSide =
+            side != Side.TOP &&
+            (player == null || !player.isSneaking()) &&
+            world.getBlockType(placePos) == this.block;
+
+        if (placeOnSide) while (placePos.y-- > 0) {
+            if (world.getBlockType(placePos) == this.block) continue;
+            if (world.canBlockIdBePlacedAt(this.block.id(), placePos, false, side)) break;
+            else return false;
         }
-        return false;
+        if (super.shouldShiftOutOf(selfStack, world, player, placePos, side, xHit, yHit)) placePos.add(side);
+
+        final int meta = (placeOnSide) ? BlockLogicRope.MASK_ROPE_CONNECT : 0;
+        return super.placeDirectly(selfStack, world, player, placePos, meta, side, xHit, yHit);
+    }
+
+    @Override
+    public void onUseByActivator(@NotNull ItemStack selfStack, @NotNull World world, @NotNull TileEntityActivator activator, @NotNull Random random, @NotNull TilePosc blockPos, @NotNull Direction direction, double offX, double offY, double offZ) {
+        blockPos = blockPos.add(direction, new TilePos());
+        this.onUseOnBlock(selfStack, world, null, blockPos, direction.opposite().side(), Double.NaN, Double.NaN);
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemStack.java b/game/core/src/main/java/net/minecraft/core/item/ItemStack.java
index d8e53be79..998e08a73 100644
--- a/game/core/src/main/java/net/minecraft/core/item/ItemStack.java
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemStack.java
@@ -526,4 +526,12 @@ public final class ItemStack {
         this.tag.putInt(key, value);
         return this;
     }
+
+    public boolean interactsWithFluid() {
+        return this.getItem().interactsWithFluid(this);
+    }
+
+    public boolean interactsWithEntity(@NotNull Entity entity) {
+        return this.getItem().interactsWithEntity(this, entity);
+    }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemStatue.java b/game/core/src/main/java/net/minecraft/core/item/ItemStatue.java
index 6c6cab39d..c759d0ed2 100644
--- a/game/core/src/main/java/net/minecraft/core/item/ItemStatue.java
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemStatue.java
@@ -1,11 +1,7 @@
 package net.minecraft.core.item;
 
 import net.minecraft.core.block.Block;
-import net.minecraft.core.block.entity.TileEntity;
-import net.minecraft.core.block.entity.TileEntityActivator;
 import net.minecraft.core.entity.player.Player;
-import net.minecraft.core.enums.EnumBlockSoundEffectType;
-import net.minecraft.core.util.helper.Direction;
 import net.minecraft.core.util.helper.MathHelper;
 import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.world.World;
@@ -13,82 +9,38 @@ import net.minecraft.core.world.pos.TilePos;
 import net.minecraft.core.world.pos.TilePosc;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
+import org.joml.primitives.AABBd;
 
-import java.util.Random;
-
-public class ItemStatue extends Item {
-    protected final @NotNull Block<?> statueBlockBottom;
-    protected final @NotNull Block<?> statueBlockTop;
-
+public class ItemStatue extends ItemPlaceablePair {
     public ItemStatue(@NotNull String name, @NotNull String namespaceId, int id, @NotNull Block<?> statueBlockBottom, @NotNull Block<?> statueBlockTop) {
-        super(name, namespaceId, id);
-        this.maxStackSize = 64;
-        this.statueBlockBottom = statueBlockBottom;
-        this.statueBlockTop = statueBlockTop;
+        super(name, namespaceId, id, statueBlockBottom, statueBlockTop);
     }
 
     @Override
-    public boolean onUseOnBlock(@NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player, @NotNull TilePosc blockPos, @NotNull Side side, double xHit, double yHit) {
-        if (!world.canPlaceInsideBlock(blockPos)) {
-            blockPos = blockPos.add(side.direction(), new TilePos());
-        }
-        if (!this.statueBlockBottom.canPlaceAt(world, blockPos)) {
-            return false;
-        }
-
-        final var bb = statueBlockBottom.getCollisionAABB(world, blockPos);
-        if (bb != null && !world.checkIfAABBIsClear(bb)) {
-            return false;
-        }
-
-        selfStack.consumeItem(player);
-        world.setBlockTypeData(blockPos, this.statueBlockBottom, MathHelper.floor((double) (((player.yRot + 180F) * 16F) / 360F) + 0.5D) & 0xf);
-        world.setBlockTypeData(blockPos.up(new TilePos()), this.statueBlockTop, MathHelper.floor((double) (((player.yRot + 180F) * 16F) / 360F) + 0.5D) & 0xf);
-        world.markBlockNeedsUpdate(blockPos);
-        world.markBlockNeedsUpdate(blockPos.up(new TilePos()));
-        
-        this.statueBlockBottom.onPlacedByMob(world, blockPos, side, player, xHit, yHit);
-        this.statueBlockTop.onPlacedByMob(world, blockPos.up(new TilePos()), side, player, xHit, yHit);
-
-        if (this.statueBlockBottom.isEntityTile && selfStack.getData().containsKey("tileEntityData")) {
-            TileEntity tileEntity = world.getTileEntity(blockPos);
-            if (tileEntity != null) {
-                tileEntity.readAdditionalData(selfStack.getData().getCompound("tileEntityData"));
-            }
-        }
-        world.notifyBlocksInCapsuleOfNeighborChange(Direction.UP, blockPos, this.statueBlockBottom);
-        
-        world.playBlockSoundEffect(player, blockPos.x() + 0.5F, blockPos.y() + 0.5F, blockPos.z() + 0.5F, this.statueBlockBottom, EnumBlockSoundEffectType.PLACE);
-
-        return true;
+    protected @NotNull PlacementResult getPlacement(
+        @NotNull ItemStack selfStack,
+        @NotNull World world, @Nullable Player player, @NotNull TilePosc blockPos,
+        @NotNull Side side, double xHit, double yHit,
+        @NotNull PlacementResult result
+    ) {
+        final double yRot;
+        if (player == null) yRot = side.direction.opposite().yaw();
+        else if (player.rotationLockHorizontal != null) yRot = player.rotationLockHorizontal.yaw();
+        else yRot = player.yRot;
+
+        result.dataA = result.dataB = MathHelper.floor(((yRot + 180D) * (16D / 360D)) + 0.5D) & 0xF;
+        result.offsetB.set(0,1,0);
+        return result;
     }
 
     @Override
-    public void onUseByActivator(@NotNull ItemStack selfStack, @NotNull World world, @NotNull TileEntityActivator activator, @NotNull Random random, @NotNull TilePosc blockPos, @NotNull Direction direction, double offX, double offY, double offZ) {
-        if (!world.canPlaceInsideBlock(blockPos)) {
-            blockPos = blockPos.add(direction, new TilePos());
-        }
-        if (!this.statueBlockBottom.canPlaceAt(world, blockPos)) {
-            return;
-        }
-
-        world.setBlockType(blockPos, this.statueBlockBottom);
-        world.setBlockType(blockPos.up(new TilePos()), this.statueBlockTop);
-        world.markBlockNeedsUpdate(blockPos);
-        world.markBlockNeedsUpdate(blockPos.up(new TilePos()));
-        world.notifyBlocksInCapsuleOfNeighborChange(Direction.UP, blockPos, this.statueBlockBottom);
-
-        world.playBlockSoundEffect(null, blockPos.x() + 0.5F, blockPos.y() + 0.5F, blockPos.z() + 0.5F, this.statueBlockBottom, EnumBlockSoundEffectType.PLACE);
-        this.statueBlockBottom.onPlacedOnSide(world, blockPos, direction.side(), 0.5, 0.5);
-        this.statueBlockTop.onPlacedOnSide(world, blockPos.up(new TilePos()), direction.side(), 0.5, 0.5);
-
-        if (this.statueBlockBottom.isEntityTile && selfStack.getData().containsKey("tileEntityData")) {
-            TileEntity tileEntity = world.getTileEntity(blockPos);
-            if (tileEntity != null) {
-                tileEntity.readAdditionalData(selfStack.getData().getCompound("tileEntityData"));
-            }
-        }
-
-        selfStack.consumeItem(null);
+    public boolean canPlaceDirectlyAtPosition(
+        @NotNull ItemStack selfStack,
+        @NotNull World world, @Nullable Player player, @NotNull TilePosc blockPos,
+        @NotNull Side side, double xHit, double yHit
+    ) {
+        return
+            IPlaceable.canPlaceBlockDirectlyAtPosition(blockA, world, blockPos, side) &&
+            IPlaceable.canPlaceBlockDirectlyAtPosition(blockB, world, blockPos.up(new TilePos()), side);
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/item/block/ItemBlock.java b/game/core/src/main/java/net/minecraft/core/item/block/ItemBlock.java
index deb28011a..7f2228482 100644
--- a/game/core/src/main/java/net/minecraft/core/item/block/ItemBlock.java
+++ b/game/core/src/main/java/net/minecraft/core/item/block/ItemBlock.java
@@ -2,124 +2,58 @@ package net.minecraft.core.item.block;
 
 import net.minecraft.core.block.Block;
 import net.minecraft.core.block.BlockLogic;
-import net.minecraft.core.block.Blocks;
-import net.minecraft.core.block.entity.TileEntity;
 import net.minecraft.core.block.entity.TileEntityActivator;
-import net.minecraft.core.block.tag.BlockTags;
 import net.minecraft.core.entity.player.Player;
-import net.minecraft.core.enums.EnumBlockSoundEffectType;
+import net.minecraft.core.item.IPlaceable;
 import net.minecraft.core.item.Item;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.util.helper.Direction;
 import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.world.World;
-import net.minecraft.core.world.pos.TilePos;
 import net.minecraft.core.world.pos.TilePosc;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
 
-import java.util.Objects;
 import java.util.Random;
 
-public class ItemBlock<T extends BlockLogic> extends Item {
-    protected @NotNull Block<T> block;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class ItemBlock<T extends BlockLogic> extends Item implements IPlaceable.PlaceableBlock<T> {
+    protected final @NotNull Block<T> block;
 
     public ItemBlock(@NotNull Block<T> block) {
         super(block.namespaceId(), block.getKey(), block.id());
-        this.block = Objects.requireNonNull(block);
-    }
-
-    public @NotNull Block<T> getBlock() {
-        return this.block;
+        this.block = block;
     }
 
     @Override
-    public boolean onUseOnBlock(
-        @NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player,
-        @NotNull TilePosc blockPos, @NotNull Side side, double xHit, double yHit
-    ) {
-        if (shouldShift(world, blockPos, this.block)) blockPos = blockPos.add(side.direction(), new TilePos());
-        if (!world.canBlockIdBePlacedAt(this.block.id(), blockPos, false, side)) return false;
-
-        final int meta = getPlacedData(selfStack, world, player, blockPos, side, xHit, yHit);
-        return this.placeDirectly(selfStack, world, player, blockPos, meta, side, xHit, yHit);
-    }
-
-    public static boolean shouldShift(@NotNull World world, @NotNull TilePosc blockPos, @NotNull Block<?> block) {
-        if (!world.canPlaceInsideBlock(blockPos)) return true;
-        return block.hasTag(BlockTags.PLACE_OVERWRITES) && block == world.getBlockType(blockPos);
-    }
-
-    protected final boolean placeWithMeta(
-        @NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player,
-        @NotNull TilePosc blockPos, int meta, @NotNull Side side, double xHit, double yHit
-    ) {
-        if (shouldShift(world, blockPos, this.block)) blockPos = blockPos.add(side.direction(), new TilePos());
-        if (!world.canBlockIdBePlacedAt(this.block.id(), blockPos, false, side)) return false;
-
-        return this.placeDirectly(selfStack, world, player, blockPos, meta, side, xHit, yHit);
-    }
-
-    protected final boolean placeDirectly(
-        @NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player,
-        @NotNull TilePosc blockPos, int meta, @NotNull Side side, double xHit, double yHit
-    ) {
-        if (selfStack.stackSize <= 0) return false;
-        final var pbloc = world.getBlockType(blockPos);
-        final int pdata = world.getBlockData(blockPos);
-
-        if (!world.setBlockTypeDataRaw(blockPos, this.block, meta)) return false;
-
-        if (pbloc != this.block || pdata != meta) {
-            pbloc.onRemoved(world, blockPos, pdata);
-        }
-
-        selfStack.consumeItem(player);
-
-        if (player == null) {
-            this.block.onPlacedOnSide(world, blockPos, side, xHit, yHit);
-        } else {
-            this.block.onPlacedByMob(world, blockPos, side, player, xHit, yHit);
-        }
-
-        this.block.onPlacedByWorld(world, blockPos);
-
-        if (this.block.isEntityTile && selfStack.getData().containsKey("tileEntityData")) {
-            final TileEntity tileEntity = world.getTileEntity(blockPos);
-            if (tileEntity != null) {
-                tileEntity.readAdditionalData(selfStack.getData().getCompound("tileEntityData"));
-            }
-        }
-
-        world.notifyBlockChange(blockPos, this.block);
-        world.playBlockSoundEffect(
-            player,
-            blockPos.x() + 0.5F, blockPos.y() + 0.5F, blockPos.z() + 0.5F,
-            this.block, EnumBlockSoundEffectType.PLACE
-        );
-        return true;
+    public @NotNull String getKey() {
+        return this.block.getKey();
     }
 
     @Override
-    public void onUseByActivator(@NotNull ItemStack selfStack, @NotNull World world, @NotNull TileEntityActivator activator, @NotNull Random random, @NotNull TilePosc blockPos, @NotNull Direction direction, double offX, double offY, double offZ) {
-        blockPos = new TilePos(blockPos).add(direction);
-        @NotNull Block<?> b = world.getBlockType(blockPos);
-        if (b == Blocks.AIR || BlockTags.PLACE_OVERWRITES.appliesTo(b)) {
-            this.onUseOnBlock(selfStack, world, null, blockPos, direction.side(), 0.5, 0.5);
-        }
+    public @NotNull String getLanguageKey(@NotNull ItemStack selfStack) {
+        return this.block.getLanguageKey(selfStack.getMetadata());
     }
 
-    public int getPlacedData(@NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player, @NotNull TilePosc blockPos, @NotNull Side side, double xHit, double yHit) {
-        return this.block.getPlacedData(player, selfStack, world, blockPos, side, xHit, yHit);
+    @Override
+    public @NotNull Block<T> getBlock() {
+        return this.block;
     }
 
     @Override
-    public @NotNull String getLanguageKey(@NotNull ItemStack selfStack) {
-        return this.block.getLanguageKey(selfStack.getMetadata());
+    public boolean onUseOnBlock(
+        @NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player,
+        @NotNull TilePosc blockPos, @NotNull Side side, double xHit, double yHit
+    ) {
+        return this.placeOnBlock(selfStack, world, player, blockPos, side, xHit, yHit);
     }
 
     @Override
-    public @NotNull String getKey() {
-        return this.block.getKey();
+    public void onUseByActivator(
+        @NotNull ItemStack selfStack, @NotNull World world,
+        @NotNull TileEntityActivator activator, @NotNull Random random, @NotNull TilePosc blockPos,
+        @NotNull Direction direction, double offX, double offY, double offZ
+    ) {
+        this.placeByActivator(selfStack, world, activator, random, blockPos, direction, offX, offY, offZ);
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockAlgae.java b/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockAlgae.java
index 829a4db67..5ffc9a08d 100644
--- a/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockAlgae.java
+++ b/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockAlgae.java
@@ -3,14 +3,12 @@ package net.minecraft.core.item.block;
 import net.minecraft.core.block.Block;
 import net.minecraft.core.block.BlockLogic;
 import net.minecraft.core.block.BlockLogicFluid;
-import net.minecraft.core.block.Blocks;
 import net.minecraft.core.block.FluidWater;
 import net.minecraft.core.block.entity.TileEntityActivator;
 import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.util.helper.Direction;
 import net.minecraft.core.util.helper.Side;
-import net.minecraft.core.util.phys.HitResult;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.pos.TilePos;
 import net.minecraft.core.world.pos.TilePosc;
@@ -28,45 +26,33 @@ public class ItemBlockAlgae<T extends BlockLogic> extends ItemBlock<T> {
     }
 
     @Override
-    public boolean onUseOnBlock(@NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player, @NotNull TilePosc blockPos, @NotNull Side side, double xHit, double yHit) {
-        return false;
+    public boolean interactsWithFluid(@NotNull ItemStack selfStack) {
+        return true;
     }
 
     @Override
-    public @Nullable ItemStack onUse(@NotNull ItemStack selfStack, @NotNull World world, @NotNull Player player) {
-        final double reachDistance = player.getGamemode().getBlockReachDistance();
-        final HitResult rayCast = player.rayCast(reachDistance, 1f, true, false, false);
-        if (!(rayCast instanceof HitResult.Tile hitTile)) return selfStack;
-
-        final var pos = new TilePos(hitTile.tilePos);
-        if (!testPlace(world, pos)) return selfStack;
-
-        super.onUseOnBlock(selfStack, world, player, pos, Side.NONE, 0.5, 0.5);
-        return selfStack;
+    public boolean onUseOnBlock(
+        @NotNull ItemStack selfStack,
+        @NotNull World world, @Nullable Player player, @NotNull TilePosc tilePos,
+        @NotNull Side side, double xHit, double yHit
+    ) {
+        if (!this.isWaterSource(world, tilePos)) return false;
+        return super.placeWithoutShift(selfStack, world, player, tilePos.up(new TilePos()), Side.TOP, 0.5, 0.5);
     }
     
     @Override
     public void onUseByActivator(@NotNull ItemStack selfStack, @NotNull World world, @NotNull TileEntityActivator activator, @NotNull Random random, @NotNull TilePosc blockPos, @NotNull Direction direction, double offX, double offY, double offZ) {
         
         final var pos = new TilePos(blockPos).add(direction);
-        if (!testPlace(world, pos)) return;
+        if (!this.isWaterSource(world, pos)) return;
         
-        if (!world.setBlockType(pos, this.block)) return;
-        selfStack.consumeItem(null);
-
-        this.block.onPlacedOnSide(world, pos, Side.NONE, 0.5, 0.5);
-        world.notifyBlockChange(pos, this.block);
+        super.placeWithoutShift(selfStack, world, null, pos.up(), Side.TOP, 0.5, 0.5);
     }
 
-    private static final boolean testPlace(@NotNull World world, @NotNull TilePos toMove) {
-        final TilePos origin = new TilePos(toMove);
-
-        @NotNull Block<?> up = world.getBlockType(toMove.up());
-        if (up != Blocks.AIR) return false;
-
-        if (!(world.getBlockType(origin).getLogic() instanceof BlockLogicFluid fluid)) return false;
+    private final boolean isWaterSource(@NotNull World world, @NotNull TilePosc pos) {
+        if (!(world.getBlockType(pos).getLogic() instanceof BlockLogicFluid fluid)) return false;
         if (!(fluid.fluid instanceof FluidWater)) return false;
-        if (fluid.getDepth(world, origin) != 0) return false;
+        if (fluid.getDepth(world, pos) != 0) return false;
         return true;
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockLadder.java b/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockLadder.java
index 02ec02e26..4ba690e00 100644
--- a/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockLadder.java
+++ b/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockLadder.java
@@ -2,12 +2,17 @@ package net.minecraft.core.item.block;
 
 import net.minecraft.core.block.Block;
 import net.minecraft.core.block.BlockLogicLadder;
+import net.minecraft.core.block.entity.TileEntityActivator;
 import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.item.ItemStack;
+import net.minecraft.core.util.helper.Direction;
 import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.pos.TilePos;
 import net.minecraft.core.world.pos.TilePosc;
+
+import java.util.Random;
+
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -23,8 +28,8 @@ public class ItemBlockLadder<T extends BlockLogicLadder> extends ItemBlock<T> {
         final @NotNull BlockLogicLadder ladder = this.block.getLogic();
 
         final Side pside;
-        if (player == null || player.isSneaking() || world.getBlockType(bp) != this.block) {
-            if (shouldShift(world, bp, this.block)) bp.add(side.direction());
+        if ((player != null && player.isSneaking()) || world.getBlockType(bp) != this.block) {
+            if (super.shouldShiftOutOf(selfStack, world, player, bp, side, xHit, yHit)) bp.add(side.direction());
             pside = ladder.getSideForPlacement(world, bp, side);
         } else {
             xHit = 0.5;
@@ -39,4 +44,14 @@ public class ItemBlockLadder<T extends BlockLogicLadder> extends ItemBlock<T> {
         if (!ladder.canExistAt(world, bp, meta)) return false;
         return super.placeDirectly(selfStack, world, player, bp, meta, pside, xHit, yHit);
     }
+
+    @Override
+    public void onUseByActivator(@NotNull ItemStack selfStack,
+        @NotNull World world,
+        @NotNull TileEntityActivator activator, @NotNull Random random, @NotNull TilePosc blockPos,
+        @NotNull Direction direction, double offX, double offY, double offZ
+    ) {
+        blockPos = blockPos.add(direction, new TilePos());
+        this.onUseOnBlock(selfStack, world, null, blockPos, direction.opposite().side(), Double.NaN, Double.NaN);
+    }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockLayer.java b/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockLayer.java
index 0d4f4bf49..29df0de45 100644
--- a/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockLayer.java
+++ b/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockLayer.java
@@ -3,68 +3,44 @@ package net.minecraft.core.item.block;
 import net.minecraft.core.block.Block;
 import net.minecraft.core.block.BlockLogicLayerBase;
 import net.minecraft.core.entity.player.Player;
-import net.minecraft.core.enums.EnumBlockSoundEffectType;
+import net.minecraft.core.item.IAccumulatable;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.world.World;
-import net.minecraft.core.world.pos.TilePos;
 import net.minecraft.core.world.pos.TilePosc;
+
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
-import org.joml.Vector3d;
-import org.joml.primitives.AABBd;
 
 
-public class ItemBlockLayer<T extends BlockLogicLayerBase> extends ItemBlock<T> {
+public class ItemBlockLayer<T extends BlockLogicLayerBase> extends ItemBlock<T> 
+    implements IAccumulatable<T>
+{
     public ItemBlockLayer(@NotNull Block<T> block) {
         super(block);
     }
 
-    // TODO: sideways placement into layer
     @Override
-    public boolean onUseOnBlock(@NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player, @NotNull TilePosc tilePos, @NotNull Side side, double xHit, double yHit) {
-        if (selfStack.stackSize <= 0) return false; 
-        if (tilePos.y() == world.getHeightBlocks() - 1 && this.block.getMaterial().isSolid()) {
-            return false;
-        }
-        final var targetBlock = world.getBlockType(tilePos);
-        if (targetBlock != this.block || side != Side.TOP) {
-            // you can no longer stack layers by placing on
-            // the top face of a partial block under the layers :3
-            return super.placeWithMeta(selfStack, world, player, tilePos, 0, side, xHit, yHit);
-        }
-
-        final @NotNull BlockLogicLayerBase blockLayer = this.block.getLogic();
-        final int meta = world.getBlockData(tilePos) + 1;
-
-        final var bbmin = new Vector3d(tilePos.vec());
-        final var bbmax = new Vector3d(bbmin).add(1, (meta + 1) / 8.0f, 1);
-        if (!world.checkIfAABBIsClear(new AABBd(bbmin, bbmax))) return false;
-
-        final boolean placed;
-        final @NotNull Block<?> placedBlock;
-        final var placedPos = new TilePos(tilePos);
+    public boolean canAccumulateInto(
+        @NotNull ItemStack selfStack,
+        @NotNull Block<?> block, int data, @NotNull Side side, double xHit, double yHit
+    ) {
+        return block == this.block && !(yHit - ((data + 1) * 0.125) <= -0x1p-12);
+    }
 
-        if (meta < 7) {
-            placedBlock = this.block;
-            placed = world.setBlockTypeData(placedPos, placedBlock, meta);
-        } else if (blockLayer.fullBlock != null) {
-            placedBlock = blockLayer.fullBlock;
-            placed = world.setBlockType(placedPos, placedBlock);
+    @Override
+    public @NotNull BlockDataResult getAccumulationResult(
+        @NotNull ItemStack selfStack,
+        @NotNull World world, @Nullable Player player, @NotNull TilePosc tilePos,
+        @NotNull Side side, double xHit, double yHit,
+        @NotNull BlockDataResult result
+    ) {
+        if (result.data < 6) {
+            result.data += 1;
         } else {
-            placedBlock = this.block;
-            // TODO this can place inside entities
-            placed = world.setBlockType(placedPos.up(), placedBlock);
+            result.block = this.block.getLogic().fullBlock;
+            result.data = 0;
         }
-        if (!placed) return false;
-        selfStack.consumeItem(player);
-
-        placedBlock.onPlacedByMob(world, tilePos, side, player, xHit, yHit);
-        world.notifyBlockChange(placedPos, placedBlock);
-
-        world.playBlockSoundEffect(player,
-            placedPos.x() + 0.5F, placedPos.y() + 0.5F, placedPos.z() + 0.5F,
-            this.block, EnumBlockSoundEffectType.PLACE);
-        return true;
+        return result;
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockLayerLeaves.java b/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockLayerLeaves.java
index 160c93803..030a7e430 100644
--- a/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockLayerLeaves.java
+++ b/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockLayerLeaves.java
@@ -1,71 +1,42 @@
 package net.minecraft.core.item.block;
 
 import net.minecraft.core.block.Block;
-import net.minecraft.core.block.BlockLogicLayerBase;
 import net.minecraft.core.block.BlockLogicLayerLeaves;
 import net.minecraft.core.entity.player.Player;
-import net.minecraft.core.enums.EnumBlockSoundEffectType;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.world.World;
-import net.minecraft.core.world.pos.TilePos;
 import net.minecraft.core.world.pos.TilePosc;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
-import org.joml.Vector3d;
-import org.joml.primitives.AABBd;
 
-public class ItemBlockLayerLeaves<T extends BlockLogicLayerLeaves> extends ItemBlock<T> {
+public class ItemBlockLayerLeaves<T extends BlockLogicLayerLeaves> extends ItemBlockLayer<T> {
     public ItemBlockLayerLeaves(@NotNull Block<T> block) {
         super(block);
     }
 
-    // TODO: refactor to reuse ItemBlockLayer?
     @Override
-    public boolean onUseOnBlock(@NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player, @NotNull TilePosc tilePos, @NotNull Side side, double xHit, double yHit) {
-        if (selfStack.stackSize <= 0) return false; 
-        if (tilePos.y() == world.getHeightBlocks() - 1 && this.block.getMaterial().isSolid()) {
-            return false;
-        }
-        final var targetBlock = world.getBlockType(tilePos);
-        if (targetBlock != this.block || side != Side.TOP) {
-            // you can no longer stack layers by placing on
-            // the top face of a partial block under the layers :3
-            return super.placeWithMeta(selfStack, world, player, tilePos, 
-                BlockLogicLayerLeaves.setPermanent(0, true), side, xHit, yHit);
-        }
-
-        final @NotNull BlockLogicLayerBase blockLayer = this.block.getLogic();
-        final int meta = world.getBlockData(tilePos) + 1;
-
-        final var bbmin = new Vector3d(tilePos.vec());
-        final var bbmax = new Vector3d(bbmin).add(1, (meta + 1) / 8.0f, 1);
-        if (!world.checkIfAABBIsClear(new AABBd(bbmin, bbmax))) return false;
-
-        final boolean placed;
-        final @NotNull Block<?> placedBlock;
-        final var placedPos = new TilePos(tilePos);
+    public boolean canAccumulateInto(
+        @NotNull ItemStack selfStack,
+        @NotNull Block<?> block, int data, @NotNull Side side, double xHit, double yHit
+    ) {
+        return block == this.block && !(yHit - (((data&0xF) + 1) * 0.125) <= -0x1p-12);
+    }
 
-        if ((meta & 0x0F) < 7) {
-            placedBlock = this.block;
-            placed = world.setBlockTypeData(placedPos, placedBlock, BlockLogicLayerLeaves.setPermanent(meta, true));
-        } else if (blockLayer.fullBlock != null) {
-            placedBlock = blockLayer.fullBlock;
-            placed = world.setBlockTypeData(placedPos, placedBlock, BlockLogicLayerLeaves.setPermanent(0, true));
+    @Override
+    public @NotNull BlockDataResult getAccumulationResult(
+        @NotNull ItemStack selfStack,
+        @NotNull World world, @Nullable Player player, @NotNull TilePosc tilePos,
+        @NotNull Side side, double xHit, double yHit,
+        @NotNull BlockDataResult result
+    ) {
+        result.data |= BlockLogicLayerLeaves.MASK_PERMANENT;
+        if ((result.data & 0xF) < 6) {
+            result.data += 1;
         } else {
-            placedBlock = this.block;
-            // TODO this can place inside entities
-            placed = world.setBlockTypeData(placedPos.up(), placedBlock, BlockLogicLayerLeaves.setPermanent(0, true));
+            result.block = this.block.getLogic().fullBlock;
+            result.data &= ~0xF;
         }
-        if (!placed) return false;
-        selfStack.consumeItem(player);
-
-        placedBlock.onPlacedByMob(world, tilePos, side, player, xHit, yHit);
-        world.notifyBlockChange(placedPos, placedBlock);
-
-        world.playBlockSoundEffect(player,
-            placedPos.x() + 0.5F, placedPos.y() + 0.5F, placedPos.z() + 0.5F,
-            this.block, EnumBlockSoundEffectType.PLACE);
-        return true;
+        return result;
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockSlab.java b/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockSlab.java
index 6075de3e5..953a53c21 100644
--- a/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockSlab.java
+++ b/game/core/src/main/java/net/minecraft/core/item/block/ItemBlockSlab.java
@@ -2,73 +2,55 @@ package net.minecraft.core.item.block;
 
 import net.minecraft.core.block.Block;
 import net.minecraft.core.block.BlockLogic;
-import net.minecraft.core.block.tag.BlockTags;
 import net.minecraft.core.entity.player.Player;
-import net.minecraft.core.enums.EnumBlockSoundEffectType;
+import net.minecraft.core.item.IAccumulatable;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.world.World;
-import net.minecraft.core.world.pos.TilePos;
 import net.minecraft.core.world.pos.TilePosc;
+
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
-import org.joml.Vector3d;
-import org.joml.primitives.AABBd;
 
-public class ItemBlockSlab<T extends BlockLogic> extends ItemBlock<T> {
+public class ItemBlockSlab<T extends BlockLogic> extends ItemBlock<T>
+    implements IAccumulatable<T>
+{
     public ItemBlockSlab(@NotNull Block<T> block) {
         super(block);
     }
 
     @Override
-    public boolean onUseOnBlock(@NotNull ItemStack selfStack, @NotNull World world, @Nullable Player player, @NotNull TilePosc blockPos, @NotNull Side side, double xHit, double yHit) {
-        if (selfStack.stackSize <= 0) return false;
-        if (blockPos.y() == world.getHeightBlocks() - 1 && this.block.getMaterial().isSolid()) {
-            return false;
-        }
-
-        var block = world.getBlockType(blockPos);
-        if (block.hasTag(BlockTags.PLACE_OVERWRITES)) {
-            return super.onUseOnBlock(selfStack, world, player, blockPos, side, xHit, yHit);
-        }
-
-        final int selfMeta = selfStack.getMetadata();
-        final var shiftPos = new TilePos(blockPos);
-        
-        int i;
-        for (i = 0; i < 2; i++) {
-            block = world.getBlockType(shiftPos);
-            final var blockMeta = world.getBlockData(shiftPos);
-            final boolean canPlaceInto =
-                block == this.block &&
-                    (blockMeta & ~0b11) == selfMeta &&
-                    switch (blockMeta & 0b11) {
-                        default -> false;
-                        case 0 -> yHit >= 0.5f;
-                        case 2 -> yHit <= 0.5f;
-                    };
-            if (canPlaceInto) break;
-            shiftPos.add(side);
-            yHit -= side.offsetY();
-        } if (i == 2) { // for else
-            return super.onUseOnBlock(selfStack, world, player, blockPos, side, xHit, yHit);
-        }
-
-        final var bbmin = new Vector3d(shiftPos.vec());
-        final var bbmax = new Vector3d(bbmin).add(1,1,1);
-        if (!world.checkIfAABBIsClear(new AABBd(bbmin, bbmax))) return false;
-
-        if (!world.setBlockData(shiftPos, 1 | selfStack.getMetadata())) return false;
-        selfStack.consumeItem(player);
+    public boolean canAccumulateInto(
+        @NotNull ItemStack selfStack,
+        @NotNull Block<?> block, int data, @NotNull Side side, double xHit, double yHit
+    ) {
+        return this.block == block && (data & ~0b11) == selfStack.getMetadata() &&
+            switch (data & 0b11) {
+                default -> false;
+                case 0 -> !(yHit - 0.5f <= -0x1p-12);
+                case 2 -> !(yHit - 0.5f >= +0x1p-12);
+            };
+    }
 
-        // this invokes modelBlock's on place logic. is this a good idea?
-        this.block.onPlacedByWorld(world, shiftPos);
+    @Override
+    public @NotNull BlockDataResult getAccumulationResult(
+        @NotNull ItemStack selfStack,
+        @NotNull World world, @Nullable Player player, @NotNull TilePosc tilePos,
+        @NotNull Side side, double xHit, double yHit,
+        @NotNull BlockDataResult result
+    ) {
+        result.data = 1 | selfStack.getMetadata();
+        result.bounds.setMin(0,0,0).setMax(1,1,1);
+        return result;
+    }
 
-        world.notifyBlockChange(shiftPos, block);
-        world.playBlockSoundEffect(player,
-            shiftPos.x() + 0.5F, shiftPos.y() + 0.5F, shiftPos.z() + 0.5F,
-            this.block, EnumBlockSoundEffectType.PLACE);
-        
-        return true;
+    @Override
+    public void postAccumulate(
+        @NotNull ItemStack selfStack,
+        @NotNull World world, @Nullable Player player, @NotNull TilePosc tilePos,
+        @NotNull Side side, double xHit, double yHit,
+        @NotNull BlockDataResult result
+    ) {
+        this.block.onPlacedByWorld(world, tilePos);
     }
-}
\ No newline at end of file
+}
diff --git a/game/core/src/main/java/net/minecraft/core/player/inventory/CreativeMenuContents.java b/game/core/src/main/java/net/minecraft/core/player/inventory/CreativeMenuContents.java
index 8dc5e25ad..53766556e 100644
--- a/game/core/src/main/java/net/minecraft/core/player/inventory/CreativeMenuContents.java
+++ b/game/core/src/main/java/net/minecraft/core/player/inventory/CreativeMenuContents.java
@@ -17,7 +17,7 @@ import static net.minecraft.core.player.inventory.menu.MenuInventoryCreative.CRE
 import static net.minecraft.core.player.inventory.menu.MenuInventoryCreative.CREATIVE_PAGE_SIZE;
 
 public final class CreativeMenuContents {
-	public static boolean ADD_GAPS = false; // Disabled for now, need to discuss full creative gui rework for 8.1
+	public static boolean ADD_GAPS = true; // Disabled for now, need to discuss full creative gui rework for 8.1
 
 	private static final DyeColor[] RAINBOW_ORDER = {
 		DyeColor.RED,
@@ -90,6 +90,8 @@ public final class CreativeMenuContents {
 
 		addLogTypes(list);
 		newLine(list);
+		addWorkstationsAndGlass(list);
+		newLine(list);
 		addWoodTypes(list);
 		newPage(list);
 
@@ -98,9 +100,6 @@ public final class CreativeMenuContents {
 		addOrganicTypes(list);
 		newPage(list);
 
-		addWorkstationsAndGlass(list);
-		newPage(list);
-
 		addTools(list);
 		newPage(list);
 
@@ -180,6 +179,9 @@ public final class CreativeMenuContents {
 
 		list.add(new ItemStack(Items.TOOL_SHEARS));
 		list.add(new ItemStack(Items.TOOL_SHEARS_STEEL));
+
+		list.add(new ItemStack(Items.MAP));
+		list.add(new ItemStack(Items.LABEL));
 		newLine(list);
 
 		addBucketVariants(list, Items.BUCKET_IRON);
@@ -191,25 +193,23 @@ public final class CreativeMenuContents {
 		list.add(new ItemStack(Items.WAND_MONSTER_SPAWNER));
 		list.add(new ItemStack(Items.WAND_NBT));
 		list.add(new ItemStack(Items.TOOL_FISHINGROD));
+		addGap(list, 1);
+		list.add(new ItemStack(Items.TOOL_COMPASS));
 		newLine(list);
 
 		list.add(new ItemStack(Items.TOOL_BOW));
 		list.add(new ItemStack(Items.AMMO_ARROW));
 		list.add(new ItemStack(Items.AMMO_ARROW_GOLD));
 		list.add(new ItemStack(Items.AMMO_ARROW_FLAMING));
+		addGap(list, 1);
+		list.add(new ItemStack(Items.TOOL_CLOCK));
 		newLine(list);
 
 		list.add(new ItemStack(Items.HANDCANNON_UNLOADED));
 		list.add(new ItemStack(Items.AMMO_CHARGE_EXPLOSIVE));
 		list.add(new ItemStack(Items.AMMO_FIREBALL));
-		newLine(list);
-
-		list.add(new ItemStack(Items.TOOL_COMPASS));
-		list.add(new ItemStack(Items.TOOL_CLOCK));
+		addGap(list, 2);
 		list.add(new ItemStack(Items.TOOL_CALENDAR));
-		list.add(new ItemStack(Items.MAP));
-		list.add(new ItemStack(Items.ROPE));
-		list.add(new ItemStack(Items.LABEL));
 		newLine(list);
 	}
 
@@ -236,8 +236,21 @@ public final class CreativeMenuContents {
 		addBlock(list, Blocks.BUTTON_STONE);
 		addBlock(list, Blocks.PRESSURE_PLATE_STONE);
 		addBlock(list, Blocks.PRESSURE_PLATE_COBBLE_STONE);
+		newLine(list);
+
+		addBlock(list, Blocks.SLATE);
+		addBlock(list, Blocks.LAYER_SLATE);
+
+		addBlock(list, Blocks.BRICK_SLATE);
+		addBlock(list, Blocks.STAIRS_BRICK_SLATE);
+		addBlock(list, Blocks.SLAB_BRICK_SLATE);
+
+		addBlock(list, Blocks.SLATE_POLISHED);
+		addBlock(list, Blocks.SLAB_SLATE_POLISHED);
+		list.add(new ItemStack(Items.STATUE_SLATE));
 		newPage(list);
 
+
 		addBlock(list, Blocks.BASALT);
 		addBlock(list, Blocks.MOSS_BASALT);
 
@@ -256,7 +269,7 @@ public final class CreativeMenuContents {
 		addBlock(list, Blocks.BUTTON_BASALT);
 		addBlock(list, Blocks.PRESSURE_PLATE_BASALT);
 		addBlock(list, Blocks.PRESSURE_PLATE_COBBLE_BASALT);
-		newPage(list);
+		newLine(list);
 
 		addBlock(list, Blocks.LIMESTONE);
 		addBlock(list, Blocks.MOSS_LIMESTONE);
@@ -296,7 +309,7 @@ public final class CreativeMenuContents {
 		addBlock(list, Blocks.BUTTON_GRANITE);
 		addBlock(list, Blocks.PRESSURE_PLATE_GRANITE);
 		addBlock(list, Blocks.PRESSURE_PLATE_COBBLE_GRANITE);
-		newPage(list);
+		newLine(list);
 
 		addBlock(list, Blocks.PERMAFROST);
 
@@ -333,7 +346,7 @@ public final class CreativeMenuContents {
 		addBlock(list, Blocks.BUTTON_NETHERRACK);
 		addBlock(list, Blocks.PRESSURE_PLATE_NETHERRACK);
 		addBlock(list, Blocks.PRESSURE_PLATE_COBBLE_NETHERRACK);
-		newPage(list);
+		newLine(list);
 
 		addBlock(list, Blocks.GLOOMSTONE);
 
@@ -371,7 +384,7 @@ public final class CreativeMenuContents {
 		addBlock(list, Blocks.SLAB_BRICK_BRIMSTONE);
 		addBlock(list, Blocks.BUTTON_BRIMSTONE);
 		addBlock(list, Blocks.PRESSURE_PLATE_BRIMSTONE);
-		newPage(list);
+		newLine(list);
 
 		addBlock(list, Blocks.MARBLE);
 
@@ -384,18 +397,8 @@ public final class CreativeMenuContents {
 		addBlock(list, Blocks.SLAB_CAPSTONE_MARBLE);
 		list.add(new ItemStack(Items.STATUE_MARBLE));
 		list.add(new ItemStack(Items.STATUE_PIGMAN));
-		newLine(list);
-
-		addBlock(list, Blocks.SLATE);
-		addBlock(list, Blocks.LAYER_SLATE);
 
-		addBlock(list, Blocks.BRICK_SLATE);
-		addBlock(list, Blocks.STAIRS_BRICK_SLATE);
-		addBlock(list, Blocks.SLAB_BRICK_SLATE);
 
-		addBlock(list, Blocks.SLATE_POLISHED);
-		addBlock(list, Blocks.SLAB_SLATE_POLISHED);
-		list.add(new ItemStack(Items.STATUE_SLATE));
 		newPage(list);
 	}
 
@@ -409,7 +412,6 @@ public final class CreativeMenuContents {
 		addBlock(list, Blocks.LOG_THORN);
 		addBlock(list, Blocks.LOG_PALM);
 		addBlock(list, Blocks.LOG_SCORCHED);
-		newPage(list);
 	}
 
 	private static void addWoodTypes(List<ItemStack> list) {
@@ -451,7 +453,7 @@ public final class CreativeMenuContents {
 		list.add(new ItemStack(Items.SIGN_PAINTED, 1, color.itemMeta));
 	}
 
-	private static void addPaintedWoodBlock(List<ItemStack> list, Block<?> block, DyeColor color) {
+	private static void addPaintedWoodBlock(@NotNull List<ItemStack> list, Block<?> block, DyeColor color) {
 		list.add(new ItemStack(block, 1, painted(block).toMetadata(color)));
 	}
 
@@ -864,6 +866,7 @@ public final class CreativeMenuContents {
 		addBlock(list, Blocks.FENCE_STEEL);
 
 		list.add(new ItemStack(Items.PAINTING));
+		list.add(new ItemStack(Items.ROPE));
 		newLine(list);
 
 		list.add(new ItemStack(Items.DOOR_GLASS));
diff --git a/game/core/src/main/java/net/minecraft/core/util/helper/Direction.java b/game/core/src/main/java/net/minecraft/core/util/helper/Direction.java
index 2ad48ff04..e4d61e392 100644
--- a/game/core/src/main/java/net/minecraft/core/util/helper/Direction.java
+++ b/game/core/src/main/java/net/minecraft/core/util/helper/Direction.java
@@ -210,6 +210,20 @@ public enum Direction {
         }
     }
 
+    public static @NotNull double[] YAW_MAP = new double[6];
+    static {
+        for (int i = 0; i < 4; i++) {
+            final double yaw = i * 90D;
+            final var dir = Direction.fromYaw(yaw);
+            YAW_MAP[dir.id] = yaw;
+        }
+        YAW_MAP[UP.id] = YAW_MAP[DOWN.id] = Double.NaN;
+    }
+
+    public double yaw() {
+        return YAW_MAP[this.id];
+    }
+
     /**
      * Right-handed around the +X axis
      */
diff --git a/game/core/src/main/java/net/minecraft/core/world/World.java b/game/core/src/main/java/net/minecraft/core/world/World.java
index 27a143f3f..5546e3925 100644
--- a/game/core/src/main/java/net/minecraft/core/world/World.java
+++ b/game/core/src/main/java/net/minecraft/core/world/World.java
@@ -2986,18 +2986,21 @@ public abstract class World implements MutableWorldSource {
         return this.dimensionData.getWorldType();
     }
 
-    public @NotNull EntityItem dropItem(final @NotNull TilePosc tilePos, final @NotNull ItemStack itemStack) {
-        final float radius = 0.7f;
-        final double x = (this.rand.nextFloat() * radius) + (1.0F - radius) * 0.5D;
-        final double y = (this.rand.nextFloat() * radius) + (1.0F - radius) * 0.5D;
-        final double z = (this.rand.nextFloat() * radius) + (1.0F - radius) * 0.5D;
+    public @NotNull EntityItem dropItem(double x, double y, double z, final @NotNull ItemStack itemStack, double radius) {
+        x += this.rand.nextFloat() * radius;
+        y += this.rand.nextFloat() * radius;
+        z += this.rand.nextFloat() * radius;
 
-        final @NotNull EntityItem item = new EntityItem(this, tilePos.x() + x, tilePos.y() + y, tilePos.z() + z, itemStack);
+        final @NotNull EntityItem item = new EntityItem(this, x, y, z, itemStack);
         item.pickupDelay = 10;
         this.entityJoinedWorld(item);
         return item;
     }
 
+    public @NotNull EntityItem dropItem(final @NotNull TilePosc tilePos, final @NotNull ItemStack itemStack) {
+        return this.dropItem(tilePos.x(), tilePos.y(), tilePos.z(), itemStack, 0.7);
+    }
+
     @Deprecated
     public final @NotNull EntityItem dropItem(final int x, final int y, final int z, final @NotNull ItemStack itemStack) {
         return this.dropItem(new TilePos(x, y, z), itemStack);
