diff --git a/game/client/src/main/java/net/minecraft/client/entity/player/PlayerLocalMultiplayer.java b/game/client/src/main/java/net/minecraft/client/entity/player/PlayerLocalMultiplayer.java
index 1d5c84f87..658689a45 100644
--- a/game/client/src/main/java/net/minecraft/client/entity/player/PlayerLocalMultiplayer.java
+++ b/game/client/src/main/java/net/minecraft/client/entity/player/PlayerLocalMultiplayer.java
@@ -15,7 +15,6 @@ import net.minecraft.core.net.packet.PacketPlayerConfig;
 import net.minecraft.core.net.packet.PacketUpdatePlayerState;
 import net.minecraft.core.net.packet.PacketMovePlayer;
 import net.minecraft.core.net.packet.PacketContainerClose;
-import net.minecraft.core.net.packet.PacketUpdateCreativeInventory;
 import net.minecraft.core.net.packet.PacketSetHotbarOffset;
 import net.minecraft.core.net.packet.PacketGuidebook;
 import net.minecraft.core.net.packet.PacketPlayerAction;
@@ -172,12 +171,6 @@ public class PlayerLocalMultiplayer extends PlayerLocal implements Player.Player
     	addToSendHistory(s);
     }
     
-    @Override
-	public void updateCreativeInventory(int page, String searchText)
-    {
-    	sendQueue.addToSendQueue(new PacketUpdateCreativeInventory(containerMenu.containerId, page, searchText));
-    }
-
     @Override
     public void setHotbarOffset(int offset)
     {
diff --git a/game/client/src/main/java/net/minecraft/client/gui/IntegerSliderElement.java b/game/client/src/main/java/net/minecraft/client/gui/IntegerSliderElement.java
index db32b7bfc..950bb3256 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/IntegerSliderElement.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/IntegerSliderElement.java
@@ -1,5 +1,7 @@
 package net.minecraft.client.gui;
 
+import net.minecraft.client.option.Option;
+import net.minecraft.client.option.OptionRange;
 import net.minecraft.client.render.renderer.GLRenderer;
 import net.minecraft.client.render.texture.stitcher.IconCoordinate;
 import net.minecraft.client.render.texture.stitcher.TextureRegistry;
@@ -41,6 +43,14 @@ public class IntegerSliderElement extends ButtonElement {
         return 8;
     }
 
+    private int getStep() {
+        if(option instanceof OptionRange range) {
+            return range.step;
+        }
+
+        return 1;
+    }
+
     @Override
     public void mouseDragged(Minecraft mc, int mouseX, int mouseY) {
         if (!this.visible) {
@@ -51,11 +61,15 @@ public class IntegerSliderElement extends ButtonElement {
         if (this.dragging) {
             int segments = maxValue;
             float percentage = (float) (mouseX - (this.xPosition + 4) - (this.width / (2 * segments))) / (float) (this.width - sliderWidth);
+
             if (percentage > 1.0f)
                 percentage = 1.0f;
             if (percentage < 0.0f)
                 percentage = 0.0f;
-            this.sliderValue = (int) Math.ceil(segments * percentage);
+
+            int step = this.getStep();
+
+            this.sliderValue = Math.round((segments * percentage) / (float) step) * step;
             this.option.setValueWithIndex(this.sliderValue);
             this.displayString = this.option.getDisplayStringValue();
         }
@@ -107,7 +121,10 @@ public class IntegerSliderElement extends ButtonElement {
                 percentage = 1.0f;
             if (percentage < 0.0f)
                 percentage = 0.0f;
-            this.sliderValue = (int) Math.ceil(segments * percentage);
+
+            int step = this.getStep();
+
+            this.sliderValue = Math.round((segments * percentage) / (float) step) * step;
             this.option.setValueWithIndex(this.sliderValue);
             this.displayString = this.option.getDisplayStringValue();
             this.dragging = true;
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 883fa65f7..7fe5970c6 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
@@ -248,40 +248,41 @@ public abstract class ScreenContainerAbstract extends Screen {
                 cancelClick = true;
             }
             // Horrifying
+            // Still Horrifying...
             if (GameSettings.KEY_HOT_BAR_SLOT_1.isKeyOrMouse(key, mouseButton)) {
-                this.mc.playerController.handleInventoryMouseClick(this.inventorySlots.containerId, InventoryAction.HOTBAR_ITEM_SWAP, new int[]{slotId, 1}, this.mc.thePlayer);
+                this.mc.playerController.handleInventoryMouseClick(this.inventorySlots.containerId, InventoryAction.HOTBAR_ITEM_SWAP, hotbarSwapArgs(slotId, 1), this.mc.thePlayer);
                 cancelClick = true;
             }
             if (GameSettings.KEY_HOT_BAR_SLOT_2.isKeyOrMouse(key, mouseButton)) {
-                this.mc.playerController.handleInventoryMouseClick(this.inventorySlots.containerId, InventoryAction.HOTBAR_ITEM_SWAP, new int[]{slotId, 2}, this.mc.thePlayer);
+                this.mc.playerController.handleInventoryMouseClick(this.inventorySlots.containerId, InventoryAction.HOTBAR_ITEM_SWAP, hotbarSwapArgs(slotId, 2), this.mc.thePlayer);
                 cancelClick = true;
             }
             if (GameSettings.KEY_HOT_BAR_SLOT_3.isKeyOrMouse(key, mouseButton)) {
-                this.mc.playerController.handleInventoryMouseClick(this.inventorySlots.containerId, InventoryAction.HOTBAR_ITEM_SWAP, new int[]{slotId, 3}, this.mc.thePlayer);
+                this.mc.playerController.handleInventoryMouseClick(this.inventorySlots.containerId, InventoryAction.HOTBAR_ITEM_SWAP, hotbarSwapArgs(slotId, 3), this.mc.thePlayer);
                 cancelClick = true;
             }
             if (GameSettings.KEY_HOT_BAR_SLOT_4.isKeyOrMouse(key, mouseButton)) {
-                this.mc.playerController.handleInventoryMouseClick(this.inventorySlots.containerId, InventoryAction.HOTBAR_ITEM_SWAP, new int[]{slotId, 4}, this.mc.thePlayer);
+                this.mc.playerController.handleInventoryMouseClick(this.inventorySlots.containerId, InventoryAction.HOTBAR_ITEM_SWAP, hotbarSwapArgs(slotId, 4), this.mc.thePlayer);
                 cancelClick = true;
             }
             if (GameSettings.KEY_HOT_BAR_SLOT_5.isKeyOrMouse(key, mouseButton)) {
-                this.mc.playerController.handleInventoryMouseClick(this.inventorySlots.containerId, InventoryAction.HOTBAR_ITEM_SWAP, new int[]{slotId, 5}, this.mc.thePlayer);
+                this.mc.playerController.handleInventoryMouseClick(this.inventorySlots.containerId, InventoryAction.HOTBAR_ITEM_SWAP, hotbarSwapArgs(slotId, 5), this.mc.thePlayer);
                 cancelClick = true;
             }
             if (GameSettings.KEY_HOT_BAR_SLOT_6.isKeyOrMouse(key, mouseButton)) {
-                this.mc.playerController.handleInventoryMouseClick(this.inventorySlots.containerId, InventoryAction.HOTBAR_ITEM_SWAP, new int[]{slotId, 6}, this.mc.thePlayer);
+                this.mc.playerController.handleInventoryMouseClick(this.inventorySlots.containerId, InventoryAction.HOTBAR_ITEM_SWAP, hotbarSwapArgs(slotId, 6), this.mc.thePlayer);
                 cancelClick = true;
             }
             if (GameSettings.KEY_HOT_BAR_SLOT_7.isKeyOrMouse(key, mouseButton)) {
-                this.mc.playerController.handleInventoryMouseClick(this.inventorySlots.containerId, InventoryAction.HOTBAR_ITEM_SWAP, new int[]{slotId, 7}, this.mc.thePlayer);
+                this.mc.playerController.handleInventoryMouseClick(this.inventorySlots.containerId, InventoryAction.HOTBAR_ITEM_SWAP, hotbarSwapArgs(slotId, 7), this.mc.thePlayer);
                 cancelClick = true;
             }
             if (GameSettings.KEY_HOT_BAR_SLOT_8.isKeyOrMouse(key, mouseButton)) {
-                this.mc.playerController.handleInventoryMouseClick(this.inventorySlots.containerId, InventoryAction.HOTBAR_ITEM_SWAP, new int[]{slotId, 8}, this.mc.thePlayer);
+                this.mc.playerController.handleInventoryMouseClick(this.inventorySlots.containerId, InventoryAction.HOTBAR_ITEM_SWAP, hotbarSwapArgs(slotId, 8), this.mc.thePlayer);
                 cancelClick = true;
             }
             if (GameSettings.KEY_HOT_BAR_SLOT_9.isKeyOrMouse(key, mouseButton)) {
-                this.mc.playerController.handleInventoryMouseClick(this.inventorySlots.containerId, InventoryAction.HOTBAR_ITEM_SWAP, new int[]{slotId, 9}, this.mc.thePlayer);
+                this.mc.playerController.handleInventoryMouseClick(this.inventorySlots.containerId, InventoryAction.HOTBAR_ITEM_SWAP, hotbarSwapArgs(slotId, 9), this.mc.thePlayer);
                 cancelClick = true;
             }
 
@@ -339,6 +340,19 @@ public abstract class ScreenContainerAbstract extends Screen {
         super.mouseReleased(mx, my, buttonNum);
     }
 
+    protected int[] creativeSlotArgs(Slot slot, int amount) {
+        ItemStack stack = slot.getItemStack();
+        return new int[]{slot.index, amount, stack != null ? stack.itemID : 0, stack != null ? stack.getMetadata() : 0};
+    }
+
+    protected int[] hotbarSwapArgs(int slotId, int hotbarSlotNumber) {
+        Slot slot = this.inventorySlots.getSlot(slotId);
+        if (slot instanceof SlotCreative) {
+            return creativeSlotArgs(slot, hotbarSlotNumber);
+        }
+        return new int[]{slotId, hotbarSlotNumber};
+    }
+
     /**
      * This method controls what InventoryAction is sent to the server
      */
@@ -441,20 +455,20 @@ public abstract class ScreenContainerAbstract extends Screen {
             if (heldItem != null) {
                 if (heldItem.canStackWith(stackInSlot)) {
                     if (shiftPressed || ctrlPressed) {
-                        this.mc.playerController.handleInventoryMouseClick(this.inventorySlots.containerId, InventoryAction.CREATIVE_GRAB, new int[]{slot.index, heldItem.getMaxStackSize()}, this.mc.thePlayer);
+                        this.mc.playerController.handleInventoryMouseClick(this.inventorySlots.containerId, InventoryAction.CREATIVE_GRAB, creativeSlotArgs(slot, heldItem.getMaxStackSize()), this.mc.thePlayer);
                     } else {
-                        this.mc.playerController.handleInventoryMouseClick(this.inventorySlots.containerId, InventoryAction.CREATIVE_GRAB, new int[]{slot.index, heldItem.stackSize + 1}, this.mc.thePlayer);
+                        this.mc.playerController.handleInventoryMouseClick(this.inventorySlots.containerId, InventoryAction.CREATIVE_GRAB, creativeSlotArgs(slot, heldItem.stackSize + 1), this.mc.thePlayer);
                     }
                 } else {
-                    this.mc.playerController.handleInventoryMouseClick(this.inventorySlots.containerId, InventoryAction.CREATIVE_GRAB, new int[]{slot.index, 0}, this.mc.thePlayer);
+                    this.mc.playerController.handleInventoryMouseClick(this.inventorySlots.containerId, InventoryAction.CREATIVE_GRAB, creativeSlotArgs(slot, 0), this.mc.thePlayer);
                 }
             } else {
                 if (shiftPressed || ctrlPressed) {
                     int amount = 1;
                     if (shiftPressed && stackInSlot != null) amount = stackInSlot.getMaxStackSize();
-                    this.mc.playerController.handleInventoryMouseClick(this.inventorySlots.containerId, InventoryAction.CREATIVE_MOVE, new int[]{slot.index, amount}, this.mc.thePlayer);
+                    this.mc.playerController.handleInventoryMouseClick(this.inventorySlots.containerId, InventoryAction.CREATIVE_MOVE, creativeSlotArgs(slot, amount), this.mc.thePlayer);
                 } else {
-                    this.mc.playerController.handleInventoryMouseClick(this.inventorySlots.containerId, InventoryAction.CREATIVE_GRAB, new int[]{slot.index, 1}, this.mc.thePlayer);
+                    this.mc.playerController.handleInventoryMouseClick(this.inventorySlots.containerId, InventoryAction.CREATIVE_GRAB, creativeSlotArgs(slot, 1), this.mc.thePlayer);
                 }
             }
             return;
diff --git a/game/client/src/main/java/net/minecraft/client/gui/container/ScreenInventoryCreative.java b/game/client/src/main/java/net/minecraft/client/gui/container/ScreenInventoryCreative.java
index 4767708ca..90b1e8cdb 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/container/ScreenInventoryCreative.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/container/ScreenInventoryCreative.java
@@ -116,11 +116,7 @@ public class ScreenInventoryCreative extends ScreenInventory implements ITextCha
             GameSettings.LAST_CREATIVE_INVENTORY_PAGE.set(clamped);
             GameSettings.saveOptions();
         }
-        if (clamped == this.container.page) {
-            this.container.broadcastInventoryStatus();
-        } else {
-            this.container.setInventoryStatus(clamped, this.container.searchText);
-        }
+        this.container.setInventoryStatus(clamped, this.container.searchText);
     }
 
     private void persistCreativeInventoryPage() {
diff --git a/game/client/src/main/java/net/minecraft/client/option/GameSettings.java b/game/client/src/main/java/net/minecraft/client/option/GameSettings.java
index 8d835be10..74705fb43 100644
--- a/game/client/src/main/java/net/minecraft/client/option/GameSettings.java
+++ b/game/client/src/main/java/net/minecraft/client/option/GameSettings.java
@@ -247,6 +247,7 @@ public final class GameSettings {
         .addOnChangeCallback((mc, option) -> mc.gameWindow.setVsync(option.value)));
     public static final @NotNull OptionEnum<ChatVisibility> chatVisibility = register(new OptionEnum<>("chatVisibility", ChatVisibility.class, ChatVisibility.SHOW_ALL));
     public static final @NotNull OptionRange CUSTOM_FPS_LIMIT = register(new OptionRange("customFpsLimit", 165, 301)
+        .withStep(5)
         .withDisplayStringProvider((mc, i18n, option) -> {
             int value = option.value;
             int maxValue = 300;
diff --git a/game/client/src/main/java/net/minecraft/client/option/OptionRange.java b/game/client/src/main/java/net/minecraft/client/option/OptionRange.java
index 36a7d9096..9cbb077c9 100644
--- a/game/client/src/main/java/net/minecraft/client/option/OptionRange.java
+++ b/game/client/src/main/java/net/minecraft/client/option/OptionRange.java
@@ -5,6 +5,8 @@ import org.jetbrains.annotations.NotNull;
 public class OptionRange extends OptionToggleable<Integer> {
 	public final int highest;
 	public final int lowest;
+	public int step = 1;
+
     public OptionRange(@NotNull String name, int defaultValue, int values) {
         super(name, defaultValue, getValueArray(values));
         this.highest = this.values[this.values.length - 1];
@@ -41,6 +43,11 @@ public class OptionRange extends OptionToggleable<Integer> {
 		return array;
 	}
 
+	public OptionRange withStep(int step) {
+		this.step = step;
+		return this;
+	}
+
 	@Override
 	public boolean isSlider() {
 		return true;
diff --git a/game/client/src/main/java/net/minecraft/client/player/controller/PlayerControllerMP.java b/game/client/src/main/java/net/minecraft/client/player/controller/PlayerControllerMP.java
index 4dc8f5ed9..8dcb91476 100644
--- a/game/client/src/main/java/net/minecraft/client/player/controller/PlayerControllerMP.java
+++ b/game/client/src/main/java/net/minecraft/client/player/controller/PlayerControllerMP.java
@@ -80,21 +80,21 @@ public class PlayerControllerMP
     @Override
     public boolean useOrPlaceItemStackOnTile(final @NotNull Player player, final @NotNull World world, final @Nullable ItemStack itemStack, final @NotNull TilePosc tilePos, final @NotNull Side side, final double xPlaced, final double yPlaced) {
         this.syncCurrentPlayItem();
-        this.packetHandler.addToSendQueue(new PacketUseOrPlaceItemStack(tilePos.x(), tilePos.y(), tilePos.z(), side.direction(), player.inventory.getCurrentItem(), xPlaced, yPlaced, PacketUseOrPlaceItemStack.TYPE_USE_OR_PLACE_ON_TILE));
+        this.packetHandler.addToSendQueue(new PacketUseOrPlaceItemStack(tilePos.x(), tilePos.y(), tilePos.z(), side.direction(), ItemStack.copyItemStack(player.inventory.getCurrentItem()), xPlaced, yPlaced, PacketUseOrPlaceItemStack.TYPE_USE_OR_PLACE_ON_TILE));
         return super.useOrPlaceItemStackOnTile(player, world, itemStack, tilePos, side, xPlaced, yPlaced);
     }
 
     @Override
     public boolean useItemStackOnNothing(final @NotNull Player player, final @NotNull World world, final @NotNull ItemStack itemStack) {
         this.syncCurrentPlayItem();
-        this.packetHandler.addToSendQueue(new PacketUseOrPlaceItemStack(-1, -1, -1, Direction.NONE, player.inventory.getCurrentItem(), 0, 0, PacketUseOrPlaceItemStack.TYPE_USE_ON_NOTHING));
+        this.packetHandler.addToSendQueue(new PacketUseOrPlaceItemStack(-1, -1, -1, Direction.NONE, ItemStack.copyItemStack(player.inventory.getCurrentItem()), 0, 0, PacketUseOrPlaceItemStack.TYPE_USE_ON_NOTHING));
         return super.useItemStackOnNothing(player, world, itemStack);
     }
 
     @Override
     public boolean placeItemStackOnTile(final @NotNull Player player, final @NotNull World world, final @Nullable ItemStack itemStack, final @NotNull TilePosc tilePos, final @NotNull Side side, final double xPlaced, final double yPlaced) {
         this.syncCurrentPlayItem();
-        this.packetHandler.addToSendQueue(new PacketUseOrPlaceItemStack(tilePos.x(), tilePos.y(), tilePos.z(), side.direction(), player.inventory.getCurrentItem(), xPlaced, yPlaced, PacketUseOrPlaceItemStack.TYPE_PLACE_ONLY));
+        this.packetHandler.addToSendQueue(new PacketUseOrPlaceItemStack(tilePos.x(), tilePos.y(), tilePos.z(), side.direction(), ItemStack.copyItemStack(player.inventory.getCurrentItem()), xPlaced, yPlaced, PacketUseOrPlaceItemStack.TYPE_PLACE_ONLY));
         return super.placeItemStackOnTile(player, world, itemStack, tilePos, side, xPlaced, yPlaced);
     }
 
diff --git a/game/core/src/main/java/net/minecraft/core/Global.java b/game/core/src/main/java/net/minecraft/core/Global.java
index e52cbd558..3a4249ab4 100644
--- a/game/core/src/main/java/net/minecraft/core/Global.java
+++ b/game/core/src/main/java/net/minecraft/core/Global.java
@@ -4,7 +4,7 @@ import org.jetbrains.annotations.NotNull;
 
 public class Global
 {
-	public static final int TICKS_PER_SECOND = 21;
+	public static final int TICKS_PER_SECOND = 20;
 	public static final int DAY_LENGTH_TICKS = 24000;
 	public static final @NotNull String VERSION = Version.BUILD_CHANNEL.modifyVersionString(Version.VERSION);
 	public static final @NotNull BuildChannel BUILD_CHANNEL = Version.BUILD_CHANNEL;
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicActivator.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicActivator.java
index 5ea60b1f3..bb7863a17 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicActivator.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicActivator.java
@@ -41,7 +41,7 @@ public class BlockLogicActivator
 
     @Override
     public int tickDelay() {
-        return 4;
+        return 2;
     }
 
     @Override
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluid.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluid.java
index 5ffc6f297..90ee521e1 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluid.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluid.java
@@ -23,7 +23,7 @@ import org.joml.primitives.AABBdc;
 
 import java.util.Random;
 
-public abstract class BlockLogicFluid extends BlockLogic {
+public abstract class BlockLogicFluid extends BlockLogic implements BlockLogic.MatcherDataEquivalency {
     public final @NotNull Fluid fluid;
     protected static final Direction[] CHECK_HARDEN_DIRECTIONS = new Direction[] {Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST, Direction.UP};
 
@@ -240,10 +240,9 @@ public abstract class BlockLogicFluid extends BlockLogic {
     @Override
     protected boolean isEquivalentBlockForMatcher(@NotNull Block<?> other) {
         if (other == this.block) return true;
-        return other.getLogic() instanceof BlockLogicFluid fluid && fluid.material == this.material;
+        return other.getLogic() instanceof BlockLogicFluid fluidBlock && this.fluid.equals(fluidBlock.fluid);
     }
 
-
     public static boolean isStillEquivalentToFlowing(Block<?> block1, Block<?> block2) {
         if (block1 == null || block2 == null) return false;
         if (block1 == block2) return true;
@@ -251,7 +250,7 @@ public abstract class BlockLogicFluid extends BlockLogic {
         if (block1.getLogic() instanceof BlockLogicFluid blockLogic1 &&
                 block2.getLogic() instanceof BlockLogicFluid blockLogic2) {
 
-            return blockLogic1.fluid == blockLogic2.fluid;
+            return blockLogic1.fluid.equals(blockLogic2.fluid);
         }
 
         return false;
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluidFlowing.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluidFlowing.java
index 92852658d..b8c1f305c 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluidFlowing.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluidFlowing.java
@@ -14,7 +14,7 @@ import it.unimi.dsi.fastutil.ints.IntArrayList;
 import java.util.Arrays;
 import java.util.Random;
 
-public class BlockLogicFluidFlowing extends BlockLogicFluid implements BlockLogic.MatcherDataEquivalency {
+public class BlockLogicFluidFlowing extends BlockLogicFluid {
     int maxCount;
     public final @NotNull Block<?> blockStill;
 
@@ -52,7 +52,7 @@ public class BlockLogicFluidFlowing extends BlockLogicFluid implements BlockLogi
             if (flowDecayAbove >= 8) {
                 newFlowDecay = flowDecayAbove;
             } else if (flowDecayAbove >= 0) {
-                newFlowDecay = flowDecayAbove + 8;
+                newFlowDecay = 8;
             }
             // Become source block
             if (this.maxCount >= 2 && this.fluid.canBecomeSource(this, world, tilePos, rand)) {
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicMatcher.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicMatcher.java
index 13508dc26..f7c95559d 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicMatcher.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicMatcher.java
@@ -35,7 +35,7 @@ public class BlockLogicMatcher extends BlockLogicAxisAligned {
 
     @Override
     public int tickDelay() {
-        return 4;
+        return 2;
     }
 
     @Override
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicSaplingBase.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicSaplingBase.java
index 8ec08268a..50eabce42 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicSaplingBase.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicSaplingBase.java
@@ -15,6 +15,7 @@ import org.jetbrains.annotations.Nullable;
 
 public abstract class BlockLogicSaplingBase extends BlockLogicFlower /*implements IBonemealable*/ {
     public boolean canGrowOnSand = false;
+    public static final int GROWTH_MASK = 8;
 
     public BlockLogicSaplingBase(@NotNull Block<?> block) {
         super(block);
@@ -48,8 +49,8 @@ public abstract class BlockLogicSaplingBase extends BlockLogicFlower /*implement
         }
         if (world.getBlockLightValue(tilePos.up(queryPos)) >= 9 && rand.nextInt(growthRate) == 0) {
             int l = world.getBlockData(tilePos);
-            if ((l & 8) == 0) {
-                world.setBlockData(tilePos, l | 8);
+            if ((l & GROWTH_MASK) == 0) {
+                world.setBlockData(tilePos, l | GROWTH_MASK);
             } else {
                 growTree(world, tilePos, rand);
             }
@@ -68,4 +69,9 @@ public abstract class BlockLogicSaplingBase extends BlockLogicFlower /*implement
     }
 
     public abstract void growTree(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Random random);
+    
+    @Override
+    public int getMatcherDataEquivalencyMask() {
+        return super.getMatcherDataEquivalencyMask() & ~GROWTH_MASK;
+    }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicTimer.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicTimer.java
index ab3dbeafc..204ca8960 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicTimer.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicTimer.java
@@ -30,7 +30,7 @@ public class BlockLogicTimer extends BlockLogic implements ISupportable, BlockLo
         -0.0625D, 0.0625D, 0.1875D, 0.3125D
     };
     public static final int[] tickDelayMap = {
-        4, 8, 12, 16
+        2, 4, 6, 8
     };
 
     public static final int MASK_AXIS = 0b0000_0001;
diff --git a/game/core/src/main/java/net/minecraft/core/block/FluidAcid.java b/game/core/src/main/java/net/minecraft/core/block/FluidAcid.java
index 62745563d..278b1fb4a 100644
--- a/game/core/src/main/java/net/minecraft/core/block/FluidAcid.java
+++ b/game/core/src/main/java/net/minecraft/core/block/FluidAcid.java
@@ -14,8 +14,10 @@ import org.jetbrains.annotations.NotNull;
 import java.util.Random;
 
 public class FluidAcid implements Fluid {
-	public FluidAcid() {
-	}
+	@Override
+    public boolean equals(Object obj) {
+        return obj instanceof FluidAcid;
+    }
 
 	@Override
 	public int tickDelay() {
diff --git a/game/core/src/main/java/net/minecraft/core/block/FluidLava.java b/game/core/src/main/java/net/minecraft/core/block/FluidLava.java
index 4e08fd416..43430dbce 100644
--- a/game/core/src/main/java/net/minecraft/core/block/FluidLava.java
+++ b/game/core/src/main/java/net/minecraft/core/block/FluidLava.java
@@ -14,6 +14,12 @@ import java.util.Random;
 
 public class FluidLava
     implements Fluid {
+    
+   	@Override
+    public boolean equals(Object obj) {
+        return obj instanceof FluidLava;
+    }
+
     @Override
     public int tickDelay() {
         return 30;
diff --git a/game/core/src/main/java/net/minecraft/core/block/FluidWater.java b/game/core/src/main/java/net/minecraft/core/block/FluidWater.java
index 3336d57b1..997d673f3 100644
--- a/game/core/src/main/java/net/minecraft/core/block/FluidWater.java
+++ b/game/core/src/main/java/net/minecraft/core/block/FluidWater.java
@@ -15,6 +15,12 @@ import java.util.Random;
 
 public class FluidWater
     implements Fluid {
+
+	@Override
+    public boolean equals(Object obj) {
+        return obj instanceof FluidWater;
+    }
+
     @Override
     public int tickDelay() {
         return 5;
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 dbeb39bd4..d7dc626da 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
@@ -1267,9 +1267,10 @@ public abstract class Player
             if (this.yd < 0.0D) {
                 damage++;
             }
+            boolean wasAlive = entity.isAlive();
             entity.hurt(this, damage, DamageType.COMBAT);
             ItemStack itemstack = getCurrentEquippedItem();
-            if (itemstack != null && (entity instanceof Mob)) {
+            if (itemstack != null && (entity instanceof Mob) && wasAlive) {
                 itemstack.hitEntity((Mob) entity, this);
                 if (itemstack.stackSize <= 0) {
                     destroyCurrentEquippedItem();
@@ -1524,9 +1525,6 @@ public abstract class Player
         this.inventory.setCurrentItem(item);
     }
 
-    public void updateCreativeInventory(int page, String searchText) {
-    }
-
     public @Nullable TilePos getPlayerSpawnPoint() {
         return this.playerSpawnPoint;
     }
diff --git a/game/core/src/main/java/net/minecraft/core/net/handler/PacketHandler.java b/game/core/src/main/java/net/minecraft/core/net/handler/PacketHandler.java
index 609a47978..efa30dad7 100644
--- a/game/core/src/main/java/net/minecraft/core/net/handler/PacketHandler.java
+++ b/game/core/src/main/java/net/minecraft/core/net/handler/PacketHandler.java
@@ -260,10 +260,6 @@ public abstract class PacketHandler {
         handleInvalidPacket(packetPlayerGamemode);
     }
 
-    public void handleUpdateCreativeInventory(@NotNull PacketUpdateCreativeInventory packetUpdateCreativeInventory) {
-        handleInvalidPacket(packetUpdateCreativeInventory);
-    }
-
     public void handleWeatherStatus(@NotNull PacketWeatherStatus packetWeatherStatus) {
         handleInvalidPacket(packetWeatherStatus);
     }
diff --git a/game/core/src/main/java/net/minecraft/core/net/packet/Packet.java b/game/core/src/main/java/net/minecraft/core/net/packet/Packet.java
index c871d0372..42581b9eb 100644
--- a/game/core/src/main/java/net/minecraft/core/net/packet/Packet.java
+++ b/game/core/src/main/java/net/minecraft/core/net/packet/Packet.java
@@ -318,7 +318,6 @@ public abstract class Packet
         addMapping(104, true, false, PacketContainerSetContent.class);
         addMapping(105, true, false, PacketContainerSetData.class);
         addMapping(106, true, true, PacketContainerAck.class);
-        addMapping(107, false, true, PacketUpdateCreativeInventory.class);
         addMapping(108, true, true, PacketSetHotbarOffset.class);
         addMapping(120, true, false, PacketCommandManager.class);
         addMapping(121, false, true, PacketRequestCommandManager.class);
diff --git a/game/core/src/main/java/net/minecraft/core/net/packet/PacketContainerClick.java b/game/core/src/main/java/net/minecraft/core/net/packet/PacketContainerClick.java
index 3afb71bb0..6cab152be 100644
--- a/game/core/src/main/java/net/minecraft/core/net/packet/PacketContainerClick.java
+++ b/game/core/src/main/java/net/minecraft/core/net/packet/PacketContainerClick.java
@@ -45,7 +45,7 @@ public class PacketContainerClick extends Packet {
 		int size = dis.readByte();
 		args = new int[size];
 		for(int i=0; i < size; i++) {
-			args[i] = dis.read();
+			args[i] = dis.readInt();
 		}
 
 		stateId = dis.readInt();
@@ -76,7 +76,7 @@ public class PacketContainerClick extends Packet {
         	}else {
             	dos.write(args.length);
             	for(int i=0; i < args.length; i++) {
-            		dos.write(args[i]);
+            		dos.writeInt(args[i]);
             	}
         	}
         }else {
@@ -122,6 +122,6 @@ public class PacketContainerClick extends Packet {
 
 	@Override
 	public int getEstimatedSize() {
-		return 11 + (args != null ? args.length : 0) + (changedSlots != null ? changedSlots.size() * 7 : 0);
+		return 11 + (args != null ? args.length * 4 : 0) + (changedSlots != null ? changedSlots.size() * 7 : 0);
 	}
 }
diff --git a/game/core/src/main/java/net/minecraft/core/net/packet/PacketUpdateCreativeInventory.java b/game/core/src/main/java/net/minecraft/core/net/packet/PacketUpdateCreativeInventory.java
deleted file mode 100644
index 2b2745a7c..000000000
--- a/game/core/src/main/java/net/minecraft/core/net/packet/PacketUpdateCreativeInventory.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package net.minecraft.core.net.packet;
-
-import net.minecraft.core.net.handler.PacketHandler;
-
-import java.io.*;
-
-public class PacketUpdateCreativeInventory extends Packet
-{
-
-    public PacketUpdateCreativeInventory()
-    {
-    }
-
-    public PacketUpdateCreativeInventory(int windowId, int page, String searchText)
-    {
-        this.windowId = windowId;
-        this.page = page;
-        this.searchText = searchText;
-    }
-
-    @Override
-	public void handlePacket(PacketHandler packetHandler)
-    {
-        packetHandler.handleUpdateCreativeInventory(this);
-    }
-
-    @Override
-	public void read(DataInputStream dis)
-        throws IOException
-    {
-        windowId = dis.readByte();
-        page = dis.readInt();
-        searchText = dis.readUTF();
-    }
-
-    @Override
-	public void write(DataOutputStream dos)
-        throws IOException
-    {
-        dos.writeByte(windowId);
-        dos.writeInt(page);
-        dos.writeUTF(searchText);
-    }
-
-    @Override
-	public int getEstimatedSize()
-    {
-        return 4 + 4 + searchText.length() * 2;
-    }
-
-    public int windowId;
-    public int page;
-    public String searchText;
-}
diff --git a/game/core/src/main/java/net/minecraft/core/player/inventory/menu/MenuAbstract.java b/game/core/src/main/java/net/minecraft/core/player/inventory/menu/MenuAbstract.java
index 7f02e44c3..57a3f5d05 100644
--- a/game/core/src/main/java/net/minecraft/core/player/inventory/menu/MenuAbstract.java
+++ b/game/core/src/main/java/net/minecraft/core/player/inventory/menu/MenuAbstract.java
@@ -16,6 +16,7 @@ import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.player.inventory.container.Container;
 import net.minecraft.core.player.inventory.container.ContainerInventory;
 import net.minecraft.core.player.inventory.slot.Slot;
+import net.minecraft.core.player.inventory.slot.SlotCreative;
 import net.minecraft.core.player.inventory.slot.SlotResult;
 import net.minecraft.core.util.helper.MathHelper;
 import org.jetbrains.annotations.NotNull;
@@ -322,16 +323,23 @@ public abstract class MenuAbstract
 					}
 				}
 			}else {
+				ItemStack sourceStack = stackInSlot;
+				if(slot instanceof SlotCreative) {
+					sourceStack = args.length >= 4 ? MenuInventoryCreative.getValidCreativeItem(args[2], args[3]) : null;
+					if(sourceStack == null && args.length >= 4 && args[2] != 0) {
+						LOGGER.warn("Player {} requested item {}:{} which the creative menu does not offer!", player.username, args[2], args[3]);
+					}
+				}
 				int amount = args.length > 1 ? args[1] : 0;
-				if(stackInSlot != null) {
-					amount = MathHelper.clamp(amount, 0, stackInSlot.getMaxStackSize());
+				if(sourceStack != null) {
+					amount = MathHelper.clamp(amount, 0, sourceStack.getMaxStackSize());
 				}else {
 					amount = 0;
 				}
 				if(action == InventoryAction.CREATIVE_GRAB) {
 					ItemStack stack;
 					if(amount > 0) {
-						stack = slot.getItemStack().copy();
+						stack = sourceStack.copy();
 						stack.stackSize = amount;
 					}else {
 						stack = null;
@@ -340,7 +348,7 @@ public abstract class MenuAbstract
 				}
 				if(action == InventoryAction.CREATIVE_MOVE) {
 					if(amount > 0) {
-						ItemStack stack = slot.getItemStack().copy();
+						ItemStack stack = sourceStack.copy();
 						stack.stackSize = amount;
 						player.inventory.insertItem(stack, false);
 					}
@@ -490,7 +498,13 @@ public abstract class MenuAbstract
 			return;
 		}
 
-		ItemStack slotStack = slot.getItemStack();
+		ItemStack slotStack;
+		if(slot instanceof SlotCreative) {
+			slotStack = args.length >= 4 ? MenuInventoryCreative.getValidCreativeItem(args[2], args[3]) : null;
+		} else {
+            assert slot != null;
+            slotStack = slot.getItemStack();
+		}
 		ItemStack hotbarStack = hotbarSlot.getItemStack();
 
 		if(slotStack != null) {
diff --git a/game/core/src/main/java/net/minecraft/core/player/inventory/menu/MenuInventoryCreative.java b/game/core/src/main/java/net/minecraft/core/player/inventory/menu/MenuInventoryCreative.java
index d91a68214..a202503be 100644
--- a/game/core/src/main/java/net/minecraft/core/player/inventory/menu/MenuInventoryCreative.java
+++ b/game/core/src/main/java/net/minecraft/core/player/inventory/menu/MenuInventoryCreative.java
@@ -151,13 +151,18 @@ public class MenuInventoryCreative extends MenuInventory
 				((SlotCreative)this.slots.get(creativeSlotsStart + i)).item = searchedItems.get(idx);
 			}
 		}
-
-		inventory.player.updateCreativeInventory(page, searchText);
 	}
 
-	public void broadcastInventoryStatus()
+	public static ItemStack getValidCreativeItem(int itemId, int metadata)
 	{
-		inventory.player.updateCreativeInventory(page, searchText);
+		for (ItemStack stack : creativeContents)
+		{
+			if (stack != null && stack.itemID == itemId && stack.getMetadata() == metadata)
+			{
+				return stack.copy();
+			}
+		}
+		return null;
 	}
 
 	public String getSearchText(){
diff --git a/game/core/src/main/java/net/minecraft/core/world/PortalHandler.java b/game/core/src/main/java/net/minecraft/core/world/PortalHandler.java
index 06ea8e0b1..2a51cfea4 100644
--- a/game/core/src/main/java/net/minecraft/core/world/PortalHandler.java
+++ b/game/core/src/main/java/net/minecraft/core/world/PortalHandler.java
@@ -5,6 +5,10 @@ import net.minecraft.core.block.BlockLogicPortal;
 import net.minecraft.core.entity.Entity;
 import net.minecraft.core.util.helper.DyeColor;
 import net.minecraft.core.util.helper.MathHelper;
+import net.minecraft.core.world.chunk.Chunk;
+import net.minecraft.core.world.chunk.provider.ChunkProvider;
+import net.minecraft.core.world.pos.ChunkPos;
+import net.minecraft.core.world.pos.ChunkTilePos;
 import net.minecraft.core.world.pos.TilePos;
 import net.minecraft.core.world.type.WorldType;
 import org.jetbrains.annotations.Nullable;
@@ -61,38 +65,80 @@ public class PortalHandler
         	targetPortal = newDim.portalBlock;
         }
 
-        // Attempt to find closest portal along the xz plane
-        for(int dx = entityBlockX - searchRadius; dx <= entityBlockX + searchRadius; dx++)
+        if(targetPortal == null)
         {
-            double xEntityDistance = ((double)dx + 0.5D) - entity.x;
-            for(int dz = entityBlockZ - searchRadius; dz <= entityBlockZ + searchRadius; dz++)
+            return false;
+        }
+
+        // Attempt to find closest portal along the xz plane.
+
+        // Portals can only exist in chunks that were generated before, so the search now only visits
+        // chunks that are already loaded or saved on disk.
+
+        int targetPortalId = targetPortal.id();
+        int maxPortalY = newWorldType.getMaxPortalY();
+        int minPortalY = newWorldType.getMinPortalY();
+        ChunkProvider chunkProvider = world.getChunkProvider();
+        ChunkPos chunkQueryPos = new ChunkPos();
+        ChunkTilePos chunkTileQueryPos = new ChunkTilePos();
+        TilePos colorQueryPos = new TilePos();
+        int minBlockX = entityBlockX - searchRadius;
+        int maxBlockX = entityBlockX + searchRadius;
+        int minBlockZ = entityBlockZ - searchRadius;
+        int maxBlockZ = entityBlockZ + searchRadius;
+        for(int chunkX = minBlockX >> 4; chunkX <= maxBlockX >> 4; chunkX++)
+        {
+            for(int chunkZ = minBlockZ >> 4; chunkZ <= maxBlockZ >> 4; chunkZ++)
             {
-                double zEntityDistance = ((double)dz + 0.5D) - entity.z;
-                for(int dy = newWorldType.getMaxPortalY() - 1; dy >= newWorldType.getMinPortalY(); dy--)
+                chunkQueryPos.set(chunkX, chunkZ);
+                if(!chunkProvider.chunkExists(chunkQueryPos))
                 {
-                    if(world.getBlock(dx, dy, dz) != targetPortal)
+                    continue;
+                }
+                Chunk chunk = chunkProvider.provideChunk(chunkQueryPos, true);
+                if(!chunk.isAtLocation(chunkQueryPos))
+                {
+                    // The provider returned a placeholder chunk instead of the real one
+                    continue;
+                }
+                int startX = Math.max(minBlockX, chunkX * Chunk.CHUNK_SIZE_X);
+                int endX = Math.min(maxBlockX, chunkX * Chunk.CHUNK_SIZE_X + Chunk.CHUNK_SIZE_X - 1);
+                int startZ = Math.max(minBlockZ, chunkZ * Chunk.CHUNK_SIZE_Z);
+                int endZ = Math.min(maxBlockZ, chunkZ * Chunk.CHUNK_SIZE_Z + Chunk.CHUNK_SIZE_Z - 1);
+                for(int dx = startX; dx <= endX; dx++)
+                {
+                    double xEntityDistance = ((double)dx + 0.5D) - entity.x;
+                    for(int dz = startZ; dz <= endZ; dz++)
                     {
-                        continue;
-                    }
+                        double zEntityDistance = ((double)dz + 0.5D) - entity.z;
+                        for(int dy = maxPortalY - 1; dy >= minPortalY; dy--)
+                        {
+                            if(chunk.getBlockId(chunkTileQueryPos.set(dx, dy, dz)) != targetPortalId)
+                            {
+                                continue;
+                            }
 
-                    if (portalColor != null && targetPortal != null && targetPortal.getLogic().getColor(world, dx, dy, dz) != portalColor) {
-                        continue;
-                    }
+                            if (portalColor != null && targetPortal.getLogic().getColor(world, colorQueryPos.set(dx, dy, dz)) != portalColor) {
+                                continue;
+                            }
 
-                    // Find the bottom portal block of the portal
-                    for(; world.getBlock(dx, dy - 1, dz) == targetPortal; dy--);
+                            // Find the bottom portal block of the portal
+                            for(; dy > 0 && chunk.getBlockId(chunkTileQueryPos.set(dx, dy - 1, dz)) == targetPortalId; dy--);
+
+//                            double yEntityDistance = ((double)dy + 0.5D) - entityPosYScaled;
+                            double entityDistanceSquared = xEntityDistance * xEntityDistance /*+ yEntityDistance * yEntityDistance*/ + zEntityDistance * zEntityDistance;
+                            if(lowestEntityDistanceSquaredXZ < 0.0D || entityDistanceSquared < lowestEntityDistanceSquaredXZ)
+                            {
+                                lowestEntityDistanceSquaredXZ = entityDistanceSquared;
+                                closestPortalX = dx;
+                                closestPortalY = dy;
+                                closestPortalZ = dz;
+                            }
+                        }
 
-//                    double yEntityDistance = ((double)dy + 0.5D) - entityPosYScaled;
-                    double entityDistanceSquared = xEntityDistance * xEntityDistance /*+ yEntityDistance * yEntityDistance*/ + zEntityDistance * zEntityDistance;
-                    if(lowestEntityDistanceSquaredXZ < 0.0D || entityDistanceSquared < lowestEntityDistanceSquaredXZ)
-                    {
-                        lowestEntityDistanceSquaredXZ = entityDistanceSquared;
-                        closestPortalX = dx;
-                        closestPortalY = dy;
-                        closestPortalZ = dz;
                     }
-                }
 
+                }
             }
 
         }
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 84197e86c..8c866a257 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
@@ -2455,7 +2455,11 @@ public abstract class World implements MutableWorldSource {
     }
 
     public void unloadEntities(final List<Entity> list) {
-        this.entitiesToRemove.addAll(list);
+        for (final Entity entity : list) {
+            if (!(entity instanceof Player)) {
+                this.entitiesToRemove.add(entity);
+            }
+        }
     }
 
     public void dropOldChunks() {
diff --git a/game/core/src/main/java/net/minecraft/core/world/chunk/Chunk.java b/game/core/src/main/java/net/minecraft/core/world/chunk/Chunk.java
index 4d678cdf0..84f3b9af6 100644
--- a/game/core/src/main/java/net/minecraft/core/world/chunk/Chunk.java
+++ b/game/core/src/main/java/net/minecraft/core/world/chunk/Chunk.java
@@ -167,6 +167,9 @@ public class Chunk {
     }
 
     public void checkForLightGaps() {
+        if (this.world.getWorldType().hasCeiling()) {
+            return;
+        }
         for (int x = 0; x < CHUNK_SIZE_X; x++) {
             for (int z = 0; z < CHUNK_SIZE_Z; z++) {
                 lightGaps(x, z);
diff --git a/game/core/src/main/java/net/minecraft/core/world/chunk/ChunkLoader.java b/game/core/src/main/java/net/minecraft/core/world/chunk/ChunkLoader.java
index 96b6b1048..187b281b6 100644
--- a/game/core/src/main/java/net/minecraft/core/world/chunk/ChunkLoader.java
+++ b/game/core/src/main/java/net/minecraft/core/world/chunk/ChunkLoader.java
@@ -26,6 +26,15 @@ public interface ChunkLoader
     void saveChunk(World world, Chunk chunk)
         throws IOException;
 
+    /**
+     * Checks whether a chunk has previously been saved to disk, without loading it.
+     * @param world The world the chunk belongs to.
+     * @param x The X coordinate of the chunk.
+     * @param z The Z coordinate of the chunk.
+     * @return true if the chunk can be loaded from disk.
+     */
+    boolean chunkExists(World world, int x, int z);
+
 
     /**
      * @return true if busy saving
diff --git a/game/core/src/main/java/net/minecraft/core/world/chunk/ChunkLoaderLegacy.java b/game/core/src/main/java/net/minecraft/core/world/chunk/ChunkLoaderLegacy.java
index c731aeac4..0b5e3be86 100644
--- a/game/core/src/main/java/net/minecraft/core/world/chunk/ChunkLoaderLegacy.java
+++ b/game/core/src/main/java/net/minecraft/core/world/chunk/ChunkLoaderLegacy.java
@@ -165,6 +165,15 @@ public class ChunkLoaderLegacy
         }
     }
 
+    @Override
+    public boolean chunkExists(World world, int x, int z)
+    {
+        String fileName = "c." + Integer.toString(x, 36) + "." + Integer.toString(z, 36) + ".dat";
+        String xDirName = Integer.toString(x & 0x3f, 36);
+        String zDirName = Integer.toString(z & 0x3f, 36);
+        return new File(new File(new File(worldDir, xDirName), zDirName), fileName).exists();
+    }
+
     @Override
     public boolean isSaving() {
         return false;
diff --git a/game/core/src/main/java/net/minecraft/core/world/chunk/ChunkLoaderRegion.java b/game/core/src/main/java/net/minecraft/core/world/chunk/ChunkLoaderRegion.java
index 4d9a5f59f..3e9bd7609 100644
--- a/game/core/src/main/java/net/minecraft/core/world/chunk/ChunkLoaderRegion.java
+++ b/game/core/src/main/java/net/minecraft/core/world/chunk/ChunkLoaderRegion.java
@@ -87,6 +87,12 @@ public class ChunkLoaderRegion
         }
     }
 
+    @Override
+    public boolean chunkExists(World world, int x, int z)
+    {
+        return RegionFileCache.chunkExists(worldDir, x, z);
+    }
+
     @Override
     public boolean isSaving() {
         return false;
diff --git a/game/core/src/main/java/net/minecraft/core/world/chunk/ChunkLoaderRegionAsync.java b/game/core/src/main/java/net/minecraft/core/world/chunk/ChunkLoaderRegionAsync.java
index 995322c38..df0140e39 100644
--- a/game/core/src/main/java/net/minecraft/core/world/chunk/ChunkLoaderRegionAsync.java
+++ b/game/core/src/main/java/net/minecraft/core/world/chunk/ChunkLoaderRegionAsync.java
@@ -64,6 +64,32 @@ public class ChunkLoaderRegionAsync
         }
     }
 
+    @Override
+    public boolean chunkExists(World world, int x, int z)
+    {
+        //chunks waiting in the save queue exist
+        lockChunksToSave.lock();
+        try
+        {
+            java.util.Iterator<Chunk> chunkIterator = chunksToSave.iterator();
+            java.util.Iterator<World> worldIterator = worldsToSave.iterator();
+            while (chunkIterator.hasNext() && worldIterator.hasNext())
+            {
+                Chunk chunk = chunkIterator.next();
+                World chunkWorld = worldIterator.next();
+                if (chunkWorld == world && chunk.pos.x == x && chunk.pos.z == z)
+                {
+                    return true;
+                }
+            }
+        }
+        finally
+        {
+            lockChunksToSave.unlock();
+        }
+        return chunkLoaderRegion.chunkExists(world, x, z);
+    }
+
     @Override
     public boolean isSaving() {
         return !chunksToSave.isEmpty();
diff --git a/game/core/src/main/java/net/minecraft/core/world/chunk/provider/ChunkProvider.java b/game/core/src/main/java/net/minecraft/core/world/chunk/provider/ChunkProvider.java
index 7e370efbd..d4dd197fc 100644
--- a/game/core/src/main/java/net/minecraft/core/world/chunk/provider/ChunkProvider.java
+++ b/game/core/src/main/java/net/minecraft/core/world/chunk/provider/ChunkProvider.java
@@ -15,6 +15,10 @@ public interface ChunkProvider {
     }
     boolean isChunkLoaded(@NotNull ChunkPosc chunkPos);
 
+    default boolean chunkExists(@NotNull ChunkPosc chunkPos) {
+        return isChunkLoaded(chunkPos);
+    }
+
     @Deprecated
     default @NotNull Chunk provideChunk(int chunkX, int chunkZ) {
         return provideChunk(new ChunkPos(chunkX, chunkZ), true);
diff --git a/game/core/src/main/java/net/minecraft/core/world/chunk/provider/ChunkProviderThreaded.java b/game/core/src/main/java/net/minecraft/core/world/chunk/provider/ChunkProviderThreaded.java
index 8ffa47a9e..6e2c0b182 100644
--- a/game/core/src/main/java/net/minecraft/core/world/chunk/provider/ChunkProviderThreaded.java
+++ b/game/core/src/main/java/net/minecraft/core/world/chunk/provider/ChunkProviderThreaded.java
@@ -73,6 +73,19 @@ public class ChunkProviderThreaded implements ChunkProvider {
         return isLoaded;
     }
 
+    @Override
+    public boolean chunkExists(@NotNull ChunkPosc chunkPos) {
+        if (isChunkLoaded(chunkPos)) {
+            return true;
+        }
+        synchronized (this.preparedUndecoratedChunks) {
+            if (this.preparedUndecoratedChunks.containsKey(chunkPos)) {
+                return true;
+            }
+        }
+        return this.chunkLoader.chunkExists(this.world, chunkPos.x(), chunkPos.z());
+    }
+
     @Override
     public @NotNull Chunk provideChunk(@NotNull ChunkPosc chunkPos, boolean priority) {
         if (chunkPos.equals(this.lastQueriedChunkPos)) return this.lastQueriedChunk;
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/ChunkDecoratorNether.java b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/ChunkDecoratorNether.java
index b1d2967af..8d7f9e51c 100644
--- a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/ChunkDecoratorNether.java
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/ChunkDecoratorNether.java
@@ -20,6 +20,7 @@ import net.minecraft.core.world.generate.feature.WorldFeatureNetherScatterEmberA
 import net.minecraft.core.world.noise.FractalNoise2D;
 import net.minecraft.core.world.noise.ImprovedPerlinNoise;
 import net.minecraft.core.world.noise.WorleyNoise;
+import net.minecraft.core.world.pos.ChunkTilePos;
 import net.minecraft.core.world.pos.TilePos;
 import org.jetbrains.annotations.NotNull;
 
@@ -339,28 +340,42 @@ public class ChunkDecoratorNether
 
     @Override
     public void postDecorate(@NotNull World world, @NotNull Chunk chunk) {
-        TilePos queryPose = new TilePos();
+        TilePos placePos = new TilePos();
+        ChunkTilePos queryPos = new ChunkTilePos();
         int worldX = chunk.pos.x() * 16;
         int worldZ = chunk.pos.z() * 16;
         int startY = world.getWorldType().getMaxY(world);
+        int stopY = world.getWorldType().getOceanY();
 
-        for (int dx = worldX; dx < worldX + 16; ++dx) {
-            for (int dz = worldZ; dz < worldZ + 16; ++dz) {
-                for (int dy = startY; dy > world.getWorldType().getOceanY(); --dy) {
-                    queryPose.set(dx, dy, dz);
+        for (int x = 0; x < 16; ++x) {
+            for (int z = 0; z < 16; ++z) {
+                int blockId = chunk.getBlockId(queryPos.set(x, startY, z));
+                Biome cachedBiome = null;
+                boolean cachedBiomeHasAsh = false;
+                int cachedBiomeCellY = Integer.MIN_VALUE;
 
-                    if (world.isAirBlock(queryPose)) {
-                        Block<?> blockBelow = world.getBlock(dx, dy - 1, dz);
+                for (int y = startY; y > stopY; --y) {
+                    // Each block is read once and reused as the blockBelow of the row above it
+                    int blockBelowId = chunk.getBlockId(queryPos.set(x, y - 1, z));
+
+                    if (blockId == 0 && blockBelowId != 0) {
+                        Block<?> blockBelow = Blocks.getBlock(blockBelowId);
 
                         if (blockBelow != Blocks.OBSIDIAN && blockBelow != Blocks.SOULSAND && blockBelow.getMaterial().blocksMotion()
                                 && !isRubyglassAshSupportForbidden(blockBelow)) {
 
-                            Biome localBiome = world.getBlockBiome(queryPose);
-                            if (localBiome.hasTag(BiomeTags.HAS_SURFACE_ASH)) {
-                                world.setBlockType(queryPose, Blocks.LAYER_ASH);
+                            if ((y >> 3) != cachedBiomeCellY) {
+                                cachedBiomeCellY = y >> 3;
+                                cachedBiome = chunk.getBlockBiome(queryPos.set(x, y, z));
+                                cachedBiomeHasAsh = cachedBiome != null && cachedBiome.hasTag(BiomeTags.HAS_SURFACE_ASH);
+                            }
+                            if (cachedBiomeHasAsh) {
+                                world.setBlockType(placePos.set(worldX + x, y, worldZ + z), Blocks.LAYER_ASH);
                             }
                         }
                     }
+
+                    blockId = blockBelowId;
                 }
             }
         }
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/SurfaceGeneratorNether.java b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/SurfaceGeneratorNether.java
index b75207a26..95f8afc70 100644
--- a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/SurfaceGeneratorNether.java
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/SurfaceGeneratorNether.java
@@ -8,6 +8,7 @@ import net.minecraft.core.world.biome.Biomes;
 import net.minecraft.core.world.chunk.Chunk;
 import net.minecraft.core.world.generate.chunk.ChunkGeneratorResult;
 import net.minecraft.core.world.generate.chunk.perlin.SurfaceGenerator;
+import net.minecraft.core.world.pos.ChunkTilePos;
 import net.minecraft.core.world.noise.FractalNoise3D;
 import net.minecraft.core.world.noise.ImprovedPerlinNoise;
 import org.jetbrains.annotations.NotNull;
@@ -78,6 +79,8 @@ public class SurfaceGeneratorNether implements SurfaceGenerator {
             stoneLayerNoiseGloomstone = this.beachNoise.getRegion(null, chunkX * Chunk.CHUNK_SIZE_X, chunkZ * Chunk.CHUNK_SIZE_Z, 0.0D, Chunk.CHUNK_SIZE_X, Chunk.CHUNK_SIZE_Z, 1, beachScale * 4D, beachScale * 4D, beachScale * 4D);
         }*/
 
+        ChunkTilePos biomeQueryPos = new ChunkTilePos();
+
         for (int z = 0; z < 16; z++) {
             for (int x = 0; x < 16; x++) {
                 int noiseIndex = z + x * 16;
@@ -98,10 +101,17 @@ public class SurfaceGeneratorNether implements SurfaceGenerator {
                 boolean cachedBiomeGeneratesSulfur = false;
                 int cachedBiomeStoneBlockId = worldFillBlock;
 
+                Biome biome = null;
+                int lastBiomeCellY = Integer.MIN_VALUE;
+
                 for (int y = maxY; y >= minY; y--) {
-                    Biome biome = chunk.getBlockBiome(x, y, z);
-                    if (biome == null) {
-                        biome = this.world.getBiomeProvider().getBiome(worldX, y >> 3, worldZ);
+                    int biomeCellY = y >> 3;
+                    if (biomeCellY != lastBiomeCellY) {
+                        lastBiomeCellY = biomeCellY;
+                        biome = chunk.getBlockBiome(biomeQueryPos.set(x, y, z));
+                        if (biome == null) {
+                            biome = this.world.getBiomeProvider().getBiome(worldX, biomeCellY, worldZ);
+                        }
                     }
 
                     if (biome != lastBiome) {
diff --git a/game/core/src/main/java/net/minecraft/core/world/save/mcregion/RegionFileCache.java b/game/core/src/main/java/net/minecraft/core/world/save/mcregion/RegionFileCache.java
index 8c4d9c351..4001ae3df 100644
--- a/game/core/src/main/java/net/minecraft/core/world/save/mcregion/RegionFileCache.java
+++ b/game/core/src/main/java/net/minecraft/core/world/save/mcregion/RegionFileCache.java
@@ -67,6 +67,20 @@ public class RegionFileCache
         return regionfile.getSizeDeltaBytes();
     }
 
+    public static synchronized boolean chunkExists(File worldDir, int x, int z)
+    {
+        File regionDir = new File(worldDir, "region");
+        File regionFileName = new File(regionDir, "r." + (x >> 5) + "." + (z >> 5) + ".mcr");
+        Reference<RegionFile> reference = cache.get(regionFileName);
+        RegionFile cachedRegion = reference != null ? reference.get() : null;
+        if(cachedRegion == null && !regionFileName.exists())
+        {
+            // Avoids creating an empty region file just to probe for a chunk
+            return false;
+        }
+        return loadRegionFileFromCoords(worldDir, x, z).chunkExists(x & 0x1f, z & 0x1f);
+    }
+
     public static DataInputStream getChunkInputStream(File worldDir, int x, int z)
     {
         RegionFile regionFile = loadRegionFileFromCoords(worldDir, x, z);
diff --git a/game/server/src/main/java/net/minecraft/server/entity/player/PlayerServer.java b/game/server/src/main/java/net/minecraft/server/entity/player/PlayerServer.java
index 120899d98..3d3aab87e 100644
--- a/game/server/src/main/java/net/minecraft/server/entity/player/PlayerServer.java
+++ b/game/server/src/main/java/net/minecraft/server/entity/player/PlayerServer.java
@@ -306,29 +306,55 @@ public class PlayerServer extends Player implements ContainerListener {
     }
 
     public void tickSendChunks() {
-        if (!loadedChunks.isEmpty())
+
+        if (loadedChunks.isEmpty()) return;
+
+        WorldServer worldserver = mcServer.getDimensionWorld(dimension);
+
+        final long timeBudgetNanos = 8_000_000L;
+        long startTime = System.nanoTime();
+        int chunksSent = 0;
+        while (!loadedChunks.isEmpty() && playerNetServerHandler.getNumChunkDataPackets() < 8)
         {
             ChunkCoordinate chunkCoord = (ChunkCoordinate) loadedChunks.get(0);
-            if (chunkCoord != null)
+            if (chunkCoord == null)
+            {
+                break;
+            }
+            if (chunksSent > 0 && (System.nanoTime() - startTime > timeBudgetNanos || !isChunkNeighborhoodLoaded(worldserver, chunkCoord)))
             {
-                boolean canSendMoreDataPackets = playerNetServerHandler.getNumChunkDataPackets() < 8;
-                if (canSendMoreDataPackets)
+                break;
+            }
+            loadedChunks.remove(0);
+            ChunkPos sentChunkPos = new ChunkPos(chunkCoord.x, chunkCoord.z);
+            worldserver.getChunkProvider().prepareChunk(sentChunkPos, true);
+            worldserver.getChunkProvider().ensureDecorated(sentChunkPos);
+            worldserver.getChunk(sentChunkPos, true).sentToClients = true;
+            playerNetServerHandler.sendPacket(new PacketBlockRegionUpdate(chunkCoord.x * Chunk.CHUNK_SIZE_X, 0, chunkCoord.z * Chunk.CHUNK_SIZE_Z, Chunk.CHUNK_SIZE_X, Chunk.CHUNK_SECTIONS * ChunkSection.SECTION_SIZE_Y, Chunk.CHUNK_SIZE_Z, worldserver));
+            List<TileEntity> list = worldserver.getBlockEntitiesWithinBounds(chunkCoord.x * Chunk.CHUNK_SIZE_X, 0, chunkCoord.z * Chunk.CHUNK_SIZE_Z, chunkCoord.x * Chunk.CHUNK_SIZE_X + Chunk.CHUNK_SIZE_X, Chunk.CHUNK_SECTIONS * ChunkSection.SECTION_SIZE_Y, chunkCoord.z * Chunk.CHUNK_SIZE_Z + Chunk.CHUNK_SIZE_Z);
+            for (int j = 0; j < list.size(); j++)
+            {
+                getTileEntityInfo(list.get(j));
+            }
+            chunksSent++;
+        }
+    }
+
+    private boolean isChunkNeighborhoodLoaded(WorldServer worldserver, ChunkCoordinate chunkCoord)
+    {
+        ChunkPos queryPos = new ChunkPos();
+        for (int dx = -1; dx <= 1; dx++)
+        {
+            for (int dz = -1; dz <= 1; dz++)
+            {
+                queryPos.set(chunkCoord.x + dx, chunkCoord.z + dz);
+                if (!worldserver.getChunkProvider().isChunkLoaded(queryPos))
                 {
-                    WorldServer worldserver = mcServer.getDimensionWorld(dimension);
-                    loadedChunks.remove(chunkCoord);
-                    ChunkPos sentChunkPos = new ChunkPos(chunkCoord.x, chunkCoord.z);
-                    worldserver.getChunkProvider().prepareChunk(sentChunkPos, true);
-                    worldserver.getChunkProvider().ensureDecorated(sentChunkPos);
-                    worldserver.getChunk(sentChunkPos, true).sentToClients = true;
-                    playerNetServerHandler.sendPacket(new PacketBlockRegionUpdate(chunkCoord.x * Chunk.CHUNK_SIZE_X, 0, chunkCoord.z * Chunk.CHUNK_SIZE_Z, Chunk.CHUNK_SIZE_X, Chunk.CHUNK_SECTIONS * ChunkSection.SECTION_SIZE_Y, Chunk.CHUNK_SIZE_Z, worldserver));
-                    List<TileEntity> list = worldserver.getBlockEntitiesWithinBounds(chunkCoord.x * Chunk.CHUNK_SIZE_X, 0, chunkCoord.z * Chunk.CHUNK_SIZE_Z, chunkCoord.x * Chunk.CHUNK_SIZE_X + Chunk.CHUNK_SIZE_X, Chunk.CHUNK_SECTIONS * ChunkSection.SECTION_SIZE_Y, chunkCoord.z * Chunk.CHUNK_SIZE_Z + Chunk.CHUNK_SIZE_Z);
-                    for (int j = 0; j < list.size(); j++)
-                    {
-                        getTileEntityInfo(list.get(j));
-                    }
+                    return false;
                 }
             }
         }
+        return true;
     }
 
     private void getTileEntityInfo(TileEntity tileentity)
diff --git a/game/server/src/main/java/net/minecraft/server/net/handler/PacketHandlerServer.java b/game/server/src/main/java/net/minecraft/server/net/handler/PacketHandlerServer.java
index 98061e04c..f9368fbc7 100644
--- a/game/server/src/main/java/net/minecraft/server/net/handler/PacketHandlerServer.java
+++ b/game/server/src/main/java/net/minecraft/server/net/handler/PacketHandlerServer.java
@@ -27,7 +27,6 @@ import net.minecraft.core.net.handler.PacketHandler;
 import net.minecraft.core.net.packet.*;
 import net.minecraft.core.player.inventory.menu.MenuAbstract;
 import net.minecraft.core.player.inventory.menu.MenuFlag;
-import net.minecraft.core.player.inventory.menu.MenuInventoryCreative;
 import net.minecraft.core.player.inventory.slot.Slot;
 import net.minecraft.core.util.helper.AES;
 import net.minecraft.core.util.helper.NetCharacters;
@@ -743,13 +742,6 @@ public class PacketHandlerServer extends PacketHandler
         worldserver.markBlockNeedsUpdate(pos);
     }
 
-    @Override
-    public void handleUpdateCreativeInventory(@NotNull PacketUpdateCreativeInventory packet) {
-        if (this.playerEntity.containerMenu.containerId == packet.windowId && this.playerEntity.containerMenu instanceof MenuInventoryCreative) {
-            ((MenuInventoryCreative) this.playerEntity.containerMenu).setInventoryStatus(packet.page, packet.searchText);
-        }
-    }
-
     @Override
     public void handleSetHotbarOffset(@NotNull PacketSetHotbarOffset packet) {
 
diff --git a/game/server/src/main/java/net/minecraft/server/world/chunk/provider/ChunkProviderServer.java b/game/server/src/main/java/net/minecraft/server/world/chunk/provider/ChunkProviderServer.java
index 4e505a68a..3f8a4a0e1 100644
--- a/game/server/src/main/java/net/minecraft/server/world/chunk/provider/ChunkProviderServer.java
+++ b/game/server/src/main/java/net/minecraft/server/world/chunk/provider/ChunkProviderServer.java
@@ -48,6 +48,14 @@ public class ChunkProviderServer
         return this.chunkMap.containsKey(chunkPos);
     }
 
+    @Override
+    public boolean chunkExists(@NotNull ChunkPosc chunkPos) {
+        if (this.chunkMap.containsKey(chunkPos)) {
+            return true;
+        }
+        return this.chunkLoader != null && this.chunkLoader.chunkExists(this.world, chunkPos.x(), chunkPos.z());
+    }
+
     public void dropChunk(@NotNull ChunkPos chunkPos) {
         if (!this.chunkMap.containsKey(chunkPos)) {return;}
         TilePos spawnCoords = this.world.getSpawnPoint();
diff --git a/util/datagen/src/main/java/net/minecraft/datagen/WorkbenchGenerator.java b/util/datagen/src/main/java/net/minecraft/datagen/WorkbenchGenerator.java
index cf27cb6f6..ec3d8fba8 100644
--- a/util/datagen/src/main/java/net/minecraft/datagen/WorkbenchGenerator.java
+++ b/util/datagen/src/main/java/net/minecraft/datagen/WorkbenchGenerator.java
@@ -1478,6 +1478,7 @@ class WorkbenchGenerator {
         Registries.RECIPES.WORKBENCH.register("scrap_chainmail_leggings",     new RecipeEntryScrap(Items.ARMOR_LEGGINGS_CHAINMAIL,    Items.CHAINLINK, 4));
         Registries.RECIPES.WORKBENCH.register("scrap_chainmail_chestplate",   new RecipeEntryScrap(Items.ARMOR_CHESTPLATE_CHAINMAIL,  Items.CHAINLINK, 4));
         Registries.RECIPES.WORKBENCH.register("scrap_chainmail_helmet",       new RecipeEntryScrap(Items.ARMOR_HELMET_CHAINMAIL,      Items.CHAINLINK, 4));
+        Registries.RECIPES.WORKBENCH.register("scrap_chainmail_wolf",         new RecipeEntryScrap(Items.ARMOR_WOLF_CHAINMAIL,        Items.CHAINLINK, 4));
     }
 
     private static void generateRepairableRecipes() {
