diff --git a/game/client/src/main/java/net/minecraft/client/option/enums/RenderScale.java b/game/client/src/main/java/net/minecraft/client/option/enums/RenderScale.java
index 8653a5e08..975813103 100644
--- a/game/client/src/main/java/net/minecraft/client/option/enums/RenderScale.java
+++ b/game/client/src/main/java/net/minecraft/client/option/enums/RenderScale.java
@@ -2,11 +2,14 @@ package net.minecraft.client.option.enums;
 
 public enum RenderScale {
 
+	SCALE_0625(0.0625, false),
+	SCALE_125(0.125, false),
 	SCALE_25(0.25, false),
 	SCALE_50(0.5, false),
 	SCALE_100(1.0, true),
-	SCALE_200(2.0, true);
-	
+	SCALE_200(2.0, true),
+	SCALE_400(4.0, true);
+
 	public final double scale;
 	public final boolean useLinearFiltering;
 	
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelButtonPainted.java b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelButtonPainted.java
new file mode 100644
index 000000000..d8fa8116d
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelButtonPainted.java
@@ -0,0 +1,24 @@
+package net.minecraft.client.render.block.model;
+
+import net.minecraft.client.render.texture.stitcher.IconCoordinate;
+import net.minecraft.client.render.texture.stitcher.TextureRegistry;
+import net.minecraft.core.block.Block;
+import net.minecraft.core.block.BlockLogic;
+import net.minecraft.core.util.helper.DyeColor;
+import net.minecraft.core.util.helper.Side;
+import org.jetbrains.annotations.Nullable;
+
+public class BlockModelButtonPainted<T extends BlockLogic> extends BlockModelStandard<T> {
+    protected final IconCoordinate[] texCoords = new IconCoordinate[DyeColor.COLOR_AMOUNT];
+    public BlockModelButtonPainted(final Block<T> block) {
+        super(block);
+        for (final DyeColor c : DyeColor.blockOrderedColors()){
+            this.texCoords[c.blockMeta] = TextureRegistry.getTexture("minecraft:block/planks/" + c.colorID);
+        }
+    }
+
+    @Override
+    public @Nullable IconCoordinate getBlockTextureFromSideAndMetadata(final Side side, final int meta) {
+        return this.texCoords[(meta >> 4) & DyeColor.MASK_COLOR];
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelDispatcher.java b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelDispatcher.java
index a1b152b1c..a25d6f762 100644
--- a/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelDispatcher.java
+++ b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelDispatcher.java
@@ -475,6 +475,8 @@ public final class BlockModelDispatcher
         final float height = 0.125F;
         final float depth = 0.125F;
         addDispatch(new BlockModelStandard<>(Blocks.BUTTON_STONE).setAllTextures(BLOCK_TEXTURES, "minecraft:block/stone").withCustomItemBounds(0.5F - width, 0.5F - height, 0.5F - depth, 0.5F + width, 0.5F + height, 0.5F + depth));
+        addDispatch(new BlockModelStandard<>(Blocks.BUTTON_PLANKS).setAllTextures(BLOCK_TEXTURES, "minecraft:block/planks/oak").withCustomItemBounds(0.5F - width, 0.5F - height, 0.5F - depth, 0.5F + width, 0.5F + height, 0.5F + depth));
+        addDispatch(new BlockModelButtonPainted<>(Blocks.BUTTON_PLANKS_PAINTED).setAllTextures(BLOCK_TEXTURES, "minecraft:block/planks/oak").withCustomItemBounds(0.5F - width, 0.5F - height, 0.5F - depth, 0.5F + width, 0.5F + height, 0.5F + depth));
 
         addDispatch(new BlockModelLever<>(Blocks.LEVER_COBBLE_STONE).setAllTextures(BLOCK_TEXTURES, "minecraft:block/lever_cobbled_stone"));
 
@@ -484,6 +486,7 @@ public final class BlockModelDispatcher
         addDispatch(new BlockModelStandard<>(Blocks.PRESSURE_PLATE_STONE).setAllTextures(BLOCK_TEXTURES, "minecraft:block/stone").withCustomItemBounds(0.5F - f, 0.5F - f1, 0.5F - f2, 0.5F + f, 0.5F + f1, 0.5F + f2));
         addDispatch(new BlockModelStandard<>(Blocks.PRESSURE_PLATE_PLANKS_OAK).setAllTextures(BLOCK_TEXTURES, "minecraft:block/planks/oak").withCustomItemBounds(0.5F - f, 0.5F - f1, 0.5F - f2, 0.5F + f, 0.5F + f1, 0.5F + f2));
         addDispatch(new BlockModelStandard<>(Blocks.PRESSURE_PLATE_COBBLE_STONE).setAllTextures(BLOCK_TEXTURES, "minecraft:block/cobbled_stone").withCustomItemBounds(0.5F - f, 0.5F - f1, 0.5F - f2, 0.5F + f, 0.5F + f1, 0.5F + f2));
+        addDispatch(new BlockModelPressurePlatePainted<>(Blocks.PRESSURE_PLATE_PLANKS_OAK_PAINTED).setAllTextures(BLOCK_TEXTURES, "minecraft:block/planks/oak").withCustomItemBounds(0.5F - f, 0.5F - f1, 0.5F - f2, 0.5F + f, 0.5F + f1, 0.5F + f2));
 
         addDispatch(new BlockModelVeryRotatable<>(Blocks.MOTION_SENSOR_IDLE)
             .setAllTextures(BLOCK_TEXTURES, "minecraft:block/motion_sensor/side")
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelPressurePlatePainted.java b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelPressurePlatePainted.java
new file mode 100644
index 000000000..7f08fa84c
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelPressurePlatePainted.java
@@ -0,0 +1,24 @@
+package net.minecraft.client.render.block.model;
+
+import net.minecraft.client.render.texture.stitcher.IconCoordinate;
+import net.minecraft.client.render.texture.stitcher.TextureRegistry;
+import net.minecraft.core.block.Block;
+import net.minecraft.core.block.BlockLogic;
+import net.minecraft.core.util.helper.DyeColor;
+import net.minecraft.core.util.helper.Side;
+import org.jetbrains.annotations.Nullable;
+
+public class BlockModelPressurePlatePainted<T extends BlockLogic> extends BlockModelStandard<T> {
+    protected final IconCoordinate[] texCoords = new IconCoordinate[DyeColor.COLOR_AMOUNT];
+    public BlockModelPressurePlatePainted(final Block<T> block) {
+        super(block);
+        for (final DyeColor c : DyeColor.blockOrderedColors()){
+            this.texCoords[c.blockMeta] = TextureRegistry.getTexture("minecraft:block/planks/" + c.colorID);
+        }
+    }
+
+    @Override
+    public @Nullable IconCoordinate getBlockTextureFromSideAndMetadata(final Side side, final int meta) {
+        return this.texCoords[(meta >> 4) & DyeColor.MASK_COLOR];
+    }
+}
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 9f53e013a..1664cf66c 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
@@ -10,6 +10,7 @@ import net.minecraft.core.block.BlockLogic;
 import net.minecraft.core.block.BlockLogicWireRedstone;
 import net.minecraft.core.enums.LightLayer;
 import net.minecraft.core.util.helper.Color;
+import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.world.WorldSource;
 import org.jetbrains.annotations.NotNull;
 
@@ -53,32 +54,32 @@ public class BlockModelWireRedstone<T extends BlockLogic> extends BlockModelStan
         float blue = color.getBlue() / 255.0f;
 
         tessellator.setColorOpaque_F(brightness * red, brightness * green, brightness * blue);
-        boolean west = BlockLogicWireRedstone.shouldConnectTo(worldSource, x - 1, y, z, 1) || !worldSource.isBlockNormalCube(x - 1, y, z) && BlockLogicWireRedstone.shouldConnectTo(worldSource, x - 1, y - 1, z, -1);
-        boolean east = BlockLogicWireRedstone.shouldConnectTo(worldSource, x + 1, y, z, 3) || !worldSource.isBlockNormalCube(x + 1, y, z) && BlockLogicWireRedstone.shouldConnectTo(worldSource, x + 1, y - 1, z, -1);
-        boolean north = BlockLogicWireRedstone.shouldConnectTo(worldSource, x, y, z - 1, 2) || !worldSource.isBlockNormalCube(x, y, z - 1) && BlockLogicWireRedstone.shouldConnectTo(worldSource, x, y - 1, z - 1, -1);
-        boolean south = BlockLogicWireRedstone.shouldConnectTo(worldSource, x, y, z + 1, 0) || !worldSource.isBlockNormalCube(x, y, z + 1) && BlockLogicWireRedstone.shouldConnectTo(worldSource, x, y - 1, z + 1, -1);
+        boolean west = BlockLogicWireRedstone.shouldConnectTo(worldSource, x - 1, y, z, Side.WEST) || !worldSource.isBlockNormalCube(x - 1, y, z) && BlockLogicWireRedstone.shouldConnectToDiagonal(worldSource, x - 1, y - 1, z, Side.NONE, Side.BOTTOM);
+        boolean east = BlockLogicWireRedstone.shouldConnectTo(worldSource, x + 1, y, z, Side.EAST) || !worldSource.isBlockNormalCube(x + 1, y, z) && BlockLogicWireRedstone.shouldConnectToDiagonal(worldSource, x + 1, y - 1, z, Side.NONE, Side.BOTTOM);
+        boolean north = BlockLogicWireRedstone.shouldConnectTo(worldSource, x, y, z - 1, Side.NORTH) || !worldSource.isBlockNormalCube(x, y, z - 1) && BlockLogicWireRedstone.shouldConnectToDiagonal(worldSource, x, y - 1, z - 1, Side.NONE, Side.BOTTOM);
+        boolean south = BlockLogicWireRedstone.shouldConnectTo(worldSource, x, y, z + 1, Side.SOUTH) || !worldSource.isBlockNormalCube(x, y, z + 1) && BlockLogicWireRedstone.shouldConnectToDiagonal(worldSource, x, y - 1, z + 1, Side.NONE, Side.BOTTOM);
         boolean westUp = false;
         boolean eastUp = false;
         boolean northUp = false;
         boolean southUp = false;
         if(!worldSource.isBlockOpaqueCube(x, y + 1, z))
         {
-            if(worldSource.isBlockNormalCube(x - 1, y, z) && BlockLogicWireRedstone.shouldConnectTo(worldSource, x - 1, y + 1, z, -1))
+            if(worldSource.isBlockNormalCube(x - 1, y, z) && BlockLogicWireRedstone.shouldConnectToDiagonal(worldSource, x - 1, y + 1, z, Side.NONE, Side.TOP))
             {
                 west = true;
                 westUp = true;
             }
-            if(worldSource.isBlockNormalCube(x + 1, y, z) && BlockLogicWireRedstone.shouldConnectTo(worldSource, x + 1, y + 1, z, -1))
+            if(worldSource.isBlockNormalCube(x + 1, y, z) && BlockLogicWireRedstone.shouldConnectToDiagonal(worldSource, x + 1, y + 1, z, Side.NONE, Side.TOP))
             {
                 east = true;
                 eastUp = true;
             }
-            if(worldSource.isBlockNormalCube(x, y, z - 1) && BlockLogicWireRedstone.shouldConnectTo(worldSource, x, y + 1, z - 1, -1))
+            if(worldSource.isBlockNormalCube(x, y, z - 1) && BlockLogicWireRedstone.shouldConnectToDiagonal(worldSource, x, y + 1, z - 1, Side.NONE, Side.TOP))
             {
                 north = true;
                 northUp = true;
             }
-            if(worldSource.isBlockNormalCube(x, y, z + 1) && BlockLogicWireRedstone.shouldConnectTo(worldSource, x, y + 1, z + 1, -1))
+            if(worldSource.isBlockNormalCube(x, y, z + 1) && BlockLogicWireRedstone.shouldConnectToDiagonal(worldSource, x, y + 1, z + 1, Side.NONE, Side.TOP))
             {
                 south = true;
                 southUp = true;
diff --git a/game/client/src/main/resources/credits.txt b/game/client/src/main/resources/credits.txt
index 1dc4b5375..41fede447 100644
--- a/game/client/src/main/resources/credits.txt
+++ b/game/client/src/main/resources/credits.txt
@@ -39,6 +39,8 @@ Tocinin
 Vivian (@vivianfox.)
 Zaraz7
 ph
+Rikai
+Celeste
 
 !Thank you for playing
 !Better than Adventure!
\ No newline at end of file
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicButton.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicButton.java
index ee8c08f96..a85ea86a9 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicButton.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicButton.java
@@ -9,6 +9,7 @@ import net.minecraft.core.item.Item;
 import net.minecraft.core.item.tag.ItemTags;
 import net.minecraft.core.sound.SoundCategory;
 import net.minecraft.core.util.helper.Direction;
+import net.minecraft.core.util.helper.DyeColor;
 import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.util.phys.AABB;
 import net.minecraft.core.world.World;
@@ -18,7 +19,7 @@ import org.jetbrains.annotations.Nullable;
 
 import java.util.Random;
 
-public class BlockLogicButton extends BlockLogic
+public class BlockLogicButton extends BlockLogic implements IPaintable
 {
     public static final int MASK_POWERED = 0b0000_1000;
     public static final int MASK_DIRECTION = 0b0000_0111;
@@ -290,7 +291,7 @@ public class BlockLogicButton extends BlockLogic
         {
             return true;
         }
-        world.setBlockMetadataWithNotify(x, y, z, direction | MASK_POWERED);
+        world.setBlockMetadataWithNotify(x, y, z, (meta & ~MASK_DIRECTION) | direction | MASK_POWERED);
         world.markBlocksDirty(x, y, z, x, y, z);
         world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS, (double) x + 0.5D, (double) y + 0.5D, (double) z + 0.5D, "random.click", 0.3F, 0.6F);
         world.notifyBlocksOfNeighborChange(x, y, z, block.id());
@@ -430,4 +431,20 @@ public class BlockLogicButton extends BlockLogic
         world.playSoundEffect(null, SoundCategory.WORLD_SOUNDS, (double) x + 0.5D, (double) y + 0.5D, (double) z + 0.5D, "random.click", 0.3F, 0.5F);
         world.markBlocksDirty(x, y, z, x, y, z);
     }
+
+    @Override
+    public boolean canBePainted() {
+        return id() == Blocks.BUTTON_PLANKS.id();
+    }
+
+    @Override
+    public void setColor(World world, int x, int y, int z, DyeColor color) {
+        final int meta = world.getBlockMetadata(x, y, z);
+        world.setBlockAndMetadataRaw(x, y, z, Blocks.BUTTON_PLANKS_PAINTED.id(), meta);
+        world.setBlockMetadata(x, y, z, meta);
+        Blocks.BUTTON_PLANKS_PAINTED.getLogic().setColor(world, x, y, z, color);
+        if ((meta & MASK_POWERED) != 0) {
+            world.scheduleBlockUpdate(x, y, z, Blocks.BUTTON_PLANKS_PAINTED.id(), tickDelay());
+        }
+    }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicButtonPainted.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicButtonPainted.java
new file mode 100644
index 000000000..1b9d8fa47
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicButtonPainted.java
@@ -0,0 +1,50 @@
+package net.minecraft.core.block;
+
+import net.minecraft.core.entity.player.Player;
+import net.minecraft.core.item.ItemStack;
+import net.minecraft.core.util.helper.DyeColor;
+import net.minecraft.core.util.helper.Side;
+import net.minecraft.core.world.World;
+import org.jetbrains.annotations.Nullable;
+
+public class BlockLogicButtonPainted extends BlockLogicButton implements IPainted{
+    public BlockLogicButtonPainted(Block<?> block) {
+        super(block);
+    }
+
+    @Override
+    public boolean canBePainted() {
+        return true;
+    }
+
+    @Override
+    public DyeColor fromMetadata(int meta) {
+        return DyeColor.colorFromBlockMeta(meta >> 4);
+    }
+
+    @Override
+    public int toMetadata(DyeColor color) {
+        return color.blockMeta << 4;
+    }
+
+    @Override
+    public int stripColorFromMetadata(int meta) {
+        return meta & (MASK_POWERED | MASK_DIRECTION);
+    }
+
+    @Override
+    public void removeDye(World world, int x, int y, int z) {
+        world.setBlockWithNotify(x, y, z, Blocks.BUTTON_PLANKS.id());
+    }
+
+    @Override
+    public int getPlacedBlockMetadata(@Nullable final Player player, final ItemStack stack, final World world, final int x, final int y, final int z, final Side side, final double xPlaced, final double yPlaced) {
+        return stack.getMetadata() & ~(MASK_POWERED | MASK_DIRECTION);
+    }
+
+    @Override
+    public void setColor(World world, int x, int y, int z, DyeColor color) {
+        final int meta = world.getBlockMetadata(x, y, z);
+        world.setBlockMetadataWithNotify(x, y, z, stripColorFromMetadata(meta) | toMetadata(color));
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicPressurePlate.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicPressurePlate.java
index 7575767fc..2edc7d431 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicPressurePlate.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicPressurePlate.java
@@ -3,6 +3,7 @@ package net.minecraft.core.block;
 import net.minecraft.core.block.material.Material;
 import net.minecraft.core.entity.Entity;
 import net.minecraft.core.enums.EnumDropCause;
+import net.minecraft.core.util.helper.DyeColor;
 import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.sound.SoundCategory;
 import net.minecraft.core.world.WorldSource;
@@ -13,10 +14,10 @@ import org.jetbrains.annotations.NotNull;
 import java.util.List;
 import java.util.Random;
 
-public class BlockLogicPressurePlate<T extends Entity> extends BlockLogic
+public class BlockLogicPressurePlate<T extends Entity> extends BlockLogic implements IPaintable
 {
     public static final int MASK_POWERED = 0b0000_0001;
-    public static final int MASK_CEILING = 0b0000_0010;
+    public static final int MASK_SIDE = 0b0000_1110;
     private final Class<T> triggerMobClass;
 
 	public BlockLogicPressurePlate(Block<?> block, Class<T> mobType, Material material)
@@ -55,7 +56,12 @@ public class BlockLogicPressurePlate<T extends Entity> extends BlockLogic
     @Override
 	public boolean canPlaceBlockAt(World world, int x, int y, int z)
     {
-        return world.canPlaceOnSurfaceOfBlock(x, y - 1, z) || world.isBlockNormalCube(x, y + 1, z);
+        return world.canPlaceOnSurfaceOfBlock(x, y - 1, z) ||
+            world.isBlockNormalCube(x, y + 1, z) ||
+            world.isBlockNormalCube(x + 1, y, z) ||
+            world.isBlockNormalCube(x - 1, y, z) ||
+            world.isBlockNormalCube(x, y, z + 1) ||
+            world.isBlockNormalCube(x, y, z - 1);
     }
 
     @Override
@@ -97,10 +103,20 @@ public class BlockLogicPressurePlate<T extends Entity> extends BlockLogic
     {
         int meta = world.getBlockMetadata(x, y, z);
         boolean isPressed = isPressed(meta);
-        boolean isCeiling = onCeiling(meta);
+        Side side = sideFromMeta(world.getBlockMetadata(x, y, z));
         boolean isSteppedOn = false;
-        float f = 0.125F;
-        List<T> list = world.getEntitiesWithinAABB(triggerMobClass, AABB.getTemporaryBB((float)x + f, y + (isCeiling ? .85f : 0f), (float)z + f, (float)(x + 1) - f, (double)y + (isCeiling ? 1 : 0.25D), (float)(z + 1) - f));;
+        float pixel = 0.125F;
+        List<T> list;
+        switch (sideFromMeta(meta)) {
+            case BOTTOM: list = world.getEntitiesWithinAABB(triggerMobClass, AABB.getTemporaryBB(pixel, 0.0F, pixel, 1.0F - pixel, (isPressed ? pixel/2f : pixel), 1.0F - pixel).move(x, y, z)); break;
+            case TOP: list = world.getEntitiesWithinAABB(triggerMobClass, AABB.getTemporaryBB(pixel, 1f - (isPressed ? pixel/2f : pixel), pixel, 1.0F - pixel, 1f, 1.0F - pixel).move(x, y, z)); break;
+            case NORTH: list = world.getEntitiesWithinAABB(triggerMobClass, AABB.getTemporaryBB(pixel, pixel, 0, 1 - pixel, 1 - pixel, (isPressed ? pixel/2f : pixel)).move(x, y, z)); break;
+            case SOUTH: list = world.getEntitiesWithinAABB(triggerMobClass, AABB.getTemporaryBB(pixel, pixel, 1f - (isPressed ? pixel/2f : pixel), 1 - pixel, 1 - pixel, 1).move(x, y, z)); break;
+            case WEST: list = world.getEntitiesWithinAABB(triggerMobClass, AABB.getTemporaryBB(0, pixel, pixel, (isPressed ? pixel/2f : pixel), 1 - pixel, 1 - pixel).move(x, y, z)); break;
+            case EAST:
+            default: list = world.getEntitiesWithinAABB(triggerMobClass, AABB.getTemporaryBB(1 - (isPressed ? pixel/2f : pixel), pixel, pixel, 1, 1 - pixel, 1 - pixel).move(x, y, z)); break;
+        }
+
         if(list != null && !list.isEmpty())
         {
             isSteppedOn = true;
@@ -109,7 +125,7 @@ public class BlockLogicPressurePlate<T extends Entity> extends BlockLogic
         {
             world.setBlockMetadataWithNotify(x, y, z, meta | MASK_POWERED);
             world.notifyBlocksOfNeighborChange(x, y, z, id());
-            world.notifyBlocksOfNeighborChange(x, y + (isCeiling ? 1 : -1), z, id());
+            world.notifyBlocksOfNeighborChange(x + side.getOffsetX(), y + side.getOffsetY(), z + side.getOffsetZ(), id());
             world.markBlocksDirty(x, y, z, x, y, z);
             world.playSoundEffect(null, SoundCategory.WORLD_SOUNDS, (double)x + 0.5D, (double)y + 0.1D, (double)z + 0.5D, "random.click", 0.3F, 0.6F);
         }
@@ -117,7 +133,7 @@ public class BlockLogicPressurePlate<T extends Entity> extends BlockLogic
         {
             world.setBlockMetadataWithNotify(x, y, z, meta & ~MASK_POWERED);
             world.notifyBlocksOfNeighborChange(x, y, z, id());
-            world.notifyBlocksOfNeighborChange(x, y + (isCeiling ? 1 : -1), z, id());
+            world.notifyBlocksOfNeighborChange(x + side.getOffsetX(), y + side.getOffsetY(), z + side.getOffsetZ(), id());
             world.markBlocksDirty(x, y, z, x, y, z);
             world.playSoundEffect(null, SoundCategory.WORLD_SOUNDS, (double)x + 0.5D, (double)y + 0.1D, (double)z + 0.5D, "random.click", 0.3F, 0.5F);
         }
@@ -130,11 +146,11 @@ public class BlockLogicPressurePlate<T extends Entity> extends BlockLogic
     @Override
     public void onBlockRemoved(World world, int x, int y, int z, int data)
     {
-        boolean isCeiling = onCeiling(world.getBlockMetadata(x, y, z));
         if(isPressed(data))
         {
+            Side side = sideFromMeta(world.getBlockMetadata(x, y, z));
             world.notifyBlocksOfNeighborChange(x, y, z, id());
-            world.notifyBlocksOfNeighborChange(x, y + (isCeiling ? 1 : -1), z, id());
+            world.notifyBlocksOfNeighborChange(x + side.getOffsetX(), y + side.getOffsetY(), z + side.getOffsetZ(), id());
         }
         super.onBlockRemoved(world, x, y, z, data);
     }
@@ -144,22 +160,15 @@ public class BlockLogicPressurePlate<T extends Entity> extends BlockLogic
     {
         int meta = world.getBlockMetadata(x, y, z);
         boolean isPressed = isPressed(meta);
-        boolean isCeiling = onCeiling(meta);
         float pixel = 0.0625F;
-        if(isPressed)
-        {
-            if (isCeiling){
-                return AABB.getTemporaryBB(pixel, 1f - 0.03125F, pixel, 1.0F - pixel, 1f, 1.0F - pixel);
-            } else {
-                return AABB.getTemporaryBB(pixel, 0.0F, pixel, 1.0F - pixel, 0.03125F, 1.0F - pixel);
-            }
-        } else
-        {
-            if (isCeiling){
-                return AABB.getTemporaryBB(pixel, 1f - 0.0625F, pixel, 1.0F - pixel, 1f, 1.0F - pixel);
-            } else {
-                return AABB.getTemporaryBB(pixel, 0.0F, pixel, 1.0F - pixel, 0.0625F, 1.0F - pixel);
-            }
+        switch (sideFromMeta(meta)) {
+            case BOTTOM: return AABB.getTemporaryBB(pixel, 0.0F, pixel, 1.0F - pixel, (isPressed ? pixel/2f : pixel), 1.0F - pixel);
+            case TOP: return AABB.getTemporaryBB(pixel, 1f - (isPressed ? pixel/2f : pixel), pixel, 1.0F - pixel, 1f, 1.0F - pixel);
+            case NORTH: return AABB.getTemporaryBB(pixel, pixel, 0, 1 - pixel, 1 - pixel, (isPressed ? pixel/2f : pixel));
+            case SOUTH: return AABB.getTemporaryBB(pixel, pixel, 1f - (isPressed ? pixel/2f : pixel), 1 - pixel, 1 - pixel, 1);
+            case WEST: return AABB.getTemporaryBB(0, pixel, pixel, (isPressed ? pixel/2f : pixel), 1 - pixel, 1 - pixel);
+            case EAST:
+            default: return AABB.getTemporaryBB(1 - (isPressed ? pixel/2f : pixel), pixel, pixel, 1, 1 - pixel, 1 - pixel);
         }
     }
 
@@ -174,9 +183,8 @@ public class BlockLogicPressurePlate<T extends Entity> extends BlockLogic
     public boolean getDirectSignal(World world, int x, int y, int z, Side side)
     {
         int meta = world.getBlockMetadata(x, y, z);
-        boolean isCeiling = onCeiling(meta);
         if(isPressed(world.getBlockMetadata(x, y, z))) {
-            return side == (isCeiling ? Side.BOTTOM : Side.TOP);
+            return side == sideFromMeta(meta).getOpposite();
         } else {
             return false;
         }
@@ -185,8 +193,12 @@ public class BlockLogicPressurePlate<T extends Entity> extends BlockLogic
         return (meta & MASK_POWERED) != 0;
     }
 
-    public static boolean onCeiling(int meta) {
-        return (meta & MASK_CEILING) != 0;
+    public static int setSide(int meta, @NotNull Side side) {
+        return (meta & ~MASK_SIDE) | (side.getId() << 1);
+    }
+
+    public static @NotNull Side sideFromMeta(int meta) {
+        return Side.getSideById((meta & MASK_SIDE) >> 1);
     }
 
     @Override
@@ -202,8 +214,27 @@ public class BlockLogicPressurePlate<T extends Entity> extends BlockLogic
     }
     @Override
     public void onBlockPlacedOnSide(World world, int x, int y, int z, @NotNull Side side, double xPlaced, double yPlaced) {
-        if (!world.canPlaceOnSurfaceOfBlock(x, y - 1 , z) || (side.isHorizontal() && yPlaced > 0.5 && world.isBlockNormalCube(x, y + 1, z))){
-            world.setBlockMetadata(x, y, z, world.getBlockMetadata(x, y, z) | MASK_CEILING);
+        side = side.getOpposite();
+        if (world.isBlockNormalCube(x + side.getOffsetX(), y + side.getOffsetY(), z + side.getOffsetZ())) {
+            world.setBlockMetadata(x, y, z, setSide(world.getBlockMetadata(x, y, z), side));
+        } else if (world.canPlaceOnSurfaceOfBlock(x, y - 1, z)) {
+            world.setBlockMetadata(x, y, z, setSide(world.getBlockMetadata(x, y, z), Side.BOTTOM));
+        }
+    }
+
+    @Override
+    public boolean canBePainted() {
+        return id() == Blocks.PRESSURE_PLATE_PLANKS_OAK.id();
+    }
+
+    @Override
+    public void setColor(World world, int x, int y, int z, DyeColor color) {
+        final int meta = world.getBlockMetadata(x, y, z);
+        world.setBlockAndMetadataRaw(x, y, z, Blocks.PRESSURE_PLATE_PLANKS_OAK_PAINTED.id(), meta);
+        world.setBlockMetadata(x, y, z, meta);
+        Blocks.PRESSURE_PLATE_PLANKS_OAK_PAINTED.getLogic().setColor(world, x, y, z, color);
+        if (isPressed(meta)) {
+            world.scheduleBlockUpdate(x, y, z, Blocks.PRESSURE_PLATE_PLANKS_OAK_PAINTED.id(), tickDelay());
         }
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicPressurePlatePainted.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicPressurePlatePainted.java
new file mode 100644
index 000000000..aa348bf85
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicPressurePlatePainted.java
@@ -0,0 +1,52 @@
+package net.minecraft.core.block;
+
+import net.minecraft.core.block.material.Material;
+import net.minecraft.core.entity.Entity;
+import net.minecraft.core.entity.player.Player;
+import net.minecraft.core.item.ItemStack;
+import net.minecraft.core.util.helper.DyeColor;
+import net.minecraft.core.util.helper.Side;
+import net.minecraft.core.world.World;
+import org.jetbrains.annotations.Nullable;
+
+public class BlockLogicPressurePlatePainted<T extends Entity> extends BlockLogicPressurePlate<T> implements IPainted{
+    public BlockLogicPressurePlatePainted(Block<?> block, Class<T> mobType, Material material) {
+        super(block, mobType, material);
+    }
+
+    @Override
+    public boolean canBePainted() {
+        return true;
+    }
+
+    @Override
+    public DyeColor fromMetadata(int meta) {
+        return DyeColor.colorFromBlockMeta(meta >> 4);
+    }
+
+    @Override
+    public int toMetadata(DyeColor color) {
+        return color.blockMeta << 4;
+    }
+
+    @Override
+    public int stripColorFromMetadata(int meta) {
+        return meta & (MASK_POWERED | MASK_SIDE);
+    }
+
+    @Override
+    public void removeDye(World world, int x, int y, int z) {
+        world.setBlockWithNotify(x, y, z, Blocks.PRESSURE_PLATE_PLANKS_OAK.id());
+    }
+
+    @Override
+    public int getPlacedBlockMetadata(@Nullable final Player player, final ItemStack stack, final World world, final int x, final int y, final int z, final Side side, final double xPlaced, final double yPlaced) {
+        return stack.getMetadata() & ~(MASK_POWERED | MASK_SIDE);
+    }
+
+    @Override
+    public void setColor(World world, int x, int y, int z, DyeColor color) {
+        final int meta = world.getBlockMetadata(x, y, z);
+        world.setBlockMetadataWithNotify(x, y, z, stripColorFromMetadata(meta) | toMetadata(color));
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicRepeater.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicRepeater.java
index 2e6fdd9ad..09e969d47 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicRepeater.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicRepeater.java
@@ -26,6 +26,10 @@ public class BlockLogicRepeater extends BlockLogic
     };
     public static final int MASK_DIRECTION = 0b0000_0011;
     public static final int MASK_TICK_DELAY = 0b0000_1100;
+    public static final int DIRECTION_SOUTH = 0;
+    public static final int DIRECTION_WEST = 1;
+    public static final int DIRECTION_NORTH = 2;
+    public static final int DIRECTION_EAST = 3;
 
     public final boolean isRepeaterPowered;
 
@@ -87,6 +91,23 @@ public class BlockLogicRepeater extends BlockLogic
         }
     }
 
+    // Ensure that repeaters can activate doors, tnt, and other redstone components through powering a neighboring block.
+    @Override
+    public boolean isSignalSource() {
+        return true;
+    }
+
+    // Cause block updates on the target block when removing a repeater,
+    // which prevents redstone components on the other wide of the target block from staying powered.
+    @Override
+    public void onBlockRemoved(World world, int x, int y, int z, int data) {
+        Side front = getSideFromMeta(data);
+        world.notifyBlocksOfNeighborChange(x + front.getOffsetX(), y + front.getOffsetY(), z + front.getOffsetZ(), id());
+        Side back = front.getOpposite();
+        world.notifyBlocksOfNeighborChange(x + back.getOffsetX(), y + back.getOffsetY(), z + back.getOffsetZ(), id());
+    }
+
+
     @Override
     public boolean getDirectSignal(World world, int x, int y, int z, Side side) {
         return getSignal(world, x, y, z, side);
@@ -98,16 +119,22 @@ public class BlockLogicRepeater extends BlockLogic
             return false;
         }
         int meta = worldSource.getBlockMetadata(x, y, z) & MASK_DIRECTION;
-        if(meta == 0 && side == Side.SOUTH) {
-            return true;
-        }
-        if(meta == 1 && side == Side.WEST) {
-            return true;
-        }
-        if(meta == 2 && side == Side.NORTH) {
-            return true;
+        return side == getSideFromMeta(meta);
+    }
+
+    public static Side getSideFromMeta(final int meta) {
+        switch (meta & 0b11) {
+            case DIRECTION_SOUTH:
+                return Side.SOUTH;
+            case DIRECTION_WEST:
+                return Side.WEST;
+            case DIRECTION_NORTH:
+                return Side.NORTH;
+            case DIRECTION_EAST:
+                return Side.EAST;
+            default:
+                throw new RuntimeException("No side for direction " + (meta & 0b11) + "!");
         }
-        return meta == 3 && side == Side.EAST;
     }
 
     @Override
@@ -129,20 +156,19 @@ public class BlockLogicRepeater extends BlockLogic
 
     private boolean isGettingPower(World world, int x, int y, int z, int meta)
     {
-        int direction = meta & MASK_DIRECTION;
-        switch(direction)
-        {
-        case 0: // '\0'
-            return world.getSignal(x, y, z + 1, Side.SOUTH) || world.getBlockId(x, y, z + 1) == Blocks.WIRE_REDSTONE.id() && world.getBlockMetadata(x, y, z + 1) > 0;
+        Side direction = getSideFromMeta(meta);
+        switch(direction) {
+            case SOUTH:
+                return world.getSignal(x, y, z + 1, Side.SOUTH) || world.getBlockId(x, y, z + 1) == Blocks.WIRE_REDSTONE.id() && world.getBlockMetadata(x, y, z + 1) > 0;
 
-        case 2: // '\002'
-            return world.getSignal(x, y, z - 1, Side.NORTH) || world.getBlockId(x, y, z - 1) == Blocks.WIRE_REDSTONE.id() && world.getBlockMetadata(x, y, z - 1) > 0;
+            case NORTH:
+                return world.getSignal(x, y, z - 1, Side.NORTH) || world.getBlockId(x, y, z - 1) == Blocks.WIRE_REDSTONE.id() && world.getBlockMetadata(x, y, z - 1) > 0;
 
-        case 3: // '\003'
-            return world.getSignal(x + 1, y, z, Side.EAST) || world.getBlockId(x + 1, y, z) == Blocks.WIRE_REDSTONE.id() && world.getBlockMetadata(x + 1, y, z) > 0;
+            case EAST:
+                return world.getSignal(x + 1, y, z, Side.EAST) || world.getBlockId(x + 1, y, z) == Blocks.WIRE_REDSTONE.id() && world.getBlockMetadata(x + 1, y, z) > 0;
 
-        case 1: // '\001'
-            return world.getSignal(x - 1, y, z, Side.WEST) || world.getBlockId(x - 1, y, z) == Blocks.WIRE_REDSTONE.id() && world.getBlockMetadata(x - 1, y, z) > 0;
+            case WEST:
+                return world.getSignal(x - 1, y, z, Side.WEST) || world.getBlockId(x - 1, y, z) == Blocks.WIRE_REDSTONE.id() && world.getBlockMetadata(x - 1, y, z) > 0;
         }
         return false;
     }
@@ -218,35 +244,35 @@ public class BlockLogicRepeater extends BlockLogic
         final int redstoneBrightness = 15;
 		
 		if (rand.nextInt(2) == 0) {
-			switch (meta & MASK_DIRECTION) {
-			case 0:
-				zOffset = -0.3125D;
-				break;
-			case 2:
-				zOffset = 0.3125D;
-				break;
-			case 3:
-				xOffset = -0.3125D;
-				break;
-			case 1:
-				xOffset = 0.3125D;
-				break;
+			switch (getSideFromMeta(meta)) {
+                case SOUTH:
+				    zOffset = -0.3125D;
+				    break;
+                case NORTH:
+				    zOffset = 0.3125D;
+				    break;
+                case EAST:
+				    xOffset = -0.3125D;
+				    break;
+                case WEST:
+				    xOffset = 0.3125D;
+				    break;
 			}
 		} else {
 			int tickDelay = (meta & MASK_TICK_DELAY) >> 2;
-			switch (meta & MASK_DIRECTION) {
-			case 0:
-				zOffset = torchPosOffset[tickDelay];
-				break;
-			case 2:
-				zOffset = -torchPosOffset[tickDelay];
-				break;
-			case 3:
-				xOffset = torchPosOffset[tickDelay];
-				break;
-			case 1:
-				xOffset = -torchPosOffset[tickDelay];
-				break;
+			switch (getSideFromMeta(meta)) {
+                case SOUTH:
+				    zOffset = torchPosOffset[tickDelay];
+				    break;
+                case NORTH:
+				    zOffset = -torchPosOffset[tickDelay];
+				    break;
+                case EAST:
+				    xOffset = torchPosOffset[tickDelay];
+				    break;
+                case WEST:
+				    xOffset = -torchPosOffset[tickDelay];
+				    break;
 			}
 		}
 		world.spawnParticle("reddust", px + xOffset, py, pz + zOffset, 0, 0, 0, redstoneBrightness);
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicTNT.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicTNT.java
index a648a7a60..0752a7321 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicTNT.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicTNT.java
@@ -26,7 +26,7 @@ public class BlockLogicTNT extends BlockLogic {
 
 	@Override
 	public void onNeighborBlockChange(World world, int x, int y, int z, int blockId) {
-		if (blockId > 0 && Blocks.blocksList[blockId].isSignalSource() && world.hasNeighborSignal(x, y, z)) {
+		if (blockId > 0 && world.hasNeighborSignal(x, y, z)) {
 			ignite(world, x, y, z, true);
 		}
 	}
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 f8d281138..22f21038a 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
@@ -2,6 +2,8 @@ package net.minecraft.core.block;
 
 import net.minecraft.core.block.entity.TileEntity;
 import net.minecraft.core.block.material.Material;
+import net.minecraft.core.current.util.BlockPos;
+import net.minecraft.core.current.util.BlockState;
 import net.minecraft.core.enums.EnumDropCause;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.item.Items;
@@ -174,7 +176,8 @@ public class BlockLogicWireRedstone extends BlockLogic {
         if (world.isClientSide) {
             return;
         }
-        updatePowerStrength(world, x, y, z);
+//        updatePowerStrength(world, x, y, z);
+        world.getWireHandler().onWireAdded(new BlockPos(x, y, z));
         world.notifyBlocksOfNeighborChange(x, y + 1, z, id());
         world.notifyBlocksOfNeighborChange(x, y - 1, z, id());
         checkCornerChangeAt(world, x - 1, y, z);
@@ -211,7 +214,8 @@ public class BlockLogicWireRedstone extends BlockLogic {
         }
         world.notifyBlocksOfNeighborChange(x, y + 1, z, id());
         world.notifyBlocksOfNeighborChange(x, y - 1, z, id());
-        updatePowerStrength(world, x, y, z);
+//        updatePowerStrength(world, x, y, z);
+        world.getWireHandler().onWireRemoved(new BlockPos(x, y, z), new BlockState(this.id(), world.getBlockMetadata(x, y, z)));
         checkCornerChangeAt(world, x - 1, y, z);
         checkCornerChangeAt(world, x + 1, y, z);
         checkCornerChangeAt(world, x, y, z - 1);
@@ -249,7 +253,7 @@ public class BlockLogicWireRedstone extends BlockLogic {
 
     @Override
     public void onNeighborBlockChange(World world, int x, int y, int z, int blockId) {
-        if (world.isClientSide) {
+        if (world.isClientSide || world.getWireHandler().onWireUpdated(new BlockPos(x, y, z))) {
             return;
         }
 
@@ -289,37 +293,59 @@ public class BlockLogicWireRedstone extends BlockLogic {
         if (side == Side.TOP) {
             return true;
         }
-        boolean negXShouldConnectTo = shouldConnectTo(worldSource, x - 1, y, z, 1) || !worldSource.isBlockNormalCube(x - 1, y, z) && shouldConnectTo(worldSource, x - 1, y - 1, z, -1);
-        boolean posXShouldConnectTo = shouldConnectTo(worldSource, x + 1, y, z, 3) || !worldSource.isBlockNormalCube(x + 1, y, z) && shouldConnectTo(worldSource, x + 1, y - 1, z, -1);
-        boolean negZShouldConnectTo = shouldConnectTo(worldSource, x, y, z - 1, 2) || !worldSource.isBlockNormalCube(x, y, z - 1) && shouldConnectTo(worldSource, x, y - 1, z - 1, -1);
-        boolean posZShouldConnectTo = shouldConnectTo(worldSource, x, y, z + 1, 0) || !worldSource.isBlockNormalCube(x, y, z + 1) && shouldConnectTo(worldSource, x, y - 1, z + 1, -1);
+        boolean negXShouldConnectTo = shouldConnectTo(worldSource, x - 1, y, z, Side.WEST) || !worldSource.isBlockNormalCube(x - 1, y, z) && shouldConnectToDiagonal(worldSource, x - 1, y - 1, z, Side.NONE, Side.BOTTOM);
+        boolean posXShouldConnectTo = shouldConnectTo(worldSource, x + 1, y, z, Side.EAST) || !worldSource.isBlockNormalCube(x + 1, y, z) && shouldConnectToDiagonal(worldSource, x + 1, y - 1, z, Side.NONE, Side.BOTTOM);
+        boolean negZShouldConnectTo = shouldConnectTo(worldSource, x, y, z - 1, Side.NORTH) || !worldSource.isBlockNormalCube(x, y, z - 1) && shouldConnectToDiagonal(worldSource, x, y - 1, z - 1, Side.NONE, Side.BOTTOM);
+        boolean posZShouldConnectTo = shouldConnectTo(worldSource, x, y, z + 1, Side.SOUTH) || !worldSource.isBlockNormalCube(x, y, z + 1) && shouldConnectToDiagonal(worldSource, x, y - 1, z + 1, Side.NONE, Side.BOTTOM);
         if (!worldSource.isBlockNormalCube(x, y + 1, z)) {
-            if (worldSource.isBlockNormalCube(x - 1, y, z) && shouldConnectTo(worldSource, x - 1, y + 1, z, -1)) {
+            if (worldSource.isBlockNormalCube(x - 1, y, z) && shouldConnectToDiagonal(worldSource, x - 1, y + 1, z, Side.NONE, Side.TOP)) {
                 negXShouldConnectTo = true;
             }
-            if (worldSource.isBlockNormalCube(x + 1, y, z) && shouldConnectTo(worldSource, x + 1, y + 1, z, -1)) {
+            if (worldSource.isBlockNormalCube(x + 1, y, z) && shouldConnectToDiagonal(worldSource, x + 1, y + 1, z, Side.NONE, Side.TOP)) {
                 posXShouldConnectTo = true;
             }
-            if (worldSource.isBlockNormalCube(x, y, z - 1) && shouldConnectTo(worldSource, x, y + 1, z - 1, -1)) {
+            if (worldSource.isBlockNormalCube(x, y, z - 1) && shouldConnectToDiagonal(worldSource, x, y + 1, z - 1, Side.NONE, Side.TOP)) {
                 negZShouldConnectTo = true;
             }
-            if (worldSource.isBlockNormalCube(x, y, z + 1) && shouldConnectTo(worldSource, x, y + 1, z + 1, -1)) {
+            if (worldSource.isBlockNormalCube(x, y, z + 1) && shouldConnectToDiagonal(worldSource, x, y + 1, z + 1, Side.NONE, Side.TOP)) {
                 posZShouldConnectTo = true;
             }
         }
-        if (!negZShouldConnectTo && !posXShouldConnectTo && !negXShouldConnectTo && !posZShouldConnectTo && side.getAxis() != Axis.Y) {
-            return true;
-        }
-        if (side == Side.NORTH && negZShouldConnectTo && !negXShouldConnectTo && !posXShouldConnectTo) {
-            return true;
-        }
-        if (side == Side.SOUTH && posZShouldConnectTo && !negXShouldConnectTo && !posXShouldConnectTo) {
-            return true;
-        }
-        if (side == Side.WEST && negXShouldConnectTo && !negZShouldConnectTo && !posZShouldConnectTo) {
-            return true;
+        // Make wire emit block updates when a redirection happens
+        if (worldSource instanceof World) {
+            World world = (World) worldSource;
+
+            int meta = world.getBlockMetadata(x, y, z);
+            int direction = (meta & (15 << 4)) >> 4; // 0b00000000 00000000 00000000 DDDD0000
+
+            int newDirectionNorth = negZShouldConnectTo ? 1 : 0;
+            int newDirectionSouth = (posZShouldConnectTo ? 1 : 0) << 1;
+            int newDirectionWest = (negXShouldConnectTo ? 1 : 0) << 2;
+            int newDirectionEast = (posXShouldConnectTo ? 1 : 0) << 3;
+            int newDirection = newDirectionNorth | newDirectionSouth | newDirectionWest | newDirectionEast;
+
+            if (direction != newDirection) {
+                newDirection <<= 4;
+                meta &= (~(15 << 4)); // Mask out direction bits
+                meta |= newDirection;
+                world.setBlockMetadata(x, y, z, meta);
+            }
         }
-        return side == Side.EAST && posXShouldConnectTo && !negZShouldConnectTo && !posZShouldConnectTo;
+
+
+        boolean isXConnected = posXShouldConnectTo || negXShouldConnectTo;
+        boolean isZConnected = posZShouldConnectTo || negZShouldConnectTo;
+        return !isZConnected && !isXConnected && side.getAxis() != Axis.Y // Default single dust
+            // When the signal request comes from a block that dust connects to (usually power sources)
+            || side == Side.SOUTH && negZShouldConnectTo
+            || side == Side.NORTH && posZShouldConnectTo
+            || side == Side.EAST && negXShouldConnectTo
+            || side == Side.WEST && posXShouldConnectTo
+            // When the signal request comes from the block opposite of a connection (most redstone components)
+            || side == Side.NORTH && negZShouldConnectTo && !isXConnected
+            || side == Side.SOUTH && posZShouldConnectTo && !isXConnected
+            || side == Side.WEST && negXShouldConnectTo && !isZConnected
+            || side == Side.EAST && posXShouldConnectTo && !isZConnected;
     }
 
     @Override
@@ -342,30 +368,30 @@ public class BlockLogicWireRedstone extends BlockLogic {
         world.spawnParticle("reddust", px, py, pz, 0, 0, 0, redstoneBrightness);
     }
 
-    public static boolean shouldConnectTo(WorldSource worldSource, int x, int y, int z, int data) {
-        int blockId = worldSource.getBlockId(x, y, z);
-        if (blockId == Blocks.WIRE_REDSTONE.id()) {
-            return true;
-        }
-        if (blockId <= 0) {
+    public static boolean shouldConnectTo(WorldSource worldSource, int x, int y, int z, Side side) {
+        // TODO make connections handled per block rather then by the dust
+        final Block<?> block = worldSource.getBlock(x, y, z);
+        if (block == null) {
             return false;
         }
-        if (Blocks.blocksList[blockId].isSignalSource()) {
+        if (block == Blocks.WIRE_REDSTONE) {
             return true;
         }
-        if (blockId == Blocks.PUMPKIN_REDSTONE.id()) {
-            if (data < 0 || data > 3) {
-                return false;
-            }
-            Side[] lookup = {Side.NORTH, Side.EAST, Side.SOUTH, Side.WEST};
-            Side side = Side.getSideById(worldSource.getBlockMetadata(x, y, z));
-            return side == lookup[data];
+        if (block == Blocks.PUMPKIN_REDSTONE) {
+            if (side == Side.NONE || side.isVertical()) return false;
+            return Side.getSideById(worldSource.getBlockMetadata(x, y, z)).getOpposite() == side;
         }
-        if (blockId == Blocks.REPEATER_IDLE.id() || blockId == Blocks.REPEATER_ACTIVE.id()) {
-            int meta = worldSource.getBlockMetadata(x, y, z);
-            return data == BlockLogicBed.footToHeadMap[meta & 0b11];
-        } else {
-            return false;
+        if (block == Blocks.REPEATER_IDLE || block == Blocks.REPEATER_ACTIVE) {
+            final Side direction = BlockLogicRepeater.getSideFromMeta(worldSource.getBlockMetadata(x, y, z));
+            return side == direction || side == direction.getOpposite();
         }
+        return block.isSignalSource();
+    }
+
+    public static boolean shouldConnectToDiagonal(WorldSource worldSource, int x, int y, int z, Side side1, 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?
+
+        int blockId = worldSource.getBlockId(x, y, z);
+        return blockId == Blocks.WIRE_REDSTONE.id();
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/block/Blocks.java b/game/core/src/main/java/net/minecraft/core/block/Blocks.java
index 8c0611d23..5ca68761e 100644
--- a/game/core/src/main/java/net/minecraft/core/block/Blocks.java
+++ b/game/core/src/main/java/net/minecraft/core/block/Blocks.java
@@ -757,6 +757,13 @@ public final class Blocks {
     public static final Block<?> BUTTON_STONE = register("button.stone", "minecraft:block/button_stone", 470, BlockLogicButton::new)
         .withSound(BlockSounds.STONE).withHardness(0.5F).withDisabledNeighborNotifyOnMetadataChange()
         .withTags(BlockTags.BROKEN_BY_FLUIDS, BlockTags.MINEABLE_BY_PICKAXE, BlockTags.PREVENT_MOB_SPAWNS);
+    public static final Block<?> BUTTON_PLANKS = register("button.planks", "minecraft:block/button_planks", 471, BlockLogicButton::new)
+        .withSound(BlockSounds.WOOD).withHardness(0.5F).withDisabledNeighborNotifyOnMetadataChange()
+        .withTags(BlockTags.BROKEN_BY_FLUIDS, BlockTags.MINEABLE_BY_PICKAXE, BlockTags.PREVENT_MOB_SPAWNS);
+    public static final Block<BlockLogicButtonPainted> BUTTON_PLANKS_PAINTED = register("button.planks.painted", "minecraft:block/button_planks_painted", 472, BlockLogicButtonPainted::new)
+        .setBlockItem((b) -> new ItemBlockPainted<>(b, true))
+        .withSound(BlockSounds.WOOD).withHardness(0.5F).withDisabledNeighborNotifyOnMetadataChange()
+        .withTags(BlockTags.BROKEN_BY_FLUIDS, BlockTags.MINEABLE_BY_PICKAXE, BlockTags.PREVENT_MOB_SPAWNS);
 
     public static final Block<?> LEVER_COBBLE_STONE = register("lever.cobble.stone", "minecraft:block/lever_cobble_stone", 480, BlockLogicLever::new)
         .withSound(BlockSounds.STONE).withHardness(0.5F).withDisabledNeighborNotifyOnMetadataChange()
@@ -771,6 +778,10 @@ public final class Blocks {
     public static final Block<?> PRESSURE_PLATE_COBBLE_STONE = register("pressureplate.cobble.stone", "minecraft:block/pressure_plate_cobble_stone", 492, (b) -> new BlockLogicPressurePlate<>(b, Player.class, Material.stone))
         .withSound(BlockSounds.STONE).withHardness(0.5F).withDisabledNeighborNotifyOnMetadataChange()
         .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.PREVENT_MOB_SPAWNS);
+    public static final Block<BlockLogicPressurePlatePainted> PRESSURE_PLATE_PLANKS_OAK_PAINTED = register("pressureplate.planks.oak.painted", "minecraft:block/pressure_plate_planks_oak_painted", 493, (BlockLogicSupplier<BlockLogicPressurePlatePainted>)b -> new BlockLogicPressurePlatePainted<>(b, Entity.class, Material.wood))
+        .setBlockItem((b) -> new ItemBlockPainted<>(b, true))
+        .withSound(BlockSounds.WOOD).withHardness(0.5F).withDisabledNeighborNotifyOnMetadataChange()
+        .withTags(BlockTags.MINEABLE_BY_AXE, BlockTags.PREVENT_MOB_SPAWNS);
 
     public static final Block<BlockLogicMotionSensor> MOTION_SENSOR_IDLE = register("motionsensor.idle", "minecraft:block/motion_sensor_idle", 500, (BlockLogicSupplier<BlockLogicMotionSensor>)(b) -> new BlockLogicMotionSensor(b, false))
         .withSound(BlockSounds.STONE).withHardness(2.0F).withBlastResistance(10F).withDisabledNeighborNotifyOnMetadataChange().withOverrideColor(MaterialColor.stone)
diff --git a/game/core/src/main/java/net/minecraft/core/current/LICENSE b/game/core/src/main/java/net/minecraft/core/current/LICENSE
new file mode 100644
index 000000000..dee59d479
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/current/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 Space Walker
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/game/core/src/main/java/net/minecraft/core/current/util/BlockPos.java b/game/core/src/main/java/net/minecraft/core/current/util/BlockPos.java
new file mode 100644
index 000000000..c79344c05
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/current/util/BlockPos.java
@@ -0,0 +1,41 @@
+package net.minecraft.core.current.util;
+
+public class BlockPos {
+
+	public final int x;
+	public final int y;
+	public final int z;
+
+	public BlockPos(int x, int y, int z) {
+		this.x = x;
+		this.y = y;
+		this.z = z;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (obj instanceof BlockPos) {
+			BlockPos pos = (BlockPos)obj;
+			return pos.x == x && pos.y == y && pos.z == z;
+		}
+
+		return false;
+	}
+
+	@Override
+	public int hashCode() {
+		return x + 31 * (y + 31 * z);
+	}
+
+	public BlockPos offset(Direction dir) {
+		return offset(dir, 1);
+	}
+
+	public BlockPos offset(Direction dir, int dist) {
+		return offset(dist * dir.offsetX, dist * dir.offsetY, dist * dir.offsetZ);
+	}
+
+	public BlockPos offset(int offsetX, int offsetY, int offsetZ) {
+		return new BlockPos(x + offsetX, y + offsetY, z + offsetZ);
+	}
+}
diff --git a/game/core/src/main/java/net/minecraft/core/current/util/BlockState.java b/game/core/src/main/java/net/minecraft/core/current/util/BlockState.java
new file mode 100644
index 000000000..1e87b735c
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/current/util/BlockState.java
@@ -0,0 +1,101 @@
+package net.minecraft.core.current.util;
+
+
+import net.minecraft.core.block.Block;
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.enums.EnumDropCause;
+import net.minecraft.core.util.helper.Side;
+import net.minecraft.core.world.World;
+
+public class BlockState {
+
+	public static final BlockState AIR = new BlockState(null, 0) {
+		public int getBlockId() { return 0; }
+		public boolean is(Block<?> block) { return false; }
+		public BlockState set(int metadata) { return this; }
+		public boolean isAir() { return true; }
+		public boolean isConductor() { return false; }
+		public boolean isSignalSource() { return false; }
+		public boolean hasSignal(World world, BlockPos pos, Direction dir) { return false; }
+		public boolean hasDirectSignal(World world, BlockPos pos, Direction dir) { return false; }
+		public boolean canSurvive(World world, BlockPos pos) { return true; }
+		public void dropItems(World world, BlockPos pos) { }
+		public void neighborChanged(World world, BlockPos pos, Block<?> fromBlock) { }
+	};
+
+	private final Block<?> block;
+	private final int metadata;
+
+	public BlockState(int blockId, int metadata) {
+		this(Blocks.getBlock(blockId), metadata);
+	}
+
+	public BlockState(Block<?> block, int metadata) {
+		this.block = block;
+		this.metadata = metadata;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (obj instanceof BlockState) {
+			BlockState state = (BlockState)obj;
+			return state.block == block && state.metadata == metadata;
+		}
+
+		return false;
+	}
+
+	public int getBlockId() {
+		return block.id();
+	}
+
+	public Block<?> getBlock() {
+		return block;
+	}
+
+	public boolean is(Block<?> block) {
+		return this.block == block;
+	}
+
+	public int get() {
+		return metadata;
+	}
+
+	public BlockState set(int metadata) {
+		return new BlockState(block, metadata);
+	}
+
+	public boolean isAir() {
+		return this == AIR;
+	}
+
+	public boolean isConductor() {
+		return block.getMaterial().isSolidBlocking() && block.isSolidRender(); // Would use isCubeShaped, but slabs are cube shaped...
+	}
+
+	public boolean isSignalSource() {
+		return block.isSignalSource();
+	}
+
+	public boolean hasSignal(World world, BlockPos pos, Direction dir) {
+		return block.getSignal(world, pos.x, pos.y, pos.z, Side.getSideById(dir.index));
+	}
+
+	public boolean hasDirectSignal(World world, BlockPos pos, Direction dir) {
+		return block.getDirectSignal(world, pos.x, pos.y, pos.z, Side.getSideById(dir.index));
+	}
+
+	public boolean canSurvive(World world, BlockPos pos) {
+		Block<?> block = world.getBlock(pos.x, pos.y, pos.z);
+		Block<?> underBlock = world.getBlock(pos.x, pos.y - 1, pos.z);
+		return (block == null || block == Blocks.WIRE_REDSTONE || block.getMaterial().isReplaceable()) && underBlock != null && underBlock.isCubeShaped();
+	}
+
+	public void dropItems(World world, BlockPos pos) {
+		block.dropBlockWithCause(world, EnumDropCause.WORLD, pos.x, pos.y, pos.z, 0, null, null);
+	}
+
+	public void neighborChanged(World world, BlockPos pos, Block<?> neighborBlock) {
+		block.onNeighborBlockChange(world, pos.x, pos.y, pos.z, neighborBlock.id());
+	}
+}
diff --git a/game/core/src/main/java/net/minecraft/core/current/util/BlockUtil.java b/game/core/src/main/java/net/minecraft/core/current/util/BlockUtil.java
new file mode 100644
index 000000000..8fc442573
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/current/util/BlockUtil.java
@@ -0,0 +1,7 @@
+package net.minecraft.core.current.util;
+
+public class BlockUtil {
+
+	public static final int FLAG_UPDATE_CLIENTS = 0b10;
+
+}
diff --git a/game/core/src/main/java/net/minecraft/core/current/util/Direction.java b/game/core/src/main/java/net/minecraft/core/current/util/Direction.java
new file mode 100644
index 000000000..59884135b
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/current/util/Direction.java
@@ -0,0 +1,48 @@
+package net.minecraft.core.current.util;
+
+public enum Direction {
+
+	DOWN(0, 1, 0, -1, 0),
+	UP(1, 0, 0, 1, 0),
+	NORTH(2, 3, 0, 0, -1),
+	SOUTH(3, 2, 0, 0, 1),
+	WEST(4, 5, -1, 0, 0),
+	EAST(5, 4, 1, 0, 0);
+
+	public static final Direction[] ALL;
+
+	static {
+
+		ALL = new Direction[values().length];
+
+		for (Direction dir : values()) {
+			ALL[dir.index] = dir;
+		}
+	}
+
+	public final int index;
+	public final int oppositeIndex;
+	public final int offsetX;
+	public final int offsetY;
+	public final int offsetZ;
+
+	private Direction(int index, int opposite, int offsetX, int offsetY, int offsetZ) {
+		this.index = index;
+		this.oppositeIndex = opposite;
+		this.offsetX = offsetX;
+		this.offsetY = offsetY;
+		this.offsetZ = offsetZ;
+	}
+
+	public static Direction fromIndex(int index) {
+		if (index < 0 || index >= ALL.length) {
+			return null;
+		}
+
+		return ALL[index];
+	}
+
+	public Direction getOpposite() {
+		return ALL[oppositeIndex];
+	}
+}
diff --git a/game/core/src/main/java/net/minecraft/core/current/util/Mth.java b/game/core/src/main/java/net/minecraft/core/current/util/Mth.java
new file mode 100644
index 000000000..992f9470f
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/current/util/Mth.java
@@ -0,0 +1,7 @@
+package net.minecraft.core.current.util;
+
+public class Mth {
+	public static int clamp(int i, int min, int max) {
+		return i <= min ? min : Math.min(i, max);
+	}
+}
diff --git a/game/core/src/main/java/net/minecraft/core/current/util/Redstone.java b/game/core/src/main/java/net/minecraft/core/current/util/Redstone.java
new file mode 100644
index 000000000..ec470d758
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/current/util/Redstone.java
@@ -0,0 +1,7 @@
+package net.minecraft.core.current.util;
+
+public class Redstone {
+	public static final int SIGNAL_MIN = 0;
+	public static final int SIGNAL_MAX = 15;
+	public static final int SIGNAL_STEP = 1;
+}
diff --git a/game/core/src/main/java/net/minecraft/core/current/wire/Node.java b/game/core/src/main/java/net/minecraft/core/current/wire/Node.java
new file mode 100644
index 000000000..1159096fc
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/current/wire/Node.java
@@ -0,0 +1,112 @@
+package net.minecraft.core.current.wire;
+
+import net.minecraft.core.current.util.BlockPos;
+import net.minecraft.core.current.util.BlockState;
+import net.minecraft.core.current.wire.WireHandler.Directions;
+import net.minecraft.core.world.World;
+import net.minecraft.core.block.Blocks;
+
+import java.util.Arrays;
+
+/**
+ * A Node represents a block in the world. It also holds a few other pieces of
+ * information that speed up the calculations in the WireHandler class.
+ *
+ * @author Space Walker
+ */
+public class Node {
+
+	// flags that encode the Node type
+	private static final int CONDUCTOR = 0b01;
+	private static final int SOURCE    = 0b10;
+
+	final World world;
+	final Node[] neighbors;
+
+	BlockPos pos;
+	BlockState state;
+	boolean invalid;
+
+	private int flags;
+
+	/** The previous node in the priority queue. */
+	Node prev_node;
+	/** The next node in the priority queue. */
+	Node next_node;
+	/** The priority with which this node was queued. */
+	int priority;
+	/** The wire that queued this node for an update. */
+	WireNode neighborWire;
+
+	Node(World world) {
+		this.world = world;
+		this.neighbors = new Node[Directions.ALL.length];
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj) {
+			return true;
+		}
+		if (!(obj instanceof Node)) {
+			return false;
+		}
+
+		Node node = (Node)obj;
+
+		return world == node.world && pos.equals(node.pos);
+	}
+
+	@Override
+	public int hashCode() {
+		return pos.hashCode();
+	}
+
+	Node set(BlockPos pos, BlockState state, boolean clearNeighbors) {
+		if (state.is(Blocks.WIRE_REDSTONE)) {
+			throw new IllegalStateException("Cannot update a regular Node to a WireNode!");
+		}
+
+		if (clearNeighbors) {
+			Arrays.fill(neighbors, null);
+		}
+
+		this.pos = pos;
+		this.state = state;
+		this.invalid = false;
+
+		this.flags = 0;
+
+		if (this.state.isConductor()) {
+			this.flags |= CONDUCTOR;
+		}
+		if (this.state.isSignalSource()) {
+			this.flags |= SOURCE;
+		}
+
+		return this;
+	}
+
+	/**
+	 * Determine the priority with which this node should be queued.
+	 */
+	int priority() {
+		return neighborWire.priority;
+	}
+
+	public boolean isWire() {
+		return false;
+	}
+
+	public boolean isConductor() {
+		return (flags & CONDUCTOR) != 0;
+	}
+
+	public boolean isSignalSource() {
+		return (flags & SOURCE) != 0;
+	}
+
+	public WireNode asWire() {
+		throw new UnsupportedOperationException("Not a WireNode!");
+	}
+}
diff --git a/game/core/src/main/java/net/minecraft/core/current/wire/PriorityQueue.java b/game/core/src/main/java/net/minecraft/core/current/wire/PriorityQueue.java
new file mode 100644
index 000000000..32000d8b1
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/current/wire/PriorityQueue.java
@@ -0,0 +1,211 @@
+package net.minecraft.core.current.wire;
+
+import net.minecraft.core.current.util.Redstone;
+
+import java.util.AbstractQueue;
+import java.util.Arrays;
+import java.util.Iterator;
+
+public class PriorityQueue extends AbstractQueue<Node> {
+
+	private static final int OFFSET = -Redstone.SIGNAL_MIN;
+
+	/** The last node for each priority value. */
+	private final Node[] tails;
+
+	private Node head;
+	private Node tail;
+
+	private int size;
+
+	PriorityQueue() {
+		this.tails = new Node[(Redstone.SIGNAL_MAX + OFFSET) + 1];
+	}
+
+	@Override
+	public boolean offer(Node node) {
+		if (node == null) {
+			throw new NullPointerException();
+		}
+
+		int priority = node.priority();
+
+		if (contains(node)) {
+			if (node.priority == priority) {
+				// already queued with this priority; exit
+				return false;
+			} else {
+				// already queued with different priority; move it
+				move(node, priority);
+			}
+		} else {
+			insert(node, priority);
+		}
+
+		return true;
+	}
+
+	@Override
+	public Node poll() {
+		if (head == null) {
+			return null;
+		}
+
+		Node node = head;
+		Node next = node.next_node;
+
+		if (next == null) {
+			clear(); // reset the tails array
+		} else {
+			if (node.priority != next.priority) {
+				// If the head is also a tail, its entry in the array
+				// can be cleared; there is no previous node with the
+				// same priority to take its place.
+				tails[node.priority + OFFSET] = null;
+			}
+
+			node.next_node = null;
+			next.prev_node = null;
+			head = next;
+
+			size--;
+		}
+
+		return node;
+	}
+
+	@Override
+	public Node peek() {
+		return head;
+	}
+
+	@Override
+	public void clear() {
+		for (Node node = head; node != null; ) {
+			Node n = node;
+			node = node.next_node;
+
+			n.prev_node = null;
+			n.next_node = null;
+		}
+
+		Arrays.fill(tails, null);
+
+		head = null;
+		tail = null;
+
+		size = 0;
+	}
+
+	@Override
+	public Iterator<Node> iterator() {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public int size() {
+		return size;
+	}
+
+	public boolean contains(Node node) {
+		return node == head || node.prev_node != null;
+	}
+
+	private void move(Node node, int priority) {
+		remove(node);
+		insert(node, priority);
+	}
+
+	private void remove(Node node) {
+		Node prev = node.prev_node;
+		Node next = node.next_node;
+
+		if (node == tail || node.priority != next.priority) {
+			// assign a new tail for this node's priority
+			if (node == head || node.priority != prev.priority) {
+				// there is no other node with the same priority; clear
+				tails[node.priority + OFFSET] = null;
+			} else {
+				// the previous node in the queue becomes the tail
+				tails[node.priority + OFFSET] = prev;
+			}
+		}
+
+		if (node == head) {
+			head = next;
+		} else {
+			prev.next_node = next;
+		}
+		if (node == tail) {
+			tail = prev;
+		} else {
+			next.prev_node = prev;
+		}
+
+		node.prev_node = null;
+		node.next_node = null;
+
+		size--;
+	}
+
+	private void insert(Node node, int priority) {
+		node.priority = priority;
+
+		// nodes are sorted by priority (highest to lowest)
+		// nodes with the same priority are ordered FIFO
+		if (head == null) {
+			// first element in this queue \o/
+			head = tail = node;
+		} else if (priority > head.priority) {
+			linkHead(node);
+		} else if (priority <= tail.priority) {
+			linkTail(node);
+		} else {
+			// since the node is neither the head nor the tail
+			// findPrev is guaranteed to find a non-null element
+			linkAfter(findPrev(node), node);
+		}
+
+		tails[priority + OFFSET] = node;
+
+		size++;
+	}
+
+	private void linkHead(Node node) {
+		node.next_node = head;
+		head.prev_node = node;
+		head = node;
+	}
+
+	private void linkTail(Node node) {
+		tail.next_node = node;
+		node.prev_node = tail;
+		tail = node;
+	}
+
+	private void linkAfter(Node prev, Node node) {
+		linkBetween(prev, node, prev.next_node);
+	}
+
+	private void linkBetween(Node prev, Node node, Node next) {
+		prev.next_node = node;
+		node.prev_node = prev;
+
+		node.next_node = next;
+		next.prev_node = node;
+	}
+
+	private Node findPrev(Node node) {
+		Node prev = null;
+
+		for (int i = node.priority + OFFSET; i < tails.length; i++) {
+			prev = tails[i];
+
+			if (prev != null) {
+				break;
+			}
+		}
+
+		return prev;
+	}
+}
diff --git a/game/core/src/main/java/net/minecraft/core/current/wire/SimpleQueue.java b/game/core/src/main/java/net/minecraft/core/current/wire/SimpleQueue.java
new file mode 100644
index 000000000..405861d9c
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/current/wire/SimpleQueue.java
@@ -0,0 +1,112 @@
+package net.minecraft.core.current.wire;
+
+import java.util.AbstractQueue;
+import java.util.Iterator;
+
+public class SimpleQueue extends AbstractQueue<WireNode> {
+
+	private WireNode head;
+	private WireNode tail;
+
+	private int size;
+
+	SimpleQueue() {
+
+	}
+
+	@Override
+	public boolean offer(WireNode node) {
+		if (node == null) {
+			throw new NullPointerException();
+		}
+
+		if (tail == null) {
+			head = tail = node;
+		} else {
+			tail.next_wire = node;
+			tail = node;
+		}
+
+		size++;
+
+		return true;
+	}
+
+	@Override
+	public WireNode poll() {
+		if (head == null) {
+			return null;
+		}
+
+		WireNode node = head;
+		WireNode next = node.next_wire;
+
+		if (next == null) {
+			head = tail = null;
+		} else {
+			node.next_wire = null;
+			head = next;
+		}
+
+		size--;
+
+		return node;
+	}
+
+	@Override
+	public WireNode peek() {
+		return head;
+	}
+
+	@Override
+	public void clear() {
+		for (WireNode node = head; node != null; ) {
+			WireNode n = node;
+			node = node.next_wire;
+
+			n.next_wire = null;
+		}
+
+		head = null;
+		tail = null;
+
+		size = 0;
+	}
+
+	@Override
+	public Iterator<WireNode> iterator() {
+		return new SimpleIterator();
+	}
+
+	@Override
+	public int size() {
+		return size;
+	}
+
+	private class SimpleIterator implements Iterator<WireNode> {
+
+		private WireNode curr;
+		private WireNode next;
+
+		private SimpleIterator() {
+			next = head;
+		}
+
+		@Override
+		public boolean hasNext() {
+			if (next == null && curr != null) {
+				next = curr.next_wire;
+			}
+
+			return next != null;
+		}
+
+		@Override
+		public WireNode next() {
+			curr = next;
+			next = curr.next_wire;
+
+			return curr;
+		}
+	}
+}
diff --git a/game/core/src/main/java/net/minecraft/core/current/wire/WireConnection.java b/game/core/src/main/java/net/minecraft/core/current/wire/WireConnection.java
new file mode 100644
index 000000000..86c6fa7bd
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/current/wire/WireConnection.java
@@ -0,0 +1,30 @@
+package net.minecraft.core.current.wire;
+
+/**
+ * This class represents a connection between some WireNode (the 'owner') and a
+ * neighboring WireNode. Two wires are considered to be connected if power can
+ * flow from one wire to the other (and/or vice versa).
+ * 
+ * @author Space Walker
+ */
+public class WireConnection {
+
+	/** The connected wire. */
+	final WireNode wire;
+	/** Cardinal direction to the connected wire. */
+	final int iDir;
+	/** True if the owner of the connection can provide power to the connected wire. */
+	final boolean offer;
+	/** True if the connected wire can provide power to the owner of the connection. */
+	final boolean accept;
+
+	/** The next connection in the sequence. */
+	WireConnection next;
+
+	WireConnection(WireNode wire, int iDir, boolean offer, boolean accept) {
+		this.wire = wire;
+		this.iDir = iDir;
+		this.offer = offer;
+		this.accept = accept;
+	}
+}
diff --git a/game/core/src/main/java/net/minecraft/core/current/wire/WireConnectionManager.java b/game/core/src/main/java/net/minecraft/core/current/wire/WireConnectionManager.java
new file mode 100644
index 000000000..85f14e5c9
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/current/wire/WireConnectionManager.java
@@ -0,0 +1,136 @@
+package net.minecraft.core.current.wire;
+
+import net.minecraft.core.current.wire.WireHandler.Directions;
+import net.minecraft.core.current.wire.WireHandler.NodeProvider;
+
+import java.util.Arrays;
+import java.util.function.Consumer;
+
+public class WireConnectionManager {
+
+	/** The owner of these connections. */
+	final WireNode owner;
+
+	/** The first connection for each cardinal direction. */
+	private final WireConnection[] heads;
+
+	private WireConnection head;
+	private WireConnection tail;
+
+	/** The total number of connections. */
+	int total;
+
+	/**
+	 * A 4 bit number that encodes which in direction(s) the owner has connections
+	 * to other wires.
+	 */
+	private int flowTotal;
+	/** The direction of flow based connections to other wires. */
+	int iFlowDir;
+
+	WireConnectionManager(WireNode owner) {
+		this.owner = owner;
+
+		this.heads = new WireConnection[Directions.HORIZONTAL.length];
+
+		this.total = 0;
+
+		this.flowTotal = 0;
+		this.iFlowDir = -1;
+	}
+
+	void set(NodeProvider nodes) {
+		if (total > 0) {
+			clear();
+		}
+
+		boolean belowIsConductor = nodes.getNeighbor(owner, Directions.DOWN).isConductor();
+		boolean aboveIsConductor = nodes.getNeighbor(owner, Directions.UP).isConductor();
+
+		for (int iDir = 0; iDir < Directions.HORIZONTAL.length; iDir++) {
+			Node neighbor = nodes.getNeighbor(owner, iDir);
+
+			if (neighbor.isWire()) {
+				add(neighbor.asWire(), iDir, true, true);
+
+				continue;
+			}
+
+			boolean sideIsConductor = neighbor.isConductor();
+
+			if (!sideIsConductor) {
+				Node node = nodes.getNeighbor(neighbor, Directions.DOWN);
+
+				if (node.isWire()) {
+					add(node.asWire(), iDir, belowIsConductor, true);
+				}
+			}
+			if (!aboveIsConductor) {
+				Node node = nodes.getNeighbor(neighbor, Directions.UP);
+
+				if (node.isWire()) {
+					add(node.asWire(), iDir, true, sideIsConductor);
+				}
+			}
+		}
+
+		if (total > 0) {
+			iFlowDir = WireHandler.FLOW_IN_TO_FLOW_OUT[flowTotal];
+		}
+	}
+
+	private void clear() {
+		Arrays.fill(heads, null);
+
+		head = null;
+		tail = null;
+
+		total = 0;
+
+		flowTotal = 0;
+		iFlowDir = -1;
+	}
+
+	private void add(WireNode wire, int iDir, boolean offer, boolean accept) {
+		add(new WireConnection(wire, iDir, offer, accept));
+	}
+
+	private void add(WireConnection connection) {
+		if (head == null) {
+			head = connection;
+			tail = connection;
+		} else {
+			tail.next = connection;
+			tail = connection;
+		}
+
+		total++;
+
+		if (heads[connection.iDir] == null) {
+			heads[connection.iDir] = connection;
+			flowTotal |= (1 << connection.iDir);
+		}
+	}
+
+	/**
+	 * Iterate over all connections. Use this method if the iteration order is not
+	 * important.
+	 */
+	void forEach(Consumer<WireConnection> consumer) {
+		for (WireConnection c = head; c != null; c = c.next) {
+			consumer.accept(c);
+		}
+	}
+
+	/**
+	 * Iterate over all connections. Use this method if the iteration order is
+	 * important.
+	 */
+	void forEach(Consumer<WireConnection> consumer, int iFlowDir) {
+		for (int iDir : WireHandler.CARDINAL_UPDATE_ORDERS[iFlowDir]) {
+			for (WireConnection c = heads[iDir]; c != null && c.iDir == iDir; c = c.next) {
+				consumer.accept(c);
+			}
+		}
+	}
+}
diff --git a/game/core/src/main/java/net/minecraft/core/current/wire/WireHandler.java b/game/core/src/main/java/net/minecraft/core/current/wire/WireHandler.java
new file mode 100644
index 000000000..d72950e5b
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/current/wire/WireHandler.java
@@ -0,0 +1,1103 @@
+package net.minecraft.core.current.wire;
+
+import net.minecraft.core.current.util.BlockPos;
+import net.minecraft.core.current.util.BlockState;
+import net.minecraft.core.current.util.Direction;
+import net.minecraft.core.current.util.Redstone;
+import net.minecraft.core.block.Block;
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.world.World;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Queue;
+import java.util.function.Consumer;
+
+/**
+ * This class handles power changes for redstone wire. The algorithm was
+ * designed with the following goals in mind:
+ * <br>
+ * 1. Minimize the number of times a wire checks its surroundings to determine
+ * its power level.
+ * <br>
+ * 2. Minimize the number of block and shape updates emitted.
+ * <br>
+ * 3. Emit block and shape updates in a deterministic, non-locational order,
+ * fixing bug MC-11193.
+ *
+ * <p>
+ * In Vanilla redstone wire is laggy because it fails on points 1 and 2.
+ *
+ * <p>
+ * Redstone wire updates recursively and each wire calculates its power level in
+ * isolation rather than in the context of the network it is a part of. This
+ * means a wire in a grid can change its power level over half a dozen times
+ * before settling on its final value. This problem used to be worse in 1.13 and
+ * below, where a wire would only decrease its power level by 1 at a time.
+ *
+ * <p>
+ * In addition to this, a wire emits 42 block updates and up to 22 shape updates
+ * each time it changes its power level.
+ *
+ * <p>
+ * Of those 42 block updates, 6 are to itself, which are thus not only
+ * redundant, but a big source of lag, since those cause the wire to
+ * unnecessarily re-calculate its power level. A block only has 24 neighbors
+ * within a Manhattan distance of 2, meaning 12 of the remaining 36 block
+ * updates are duplicates and thus also redundant.
+ *
+ * <p>
+ * Of the 22 shape updates, only 6 are strictly necessary. The other 16 are sent
+ * to blocks diagonally above and below. These are necessary if a wire changes
+ * its connections, but not when it changes its power level.
+ *
+ * <p>
+ * Redstone wire in Vanilla also fails on point 3, though this is more of a
+ * quality-of-life issue than a lag issue. The recursive nature in which it
+ * updates, combined with the location-dependent order in which each wire
+ * updates its neighbors, makes the order in which neighbors of a wire network
+ * are updated incredibly inconsistent and seemingly random.
+ *
+ * <p>
+ * Alternate Current fixes each of these problems as follows.
+ *
+ * <p>
+ * 1. To make sure a wire calculates its power level as little as possible, we
+ * remove the recursive nature in which redstone wire updates in Vanilla.
+ * Instead, we build a network of connected wires, find those wires that receive
+ * redstone power from "outside" the network, and spread the power from there.
+ * This has a few advantages:
+ * <br>
+ * - Each wire checks for power from non-wire components at most once, and from
+ * nearby wires just twice.
+ * <br>
+ * - Each wire only sets its power level in the world once. This is important,
+ * because calls to World.setBlock are even more expensive than calls to
+ * World.getBlock.
+ *
+ * <p>
+ * 2. There are 2 obvious ways in which we can reduce the number of block and
+ * shape updates.
+ * <br>
+ * - Get rid of the 18 redundant block updates and 16 redundant shape updates,
+ * so each wire only emits 24 block updates and 6 shape updates whenever it
+ * changes its power level.
+ * <br>
+ * - Only emit block updates and shape updates once a wire reaches its final
+ * power level, rather than at each intermediary stage.
+ * <br>
+ * For an individual wire, these two optimizations are the best you can do, but
+ * for an entire grid, you can do better!
+ *
+ * <p>
+ * Since we calculate the power of the entire network, sending block and shape
+ * updates to the wires in it is redundant. Removing those updates can reduce
+ * the number of block and shape updates by up to 20%.
+ *
+ * <p>
+ * 3. To make the order of block updates to neighbors of a network
+ * deterministic, the first thing we must do is to replace the location-
+ * dependent order in which a wire updates its neighbors. Instead, we base it on
+ * the direction of power flow. This part of the algorithm was heavily inspired
+ * by theosib's 'RedstoneWireTurbo', which you can read more about in theosib's
+ * comment on Mojira <a href="https://bugs.mojang.com/browse/MC-81098?focusedCommentId=420777&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-420777">here</a>
+ * or by checking out its implementation in carpet mod <a href="https://github.com/gnembon/fabric-carpet/blob/master/src/main/java/carpet/helpers/RedstoneWireTurbo.java">here</a>.
+ *
+ * <p>
+ * The idea is to determine the direction of power flow through a wire based on
+ * the power it receives from neighboring wires. For example, if the only power
+ * a wire receives is from a neighboring wire to its west, it can be said that
+ * the direction of power flow through the wire is east.
+ *
+ * <p>
+ * We make the order of block updates to neighbors of a wire depend on what is
+ * determined to be the direction of power flow. This not only removes
+ * locationality entirely, it even removes directionality in a large number of
+ * cases. Unlike in 'RedstoneWireTurbo', however, I have decided to keep a
+ * directional element in ambiguous cases, rather than to introduce randomness,
+ * though this is trivial to change.
+ *
+ * <p>
+ * While this change fixes the block update order of individual wires, we must
+ * still address the overall block update order of a network. This turns out to
+ * be a simple fix, because of a change we made earlier: we search through the
+ * network for wires that receive power from outside it, and spread the power
+ * from there. If we make each wire transmit its power to neighboring wires in
+ * an order dependent on the direction of power flow, we end up with a
+ * non-locational and largely non-directional wire update order.
+ *
+ * @author Space Walker
+ */
+public class WireHandler {
+
+	public static class Directions {
+
+		public static final Direction[] ALL        = { Direction.WEST, Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.DOWN, Direction.UP };
+		public static final Direction[] HORIZONTAL = { Direction.WEST, Direction.NORTH, Direction.EAST, Direction.SOUTH };
+
+		// Indices for the arrays above.
+		// The cardinal directions are ordered clockwise. This allows
+		// for conversion between relative and absolute directions
+		// ('left' 'right' vs 'east' 'west') with simple arithmetic:
+		// If some Direction index 'iDir' is considered 'forward', then
+		// '(iDir + 1) & 0b11' is 'right', '(iDir + 2) & 0b11' is 'backward', etc.
+		public static final int WEST  = 0b000; // 0
+		public static final int NORTH = 0b001; // 1
+		public static final int EAST  = 0b010; // 2
+		public static final int SOUTH = 0b011; // 3
+		public static final int DOWN  = 0b100; // 4
+		public static final int UP    = 0b101; // 5
+
+		public static int iOpposite(int iDir) {
+			return iDir ^ (0b10 >>> (iDir >>> 2));
+		}
+
+		// Each array is placed at the index that encodes the direction that is missing
+		// from the array.
+		private static final int[][] I_EXCEPT = {
+			{       NORTH, EAST, SOUTH, DOWN, UP },
+			{ WEST,        EAST, SOUTH, DOWN, UP },
+			{ WEST, NORTH,       SOUTH, DOWN, UP },
+			{ WEST, NORTH, EAST,        DOWN, UP },
+			{ WEST, NORTH, EAST, SOUTH,       UP },
+			{ WEST, NORTH, EAST, SOUTH, DOWN     }
+		};
+		private static final int[][] I_EXCEPT_CARDINAL = {
+			{       NORTH, EAST, SOUTH },
+			{ WEST,        EAST, SOUTH },
+			{ WEST, NORTH,       SOUTH },
+			{ WEST, NORTH, EAST,       },
+			{ WEST, NORTH, EAST, SOUTH },
+			{ WEST, NORTH, EAST, SOUTH }
+		};
+	}
+
+	/**
+	 * This conversion table takes in information about incoming flow, and outputs
+	 * the determined outgoing flow.
+	 *
+	 * <p>
+	 * The input is a 4 bit number that encodes the incoming flow. Each bit
+	 * represents a cardinal direction, and when it is 'on', there is flow in that
+	 * direction.
+	 *
+	 * <p>
+	 * The output is a single Direction index, or -1 for ambiguous cases.
+	 *
+	 * <p>
+	 * The outgoing flow is determined as follows:
+	 *
+	 * <p>
+	 * If there is just 1 direction of incoming flow, that direction will be the
+	 * direction of outgoing flow.
+	 *
+	 * <p>
+	 * If there are 2 directions of incoming flow, and these directions are not each
+	 * other's opposites, the direction that is 'more clockwise' will be the
+	 * direction of outgoing flow. More precisely, the direction that is 1 clockwise
+	 * turn from the other is picked.
+	 *
+	 * <p>
+	 * If there are 3 directions of incoming flow, the two opposing directions
+	 * cancel each other out, and the remaining direction will be the direction of
+	 * outgoing flow.
+	 *
+	 * <p>
+	 * In all other cases, the flow is completely ambiguous.
+	 */
+	static final int[] FLOW_IN_TO_FLOW_OUT = {
+		-1,               // 0b0000: -                     -> x
+		Directions.WEST,  // 0b0001: west                  -> west
+		Directions.NORTH, // 0b0010: north                 -> north
+		Directions.NORTH, // 0b0011: west/north            -> north
+		Directions.EAST,  // 0b0100: east                  -> east
+		-1,               // 0b0101: west/east             -> x
+		Directions.EAST,  // 0b0110: north/east            -> east
+		Directions.NORTH, // 0b0111: west/north/east       -> north
+		Directions.SOUTH, // 0b1000: south                 -> south
+		Directions.WEST,  // 0b1001: west/south            -> west
+		-1,               // 0b1010: north/south           -> x
+		Directions.WEST,  // 0b1011: west/north/south      -> west
+		Directions.SOUTH, // 0b1100: east/south            -> south
+		Directions.SOUTH, // 0b1101: west/east/south       -> south
+		Directions.EAST,  // 0b1110: north/east/south      -> east
+		-1,               // 0b1111: west/north/east/south -> x
+	};
+	/**
+	 * Update orders of all directions. Given that the index encodes the direction
+	 * that is to be considered 'forward', the resulting update order is
+	 * { front, back, right, left, down, up }.
+	 */
+	static final int[][] FULL_UPDATE_ORDERS = {
+		{ Directions.WEST , Directions.EAST , Directions.NORTH, Directions.SOUTH, Directions.DOWN, Directions.UP },
+		{ Directions.NORTH, Directions.SOUTH, Directions.EAST , Directions.WEST , Directions.DOWN, Directions.UP },
+		{ Directions.EAST , Directions.WEST , Directions.SOUTH, Directions.NORTH, Directions.DOWN, Directions.UP },
+		{ Directions.SOUTH, Directions.NORTH, Directions.WEST , Directions.EAST , Directions.DOWN, Directions.UP }
+	};
+	/**
+	 * The default update order of all directions. It is equivalent to the order of
+	 * shape updates in vanilla Minecraft.
+	 */
+	static final int[] DEFAULT_FULL_UPDATE_ORDER = FULL_UPDATE_ORDERS[0];
+	/**
+	 * Update orders of cardinal directions. Given that the index encodes the
+	 * direction that is to be considered 'forward', the resulting update order is
+	 * { front, back, right, left }.
+	 */
+	static final int[][] CARDINAL_UPDATE_ORDERS = {
+		{ Directions.WEST , Directions.EAST , Directions.NORTH, Directions.SOUTH },
+		{ Directions.NORTH, Directions.SOUTH, Directions.EAST , Directions.WEST  },
+		{ Directions.EAST , Directions.WEST , Directions.SOUTH, Directions.NORTH },
+		{ Directions.SOUTH, Directions.NORTH, Directions.WEST , Directions.EAST  }
+	};
+	/**
+	 * The default update order of all cardinal directions.
+	 */
+	static final int[] DEFAULT_CARDINAL_UPDATE_ORDER = CARDINAL_UPDATE_ORDERS[0];
+
+	private static final int POWER_MIN = Redstone.SIGNAL_MIN;
+	private static final int POWER_MAX = Redstone.SIGNAL_MAX;
+	private static final int POWER_STEP = Redstone.SIGNAL_STEP;
+
+	// If Vanilla will ever multi-thread the ticking of worlds, there should
+	// be only one WireHandler per world, in case redstone updates in multiple
+	// worlds at the same time. There are already mods that add multi-threading
+	// as well.
+	private final World world;
+
+	/** Map of wires and neighboring blocks. */
+	private final Map<BlockPos, Node> nodes;
+	/** Queue for the breadth-first search through the network. */
+	private final Queue<WireNode> search;
+	/** Queue of updates to wires and neighboring blocks. */
+	private final Queue<Node> updates;
+
+	// Rather than creating new nodes every time a network is updated we keep
+	// a cache of nodes that can be re-used.
+	private Node[] nodeCache;
+	private int nodeCount;
+
+	/** Is this WireHandler currently working through the update queue? */
+	private boolean updating;
+
+	public WireHandler(World world) {
+		this.world = world;
+
+		this.nodes = new HashMap<>();
+		this.search = new SimpleQueue();
+		this.updates = new PriorityQueue();
+
+		this.nodeCache = new Node[16];
+		this.fillNodeCache(0, 16);
+	}
+
+	/**
+	 * Retrieve the {@link Node Node} that represents the
+	 * block at the given position in the world.
+	 */
+	private Node getOrAddNode(BlockPos pos) {
+		return nodes.compute(pos, (key, node) -> {
+			if (node == null) {
+				// If there is not yet a node at this position, retrieve and
+				// update one from the cache.
+				return getNextNode(pos);
+			}
+			if (node.invalid) {
+				return revalidateNode(node);
+			}
+
+			return node;
+		});
+	}
+
+	/**
+	 * Remove and return the {@link Node Node} at the given
+	 * position.
+	 */
+	private Node removeNode(BlockPos pos) {
+		return nodes.remove(pos);
+	}
+
+	/**
+	 * Return a {@link Node Node} that represents the block
+	 * at the given position.
+	 */
+	private Node getNextNode(BlockPos pos) {
+		return getNextNode(pos, WorldHelper.getBlockState(world, pos));
+	}
+
+	/**
+	 * Return a node that represents the given position and block state. If it is a
+	 * wire, then create a new {@link WireNode WireNode}.
+	 * Otherwise, grab the next {@link Node Node} from the
+	 * cache and update it.
+	 */
+	private Node getNextNode(BlockPos pos, BlockState state) {
+		return state.is(Blocks.WIRE_REDSTONE) ? new WireNode(world, pos, state) : getNextNode().set(pos, state, true);
+	}
+
+	/**
+	 * Grab the first unused node from the cache. If all of the cache is already in
+	 * use, increase it in size first.
+	 */
+	private Node getNextNode() {
+		if (nodeCount == nodeCache.length) {
+			increaseNodeCache();
+		}
+
+		return nodeCache[nodeCount++];
+	}
+
+	private void increaseNodeCache() {
+		Node[] oldCache = nodeCache;
+		nodeCache = new Node[oldCache.length << 1];
+
+		for (int index = 0; index < oldCache.length; index++) {
+			nodeCache[index] = oldCache[index];
+		}
+
+		fillNodeCache(oldCache.length, nodeCache.length);
+	}
+
+	private void fillNodeCache(int start, int end) {
+		for (int index = start; index < end; index++) {
+			nodeCache[index] = new Node(world);
+		}
+	}
+
+	/**
+	 * Try to revalidate the given node by looking at the block state that is
+	 * occupying its position. If the given node is a wire but the block state is
+	 * not, or vice versa, a new node must be created/grabbed from the cache.
+	 * Otherwise, the node can be quickly revalidated with the new block state.
+	 */
+	private Node revalidateNode(Node node) {
+		BlockPos pos = node.pos;
+		BlockState state = WorldHelper.getBlockState(world, pos);
+
+		boolean wasWire = node.isWire();
+		boolean isWire = state.is(Blocks.WIRE_REDSTONE);
+
+		if (wasWire != isWire) {
+			return getNextNode(pos, state);
+		}
+
+		node.invalid = false;
+
+		if (isWire) {
+			// No need to update the block state of this wire - it will grab
+			// the current block state just before setting power anyway.
+			WireNode wire = node.asWire();
+
+			wire.root = false;
+			wire.discovered = false;
+			wire.searched = false;
+		} else {
+			node.set(pos, state, false);
+		}
+
+		return node;
+	}
+
+	/**
+	 * Retrieve the neighbor of a node in the given direction and create a link
+	 * between the two nodes if they are not yet linked. This link makes accessing
+	 * neighbors of a node signficantly faster.
+	 */
+	private Node getNeighbor(Node node, int iDir) {
+		Node neighbor = node.neighbors[iDir];
+
+		if (neighbor == null || neighbor.invalid) {
+			Direction dir = Directions.ALL[iDir];
+			BlockPos pos = node.pos.offset(dir);
+
+			Node oldNeighbor = neighbor;
+			neighbor = getOrAddNode(pos);
+
+			if (neighbor != oldNeighbor) {
+				int iOpp = Directions.iOpposite(iDir);
+
+				node.neighbors[iDir] = neighbor;
+				neighbor.neighbors[iOpp] = node;
+			}
+		}
+
+		return neighbor;
+	}
+
+	/**
+	 * Iterate over all neighboring nodes of the given wire. The iteration order is
+	 * designed to be an extension of the default block update order, and is
+	 * determined as follows:
+	 * <br>
+	 * 1. The direction of power flow through the wire is to be considered
+	 * 'forward'. The iteration order depends on the neighbors' relative positions
+	 * to the wire.
+	 * <br>
+	 * 2. Each neighbor is identified by the step(s) you must take, starting at the
+	 * wire, to reach it. Each step is 1 block, thus the position of a neighbor is
+	 * encoded by the direction(s) of the step(s), e.g. (right), (down), (up, left),
+	 * etc.
+	 * <br>
+	 * 3. Neighbors are iterated over in pairs that lie on opposite sides of the
+	 * wire.
+	 * <br>
+	 * 4. Neighbors are iterated over in order of their distance from the wire. This
+	 * means they are iterated over in 3 groups: direct neighbors first, then
+	 * diagonal neighbors, and last are the far neighbors that are 2 blocks directly
+	 * out.
+	 * <br>
+	 * 5. The order within each group is determined using the following basic order:
+	 * { front, back, right, left, down, up }. This order was chosen because it
+	 * converts to the following order of absolute directions when west is said to
+	 * be 'forward': { west, east, north, south, down, up } - this is the order of
+	 * shape updates.
+	 */
+	private void forEachNeighbor(WireNode wire, Consumer<Node> consumer) {
+		int forward   = wire.iFlowDir;
+		int rightward = (forward + 1) & 0b11;
+		int backward  = (forward + 2) & 0b11;
+		int leftward  = (forward + 3) & 0b11;
+		int downward  = Directions.DOWN;
+		int upward    = Directions.UP;
+
+		Node front = getNeighbor(wire, forward);
+		Node right = getNeighbor(wire, rightward);
+		Node back  = getNeighbor(wire, backward);
+		Node left  = getNeighbor(wire, leftward);
+		Node below = getNeighbor(wire, downward);
+		Node above = getNeighbor(wire, upward);
+
+		// direct neighbors (6)
+		consumer.accept(front);
+		consumer.accept(back);
+		consumer.accept(right);
+		consumer.accept(left);
+		consumer.accept(below);
+		consumer.accept(above);
+
+		// diagonal neighbors (12)
+		consumer.accept(getNeighbor(front, rightward));
+		consumer.accept(getNeighbor(back, leftward));
+		consumer.accept(getNeighbor(front, leftward));
+		consumer.accept(getNeighbor(back, rightward));
+		consumer.accept(getNeighbor(front, downward));
+		consumer.accept(getNeighbor(back, upward));
+		consumer.accept(getNeighbor(front, upward));
+		consumer.accept(getNeighbor(back, downward));
+		consumer.accept(getNeighbor(right, downward));
+		consumer.accept(getNeighbor(left, upward));
+		consumer.accept(getNeighbor(right, upward));
+		consumer.accept(getNeighbor(left, downward));
+
+		// far neighbors (6)
+		consumer.accept(getNeighbor(front, forward));
+		consumer.accept(getNeighbor(back, backward));
+		consumer.accept(getNeighbor(right, rightward));
+		consumer.accept(getNeighbor(left, leftward));
+		consumer.accept(getNeighbor(below, downward));
+		consumer.accept(getNeighbor(above, upward));
+	}
+
+	/**
+	 * This method should be called whenever a wire receives a block update.
+	 */
+	public boolean onWireUpdated(BlockPos pos) {
+		Node node = getOrAddNode(pos);
+
+		invalidate();
+		findRoots(pos);
+		tryUpdate();
+
+		return node.isWire();
+	}
+
+	/**
+	 * This method should be called whenever a wire is placed.
+	 */
+	public void onWireAdded(BlockPos pos) {
+		Node node = getOrAddNode(pos);
+
+		if (!node.isWire()) {
+			return; // we should never get here
+		}
+
+		WireNode wire = node.asWire();
+		wire.added = true;
+
+		invalidate();
+		revalidateNode(wire);
+		findRoot(wire);
+		tryUpdate();
+	}
+
+	/**
+	 * This method should be called whenever a wire is removed.
+	 */
+	public void onWireRemoved(BlockPos pos, BlockState state) {
+		Node node = removeNode(pos);
+		WireNode wire;
+
+		if (node == null || !node.isWire()) {
+			wire = new WireNode(world, pos, state);
+		} else {
+			wire = node.asWire();
+		}
+
+		wire.invalid = true;
+		wire.removed = true;
+
+		// If these fields are set to 'true', the removal of this wire was part of
+		// already ongoing power changes, so we can exit early here.
+		if (updating && wire.shouldBreak) {
+			return;
+		}
+
+		invalidate();
+		revalidateNode(wire);
+		findRoot(wire);
+		tryUpdate();
+	}
+
+	/**
+	 * The nodes map is a snapshot of the state of the world. It becomes invalid
+	 * when power changes are carried out, since the block and shape updates can
+	 * lead to block changes. If these block changes cause the network to be updated
+	 * again every node must be invalidated, and revalidated before it is used
+	 * again. This ensures the power calculations of the network are accurate.
+	 */
+	private void invalidate() {
+		if (updating && !nodes.isEmpty()) {
+			nodes.forEach((key, node) -> {
+				node.invalid = true;
+			});
+		}
+	}
+
+	/**
+	 * Look for wires at and around the given position that are in an invalid state
+	 * and require power changes. These wires are called 'roots' because it is only
+	 * when these wires change power level that neighboring wires must adjust as
+	 * well.
+	 *
+	 * <p>
+	 * While it is strictly only necessary to check the wire at the given position,
+	 * if that wire is part of a network, it is beneficial to check its surroundings
+	 * for other wires that require power changes. This is because a network can
+	 * receive power at multiple points. Consider the following setup:
+	 *
+	 * <p>
+	 * (top-down view, W = wire, L = lever, _ = air/other)
+	 * <br> {@code _ _ W _ _ }
+	 * <br> {@code _ W W W _ }
+	 * <br> {@code W W L W W }
+	 * <br> {@code _ W W W _ }
+	 * <br> {@code _ _ W _ _ }
+	 *
+	 * <p>
+	 * The lever powers four wires in the network at once. If this is identified
+	 * correctly, the entire network can (un)power at once. While it is not
+	 * practical to cover every possible situation where a network is (un)powered
+	 * from multiple points at once, checking for common cases like the one
+	 * described above is relatively straight-forward.
+	 */
+	private void findRoots(BlockPos pos) {
+		Node node = getOrAddNode(pos);
+
+		if (!node.isWire()) {
+			return; // we should never get here
+		}
+
+		WireNode wire = node.asWire();
+		findRoot(wire);
+
+		// If the wire at the given position is not in an invalid state
+		// we can exit early.
+		if (!wire.searched) {
+			return;
+		}
+
+		for (int iDir : FULL_UPDATE_ORDERS[wire.iFlowDir]) {
+			Node neighbor = getNeighbor(wire, iDir);
+
+			if (neighbor.isConductor() || neighbor.isSignalSource()) {
+				findRootsAround(neighbor, Directions.iOpposite(iDir));
+			}
+		}
+	}
+
+	/**
+	 * Look for wires around the given node that require power changes.
+	 */
+	private void findRootsAround(Node node, int except) {
+		for (int iDir : Directions.I_EXCEPT_CARDINAL[except]) {
+			Node neighbor = getNeighbor(node, iDir);
+
+			if (neighbor.isWire()) {
+				findRoot(neighbor.asWire());
+			}
+		}
+	}
+
+	/**
+	 * Check if the given wire requires power changes. If it does, queue it for the
+	 * breadth-first search as a root.
+	 */
+	private void findRoot(WireNode wire) {
+		// Each wire only needs to be checked once.
+		if (wire.discovered) {
+			return;
+		}
+
+		discover(wire);
+		findExternalPower(wire);
+		findPower(wire, false);
+
+		if (needsUpdate(wire)) {
+			searchRoot(wire);
+		}
+	}
+
+	/**
+	 * Prepare the given wire for the breadth-first search. This means:
+	 * <br>
+	 * - Check if the wire should break. Rather than breaking the wire right away,
+	 * its effects are integrated into the power calculations.
+	 * <br>
+	 * - Reset the virtual and external power.
+	 * <br>
+	 * - Find connections to neighboring wires.
+	 */
+	private void discover(WireNode wire) {
+		if (wire.discovered) {
+			return;
+		}
+
+		wire.discovered = true;
+		wire.searched = false;
+
+		if (!wire.removed && !wire.shouldBreak && !wire.state.canSurvive(world, wire.pos)) {
+			wire.shouldBreak = true;
+		}
+
+		wire.virtualPower = wire.currentPower;
+		wire.externalPower = POWER_MIN - 1;
+
+		wire.connections.set(this::getNeighbor);
+	}
+
+	/**
+	 * Determine the power level the given wire receives from the blocks around it.
+	 * Power from non-wire components only needs to be computed if power from
+	 * neighboring wires has decreased, so as to determine how low the power of the
+	 * wire can fall.
+	 */
+	private void findPower(WireNode wire, boolean ignoreSearched) {
+		// As wire power is (re-)computed, flow information must be reset.
+		wire.virtualPower = wire.externalPower;
+		wire.flowIn = 0;
+
+		// If the wire is removed or going to break, its power level should always be
+		// the minimum value. This is because it (effectively) no longer exists, so
+		// cannot provide any power to neighboring wires.
+		if (wire.removed || wire.shouldBreak) {
+			return;
+		}
+
+		// Power received from neighboring wires will never exceed POWER_MAX -
+		// POWER_STEP, so if the external power is already larger than or equal to
+		// that, there is no need to check for power from neighboring wires.
+		if (wire.externalPower < (POWER_MAX - POWER_STEP)) {
+			findWirePower(wire, ignoreSearched);
+		}
+	}
+
+	/**
+	 * Determine the power the given wire receives from connected neighboring wires
+	 * and update the virtual power accordingly.
+	 */
+	private void findWirePower(WireNode wire, boolean ignoreSearched) {
+		wire.connections.forEach(connection -> {
+			if (!connection.accept) {
+				return;
+			}
+
+			WireNode neighbor = connection.wire;
+
+			if (!ignoreSearched || !neighbor.searched) {
+				int power = Math.max(POWER_MIN, neighbor.virtualPower - POWER_STEP);
+				int iOpp = Directions.iOpposite(connection.iDir);
+
+				wire.offerPower(power, iOpp);
+			}
+		});
+	}
+
+	/**
+	 * Determine the redstone signal the given wire receives from non-wire
+	 * components and update the virtual power accordingly.
+	 */
+	private void findExternalPower(WireNode wire) {
+		// If the wire is removed or going to break, its power level should always be
+		// the minimum value. Thus external power need not be computed.
+		// In other cases external power need only be computed once.
+		if (wire.removed || wire.shouldBreak || wire.externalPower >= POWER_MIN) {
+			return;
+		}
+
+		wire.externalPower = hasExternalPower(wire) ? Redstone.SIGNAL_MAX : Redstone.SIGNAL_MIN;
+
+		if (wire.externalPower > wire.virtualPower) {
+			wire.virtualPower = wire.externalPower;
+		}
+	}
+
+	/**
+	 * Determine whether the given wire receives a redstone signal from non-wire
+	 * components.
+	 */
+	private boolean hasExternalPower(WireNode wire) {
+		for (int iDir = 0; iDir < Directions.ALL.length; iDir++) {
+			Node neighbor = getNeighbor(wire, iDir);
+
+			// Power from wires is handled separately.
+			if (neighbor.isWire()) {
+				continue;
+			}
+
+			// Since 1.16 there is a block that is both a conductor and a signal
+			// source: the target block!
+			if (neighbor.isConductor() && hasDirectSignalTo(wire, neighbor, Directions.iOpposite(iDir))) {
+				return true;
+			}
+			if (neighbor.isSignalSource() && neighbor.state.hasSignal(world, neighbor.pos, Directions.ALL[iDir])) {
+				return true;
+			}
+		}
+
+		return false;
+	}
+
+	/**
+	 * Determine whether the given wire receives a direct signal from non-wire
+	 * components through the given conductor node.
+	 */
+	private boolean hasDirectSignalTo(WireNode wire, Node node, int except) {
+		for (int iDir : Directions.I_EXCEPT[except]) {
+			Node neighbor = getNeighbor(node, iDir);
+
+			if (neighbor.isSignalSource() && neighbor.state.hasDirectSignal(world, neighbor.pos, Directions.ALL[iDir])) {
+				return true;
+			}
+		}
+
+		return false;
+	}
+
+	/**
+	 * Check if the given wire needs to update its state in the world.
+	 */
+	private boolean needsUpdate(WireNode wire) {
+		return wire.removed || wire.shouldBreak || wire.virtualPower != wire.currentPower;
+	}
+
+	/**
+	 * Queue the given wire for the breadth-first search as a root.
+	 */
+	private void searchRoot(WireNode wire) {
+		int iBackupFlowDir;
+
+		if (wire.connections.iFlowDir < 0) {
+			iBackupFlowDir = 0;
+		} else {
+			iBackupFlowDir = wire.connections.iFlowDir;
+		}
+
+		search(wire, true, iBackupFlowDir);
+	}
+
+	/**
+	 * Queue the given wire for the breadth-first search and set a backup flow
+	 * direction.
+	 */
+	private void search(WireNode wire, boolean root, int iBackupFlowDir) {
+		search.offer(wire);
+
+		wire.root = root;
+		wire.searched = true;
+		// Normally the flow is not set until the power level is updated. However,
+		// in networks with multiple power sources the update order between them
+		// depends on which was discovered first. To make this less prone to
+		// directionality, each wire node is given a 'backup' flow. For roots, this
+		// is the determined flow of their connections. For non-roots this is the
+		// direction from which they were discovered.
+		wire.iFlowDir = iBackupFlowDir;
+	}
+
+	private void tryUpdate() {
+		if (!search.isEmpty()) {
+			update();
+		}
+		if (!updating) {
+			nodes.clear();
+			nodeCount = 0;
+		}
+	}
+
+	/**
+	 * Update the network and neighboring blocks. This is done in 3 steps.
+	 *
+	 * <p>
+	 * <b>1. Search through the network</b>
+	 * <br>
+	 * Conduct a breadth-first search around the roots to find wires that are in an
+	 * invalid state and need power changes.
+	 *
+	 * <p>
+	 * <b>2. Depower the network</b>
+	 * <br>
+	 * Depower all wires in the network. This allows power to be spread most
+	 * efficiently.
+	 *
+	 * <p>
+	 * <b>3. Power the network</b>
+	 * <br>
+	 * Work through the update queue, setting the new power level of each wire and
+	 * updating neighboring blocks. After a wire has updated its power level, it
+	 * will queue updates for neighboring wires and blocks.
+	 */
+	private void update() {
+		// The profiler keeps track of how long various parts of the algorithm take.
+		// It is only here for debugging purposes, and is commented out in production.
+//		Profiler profiler = AlternateCurrentMod.createProfiler();
+//		profiler.start();
+
+		// Search through the network for wires that need power changes. This includes
+		// the roots as well as any wires that will be affected by power changes to
+		// those roots.
+//		profiler.push("search network");
+		searchNetwork();
+
+		// Depower all the wires in the network.
+//		profiler.swap("depower network");
+		depowerNetwork();
+
+		// Bring each wire up to its new power level and update neighboring blocks.
+//		profiler.swap("power network");
+		try {
+			powerNetwork();
+		} catch (Throwable t) {
+			// If anything goes wrong while carrying out power changes, this field must
+			// be reset to 'false', or the wire handler will be locked out of carrying
+			// out power changes until the world is reloaded.
+			updating = false;
+
+			throw t;
+//		} finally {
+//			profiler.pop();
+//			profiler.end();
+		}
+	}
+
+	/**
+	 * Search through the network for wires that are in an invalid state and need
+	 * power changes. These wires are added to the end of the queue, so that their
+	 * neighbors can be searched next.
+	 */
+	private void searchNetwork() {
+		for (WireNode wire : search) {
+			// The order in which wires are searched will influence the order in
+			// which they update their power levels.
+			wire.connections.forEach(connection -> {
+				if (!connection.offer) {
+					return;
+				}
+
+				WireNode neighbor = connection.wire;
+
+				if (neighbor.searched) {
+					return;
+				}
+
+				discover(neighbor);
+				findPower(neighbor, false);
+
+				// If power from neighboring wires has decreased, check for power
+				// from non-wire components so as to determine how low power can
+				// fall.
+				if (neighbor.virtualPower < neighbor.currentPower) {
+					findExternalPower(neighbor);
+				}
+
+				if (needsUpdate(neighbor)) {
+					search(neighbor, false, connection.iDir);
+				}
+			}, wire.iFlowDir);
+		}
+	}
+
+	/**
+	 * Depower all wires in the network so that power can be spread from the power
+	 * sources.
+	 */
+	private void depowerNetwork() {
+		while (!search.isEmpty()) {
+			WireNode wire = search.poll();
+			findPower(wire, true);
+
+			if (wire.root || wire.removed || wire.shouldBreak || wire.virtualPower > POWER_MIN) {
+				queueWire(wire);
+			} else {
+				// Wires that do not receive any power do not queue power changes
+				// until they are offered power from a neighboring wire. To ensure
+				// that they accept any power from neighboring wires and thus queue
+				// their power changes, their virtual power is set to below the
+				// minimum.
+				wire.virtualPower--;
+			}
+		}
+	}
+
+	/**
+	 * Work through the update queue, setting the new power level of each wire, then
+	 * queueing updates to connected wires and neighboring blocks.
+	 */
+	private void powerNetwork() {
+		// If an instantaneous update chain causes updates to another network
+		// (or the same network in another place), new power changes will be
+		// integrated into the already ongoing power queue, so we can exit early
+		// here.
+		if (updating) {
+			return;
+		}
+
+		updating = true;
+
+		while (!updates.isEmpty()) {
+			Node node = updates.poll();
+
+			if (node.isWire()) {
+				WireNode wire = node.asWire();
+
+				if (!needsUpdate(wire)) {
+					continue;
+				}
+
+				findPowerFlow(wire);
+				transmitPower(wire);
+
+				if (wire.setPower()) {
+					queueNeighbors(wire);
+				}
+			} else {
+				WireNode neighborWire = node.neighborWire;
+
+				if (neighborWire != null) {
+					updateBlock(node, neighborWire.state.getBlock());
+				}
+			}
+		}
+
+		updating = false;
+	}
+
+	/**
+	 * Use the information of incoming power flow to determine the direction of
+	 * power flow through this wire. If that flow is ambiguous, try to use a flow
+	 * direction based on connections to neighboring wires. If that is also
+	 * ambiguous, use the backup value that was set when the wire was first added to
+	 * the network.
+	 */
+	private void findPowerFlow(WireNode wire) {
+		int flow = FLOW_IN_TO_FLOW_OUT[wire.flowIn];
+
+		if (flow >= 0) {
+			wire.iFlowDir = flow;
+		} else if (wire.connections.iFlowDir >= 0) {
+			wire.iFlowDir = wire.connections.iFlowDir;
+		}
+	}
+
+	/**
+	 * Transmit power from the given wire to neighboring wires and queue updates to
+	 * those wires.
+	 */
+	private void transmitPower(WireNode wire) {
+		wire.connections.forEach(connection -> {
+			if (!connection.offer) {
+				return;
+			}
+
+			WireNode neighbor = connection.wire;
+
+			int power = Math.max(POWER_MIN, wire.virtualPower - POWER_STEP);
+			int iDir = connection.iDir;
+
+			if (neighbor.offerPower(power, iDir)) {
+				queueWire(neighbor);
+			}
+		}, wire.iFlowDir);
+	}
+
+	/**
+	 * Queue block updates to nodes around the given wire.
+	 */
+	private void queueNeighbors(WireNode wire) {
+		forEachNeighbor(wire, neighbor -> {
+			queueNeighbor(neighbor, wire);
+		});
+	}
+
+	/**
+	 * Queue the given node for an update from the given neighboring wire.
+	 */
+	private void queueNeighbor(Node node, WireNode neighborWire) {
+		// Updates to wires are queued when power is transmitted.
+		if (!node.isWire()) {
+			node.neighborWire = neighborWire;
+			updates.offer(node);
+		}
+	}
+
+	/**
+	 * Queue the given wire for a power change. If the wire does not need a power
+	 * change (perhaps because its power has already changed), transmit power to
+	 * neighboring wires.
+	 */
+	private void queueWire(WireNode wire) {
+		if (needsUpdate(wire)) {
+			updates.offer(wire);
+		} else {
+			findPowerFlow(wire);
+			transmitPower(wire);
+		}
+	}
+
+	/**
+	 * Emit a block update to the given node.
+	 */
+	private void updateBlock(Node node, Block<?> neighborBlock) {
+		BlockPos pos = node.pos;
+		BlockState state = WorldHelper.getBlockState(world, pos);
+
+		// While this check makes sure wires in the network are not given block
+		// updates, it also prevents block updates to wires in neighboring networks.
+		// While this should not make a difference in theory, in practice, it is
+		// possible to force a network into an invalid state without updating it, even
+		// if it is relatively obscure.
+		// While I was willing to make this compromise in return for some significant
+		// performance gains in certain setups, if you are not, you can add all the
+		// positions of the network to a set and filter out block updates to wires in
+		// the network that way.
+		if (!state.isAir() && !state.is(Blocks.WIRE_REDSTONE)) {
+			state.neighborChanged(world, pos, neighborBlock);
+		}
+	}
+
+	@FunctionalInterface
+	public static interface NodeProvider {
+
+		public Node getNeighbor(Node node, int iDir);
+
+	}
+}
diff --git a/game/core/src/main/java/net/minecraft/core/current/wire/WireNode.java b/game/core/src/main/java/net/minecraft/core/current/wire/WireNode.java
new file mode 100644
index 000000000..3813cb851
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/current/wire/WireNode.java
@@ -0,0 +1,120 @@
+package net.minecraft.core.current.wire;
+
+import net.minecraft.core.current.util.BlockPos;
+import net.minecraft.core.current.util.BlockState;
+import net.minecraft.core.current.util.Mth;
+import net.minecraft.core.current.util.Redstone;
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.world.World;
+
+/**
+ * A WireNode is a Node that represents a wire in the world. It stores all the
+ * information about the wire that the WireHandler needs to calculate power
+ * changes.
+ *
+ * @author Space Walker
+ */
+public class WireNode extends Node {
+
+	final WireConnectionManager connections;
+
+	/** The power level this wire currently holds in the world. */
+	int currentPower;
+	/**
+	 * While calculating power changes for a network, this field is used to keep
+	 * track of the power level this wire should have.
+	 */
+	int virtualPower;
+	/** The power level received from non-wire components. */
+	int externalPower;
+	/**
+	 * A 4-bit number that keeps track of the power flow of the wires that give this
+	 * wire its power level.
+	 */
+	int flowIn;
+	/** The direction of power flow, based on the incoming flow. */
+	int iFlowDir;
+	boolean added;
+	boolean removed;
+	boolean shouldBreak;
+	boolean root;
+	boolean discovered;
+	boolean searched;
+
+	/** The next wire in the simple queue. */
+	WireNode next_wire;
+
+	WireNode(World world, BlockPos pos, BlockState state) {
+		super(world);
+
+		this.pos = pos;
+		this.state = state;
+
+		this.connections = new WireConnectionManager(this);
+
+		this.virtualPower = this.currentPower = this.state.get();
+		this.priority = priority();
+	}
+
+	@Override
+	Node set(BlockPos pos, BlockState state, boolean clearNeighbors) {
+		throw new UnsupportedOperationException("Cannot update a WireNode!");
+	}
+
+	@Override
+	int priority() {
+		return Mth.clamp(virtualPower, Redstone.SIGNAL_MIN, Redstone.SIGNAL_MAX);
+	}
+
+	@Override
+	public boolean isWire() {
+		return true;
+	}
+
+	@Override
+	public WireNode asWire() {
+		return this;
+	}
+
+	boolean offerPower(int power, int iDir) {
+		if (removed || shouldBreak) {
+			return false;
+		}
+		if (power == virtualPower) {
+			flowIn |= (1 << iDir);
+			return false;
+		}
+		if (power > virtualPower) {
+			virtualPower = power;
+			flowIn = (1 << iDir);
+
+			return true;
+		}
+
+		return false;
+	}
+
+	boolean setPower() {
+		if (removed) {
+			return true;
+		}
+
+		state = WorldHelper.getBlockState(world, pos);
+
+		if (!state.is(Blocks.WIRE_REDSTONE)) {
+			return false; // we should never get here
+		}
+
+		if (shouldBreak) {
+			state.dropItems(world, pos);
+			world.setBlock(pos.x, pos.y, pos.z, 0);
+
+			return true;
+		}
+
+		currentPower = Mth.clamp(virtualPower, Redstone.SIGNAL_MIN, Redstone.SIGNAL_MAX);
+		state = state.set(currentPower);
+
+		return WorldHelper.setWireState(world, pos, state);
+	}
+}
diff --git a/game/core/src/main/java/net/minecraft/core/current/wire/WorldHelper.java b/game/core/src/main/java/net/minecraft/core/current/wire/WorldHelper.java
new file mode 100644
index 000000000..939a59990
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/current/wire/WorldHelper.java
@@ -0,0 +1,59 @@
+package net.minecraft.core.current.wire;
+
+import net.minecraft.core.current.util.BlockPos;
+import net.minecraft.core.current.util.BlockState;
+import net.minecraft.core.world.World;
+
+public class WorldHelper {
+
+	private static final int Y_MIN = 0;
+
+	static BlockState getBlockState(World world, BlockPos pos) {
+		if (pos.y < Y_MIN || pos.y >= world.getHeightBlocks()) {
+			return BlockState.AIR;
+		}
+
+		int blockId = world.getBlockId(pos.x, pos.y, pos.z);
+
+		if (blockId == 0) {
+			return BlockState.AIR;
+		}
+
+		return new BlockState(blockId, world.getBlockMetadata(pos.x, pos.y, pos.z));
+	}
+
+	/**
+	 * An optimized version of {@link net.minecraft.world.World#setBlockState
+	 * World.setBlockState}. Since this method is only used to update redstone wire block
+	 * states, lighting checks, height map updates, and block entity updates are
+	 * omitted.
+	 */
+	static boolean setWireState(World world, BlockPos pos, BlockState state) {
+		if (pos.y < Y_MIN || pos.y >= world.getHeightBlocks()) {
+			return false;
+		}
+
+		int blockId = state.getBlockId();
+		int prevBlockId = world.getBlockId(pos.x, pos.y, pos.z);
+
+		if (blockId != prevBlockId) {
+			return false;
+		}
+
+		int metadata = state.get();
+		int prevMetadata = world.getBlockMetadata(pos.x, pos.y, pos.z);
+
+		if (metadata == prevMetadata) {
+			return false;
+		}
+
+		world.setBlockMetadata(pos.x, pos.y, pos.z, metadata);
+
+		// notify clients of the BlockState change
+		world.notifyBlockChange(pos.x, pos.y, pos.z, blockId);
+		// mark the chunk for saving
+		world.markBlockDirty(pos.x, pos.y, pos.z);
+
+		return true;
+	}
+}
diff --git a/game/core/src/main/java/net/minecraft/core/data/registry/Registries.java b/game/core/src/main/java/net/minecraft/core/data/registry/Registries.java
index bfc8547ba..b88295fd0 100644
--- a/game/core/src/main/java/net/minecraft/core/data/registry/Registries.java
+++ b/game/core/src/main/java/net/minecraft/core/data/registry/Registries.java
@@ -94,6 +94,8 @@ public class Registries extends Registry<Registry<?>>
             List<ItemStack> woolStackList = new ArrayList<>();
             List<ItemStack> lampStackList = new ArrayList<>();
             List<ItemStack> invertedLampStackList = new ArrayList<>();
+            List<ItemStack> woodenButtonsList = new ArrayList<>();
+            List<ItemStack> woodenPressurePlatesList = new ArrayList<>();
 
             plankStackList.add(Blocks.PLANKS_OAK.getDefaultStack());
             signStackList.add(Items.SIGN.getDefaultStack());
@@ -104,6 +106,8 @@ public class Registries extends Registry<Registry<?>>
             fencesStackList.add(Blocks.FENCE_PLANKS_OAK.getDefaultStack());
             fenceGatesStackList.add(Blocks.FENCE_GATE_PLANKS_OAK.getDefaultStack());
             trapdoorStackList.add(Blocks.TRAPDOOR_PLANKS_OAK.getDefaultStack());
+            woodenButtonsList.add(Blocks.BUTTON_PLANKS.getDefaultStack());
+            woodenPressurePlatesList.add(Blocks.PRESSURE_PLATE_PLANKS_OAK.getDefaultStack());
 
             for (DyeColor color : DyeColor.values()) {
                 plankStackList.add(new ItemStack(Blocks.PLANKS_OAK_PAINTED, 1, color.blockMeta));
@@ -118,6 +122,8 @@ public class Registries extends Registry<Registry<?>>
                 woolStackList.add(new ItemStack(Blocks.WOOL, 1, color.blockMeta));
                 lampStackList.add(new ItemStack(Blocks.LAMP_IDLE, 1, color.blockMeta));
                 invertedLampStackList.add(new ItemStack(Blocks.LAMP_INVERTED_ACTIVE, 1, color.blockMeta));
+                woodenButtonsList.add(new ItemStack(Blocks.BUTTON_PLANKS_PAINTED, 1, color.blockMeta << 4));
+                woodenPressurePlatesList.add(new ItemStack(Blocks.PRESSURE_PLATE_PLANKS_OAK_PAINTED, 1, color.blockMeta << 4));
             }
             ITEM_GROUPS.register("minecraft:planks",plankStackList);
             ITEM_GROUPS.register("minecraft:signs",signStackList);
@@ -131,6 +137,8 @@ public class Registries extends Registry<Registry<?>>
             ITEM_GROUPS.register("minecraft:fences_planks", fencesStackList);
             ITEM_GROUPS.register("minecraft:fence_gates_planks", fenceGatesStackList);
             ITEM_GROUPS.register("minecraft:trapdoor_planks", trapdoorStackList);
+            ITEM_GROUPS.register("minecraft:button_planks", woodenButtonsList);
+            ITEM_GROUPS.register("minecraft:pressureplates_planks", woodenPressurePlatesList);
         }
 
         ITEM_GROUPS.register("minecraft:tool_swords",
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemCoal.java b/game/core/src/main/java/net/minecraft/core/item/ItemCoal.java
index 0382a6e3d..a9b471384 100644
--- a/game/core/src/main/java/net/minecraft/core/item/ItemCoal.java
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemCoal.java
@@ -2,6 +2,8 @@ package net.minecraft.core.item;
 
 public class ItemCoal extends Item
 {
+    public static final int META_COAL = 0;
+    public static final int META_CHARCOAL = 1;
 
     public ItemCoal(String name, String namespaceId, int id)
     {
@@ -13,7 +15,7 @@ public class ItemCoal extends Item
     @Override
     public String getLanguageKey(ItemStack itemstack)
     {
-        if(itemstack.getMetadata() == 1)
+        if(itemstack.getMetadata() == META_CHARCOAL)
         {
             return "item.charcoal";
         } else
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemSeeds.java b/game/core/src/main/java/net/minecraft/core/item/ItemSeeds.java
index 944f96407..d90b20728 100644
--- a/game/core/src/main/java/net/minecraft/core/item/ItemSeeds.java
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemSeeds.java
@@ -1,6 +1,7 @@
 package net.minecraft.core.item;
 
 import net.minecraft.core.block.Block;
+import net.minecraft.core.block.BlockLogicFarmland;
 import net.minecraft.core.block.Blocks;
 
 import net.minecraft.core.block.entity.TileEntityActivator;
@@ -45,8 +46,9 @@ public class ItemSeeds extends Item {
 		int y = blockY + direction.getOffsetY();
 		int z = blockZ + direction.getOffsetZ();
 		Block<?> b = world.getBlock(x, y, z);
-		if (b == null || BlockTags.PLACE_OVERWRITES.appliesTo(b)) {
-			onUseItemOnBlock(itemStack, null, world, x, y, z, direction.getSide(), 0.5, 0.5);
+		boolean isFarmland = Block.hasLogicClass(b, BlockLogicFarmland.class);
+		if (b == null || isFarmland || BlockTags.PLACE_OVERWRITES.appliesTo(b)) {
+			onUseItemOnBlock(itemStack, null, world, x, y + (isFarmland ? 1 : 0), z, direction.getSide(), 0.5, 0.5);
 		}
 	}
 }
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 8edb31b85..fa8af726c 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
@@ -1,6 +1,9 @@
 package net.minecraft.core.player.inventory.menu;
 
+import net.minecraft.core.block.Block;
+import net.minecraft.core.block.IPainted;
 import net.minecraft.core.block.tag.BlockTags;
+import net.minecraft.core.item.ItemCoal;
 import net.minecraft.core.item.tag.ItemTags;
 import net.minecraft.core.lang.I18n;
 import net.minecraft.core.item.Item;
@@ -9,6 +12,7 @@ import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.player.inventory.container.ContainerInventory;
 import net.minecraft.core.player.inventory.slot.SlotCreative;
+import net.minecraft.core.util.helper.DyeColor;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -80,8 +84,9 @@ public class MenuInventoryCreative extends MenuInventory
 		searchedItems.clear();
 		page = 0;
 		I18n t = I18n.getInstance();
-		
-		for (int i = 0; i < creativeItemsCount; i++)
+
+		int itemsCount = creativeItems.size();
+		for (int i = 0; i < itemsCount; i++)
 		{
 			if (t.translateKey(creativeItems.get(i).getItemKey() + ".name").toLowerCase().contains(search.toLowerCase()))
 			{
@@ -123,71 +128,38 @@ public class MenuInventoryCreative extends MenuInventory
 	}
 	
 	public static List<ItemStack> creativeItems = new ArrayList<>();
-	public static int creativeItemsCount;
-	
+
 	static
 	{
-		int count = 0;
-		
-		for (int id = 0; id < Blocks.blocksList.length; id++)
-		{
-			if (Blocks.blocksList[id] != null && !Blocks.blocksList[id].hasTag(BlockTags.NOT_IN_CREATIVE_MENU))
-			{
-				creativeItems.add(new ItemStack(Blocks.blocksList[id]));
-				count++;
-				
-				if (id == Blocks.WOOL.id() || id == Blocks.PLANKS_OAK_PAINTED.id() || id == Blocks.LAMP_IDLE.id() || id == Blocks.LAMP_INVERTED_ACTIVE.id() || id == Blocks.FENCE_PLANKS_OAK_PAINTED.id())
-				{
-					for (int i = 1; i < 16; i++)
-					{
-						creativeItems.add(new ItemStack(Blocks.blocksList[id], 1, i));
-						count++;
-					}
-				}
-				else if (id == Blocks.CHEST_PLANKS_OAK_PAINTED.id() || id == Blocks.SLAB_PLANKS_PAINTED.id() || id == Blocks.FENCE_GATE_PLANKS_OAK_PAINTED.id()
-					  || id == Blocks.STAIRS_PLANKS_PAINTED.id() || id == Blocks.DOOR_PLANKS_PAINTED_BOTTOM.id() || id == Blocks.DOOR_PLANKS_PAINTED_TOP.id()
-					  || id == Blocks.TRAPDOOR_PLANKS_PAINTED.id())
-				{
-					for (int i = 0x10; i < 0x100; i += 0x10)
-					{
-						creativeItems.add(new ItemStack(Blocks.blocksList[id], 1, i));
-						count++;
-					}
+		for (Block<?> block : Blocks.blocksList) {
+			if (block == null || block.hasTag(BlockTags.NOT_IN_CREATIVE_MENU)) continue;
+
+			if (Block.hasLogicClass(block, IPainted.class)) {
+				IPainted painted = (IPainted) block.getLogic();
+				for (DyeColor color : DyeColor.values()) {
+					creativeItems.add(new ItemStack(block, 1, painted.toMetadata(color)));
 				}
+			} else {
+				creativeItems.add(new ItemStack(block));
 			}
 		}
 		
 		for (int i = Blocks.blocksList.length; i < Item.itemsList.length; i++)
 		{
-			if (Item.itemsList[i] != null && !Item.itemsList[i].hasTag(ItemTags.NOT_IN_CREATIVE_MENU))
-			{
-				if (i == Items.PAINTBRUSH.id) {
-					creativeItems.add(new ItemStack(Item.itemsList[i], 1, Items.PAINTBRUSH.getMaxDamage()));
-				} else {
-					creativeItems.add(new ItemStack(Item.itemsList[i]));
-				}
-				count++;
-
-				if (i == Items.COAL.id)
-				{
-					for (int j = 1; j < 2; j++)
-					{
-						creativeItems.add(new ItemStack(Item.itemsList[i], 1, j));
-						count++;
-					}
-				}
-
-				if (i == Items.DYE.id || i == Items.DOOR_OAK_PAINTED.id || i == Items.SIGN_PAINTED.id)
-				{
-					for (int j = 1; j < 16; j++)
-					{
-						creativeItems.add(new ItemStack(Item.itemsList[i], 1, j));
-						count++;
-					}
+			Item item = Item.itemsList[i];
+			if (item == null || item.hasTag(ItemTags.NOT_IN_CREATIVE_MENU)) continue;
+			if (item == Items.PAINTBRUSH) {
+				creativeItems.add(new ItemStack(item, 1, Items.PAINTBRUSH.getMaxDamage()));
+			} else if (item == Items.COAL) {
+				creativeItems.add(new ItemStack(item, 1, ItemCoal.META_COAL));
+				creativeItems.add(new ItemStack(item, 1, ItemCoal.META_CHARCOAL));
+			} else if (item == Items.DYE || item == Items.DOOR_OAK_PAINTED || item == Items.SIGN_PAINTED) {
+				for (DyeColor color : DyeColor.values()) {
+					creativeItems.add(new ItemStack(item, 1, color.itemMeta));
 				}
+			} else {
+				creativeItems.add(new ItemStack(item));
 			}
 		}
-		
-		creativeItemsCount = count;
 	}
 }
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 d6aec1900..129b38da5 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
@@ -16,6 +16,7 @@ import com.mojang.nbt.tags.CompoundTag;
 
 import net.minecraft.core.block.BlockLogicOreRedstone;
 import net.minecraft.core.block.entity.TileEntity;
+import net.minecraft.core.current.wire.WireHandler;
 import net.minecraft.core.entity.EntityItem;
 import net.minecraft.core.entity.animal.MobFireflyCluster;
 import net.minecraft.core.enums.Difficulty;
@@ -139,6 +140,7 @@ public class World implements WorldSource {
     public WeatherManager weatherManager;
 
     private CommandManager commandManager;
+    private final @NotNull WireHandler wireHandler;
 
     //Usually called when a world is loaded or generated (Singleplayer & Server)
     public World(LevelStorage saveHandler, String name, long seed, Dimension dimension, WorldType worldType)
@@ -228,6 +230,8 @@ public class World implements WorldSource {
 
         this.commandManager = new CommandManager(isServer);
         this.commandManager.init();
+
+        this.wireHandler = new WireHandler(this);
     }
 
     //Usually called when going through portals
@@ -270,6 +274,8 @@ public class World implements WorldSource {
 
         this.commandManager = new CommandManager(isServer);
         this.commandManager.init();
+
+        this.wireHandler = new WireHandler(this);
     }
 
     //Usually called when joining a server
@@ -293,9 +299,13 @@ public class World implements WorldSource {
 
         this.commandManager = new CommandManager(isServer);
         this.commandManager.init();
+
+        this.wireHandler = new WireHandler(this);
     }
 
-    public World(){}
+    public World(){
+        this.wireHandler = new WireHandler(this);
+    }
 
     public <T> T getGameRuleValue(GameRule<T> gameRule)
     {
@@ -320,6 +330,10 @@ public class World implements WorldSource {
         return commandManager;
     }
 
+    public @NotNull WireHandler getWireHandler() {
+        return this.wireHandler;
+    }
+
     protected IChunkProvider createChunkProvider()
     {
         IChunkLoader chunkLoader = saveHandler.getChunkLoader(dimension);
@@ -2647,15 +2661,31 @@ public class World implements WorldSource {
 
     public boolean getSignal(int x, int y, int z, Side side) {
         Block<?> block = getBlock(x, y, z);
-        if(isBlockNormalCube(x, y, z) && block != Blocks.BLOCK_REDSTONE && !Block.hasLogicClass(block, BlockLogicOreRedstone.class) && block != Blocks.PUMPKIN_REDSTONE) {
+        if(isBlockNormalCube(x, y, z) && block != Blocks.BLOCK_REDSTONE  && block != Blocks.PUMPKIN_REDSTONE) {
             return hasDirectSignal(x, y, z);
         }
         if(block == null) {
             return false;
         }
 
-        return block.getSignal(this, x, y, z, side);
+        if (block == Blocks.PUMPKIN_REDSTONE) { // TODO make actual block methods to handle sided-ness
+            return pumpkinHasDirectSignal(x, y, z) || block.getSignal(this, x, y, z, side);
+        } else {
+            return block.getSignal(this, x, y, z, side);
+        }
     }
+    // Special version of hasDirectSignal that excludes the front side of the redstone lantern.
+    private boolean pumpkinHasDirectSignal(int x, int y, int z) {
+        for (Side side : Side.sides) {
+            if(Side.getSideById(getBlockMetadata(x, y, z)) == side) continue;
+            if (getDirectSignal(x + side.getOffsetX(), y + side.getOffsetY(), z + side.getOffsetZ(), side)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
 
     public boolean hasNeighborSignal(int x, int y, int z) {
         for (Side side : Side.sides){
diff --git a/game/core/src/main/resources/lang/en_US/options.lang b/game/core/src/main/resources/lang/en_US/options.lang
index f85f82a36..316facd76 100644
--- a/game/core/src/main/resources/lang/en_US/options.lang
+++ b/game/core/src/main/resources/lang/en_US/options.lang
@@ -128,10 +128,13 @@ options.bloom=Bloom
 options.bloom.low=Low
 options.bloom.high=High
 options.renderScale=Render Scale
+options.renderScale.scale_0625=0.0625x
+options.renderScale.scale_125=0.125x
 options.renderScale.scale_25=0.25x
 options.renderScale.scale_50=0.5x
 options.renderScale.scale_100=1x
 options.renderScale.scale_200=2x
+options.renderScale.scale_400=4x
 options.heatHaze=Heat Haze
 options.showCollisionBoxes=Show Collision Boxes
 options.showDebugScreen=Show Debug Screen
diff --git a/game/core/src/main/resources/lang/en_US/tile.lang b/game/core/src/main/resources/lang/en_US/tile.lang
index 632881212..14952b2b0 100644
--- a/game/core/src/main/resources/lang/en_US/tile.lang
+++ b/game/core/src/main/resources/lang/en_US/tile.lang
@@ -672,8 +672,45 @@ tile.torch.redstone.idle.desc=Emits a constant redstone signal.
 tile.torch.redstone.active.name=Redstone Torch
 tile.torch.redstone.active.desc=Emits a constant redstone signal.
 
-tile.button.stone.name=Button
+tile.button.stone.name=Stone Button
 tile.button.stone.desc=Emits a brief redstone signal when pressed.
+tile.button.planks.name=Wooden Button
+tile.button.planks.desc=Emits a brief redstone signal when pressed.
+
+tile.button.planks.painted.name=Mystery Button
+tile.button.planks.painted.desc=Emits a brief redstone signal when pressed.
+tile.button.planks.painted.black.name=Black Wooden Button
+tile.button.planks.painted.black.desc=Emits a brief redstone signal when pressed.
+tile.button.planks.painted.red.name=Red Wooden Button
+tile.button.planks.painted.red.desc=Emits a brief redstone signal when pressed.
+tile.button.planks.painted.green.name=Green Wooden Button
+tile.button.planks.painted.green.desc=Emits a brief redstone signal when pressed.
+tile.button.planks.painted.brown.name=Brown Wooden Button
+tile.button.planks.painted.brown.desc=Emits a brief redstone signal when pressed.
+tile.button.planks.painted.blue.name=Blue Wooden Button
+tile.button.planks.painted.blue.desc=Emits a brief redstone signal when pressed.
+tile.button.planks.painted.purple.name=Purple Wooden Button
+tile.button.planks.painted.purple.desc=Emits a brief redstone signal when pressed.
+tile.button.planks.painted.cyan.name=Cyan Wooden Button
+tile.button.planks.painted.cyan.desc=Emits a brief redstone signal when pressed.
+tile.button.planks.painted.silver.name=Light Gray Wooden Button
+tile.button.planks.painted.silver.desc=Emits a brief redstone signal when pressed.
+tile.button.planks.painted.gray.name=Gray Wooden Button
+tile.button.planks.painted.gray.desc=Emits a brief redstone signal when pressed.
+tile.button.planks.painted.pink.name=Pink Wooden Button
+tile.button.planks.painted.pink.desc=Emits a brief redstone signal when pressed.
+tile.button.planks.painted.lime.name=Lime Wooden Button
+tile.button.planks.painted.lime.desc=Emits a brief redstone signal when pressed.
+tile.button.planks.painted.yellow.name=Yellow Wooden Button
+tile.button.planks.painted.yellow.desc=Emits a brief redstone signal when pressed.
+tile.button.planks.painted.lightblue.name=Light Blue Wooden Button
+tile.button.planks.painted.lightblue.desc=Emits a brief redstone signal when pressed.
+tile.button.planks.painted.magenta.name=Magenta Wooden Button
+tile.button.planks.painted.magenta.desc=Emits a brief redstone signal when pressed.
+tile.button.planks.painted.orange.name=Orange Wooden Button
+tile.button.planks.painted.orange.desc=Emits a brief redstone signal when pressed.
+tile.button.planks.painted.white.name=White Wooden Button
+tile.button.planks.painted.white.desc=Emits a brief redstone signal when pressed.
 
 tile.lever.cobble.stone.name=Lever
 tile.lever.cobble.stone.desc=Allows turning a redstone signal on or off.
@@ -685,6 +722,41 @@ tile.pressureplate.planks.oak.desc=Emits a redstone signal when anything steps o
 tile.pressureplate.cobble.stone.name=Cobblestone Pressure Plate
 tile.pressureplate.cobble.stone.desc=Emits a redstone signal when players step on it.
 
+tile.pressureplate.planks.oak.painted.name=Mystery Wooden Plate
+tile.pressureplate.planks.oak.painted.desc=Emits a redstone signal when anything steps on it.
+tile.pressureplate.planks.oak.painted.black.name=Black Wooden Pressure Plate
+tile.pressureplate.planks.oak.painted.black.desc=Emits a redstone signal when anything steps on it.
+tile.pressureplate.planks.oak.painted.red.name=Red Wooden Pressure Plate
+tile.pressureplate.planks.oak.painted.red.desc=Emits a redstone signal when anything steps on it.
+tile.pressureplate.planks.oak.painted.green.name=Green Wooden Pressure Plate
+tile.pressureplate.planks.oak.painted.green.desc=Emits a redstone signal when anything steps on it.
+tile.pressureplate.planks.oak.painted.brown.name=Brown Wooden Pressure Plate
+tile.pressureplate.planks.oak.painted.brown.desc=Emits a redstone signal when anything steps on it.
+tile.pressureplate.planks.oak.painted.blue.name=Blue Wooden Pressure Plate
+tile.pressureplate.planks.oak.painted.blue.desc=Emits a redstone signal when anything steps on it.
+tile.pressureplate.planks.oak.painted.purple.name=Purple Wooden Pressure Plate
+tile.pressureplate.planks.oak.painted.purple.desc=Emits a redstone signal when anything steps on it.
+tile.pressureplate.planks.oak.painted.cyan.name=Cyan Wooden Pressure Plate
+tile.pressureplate.planks.oak.painted.cyan.desc=Emits a redstone signal when anything steps on it.
+tile.pressureplate.planks.oak.painted.silver.name=Light Gray Wooden Pressure Plate
+tile.pressureplate.planks.oak.painted.silver.desc=Emits a redstone signal when anything steps on it.
+tile.pressureplate.planks.oak.painted.gray.name=Gray Wooden Pressure Plate
+tile.pressureplate.planks.oak.painted.gray.desc=Emits a redstone signal when anything steps on it.
+tile.pressureplate.planks.oak.painted.pink.name=Pink Wooden Pressure Plate
+tile.pressureplate.planks.oak.painted.pink.desc=Emits a redstone signal when anything steps on it.
+tile.pressureplate.planks.oak.painted.lime.name=Lime Wooden Pressure Plate
+tile.pressureplate.planks.oak.painted.lime.desc=Emits a redstone signal when anything steps on it.
+tile.pressureplate.planks.oak.painted.yellow.name=Yellow Wooden Pressure Plate
+tile.pressureplate.planks.oak.painted.yellow.desc=Emits a redstone signal when anything steps on it.
+tile.pressureplate.planks.oak.painted.lightblue.name=Light Blue Wooden Pressure Plate
+tile.pressureplate.planks.oak.painted.lightblue.desc=Emits a redstone signal when anything steps on it.
+tile.pressureplate.planks.oak.painted.magenta.name=Magenta Wooden Pressure Plate
+tile.pressureplate.planks.oak.painted.magenta.desc=Emits a redstone signal when anything steps on it.
+tile.pressureplate.planks.oak.painted.orange.name=Orange Wooden Pressure Plate
+tile.pressureplate.planks.oak.painted.orange.desc=Emits a redstone signal when anything steps on it.
+tile.pressureplate.planks.oak.painted.white.name=White Wooden Pressure Plate
+tile.pressureplate.planks.oak.painted.white.desc=Emits a redstone signal when anything steps on it.
+
 tile.motionsensor.idle.name=Motion Sensor
 tile.motionsensor.idle.desc=Emits a redstone signal when something moves past it.
 tile.motionsensor.active.name=Active Motion Sensor
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 0482f3a12..d9c3fbbea 100644
--- a/util/datagen/src/main/java/net/minecraft/datagen/WorkbenchGenerator.java
+++ b/util/datagen/src/main/java/net/minecraft/datagen/WorkbenchGenerator.java
@@ -1169,8 +1169,11 @@ class WorkbenchGenerator {
         RecipeBuilderShaped plate = RecipeBuilder.Shaped(CORE_NAMESPACE).setShape("##");
 
         plate.addInput('#', Blocks.STONE).create("stone_pressure_plate", Blocks.PRESSURE_PLATE_STONE.getDefaultStack());
-        plate.addInput('#', "minecraft:planks").create("wooden_pressure_plate", Blocks.PRESSURE_PLATE_PLANKS_OAK.getDefaultStack());
+        plate.addInput('#', Blocks.PLANKS_OAK).create("wooden_pressure_plate", Blocks.PRESSURE_PLATE_PLANKS_OAK.getDefaultStack());
         plate.addInput('#', "minecraft:cobblestones").create("cobblestone_pressure_plate", Blocks.PRESSURE_PLATE_COBBLE_STONE.getDefaultStack());
+        for (DyeColor color : DyeColor.values()) {
+            plate.addInput('#', Blocks.PLANKS_OAK_PAINTED, color.blockMeta).create(color.colorID +  "_wooden_pressure_plate", new ItemStack(Blocks.PRESSURE_PLATE_PLANKS_OAK_PAINTED, 1, color.blockMeta << 4));
+        }
 
         RecipeBuilder.Shaped(CORE_NAMESPACE)
             .setShape(
@@ -1221,6 +1224,10 @@ class WorkbenchGenerator {
             .create("tnt", new ItemStack(Blocks.TNT));
 
         RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput("minecraft:stones").create("stone_button", new ItemStack(Blocks.BUTTON_STONE, 4));
+        RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.PLANKS_OAK).create("wooden_button", new ItemStack(Blocks.BUTTON_PLANKS, 4));
+        for (DyeColor color : DyeColor.values()) {
+            RecipeBuilder.Shapeless(CORE_NAMESPACE).addInput(Blocks.PLANKS_OAK_PAINTED, color.blockMeta).create(color.colorID + "_wooden_button", new ItemStack(Blocks.BUTTON_PLANKS_PAINTED, 4, color.blockMeta << 4));
+        }
 
         RecipeBuilder.Shaped(CORE_NAMESPACE)
             .setShape(
@@ -1344,6 +1351,8 @@ class WorkbenchGenerator {
         Registries.RECIPES.WORKBENCH.register(String.format(format, "lamps"), new RecipeEntryUndyeing(new RecipeSymbol("minecraft:lamps"), new ItemStack(Blocks.LAMP_IDLE)));
         Registries.RECIPES.WORKBENCH.register(String.format(format, "wools"), new RecipeEntryUndyeing(new RecipeSymbol("minecraft:wools"), new ItemStack(Blocks.WOOL)));
         Registries.RECIPES.WORKBENCH.register(String.format(format, "chests"), new RecipeEntryUndyeing(new RecipeSymbol("minecraft:chests"), new ItemStack(Blocks.CHEST_PLANKS_OAK)));
+        Registries.RECIPES.WORKBENCH.register(String.format(format, "button_planks"), new RecipeEntryUndyeing(new RecipeSymbol("minecraft:button_planks"), new ItemStack(Blocks.BUTTON_PLANKS)));
+        Registries.RECIPES.WORKBENCH.register(String.format(format, "pressureplates_planks"), new RecipeEntryUndyeing(new RecipeSymbol("minecraft:pressureplates_planks"), new ItemStack(Blocks.PRESSURE_PLATE_PLANKS_OAK)));
     }
 
     private static void generateStatueRecipes() {
