diff --git a/game/client/src/main/java/net/minecraft/client/entity/player/PlayerLocal.java b/game/client/src/main/java/net/minecraft/client/entity/player/PlayerLocal.java
index 9bad9c1ac..2b86ed7c2 100644
--- a/game/client/src/main/java/net/minecraft/client/entity/player/PlayerLocal.java
+++ b/game/client/src/main/java/net/minecraft/client/entity/player/PlayerLocal.java
@@ -5,7 +5,6 @@ import com.mojang.logging.LogUtils;
 import com.mojang.nbt.tags.CompoundTag;
 
 import net.minecraft.client.Minecraft;
-import net.minecraft.client.gui.hud.HudIngame;
 import net.minecraft.client.gui.hud.component.HudComponents;
 import net.minecraft.client.option.GameSettings;
 import net.minecraft.client.option.enums.ParticlesQuality;
@@ -157,7 +156,7 @@ public class PlayerLocal extends Player {
             if (this.timeInPortal >= 1.0F || getGamemode().hasInstantPortalTravel()) {
                 this.timeInPortal = 1.0F;
                 if (!this.world.isClientSide) {
-                    this.timeUntilPortal = 10;
+                    this.timeUntilPortal = PORTAL_DELAY_TICKS;
                     this.mc.sndManager.playSound("portal.travel", SoundCategory.WORLD_SOUNDS, 1.0F, this.random.nextFloat() * 0.4F + 0.8F);
                     Dimension targetDim = ((BlockLogicPortal) Blocks.blocksList[this.portalID].getLogic()).targetDimension;
                     if (this.dimension == targetDim.id) {
diff --git a/game/client/src/main/java/net/minecraft/client/gui/CycleButtonElement.java b/game/client/src/main/java/net/minecraft/client/gui/CycleButtonElement.java
new file mode 100644
index 000000000..d19d11eab
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/gui/CycleButtonElement.java
@@ -0,0 +1,55 @@
+package net.minecraft.client.gui;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.function.Function;
+
+public class CycleButtonElement<T> extends ButtonElement {
+    private final T @NotNull [] values;
+    private final @NotNull Function<T, String> labelGetter;
+
+    private int index;
+
+    public CycleButtonElement(int id, int xPosition, int yPosition, T @NotNull [] values, @NotNull T value, @NotNull Function<T, String> labelGetter) {
+        this(id, xPosition, yPosition, 200, 20, values, value, labelGetter);
+    }
+
+    public CycleButtonElement(int id, int xPosition, int yPosition, int width, int height, T @NotNull [] values, @NotNull T value, @NotNull Function<T, String> labelGetter) {
+        super(id, xPosition, yPosition, width, height, "");
+
+        if (values.length == 0) {
+            throw new IllegalArgumentException("values must not be empty");
+        }
+        this.values = values;
+        this.labelGetter = labelGetter;
+        this.setValue(value);
+    }
+
+    public @NotNull T getValue() {
+        return this.values[this.index];
+    }
+
+    public void setValue(@NotNull T value) {
+        this.index = this.indexOf(value);
+        this.updateText();
+    }
+
+    public @NotNull T cycle() {
+        this.index = (this.index + 1) % this.values.length;
+        this.updateText();
+        return this.getValue();
+    }
+
+    private int indexOf(@NotNull T value) {
+        for (int i = 0; i < this.values.length; i++) {
+            if (this.values[i].equals(value)) {
+                return i;
+            }
+        }
+        return 0;
+    }
+
+    private void updateText() {
+        this.displayString = this.labelGetter.apply(this.getValue());
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentLog.java b/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentLog.java
index 913cdd37c..92be8e933 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentLog.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentLog.java
@@ -13,10 +13,7 @@ import net.minecraft.client.render.renderer.State;
 import net.minecraft.core.net.command.TextFormatting;
 import org.jetbrains.annotations.NotNull;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 public class HudComponentLog extends HudComponentMovable {
     private static final class LogEntry {
@@ -71,7 +68,7 @@ public class HudComponentLog extends HudComponentMovable {
         return GameSettings.DEVELOPER_MODE.value;
     }
 
-    private void drawAlignedString(@NotNull FontRenderer sr, String text, int boxX, int boxY, int boxWidth, int argb) {
+    private void drawAlignedString(@NotNull FontRenderer sr, @NotNull String text, int boxX, int boxY, int boxWidth, int argb) {
         int textWidth = sr.stringWidth(text);
         float xMult = GameSettings.HORIZONTAL_LOG_TEXT_ALIGNMENT.value.multiplier;
         int renderX = boxX + PADDING + (int) ((boxWidth - (PADDING * 2) - textWidth) * xMult);
@@ -124,11 +121,18 @@ public class HudComponentLog extends HudComponentMovable {
             }
         }
 
-        final @NotNull String @NotNull [] messages = new String[this.logEntries.size()];
-        final float @NotNull [] opacities = new float[this.logEntries.size()];
-        for (int i = 0; i < this.logEntries.size(); i++) {
-            messages[i] = this.logEntries.get(i).message;
-            opacities[i] = this.logEntries.get(i).getOpacity();
+        final @NotNull String @NotNull [] messages;
+        final float @NotNull [] opacities;
+
+        synchronized (this.logEntries) {
+            int size = this.logEntries.size();
+            messages = new String[size];
+            opacities = new float[size];
+            for (int i = 0; i < size; i++) {
+                var entry = this.logEntries.get(i);
+                messages[i] = entry.message;
+                opacities[i] = entry.getOpacity();
+            }
         }
 
         render(mc.font, x, y, messages, opacities);
diff --git a/game/client/src/main/java/net/minecraft/client/gui/worldsettings/ScreenWorldSettings.java b/game/client/src/main/java/net/minecraft/client/gui/worldsettings/ScreenWorldSettings.java
index d138d87a7..70d624cc5 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/worldsettings/ScreenWorldSettings.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/worldsettings/ScreenWorldSettings.java
@@ -7,10 +7,12 @@ import net.minecraft.client.gui.paged.PageComponent;
 import net.minecraft.client.gui.paged.PageRegistry;
 import net.minecraft.client.gui.paged.ScreenPaged;
 import net.minecraft.client.gui.worldsettings.gamerule.BooleanGameRuleComponent;
+import net.minecraft.client.gui.worldsettings.gamerule.EnumGameRuleComponent;
 import net.minecraft.client.gui.worldsettings.settingnode.CategoryComponent;
 import net.minecraft.client.gui.worldsettings.settingnode.NestedNodeComponent;
 import net.minecraft.core.data.gamerule.GameRule;
 import net.minecraft.core.data.gamerule.GameRuleBoolean;
+import net.minecraft.core.data.gamerule.GameRuleEnum;
 import net.minecraft.core.data.registry.Registries;
 import net.minecraft.core.entity.EntityDispatcher;
 import net.minecraft.core.entity.Mob;
@@ -53,6 +55,8 @@ public class ScreenWorldSettings extends ScreenPaged implements WorldConfigurati
         for (final @NotNull GameRule<?> gameRule : Registries.GAME_RULES) {
             if (gameRule instanceof GameRuleBoolean) {
                 PAGE_GAME_RULES.withComponent(new BooleanGameRuleComponent((GameRuleBoolean) gameRule));
+            } else if (gameRule instanceof GameRuleEnum) {
+                PAGE_GAME_RULES.withComponent(new EnumGameRuleComponent<>((GameRuleEnum<?>) gameRule));
             }
         }
     }
@@ -92,6 +96,8 @@ public class ScreenWorldSettings extends ScreenPaged implements WorldConfigurati
         for (final @NotNull PageComponent component : gameRulesPageComponents) {
             if (component instanceof BooleanGameRuleComponent ruleComponent) {
                 ruleComponent.setGameRuleCollection(worldConfiguration.getGameRules());
+            } else if (component instanceof EnumGameRuleComponent<?> ruleComponent) {
+                ruleComponent.setGameRuleCollection(worldConfiguration.getGameRules());
             }
         }
 
diff --git a/game/client/src/main/java/net/minecraft/client/gui/worldsettings/gamerule/EnumGameRuleComponent.java b/game/client/src/main/java/net/minecraft/client/gui/worldsettings/gamerule/EnumGameRuleComponent.java
new file mode 100644
index 000000000..3bd20f75c
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/gui/worldsettings/gamerule/EnumGameRuleComponent.java
@@ -0,0 +1,81 @@
+package net.minecraft.client.gui.worldsettings.gamerule;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.CycleButtonElement;
+import net.minecraft.client.gui.worldsettings.ButtonComponent;
+import net.minecraft.client.render.window.CursorShape;
+import net.minecraft.core.data.gamerule.GameRuleCollection;
+import net.minecraft.core.data.gamerule.GameRuleEnum;
+import net.minecraft.core.lang.I18n;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Locale;
+
+public class EnumGameRuleComponent<E extends Enum<E>>
+    extends ButtonComponent
+{
+    private @Nullable GameRuleCollection gameRuleCollection = null;
+    private final @NotNull GameRuleEnum<E> gameRule;
+    private final @NotNull CycleButtonElement<E> button;
+
+    public EnumGameRuleComponent(final @NotNull GameRuleEnum<E> gameRule) {
+        super(gameRule.getTranslationKey() + ".name", gameRule.getTranslationKey() + ".desc");
+
+        this.gameRule = gameRule;
+        this.button = new CycleButtonElement<>(0, 0, 0, 150, 20, gameRule.getValues(), gameRule.getDefaultValue(), this::translateValue);
+    }
+
+    public void setGameRuleCollection(final @NotNull GameRuleCollection collection) {
+        this.gameRuleCollection = collection;
+    }
+
+    private @NotNull String translateValue(final @NotNull E value) {
+        return I18n.getInstance().translateKey(this.gameRule.getTranslationKey() + ".value." + value.name().toLowerCase(Locale.ROOT));
+    }
+
+    @Override
+    public void resetValue() {
+        assert this.gameRuleCollection != null;
+        this.gameRuleCollection.setValue(this.gameRule, this.gameRule.getDefaultValue());
+        this.onChange();
+    }
+
+    @Override
+    public void init(final @NotNull Minecraft mc){
+        this.onChange();
+    }
+
+    @Override
+    public boolean isDefault() {
+        assert this.gameRuleCollection != null;
+        return this.gameRuleCollection.getValue(this.gameRule).equals(this.gameRule.getDefaultValue());
+    }
+
+    @Override
+    protected void buttonClicked(final int mouseButton, final int x, final int y, final int width, final int height, final int relativeMouseX, final int relativeMouseY) {
+        assert this.gameRuleCollection != null;
+        this.gameRuleCollection.setValue(this.gameRule, this.button.cycle());
+    }
+
+    protected void onChange() {
+        assert this.gameRuleCollection != null;
+        this.button.setValue(this.gameRuleCollection.getValue(this.gameRule));
+    }
+
+    @Override
+    protected void renderButton(final int x, final int y, final int relativeButtonX, final int relativeButtonY, final int buttonWidth, final int buttonHeight, final int relativeMouseX, final int relativeMouseY) {
+        super.renderButton(x, y, relativeButtonX, relativeButtonY, buttonWidth, buttonHeight, relativeMouseX, relativeMouseY);
+
+        this.button.xPosition = x + relativeButtonX;
+        this.button.yPosition = y + relativeButtonY;
+        this.button.width = buttonWidth;
+        this.button.height = buttonHeight;
+
+        this.button.drawButton(mc, x + relativeMouseX, y + relativeMouseY);
+
+        if (mc.currentScreen != null && relativeMouseX >= relativeButtonX && relativeMouseX < relativeButtonX + buttonWidth && relativeMouseY >= relativeButtonY && relativeButtonY < relativeButtonY + buttonHeight) {
+            mc.currentScreen.setDesiredCursor(CursorShape.HAND);
+        }
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelWireRedstone.java b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelWireRedstone.java
index 263d8f18f..83c596db1 100644
--- a/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelWireRedstone.java
+++ b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelWireRedstone.java
@@ -13,8 +13,6 @@ import net.minecraft.core.world.pos.TilePos;
 import net.minecraft.core.world.pos.TilePosc;
 import org.jetbrains.annotations.NotNull;
 
-import static net.minecraft.core.block.BlockLogicWireRedstone.isClimbableWall;
-
 public class BlockModelWireRedstone<T extends BlockLogic> extends BlockModelStandard<T> {
     protected final IconCoordinate wireStraight = TextureRegistry.getTexture("minecraft:block/wire_redstone_straight");
     protected final IconCoordinate wireCross = TextureRegistry.getTexture("minecraft:block/wire_redstone_cross");
@@ -42,31 +40,19 @@ public class BlockModelWireRedstone<T extends BlockLogic> extends BlockModelStan
 
         tessellator.setLightmapCoord2i(Math.max(worldSource.getSavedLightValue(LightLayer.Block, tilePos), meta & 0xF), worldSource.getSavedLightValue(LightLayer.Sky, tilePos));
         tessellator.setColorOpaque3f(red, green, blue);
-        boolean west = BlockLogicWireRedstone.shouldConnectTo(worldSource, tilePos.west(queryPos), Side.WEST) || !isClimbableWall(worldSource, tilePos.west(queryPos), Side.WEST) && BlockLogicWireRedstone.shouldConnectToDiagonal(worldSource, tilePos.add(-1, -1, 0, queryPos), Side.WEST, Side.BOTTOM);
-        boolean east = BlockLogicWireRedstone.shouldConnectTo(worldSource, tilePos.east(queryPos), Side.EAST) || !isClimbableWall(worldSource, tilePos.east(queryPos), Side.EAST) && BlockLogicWireRedstone.shouldConnectToDiagonal(worldSource, tilePos.add(1, -1, 0, queryPos), Side.EAST, Side.BOTTOM);
-        boolean north = BlockLogicWireRedstone.shouldConnectTo(worldSource, tilePos.north(queryPos), Side.NORTH) || !isClimbableWall(worldSource, tilePos.north(queryPos), Side.NORTH) && BlockLogicWireRedstone.shouldConnectToDiagonal(worldSource, tilePos.add(0, -1, -1, queryPos), Side.NORTH, Side.BOTTOM);
-        boolean south = BlockLogicWireRedstone.shouldConnectTo(worldSource, tilePos.south(queryPos), Side.SOUTH) || !isClimbableWall(worldSource, tilePos.south(queryPos), Side.SOUTH) && BlockLogicWireRedstone.shouldConnectToDiagonal(worldSource, tilePos.add(0, -1, 1, queryPos), Side.SOUTH, Side.BOTTOM);
+        boolean west = BlockLogicWireRedstone.shouldConnectToSide(worldSource, tilePos, Side.WEST);
+        boolean east = BlockLogicWireRedstone.shouldConnectToSide(worldSource, tilePos, Side.EAST);
+        boolean north = BlockLogicWireRedstone.shouldConnectToSide(worldSource, tilePos, Side.NORTH);
+        boolean south = BlockLogicWireRedstone.shouldConnectToSide(worldSource, tilePos, Side.SOUTH);
         boolean westUp = false;
         boolean eastUp = false;
         boolean northUp = false;
         boolean southUp = false;
         if (!worldSource.isBlockOpaqueCube(tilePos.up(queryPos))) {
-            if (BlockLogicWireRedstone.shouldConnectToDiagonal(worldSource, tilePos.add(-1, 1, 0, queryPos), Side.WEST, Side.TOP)) {
-                west = true;
-                westUp = true;
-            }
-            if (BlockLogicWireRedstone.shouldConnectToDiagonal(worldSource, tilePos.add(1, 1, 0, queryPos), Side.EAST, Side.TOP)) {
-                east = true;
-                eastUp = true;
-            }
-            if (BlockLogicWireRedstone.shouldConnectToDiagonal(worldSource, tilePos.add(0, 1, -1, queryPos), Side.NORTH, Side.TOP)) {
-                north = true;
-                northUp = true;
-            }
-            if (BlockLogicWireRedstone.shouldConnectToDiagonal(worldSource, tilePos.add(0, 1, 1, queryPos), Side.SOUTH, Side.TOP)) {
-                south = true;
-                southUp = true;
-            }
+            westUp = BlockLogicWireRedstone.shouldConnectToDiagonal(worldSource, tilePos.add(-1, 1, 0, queryPos), Side.WEST, Side.TOP);
+            eastUp = BlockLogicWireRedstone.shouldConnectToDiagonal(worldSource, tilePos.add(1, 1, 0, queryPos), Side.EAST, Side.TOP);
+            northUp = BlockLogicWireRedstone.shouldConnectToDiagonal(worldSource, tilePos.add(0, 1, -1, queryPos), Side.NORTH, Side.TOP);
+            southUp = BlockLogicWireRedstone.shouldConnectToDiagonal(worldSource, tilePos.add(0, 1, 1, queryPos), Side.SOUTH, Side.TOP);
         }
 
         final int x = tilePos.x();
diff --git a/game/client/src/main/java/net/minecraft/client/render/item/model/ItemModelStandard.java b/game/client/src/main/java/net/minecraft/client/render/item/model/ItemModelStandard.java
index 0e30bd73c..34143c39a 100644
--- a/game/client/src/main/java/net/minecraft/client/render/item/model/ItemModelStandard.java
+++ b/game/client/src/main/java/net/minecraft/client/render/item/model/ItemModelStandard.java
@@ -38,7 +38,7 @@ public class ItemModelStandard extends ItemModel{
     public static final DisplayPos DEFAULT_ITEM_THIRD_PERSON_RIGHT_HAND = new DisplayPos(0, 3/16f, 1/16f, 0, 0, 0, 0.55f, 0.55f, 0.55f);
     public static final DisplayPos DEFAULT_ITEM_THIRD_PERSON_LEFT_HAND = new DisplayPos(0, 3/16f, 1/16f, 0, 0, 0, 0.55f, 0.55f, 0.55f);
     public static final DisplayPos DEFAULT_ITEM_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 DEFAULT_ITEM_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 DEFAULT_ITEM_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 DEFAULT_ITEM_FIXED = new DisplayPos(0, 0, 0, 0, 180, 0, 1, 1, 1);
 
     protected boolean itemfullBright = false; // TODO this is probably redundant with the emissive textures now
diff --git a/game/client/src/main/resources/assets/minecraft/models/block/stairs.json b/game/client/src/main/resources/assets/minecraft/models/block/stairs.json
index 886682db1..0719965bd 100644
--- a/game/client/src/main/resources/assets/minecraft/models/block/stairs.json
+++ b/game/client/src/main/resources/assets/minecraft/models/block/stairs.json
@@ -10,10 +10,25 @@
             "translation": [ 0, 0, 0 ],
             "scale": [ 1, 1, 1 ]
         },
+        "thirdperson_righthand": {
+            "rotation": [75, 45, 0],
+            "translation": [ 0, 2.5, 0],
+            "scale": [ 0.375, 0.375, 0.375 ]
+        },
+        "firstperson_righthand": {
+            "rotation": [ 0, 45, 0 ],
+            "translation": [ 0, 0, 0 ],
+            "scale": [0.4, 0.4, 0.4]
+        },
         "thirdperson_lefthand": {
-            "rotation": [ 75, -135, 0 ],
+            "rotation": [75, -135, 0],
             "translation": [ 0, 2.5, 0],
             "scale": [ 0.375, 0.375, 0.375 ]
+        },
+        "firstperson_lefthand": {
+            "rotation": [ 0, -135, 0 ],
+            "translation": [ 0, 0, 0 ],
+            "scale": [0.4, 0.4, 0.4]
         }
     },
     "textures": {
diff --git a/game/client/src/main/resources/credits.txt b/game/client/src/main/resources/credits.txt
index 78a297d84..cef07c221 100644
--- a/game/client/src/main/resources/credits.txt
+++ b/game/client/src/main/resources/credits.txt
@@ -53,6 +53,7 @@ Doop (@brokendoop)
 --Gloomstone bricks
 --Sorched Logs
 --Crude steel texture
+--Statue item textures
 Gungun974
 --Improved packet processing
 --Bug fixes
diff --git a/game/core/src/main/java/net/minecraft/core/block/Block.java b/game/core/src/main/java/net/minecraft/core/block/Block.java
index 3880df35b..348e74da5 100644
--- a/game/core/src/main/java/net/minecraft/core/block/Block.java
+++ b/game/core/src/main/java/net/minecraft/core/block/Block.java
@@ -401,6 +401,11 @@ public final class Block<T extends BlockLogic>
         this.logic.onNeighborChanged(world, tilePos, block);
     }
 
+    @Override
+    public void onChunkLoad(@NotNull World world, @NotNull TilePosc tilePos) {
+        this.logic.onChunkLoad(world, tilePos);
+    }
+
     @Override
     public int tickDelay() {
         return this.logic.tickDelay();
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockInterface.java b/game/core/src/main/java/net/minecraft/core/block/BlockInterface.java
index a259c11c0..b2384a9c4 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockInterface.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockInterface.java
@@ -159,6 +159,8 @@ interface BlockInterface {
 
     void onNeighborChanged(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Block<?> block);
 
+    void onChunkLoad(final @NotNull World world, final @NotNull TilePosc tilePos);
+
     @Deprecated
     default void onNeighborBlockChange(final @NotNull World world, final int x, final int y, final int z, final int id) {
         this.onNeighborChanged(world, new TilePos(x, y, z), Objects.requireNonNull(Blocks.getBlock(id)));
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogic.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogic.java
index 05e590e5b..72098243e 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogic.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogic.java
@@ -203,6 +203,9 @@ public class BlockLogic implements BlockInterface, IItemConvertible {
     @Override
     public void onNeighborChanged(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Block<?> block) { }
 
+    @Override
+    public void onChunkLoad(final @NotNull World world, final @NotNull TilePosc tilePos) { }
+
     @Override
     public int tickDelay() {
         return 10;
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFire.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFire.java
index 7571fabb6..36c0730cb 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFire.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFire.java
@@ -8,6 +8,8 @@ import net.minecraft.core.block.tag.BlockTags;
 import net.minecraft.core.data.gamerule.GameRules;
 import net.minecraft.core.data.tag.Tag;
 import net.minecraft.core.entity.Entity;
+import net.minecraft.core.enums.EnumFireSpread;
+import net.minecraft.core.item.block.ItemBlock;
 import net.minecraft.core.sound.SoundCategory;
 import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.util.phys.BoundingVolume;
@@ -250,8 +252,10 @@ public class BlockLogicFire
         }
         world.scheduleBlockUpdate(tilePos, this.block, this.tickDelay());
 
+        final EnumFireSpread spreadRule = world.getGameRuleValue(GameRules.FIRE_SPREAD);
+        final boolean doFireBurn = world.getGameRuleValue(GameRules.DO_FIRE_BURN);
 
-        if (!infiniBurn && !this.canNeighborCatchFire(world, tilePos)) {
+        if (!infiniBurn && (!doFireBurn || !this.canNeighborCatchFire(world, tilePos))) {
             if (!world.isBlockNormalCube(tilePos.down(queryPos)) || meta > 3) {
                 this.setBurnResult(world, tilePos);
             }
@@ -263,12 +267,18 @@ public class BlockLogicFire
             return;
         }
 
-        this.checkBurn(world, tilePos.east(queryPos), 300, rand, meta);
-        this.checkBurn(world, tilePos.west(queryPos), 300, rand, meta);
-        this.checkBurn(world, tilePos.down(queryPos), 250, rand, meta);
-        this.checkBurn(world, tilePos.up(queryPos), 250, rand, meta);
-        this.checkBurn(world, tilePos.north(queryPos), 300, rand, meta);
-        this.checkBurn(world, tilePos.south(queryPos), 300, rand, meta);
+        if (doFireBurn) {
+            this.checkBurn(world, tilePos.east(queryPos), 300, rand, meta, spreadRule);
+            this.checkBurn(world, tilePos.west(queryPos), 300, rand, meta, spreadRule);
+            this.checkBurn(world, tilePos.down(queryPos), 250, rand, meta, spreadRule);
+            this.checkBurn(world, tilePos.up(queryPos), 250, rand, meta, spreadRule);
+            this.checkBurn(world, tilePos.north(queryPos), 300, rand, meta, spreadRule);
+            this.checkBurn(world, tilePos.south(queryPos), 300, rand, meta, spreadRule);
+        }
+
+        if (spreadRule == EnumFireSpread.NONE) {
+            return;
+        }
 
         final @NotNull TilePos localPos = new TilePos(tilePos);
         for (localPos.x = tilePos.x() - 1; localPos.x <= tilePos.x() + 1; localPos.x++) {
@@ -282,7 +292,7 @@ public class BlockLogicFire
                     if (localPos.y > tilePos.y() + 1) {
                         a += (localPos.y - (tilePos.y() + 1)) * 100;
                     }
-                    final int b = this.getFireChance(world, localPos);
+                    final int b = this.getFireChance(world, localPos, spreadRule);
                     if (b <= 0) {
                         continue;
                     }
@@ -298,7 +308,7 @@ public class BlockLogicFire
                         continue;
                     }
 
-                    if (world.getGameRuleValue(GameRules.DO_FIRE_SPREAD) && this.getBurnResult(world, localPos) == Blocks.AIR /* Fix for fire destroying moss blocks */) {
+                    if (this.getBurnResult(world, localPos) == Blocks.AIR /* Fix for fire destroying moss blocks */) {
                         //Spread Fire with higher meta
                         world.setBlockTypeDataNotify(localPos, this.getSpreadFireBlock(), Math.min(meta + rand.nextInt(5) / 4, 15));}
                 }
@@ -306,10 +316,10 @@ public class BlockLogicFire
         }
     }
 
-    private void checkBurn(final @NotNull World world, final @NotNull TilePos tilePos, final int chance, final @NotNull Random random, final int meta) {
-        if (!world.getGameRuleValue(GameRules.DO_FIRE_SPREAD)) return;
+    private void checkBurn(final @NotNull World world, final @NotNull TilePos tilePos, final int chance, final @NotNull Random random, final int meta, final @NotNull EnumFireSpread spreadRule) {
         if (random.nextInt(chance) < burnChance[world.getBlockType(tilePos).id()]) {
             final Block<?> targetBlock = world.getBlockType(tilePos);
+            if (spreadRule == EnumFireSpread.FOLIAGE && !isFoliage(targetBlock)) return;
             final boolean isTNT = targetBlock == Blocks.TNT;
 
             // Logs should always scorch when they burn, never convert into a fire block first.
@@ -319,7 +329,7 @@ public class BlockLogicFire
                 return;
             }
 
-            if (random.nextInt(meta + 10) < 5 && !world.isBlockBeingRainedOn(tilePos)) {
+            if (spreadRule != EnumFireSpread.NONE && random.nextInt(meta + 10) < 5 && !world.isBlockBeingRainedOn(tilePos)) {
                 if (this.getBurnResult(world, tilePos) == Blocks.AIR /* Fix for fire destroying moss blocks */) {
                     //Spread Fire with higher meta
                     world.setBlockTypeDataNotify(tilePos, this.getSpreadFireBlock(), Math.min(meta + random.nextInt(5) / 4, 15));
@@ -391,18 +401,18 @@ public class BlockLogicFire
         return canBurn(world, tilePos.south(queryPos));
     }
 
-    private int getFireChance(final @NotNull World world, final @NotNull TilePosc tilePos) {
+    private int getFireChance(final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull EnumFireSpread spreadRule) {
         int flammability = 0;
         if (!world.isAirBlock(tilePos)) {
             return 0;
         } else {
             TilePos queryPos = new TilePos();
-            flammability = getFlammability(world, tilePos.east(queryPos), flammability);
-            flammability = getFlammability(world, tilePos.west(queryPos), flammability);
-            flammability = getFlammability(world, tilePos.down(queryPos), flammability);
-            flammability = getFlammability(world, tilePos.up(queryPos), flammability);
-            flammability = getFlammability(world, tilePos.north(queryPos), flammability);
-            flammability = getFlammability(world, tilePos.south(queryPos), flammability);
+            flammability = getFlammability(world, tilePos.east(queryPos), flammability, spreadRule);
+            flammability = getFlammability(world, tilePos.west(queryPos), flammability, spreadRule);
+            flammability = getFlammability(world, tilePos.down(queryPos), flammability, spreadRule);
+            flammability = getFlammability(world, tilePos.up(queryPos), flammability, spreadRule);
+            flammability = getFlammability(world, tilePos.north(queryPos), flammability, spreadRule);
+            flammability = getFlammability(world, tilePos.south(queryPos), flammability, spreadRule);
             return flammability;
         }
     }
@@ -415,11 +425,23 @@ public class BlockLogicFire
         return flameChance[blockId] > 0;
     }
 
+    public static boolean isFoliage(final @NotNull Block<?> block) {
+        final BlockLogic logic = block.getLogic();
+        return logic instanceof BlockLogicLog || logic instanceof BlockLogicLeavesBase || logic instanceof BlockLogicMoss;
+    }
+
     public static int getFlammability(final @NotNull World world, final @NotNull TilePosc tilePos, final int currentFlameChance) {
         final int blockFlameChance = flameChance[world.getBlockType(tilePos).id()];
         return Math.max(blockFlameChance, currentFlameChance);
     }
 
+    public static int getFlammability(final @NotNull World world, final @NotNull TilePosc tilePos, final int currentFlameChance, final @NotNull EnumFireSpread spreadRule) {
+        if (spreadRule == EnumFireSpread.FOLIAGE && !isFoliage(world.getBlockType(tilePos))) {
+            return currentFlameChance;
+        }
+        return getFlammability(world, tilePos, currentFlameChance);
+    }
+
     public boolean isValidFireLocation(final @NotNull World world, final @NotNull TilePosc tilePos) {
         return world.isBlockNormalCube(tilePos.down(new TilePos())) || this.canNeighborCatchFire(world, tilePos);
     }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicPortal.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicPortal.java
index 6c3a6c0b6..3037261c6 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicPortal.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicPortal.java
@@ -321,13 +321,7 @@ public class BlockLogicPortal extends BlockLogicTransparent implements IPainted,
                 }
             }
         }
-        if (entity instanceof Player ridingPlayer && ridingPlayer.vehicle instanceof MobPig) {
-            ridingPlayer.handlePortal(this.block.id(), getColor(world, tilePos));
-        } else if (entity instanceof MobPig && entity.passenger instanceof Player riderPlayer) {
-            riderPlayer.handlePortal(this.block.id(), getColor(world, tilePos));
-        } else if (entity.vehicle == null && entity.passenger == null) {
-            entity.handlePortal(this.block.id(), getColor(world, tilePos));
-        }
+        entity.handlePortal(this.block.id(), getColor(world, tilePos));
     }
 
     @Override
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicTorchRedstone.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicTorchRedstone.java
index 5196b9ec6..f12ce32fe 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicTorchRedstone.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicTorchRedstone.java
@@ -70,6 +70,7 @@ public class BlockLogicTorchRedstone extends BlockLogicTorch {
 
     @Override
     public void updateTick(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Random rand, boolean isRandomTick) {
+        if (isRandomTick || world.isClientSide) return;
         boolean isPowered = hasNeighborSignal(world, tilePos);
         if (this.torchActive) {
             if (isPowered) {
@@ -80,10 +81,17 @@ public class BlockLogicTorchRedstone extends BlockLogicTorch {
         }
     }
 
+    @Override
+    public void onChunkLoad(@NotNull World world, @NotNull TilePosc tilePos) {
+        world.scheduleBlockUpdate(tilePos, this.block, tickDelay());
+    }
+
     @Override
     public void onNeighborChanged(@NotNull World world, @NotNull TilePosc tilePos, final @NotNull Block<?> block) {
         super.onNeighborChanged(world, tilePos, block);
-        world.scheduleBlockUpdate(tilePos, this.block, tickDelay());
+        if (this.torchActive == hasNeighborSignal(world, tilePos)) {
+            world.scheduleBlockUpdate(tilePos, this.block, tickDelay());
+        }
     }
 
     @Override
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicWireRedstone.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicWireRedstone.java
index 65755d6a8..e9df3967f 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicWireRedstone.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicWireRedstone.java
@@ -317,37 +317,10 @@ public class BlockLogicWireRedstone extends BlockLogic implements ISupportable,
             return true;
         }
 
-        TilePos queryPos = new TilePos();
-        boolean negXShouldConnectTo =
-            shouldConnectTo(source, tilePos.add(-1, 0, 0, queryPos), Side.WEST) ||
-                    shouldConnectToDiagonal(source, tilePos.add(-1, -1, 0, queryPos), Side.WEST, Side.BOTTOM);
-
-        boolean posXShouldConnectTo =
-            shouldConnectTo(source, tilePos.add(1, 0, 0, queryPos), Side.EAST) ||
-                    shouldConnectToDiagonal(source, tilePos.add(1, -1, 0, queryPos), Side.EAST, Side.BOTTOM);
-
-        boolean negZShouldConnectTo =
-            shouldConnectTo(source, tilePos.add(0, 0, -1, queryPos), Side.NORTH) ||
-                    shouldConnectToDiagonal(source, tilePos.add(0, -1, -1, queryPos), Side.NORTH, Side.BOTTOM);
-
-        boolean posZShouldConnectTo =
-            shouldConnectTo(source, tilePos.add(0, 0, 1, queryPos), Side.SOUTH) ||
-                    shouldConnectToDiagonal(source, tilePos.add(0, -1, 1, queryPos), Side.SOUTH, Side.BOTTOM);
-
-        if (!source.isBlockNormalCube(tilePos.up(queryPos))) {
-            if (isClimbableWall(source, tilePos.add(-1, 0, 0, queryPos), Side.WEST) &&
-                shouldConnectToDiagonal(source, tilePos.add(-1, 1, 0, queryPos), Side.WEST, Side.TOP))
-                    {negXShouldConnectTo = true;}
-            if (isClimbableWall(source, tilePos.add(1, 0, 0, queryPos), Side.EAST) &&
-                shouldConnectToDiagonal(source, tilePos.add(1, 1, 0, queryPos), Side.EAST, Side.TOP))
-                    {posXShouldConnectTo = true;}
-            if (isClimbableWall(source, tilePos.add(0, 0, -1, queryPos), Side.NORTH) &&
-                shouldConnectToDiagonal(source, tilePos.add(0, 1, -1, queryPos), Side.NORTH, Side.TOP))
-                    {negZShouldConnectTo = true;}
-            if (isClimbableWall(source, tilePos.add(0, 0, 1, queryPos), Side.SOUTH) &&
-                shouldConnectToDiagonal(source, tilePos.add(0, 1, 1, queryPos), Side.SOUTH, Side.TOP))
-                    {posZShouldConnectTo = true;}
-        }
+        boolean negXShouldConnectTo = shouldConnectToSide(source, tilePos, Side.WEST);
+        boolean posXShouldConnectTo = shouldConnectToSide(source, tilePos, Side.EAST);
+        boolean negZShouldConnectTo = shouldConnectToSide(source, tilePos, Side.NORTH);
+        boolean posZShouldConnectTo = shouldConnectToSide(source, tilePos, Side.SOUTH);
         // Make wire emit block updates when a redirection happens
         if (source instanceof World world) {
             int direction = (data & MASK_DIRECTION) >> 4; // 0b00000000 00000000 00000000 DDDD0000
@@ -442,6 +415,19 @@ public class BlockLogicWireRedstone extends BlockLogic implements ISupportable,
         return shouldConnectToDiagonal(worldSource, new TilePos(x, y, z), side1, side2);
     }
 
+    public static boolean shouldConnectToSide(@NotNull WorldSource source, @NotNull TilePosc wirePos, @NotNull Side side) {
+        TilePos adjacentPos = wirePos.add(side.direction(), new TilePos());
+        if (shouldConnectTo(source, adjacentPos, side)) {
+            return true;
+        }
+        if (!isClimbableWall(source, adjacentPos, side) &&
+            shouldConnectToDiagonal(source, adjacentPos.down(new TilePos()), side, Side.BOTTOM)) {
+            return true;
+        }
+        return !source.isBlockOpaqueCube(wirePos.up(new TilePos())) &&
+            shouldConnectToDiagonal(source, adjacentPos.up(new TilePos()), side, Side.TOP);
+    }
+
     public static boolean shouldConnectToDiagonal(@NotNull WorldSource worldSource, @NotNull TilePos tilePos, @NotNull Side side1, @NotNull Side side2) {
         if (!(side2 == Side.TOP || side2 == Side.BOTTOM))
             return false; // Maybe some can generate a connection if placed below a block in the future?
diff --git a/game/core/src/main/java/net/minecraft/core/block/FluidHardening.java b/game/core/src/main/java/net/minecraft/core/block/FluidHardening.java
index 2e6e82020..74ea6f569 100644
--- a/game/core/src/main/java/net/minecraft/core/block/FluidHardening.java
+++ b/game/core/src/main/java/net/minecraft/core/block/FluidHardening.java
@@ -5,6 +5,7 @@ import net.minecraft.core.block.material.Material;
 import net.minecraft.core.block.material.Materials;
 import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.world.World;
+import net.minecraft.core.world.pos.TilePos;
 import net.minecraft.core.world.pos.TilePosc;
 import net.minecraft.core.world.type.tag.WorldTypeTags;
 import org.jetbrains.annotations.NotNull;
@@ -48,6 +49,9 @@ public final class FluidHardening {
     }
 
     private static boolean hardenWaterWithLava(final @NotNull World world, final @NotNull TilePosc tilePos) {
+        if (world.getBlockMaterial(tilePos.up(new TilePos())) != Materials.LAVA) {
+            return false;
+        }
         final int data = world.getBlockData(tilePos) & 0x0F;
         if (world.getWorldType().hasTag(WorldTypeTags.NETHER)) {
             world.setBlockTypeNotify(tilePos, data == 0 ? Blocks.OBSIDIAN : Blocks.COBBLE_GLOOMSTONE);
@@ -59,6 +63,9 @@ public final class FluidHardening {
     }
 
     private static boolean hardenWaterWithAcid(final @NotNull World world, final @NotNull TilePosc tilePos) {
+        if (world.getBlockMaterial(tilePos.up(new TilePos())) != Materials.ACID) {
+            return false;
+        }
         final int data = world.getBlockData(tilePos) & 0x0F;
         if (world.getWorldType().hasTag(WorldTypeTags.NETHER)) {
             world.setBlockTypeNotify(tilePos, data == 0 ? Blocks.OBSIDIAN : Blocks.GLOOMSTONE);
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 356b23d54..4e08fd416 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
@@ -3,6 +3,7 @@ package net.minecraft.core.block;
 import net.minecraft.core.block.material.Material;
 import net.minecraft.core.block.material.Materials;
 import net.minecraft.core.data.gamerule.GameRules;
+import net.minecraft.core.enums.EnumFireSpread;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.pos.TilePos;
 import net.minecraft.core.world.pos.TilePosc;
@@ -50,7 +51,7 @@ public class FluidLava
 
     @Override
     public void updateTickStill(final @NotNull BlockLogicFluid logicFluid, final @NotNull World world, final @NotNull TilePosc tilePos, final @NotNull Random rand) {
-        if (!world.getGameRuleValue(GameRules.DO_FIRE_SPREAD)) return;
+        if (world.getGameRuleValue(GameRules.FIRE_SPREAD) == EnumFireSpread.NONE) return;
         TilePos p = new TilePos(tilePos);
         TilePos queryPos = new TilePos(tilePos);
         final int count = rand.nextInt(3);
diff --git a/game/core/src/main/java/net/minecraft/core/block/piston/BlockLogicPistonBase.java b/game/core/src/main/java/net/minecraft/core/block/piston/BlockLogicPistonBase.java
index ee783315d..b8d6d4ae1 100644
--- a/game/core/src/main/java/net/minecraft/core/block/piston/BlockLogicPistonBase.java
+++ b/game/core/src/main/java/net/minecraft/core/block/piston/BlockLogicPistonBase.java
@@ -33,8 +33,6 @@ import org.joml.Vector3d;
 import org.joml.primitives.AABBd;
 import org.joml.primitives.AABBdc;
 
-import java.util.Random;
-
 import static net.minecraft.core.block.piston.PistonCommon.*;
 
 public class BlockLogicPistonBase extends BlockLogic implements BlockLogic.MatcherDataEquivalency.Masked {
@@ -426,22 +424,11 @@ public class BlockLogicPistonBase extends BlockLogic implements BlockLogic.Match
         this.signalUpdate(world, tilePos);
     }
 
-    @Override
-    public void updateTick(@NotNull World world, @NotNull TilePosc tilePos, @NotNull Random rand, boolean isRandomTick) {
-        if (isRandomTick || world.isClientSide) return;
-        this.signalUpdate(world, tilePos);
-    }
-
     @Override
     public void onNeighborChanged(@NotNull World world, @NotNull TilePosc tilePos, final @NotNull Block<?> block) {
         if (world.isClientSide) return;
-        final int data = fixLegacyBaseData(world, tilePos);
-
-        final boolean powered = hasNeighborSignal(world, tilePos, Direction.fromId(DIRECTION.get(data)));
-        final boolean extended = IS_EXTENDED.bool(data);
-        if (powered != extended) {
-            world.scheduleBlockUpdate(tilePos, this.block, 1);
-        }
+        final var data = fixLegacyBaseData(world, tilePos);
+        this.signalUpdate(world, tilePos, data);
     }
 
     @Override
diff --git a/game/core/src/main/java/net/minecraft/core/data/gamerule/GameRule.java b/game/core/src/main/java/net/minecraft/core/data/gamerule/GameRule.java
index 2f6d3f82e..c0f4669d3 100644
--- a/game/core/src/main/java/net/minecraft/core/data/gamerule/GameRule.java
+++ b/game/core/src/main/java/net/minecraft/core/data/gamerule/GameRule.java
@@ -8,12 +8,19 @@ public abstract class GameRule<T>
     private final @NotNull String key;
     private final @NotNull String translationKey;
     private final T defaultValue;
+    private final @NotNull String valueArgumentName;
 
     public GameRule(final @NotNull String key, final @NotNull String translationKey, final @NotNull T defaultValue)
+    {
+        this(key, translationKey, defaultValue, "value");
+    }
+
+    public GameRule(final @NotNull String key, final @NotNull String translationKey, final @NotNull T defaultValue, final @NotNull String valueArgumentName)
     {
         this.key = key;
         this.translationKey = translationKey;
         this.defaultValue = defaultValue;
+        this.valueArgumentName = valueArgumentName;
     }
 
     public @NotNull String getKey()
@@ -30,6 +37,11 @@ public abstract class GameRule<T>
         return this.defaultValue;
     }
 
+    public @NotNull String getValueArgumentName()
+    {
+        return this.valueArgumentName;
+    }
+
     public abstract void writeToNBT(@NotNull CompoundTag tag, T value);
 
     public abstract T readFromNBT(@NotNull CompoundTag tag);
diff --git a/game/core/src/main/java/net/minecraft/core/data/gamerule/GameRuleCollection.java b/game/core/src/main/java/net/minecraft/core/data/gamerule/GameRuleCollection.java
index daa8264c9..32c2bf57f 100644
--- a/game/core/src/main/java/net/minecraft/core/data/gamerule/GameRuleCollection.java
+++ b/game/core/src/main/java/net/minecraft/core/data/gamerule/GameRuleCollection.java
@@ -2,6 +2,7 @@ package net.minecraft.core.data.gamerule;
 
 import com.mojang.nbt.tags.CompoundTag;
 import net.minecraft.core.data.registry.Registries;
+import net.minecraft.core.enums.EnumFireSpread;
 import net.minecraft.core.world.settings.WorldConfiguration;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -85,9 +86,21 @@ public final class GameRuleCollection
             collection.setValue((GameRule)gameRule, gameRule.readFromNBT(tag));
         }
 
+        applyLegacyConversions(tag, collection);
+
         return collection;
     }
 
+    private static void applyLegacyConversions(final @NotNull CompoundTag tag, final @NotNull GameRuleCollection collection)
+    {
+        if (tag.containsKey("doFireSpread") && !tag.containsKey("fireSpread"))
+        {
+            final boolean legacyFireSpread = tag.getBoolean("doFireSpread");
+            collection.setValue(GameRules.FIRE_SPREAD, legacyFireSpread ? EnumFireSpread.ALL : EnumFireSpread.NONE);
+            collection.setValue(GameRules.DO_FIRE_BURN, legacyFireSpread);
+        }
+    }
+
     public static void writeToNBT(CompoundTag tag, GameRuleCollection collection)
     {
         for (GameRule<?> gameRule : Registries.GAME_RULES)
diff --git a/game/core/src/main/java/net/minecraft/core/data/gamerule/GameRuleEnum.java b/game/core/src/main/java/net/minecraft/core/data/gamerule/GameRuleEnum.java
new file mode 100644
index 000000000..7cf649f35
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/data/gamerule/GameRuleEnum.java
@@ -0,0 +1,51 @@
+package net.minecraft.core.data.gamerule;
+
+import com.mojang.nbt.tags.CompoundTag;
+import com.mojang.nbt.tags.StringTag;
+import com.mojang.nbt.tags.Tag;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Locale;
+
+public class GameRuleEnum<E extends Enum<E>> extends GameRule<E> {
+    private final E @NotNull [] values;
+
+    public GameRuleEnum(final String key, final String translationKey, final @NotNull E defaultValue) {
+        super(key, translationKey, defaultValue);
+        this.values = defaultValue.getDeclaringClass().getEnumConstants();
+    }
+
+    public E @NotNull [] getValues() {
+        return this.values;
+    }
+
+    @Override
+    public void writeToNBT(final @NotNull CompoundTag tag, final @NotNull E value) {
+        tag.put(getKey(), new StringTag(value.name().toLowerCase(Locale.ROOT)));
+    }
+
+    @Override
+    public E readFromNBT(final @NotNull CompoundTag tag) {
+        final Tag<?> ruleTag = tag.getTag(getKey());
+        if (ruleTag instanceof StringTag) {
+            final E value = this.parseFromString(((StringTag) ruleTag).getValue());
+            if (value != null) {
+                return value;
+            }
+        }
+
+        return getDefaultValue();
+    }
+
+    @Override
+    public @Nullable E parseFromString(final @NotNull String string) {
+        for (final E value : this.values) {
+            if (value.name().equalsIgnoreCase(string)) {
+                return value;
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/data/gamerule/GameRuleInteger.java b/game/core/src/main/java/net/minecraft/core/data/gamerule/GameRuleInteger.java
index 80a9bfe31..56606b1b4 100644
--- a/game/core/src/main/java/net/minecraft/core/data/gamerule/GameRuleInteger.java
+++ b/game/core/src/main/java/net/minecraft/core/data/gamerule/GameRuleInteger.java
@@ -10,6 +10,10 @@ public class GameRuleInteger extends GameRule<Integer> {
         super(key, translationKey, defaultValue);
     }
 
+    public GameRuleInteger(final String key, final String translationKey, final Integer defaultValue, final String valueArgumentName) {
+        super(key, translationKey, defaultValue, valueArgumentName);
+    }
+
     @Override
     public void writeToNBT(final @NotNull CompoundTag tag, final Integer value) {
         final IntTag ruleTag = new IntTag(value);
diff --git a/game/core/src/main/java/net/minecraft/core/data/gamerule/GameRules.java b/game/core/src/main/java/net/minecraft/core/data/gamerule/GameRules.java
index 509fb9c7f..e9701aed0 100644
--- a/game/core/src/main/java/net/minecraft/core/data/gamerule/GameRules.java
+++ b/game/core/src/main/java/net/minecraft/core/data/gamerule/GameRules.java
@@ -1,7 +1,8 @@
 package net.minecraft.core.data.gamerule;
 
 import net.minecraft.core.data.registry.Registries;
-import net.minecraft.core.entity.Entity;
+import net.minecraft.core.enums.EnumFireSpread;
+import org.jetbrains.annotations.NotNull;
 
 public abstract class GameRules
 {
@@ -12,17 +13,19 @@ public abstract class GameRules
     public static GameRuleBoolean DO_SEASONAL_GROWTH = register(new GameRuleBoolean("doSeasonalGrowth", "gamerule.do_seasonal_growth", true));
     public static GameRuleBoolean TREECAPITATOR = register(new GameRuleBoolean("treecapitator", "gamerule.treecapitator", false));
     public static GameRuleBoolean DWARF_MODE = register(new GameRuleBoolean("dwarfMode", "gamerule.dwarf_mode", false));
-    public static GameRuleBoolean MOB_GRIEFING = register(new GameRuleBoolean("mobGriefing", "gamerule.mob_griefing", true));
+    public static GameRuleBoolean DO_MOB_GRIEFING = register(new GameRuleBoolean("doMobGriefing", "gamerule.do_mob_griefing", true));
+    public static GameRuleBoolean DO_EXPLOSION_GRIEFING = register(new GameRuleBoolean("doExplosionGriefing", "gamerule.do_explosion_griefing", true));
     public static GameRuleBoolean DO_DAY_CYCLE = register(new GameRuleBoolean("doDaylightCycle", "gamerule.do_day_night_cycle", true));
     public static GameRuleBoolean DO_WEATHER_CYCLE = register(new GameRuleBoolean("doWeatherCycle", "gamerule.do_weather_cycle", true));
-    public static GameRuleBoolean DO_FIRE_SPREAD = register(new GameRuleBoolean("doFireSpread", "gamerule.do_fire_spread", true));
-    public static GameRuleBoolean INSTANT_HEALING = register(new GameRuleBoolean("instantHealing", "gamerule.instant_healing", false));
-    public static GameRuleInteger ACID_MELT_RATE = register(new GameRuleInteger("acidMeltRate", "gamerule.acid_melt_rate", Entity.FIRE_DAMAGE_INTERVAL_TICKS));
+    public static GameRuleEnum<EnumFireSpread> FIRE_SPREAD = register(new GameRuleEnum<>("fireSpread", "gamerule.fire_spread", EnumFireSpread.ALL));
+    public static GameRuleBoolean DO_FIRE_BURN = register(new GameRuleBoolean("doFireBurn", "gamerule.do_fire_burn", true));
+    public static GameRuleBoolean DO_LIGHTNING_BURN = register(new GameRuleBoolean("doLightningBurn", "gamerule.do_lightning_burn", true));
+    public static GameRuleBoolean DO_INSTANT_HEALING = register(new GameRuleBoolean("doInstantHealing", "gamerule.do_instant_healing", false));
     public static GameRuleInteger RANDOM_TICK_SPEED = register(new GameRuleInteger("randomTickSpeed", "gamerule.random_tick_speed", 4));
-    public static GameRuleInteger ITEM_DESPAWN_DELAY = register(new GameRuleInteger("itemDespawnDelay", "gamerule.item_despawn_delay", 12000));
-    public static GameRuleInteger DROPPED_ITEM_DESPAWN_DELAY = register(new GameRuleInteger("droppedItemDespawnDelay", "gamerule.dropped_item_despawn_delay", 12000));
+    public static GameRuleInteger ITEM_DESPAWN_DELAY = register(new GameRuleInteger("itemDespawnDelay", "gamerule.item_despawn_delay", 600, "seconds"));
+    public static GameRuleInteger PLAYER_ITEM_DESPAWN_DELAY = register(new GameRuleInteger("playerItemDespawnDelay", "gamerule.player_item_despawn_delay", 720, "seconds"));
 
-    public static <T extends GameRule<?>> T register(T gameRule)
+    public static <T extends GameRule<?>> @NotNull T register(T gameRule)
     {
         Registries.GAME_RULES.register(gameRule.getKey(), gameRule);
 
diff --git a/game/core/src/main/java/net/minecraft/core/entity/Entity.java b/game/core/src/main/java/net/minecraft/core/entity/Entity.java
index 51a312c6d..cde2b08d2 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/Entity.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/Entity.java
@@ -12,7 +12,6 @@ import net.minecraft.core.block.material.Material;
 import net.minecraft.core.block.material.Materials;
 import net.minecraft.core.block.piston.BlockLogicPistonHead;
 import net.minecraft.core.block.tag.BlockTags;
-import net.minecraft.core.data.gamerule.GameRules;
 import net.minecraft.core.entity.animal.MobPig;
 import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.entity.vehicle.EntityBoat;
@@ -698,13 +697,11 @@ public abstract class Entity
         int maxY = MathHelper.floor(this.bb.maxY - 0.001D);
         int maxZ = MathHelper.floor(this.bb.maxZ - 0.001D);
         if (this.world.areBlocksLoaded(minX, minY, minZ, maxX, maxY, maxZ)) {
-            for (int _x = minX; _x <= maxX; _x++) {
-                for (int _y = minY; _y <= maxY; _y++) {
-                    for (int _z = minZ; _z <= maxZ; _z++) {
-                        int blockId = this.world.getBlockId(_x, _y, _z);
-                        if (blockId > 0) {
-                            Blocks.blocksList[blockId].onEntityCollidedWithBlock(this.world, _x, _y, _z, this);
-                        }
+            TilePos queryPos = new TilePos();
+            for (queryPos.x = minX; queryPos.x <= maxX; queryPos.x++) {
+                for (queryPos.y = minY; queryPos.y <= maxY; queryPos.y++) {
+                    for (queryPos.z = minZ; queryPos.z <= maxZ; queryPos.z++) {
+                        this.world.getBlockType(queryPos).onEntityCollision(this.world, queryPos, this);
                     }
 
                 }
@@ -799,8 +796,7 @@ public abstract class Entity
 
     public void acidTick() {
         final boolean physicallyInAcid = isInAcid();
-        final int meltRate = Math.max(1, this.world.getGameRuleValue(GameRules.ACID_MELT_RATE));
-        final boolean shouldTickRate = (this.tickCount % meltRate == 0);
+        final boolean shouldTickRate = (this.tickCount % FIRE_DAMAGE_INTERVAL_TICKS == 0);
 
         if (physicallyInAcid) {
             this.acidExitDelayLeft = ACID_EXIT_BUFFER;
diff --git a/game/core/src/main/java/net/minecraft/core/entity/EntityItem.java b/game/core/src/main/java/net/minecraft/core/entity/EntityItem.java
index 9e292b369..8ff78129c 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/EntityItem.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/EntityItem.java
@@ -83,7 +83,7 @@ public class EntityItem extends Entity
         this.xd = (float)(Math.random() * 0.2D - 0.1D);
         this.yd = 0.2D;
         this.zd = (float)(Math.random() * 0.2D - 0.1D);
-        this.lifetime = world.getGameRuleValue(GameRules.ITEM_DESPAWN_DELAY);
+        this.lifetime = world.getGameRuleValue(GameRules.ITEM_DESPAWN_DELAY) * 20;
         if(itemstack == null) {
         	System.err.println("Created EntityItem with no item!");
         	Thread.dumpStack();
@@ -232,7 +232,7 @@ public class EntityItem extends Entity
         if(tag.containsKey("Item")) {
             this.item = ItemStack.readItemStackFromNbt(tag.getCompound("Item"));
         }
-        this.lifetime = tag.getIntegerOrDefault("Lifetime", this.world.getGameRuleValue(GameRules.ITEM_DESPAWN_DELAY));
+        this.lifetime = tag.getIntegerOrDefault("Lifetime", this.world.getGameRuleValue(GameRules.ITEM_DESPAWN_DELAY) * 20);
     }
 
 	@Override
diff --git a/game/core/src/main/java/net/minecraft/core/entity/EntityLightning.java b/game/core/src/main/java/net/minecraft/core/entity/EntityLightning.java
index 1733c2818..10501b833 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/EntityLightning.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/EntityLightning.java
@@ -3,6 +3,7 @@ package net.minecraft.core.entity;
 import net.minecraft.core.entity.animal.MobWolf;
 import net.minecraft.core.enums.Difficulty;
 import net.minecraft.core.block.Blocks;
+import net.minecraft.core.data.gamerule.GameRules;
 import net.minecraft.core.sound.SoundCategory;
 import net.minecraft.core.util.helper.MathHelper;
 import com.mojang.nbt.tags.CompoundTag;
@@ -61,7 +62,7 @@ public class EntityLightning extends Entity {
                 this.flashes--;
                 this.life = 1;
                 this.seed = this.random.nextLong();
-                if (!this.silent && this.world.areBlocksLoaded(MathHelper.floor(this.x), MathHelper.floor(this.y), MathHelper.floor(this.z), 10)) {
+                if (!this.silent && this.world.getGameRuleValue(GameRules.DO_LIGHTNING_BURN) && this.world.areBlocksLoaded(MathHelper.floor(this.x), MathHelper.floor(this.y), MathHelper.floor(this.z), 10)) {
                     int i = MathHelper.floor(this.x);
                     int j = MathHelper.floor(this.y);
                     int k = MathHelper.floor(this.z);
@@ -89,7 +90,7 @@ public class EntityLightning extends Entity {
 
     @Override
     public void spawnInit() {
-        if (this.world.getDifficulty().id() >= Difficulty.NORMAL.id() && this.world.areBlocksLoaded(MathHelper.floor(this.x), MathHelper.floor(this.y), MathHelper.floor(this.z), 10)) {
+        if (this.world.getGameRuleValue(GameRules.DO_LIGHTNING_BURN) && this.world.getDifficulty().id() >= Difficulty.NORMAL.id() && this.world.areBlocksLoaded(MathHelper.floor(this.x), MathHelper.floor(this.y), MathHelper.floor(this.z), 10)) {
             int i = MathHelper.floor(this.x);
             int k = MathHelper.floor(this.y);
             int i1 = MathHelper.floor(this.z);
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 76df3b522..725fdf11f 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
@@ -484,7 +484,7 @@ public abstract class Mob extends Entity {
 
         ItemFood foodItem = (ItemFood) stack.getItem();
 
-        if (foodItem.getTicksPerHeal(stack) == 0 || this.world.getGameRuleValue(GameRules.INSTANT_HEALING)) {
+        if (foodItem.getTicksPerHeal(stack) == 0 || this.world.getGameRuleValue(GameRules.DO_INSTANT_HEALING)) {
             heal(foodItem.getHealAmount(stack));
         } else {
             String key = stack.getItemKey();
diff --git a/game/core/src/main/java/net/minecraft/core/entity/animal/MobPig.java b/game/core/src/main/java/net/minecraft/core/entity/animal/MobPig.java
index dfa5d25d7..be0aa4c9a 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/animal/MobPig.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/animal/MobPig.java
@@ -438,6 +438,7 @@ public class MobPig
         ItemStack heldItem = player.getHeldItem();
         if (heldItem != null && (heldItem.itemID == Blocks.MUSHROOM_BROWN.id() || heldItem.itemID == Blocks.MUSHROOM_RED.id())
             && getHealth() < getMaxHealth() && heldItem.consumeItem(player)) {
+            if (heldItem.stackSize <= 0) player.setHeldItem(null);
             heal(4);
 
             this.world.playSoundAtEntity(player, this, "random.bite",
@@ -482,6 +483,7 @@ public class MobPig
     {
         if(getSaddled()){
             dropItem(Items.SADDLE.id, 1);
+            setSaddled(false);
         }
         super.dropDeathItems();
     }
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 63e154732..630ef0df8 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
@@ -121,6 +121,7 @@ public abstract class Player
     public record FluidPlacement(int dimensionId, int x, int y, int z, long tick) {}
     public @Nullable FluidPlacement lastAcidPlacement;
     public @Nullable FluidPlacement lastWaterPlacement;
+    public static final int PORTAL_DELAY_TICKS = 10; // Must leave a portal for 0.5 seconds before it can attempt to transfer you again
     public int timeUntilPortal;
     protected boolean inPortal;
     public int portalID;
@@ -149,13 +150,14 @@ public abstract class Player
         this.score = 0;
         this.isSwinging = false;
         this.swingProgressInt = 0;
-        this.timeUntilPortal = 20;
+        this.timeUntilPortal = PORTAL_DELAY_TICKS;
         this.inPortal = false;
         this.bobberEntity = null;
         this.inventoryMenu = new MenuInventory(this.inventory);
         this.containerMenu = this.inventoryMenu;
         TilePos spawnPoint = world.getSpawnPoint();
         moveTo((double) spawnPoint.x + 0.5D, spawnPoint.y + 1, (double) spawnPoint.z + 0.5D, 0.0F, 0.0F);
+        setupScale();
 //        rotOffs = 180F;
         this.fireImmuneTicks = 20;
         setTextureIdentifier("minecraft", "char");
@@ -176,7 +178,7 @@ public abstract class Player
         this.entityData.set(DATA_SELECTED_ART, (byte) ArtType.values.indexOf(selectedArt));
     }
 
-    private void setupScale() {
+    protected void setupScale() {
         this.isDwarf = this.world.getGameRuleValue(GameRules.DWARF_MODE);
         if (this.isDwarf) {
             setSize(0.6f, isSneaking() ? 0.5f : 0.8f);
@@ -854,7 +856,7 @@ public abstract class Player
 
         final @NotNull EntityItem itemEntity = new EntityItem(this.world, this.x, (this.y - 0.3) + (double) this.getHeadHeight(), this.z, stack);
         itemEntity.pickupDelay = 40;
-        itemEntity.lifetime = this.world.getGameRuleValue(GameRules.DROPPED_ITEM_DESPAWN_DELAY);
+        itemEntity.lifetime = this.world.getGameRuleValue(GameRules.PLAYER_ITEM_DESPAWN_DELAY) * 20;
 
         if (randomDirection) {
             final float speed = this.random.nextFloat() * 0.5F;
@@ -1638,7 +1640,7 @@ public abstract class Player
     @Override
     public void handlePortal(int portalBlockId, DyeColor portalColor) {
         if (this.timeUntilPortal > 0) {
-            this.timeUntilPortal = 10;
+            this.timeUntilPortal = PORTAL_DELAY_TICKS;
         } else {
             this.portalID = portalBlockId;
             this.portalColor = portalColor;
diff --git a/game/core/src/main/java/net/minecraft/core/enums/EnumFireSpread.java b/game/core/src/main/java/net/minecraft/core/enums/EnumFireSpread.java
new file mode 100644
index 000000000..060f52dfc
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/enums/EnumFireSpread.java
@@ -0,0 +1,7 @@
+package net.minecraft.core.enums;
+
+public enum EnumFireSpread {
+	ALL,
+	FOLIAGE,
+	NONE
+}
diff --git a/game/core/src/main/java/net/minecraft/core/net/command/arguments/ArgumentTypeGameruleGeneric.java b/game/core/src/main/java/net/minecraft/core/net/command/arguments/ArgumentTypeGameruleGeneric.java
index 337a2cf72..170523141 100644
--- a/game/core/src/main/java/net/minecraft/core/net/command/arguments/ArgumentTypeGameruleGeneric.java
+++ b/game/core/src/main/java/net/minecraft/core/net/command/arguments/ArgumentTypeGameruleGeneric.java
@@ -8,8 +8,10 @@ import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
 import com.mojang.brigadier.suggestion.Suggestions;
 import com.mojang.brigadier.suggestion.SuggestionsBuilder;
 import net.minecraft.core.data.gamerule.GameRule;
+import net.minecraft.core.data.gamerule.GameRuleEnum;
 import net.minecraft.core.lang.I18n;
 
+import java.util.Locale;
 import java.util.concurrent.CompletableFuture;
 
 public class ArgumentTypeGameruleGeneric implements ArgumentType<Object> {
@@ -39,6 +41,15 @@ public class ArgumentTypeGameruleGeneric implements ArgumentType<Object> {
 
     @Override
     public <S> CompletableFuture<Suggestions> listSuggestions(CommandContext<S> context, SuggestionsBuilder builder) {
+        if (this.gameRule instanceof GameRuleEnum<?> enumRule) {
+            for (final Enum<?> value : enumRule.getValues()) {
+                final String name = value.name().toLowerCase(Locale.ROOT);
+                if (name.startsWith(builder.getRemainingLowerCase())) {
+                    builder.suggest(name);
+                }
+            }
+            return builder.buildFuture();
+        }
         return ArgumentType.super.listSuggestions(context, builder);
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/net/command/commands/CommandGameRule.java b/game/core/src/main/java/net/minecraft/core/net/command/commands/CommandGameRule.java
index 00b1d2c54..2d35a1cba 100644
--- a/game/core/src/main/java/net/minecraft/core/net/command/commands/CommandGameRule.java
+++ b/game/core/src/main/java/net/minecraft/core/net/command/commands/CommandGameRule.java
@@ -18,19 +18,20 @@ public class CommandGameRule implements CommandManager.CommandRegistry {
     public void register(CommandDispatcher<CommandSource> dispatcher) {
         final ArgumentBuilderLiteral<CommandSource> argumentBuilder = ArgumentBuilderLiteral.<CommandSource>literal("gamerule").requires(CommandSource::hasAdmin);
         for (final GameRule<?> gameRule : Registries.GAME_RULES) {
+            final String argName = gameRule.getValueArgumentName();
             ArgumentBuilderRequired<CommandSource, ?> gameRuleValueArgument;
             if (gameRule instanceof GameRuleBoolean) {
-                gameRuleValueArgument = ArgumentBuilderRequired.<CommandSource, Boolean>argument("value", ArgumentTypeBool.bool())
+                gameRuleValueArgument = ArgumentBuilderRequired.<CommandSource, Boolean>argument(argName, ArgumentTypeBool.bool())
                         .executes(c -> {
-                            c.getSource().getWorld().getLevelData().getGameRules().setValue((GameRuleBoolean) gameRule, ArgumentTypeBool.getBool(c, "value"));
+                            c.getSource().getWorld().getLevelData().getGameRules().setValue((GameRuleBoolean) gameRule, ArgumentTypeBool.getBool(c, argName));
                             c.getSource().sendPacketToAllPlayers(() -> new PacketGameRule(c.getSource().getWorld().getLevelData().getGameRules()));
-                            c.getSource().sendTranslatableMessage("command.commands.gamerule.set", gameRule.getKey(), ArgumentTypeBool.getBool(c, "value"));
+                            c.getSource().sendTranslatableMessage("command.commands.gamerule.set", gameRule.getKey(), ArgumentTypeBool.getBool(c, argName));
                             return Command.SINGLE_SUCCESS;
                         });
             } else {
-                gameRuleValueArgument = ArgumentBuilderRequired.<CommandSource, Object>argument("value", ArgumentTypeGameruleGeneric.gameRule(gameRule))
+                gameRuleValueArgument = ArgumentBuilderRequired.<CommandSource, Object>argument(argName, ArgumentTypeGameruleGeneric.gameRule(gameRule))
                         .executes(c -> {
-                            Object o = c.getArgument("value", Object.class);
+                            Object o = c.getArgument(argName, Object.class);
                             c.getSource().getWorld().getLevelData().getGameRules().setValue((GameRule) gameRule, o);
                             c.getSource().sendPacketToAllPlayers(() -> new PacketGameRule(c.getSource().getWorld().getLevelData().getGameRules()));
                             c.getSource().sendTranslatableMessage("command.commands.gamerule.set", gameRule.getKey(), o);
diff --git a/game/core/src/main/java/net/minecraft/core/world/Explosion.java b/game/core/src/main/java/net/minecraft/core/world/Explosion.java
index ed6faa107..7c94fdc0a 100644
--- a/game/core/src/main/java/net/minecraft/core/world/Explosion.java
+++ b/game/core/src/main/java/net/minecraft/core/world/Explosion.java
@@ -45,9 +45,13 @@ public class Explosion {
         this.explosionY = y;
         this.explosionZ = z;
         this.destroyBlocks = true;
-        if (!world.getGameRuleValue(GameRules.MOB_GRIEFING)) {
+        if (!world.getGameRuleValue(GameRules.DO_MOB_GRIEFING)) {
             this.destroyBlocks = exploder == null || exploder instanceof Player;
         }
+
+        if (!world.isClientSide && exploder == null && !world.getGameRuleValue(GameRules.DO_EXPLOSION_GRIEFING)) {
+            this.destroyBlocks = false;
+        }
     }
 
     public void explode() {
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 5f4aa2f35..b6a74bc6c 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
@@ -1377,8 +1377,8 @@ public abstract class World implements MutableWorldSource {
 
     public void removePlayer(final Entity entity) {
         entity.remove();
-        if (entity instanceof Player) {
-            this.players.remove((Player) entity);
+        if (entity instanceof Player player) {
+            this.players.remove(player);
             this.updateEnoughPlayersSleepingFlag(null);
         }
         final int i = entity.chunkCoordX;
diff --git a/game/core/src/main/java/net/minecraft/core/world/chunk/ChunkSection.java b/game/core/src/main/java/net/minecraft/core/world/chunk/ChunkSection.java
index d5b3e4ec5..c720a92c2 100644
--- a/game/core/src/main/java/net/minecraft/core/world/chunk/ChunkSection.java
+++ b/game/core/src/main/java/net/minecraft/core/world/chunk/ChunkSection.java
@@ -1,5 +1,6 @@
 package net.minecraft.core.world.chunk;
 
+import net.minecraft.core.block.Block;
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.data.registry.Registries;
 import net.minecraft.core.entity.Entity;
@@ -11,6 +12,7 @@ import net.minecraft.core.world.biome.Biome;
 import net.minecraft.core.world.data.ChunkUnsignedByteArray;
 import net.minecraft.core.world.pos.ChunkSectionTilePos;
 import net.minecraft.core.world.pos.ChunkSectionTilePosc;
+import net.minecraft.core.world.pos.TilePos;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.joml.primitives.AABBd;
@@ -65,6 +67,10 @@ public class ChunkSection {
         return ChunkSection.makeBiomeIndex(new ChunkSectionTilePos(x, y, z));
     }
 
+    public boolean isEmpty() {
+        return this.blocks == null;
+    }
+
     public int getBlockId(final @NotNull ChunkSectionTilePosc sectionPos) {
         if (!sectionPos.inBounds() || this.blocks == null) {
             return 0;
@@ -267,8 +273,30 @@ public class ChunkSection {
         }
     }
 
-    public void onLoad(World world) {
+    public void onLoad(@NotNull World world) {
         world.addLoadedEntities(this.entities);
+
+        if (this.isEmpty() || world.isClientSide || world.scheduledUpdatesAreImmediate) {
+            return;
+        }
+
+        final int blockX = this.chunk.pos.x * Chunk.CHUNK_SIZE_X;
+        final int blockZ = this.chunk.pos.z * Chunk.CHUNK_SIZE_Z;
+        final int sectionBlockY = this.yPosition * SECTION_SIZE_Y;
+        final ChunkSectionTilePos sectionPos = new ChunkSectionTilePos();
+        final TilePos tilePos = new TilePos();
+        for (int y = 0; y < SECTION_SIZE_Y; y++) {
+            for (int z = 0; z < Chunk.CHUNK_SIZE_Z; z++) {
+                for (int x = 0; x < Chunk.CHUNK_SIZE_X; x++) {
+                    final int id = this.getBlockId(sectionPos.set(x, y, z));
+                    if (id == 0) {
+                        continue;
+                    }
+                    final Block<?> block = Blocks.getBlock(id);
+                    block.onChunkLoad(world, tilePos.set(blockX + x, sectionBlockY + y, blockZ + z));
+                }
+            }
+        }
     }
 
     public void onUnload(World world) {
diff --git a/game/core/src/main/resources/assets/minecraft/lang/en_US/gamerules.lang b/game/core/src/main/resources/assets/minecraft/lang/en_US/gamerules.lang
index 48e921839..c3e4cf41a 100644
--- a/game/core/src/main/resources/assets/minecraft/lang/en_US/gamerules.lang
+++ b/game/core/src/main/resources/assets/minecraft/lang/en_US/gamerules.lang
@@ -12,21 +12,28 @@ gamerule.treecapitator.name=Treecapitator
 gamerule.treecapitator.desc=If enabled, trees will be instantly mined in their entirety when their bottom log is broken with an axe.
 gamerule.dwarf_mode.name=Dwarf Mode
 gamerule.dwarf_mode.desc=If enabled, all players will become one-block-tall dwarves.
-gamerule.mob_griefing.name=Mob Griefing
-gamerule.mob_griefing.desc=If disabled, explosions created by mobs will not destroy blocks.
+gamerule.do_mob_griefing.name=Mob Griefing
+gamerule.do_mob_griefing.desc=If disabled, explosions created by mobs will not destroy blocks.
+gamerule.do_explosion_griefing.name=Explosion Griefing
+gamerule.do_explosion_griefing.desc=If disabled, explosions without a source entity, such as TNT and embers, will not destroy blocks or start fires.
 gamerule.do_day_night_cycle.name=Day/Night Cycle
 gamerule.do_day_night_cycle.desc=If disabled, the day/night cycle is paused.
 gamerule.do_weather_cycle.name=Weather
 gamerule.do_weather_cycle.desc=If disabled, the weather will not randomly change.
-gamerule.do_fire_spread.name=Fire Spread
-gamerule.do_fire_spread.desc=If disabled, fire will not spread.
-gamerule.instant_healing.name=Instant Healing from Food
-gamerule.instant_healing.desc=If enabled, food will heal the player instantly as it did in older versions of BTA!.
-gamerule.acid_melt_rate.name=Acid Melt Rate
-gamerule.acid_melt_rate.desc=Ticks between each acid bank tick, durability melt, and post-exit drain pulse. Default matches fire damage (every 20 ticks). Lower values run faster.
+gamerule.fire_spread.name=Fire Spread
+gamerule.fire_spread.desc=Controls what fire can spread to. All - all blocks can be spread to; Foliage - only logs, leaves, and moss; None - no blocks can be spread to
+gamerule.fire_spread.value.all=All
+gamerule.fire_spread.value.foliage=Foliage
+gamerule.fire_spread.value.none=None
+gamerule.do_fire_burn.name=Fire Consumes Blocks
+gamerule.do_fire_burn.desc=If disabled, fire will not consume blocks.
+gamerule.do_lightning_burn.name=Lightning Starts Fires
+gamerule.do_lightning_burn.desc=If disabled, lightning strikes will not start fires.
+gamerule.do_instant_healing.name=Instant Healing from Food
+gamerule.do_instant_healing.desc=If enabled, food will heal the player instantly as it did in older versions of BTA!.
 gamerule.random_tick_speed.name=Random Tick Rate
 gamerule.random_tick_speed.desc=Determines how many blocks are randomly ticked per game tick.
-gamerule.dropped_item_despawn_delay.name=Dropped Item Despawn Delay
-gamerule.dropped_item_despawn_delay.desc=The number of ticks until an item dropped by a player despawns.
 gamerule.item_despawn_delay.name=Item Despawn Delay
-gamerule.item_despawn_delay.desc=The number of ticks until an item that was not dropped by a player despawns.
+gamerule.item_despawn_delay.desc=The number of seconds until an item despawns.
+gamerule.player_item_despawn_delay.name=Player Item Despawn Delay
+gamerule.player_item_despawn_delay.desc=The number of seconds until an item dropped by a player (manually or on death) despawns.
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 130067434..bb0bb3cc3 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
@@ -184,6 +184,7 @@ public class PlayerServer extends Player implements ContainerListener {
     @Override
 	public void tick()
     {
+        setupScale();
         playerController.tick();
         ticksOfInvuln--;
         containerMenu.broadcastChanges();
@@ -293,56 +294,6 @@ public class PlayerServer extends Player implements ContainerListener {
             }
         }
 
-        if (inPortal && !hasNoPhysics())
-        {
-            Dimension targetDim = ((BlockLogicPortal) Blocks.blocksList[portalID].getLogic()).targetDimension;
-            boolean netherAllowed = mcServer.propertyManager.getBooleanProperty("allow-nether", true);
-            boolean driftAllowed = mcServer.propertyManager.getBooleanProperty("allow-drift", false);
-            if ((netherAllowed && targetDim == Dimension.NETHER) || (driftAllowed && targetDim == Dimension.DRIFT) || (targetDim != Dimension.NETHER && targetDim != Dimension.DRIFT))
-            {
-                if (containerMenu != inventoryMenu)
-                {
-                    usePersonalCraftingInventory();
-                }
-                if (vehicle != null && !(vehicle instanceof MobPig))
-                {
-                    startRiding(vehicle);
-                }
-                else
-                {
-                    timeInPortal += 0.0125F;
-                    if (timeInPortal >= 1.0F || getGamemode().hasInstantPortalTravel())
-                    {
-                        timeInPortal = 1.0F;
-                        timeUntilPortal = 10;
-                        if (dimension == targetDim.id)
-                        {
-                            mcServer.playerList.sendPlayerToOtherDimension(this, 0, portalColor, true);
-                        }
-                        else
-                        {
-                            mcServer.playerList.sendPlayerToOtherDimension(this, targetDim.id, portalColor, true);
-                        }
-                    }
-                }
-                inPortal = false;
-            }
-        }
-        else
-        {
-            if (timeInPortal > 0.0F)
-            {
-                timeInPortal -= 0.05F;
-            }
-            if (timeInPortal < 0.0F)
-            {
-                timeInPortal = 0.0F;
-            }
-        }
-        if (timeUntilPortal > 0)
-        {
-            timeUntilPortal--;
-        }
         if (getHealth() != lastHealth)
         {
             playerNetServerHandler.sendPacket(new PacketSetHealth(getHealth()));
@@ -396,6 +347,45 @@ public class PlayerServer extends Player implements ContainerListener {
     @Override
 	public void onLivingUpdate()
     {
+        if (inPortal && !hasNoPhysics()) {
+            inPortal = false;
+            Dimension targetDim = ((BlockLogicPortal) Blocks.blocksList[portalID].getLogic()).targetDimension;
+            boolean netherAllowed = mcServer.propertyManager.getBooleanProperty("allow-nether", true);
+            boolean driftAllowed = mcServer.propertyManager.getBooleanProperty("allow-drift", false);
+            if ((netherAllowed && targetDim == Dimension.NETHER) || (driftAllowed && targetDim == Dimension.DRIFT) || (targetDim != Dimension.NETHER && targetDim != Dimension.DRIFT)) {
+                if (containerMenu != inventoryMenu) {
+                    usePersonalCraftingInventory();
+                }
+                if (vehicle != null && !(vehicle instanceof MobPig)) {
+                    startRiding(vehicle);
+                }
+                else {
+                    timeInPortal += 0.0125F;
+                    if (timeInPortal >= 1.0F || (getGamemode().hasInstantPortalTravel())) {
+                        timeInPortal = 1.0F;
+                        timeUntilPortal = PORTAL_DELAY_TICKS;
+                        if (dimension == targetDim.id) {
+                            mcServer.playerList.sendPlayerToOtherDimension(this, 0, portalColor, true);
+                        }
+                        else {
+                            mcServer.playerList.sendPlayerToOtherDimension(this, targetDim.id, portalColor, true);
+                        }
+                    }
+                }
+            }
+        }
+        else {
+            if (timeInPortal > 0.0F) {
+                timeInPortal -= 0.05F;
+            }
+            if (timeInPortal < 0.0F) {
+                timeInPortal = 0.0F;
+            }
+        }
+        if (timeUntilPortal > 0) {
+            timeUntilPortal--;
+        }
+
         super.onLivingUpdate();
         if (tickCount % 10 == 0){
             Item arrow = getNextArrow();
diff --git a/game/server/src/main/java/net/minecraft/server/net/PlayerList.java b/game/server/src/main/java/net/minecraft/server/net/PlayerList.java
index 5c0b843da..acfda0aac 100644
--- a/game/server/src/main/java/net/minecraft/server/net/PlayerList.java
+++ b/game/server/src/main/java/net/minecraft/server/net/PlayerList.java
@@ -279,18 +279,18 @@ public class PlayerList
 
     public void sendPlayerToOtherDimension(PlayerServer playerServer, int targetDim, DyeColor portalColor, boolean generatePortal)
     {
-        WorldServer worldserver = server.getDimensionWorld(playerServer.dimension);
+        WorldServer currentDimensionWorld = server.getDimensionWorld(playerServer.dimension);
         Dimension lastDim = Dimension.getDimensionList().get(playerServer.dimension);
         Dimension newDim = Dimension.getDimensionList().get(targetDim);
 
         Entity mount = playerServer.vehicle instanceof MobPig ? (Entity) playerServer.vehicle : null;
 
         playerServer.dimension = targetDim;
-        WorldServer worldserver1 = server.getDimensionWorld(playerServer.dimension);
-        playerServer.playerNetServerHandler.sendPacket(new PacketRespawn((byte) playerServer.dimension, (byte) Registries.WORLD_TYPES.getNumericIdOfItem(worldserver1.getWorldType())));
-        worldserver.removePlayer(playerServer);
+        WorldServer targetDimensionWorld = server.getDimensionWorld(playerServer.dimension);
+        playerServer.playerNetServerHandler.sendPacket(new PacketRespawn((byte) playerServer.dimension, (byte) Registries.WORLD_TYPES.getNumericIdOfItem(targetDimensionWorld.getWorldType())));
+        currentDimensionWorld.removePlayer(playerServer);
         if (mount != null) {
-            worldserver.removePlayer(mount);
+            currentDimensionWorld.removePlayer(mount);
         }
         playerServer.removed = false;
         double x = playerServer.x;
@@ -313,25 +313,25 @@ public class PlayerList
         playerServer.dimensionEnterCoordinate = newCoordinates;
         if (playerServer.isAlive())
         {
-            worldserver.updateEntityWithOptionalForce(playerServer, false);
+            currentDimensionWorld.updateEntityWithOptionalForce(playerServer, false);
         }
 
         if (playerServer.isAlive())
         {
-            worldserver1.entityJoinedWorld(playerServer);
+            targetDimensionWorld.entityJoinedWorld(playerServer);
             playerServer.teleport(x, playerServer.y, z, playerServer.yRot, playerServer.xRot);
-            worldserver1.updateEntityWithOptionalForce(playerServer, false);
+            targetDimensionWorld.updateEntityWithOptionalForce(playerServer, false);
             if (generatePortal){
-                worldserver1.getChunkProvider().chunkLoadOverride = true;
-                (new PortalHandler()).teleportEntity(worldserver1, playerServer, portalColor, lastDim, newDim);
-                worldserver1.getChunkProvider().chunkLoadOverride = false;
+                targetDimensionWorld.getChunkProvider().chunkLoadOverride = true;
+                (new PortalHandler()).teleportEntity(targetDimensionWorld, playerServer, portalColor, lastDim, newDim);
+                targetDimensionWorld.getChunkProvider().chunkLoadOverride = false;
             }
         }
         syncPlayerDimension(playerServer);
         playerServer.playerNetServerHandler.teleportAndRotate(playerServer.x, playerServer.y, playerServer.z, playerServer.yRot, playerServer.xRot);
         server.playerList.sendPacketToAllPlayers(new PacketPlayerGamemode(playerServer.id, playerServer.gamemode.getId()));
-        playerServer.setWorld(worldserver1);
-        setTime(playerServer, worldserver1);
+        playerServer.setWorld(targetDimensionWorld);
+        setTime(playerServer, targetDimensionWorld);
         initializePlayerObject(playerServer);
         playerServer.playerNetServerHandler.sendPacket(new PacketGameRule(server.getDimensionWorld(0).getLevelData().getGameRules()));
         playerServer.playerNetServerHandler.sendPacket(new PacketSetHotbarOffset(playerServer.inventory.getHotbarOffset()));
@@ -339,9 +339,9 @@ public class PlayerList
 
         if (mount != null && playerServer.isAlive()) {
             mount.removed = false;
-            mount.world = worldserver1;
+            mount.world = targetDimensionWorld;
             mount.moveTo(playerServer.x, playerServer.y, playerServer.z, playerServer.yRot, mount.xRot);
-            worldserver1.entityJoinedWorld(mount);
+            targetDimensionWorld.entityJoinedWorld(mount);
             playerServer.playerNetServerHandler.sendPacket(new PacketSetRiding(playerServer, mount));
         }
     }
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 ae6bce942..72f6faebd 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
@@ -160,8 +160,8 @@ public class PacketHandlerServer extends PacketHandler
         }
 
         // In a entity vehicle
-        if (this.playerEntity.vehicle instanceof Entity) {
-            this.playerEntity.vehicle.positionRider();
+        if (this.playerEntity.vehicle instanceof Entity vehicle) {
+            vehicle.positionRider();
             double xd = 0.0D;
             double zd = 0.0D;
             if (packetMovePlayer.hasPosition && packetMovePlayer.y == -999D) {
@@ -176,8 +176,8 @@ public class PacketHandlerServer extends PacketHandler
             this.playerEntity.zd = zd;
             // TODO: Is this needing? It does allow for fast-tick exploits.
             // Update: Removing it breaks boats, so keep it for now
-            worldserver.updateEntityWithOptionalForce(((Entity) this.playerEntity.vehicle), false);
-            this.playerEntity.vehicle.positionRider();
+            worldserver.updateEntityWithOptionalForce(vehicle, false);
+            vehicle.positionRider();
             this.mcServer.playerList.onPlayerMoved(this.playerEntity);
             return;
         }
@@ -330,7 +330,8 @@ public class PacketHandlerServer extends PacketHandler
         double playerDistY = this.playerEntity.y - (y + 0.5);
         double playerDistZ = this.playerEntity.z - (z + 0.5);
         double playerDist = playerDistX * playerDistX + playerDistY * playerDistY + playerDistZ * playerDistZ;
-        if (playerDist > 44.0) {
+        double maxDist = this.playerEntity.getGamemode().getBlockReachDistance() + 2.5;
+        if (playerDist > maxDist * maxDist) {
             return;
         }
 
@@ -373,6 +374,8 @@ public class PacketHandlerServer extends PacketHandler
         WorldServer worldserver = this.mcServer.getDimensionWorld(this.playerEntity.dimension);
         ItemStack itemstack = this.playerEntity.inventory.getCurrentItem();
         boolean ignoreSpawnProtection = this.mcServer.spawnProtectionRange <= 0 || worldserver.dimension.id != 0 || this.mcServer.playerList.isOp(this.playerEntity.uuid);
+        double maxPlaceDist = this.playerEntity.getGamemode().getBlockReachDistance() + 3.5;
+        double maxPlaceDistSq = maxPlaceDist * maxPlaceDist;
         switch (packet.type) {
             case PacketUseOrPlaceItemStack.TYPE_USE_OR_PLACE_ON_TILE: {
                 int x = packet.xPosition;
@@ -387,7 +390,7 @@ public class PacketHandlerServer extends PacketHandler
                 if (i1 > j1) {
                     j1 = i1;
                 }
-                if (this.hasMoved && this.playerEntity.distanceToSqr((double) x + 0.5D, (double) y + 0.5D, (double) z + 0.5D) < 64D && (j1 > this.mcServer.spawnProtectionRange || ignoreSpawnProtection)) {
+                if (this.hasMoved && this.playerEntity.distanceToSqr((double) x + 0.5D, (double) y + 0.5D, (double) z + 0.5D) < maxPlaceDistSq && (j1 > this.mcServer.spawnProtectionRange || ignoreSpawnProtection)) {
                     this.playerEntity.playerController.useOrPlaceItemStackOnTile(this.playerEntity, worldserver, itemstack, x, y, z, direction.side(), xPlaced, yPlaced);
                 }
                 this.playerEntity.playerNetServerHandler.sendPacket(new PacketBlockUpdate(x, y, z, worldserver));
@@ -417,7 +420,7 @@ public class PacketHandlerServer extends PacketHandler
                 if (i1 > j1) {
                     j1 = i1;
                 }
-                if (this.hasMoved && this.playerEntity.distanceToSqr((double) x + 0.5D, (double) y + 0.5D, (double) z + 0.5D) < 64D && (j1 > this.mcServer.spawnProtectionRange || ignoreSpawnProtection)) {
+                if (this.hasMoved && this.playerEntity.distanceToSqr((double) x + 0.5D, (double) y + 0.5D, (double) z + 0.5D) < maxPlaceDistSq && (j1 > this.mcServer.spawnProtectionRange || ignoreSpawnProtection)) {
                     this.playerEntity.playerController.placeItemStackOnTile(this.playerEntity, worldserver, itemstack, x, y, z, direction.side(), xPlaced, yPlaced);
                 }
                 this.playerEntity.playerNetServerHandler.sendPacket(new PacketBlockUpdate(x, y, z, worldserver));
@@ -619,7 +622,8 @@ public class PacketHandlerServer extends PacketHandler
         // real problem is probably entity ids being synced incorrectly, but I have no idea what to do with that
         if(targetEntity == null) return;
 
-        if (this.playerEntity.distanceToSqr(targetEntity) < 36D) {
+        double maxEntityDist = this.playerEntity.getGamemode().getEntityReachDistance() + 3.0;
+        if (this.playerEntity.distanceToSqr(targetEntity) < maxEntityDist * maxEntityDist) {
             boolean canAttack = this.playerEntity.canEntityBeSeen(targetEntity);
 
             if (!canAttack) {
