diff --git a/game/client/src/main/java/net/minecraft/client/Minecraft.java b/game/client/src/main/java/net/minecraft/client/Minecraft.java
index 610001687..3c24eb1be 100644
--- a/game/client/src/main/java/net/minecraft/client/Minecraft.java
+++ b/game/client/src/main/java/net/minecraft/client/Minecraft.java
@@ -32,6 +32,7 @@ import net.minecraft.client.gui.error.ScreenOutOfMemory;
 import net.minecraft.client.gui.options.data.OptionsPages;
 import net.minecraft.client.gui.toasts.GuiElementToastsHud;
 import net.minecraft.client.holiday.Holidays;
+import net.minecraft.client.util.debug.GLFWLoggerCallback;
 import net.minecraft.core.net.CertificateHelper;
 import net.minecraft.client.net.handler.PacketHandlerClient;
 import net.minecraft.client.render.TileEntityRenderDispatcher;
@@ -97,6 +98,7 @@ import org.jetbrains.annotations.Nullable;
 
 import org.lwjgl.LWJGLException;
 import org.lwjgl.glfw.GLFW;
+import org.lwjgl.glfw.GLFWErrorCallback;
 import org.lwjgl.input.Keyboard;
 import org.lwjgl.input.Mouse;
 import org.lwjgl.opengl.GL11;
@@ -522,7 +524,7 @@ public class Minecraft
 		}
 
         resolution.setScale(gameSettings.guiScale.value);
-		
+
         OpenGLHelper.testCapabilities(this);
         
         if(gameSettings.enableMultiDrawRendering.value) {
diff --git a/game/client/src/main/java/net/minecraft/client/entity/particle/ParticleWaterDrop.java b/game/client/src/main/java/net/minecraft/client/entity/particle/ParticleWaterDrop.java
index 336a13bec..804253f66 100644
--- a/game/client/src/main/java/net/minecraft/client/entity/particle/ParticleWaterDrop.java
+++ b/game/client/src/main/java/net/minecraft/client/entity/particle/ParticleWaterDrop.java
@@ -43,7 +43,7 @@ public class ParticleWaterDrop extends Particle {
         }
         Material m = this.world.getBlockMaterial(MathHelper.floor(this.x), MathHelper.floor(this.y), MathHelper.floor(this.z));
         if (m.isLiquid() || m.isSolid()) {
-            double y0 = (MathHelper.floor(this.y) + 1) - BlockLogicFluid.getWaterVolume(this.world.getBlockMetadata(MathHelper.floor(this.x), MathHelper.floor(this.y), MathHelper.floor(this.z)));
+            double y0 = (MathHelper.floor(this.y) + 1) - BlockLogicFluid.getHeight(this.world.getBlockMetadata(MathHelper.floor(this.x), MathHelper.floor(this.y), MathHelper.floor(this.z)));
             if (this.y < y0) {
                 remove();
             }
diff --git a/game/client/src/main/java/net/minecraft/client/render/OpenGLHelper.java b/game/client/src/main/java/net/minecraft/client/render/OpenGLHelper.java
index b42777ffd..582835f6d 100644
--- a/game/client/src/main/java/net/minecraft/client/render/OpenGLHelper.java
+++ b/game/client/src/main/java/net/minecraft/client/render/OpenGLHelper.java
@@ -23,7 +23,8 @@ public class OpenGLHelper {
 	public static boolean enableSphericalFog = false;
 	public static boolean gl20 = false;
 	public static boolean gl31 = false;
-	
+	public static boolean gl43 = false;
+
     public static void testCapabilities(Minecraft minecraft) {
 		LOGGER.info("");
 		LOGGER.info("=== GPU VENDOR INFO ===");
@@ -42,6 +43,7 @@ public class OpenGLHelper {
         enableSphericalFog = capabilities.GL_NV_fog_distance;
         gl20 = capabilities.OpenGL20;
         gl31 = capabilities.OpenGL31;
+		gl43 = capabilities.OpenGL43;
 
 		LOGGER.info("");
         
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelFluid.java b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelFluid.java
index 0ff1ed113..c0f95f581 100644
--- a/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelFluid.java
+++ b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelFluid.java
@@ -14,7 +14,7 @@ import net.minecraft.core.util.phys.AABB;
 import net.minecraft.core.world.WorldSource;
 import org.jetbrains.annotations.NotNull;
 
-public class BlockModelFluid<T extends BlockLogic> extends BlockModelStandard<T> {
+public class BlockModelFluid<T extends BlockLogicFluid> extends BlockModelStandard<T> {
     public BlockModelFluid(Block<T> block) {
         super(block);
     }
@@ -47,17 +47,16 @@ public class BlockModelFluid<T extends BlockLogic> extends BlockModelStandard<T>
         final float sideEastWest = 0.6f;
 
         final float yOff = 0.001f;
-        Material material = block.getMaterial();
         int meta = worldSource.getBlockMetadata(x, y, z);
-        float f7 = BlockLogicFluid.getFluidHeight(worldSource, x, y, z, material);
-        float f8 = BlockLogicFluid.getFluidHeight(worldSource, x, y, z + 1, material);
-        float f9 = BlockLogicFluid.getFluidHeight(worldSource, x + 1, y, z + 1, material);
-        float f10 = BlockLogicFluid.getFluidHeight(worldSource, x + 1, y, z, material);
+        float f7 = block.getLogic().getFluidHeight(worldSource, x, y, z);
+        float f8 = block.getLogic().getFluidHeight(worldSource, x, y, z + 1);
+        float f9 = block.getLogic().getFluidHeight(worldSource, x + 1, y, z + 1);
+        float f10 = block.getLogic().getFluidHeight(worldSource, x + 1, y, z);
         if(renderBlocks.renderAllFaces || renderTop)
         {
             didRender = true;
             IconCoordinate texture = getBlockTextureFromSideAndMetadata(Side.TOP, meta);
-            float rotation = (float) BlockLogicFluid.getSlopeAngle(worldSource, x, y, z, material);
+            float rotation = (float) this.block.getLogic().getSlopeAngle(worldSource, x, y, z);
             if(rotation > -999F)
             {
                 texture = getBlockTextureFromSideAndMetadata(Side.NORTH, meta);
diff --git a/game/client/src/main/java/net/minecraft/client/render/camera/CameraUtil.java b/game/client/src/main/java/net/minecraft/client/render/camera/CameraUtil.java
index 2fd36b3db..e3861e663 100644
--- a/game/client/src/main/java/net/minecraft/client/render/camera/CameraUtil.java
+++ b/game/client/src/main/java/net/minecraft/client/render/camera/CameraUtil.java
@@ -27,7 +27,7 @@ public abstract class CameraUtil
         int id = world.getBlockId(x, y, z);
         if(id != 0 && Blocks.blocksList[id].getMaterial() == material)
         {
-            float f = BlockLogicFluid.getWaterVolume(world.getBlockMetadata(x, y, z)) - 0.2F;
+            float f = BlockLogicFluid.getHeight(world.getBlockMetadata(x, y, z)) - 0.2F;
             float f1 = (float)(y + 1) - f;
             return cameraY < (double)f1;
         }
diff --git a/game/client/src/main/java/net/minecraft/client/render/window/GameWindowGLFW.java b/game/client/src/main/java/net/minecraft/client/render/window/GameWindowGLFW.java
index 7ec56b827..0b1aa4873 100644
--- a/game/client/src/main/java/net/minecraft/client/render/window/GameWindowGLFW.java
+++ b/game/client/src/main/java/net/minecraft/client/render/window/GameWindowGLFW.java
@@ -22,12 +22,16 @@ import javax.swing.JFrame;
 import com.mojang.logging.CategorizedLogger;
 import com.mojang.logging.LogUtils;
 import com.mojang.logging.LogUtils;
+import net.minecraft.client.render.OpenGLHelper;
+import net.minecraft.client.util.debug.GLFWLoggerCallback;
+import net.minecraft.client.util.debug.OpenGLLoggerCallback;
 import net.minecraft.core.Global;
 import net.minecraft.core.util.helper.Color;
 import org.jetbrains.annotations.Nullable;
 import org.lwjgl.BufferUtils;
 import org.lwjgl.PointerBuffer;
 import org.lwjgl.glfw.GLFW;
+import org.lwjgl.glfw.GLFWErrorCallback;
 import org.lwjgl.glfw.GLFWImage;
 import org.lwjgl.glfw.GLFWVidMode;
 import org.lwjgl.input.Keyboard;
@@ -43,6 +47,9 @@ import com.github.zarzelcow.legacylwjgl3.implementation.input.InputImplementatio
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.render.PanelCrashReport;
 import net.minecraft.core.UnexpectedThrowable;
+import org.lwjgl.opengl.GL11;
+import org.lwjgl.opengl.GL43;
+import org.lwjgl.opengl.GLCapabilities;
 import org.lwjgl.stb.STBImage;
 import org.slf4j.Logger;
 
@@ -76,6 +83,7 @@ public class GameWindowGLFW implements GameWindow {
         mc = minecraft;
 
         LOGGER.info("Initializing GLFW...");
+        GLFWErrorCallback.create(new GLFWLoggerCallback()).set();
         if (!glfwInit()) {
             throw new RuntimeException("GLFW initialization failed!");
         }
@@ -147,7 +155,14 @@ public class GameWindowGLFW implements GameWindow {
 
         // OpenGL setup
         glfwMakeContextCurrent(window);
-        GL.createCapabilities();
+        GLCapabilities capabilities = GL.createCapabilities();
+        if (capabilities.OpenGL43) {
+            GL11.glEnable(GL43.GL_DEBUG_OUTPUT);
+            GL43.glDebugMessageCallback(new OpenGLLoggerCallback(), NULL);
+            LOGGER.info("GL4.3 supported, enabling GL Error logging");
+        } else {
+            LOGGER.info("GL4.3 not supported, cannot enable GL Error logging");
+        }
 
         // Input setup
         Display.setWindow(this);
diff --git a/game/client/src/main/java/net/minecraft/client/util/debug/GLFWLoggerCallback.java b/game/client/src/main/java/net/minecraft/client/util/debug/GLFWLoggerCallback.java
new file mode 100644
index 000000000..0e200e11a
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/util/debug/GLFWLoggerCallback.java
@@ -0,0 +1,42 @@
+package net.minecraft.client.util.debug;
+
+import com.mojang.logging.LogUtils;
+import org.lwjgl.glfw.GLFW;
+import org.lwjgl.glfw.GLFWErrorCallback;
+import org.lwjgl.glfw.GLFWErrorCallbackI;
+import org.lwjgl.system.APIUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.Field;
+import java.util.Map;
+
+public class GLFWLoggerCallback implements GLFWErrorCallbackI {
+    private final Logger LOGGER = LoggerFactory.getLogger("GLFW");
+    private final Map<Integer, String> errorCodes = APIUtil.apiClassTokens(GLFWLoggerCallback::filter, null, GLFW.class);
+    @Override
+    public void invoke(int error, long description) {
+        String msg = GLFWErrorCallback.getDescription(description);
+        StringBuilder sb = new StringBuilder(512)
+            .append("ERROR: ")
+            .append(errorCodes.get(error))
+            .append("\n")
+            .append("\tDescription : ")
+            .append(msg)
+            .append("\n")
+            .append("\tStacktrace  :\n");
+
+        StackTraceElement[] stack = Thread.currentThread().getStackTrace();
+        for (int i = 4; i < stack.length; i++) {
+            sb.append("\t\t");
+            sb.append(stack[i]);
+            sb.append("\n");
+        }
+
+        LOGGER.error("{}", sb);
+    }
+
+    public static boolean filter(Field field, Integer value) {
+        return 0x10000 <= value && value <= 0x1ffff;
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/util/debug/OpenGLLoggerCallback.java b/game/client/src/main/java/net/minecraft/client/util/debug/OpenGLLoggerCallback.java
new file mode 100644
index 000000000..30c2bea28
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/util/debug/OpenGLLoggerCallback.java
@@ -0,0 +1,95 @@
+package net.minecraft.client.util.debug;
+
+import com.mojang.logging.LogUtils;
+import org.jetbrains.annotations.NotNull;
+import org.lwjgl.opengl.ARBFramebufferObject;
+import org.lwjgl.opengl.GL11;
+import org.lwjgl.opengl.GL43;
+import org.lwjgl.opengl.GLDebugMessageCallbackI;
+import org.lwjgl.opengl.OpenGLException;
+import org.lwjgl.system.MemoryUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class OpenGLLoggerCallback implements GLDebugMessageCallbackI {
+    private static final Logger LOGGER = LoggerFactory.getLogger("OpenGL");
+    @Override
+    public void invoke(int source, int type, int id, int severity, int length, long message, long userParam) {
+        String msg = MemoryUtil.memASCII(message, length);
+        switch (severity) {
+            case GL43.GL_DEBUG_SEVERITY_HIGH: {
+                LOGGER.error(
+                    "High Severity error from '{}' Type='{}' ErrorCode[{}]:",
+                    getSourceName(source),
+                    getTypeName(type),
+                    getIdName(id),
+                    new RuntimeException(msg)
+                );
+                break;
+            }
+            case GL43.GL_DEBUG_SEVERITY_MEDIUM: {
+                LOGGER.warn(
+                    "Medium Severity error from '{}' Type='{}' ErrorCode[{}]:",
+                    getSourceName(source),
+                    getTypeName(type),
+                    getIdName(id),
+                    new RuntimeException(msg)
+                );
+                break;
+            }
+            case GL43.GL_DEBUG_SEVERITY_LOW: {
+                LOGGER.warn(
+                    "Low Severity issue from '{}' Type='{}' ErrorCode[{}]:",
+                    getSourceName(source),
+                    getTypeName(type),
+                    getIdName(id),
+                    new RuntimeException(msg)
+                );
+                break;
+            }
+            default: {
+                LOGGER.debug("Notification from '{}' Type='{}' ErrorCode[{}]\t'{}'", getSourceName(source), getTypeName(type), getIdName(id), msg);
+            }
+        }
+    }
+
+    public @NotNull String getIdName(int error) {
+        switch (error) {
+            case GL11.GL_INVALID_ENUM: return "Invalid Enum";
+            case GL11.GL_INVALID_VALUE: return "Invalid Value";
+            case GL11.GL_INVALID_OPERATION: return "Invalid Operation";
+            case GL11.GL_STACK_OVERFLOW: return "Stack Overflow";
+            case GL11.GL_STACK_UNDERFLOW: return "Stack Underflow";
+            case GL11.GL_OUT_OF_MEMORY: return "Out of Memory";
+            case ARBFramebufferObject.GL_INVALID_FRAMEBUFFER_OPERATION: return "Invalid Framebuffer Operation";
+            default: return "Unknown 0x" + Integer.toHexString(error);
+        }
+    }
+
+    public @NotNull String getSourceName(int source) {
+        switch (source) {
+            case GL43.GL_DEBUG_SOURCE_API: return "OpenGL API";
+            case GL43.GL_DEBUG_SOURCE_WINDOW_SYSTEM: return "Window System API";
+            case GL43.GL_DEBUG_SOURCE_SHADER_COMPILER: return "Shader Compiler";
+            case GL43.GL_DEBUG_SOURCE_THIRD_PARTY: return "Third Party";
+            case GL43.GL_DEBUG_SOURCE_APPLICATION: return "Application";
+            case GL43.GL_DEBUG_SOURCE_OTHER: return "Other";
+            default: return "Other 0x" + Integer.toHexString(source);
+        }
+    }
+
+    public @NotNull String getTypeName(int type) {
+        switch (type) {
+            case GL43.GL_DEBUG_TYPE_ERROR: return "Error";
+            case GL43.GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: return "Deprecated Behavior";
+            case GL43.GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: return "Undefined Behavior";
+            case GL43.GL_DEBUG_TYPE_PORTABILITY: return "Not Portable";
+            case GL43.GL_DEBUG_TYPE_PERFORMANCE: return "Performance";
+            case GL43.GL_DEBUG_TYPE_MARKER: return "Marker";
+            case GL43.GL_DEBUG_TYPE_PUSH_GROUP: return "Group Push";
+            case GL43.GL_DEBUG_TYPE_POP_GROUP: return "Group Pop";
+            case GL43.GL_DEBUG_TYPE_OTHER: return "Other";
+            default: return "Other 0x" + Integer.toHexString(type);
+        }
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/util/linalg/MatrixStack.java b/game/client/src/main/java/net/minecraft/client/util/linalg/MatrixStack.java
index cc7d68aa6..36af6cd3e 100644
--- a/game/client/src/main/java/net/minecraft/client/util/linalg/MatrixStack.java
+++ b/game/client/src/main/java/net/minecraft/client/util/linalg/MatrixStack.java
@@ -4,6 +4,8 @@ import net.minecraft.core.util.linalg.Matrix4;
 import org.jetbrains.annotations.NotNull;
 import org.lwjgl.opengl.GL11;
 
+import java.util.Stack;
+
 /**
  * Provides an abstraction over OpenGL's matrix functions, to aid in porting to OpenGL 3 later on.
  */
@@ -11,82 +13,82 @@ public class MatrixStack {
     /**
      * The model-view matrix stack, corresponding to GL_MODELVIEW.
      */
-    public static final @NotNull MatrixStack MODEL_VIEW = new MatrixStack(GL11.GL_MODELVIEW, GL11.GL_MODELVIEW_MATRIX);
+    public static final @NotNull MatrixStack MODEL_VIEW = new MatrixStack(GL11.GL_MODELVIEW);
     /**
      * The projection matrix stack, corresponding to GL_PROJECTION.
      */
-    public static final @NotNull MatrixStack PROJECTION = new MatrixStack(GL11.GL_PROJECTION, GL11.GL_PROJECTION_MATRIX);
+    public static final @NotNull MatrixStack PROJECTION = new MatrixStack(GL11.GL_PROJECTION);
     /**
      * The texture matrix stack, corresponding to GL_TEXTURE.
      */
-    public static final @NotNull MatrixStack TEXTURE = new MatrixStack(GL11.GL_TEXTURE, GL11.GL_TEXTURE_MATRIX);
+    public static final @NotNull MatrixStack TEXTURE = new MatrixStack(GL11.GL_TEXTURE);
 
     private final int matrixMode;
-    private final int matrixMode2;
 
-    private int depth = 0;
+    private final @NotNull Stack<Matrix4> stack = new Stack<>();
 
-    private MatrixStack(final int matrixMode, final int matrixMode2) {
+    private MatrixStack(final int matrixMode) {
         this.matrixMode = matrixMode;
-        this.matrixMode2 = matrixMode2;
+
+        this.stack.push(Matrix4.identity());
     }
 
     /**
      * Returns the top of the matrix stack.
      */
     public @NotNull Matrix4 peek() {
-        GL11.glMatrixMode(this.matrixMode);
-
-        final double[] buffer = new double[16];
-        GL11.glGetDoublev(this.matrixMode2, buffer);
-
-        return Matrix4.fromPool(
-            buffer[0], buffer[1], buffer[2], buffer[3],
-            buffer[4], buffer[5], buffer[6], buffer[7],
-            buffer[8], buffer[9], buffer[10], buffer[11],
-            buffer[12], buffer[13], buffer[14], buffer[15]
-        );
+        return this.stack.peek();
     }
 
     /**
      * Pushes a duplicate of the current matrix onto the matrix stack.
      */
-    public void push() {
-        GL11.glMatrixMode(this.matrixMode);
+    public @NotNull MatrixStack push() {
+        this.stack.push(this.peek().copyUnpooled());
 
-        GL11.glPushMatrix();
-
-        this.depth += 1;
+        return this;
     }
 
     /**
      * Pops the current matrix from the matrix stack. If the stack becomes empty, an identity matrix will be inserted.
      */
-    public void pop() {
-        GL11.glMatrixMode(this.matrixMode);
+    public @NotNull MatrixStack pop() {
+        this.stack.pop();
 
-        if (this.depth == 0) {
-            GL11.glLoadIdentity();
-        } else {
-            GL11.glPopMatrix();
+        if (this.stack.isEmpty()) {
+            this.stack.push(Matrix4.identity());
         }
+
+        return this;
     }
 
     /**
      * Replaces the current matrix with the identity matrix.
      */
-    public void loadIdentity() {
-        GL11.glMatrixMode(this.matrixMode);
+    public @NotNull MatrixStack loadIdentity() {
+        this.pop();
+        this.stack.push(Matrix4.identity());
 
-        GL11.glLoadIdentity();
+        return this;
     }
 
     /**
      * Replaces the current matrix with the given matrix.
      */
-    public void load(final @NotNull Matrix4 matrix) {
+    public @NotNull MatrixStack load(final @NotNull Matrix4 matrix) {
+        this.pop();
+        this.stack.push(matrix.copyUnpooled());
+
+        return this;
+    }
+
+    /**
+     * Writes the active matrix to the GPU.
+     */
+    public @NotNull MatrixStack writeToGpu() {
         GL11.glMatrixMode(this.matrixMode);
+        GL11.glLoadMatrixd(this.peek().asArray());
 
-        GL11.glLoadMatrixd(matrix.asArray());
+        return this;
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/block/Block.java b/game/core/src/main/java/net/minecraft/core/block/Block.java
index d9a330088..60be1347a 100644
--- a/game/core/src/main/java/net/minecraft/core/block/Block.java
+++ b/game/core/src/main/java/net/minecraft/core/block/Block.java
@@ -382,11 +382,6 @@ public final class Block<T extends BlockLogic>
         return logic.getAmbientOcclusionStrength(blockAccess, x, y, z);
     }
 
-    @Override
-    public boolean getIsBlockSolid(WorldSource blockAccess, int x, int y, int z, Side side) {
-        return logic.getIsBlockSolid(blockAccess, x, y, z, side);
-    }
-
     @Override
     public AABB getSelectedBoundingBoxFromPool(WorldSource world, int x, int y, int z) {
         return logic.getSelectedBoundingBoxFromPool(world, x, y, z);
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockInterface.java b/game/core/src/main/java/net/minecraft/core/block/BlockInterface.java
index 7e4b69a7b..845b62197 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockInterface.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockInterface.java
@@ -49,7 +49,6 @@ interface BlockInterface {
      * Used when fullbright or lightmap is active
      */
     float getAmbientOcclusionStrength(WorldSource blockAccess, int x, int y, int z);
-    boolean getIsBlockSolid(WorldSource blockAccess, int x, int y, int z, Side side);
     AABB getSelectedBoundingBoxFromPool(WorldSource world, int x, int y, int z);
     BoundingVolume getBoundingVolume(World world, int x, int y, int z);
     void getCollidingBoundingBoxes(World world, int x, int y, int z, AABB aabb, ArrayList<AABB> aabbList);
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogic.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogic.java
index a18bd25aa..8c51ac8b7 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogic.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogic.java
@@ -107,7 +107,7 @@ public class BlockLogic implements BlockInterface, IItemConvertible {
         return null;
     }
 
-    public void setBlockBounds(double minX, double minY, double minZ, double maxX, double maxY, double maxZ)
+    public final void setBlockBounds(double minX, double minY, double minZ, double maxX, double maxY, double maxZ)
     {
         bounds.set(minX, minY, minZ, maxX, maxY, maxZ);
     }
@@ -137,11 +137,6 @@ public class BlockLogic implements BlockInterface, IItemConvertible {
         return isSolidRender() ? 1.0f : 0.0f;
     }
 
-    @Override
-    public boolean getIsBlockSolid(WorldSource blockAccess, int x, int y, int z, Side side) {
-        return blockAccess.getBlockMaterial(x, y, z).isSolid();
-    }
-
     @Override
     public AABB getSelectedBoundingBoxFromPool(WorldSource world, int x, int y, int z) {
         return getBlockBoundsFromState(world, x, y, z).move(x, y, z);
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluid.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluid.java
index 6467a8ab8..cfa2bdd28 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluid.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluid.java
@@ -16,141 +16,95 @@ import org.jetbrains.annotations.NotNull;
 
 import java.util.Random;
 
-public abstract class BlockLogicFluid extends BlockLogic
-{
+public abstract class BlockLogicFluid extends BlockLogic {
+    protected final @NotNull Fluid fluid;
 
-	public BlockLogicFluid(Block<?> block, Material material)
-    {
+    public BlockLogicFluid(final Block<?> block, final Material material, final @NotNull Fluid fluid) {
         super(block, material);
         setBlockBounds(0.0F, 0.0F, 0.0F, 1.0F, 1.0F, 1.0F);
+        this.fluid = fluid;
     }
 
-    public static float getWaterVolume(int data)
-    {
+    public static float getHeight(int data) {
         data &= 0x0F;
-        if(data >= 8)
-        {
+        if (data >= 8) {
             data = 0;
         }
-        return (float)(data + 1) / 8F;
+        return (float) (data + 1) / 8F;
     }
 
-    protected int getDepth(WorldSource world, int x, int y, int z)
-    {
-        if(world.getBlockMaterial(x, y, z) != material)
-        {
+    protected int getDepth(final WorldSource worldSource, final int x, final int y, final int z) {
+        if (worldSource.getBlockMaterial(x, y, z) != this.material) {
             return -1;
-        }
-        else
-        {
-            return world.getBlockMetadata(x, y, z) & 0x0F;
+        } else {
+            return worldSource.getBlockMetadata(x, y, z) & 0x0F;
         }
     }
 
-    protected int getRenderedDepth(WorldSource blockAccess, int x, int y, int z)
-    {
-        if (blockAccess.getBlockMaterial(x, y, z) != material)
-        {
+    protected int getRenderedDepth(final WorldSource worldSource, final int x, final int y, final int z) {
+        if (worldSource.getBlockMaterial(x, y, z) != this.material) {
             return -1;
         }
-        int meta = blockAccess.getBlockMetadata(x, y, z) & 0x0F;
-        if (meta >= 8)
-        {
+        int meta = worldSource.getBlockMetadata(x, y, z) & 0x0F;
+        if (meta >= 8) {
             meta = 0;
         }
         return meta;
     }
 
     @Override
-    public boolean isCubeShaped()
-    {
+    public boolean isCubeShaped() {
         return false;
     }
 
     @Override
-    public boolean isSolidRender()
-    {
+    public boolean isSolidRender() {
         return false;
     }
 
     @Override
-    public boolean canCollideCheck(int meta, boolean shouldCollideWithFluids)
-    {
+    public boolean canCollideCheck(final int meta, final boolean shouldCollideWithFluids) {
         return shouldCollideWithFluids && (meta & 0x0F) == 0;
     }
 
     @Override
-    public boolean getIsBlockSolid(WorldSource blockAccess, int x, int y, int z, Side side)
-    {
-        Material material = blockAccess.getBlockMaterial(x, y, z);
-        if(material == this.material)
-        {
-            return false;
-        }
-        if(material == Material.ice)
-        {
-            return false;
-        }
-        if(side == Side.TOP)
-        {
-            return true;
-        }
-        else
-        {
-            return super.getIsBlockSolid(blockAccess, x, y, z, side);
-        }
-    }
-
-    @Override
-    public AABB getCollisionBoundingBoxFromPool(WorldSource world, int x, int y, int z)
-    {
+    public AABB getCollisionBoundingBoxFromPool(final WorldSource worldSource, final int x, final int y, final int z) {
         return null;
     }
 
-    private Vec3 getFlow(WorldSource world, int x, int y, int z)
-    {
+    private Vec3 getFlow(final WorldSource worldSource, final int x, final int y, final int z) {
         Vec3 result = Vec3.fromPool(0.0D, 0.0D, 0.0D);
-        int depthLocal = getRenderedDepth(world, x, y, z);
-        for(int i = 0; i < 4; i++)
-        {
+        final int depthLocal = getRenderedDepth(worldSource, x, y, z);
+        for (int i = 0; i < 4; i++) {
             int checkX = x;
-            int checkY = y;
             int checkZ = z;
-            if(i == 0)
-            {
+
+            if (i == 0) {
                 checkX--;
             }
-            if(i == 1)
-            {
+            if (i == 1) {
                 checkZ--;
             }
-            if(i == 2)
-            {
+            if (i == 2) {
                 checkX++;
             }
-            if(i == 3)
-            {
+            if (i == 3) {
                 checkZ++;
             }
-            int depthCheck = getRenderedDepth(world, checkX, checkY, checkZ);
-            if(depthCheck < 0)
-            {
-                if(world.getBlockMaterial(checkX, checkY, checkZ).blocksMotion())
-                {
+
+            int depthCheck = getRenderedDepth(worldSource, checkX, y, checkZ);
+            if (depthCheck < 0) {
+                if (worldSource.getBlockMaterial(checkX, y, checkZ).blocksMotion()) {
                     continue;
                 }
-                depthCheck = getRenderedDepth(world, checkX, checkY - 1, checkZ);
-                if(depthCheck >= 0)
-                {
-                    int mult = depthCheck - (depthLocal - 8);
-                    result = result.add((checkX - x) * mult, (checkY - y) * mult, (checkZ - z) * mult);
+                depthCheck = getRenderedDepth(worldSource, checkX, y - 1, checkZ);
+                if (depthCheck >= 0) {
+                    final int mult = depthCheck - (depthLocal - 8);
+                    result = result.add((checkX - x) * mult, 0, (checkZ - z) * mult);
                 }
-                continue;
-            }
-            else
-            {
-                int mult = depthCheck - depthLocal;
-                result = result.add((checkX - x) * mult, (checkY - y) * mult, (checkZ - z) * mult);
+            } else {
+                final int mult = depthCheck - depthLocal;
+                result = result.add((checkX - x) * mult, 0, (checkZ - z) * mult);
             }
         }
         result = result.normalize();
@@ -158,217 +112,123 @@ public abstract class BlockLogicFluid extends BlockLogic
     }
 
     @Override
-    public void handleEntityInside(World world, int x, int y, int z, Entity entity, Vec3 entityVelocity)
-    {
+    public void handleEntityInside(final World world, final int x, final int y, final int z, final Entity entity, final Vec3 entityVelocity) {
         if (entity.noPhysics) return;
-        Vec3 flowVector = getFlow(world, x, y, z);
+        final Vec3 flowVector = getFlow(world, x, y, z);
+
         entityVelocity.x += flowVector.x;
         entityVelocity.y += flowVector.y;
         entityVelocity.z += flowVector.z;
     }
 
     @Override
-    public int tickDelay()
-    {
-        if(material == Material.water)
-        {
-            return 5;
-        }
-        return material != Material.lava ? 0 : 30;
+    public int tickDelay() {
+        return this.fluid.tickDelay();
     }
 
     @Override
-    public float getBlockBrightness(WorldSource world, int x, int y, int z)
-    {
-        float f = world.getLightBrightness(x, y, z);
-        float f1 = world.getLightBrightness(x, y + 1, z);
-        return Math.max(f, f1);
+    public float getBlockBrightness(final WorldSource worldSource, final int x, final int y, final int z) {
+        final float br = worldSource.getLightBrightness(x, y, z);
+        final float brAbove = worldSource.getLightBrightness(x, y + 1, z);
+        return Math.max(br, brAbove);
     }
 
     @Override
-    public void updateTick(World world, int x, int y, int z, Random rand)
-    {
-        super.updateTick(world, x, y, z, rand);
+    public void animationTick(final World world, final int x, final int y, final int z, final Random rand) {
+        this.fluid.animationTick(this, world, x, y, z, rand);
     }
 
-    @Override
-    public void animationTick(World world, int x, int y, int z, Random rand)
-    {
-        int data = world.getBlockMetadata(x, y, z) & 0x0F;
-        boolean isTop = world.getBlockMaterial(x, y + 1, z) == Material.air && !world.isBlockOpaqueCube(x, y + 1, z);
-        if(material == Material.water && rand.nextInt(64) == 0)
-        {
-            if(data > 0 && data < 8)
-            {
-                world.playSoundEffect(null, SoundCategory.WORLD_SOUNDS, (float) x + 0.5F, (float) y + 0.5F, (float) z + 0.5F, "liquid.water", rand.nextFloat() * 0.25F + 0.75F, rand.nextFloat() * 1.0F + 0.5F);
-            }
-        }
-        if (material == Material.water && world.dimension == Dimension.NETHER && isTop && data == 0 && rand.nextInt(4) == 0) {
-            world.spawnParticle("smoke", x + Math.random(), y + (14f / 16f), z + Math.random(), 0.0D, 0.0D, 0.0D, 1);
-        }
-        if(material == Material.lava && isTop && rand.nextInt(100) == 0)
-        {
-            double xOff = (double) x + rand.nextFloat();
-            double yOff = (double) y + getBounds().maxY;
-            double zOff = (double) z + rand.nextFloat();
-            world.spawnParticle("lava", xOff, yOff, zOff, 0.0D, 0.0D, 0.0D, 0);
-        }
-    }
-
-    public static double getSlopeAngle(WorldSource blockAccess, int x, int y, int z, Material material)
-    {
-        Vec3 vec3 = null;
-        if(material == Material.water)
-        {
-            vec3 = Blocks.FLUID_WATER_FLOWING.getLogic().getFlow(blockAccess, x, y, z);
-        }
-        if(material == Material.lava)
-        {
-            vec3 = Blocks.FLUID_LAVA_FLOWING.getLogic().getFlow(blockAccess, x, y, z);
-        }
-        if(vec3 == null ||(vec3.x == 0.0D && vec3.z == 0.0D))
-        {
+    public double getSlopeAngle(final WorldSource worldSource, final int x, final int y, final int z) {
+        final Vec3 vec = getFlow(worldSource, x, y, z);
+        if (vec == null || (vec.x == 0.0D && vec.z == 0.0D)) {
             return -1000D;
-        } else
-        {
-            return Math.atan2(vec3.z, vec3.x) - Math.PI/2;
+        } else {
+            return Math.atan2(vec.z, vec.x) - Math.PI / 2;
         }
     }
 
     @Override
-    public void onBlockPlacedByWorld(World world, int x, int y, int z)
-    {
+    public void onBlockPlacedByWorld(final World world, final int x, final int y, final int z) {
         checkForHarden(world, x, y, z);
     }
 
     @Override
-    public void onNeighborBlockChange(World world, int x, int y, int z, int blockId)
-    {
+    public void onNeighborBlockChange(final World world, final int x, final int y, final int z, final int blockId) {
         checkForHarden(world, x, y, z);
     }
 
-    private void checkForHarden(World world, int x, int y, int z)
-    {
-        if(world.getBlockId(x, y, z) != block.id())
-        {
+    private void checkForHarden(final World world, final int x, final int y, final int z) {
+        if (world.getBlockId(x, y, z) != this.block.id()) {
             return;
         }
-        if(material == Material.lava)
-        {
-            if(world.getBlockMaterial(x, y, z - 1) == Material.water ||
-                world.getBlockMaterial(x, y, z + 1) == Material.water ||
-                world.getBlockMaterial(x - 1, y, z) == Material.water ||
-                world.getBlockMaterial(x + 1, y, z) == Material.water ||
-                world.getBlockMaterial(x, y + 1, z) == Material.water)
-            {
-                int data = world.getBlockMetadata(x, y, z) & 0x0F;
-                if(data == 0)
-                {
-                    if (world.dimension == Dimension.NETHER) {
-                        world.setBlockWithNotify(x, y, z, Blocks.COBBLE_NETHERRACK_IGNEOUS.id());
-                    } else {
-                        world.setBlockWithNotify(x, y, z, Blocks.OBSIDIAN.id());
-                    }
-                }
-                else if(data <= 2)
-                {
-                    world.setBlockWithNotify(x, y, z, Blocks.COBBLE_GRANITE.id());
-                }
-                else if(data <= 4)
-                {
-                    world.setBlockWithNotify(x, y, z, Blocks.COBBLE_STONE.id());
-                }
-                else
-                {
-                    world.setBlockWithNotify(x, y, z, Blocks.COBBLE_BASALT.id());
-                }
-                fizz(world, x, y, z);
-            }
-        }
 
-        if(material == Material.water)
-        {
-            if(world.getBlockMaterial(x, y, z - 1) == Material.lava ||
-                world.getBlockMaterial(x, y, z + 1) == Material.lava ||
-                world.getBlockMaterial(x - 1, y, z) == Material.lava ||
-                world.getBlockMaterial(x + 1, y, z) == Material.lava ||
-                world.getBlockMaterial(x, y + 1, z) == Material.lava)
-            {
-                int data = world.getBlockMetadata(x, y, z) & 0x0F;
-                if(data == 0)
-                {
-                    world.setBlockWithNotify(x, y, z, Blocks.COBBLE_LIMESTONE.id());
-                    fizz(world, x, y, z);
-                }
-            }
-        }
+        this.fluid.checkForHarden(this, world, x, y, z, world.getBlockMaterial(x, y, z - 1));
+        this.fluid.checkForHarden(this, world, x, y, z, world.getBlockMaterial(x, y, z + 1));
+        this.fluid.checkForHarden(this, world, x, y, z, world.getBlockMaterial(x - 1, y, z));
+        this.fluid.checkForHarden(this, world, x, y, z, world.getBlockMaterial(x + 1, y, z));
+        this.fluid.checkForHarden(this, world, x, y, z, world.getBlockMaterial(x, y + 1, z));
     }
 
     @Override
-    public ItemStack[] getBreakResult(World world, EnumDropCause dropCause, int meta, TileEntity tileEntity)
-    {
+    public ItemStack[] getBreakResult(final World world, final EnumDropCause dropCause, final int meta, final TileEntity tileEntity) {
         return null;
     }
 
-    protected void fizz(World world, int x, int y, int z)
-    {
-        world.playSoundEffect(null, SoundCategory.WORLD_SOUNDS, (float)x + 0.5F, (float)y + 0.5F, (float)z + 0.5F, "random.fizz", 0.5F, 2.6F + (world.rand.nextFloat() - world.rand.nextFloat()) * 0.8F);
-        for (int i = 0; i < 8; i++)
-        {
-            world.spawnParticle("largesmoke", (double)x + Math.random(), (double)y + 1.2D, (double)z + Math.random(), 0.0D, 0.0D, 0.0D, 0);
+    protected static void fizz(final World world, final int x, final int y, final int z) {
+        world.playSoundEffect(null, SoundCategory.WORLD_SOUNDS, (float) x + 0.5F, (float) y + 0.5F, (float) z + 0.5F, "random.fizz", 0.5F, 2.6F + (world.rand.nextFloat() - world.rand.nextFloat()) * 0.8F);
+        for (int i = 0; i < 8; i++) {
+            world.spawnParticle("largesmoke", (double) x + Math.random(), (double) y + 1.2D, (double) z + Math.random(), 0.0D, 0.0D, 0.0D, 0);
         }
     }
+
     @Override
-    public AABB getBlockBoundsFromState(WorldSource world, int x, int y, int z)
-    {
-        float f7 = BlockLogicFluid.getFluidHeight(world, x, y, z, material);
-        float f8 = BlockLogicFluid.getFluidHeight(world, x, y, z + 1, material);
-        float f9 = BlockLogicFluid.getFluidHeight(world, x + 1, y, z + 1, material);
-        float f10 = BlockLogicFluid.getFluidHeight(world, x + 1, y, z, material);
-        return AABB.fromPool(0.0F, 0.0F, 0.0F, 1.0F, Math.max((f7 + f8 + f9 + f10)/4f, 0.2), 1.0F);
+    public AABB getBlockBoundsFromState(final WorldSource worldSource, final int x, final int y, final int z) {
+        final float myHeight = getFluidHeight(worldSource, x, y, z);
+        final float heightPosZ = getFluidHeight(worldSource, x, y, z + 1);
+        final float heightPosXPosZ = getFluidHeight(worldSource, x + 1, y, z + 1);
+        final float heightPosX = getFluidHeight(worldSource, x + 1, y, z);
+
+        return AABB.fromPool(0.0F, 0.0F, 0.0F, 1.0F, Math.max((myHeight + heightPosZ + heightPosXPosZ + heightPosX) / 4f, 0.2), 1.0F);
     }
 
-    public static float getFluidHeight(WorldSource worldSource, int x, int y, int z, Material material)
-    {
-        int l = 0;
-        float f = 0.0F;
-        for(int i1 = 0; i1 < 4; i1++)
-        {
-            int j1 = x - (i1 & 1);
-            int l1 = z - (i1 >> 1 & 1);
-            if(worldSource.getBlockMaterial(j1, y + 1, l1) == material)
-            {
+    public float getFluidHeight(final WorldSource worldSource, final int x, final int y, final int z) {
+        int count = 0;
+        float height = 0.0F;
+
+        for (int i = 0; i < 4; i++) {
+            final int xx = x - (i & 1);
+            final int zz = z - (i >> 1 & 1);
+
+            if (worldSource.getBlockMaterial(xx, y + 1, zz) == this.material) {
                 return 1.0F;
             }
-            Material material1 = worldSource.getBlockMaterial(j1, y, l1);
-            if(material1 == material)
-            {
-                int i2 = worldSource.getBlockMetadata(j1, y, l1);
-                if(i2 >= 8 || i2 == 0)
-                {
-                    f += BlockLogicFluid.getWaterVolume(i2) * 10F;
-                    l += 10;
+
+            final Material adjMaterial = worldSource.getBlockMaterial(xx, y, zz);
+            if (adjMaterial == this.material) {
+                final int data = worldSource.getBlockMetadata(xx, y, zz) & 0x0F;
+                if (data >= 8 || data == 0) {
+                    height += BlockLogicFluid.getHeight(data) * 10F;
+                    count += 10;
                 }
-                f += BlockLogicFluid.getWaterVolume(i2);
-                l++;
+                height += BlockLogicFluid.getHeight(data);
+                count++;
                 continue;
             }
-            if(!material1.isSolid())
-            {
-                f++;
-                l++;
+            if (!adjMaterial.isSolid()) {
+                height++;
+                count++;
             }
         }
 
-        return Math.max(1.0F - f / (float)l, 0.015F);
+        return Math.max(1.0F - height / (float) count, 0.015F);
     }
 
     @Override
-    public boolean isEquivalent(@NotNull World world, int thisX, int thisY, int thisZ, int otherX, int otherY, int otherZ) {
-        Block<?> bBlock = world.getBlock(otherX, otherY, otherZ);
+    public boolean isEquivalent(final @NotNull World world, final int thisX, final int thisY, final int thisZ, final int otherX, final int otherY, final int otherZ) {
+        final Block<?> bBlock = world.getBlock(otherX, otherY, otherZ);
 
-        int thisMeta = world.getBlockMetadata(thisX, thisY, thisZ);
-        int otherMeta = world.getBlockMetadata(otherX, otherY, otherZ);
+        final int thisMeta = world.getBlockMetadata(thisX, thisY, thisZ);
+        final int otherMeta = world.getBlockMetadata(otherX, otherY, otherZ);
 
         if (Block.hasLogicClass(bBlock, BlockLogicFluid.class)) {
             return this.material == bBlock.getMaterial() && thisMeta == otherMeta;
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluidFlowing.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluidFlowing.java
index 02d06d26d..4d904140d 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluidFlowing.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluidFlowing.java
@@ -5,6 +5,7 @@ import net.minecraft.core.enums.EnumDropCause;
 import net.minecraft.core.world.Dimension;
 import net.minecraft.core.block.material.Material;
 import net.minecraft.core.world.World;
+import org.jetbrains.annotations.NotNull;
 
 import java.util.Random;
 
@@ -15,9 +16,9 @@ public class BlockLogicFluidFlowing extends BlockLogicFluid
     int[] distance;
     public final Block<?> blockStill;
 
-	public BlockLogicFluidFlowing(Block<?> block, Material material, Block<?> blockStill)
+	public BlockLogicFluidFlowing(Block<?> block, Material material, final @NotNull Fluid fluid, Block<?> blockStill)
     {
-        super(block, material);
+        super(block, material, fluid);
         maxCount = 0;
         result = new boolean[4];
         distance = new int[4];
@@ -36,11 +37,7 @@ public class BlockLogicFluidFlowing extends BlockLogicFluid
     public void updateTick(World world, int x, int y, int z, Random rand)
     {
         int localFlowDecay = getDepth(world, x, y, z);
-        byte flowDecayMod = 1;
-        if(material == Material.lava && world.dimension != Dimension.NETHER)
-        {
-            flowDecayMod = 2;
-        }
+        final byte flowDecayMod = this.fluid.getFlowDecayMod(this, world, x, y, z);
         if(localFlowDecay > 0)
         {
             int surroundingFlowDecay = -100;
@@ -65,7 +62,7 @@ public class BlockLogicFluidFlowing extends BlockLogicFluid
                     newFlowDecay = flowDecayAbove + 8;
                 }
             }
-            if(maxCount >= 2 && (material == Material.water && world.dimension != Dimension.NETHER)) // Become source block
+            if(maxCount >= 2 && this.fluid.canBecomeSource(this, world, x, y, z, rand)) // Become source block
             {
                 if(world.getBlockMaterial(x, y - 1, z).isSolid())
                 {
@@ -148,16 +145,10 @@ public class BlockLogicFluidFlowing extends BlockLogicFluid
     {
         if(canSpreadTo(world, x, y, z))
         {
-            int i1 = world.getBlockId(x, y, z);
-            if(i1 > 0)
+            int id = world.getBlockId(x, y, z);
+            if(id > 0)
             {
-                if(material == Material.lava)
-                {
-                    fizz(world, x, y, z);
-                } else
-                {
-                    Blocks.blocksList[i1].dropBlockWithCause(world, EnumDropCause.WORLD, x, y, z, world.getBlockMetadata(x, y, z), null, null);
-                }
+                this.fluid.onFlowIntoBlock(this, world, x, y, z, meta);
             }
             world.setBlockAndMetadataWithNotify(x, y, z, block.id(), meta);
         }
@@ -298,7 +289,7 @@ public class BlockLogicFluidFlowing extends BlockLogicFluid
         {
             return false;
         }
-        if(material == Material.lava)
+        if(!this.fluid.canSpreadTo(this, world, x, y, z, material))
         {
             return false;
         }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluidStill.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluidStill.java
index 3bc15bae9..978b7f131 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluidStill.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFluidStill.java
@@ -5,15 +5,16 @@ import java.util.Random;
 import net.minecraft.core.block.material.Material;
 import net.minecraft.core.util.helper.Side;
 import net.minecraft.core.world.World;
+import org.jetbrains.annotations.NotNull;
 
 public class BlockLogicFluidStill extends BlockLogicFluid
 {
     public final Block<?> blockFlowing;
 
-	public BlockLogicFluidStill(Block<?> block, Material material, Block<?> blockFlowing)
+	public BlockLogicFluidStill(Block<?> block, Material material, final @NotNull Fluid fluid, Block<?> blockFlowing)
     {
-        super(block, material);
-        block.setTicking(material == Material.lava);
+        super(block, material, fluid);
+        block.setTicking(this.fluid.shouldTick());
         this.blockFlowing = blockFlowing;
     }
 
@@ -41,35 +42,6 @@ public class BlockLogicFluidStill extends BlockLogicFluid
     @Override
 	public void updateTick(World world, int x, int y, int z, Random rand)
     {
-        if(material == Material.lava)
-        {
-            int l = rand.nextInt(3);
-            for(int i1 = 0; i1 < l; i1++)
-            {
-                x += rand.nextInt(3) - 1;
-                y++;
-                z += rand.nextInt(3) - 1;
-                int blockId = world.getBlockId(x, y, z);
-                if(blockId == 0)
-                {
-                    if(isFlammable(world, x - 1, y, z) || isFlammable(world, x + 1, y, z) || isFlammable(world, x, y, z - 1) || isFlammable(world, x, y, z + 1) || isFlammable(world, x, y - 1, z) || isFlammable(world, x, y + 1, z))
-                    {
-                        world.setBlockWithNotify(x, y, z, Blocks.FIRE.id());
-                        return;
-                    }
-                    continue;
-                }
-                if(Blocks.blocksList[blockId].getMaterial().blocksMotion())
-                {
-                    return;
-                }
-            }
-
-        }
-    }
-
-    private boolean isFlammable(World world, int x, int y, int z)
-    {
-        return BlockLogicFire.canBurn(world, x, y, z);
+        this.fluid.updateTickStill(this, world, x, y, z, rand);
     }
 }
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 4e291dcc7..3eff1421b 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
@@ -158,16 +158,16 @@ public class BlockLogicRepeater extends BlockLogic
         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;
+                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) & BlockLogicWireRedstone.MASK_POWER) != 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;
+                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) & BlockLogicWireRedstone.MASK_POWER) != 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;
+                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) & BlockLogicWireRedstone.MASK_POWER) != 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 world.getSignal(x - 1, y, z, Side.WEST) || (world.getBlockId(x - 1, y, z) == Blocks.WIRE_REDSTONE.id() && (world.getBlockMetadata(x - 1, y, z) & BlockLogicWireRedstone.MASK_POWER) != 0);
         }
         return false;
     }
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 38f26ebaa..bbff8d921 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
@@ -20,6 +20,8 @@ import java.util.Random;
 import java.util.Set;
 
 public class BlockLogicWireRedstone extends BlockLogic {
+    public static final int MASK_DIRECTION = 0xF0;
+    public static final int MASK_POWER = 0x0F;
     private boolean shouldSignal = true;
     private final Set<ChunkPosition> toUpdate = new HashSet<>();
 
@@ -105,7 +107,7 @@ public class BlockLogicWireRedstone extends BlockLogic {
             int blockId = world.getBlockId(x0, y0, z0);
             if (blockId == id() || blockId == Blocks.FLUID_LAVA_STILL.id() || blockId == Blocks.FLUID_LAVA_FLOWING.id()) { // Intentionally keeping lava transmutation, do not delete carelessly
                 world.noNeighborUpdate = true;
-                world.setBlockMetadataWithNotify(x0, y0, z0, newStrength);
+                world.setBlockMetadataWithNotify(x0, y0, z0, newStrength | (world.getBlockMetadata(x0, y0, z0) & MASK_DIRECTION));
                 world.markBlocksDirty(x0, y0, z0, x0, y0, z0);
                 world.noNeighborUpdate = false;
             }
@@ -129,7 +131,7 @@ public class BlockLogicWireRedstone extends BlockLogic {
                     yi += 2;
                 }
                 int targetStrength = checkTarget(world, xi, y0, zi, -1);
-                newStrength = world.getBlockMetadata(x0, y0, z0);
+                newStrength = world.getBlockMetadata(x0, y0, z0) & MASK_POWER;
                 if (newStrength > 0) {
                     newStrength--;
                 }
@@ -137,7 +139,7 @@ public class BlockLogicWireRedstone extends BlockLogic {
                     updatePowerStrength(world, xi, y0, zi, x0, y0, z0);
                 }
                 targetStrength = checkTarget(world, xi, yi, zi, -1);
-                newStrength = world.getBlockMetadata(x0, y0, z0);
+                newStrength = world.getBlockMetadata(x0, y0, z0) & MASK_POWER;
                 if (newStrength > 0) {
                     newStrength--;
                 }
@@ -247,7 +249,7 @@ public class BlockLogicWireRedstone extends BlockLogic {
             return sourceStrength;
         }
 
-        int targetStrength = world.getBlockMetadata(x, y, z);
+        int targetStrength = world.getBlockMetadata(x, y, z) & MASK_POWER;
         return Math.max(targetStrength, sourceStrength);
     }
 
@@ -287,7 +289,7 @@ public class BlockLogicWireRedstone extends BlockLogic {
         if (!shouldSignal) {
             return false;
         }
-        if (worldSource.getBlockMetadata(x, y, z) == 0) {
+        if ((worldSource.getBlockMetadata(x, y, z) & MASK_POWER) == 0) {
             return false;
         }
         if (side == Side.TOP) {
@@ -316,7 +318,7 @@ public class BlockLogicWireRedstone extends BlockLogic {
             World world = (World) worldSource;
 
             int meta = world.getBlockMetadata(x, y, z);
-            int direction = (meta & (15 << 4)) >> 4; // 0b00000000 00000000 00000000 DDDD0000
+            int direction = (meta & MASK_DIRECTION) >> 4; // 0b00000000 00000000 00000000 DDDD0000
 
             int newDirectionNorth = negZShouldConnectTo ? 1 : 0;
             int newDirectionSouth = (posZShouldConnectTo ? 1 : 0) << 1;
@@ -325,9 +327,8 @@ public class BlockLogicWireRedstone extends BlockLogic {
             int newDirection = newDirectionNorth | newDirectionSouth | newDirectionWest | newDirectionEast;
 
             if (direction != newDirection) {
-                newDirection <<= 4;
-                meta &= (~(15 << 4)); // Mask out direction bits
-                meta |= newDirection;
+                meta &= ~MASK_DIRECTION; // Mask out direction bits
+                meta |= (newDirection << 4);
                 world.setBlockMetadata(x, y, z, meta);
             }
         }
@@ -355,7 +356,7 @@ public class BlockLogicWireRedstone extends BlockLogic {
 
     @Override
     public void animationTick(World world, int x, int y, int z, Random rand) {
-        int meta = world.getBlockMetadata(x, y, z);
+        int meta = world.getBlockMetadata(x, y, z) & MASK_POWER;
         if (meta == 0) {
             return;
         }
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 1d8c8058c..ce453c96d 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
@@ -433,16 +433,16 @@ public final class Blocks {
         .withSound(BlockSounds.STONE).withHardness(16F).withBlastResistance(6000000F).withImmovableFlagSet()
         .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.NETHER_MOBS_SPAWN);
 
-    public static final Block<BlockLogicFluid> FLUID_WATER_FLOWING = register("fluid.water.flowing", "minecraft:block/fluid_water_flowing", 270, (BlockLogicSupplier<BlockLogicFluid>) b -> new BlockLogicFluidFlowing(b, Material.water, Blocks.FLUID_WATER_STILL))
+    public static final Block<BlockLogicFluid> FLUID_WATER_FLOWING = register("fluid.water.flowing", "minecraft:block/fluid_water_flowing", 270, (BlockLogicSupplier<BlockLogicFluid>) b -> new BlockLogicFluidFlowing(b, Material.water, new FluidWater(), Blocks.FLUID_WATER_STILL))
         .withHardness(100F).withLightBlock(3).withDisabledNeighborNotifyOnMetadataChange().withDisabledStats()
         .withTags(BlockTags.IS_WATER, BlockTags.PLACE_OVERWRITES, BlockTags.NOT_IN_CREATIVE_MENU);
-    public static final Block<BlockLogicFluid> FLUID_WATER_STILL = register("fluid.water.still", "minecraft:block/fluid_water_still", 271, (BlockLogicSupplier<BlockLogicFluid>) b -> new BlockLogicFluidStill(b, Material.water, Blocks.FLUID_WATER_FLOWING))
+    public static final Block<BlockLogicFluid> FLUID_WATER_STILL = register("fluid.water.still", "minecraft:block/fluid_water_still", 271, (BlockLogicSupplier<BlockLogicFluid>) b -> new BlockLogicFluidStill(b, Material.water, new FluidWater(), Blocks.FLUID_WATER_FLOWING))
         .withHardness(100F).withLightBlock(3).withDisabledNeighborNotifyOnMetadataChange().withDisabledStats().setStatParent(() -> FLUID_WATER_FLOWING)
         .withTags(BlockTags.IS_WATER, BlockTags.PLACE_OVERWRITES, BlockTags.NOT_IN_CREATIVE_MENU);
-    public static final Block<BlockLogicFluid> FLUID_LAVA_FLOWING = register("fluid.lava.flowing", "minecraft:block/fluid_lava_flowing", 272, (BlockLogicSupplier<BlockLogicFluid>) b -> new BlockLogicFluidFlowing(b, Material.lava, Blocks.FLUID_LAVA_STILL))
+    public static final Block<BlockLogicFluid> FLUID_LAVA_FLOWING = register("fluid.lava.flowing", "minecraft:block/fluid_lava_flowing", 272, (BlockLogicSupplier<BlockLogicFluid>) b -> new BlockLogicFluidFlowing(b, Material.lava, new FluidLava(), Blocks.FLUID_LAVA_STILL))
         .withHardness(0.0F).withLightEmission(1.0F).withLightBlock(255).withDisabledNeighborNotifyOnMetadataChange().withDisabledStats()
         .withTags(BlockTags.IS_LAVA, BlockTags.PLACE_OVERWRITES, BlockTags.NOT_IN_CREATIVE_MENU);
-    public static final Block<BlockLogicFluid> FLUID_LAVA_STILL = register("fluid.lava.still", "minecraft:block/fluid_lava_still", 273, (BlockLogicSupplier<BlockLogicFluid>) b -> new BlockLogicFluidStill(b, Material.lava, Blocks.FLUID_LAVA_FLOWING))
+    public static final Block<BlockLogicFluid> FLUID_LAVA_STILL = register("fluid.lava.still", "minecraft:block/fluid_lava_still", 273, (BlockLogicSupplier<BlockLogicFluid>) b -> new BlockLogicFluidStill(b, Material.lava, new FluidLava(), Blocks.FLUID_LAVA_FLOWING))
         .withHardness(100F).withLightEmission(1.0F).withLightBlock(255).withDisabledNeighborNotifyOnMetadataChange().withDisabledStats().setStatParent(() -> FLUID_LAVA_FLOWING)
         .withTags(BlockTags.IS_LAVA, BlockTags.PLACE_OVERWRITES, BlockTags.NOT_IN_CREATIVE_MENU);
 
diff --git a/game/core/src/main/java/net/minecraft/core/block/Fluid.java b/game/core/src/main/java/net/minecraft/core/block/Fluid.java
new file mode 100644
index 000000000..76a16094b
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/block/Fluid.java
@@ -0,0 +1,27 @@
+package net.minecraft.core.block;
+
+import net.minecraft.core.block.material.Material;
+import net.minecraft.core.world.World;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Random;
+
+public interface Fluid {
+    int tickDelay();
+
+    void animationTick(final @NotNull BlockLogicFluid logicFluid, final @NotNull World world, final int x, final int y, final int z, final @NotNull Random rand);
+
+    void checkForHarden(final @NotNull BlockLogicFluid logicFluid, final @NotNull World world, final int x, final int y, final int z, final @NotNull Material encountered);
+
+    boolean shouldTick();
+
+    void updateTickStill(final @NotNull BlockLogicFluid logicFluid, final @NotNull World world, final int x, final int y, final int z, final @NotNull Random rand);
+
+    byte getFlowDecayMod(final @NotNull BlockLogicFluid logicFluid, final @NotNull World world, final int x, final int y, final int z);
+
+    boolean canBecomeSource(final @NotNull BlockLogicFluid logicFluid, final @NotNull World world, final int x, final int y, final int z, final @NotNull Random rand);
+
+    void onFlowIntoBlock(final @NotNull BlockLogicFluid logicFluid, final @NotNull World world, final int x, final int y, final int z, final int meta);
+
+    boolean canSpreadTo(final @NotNull BlockLogicFluid logicFluid, final @NotNull World world, final int x, final int y, final int z, final @NotNull Material material);
+}
diff --git a/game/core/src/main/java/net/minecraft/core/block/FluidLava.java b/game/core/src/main/java/net/minecraft/core/block/FluidLava.java
new file mode 100644
index 000000000..32b533f53
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/block/FluidLava.java
@@ -0,0 +1,104 @@
+package net.minecraft.core.block;
+
+import net.minecraft.core.block.material.Material;
+import net.minecraft.core.world.Dimension;
+import net.minecraft.core.world.World;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Random;
+
+public class FluidLava
+    implements Fluid
+{
+    @Override
+    public int tickDelay() {
+        return 30;
+    }
+
+    @Override
+    public void animationTick(final @NotNull BlockLogicFluid logicFluid, final @NotNull World world, final int x, final int y, final int z, final @NotNull Random rand) {
+        final boolean isTop = world.getBlockMaterial(x, y + 1, z) == Material.air && !world.isBlockOpaqueCube(x, y + 1, z);
+
+        if (isTop && rand.nextInt(100) == 0) {
+            final double xOff = (double) x + rand.nextFloat();
+            final double yOff = (double) y + logicFluid.getBounds().maxY;
+            final double zOff = (double) z + rand.nextFloat();
+            world.spawnParticle("lava", xOff, yOff, zOff, 0.0D, 0.0D, 0.0D, 0);
+        }
+    }
+
+    @Override
+    public void checkForHarden(final @NotNull BlockLogicFluid logicFluid, final @NotNull World world, final int x, final int y, final int z, final @NotNull Material encountered) {
+        if (encountered == Material.water) {
+            final int data = world.getBlockMetadata(x, y, z) & 0x0F;
+            if (data == 0) {
+                if (world.dimension == Dimension.NETHER) {
+                    world.setBlockWithNotify(x, y, z, Blocks.COBBLE_NETHERRACK_IGNEOUS.id());
+                } else {
+                    world.setBlockWithNotify(x, y, z, Blocks.OBSIDIAN.id());
+                }
+            } else if (data <= 2) {
+                world.setBlockWithNotify(x, y, z, Blocks.COBBLE_GRANITE.id());
+            } else if (data <= 4) {
+                world.setBlockWithNotify(x, y, z, Blocks.COBBLE_STONE.id());
+            } else {
+                world.setBlockWithNotify(x, y, z, Blocks.COBBLE_BASALT.id());
+            }
+            BlockLogicFluid.fizz(world, x, y, z);
+        }
+    }
+
+    @Override
+    public boolean shouldTick() {
+        return true;
+    }
+
+    @Override
+    public void updateTickStill(final @NotNull BlockLogicFluid logicFluid, final @NotNull World world, int x, int y, int z, final @NotNull Random rand) {
+        final int count = rand.nextInt(3);
+        for (int i = 0; i < count; i++) {
+            x += rand.nextInt(3) - 1;
+            y++;
+            z += rand.nextInt(3) - 1;
+            final int id = world.getBlockId(x, y, z);
+            if (id == 0) {
+                if (isFlammable(world, x - 1, y, z) || isFlammable(world, x + 1, y, z) || isFlammable(world, x, y, z - 1) || isFlammable(world, x, y, z + 1) || isFlammable(world, x, y - 1, z) || isFlammable(world, x, y + 1, z)) {
+                    world.setBlockWithNotify(x, y, z, Blocks.FIRE.id());
+                    return;
+                }
+                continue;
+            }
+            if (Blocks.blocksList[id].getMaterial().blocksMotion()) {
+                return;
+            }
+        }
+    }
+
+    private static boolean isFlammable(final World world, final int x, final int y, final int z) {
+        return BlockLogicFire.canBurn(world, x, y, z);
+    }
+
+    @Override
+    public byte getFlowDecayMod(final @NotNull BlockLogicFluid logicFluid, final @NotNull World world, final int x, final int y, final int z) {
+        if (world.dimension == Dimension.NETHER) {
+            return 1;
+        } else {
+            return 2;
+        }
+    }
+
+    @Override
+    public boolean canBecomeSource(final @NotNull BlockLogicFluid logicFluid, final @NotNull World world, final int x, final int y, final int z, final @NotNull Random rand) {
+        return false;
+    }
+
+    @Override
+    public void onFlowIntoBlock(final @NotNull BlockLogicFluid logicFluid, final @NotNull World world, final int x, final int y, final int z, final int meta) {
+        BlockLogicFluid.fizz(world, x, y, z);
+    }
+
+    @Override
+    public boolean canSpreadTo(final @NotNull BlockLogicFluid logicFluid, final @NotNull World world, final int x, final int y, final int z, final @NotNull Material material) {
+        return material != Material.lava;
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/block/FluidWater.java b/game/core/src/main/java/net/minecraft/core/block/FluidWater.java
new file mode 100644
index 000000000..5da9a7903
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/block/FluidWater.java
@@ -0,0 +1,78 @@
+package net.minecraft.core.block;
+
+import net.minecraft.core.block.material.Material;
+import net.minecraft.core.enums.EnumDropCause;
+import net.minecraft.core.sound.SoundCategory;
+import net.minecraft.core.world.Dimension;
+import net.minecraft.core.world.World;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Random;
+
+public class FluidWater
+    implements Fluid
+{
+    @Override
+    public int tickDelay() {
+        return 5;
+    }
+
+    @Override
+    public void animationTick(final @NotNull BlockLogicFluid logicFluid, final @NotNull World world, final int x, final int y, final int z, final @NotNull Random rand) {
+        final int data = world.getBlockMetadata(x, y, z) & 0x0F;
+        final boolean isTop = world.getBlockMaterial(x, y + 1, z) == Material.air && !world.isBlockOpaqueCube(x, y + 1, z);
+        if (rand.nextInt(64) == 0) {
+            if (data > 0 && data < 8) {
+                world.playSoundEffect(null, SoundCategory.WORLD_SOUNDS, (float) x + 0.5F, (float) y + 0.5F, (float) z + 0.5F, "liquid.water", rand.nextFloat() * 0.25F + 0.75F, rand.nextFloat() * 1.0F + 0.5F);
+            }
+        }
+        if (world.dimension == Dimension.NETHER && isTop && data == 0 && rand.nextInt(4) == 0) {
+            world.spawnParticle("smoke", x + Math.random(), y + (14f / 16f), z + Math.random(), 0.0D, 0.0D, 0.0D, 1);
+        }
+
+    }
+
+    @Override
+    public void checkForHarden(final @NotNull BlockLogicFluid logicFluid, final @NotNull World world, final int x, final int y, final int z, final @NotNull Material encountered) {
+        if (encountered == Material.lava) {
+            final int data = world.getBlockMetadata(x, y, z) & 0x0F;
+            if (data == 0) {
+                world.setBlockWithNotify(x, y, z, Blocks.COBBLE_LIMESTONE.id());
+                BlockLogicFluid.fizz(world, x, y, z);
+            }
+        }
+    }
+
+    @Override
+    public boolean shouldTick() {
+        return false;
+    }
+
+    @Override
+    public void updateTickStill(final @NotNull BlockLogicFluid logicFluid, final @NotNull World world, final int x, final int y, final int z, final @NotNull Random rand) {
+        // Do nothing
+    }
+
+    @Override
+    public byte getFlowDecayMod(final @NotNull BlockLogicFluid logicFluid, final @NotNull World world, final int x, final int y, final int z) {
+        return 1;
+    }
+
+    @Override
+    public boolean canBecomeSource(final @NotNull BlockLogicFluid logicFluid, final @NotNull World world, final int x, final int y, final int z, final @NotNull Random rand) {
+        return world.dimension != Dimension.NETHER;
+    }
+
+    @Override
+    public void onFlowIntoBlock(final @NotNull BlockLogicFluid logicFluid, final @NotNull World world, final int x, final int y, final int z, final int meta) {
+        final int id = world.getBlockId(x, y, z);
+        if (Blocks.blocksList[id] != null) {
+            Blocks.blocksList[id].dropBlockWithCause(world, EnumDropCause.WORLD, x, y, z, world.getBlockMetadata(x, y, z), null, null);
+        }
+    }
+
+    @Override
+    public boolean canSpreadTo(final @NotNull BlockLogicFluid logicFluid, final @NotNull World world, final int x, final int y, final int z, final @NotNull Material material) {
+        return material != Material.lava;
+    }
+}
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
index d510153a7..69790e1f7 100644
--- 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
@@ -1,6 +1,7 @@
 package net.minecraft.core.current.wire;
 
 import com.mojang.logging.LogUtils;
+import net.minecraft.core.block.BlockLogicWireRedstone;
 import net.minecraft.core.current.util.BlockPos;
 import net.minecraft.core.current.util.BlockState;
 import net.minecraft.core.current.util.Mth;
@@ -30,6 +31,7 @@ public class WireNode extends Node {
 	int virtualPower;
 	/** The power level received from non-wire components. */
 	int externalPower;
+	int direction;
 	/**
 	 * A 4-bit number that keeps track of the power flow of the wires that give this
 	 * wire its power level.
@@ -55,7 +57,8 @@ public class WireNode extends Node {
 
 		this.connections = new WireConnectionManager(this);
 
-		this.virtualPower = this.currentPower = this.state.get();
+		this.virtualPower = this.currentPower = this.state.get() & BlockLogicWireRedstone.MASK_POWER;
+		this.direction = this.state.get() & BlockLogicWireRedstone.MASK_DIRECTION;
 		this.priority = priority();
 	}
 
@@ -117,7 +120,7 @@ public class WireNode extends Node {
 		}
 
 		currentPower = Mth.clamp(virtualPower, Redstone.SIGNAL_MIN, Redstone.SIGNAL_MAX);
-		state = state.set(currentPower);
+		state = state.set(currentPower | direction);
 
 		return WorldHelper.setWireState(world, pos, state);
 	}
diff --git a/game/core/src/main/java/net/minecraft/core/entity/Entity.java b/game/core/src/main/java/net/minecraft/core/entity/Entity.java
index 20089d7d6..ae747c612 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/Entity.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/Entity.java
@@ -814,7 +814,7 @@ public abstract class Entity
         int l = world.getBlockId(i, j, k);
         if(l != 0 && Blocks.blocksList[l].getMaterial() == material)
         {
-            float f = BlockLogicFluid.getWaterVolume(world.getBlockMetadata(i, j, k)) - 0.2F;
+            float f = BlockLogicFluid.getHeight(world.getBlockMetadata(i, j, k)) - 0.2F;
             float f1 = (float)(j + 1) - f;
             return d < (double)f1;
         } else
diff --git a/game/core/src/main/java/net/minecraft/core/util/linalg/Matrix4.java b/game/core/src/main/java/net/minecraft/core/util/linalg/Matrix4.java
index bd8655293..e8439f723 100644
--- a/game/core/src/main/java/net/minecraft/core/util/linalg/Matrix4.java
+++ b/game/core/src/main/java/net/minecraft/core/util/linalg/Matrix4.java
@@ -4,6 +4,8 @@ import net.minecraft.core.util.pool.ObjectPool;
 import net.minecraft.core.util.pool.Poolable;
 import org.jetbrains.annotations.NotNull;
 
+import java.util.Objects;
+
 public class Matrix4
     implements Poolable<Matrix4>
 {
@@ -83,9 +85,9 @@ public class Matrix4
      */
     public static @NotNull Matrix4 ortho(final double left, final double right, final double bottom, final double top, final double near, final double far) {
         return Matrix4.fromPool(
-            2.0 / (right - left), 0.0, 0.0, -(right + left) / (right - left),
-            0.0, 2.0 / (top - bottom), 0.0, -(top + bottom) / (top - bottom),
-            0.0, 0.0, -2.0 / (far - near), -(far + near) / (far - near),
+            2.0 / (right - left), 0.0, 0.0, (right + left) / (left - right),
+            0.0, 2.0 / (top - bottom), 0.0, (top + bottom) / (bottom - top),
+            0.0, 0.0, -2.0 / (far - near), (far + near) / (near - far),
             0.0, 0.0, 0.0, 1.0
         );
     }
@@ -122,7 +124,7 @@ public class Matrix4
         this.x.set(c0r0, c0r1, c0r2, c0r3);
         this.y.set(c1r0, c1r1, c1r2, c1r3);
         this.z.set(c2r0, c2r1, c2r2, c2r3);
-        this.z.set(c3r0, c3r1, c3r2, c3r3);
+        this.w.set(c3r0, c3r1, c3r2, c3r3);
 
         return this;
     }
@@ -130,18 +132,18 @@ public class Matrix4
     /**
      * Translates this matrix by a given vector.
      */
-    public @NotNull Matrix4 translate(final @NotNull Vector3 translation) {
-        return this.copyPooled().translateInPlace(translation);
+    public @NotNull Matrix4 translate(final double x, final double y, final double z) {
+        return this.copyPooled().translateInPlace(x, y, z);
     }
 
     /**
      * Translates this matrix by a given vector, modifying it in place.
      */
-    public @NotNull Matrix4 translateInPlace(final @NotNull Vector3 translation) {
+    public @NotNull Matrix4 translateInPlace(final double x, final double y, final double z) {
         final @NotNull Matrix4 translationMatrix = Matrix4.fromPool(
-            1.0, 0.0, 0.0, translation.x,
-            0.0, 1.0, 0.0, translation.y,
-            0.0, 0.0, 1.0, translation.z,
+            1.0, 0.0, 0.0, x,
+            0.0, 1.0, 0.0, y,
+            0.0, 0.0, 1.0, z,
             0.0, 0.0, 0.0, 1.0
         );
 
@@ -319,4 +321,22 @@ public class Matrix4
             this.w.x, this.w.y, this.w.z, this.w.w
         };
     }
+
+    @Override
+    public boolean equals(final Object object) {
+        if (this == object) return true;
+        if (object == null || getClass() != object.getClass()) return false;
+        final Matrix4 matrix4 = (Matrix4) object;
+        return Objects.equals(this.x, matrix4.x) && Objects.equals(this.y, matrix4.y) && Objects.equals(this.z, matrix4.z) && Objects.equals(this.w, matrix4.w);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(this.x, this.y, this.z, this.w);
+    }
+
+    @Override
+    public String toString() {
+        return "[ " + this.x + ", " + this.y + ", " + this.z + ", " + this.w + " ]";
+    }
 }
diff --git a/game/core/src/main/resources/log4j2.xml b/game/core/src/main/resources/log4j2.xml
index a32f30bc0..fb7e94559 100644
--- a/game/core/src/main/resources/log4j2.xml
+++ b/game/core/src/main/resources/log4j2.xml
@@ -2,7 +2,7 @@
 <Configuration status="WARN" packages="com.mojang.logging.plugins">
     <Appenders>
         <Console name="SysOut" target="SYSTEM_OUT">
-            <PatternLayout pattern="[%d{HH:mm:ss}] [%t/%level]: %msg{nolookups}%n" />
+            <PatternLayout pattern="[%d{HH:mm:ss}] [%logger/%level]: %msg{nolookups}%n" />
         </Console>
         <Queue name="ServerGuiConsole">
             <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
@@ -13,7 +13,7 @@
         </Queue>
         <RollingRandomAccessFile name="File" fileName="logs/latest.log" filePattern="logs/%d{yyyy-MM-dd}-%i.log.gz">
             <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
-            <PatternLayout pattern="[%d{HH:mm:ss}] [%t/%level]: %msg{nolookups}%n" />
+            <PatternLayout pattern="[%d{HH:mm:ss}] [%logger/%level]: %msg{nolookups}%n" />
             <Policies>
                 <TimeBasedTriggeringPolicy />
                 <OnStartupTriggeringPolicy />
diff --git a/util/logging/src/main/java/com/mojang/logging/LogUtils.java b/util/logging/src/main/java/com/mojang/logging/LogUtils.java
index e5e7a9ce6..7747d8292 100644
--- a/util/logging/src/main/java/com/mojang/logging/LogUtils.java
+++ b/util/logging/src/main/java/com/mojang/logging/LogUtils.java
@@ -68,7 +68,7 @@ public class LogUtils
     }
     
     public static CategorizedLogger getLogger() {
-        return new CategorizedLogger(LoggerFactory.getLogger(getCallerClass()));
+        return new CategorizedLogger(LoggerFactory.getLogger(getCallerClass().getSimpleName()));
     }
 
     private static Class<?> getCallerClass() { // Hacky way to replace the STACK_WALKER since STACK_WALKER uses java 9 language features
