diff --git a/game/client/.gitignore b/game/client/.gitignore
index 2959db291..4f654f768 100644
--- a/game/client/.gitignore
+++ b/game/client/.gitignore
@@ -1,2 +1,3 @@
 build
+dumpTextures
 logs
\ No newline at end of file
diff --git a/game/client/build.gradle.kts b/game/client/build.gradle.kts
index faee365fa..cde808703 100644
--- a/game/client/build.gradle.kts
+++ b/game/client/build.gradle.kts
@@ -13,7 +13,7 @@ val include: Configuration by configurations.creating
 
 repositories {
     mavenCentral()
-    maven("https://libraries.minecraft.net/")
+    maven("https://libraries.minecraft.net")
     maven("https://jitpack.io")
     ivy {
         url = uri("https://piston-data.mojang.com")
@@ -54,6 +54,9 @@ dependencies {
 
     implementation("legacy-lwjgl3:legacy-lwjgl3:1.0.3")?.let { include(it) }
 
+    implementation("org.apache-extras.beanshell:bsh:2.0b6")?.let { include(it) }
+    implementation("com.deepoove:codehighlight:1.0.3")?.let { include(it) }
+
     implementation(libs.bundles.lwjgl)
     for (lib in libs.bundles.lwjgl.get()) {
         runtimeOnly(lib.group, lib.name, classifier = rootProject.ext["lwjglNatives"].toString())
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 3cec32889..13500a906 100644
--- a/game/client/src/main/java/net/minecraft/client/Minecraft.java
+++ b/game/client/src/main/java/net/minecraft/client/Minecraft.java
@@ -15,6 +15,8 @@ import java.util.*;
 
 import com.mojang.logging.LogUtils;
 import com.mojang.nbt.tags.CompoundTag;
+import net.minecraft.client.console.LogMessageReceiver;
+import net.minecraft.client.console.shell.BindManager;
 import net.minecraft.client.entity.ClientSkinVariantList;
 import net.minecraft.client.entity.player.PlayerLocal;
 import net.minecraft.client.enums.EnumGPUVendor;
@@ -259,7 +261,7 @@ public class Minecraft
     public ScaledResolution resolution;
     public boolean fullbright = false;
     public Renderer renderer;
-    public ControllerInput controllerInput;
+    public @Nullable ControllerInput controllerInput;
     public final MouseInput mouseInput = new MouseInput(this);
     public InputType inputType = InputType.KEYBOARD;
     public final ScreenshotHelper screenshotHelper = new ScreenshotHelper(this);
@@ -453,6 +455,9 @@ public class Minecraft
         ImageIO.setUseCache(false); // Speeds up boot time
 		mcDataDir = getMinecraftDir();
 
+        // Initialize LogMessageReceiver to capture output
+        LogMessageReceiver.init();
+
         Holidays.initialize();
 
         // Manually initialize blocks and items
@@ -1571,8 +1576,13 @@ public class Minecraft
         		if(Keyboard.getEventKeyState() && gameSettings.showFrameTimes.value && DebugRender.keyPressed(Keyboard.getEventKey())) {
         			continue;
         		}
-            	
-            	
+
+                final @Nullable Runnable scriptBindAction = BindManager.getInstance().getActionForKey(Keyboard.getEventKey());
+                if (scriptBindAction != null) {
+                    scriptBindAction.run();
+                    continue;
+                }
+
                 boolean shift = Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) || Keyboard.isKeyDown(Keyboard.KEY_RSHIFT);
                 //Fix your keyboard Mak you broke the overlay toggle
                 boolean control = Keyboard.isKeyDown(Keyboard.KEY_LCONTROL) || Keyboard.isKeyDown(Keyboard.KEY_RCONTROL) /* || Keyboard.isKeyDown(Keyboard.KEY_F4)*/;
@@ -1720,6 +1730,10 @@ public class Minecraft
                         {
                             mouseInput.releaseCursor();
                         }
+                        else if (gameSettings.keyShowConsole.isPressEvent() && gameSettings.developerMode.value)
+                        {
+                            displayScreen(new ScreenConsole(this.currentScreen));
+                        }
                         else if (Keyboard.getEventKey() == Keyboard.KEY_ESCAPE)
                         {
                             displayInGameMenu();
@@ -2165,32 +2179,8 @@ public class Minecraft
                 thePlayer.displayGuidebookScreen();
             }
         }else {
-            Screen currentScreen = this.currentScreen;
-            if(controller.buttonB.pressedThisFrame()) {
-                currentScreen.keyPressed((char)0, Keyboard.KEY_BACK, -1, -1);
-            }
-            if(controller.buttonStart.pressedThisFrame()) {
-                currentScreen.keyPressed((char)0, Keyboard.KEY_ESCAPE, -1, -1);
-            }
-            if(controller.buttonA.pressedThisFrame()) {
-                currentScreen.mouseClicked((int)controller.cursorX, (int)controller.cursorY, 0);
-            }
-            if(controller.buttonA.releasedThisFrame()) {
-                currentScreen.mouseReleased((int)controller.cursorX, (int)controller.cursorY, 0);
-            }
-            if(controller.buttonY.pressedThisFrame()) {
-                currentScreen.mouseClicked((int)controller.cursorX, (int)controller.cursorY, 1);
-            }
-            if(controller.buttonY.releasedThisFrame()) {
-                currentScreen.mouseReleased((int)controller.cursorX, (int)controller.cursorY, 1);
-            }
-            if(controller.buttonX.pressedThisFrame()) {
-                currentScreen.mouseClicked((int)controller.cursorX, (int)controller.cursorY, 10);
-            }
-
-            //ContainerInventory Controls
             try{
-                currentScreen.controllerInput(controller);
+                this.currentScreen.controllerInput(controller);
             }catch (Exception e) {
                 LOGGER.error("Exception occurred while running gui specific controller actions!", e);
             }
diff --git a/game/client/src/main/java/net/minecraft/client/console/LogMessage.java b/game/client/src/main/java/net/minecraft/client/console/LogMessage.java
new file mode 100644
index 000000000..6cddd96d7
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/console/LogMessage.java
@@ -0,0 +1,39 @@
+package net.minecraft.client.console;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public final class LogMessage {
+    public enum Level {
+        ERROR,
+        WARN,
+        INFO,
+        DEBUG,
+        TRACE
+    }
+
+    private final @Nullable Level level;
+    private final @NotNull String message;
+
+    public LogMessage(@NotNull String message) {
+        @Nullable Level level = null;
+        for (final @NotNull Level l : Level.values()) {
+            final @NotNull String prefix = l + ": ";
+            if (message.startsWith(prefix)) {
+                level = l;
+                message = message.substring(prefix.length());
+                break;
+            }
+        }
+        this.level = level;
+        this.message = message;
+    }
+
+    public @Nullable Level getLevel() {
+        return this.level;
+    }
+
+    public @NotNull String getMessage() {
+        return this.message;
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/console/LogMessageReceiver.java b/game/client/src/main/java/net/minecraft/client/console/LogMessageReceiver.java
new file mode 100644
index 000000000..e6f6a13ae
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/console/LogMessageReceiver.java
@@ -0,0 +1,49 @@
+package net.minecraft.client.console;
+
+import com.mojang.logging.LogQueues;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+public abstract class LogMessageReceiver {
+    private static final int MESSAGE_CACHE_SIZE = 1000;
+    private static final @NotNull List<@NotNull LogMessage> MESSAGE_QUEUE = new ArrayList<>(MESSAGE_CACHE_SIZE);
+    private static final @NotNull List<@NotNull Consumer<@NotNull LogMessage>> CALLBACKS = new ArrayList<>();
+
+    public static void init() {
+        final @NotNull Thread logAppenderThread = new Thread(() -> {
+            do {
+                final @Nullable String logEvent = LogQueues.getNextLogEvent("IngameConsole");
+                if (logEvent != null) {
+                    append(logEvent);
+                } else {
+                    break;
+                }
+            } while (true);
+        });
+        logAppenderThread.setDaemon(true);
+        logAppenderThread.start();
+    }
+
+    public static void append(final @NotNull String message) {
+        final @NotNull LogMessage logMessage = new LogMessage(message);
+        MESSAGE_QUEUE.add(logMessage);
+        if (MESSAGE_QUEUE.size() > 1000) {
+            MESSAGE_QUEUE.remove(0);
+        }
+        for (@NotNull Consumer<@NotNull LogMessage> consumer : CALLBACKS) {
+            consumer.accept(logMessage);
+        }
+    }
+
+    public static void addCallback(final @NotNull Consumer<@NotNull LogMessage> callback) {
+        CALLBACKS.add(callback);
+    }
+
+    public static @NotNull List<@NotNull LogMessage> getQueue() {
+        return MESSAGE_QUEUE;
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/console/shell/BindManager.java b/game/client/src/main/java/net/minecraft/client/console/shell/BindManager.java
new file mode 100644
index 000000000..aed2a30c3
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/console/shell/BindManager.java
@@ -0,0 +1,37 @@
+package net.minecraft.client.console.shell;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+public final class BindManager {
+    private static final @NotNull BindManager INSTANCE = new BindManager();
+    public static @NotNull BindManager getInstance() {
+        return INSTANCE;
+    }
+
+    private @NotNull Map<@NotNull Integer, @NotNull Runnable> keyToRunnableMap = new HashMap<>();
+
+    public @NotNull Collection<@NotNull Integer> getKeys() {
+        return this.keyToRunnableMap.keySet();
+    }
+
+    public @Nullable Runnable getActionForKey(final int key) {
+        if (this.keyToRunnableMap.containsKey(key)) {
+            return this.keyToRunnableMap.get(key);
+        }
+
+        return null;
+    }
+
+    public void bindKeyToAction(final int key, final @Nullable Runnable action) {
+        if (action == null) {
+            this.keyToRunnableMap.remove(key);
+        } else {
+            this.keyToRunnableMap.put(key, action);
+        }
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/console/shell/RawOutputWrapper.java b/game/client/src/main/java/net/minecraft/client/console/shell/RawOutputWrapper.java
new file mode 100644
index 000000000..55157a9d5
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/console/shell/RawOutputWrapper.java
@@ -0,0 +1,9 @@
+package net.minecraft.client.console.shell;
+
+public class RawOutputWrapper {
+    public String value;
+
+    public RawOutputWrapper(String value) {
+        this.value = value;
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/console/shell/RuntimeExecutor.java b/game/client/src/main/java/net/minecraft/client/console/shell/RuntimeExecutor.java
new file mode 100644
index 000000000..bceb92777
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/console/shell/RuntimeExecutor.java
@@ -0,0 +1,155 @@
+package net.minecraft.client.console.shell;
+
+import bsh.EvalError;
+import bsh.Interpreter;
+import com.mojang.logging.LogUtils;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.console.shell.commands.reloadScripts;
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.item.Items;
+import net.minecraft.core.net.command.TextFormatting;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+
+public final class RuntimeExecutor {
+    private static final Logger LOGGER = LogUtils.getLogger();
+
+    private final @NotNull Interpreter interpreter;
+
+    public RuntimeExecutor() {
+        this.interpreter = new Interpreter();
+
+        reloadScripts.invoke(this.interpreter, null);
+    }
+
+    public @NotNull String runCode(final @NotNull String line) {
+        StringBuilder sb = new StringBuilder();
+
+        if (line.equalsIgnoreCase("help") || line.equals("?")) {
+            sb
+                .append(TextFormatting.WHITE)
+                .append("[ Better than Adventure! Developer Console ]\n")
+                .append(TextFormatting.LIGHT_GRAY)
+                .append("This is a BeanShell-powered developer shell for Better than Adventure!, ")
+                .append("which allows you to view and modify most of the game's objects through ")
+                .append("interpreted Java code.\n")
+                .append(TextFormatting.YELLOW)
+                .append("If you don't know what any of this means, it's probably best to close this ")
+                .append("window.\n")
+                .append("\n")
+                .append("Along with BeanShell's default command set, this shell provides some extra ")
+                .append("commands to make interfacing with BTA! easier.\n")
+
+                // mc()
+                .append(" - ")
+                .append(TextFormatting.WHITE)
+                .append("Minecraft ")
+                .append(TextFormatting.LIGHT_BLUE)
+                .append("mc")
+                .append(TextFormatting.WHITE)
+                .append("();\n")
+                .append("       ")
+                .append("Fetches the current Minecraft object.\n")
+
+                // fields()
+                .append(" - ")
+                .append(TextFormatting.ORANGE)
+                .append("void ")
+                .append(TextFormatting.LIGHT_BLUE)
+                .append("fields")
+                .append(TextFormatting.WHITE)
+                .append("(Object o);\n")
+                .append("   ")
+                .append(TextFormatting.ORANGE)
+                .append("void ")
+                .append(TextFormatting.LIGHT_BLUE)
+                .append("fields")
+                .append(TextFormatting.WHITE)
+                .append("(Class<?> clazz);\n")
+                .append("       ")
+                .append("Displays all the fields of a given object or class.\n")
+
+                // methods()
+                .append(" - ")
+                .append(TextFormatting.ORANGE)
+                .append("void ")
+                .append(TextFormatting.LIGHT_BLUE)
+                .append("methods")
+                .append(TextFormatting.WHITE)
+                .append("(Object o);\n")
+                .append("   ")
+                .append(TextFormatting.ORANGE)
+                .append("void ")
+                .append(TextFormatting.LIGHT_BLUE)
+                .append("methods")
+                .append(TextFormatting.WHITE)
+                .append("(Class<?> clazz);\n")
+                .append("       ")
+                .append("Displays all the methods of a given object or class.\n")
+
+                // logEnable()
+                .append(" - ")
+                .append(TextFormatting.ORANGE)
+                .append("void ")
+                .append(TextFormatting.LIGHT_BLUE)
+                .append("logEnable")
+                .append(TextFormatting.WHITE)
+                .append("(String category);\n")
+                .append("       ")
+                .append("Enables a given logging category.\n")
+
+                // logDisable()
+                .append(" - ")
+                .append(TextFormatting.ORANGE)
+                .append("void ")
+                .append(TextFormatting.LIGHT_BLUE)
+                .append("logDisable")
+                .append(TextFormatting.WHITE)
+                .append("(String category);\n")
+                .append("       ")
+                .append("Disables a given logging category.\n")
+
+                // logList()
+                .append(" - ")
+                .append(TextFormatting.ORANGE)
+                .append("void ")
+                .append(TextFormatting.LIGHT_BLUE)
+                .append("logList")
+                .append(TextFormatting.WHITE)
+                .append("();\n")
+                .append("       ")
+                .append("Displays all known logging categories.\n")
+            ;
+
+            return sb.toString();
+        }
+
+        try {
+            final @Nullable Object result = this.interpreter.eval(line);
+            sb.append(TextFormatting.WHITE);
+            if (result == null) {
+                sb.append(TextFormatting.ORANGE).append("null");
+            } else if (result instanceof RawOutputWrapper) {
+                sb.append(((RawOutputWrapper) result).value);
+            } else if (result.getClass().isPrimitive()) {
+                sb.append(TextFormatting.LIGHT_BLUE).append(result);
+            } else if (result instanceof String) {
+                sb.append(TextFormatting.GREEN).append("\"").append(result).append("\"");
+            } else {
+                sb.append(result);
+            }
+        } catch (final @NotNull EvalError e) {
+            sb
+                .append(TextFormatting.RED)
+                .append(e);
+        } catch (final @NotNull Throwable t) {
+            sb
+                .append(TextFormatting.RED)
+                .append("Caught exception: ")
+                .append(t);
+        }
+
+        return sb.toString();
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/console/shell/commands/fields.java b/game/client/src/main/java/net/minecraft/client/console/shell/commands/fields.java
new file mode 100644
index 000000000..0f76393c2
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/console/shell/commands/fields.java
@@ -0,0 +1,115 @@
+package net.minecraft.client.console.shell.commands;
+
+import bsh.CallStack;
+import bsh.ClassIdentifier;
+import bsh.Interpreter;
+import net.minecraft.client.console.shell.RawOutputWrapper;
+import net.minecraft.core.net.command.TextFormatting;
+import org.jetbrains.annotations.Nullable;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+public class fields {
+    public static RawOutputWrapper invoke(Interpreter env, CallStack callstack, Object o) {
+        final Class<?> clazz = o.getClass();
+        StringBuilder sb = new StringBuilder();
+        Field[] fields = clazz.getFields();
+        for (Field field : fields) {
+            sb
+                .append(TextFormatting.WHITE)
+                .append("    ")
+                .append(TextFormatting.ORANGE)
+                .append(Modifier.toString(field.getModifiers()))
+                .append(" ");
+            if (field.getType().isPrimitive()) {
+                sb
+                    .append(TextFormatting.ORANGE);
+            } else {
+                sb
+                    .append(TextFormatting.WHITE);
+            }
+            sb
+                .append(field.getType().getSimpleName())
+                .append(" ")
+                .append(TextFormatting.PINK)
+                .append(field.getName())
+                .append(TextFormatting.WHITE)
+                .append(" = ");
+            appendObject(field, o, sb);
+            sb
+                .append(TextFormatting.WHITE)
+                .append(";\n");
+        }
+        return new RawOutputWrapper(sb.toString());
+    }
+
+    public static RawOutputWrapper invoke(Interpreter env, CallStack callstack, ClassIdentifier clazz) {
+        StringBuilder sb = new StringBuilder();
+        if (clazz.getTargetClass() != null) {
+            Field[] fields = clazz.getTargetClass().getFields();
+            for (Field field : fields) {
+                sb
+                    .append(TextFormatting.WHITE)
+                    .append("    ")
+                    .append(TextFormatting.ORANGE)
+                    .append(Modifier.toString(field.getModifiers()))
+                    .append(" ")
+                    .append(TextFormatting.WHITE)
+                    .append(field.getType().getSimpleName())
+                    .append(" ")
+                    .append(TextFormatting.PINK)
+                    .append(field.getName());
+                if (Modifier.isStatic(field.getModifiers())) {
+                    sb
+                        .append(TextFormatting.WHITE)
+                        .append(" = ");
+                    appendObject(field, null, sb);
+                }
+                sb
+                    .append(TextFormatting.WHITE)
+                    .append(";\n");
+            }
+            return new RawOutputWrapper(sb.toString());
+        } else {
+            return null;
+        }
+    }
+
+    private static void appendObject(Field field, Object o, StringBuilder sb) {
+        try {
+            final @Nullable Object v = field.get(null);
+            if (o instanceof String) {
+                sb
+                    .append(TextFormatting.LIGHT_BLUE)
+                    .append("\"")
+                    .append(v)
+                    .append("\"");
+            } else if (!field.getType().isPrimitive() && v != null) {
+                if (v.getClass().getMethod("toString").getDeclaringClass() == Object.class) {
+                    sb
+                        .append(TextFormatting.ORANGE)
+                        .append("new ")
+                        .append(TextFormatting.WHITE)
+                        .append(v.getClass().getSimpleName())
+                        .append("()");
+                } else {
+                    sb
+                        .append(TextFormatting.LIGHT_BLUE)
+                        .append("[")
+                        .append(v)
+                        .append("]");
+                }
+            } else {
+                sb
+                    .append(TextFormatting.LIGHT_BLUE)
+                    .append(v);
+            }
+        } catch (IllegalAccessException e) {
+            sb
+                .append("?!");
+        } catch (NoSuchMethodException e) {
+            // This literally cannot happen
+        }
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/console/shell/commands/methods.java b/game/client/src/main/java/net/minecraft/client/console/shell/commands/methods.java
new file mode 100644
index 000000000..b58cd8a3e
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/console/shell/commands/methods.java
@@ -0,0 +1,84 @@
+package net.minecraft.client.console.shell.commands;
+
+import bsh.CallStack;
+import bsh.ClassIdentifier;
+import bsh.Interpreter;
+import net.minecraft.client.console.shell.RawOutputWrapper;
+import net.minecraft.core.net.command.TextFormatting;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Parameter;
+
+public class methods {
+    public static RawOutputWrapper invoke(Interpreter env, CallStack callstack, Object o) {
+        return inner(o.getClass());
+    }
+
+    public static RawOutputWrapper invoke(Interpreter env, CallStack callstack, ClassIdentifier clazz) {
+        return inner(clazz.getTargetClass());
+    }
+
+    private static RawOutputWrapper inner(Class<?> clazz) {
+        if (clazz == null) {
+            return null;
+        }
+
+        StringBuilder sb = new StringBuilder();
+        Method[] methods = clazz.getMethods();
+        for (Method method : methods) {
+            Class<?> returnType = method.getReturnType();
+            sb
+                .append(TextFormatting.WHITE)
+                .append("    ")
+                .append(TextFormatting.ORANGE)
+                .append(Modifier.toString(method.getModifiers()))
+                .append(" ");
+            if (returnType.isPrimitive()) {
+                sb
+                    .append(TextFormatting.ORANGE);
+            } else {
+                sb
+                    .append(TextFormatting.WHITE);
+            }
+            sb
+                .append(returnType.getSimpleName())
+                .append(" ")
+                .append(TextFormatting.LIGHT_BLUE)
+                .append(method.getName())
+                .append(TextFormatting.WHITE)
+                .append("(");
+            Parameter[] parameters = method.getParameters();
+            for (int i = 0; i < method.getParameterCount(); i++) {
+                Parameter parameter = parameters[i];
+                String modifierString = Modifier.toString(parameter.getModifiers());
+                if (!modifierString.isEmpty()) {
+                    sb
+                        .append(TextFormatting.ORANGE)
+                        .append(modifierString)
+                        .append(" ");
+                }
+                if (parameter.getType().isPrimitive()) {
+                    sb
+                        .append(TextFormatting.ORANGE);
+                } else {
+                    sb
+                        .append(TextFormatting.WHITE);
+                }
+                sb
+                    .append(parameter.getType().getSimpleName())
+                    .append(" ")
+                    .append(TextFormatting.WHITE)
+                    .append(parameter.getName());
+                if (i < parameters.length - 1) {
+                    sb
+                        .append(", ");
+                }
+            }
+            sb
+                .append(TextFormatting.WHITE)
+                .append(");\n");
+        }
+        return new RawOutputWrapper(sb.toString());
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/console/shell/commands/reloadScripts.java b/game/client/src/main/java/net/minecraft/client/console/shell/commands/reloadScripts.java
new file mode 100644
index 000000000..baa950179
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/console/shell/commands/reloadScripts.java
@@ -0,0 +1,118 @@
+package net.minecraft.client.console.shell.commands;
+
+import bsh.CallStack;
+import bsh.Interpreter;
+import com.mojang.logging.CategorizedLogger;
+import com.mojang.logging.LogUtils;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.console.shell.RawOutputWrapper;
+import net.minecraft.client.render.texturepack.TexturePack;
+import net.minecraft.core.net.command.TextFormatting;
+
+import java.io.File;
+import java.net.URI;
+import java.net.URL;
+import java.nio.file.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.stream.Stream;
+
+public class reloadScripts {
+    private static final CategorizedLogger LOGGER = LogUtils.getLogger();
+
+    public static void invoke(Interpreter env, CallStack callstack) {
+        try {
+
+            LOGGER.warn("Clearing current state...");
+            env.eval("clear()");
+
+            LOGGER.warn("Loading internal commands...");
+            env.eval("importCommands(\"net.minecraft.client.console.shell.commands\")");
+            env.eval("importCommands(\"scripts.beanshell.commands\")");
+
+            LOGGER.warn("Loading external commands...");
+            File[] externalCommands = new File(Minecraft.getMinecraft().getMinecraftDir(), "/scripts/beanshell/commands").listFiles();
+            if (externalCommands != null) {
+                for (File file : externalCommands) {
+                    if (!file.getAbsolutePath().endsWith(".bsh")) continue;
+                    env.eval(("source(\"" + file.getAbsolutePath() + "\")").replace("\\", "/"));
+                }
+            }
+
+            LOGGER.warn("Loading internal scripts...");
+            String[] internalFiles = getFilesInDirectory("/scripts/beanshell/");
+            for (String file : internalFiles) {
+                if (!file.endsWith(".bsh")) continue;
+                env.eval("source(\"" + file.replace("/", ".") + "\")");
+            }
+
+            LOGGER.warn("Loading external scripts...");
+            File[] externalFiles = new File(Minecraft.getMinecraft().getMinecraftDir(), "/scripts/beanshell/").listFiles();
+            if (externalFiles != null) {
+                for (File file : externalFiles) {
+                    if (!file.getAbsolutePath().endsWith(".bsh")) continue;
+                    env.eval(("source(\"" + file.getAbsolutePath() + "\")").replace("\\", "/"));
+                }
+            }
+
+            LOGGER.info("Loaded commands and scripts.");
+        } catch (Exception e) {
+            LOGGER.error("Caught exception:", e);
+            LOGGER.error("Could not reload scripts!");
+        }
+    }
+
+    private static String[] getFilesInDirectory(String directory)
+    {
+        List<String> paths = new ArrayList<>();
+
+        if (!directory.endsWith("/")) directory = directory + "/";
+
+        label: try
+        {
+            URL url = TexturePack.class.getResource(directory);
+            if (url == null) break label;
+            URI uri = url.toURI();
+            Path myPath;
+            FileSystem fileSystem = null;
+            if (uri.getScheme().equals("jar"))
+            {
+                try
+                {
+                    fileSystem = FileSystems.getFileSystem(uri);
+                }
+                catch (Exception e)
+                {
+                    fileSystem = FileSystems.newFileSystem(uri, Collections.emptyMap());
+                }
+                myPath = fileSystem.getPath(directory);
+            }
+            else
+            {
+                myPath = Paths.get(uri);
+            }
+            try (Stream<Path> walk = Files.walk(myPath, 1))
+            {
+                Iterator<Path> it = walk.iterator();
+                it.next();
+                while(it.hasNext())
+                {
+                    paths.add(directory + it.next().getFileName().toString());
+                }
+            }
+            if (fileSystem != null)
+            {
+                fileSystem.close();
+            }
+        }
+        catch (Exception e)
+        {
+            LOGGER.error("Exception while loading all files in directory '{}'!", directory, e);
+        }
+
+        return paths.toArray(new String[0]);
+    }
+
+}
diff --git a/game/client/src/main/java/net/minecraft/client/console/syntax/HighlightedRenderer.java b/game/client/src/main/java/net/minecraft/client/console/syntax/HighlightedRenderer.java
new file mode 100644
index 000000000..9ec8047a3
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/console/syntax/HighlightedRenderer.java
@@ -0,0 +1,130 @@
+package net.minecraft.client.console.syntax;
+
+import com.codewaves.codehighlight.core.StyleRenderer;
+import net.minecraft.core.net.command.TextFormatting;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.*;
+
+public class HighlightedRenderer implements StyleRenderer<String> {
+    private static final Map<String, TextFormatting> CLASS_NAME_TO_FORMATTING = new HashMap<>();
+    private static final List<String> KEYWORDS = new ArrayList<>();
+    static {
+        CLASS_NAME_TO_FORMATTING.put("comment", TextFormatting.LIGHT_GRAY);
+        CLASS_NAME_TO_FORMATTING.put("string", TextFormatting.GREEN);
+        CLASS_NAME_TO_FORMATTING.put("number", TextFormatting.LIGHT_BLUE);
+        CLASS_NAME_TO_FORMATTING.put("title", TextFormatting.LIGHT_BLUE);
+        CLASS_NAME_TO_FORMATTING.put("regexp", TextFormatting.RED);
+        CLASS_NAME_TO_FORMATTING.put("doctag", TextFormatting.GREEN);
+        CLASS_NAME_TO_FORMATTING.put("class", TextFormatting.WHITE);
+        CLASS_NAME_TO_FORMATTING.put("function", TextFormatting.LIGHT_BLUE);
+        CLASS_NAME_TO_FORMATTING.put("params", TextFormatting.WHITE);
+        CLASS_NAME_TO_FORMATTING.put("meta", TextFormatting.YELLOW);
+
+        KEYWORDS.addAll(Arrays.asList(
+            "abstract",
+            "assert",
+            "boolean",
+            "break",
+            "byte",
+            "case",
+            "catch",
+            "char",
+            "class",
+            "continue",
+            "const",
+            "default",
+            "do",
+            "double",
+            "else",
+            "enum",
+            "exports",
+            "extends",
+            "final",
+            "finally",
+            "float",
+            "for",
+            "goto",
+            "if",
+            "implements",
+            "import",
+            "instanceof",
+            "int",
+            "interface",
+            "long",
+            "module",
+            "native",
+            "new",
+            "package",
+            "private",
+            "protected",
+            "public",
+            "requires",
+            "return",
+            "short",
+            "static",
+            "strictfp",
+            "super",
+            "switch",
+            "synchronized",
+            "this",
+            "throw",
+            "throws",
+            "transient",
+            "try",
+            "var",
+            "void",
+            "volatile",
+            "while",
+            "null"
+        ));
+    }
+
+    private final @NotNull StringBuilder sb = new StringBuilder();
+
+    @Override
+    public void onStart() {
+        this.sb.setLength(0);
+    }
+
+    @Override
+    public void onFinish() { }
+
+    @Override
+    public void onPushStyle(String style) {
+        this.sb.append(CLASS_NAME_TO_FORMATTING.getOrDefault(style, TextFormatting.WHITE));
+    }
+
+    @Override
+    public void onPopStyle() {
+        this.sb.append(TextFormatting.WHITE);
+    }
+
+    @Override
+    public void onPushCodeBlock(CharSequence codeLexeme) {
+        if (KEYWORDS.contains(codeLexeme.toString())) {
+            this.sb.append(TextFormatting.ORANGE);
+        }
+        this.sb.append(codeLexeme);
+    }
+
+    @Override
+    public void onPushSubLanguage(String name, String code) {
+        this.sb.append(code);
+    }
+
+    @Override
+    public void onPushOriginalSubLanguage(String name, CharSequence code) {
+        this.sb.append(code);
+    }
+
+    @Override
+    public void onAbort(CharSequence code, Exception e) {
+        this.sb.append(code);
+    }
+
+    @Override
+    public String getResult() {
+        return this.sb.toString();
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/console/syntax/RendererFactory.java b/game/client/src/main/java/net/minecraft/client/console/syntax/RendererFactory.java
new file mode 100644
index 000000000..55e846f4a
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/console/syntax/RendererFactory.java
@@ -0,0 +1,11 @@
+package net.minecraft.client.console.syntax;
+
+import com.codewaves.codehighlight.core.StyleRenderer;
+import com.codewaves.codehighlight.core.StyleRendererFactory;
+
+public class RendererFactory implements StyleRendererFactory<String> {
+    @Override
+    public StyleRenderer<String> create(String languageName) {
+        return new HighlightedRenderer();
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/entity/particle/ParticleAsh.java b/game/client/src/main/java/net/minecraft/client/entity/particle/ParticleAsh.java
new file mode 100644
index 000000000..8ff99e5f7
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/entity/particle/ParticleAsh.java
@@ -0,0 +1,34 @@
+package net.minecraft.client.entity.particle;
+
+import net.minecraft.client.render.texture.stitcher.TextureRegistry;
+import net.minecraft.core.world.World;
+
+public class ParticleAsh extends Particle {
+    public ParticleAsh(World world, double x, double y, double z, double xa, double ya, double za) {
+        super(world, x, y, z, xa, ya, za);
+        this.xd = xa + (Math.random() * 2D - 1.0D) * 0.05F;
+        this.yd = ya - Math.random() * 2D * 0.2F;
+        this.zd = za + (Math.random() * 2D - 1.0D) * 0.05F;
+        this.rCol = this.gCol = this.bCol = 0.2F + this.random.nextFloat() * 0.3F;
+        this.size = this.random.nextFloat() * this.random.nextFloat() * 6F + 1.0F;
+        this.lifetime = (int) (64 / (this.random.nextFloat() * 0.8D + 0.2D)) + 2;
+    }
+
+    @Override
+    public void tick() {
+        this.xo = this.x;
+        this.yo = this.y;
+        this.zo = this.z;
+        if (this.age++ >= this.lifetime) {
+            this.remove();
+        }
+        this.tex = TextureRegistry.getTexture("minecraft:particle/puff_0");
+        move(this.xd, this.yd, this.zd);
+        this.xd *= 0.9D;
+        this.zd *= 0.9D;
+        if (this.onGround) {
+            this.xd *= 0.7D;
+            this.zd *= 0.7D;
+        }
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/entity/particle/ParticleDispatcher.java b/game/client/src/main/java/net/minecraft/client/entity/particle/ParticleDispatcher.java
index f899c04a5..79b564dad 100644
--- a/game/client/src/main/java/net/minecraft/client/entity/particle/ParticleDispatcher.java
+++ b/game/client/src/main/java/net/minecraft/client/entity/particle/ParticleDispatcher.java
@@ -37,7 +37,7 @@ public final class ParticleDispatcher extends Dispatcher<String, ParticleLambda>
         INSTANCE = this;
         Minecraft mc = Minecraft.getMinecraft();
         addDispatch("bubble", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleBubble(world, x, y, z, motionX, motionY, motionZ, true));
-        addDispatch("smoke", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleSmoke(world, x, y, z, motionX, motionY, motionX));
+        addDispatch("smoke", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleSmoke(world, x, y, z, motionX, motionY, motionX, 1.0f, data));
         addDispatch("note", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleNote(world, x, y, z, motionX, motionY, motionX, data));
         addDispatch("portal", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticlePortal(world, x, y, z, motionX, motionY, motionX, data));
         addDispatch("explode", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleExplode(world, x, y, z, motionX, motionY, motionX));
@@ -47,7 +47,7 @@ public final class ParticleDispatcher extends Dispatcher<String, ParticleLambda>
         addDispatch("lava", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleLava(world, x, y, z));
         addDispatch("footstep", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleFootstep(mc.textureManager, world, x, y, z));
         addDispatch("splash", (world1, d, d1, d2, d3, d4, d5, data) -> new ParticleSplash(world1, d, d1, d2, d3, d4, d5));
-        addDispatch("largesmoke", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleSmoke(world, x, y, z, motionX, motionY, motionX, 2.5f));
+        addDispatch("largesmoke", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleSmoke(world, x, y, z, motionX, motionY, motionX, 2.5f, data));
         addDispatch("puffrgb", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticlePuffRGB(world, x, y, z, (float) motionX, (float) motionY, (float) motionZ, data));
         addDispatch("reddust", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleRedstoneDust(world, x, y, z, data));
         addDispatch("item", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleItemBreaking(world, x, y, z, Item.itemsList[MathHelper.clamp(data, 0, Item.itemsList.length - 1)]));
@@ -77,5 +77,6 @@ public final class ParticleDispatcher extends Dispatcher<String, ParticleLambda>
             return new ParticleFluidDrip(world, x, y, z, color, TextureRegistry.getTexture("minecraft:block/water_flowing"));
         });
         addDispatch("dripLava", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleFluidDrip(world, x, y, z, 0xFFFFFF, TextureRegistry.getTexture("minecraft:block/lava_flowing")).setFullBright());
+        addDispatch("ashmote", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleAsh(world, x, y, z, 0, 0, 0));
     }
 }
diff --git a/game/client/src/main/java/net/minecraft/client/entity/particle/ParticleSmoke.java b/game/client/src/main/java/net/minecraft/client/entity/particle/ParticleSmoke.java
index 55dfd2fdc..bf1b54e76 100644
--- a/game/client/src/main/java/net/minecraft/client/entity/particle/ParticleSmoke.java
+++ b/game/client/src/main/java/net/minecraft/client/entity/particle/ParticleSmoke.java
@@ -7,11 +7,7 @@ import net.minecraft.core.world.World;
 public class ParticleSmoke extends Particle {
     private final float oSize;
 
-    public ParticleSmoke(World world, double x, double y, double z, double xa, double ya, double za) {
-        this(world, x, y, z, xa, ya, za, 1.0F);
-    }
-
-    public ParticleSmoke(World world, double x, double y, double z, double xa, double ya, double za, float scale) {
+    public ParticleSmoke(World world, double x, double y, double z, double xa, double ya, double za, float scale, int data) {
         super(world, x, y, z, 0.0D, 0.0D, 0.0D);
         this.xd *= 0.1D;
         this.yd *= 0.1D;
@@ -19,7 +15,11 @@ public class ParticleSmoke extends Particle {
         this.xd += xa;
         this.yd += ya;
         this.zd += za;
-        this.rCol = this.gCol = this.bCol = (float) (Math.random() * 0.3D);
+        if (data == 1) {
+            this.rCol = this.gCol = this.bCol = (float) (1.0 - (Math.random() * 0.2));
+        } else {
+            this.rCol = this.gCol = this.bCol = (float) (Math.random() * 0.3D);
+        }
         this.size *= 0.75F;
         this.size *= scale;
         this.oSize = this.size;
diff --git a/game/client/src/main/java/net/minecraft/client/entity/player/PlayerLocal.java b/game/client/src/main/java/net/minecraft/client/entity/player/PlayerLocal.java
index 20561faeb..a85d80004 100644
--- a/game/client/src/main/java/net/minecraft/client/entity/player/PlayerLocal.java
+++ b/game/client/src/main/java/net/minecraft/client/entity/player/PlayerLocal.java
@@ -20,6 +20,7 @@ import net.minecraft.client.gui.container.ScreenTrommel;
 import net.minecraft.client.gui.ScreenWandMobPicker;
 import net.minecraft.client.gui.guidebook.ScreenGuidebook;
 import net.minecraft.client.input.PlayerInput;
+import net.minecraft.client.render.worldtype.WorldTypeFXDispatcher;
 import net.minecraft.core.achievement.Achievement;
 import net.minecraft.core.achievement.Achievements;
 import net.minecraft.core.achievement.stat.Stat;
@@ -38,6 +39,7 @@ import net.minecraft.core.data.gamerule.GameRules;
 import net.minecraft.core.entity.Entity;
 import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.entity.vehicle.EntityBoat;
+import net.minecraft.core.entity.vehicle.MobGhastVehicle;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.net.command.TextFormatting;
 import net.minecraft.client.net.command.ClientCommandSource;
@@ -229,6 +231,8 @@ public class PlayerLocal extends Player
         {
            mc.ppm.update();
         }
+
+        WorldTypeFXDispatcher.getInstance().getDispatch(world.getWorldType()).playerAnimationTick(world, this);
     }
     
     @Override
@@ -528,5 +532,8 @@ public class PlayerLocal extends Player
         if (vehicle instanceof EntityBoat) {
             ((EntityBoat) vehicle).controlBoat(-input.moveForward, -input.moveStrafe);
         }
+        if (vehicle instanceof MobGhastVehicle) {
+            ((MobGhastVehicle) vehicle).controlVehicle(-input.moveForward, -input.moveStrafe, input.jump, input.sneak);
+        }
     }
 }
diff --git a/game/client/src/main/java/net/minecraft/client/gui/ButtonElement.java b/game/client/src/main/java/net/minecraft/client/gui/ButtonElement.java
index 2776c44ff..5e8331a98 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/ButtonElement.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/ButtonElement.java
@@ -3,7 +3,10 @@ package net.minecraft.client.gui;
 import net.minecraft.client.render.Font;
 import net.minecraft.client.render.texture.stitcher.IconCoordinate;
 import net.minecraft.client.render.texture.stitcher.TextureRegistry;
+import net.minecraft.client.render.window.CursorShape;
+import net.minecraft.client.render.window.GameWindowGLFW;
 import org.jetbrains.annotations.Nullable;
+import org.lwjgl.glfw.GLFW;
 import org.lwjgl.opengl.GL11;
 
 import net.minecraft.client.Minecraft;
@@ -88,6 +91,10 @@ public class ButtonElement extends Gui implements GuiElement
         boolean mouseOver = mouseX >= xPosition && mouseY >= yPosition && mouseX < xPosition + width && mouseY < yPosition + height;
         int state = getButtonState(mouseOver);
 
+        if (mouseOver && visible && enabled) {
+            mc.currentScreen.setDesiredCursor(CursorShape.HAND);
+        }
+
         GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
         IconCoordinate tex;
         switch (state) {
@@ -107,12 +114,13 @@ public class ButtonElement extends Gui implements GuiElement
 
         int textColor;
         switch (state) {
-            case 0:
+            case STATE_DISABLED:
                 textColor = 0xA0A0A0;
                 break;
-            case 1:
+            case STATE_DEFAULT:
                 textColor = 0xE0E0E0;
                 break;
+            case STATE_HOVERED:
             default:
                 textColor = 0xFFFFA0;
                 break;
@@ -143,6 +151,16 @@ public class ButtonElement extends Gui implements GuiElement
     	return this;
     }
 
+    public ButtonElement mute() {
+        this.playSound = false;
+        return this;
+    }
+
+    public ButtonElement hide() {
+        this.visible = false;
+        return this;
+    }
+
 	@Override
 	public void setX(int x) {
 		this.xPosition = x;
diff --git a/game/client/src/main/java/net/minecraft/client/gui/ClickableLabelElement.java b/game/client/src/main/java/net/minecraft/client/gui/ClickableLabelElement.java
index d634da271..cd24b7bca 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/ClickableLabelElement.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/ClickableLabelElement.java
@@ -2,6 +2,7 @@ package net.minecraft.client.gui;
 
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.render.Font;
+import net.minecraft.client.render.window.CursorShape;
 import org.lwjgl.opengl.GL11;
 
 public class ClickableLabelElement extends ButtonElement
@@ -55,9 +56,14 @@ public class ClickableLabelElement extends ButtonElement
         {
             return;
         }
+
+        boolean hovered = mouseX >= xPosition && mouseY >= yPosition && mouseX < xPosition + width && mouseY < yPosition + height;
+        if (hovered && enabled) {
+            mc.currentScreen.setDesiredCursor(CursorShape.HAND);
+        }
+
         Font font = mc.font;
         GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
-        boolean hovered = mouseX >= xPosition && mouseY >= yPosition && mouseX < xPosition + width && mouseY < yPosition + height;
         width = font.getStringWidth(displayString) + 4;
         drawRect(xPosition, yPosition, xPosition + width, yPosition + height, enabled ? (hovered ? 0x7F8080C0 : bgColor) : 0x00000000);
         if (enabled && (shadow || hovered)) {
diff --git a/game/client/src/main/java/net/minecraft/client/gui/Screen.java b/game/client/src/main/java/net/minecraft/client/gui/Screen.java
index c5045b9f9..02e9df1ab 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/Screen.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/Screen.java
@@ -9,23 +9,27 @@ import net.minecraft.client.Minecraft;
 import net.minecraft.client.input.controller.ControllerInput;
 import net.minecraft.client.render.Font;
 import net.minecraft.client.render.tessellator.Tessellator;
+import net.minecraft.client.render.window.CursorShape;
+import net.minecraft.client.render.window.GameWindowGLFW;
 import net.minecraft.client.util.debug.DebugRender;
 import net.minecraft.client.input.InputType;
 
 import net.minecraft.core.sound.SoundCategory;
+import org.lwjgl.glfw.GLFW;
 import org.lwjgl.input.Keyboard;
 import org.lwjgl.input.Mouse;
 import org.lwjgl.opengl.GL11;
 
 public abstract class Screen extends Gui {
-    protected Minecraft mc;
+    public Minecraft mc;
     public int width;
     public int height;
     public List<ButtonElement> buttons;
     public boolean passEvents;
-    protected Font font;
+    public Font font;
     protected ButtonElement clickedButton;
     final Screen parentScreen;
+    private CursorShape desiredCursor = CursorShape.ARROW;
 
     public Screen(Screen parent) {
         buttons = new ArrayList<>();
@@ -42,6 +46,14 @@ public abstract class Screen extends Gui {
         return parentScreen;
     }
 
+    public final CursorShape getDesiredCursor() {
+        return this.desiredCursor;
+    }
+
+    public final void setDesiredCursor(CursorShape desiredCursor) {
+        this.desiredCursor = desiredCursor;
+    }
+
     public <E extends ButtonElement> E add(E button) {
         buttons.add(button);
         return button;
@@ -134,6 +146,10 @@ public abstract class Screen extends Gui {
                     mc.gameWindow.toggleFullscreen();
                     return;
                 }
+                if (!(this instanceof ScreenConsole) && eventKey == mc.gameSettings.keyShowConsole.getKeyCode()) {
+                    mc.displayScreen(new ScreenConsole(this));
+                    return;
+                }
                 keyPressed(eventChar, eventKey, mouseX, mouseY);
             }
         }
@@ -171,6 +187,7 @@ public abstract class Screen extends Gui {
     }
 
     public void drawControllerCursor() {
+        if (mc.controllerInput.hideCursor) return;
         glDisable(GL_DEPTH_TEST);
         glEnable(GL_BLEND);
         glColor3d(1.0, 1.0, 1.0);
@@ -185,15 +202,35 @@ public abstract class Screen extends Gui {
 
     public void playControllerButtonSound(ControllerInput controller) {
         if (controller.buttonA.pressedThisFrame() || controller.buttonX.pressedThisFrame() || controller.buttonY.pressedThisFrame()) {
-            mc.sndManager.playSound("random.ui_click", SoundCategory.GUI_SOUNDS, 1.0f, 1.0f);
+            mc.sndManager.playSound("ui.ui_click", SoundCategory.GUI_SOUNDS, 1.0f, 1.0f);
         }
         if (controller.buttonB.pressedThisFrame()) {
-            mc.sndManager.playSound("random.ui_back", SoundCategory.GUI_SOUNDS, 1.0f, 1.0f);
+            mc.sndManager.playSound("ui.ui_back", SoundCategory.GUI_SOUNDS, 1.0f, 1.0f);
         }
     }
 
     public void guiSpecificControllerInput(ControllerInput controller) {
-
+        if(controller.buttonB.pressedThisFrame()) {
+            this.keyPressed((char)0, Keyboard.KEY_BACK, -1, -1);
+        }
+        if(controller.buttonStart.pressedThisFrame()) {
+            this.keyPressed((char)0, Keyboard.KEY_ESCAPE, -1, -1);
+        }
+        if(controller.buttonA.pressedThisFrame()) {
+            this.mouseClicked((int)controller.cursorX, (int)controller.cursorY, 0);
+        }
+        if(controller.buttonA.releasedThisFrame()) {
+            this.mouseReleased((int)controller.cursorX, (int)controller.cursorY, 0);
+        }
+        if(controller.buttonY.pressedThisFrame()) {
+            this.mouseClicked((int)controller.cursorX, (int)controller.cursorY, 1);
+        }
+        if(controller.buttonY.releasedThisFrame()) {
+            this.mouseReleased((int)controller.cursorX, (int)controller.cursorY, 1);
+        }
+        if(controller.buttonX.pressedThisFrame()) {
+            this.mouseClicked((int)controller.cursorX, (int)controller.cursorY, 10);
+        }
     }
 
     public boolean isPauseScreen() {
diff --git a/game/client/src/main/java/net/minecraft/client/gui/ScreenAddServer.java b/game/client/src/main/java/net/minecraft/client/gui/ScreenAddServer.java
index 6be2b2ce9..ae8734f2e 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/ScreenAddServer.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/ScreenAddServer.java
@@ -139,7 +139,9 @@ public class ScreenAddServer extends Screen
         drawString(font, stringtranslate.translateKey("gui.add_server.label.server_name"), serverNameTextField.xPosition, serverNameTextField.yPosition - 14, 0xa0a0a0);
         drawString(font, stringtranslate.translateKey("gui.add_server.label.ip_address"), ipTextField.xPosition, ipTextField.yPosition - 14, 0xa0a0a0);
         ipTextField.drawTextBox();
+        ipTextField.updateCursor(mc, mx, my);
         serverNameTextField.drawTextBox();
+        serverNameTextField.updateCursor(mc, mx, my);
         super.render(mx, my, partialTick);
     }
 }
diff --git a/game/client/src/main/java/net/minecraft/client/gui/ScreenConnectToIp.java b/game/client/src/main/java/net/minecraft/client/gui/ScreenConnectToIp.java
index 57bfb92f1..307ef8273 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/ScreenConnectToIp.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/ScreenConnectToIp.java
@@ -87,6 +87,7 @@ public class ScreenConnectToIp extends Screen
         drawStringCentered(font, i18n.translateKey("gui.connect_to_ip.label.title"), width / 2, (height / 4 - 60) + 20, 0xffffff);
         drawString(font, i18n.translateKey("gui.connect_to_ip.label.enter_ip"), width / 2 - 140, (height / 4 - 60) + 60 + 36, 0xa0a0a0);
         textField.drawTextBox();
+        textField.updateCursor(mc, mx, my);
         super.render(mx, my, partialTick);
     }
 
diff --git a/game/client/src/main/java/net/minecraft/client/gui/ScreenConsole.java b/game/client/src/main/java/net/minecraft/client/gui/ScreenConsole.java
new file mode 100644
index 000000000..e9574cef9
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/gui/ScreenConsole.java
@@ -0,0 +1,533 @@
+package net.minecraft.client.gui;
+
+import com.codewaves.codehighlight.core.Highlighter;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.console.LogMessage;
+import net.minecraft.client.console.LogMessageReceiver;
+import net.minecraft.client.console.syntax.RendererFactory;
+import net.minecraft.client.gui.text.ITextField;
+import net.minecraft.client.gui.text.TextFieldEditor;
+import net.minecraft.client.render.Font;
+import net.minecraft.client.console.shell.RuntimeExecutor;
+import net.minecraft.client.render.window.CursorShape;
+import net.minecraft.client.render.window.GameWindowGLFW;
+import net.minecraft.core.net.command.TextFormatting;
+import net.minecraft.core.util.helper.MathHelper;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.lwjgl.glfw.GLFW;
+import org.lwjgl.input.Keyboard;
+import org.lwjgl.input.Mouse;
+import org.lwjgl.opengl.GL11;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ScreenConsole extends Screen {
+    private static final class LogRenderer extends Gui {
+        private static final int SCROLL_INDICATOR_WIDTH = 3;
+        private static final int SCROLL_INDICATOR_HEIGHT = 32;
+
+        private static final @NotNull List<@NotNull String> WORKED_LINES = new ArrayList<>();
+
+        private static boolean callbackInitialized = false;
+
+        private final @NotNull Map<LogMessage.@NotNull Level, @NotNull TextFormatting> preludes = new HashMap<>();
+
+        private @Nullable Font font = null;
+
+        private int width = 0;
+        private int scroll = 0;
+
+        public LogRenderer() {
+            this.preludes.put(LogMessage.Level.ERROR, TextFormatting.RED);
+            this.preludes.put(LogMessage.Level.WARN, TextFormatting.YELLOW);
+            this.preludes.put(LogMessage.Level.INFO, TextFormatting.WHITE);
+            this.preludes.put(LogMessage.Level.DEBUG, TextFormatting.LIGHT_BLUE);
+            this.preludes.put(LogMessage.Level.TRACE, TextFormatting.GRAY);
+        }
+
+        public void init(final @NotNull Minecraft mc, final int width) {
+            this.width = width;
+            this.font = new Font(mc, mc.gameSettings, mc.textureManager, true);
+
+            initWorkedLines();
+            final @NotNull List<@NotNull String> workedLine = new ArrayList<>();
+            if (!callbackInitialized) {
+                LogMessageReceiver.addCallback((message) -> {
+                    formatString(message, workedLine);
+                    WORKED_LINES.addAll(workedLine);
+                    trimWorkedLines();
+                });
+                callbackInitialized = true;
+            }
+        }
+
+        public void render(final int x, final int y, final int numLines) {
+            if (numLines >= WORKED_LINES.size() - this.scroll) {
+                this.scroll = 0;
+            }
+
+            final int lines = Math.min(heightLines, WORKED_LINES.size() - this.scroll);
+            for (int i = lines - 1; i >= 0; i--) {
+                final @NotNull String line = WORKED_LINES.get((WORKED_LINES.size() - 1) - (i + this.scroll));
+                if (this.font != null) {
+                    this.font.drawString(line, x, y + getHeight(numLines) - LINE_HEIGHT - LINE_HEIGHT * i, 0xFF_CF_CF_CF);
+                }
+            }
+            renderScrollIndicator(x, y, numLines);
+        }
+
+        public int getHeight(final int numLines) {
+            return LINE_HEIGHT * numLines;
+        }
+
+        public int getWidth() {
+            return this.width;
+        }
+
+        public void scroll(final int numLines) {
+            if (WORKED_LINES.size() <= heightLines) {
+                return;
+            }
+            this.scroll = MathHelper.clamp(this.scroll + numLines, 0, WORKED_LINES.size() - heightLines);
+        }
+
+        public void setScroll(final int scroll) {
+            this.scroll = MathHelper.clamp(scroll, 0, WORKED_LINES.size() - heightLines);
+        }
+
+        private void renderScrollIndicator(final int x, final int y, final int numLines) {
+            if (numLines * LINE_HEIGHT <= SCROLL_INDICATOR_HEIGHT) {
+                drawRect(x + this.width - SCROLL_INDICATOR_WIDTH, y, x + this.width, y + numLines * LINE_HEIGHT, 0xFF_FF_FF_FF);
+                return;
+            }
+
+            final float scrollPercentage = 1.0f - getScrollPercentage();
+            final int scrollRegionHeight = numLines * LINE_HEIGHT - SCROLL_INDICATOR_HEIGHT;
+            final int scrollY = (int) (scrollRegionHeight * scrollPercentage);
+            drawRect(x + this.width - SCROLL_INDICATOR_WIDTH, y + scrollY, x + this.width, y + scrollY + SCROLL_INDICATOR_HEIGHT, 0xFF_FF_FF_FF);
+        }
+
+        private float getScrollPercentage() {
+            if (WORKED_LINES.size() <= heightLines) {
+                return 0.0f;
+            }
+            return MathHelper.clamp((float) this.scroll / (WORKED_LINES.size() - heightLines), 0.0f, 1.0f);
+        }
+
+        private void initWorkedLines() {
+            WORKED_LINES.clear();
+            final @NotNull List<@NotNull String> formattedLine = new ArrayList<>();
+
+            for (final @NotNull LogMessage message : LogMessageReceiver.getQueue()) {
+                formatString(message, formattedLine);
+                WORKED_LINES.addAll(formattedLine);
+            }
+            trimWorkedLines();
+        }
+
+        private void trimWorkedLines() {
+            while (WORKED_LINES.size() > 1000) {
+                WORKED_LINES.remove(0);
+            }
+        }
+
+        private void formatString(final @NotNull LogMessage message, final @NotNull List<@NotNull String> out) {
+            if (this.font == null) {
+                return;
+            }
+
+            out.clear();
+            final @NotNull String @NotNull[] workedString = message.getMessage()
+                .replace("\r\n", "\n")
+                .replace("\t", "    ")
+                .split("\n");
+
+            @NotNull TextFormatting currentFormat = this.preludes.getOrDefault(message.getLevel(), TextFormatting.WHITE);
+
+            for (final @NotNull String line : workedString) {
+                @NotNull String trimmedLine = line;
+
+                @NotNull StringBuilder sb = new StringBuilder();
+                int lineWidth = 0;
+                @NotNull TextFormatting nextFormat = currentFormat;
+                for (int i = 0; i < trimmedLine.length(); i++) {
+                    final char c = trimmedLine.charAt(i);
+                    if (c == TextFormatting.SPECIAL_CHAR && (i + 1 < trimmedLine.length())) {
+                        final @Nullable TextFormatting newFormat = TextFormatting.fromChar(trimmedLine.charAt(i + 1));
+                        if (newFormat != null) {
+                            nextFormat = newFormat;
+                        }
+                        lineWidth -= this.font.getCharWidth(c) + this.font.getCharWidth(trimmedLine.charAt(i + 1));
+                    }
+                    final int cWidth = this.font.getCharWidth(c);
+                    if (lineWidth + cWidth >= this.width) {
+                        out.add(currentFormat.toString() + sb);
+                        sb = new StringBuilder();
+                        sb.append(nextFormat);
+                        lineWidth = 0;
+                    }
+                    sb.append(trimmedLine.charAt(i));
+                    lineWidth += cWidth;
+                }
+                out.add(currentFormat.toString() + sb);
+            }
+        }
+    }
+
+    private static final class InputRenderer extends Gui implements ITextField {
+        private static final @NotNull List<@NotNull String> HISTORY = new ArrayList<>();
+        private static final @Nullable RuntimeExecutor EXECUTOR = new RuntimeExecutor();
+
+        private @Nullable Font font = null;
+
+        private @NotNull String text = "";
+        private @NotNull String highlightedText = "";
+        private @NotNull String originalText = "";
+        private int cursorCounter = 0;
+        private final @NotNull TextFieldEditor editor;
+        private int historyIndex = 0;
+
+        private final Highlighter<String> highlighter = new Highlighter<>(new RendererFactory());
+
+        public InputRenderer() {
+            this.editor = new TextFieldEditor(this, true);
+        }
+
+        public void init(final @NotNull Minecraft mc) {
+            this.font = new Font(mc, mc.gameSettings, mc.textureManager, true);
+        }
+
+        public void tick() {
+            this.cursorCounter++;
+        }
+
+        public void render(final int x, final int y) {
+            if (this.font == null) return;
+
+            boolean addedStart = false;
+            final @NotNull String @NotNull[] splitHighlighted = this.highlightedText.split("\n", -1);
+            for (int i = 0; i < splitHighlighted.length; i++) {
+                drawStringNoShadow(this.font, (addedStart ? "  " : "> ") + splitHighlighted[i].replace("\n", ""), x, y + i * LINE_HEIGHT, 0xFF_E0_E0_E0);
+                addedStart = true;
+            }
+
+            final @NotNull String @NotNull [] split = this.text.split("\n", -1);
+            final int offsetWidth = this.font.getStringWidth("  ");
+            final int[] cursorPos = getCursor();
+            final int cursorX;
+            if (cursorPos[0] == split[cursorPos[1]].length()) {
+                cursorX = this.font.getStringWidth(split[cursorPos[1]]);
+            } else {
+                cursorX = this.font.getStringWidth(split[cursorPos[1]].substring(0, cursorPos[0]));
+            }
+            if ((cursorCounter / 6) % 2 == 0) {
+                drawStringNoShadow(this.font, "█", x + offsetWidth + cursorX, y + cursorPos[1] * LINE_HEIGHT, 0xFF_FF_FF_FF);
+                if (this.editor.getCursor() < this.text.length() && this.text.charAt(this.editor.getCursor()) != '\n') {
+                    drawStringNoShadow(this.font, "" + this.text.charAt(this.editor.getCursor()), x + offsetWidth + cursorX, y + cursorPos[1] * LINE_HEIGHT, 0xFF_00_00_00);
+                }
+            } else {
+                drawStringNoShadow(this.font, "_", x + offsetWidth + cursorX, y + cursorPos[1] * LINE_HEIGHT, 0xFF_FF_FF_FF);
+            }
+        }
+
+        public void click(final int mx, final int my) {
+            if (this.font == null) return;
+
+            final int oneChar = this.font.getStringWidth(" ");
+
+            final int cx = (mx - (oneChar * 2)) / oneChar;
+            final int cy = my / LINE_HEIGHT;
+
+            final @NotNull String @NotNull[] split = this.text.split("\n");
+            int cursor = 0;
+            for (int y = 0; y < cy; y++) {
+                cursor += split[y].length() + 1;
+            }
+            cursor += MathHelper.clamp(cx, 0, split[cy].length());
+
+            this.editor.setCursor(cursor);
+        }
+
+        public void handleInput(final int key, final char character) {
+            final boolean isMultiLine = this.text.contains("\n");
+            if (key == Keyboard.KEY_TAB) {
+                for (int i = 0; i < 4; i++) {
+                    this.editor.handleInput(0, ' ');
+                }
+            } else if (key == Keyboard.KEY_RETURN) {
+                if (this.text.isEmpty()) {
+                    return;
+                }
+                if (Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) || Keyboard.isKeyDown(Keyboard.KEY_RSHIFT)) {
+                    this.editor.handleInput(key, '\n');
+                } else {
+                    final @NotNull String line = this.text.trim();
+                    final @NotNull String @NotNull[] split = this.highlightedText.trim().split("\n");
+                    final @NotNull StringBuilder sb = new StringBuilder();
+                    for (final @NotNull String s : split) {
+                        sb
+                            .append(TextFormatting.LIGHT_GRAY)
+                            .append("> ")
+                            .append(TextFormatting.WHITE)
+                            .append(s)
+                            .append("\n");
+                    }
+                    LogMessageReceiver.append(sb.toString());
+
+                    if (EXECUTOR != null) {
+                        final @NotNull String message = EXECUTOR.runCode(line);
+                        LogMessageReceiver.append(message);
+                    }
+                    HISTORY.add(line);
+                    this.historyIndex = 0;
+
+                    this.editor.setText("");
+                }
+            } else if (key == Keyboard.KEY_HOME && isMultiLine) {
+                final int[] cursorPos = getCursor();
+                int realPos = this.editor.getCursor();
+                realPos -= cursorPos[0];
+                this.editor.setCursor(realPos);
+            } else if (key == Keyboard.KEY_END && isMultiLine) {
+                final @NotNull String @NotNull[] split = this.text.split("\n", -1);
+                final int[] cursorPos = getCursor();
+                int realPos = this.editor.getCursor();
+                realPos -= cursorPos[0];
+                realPos += split[cursorPos[1]].length();
+                this.editor.setCursor(realPos);
+            } else if (key == Keyboard.KEY_UP) {
+                if (isMultiLine && !(Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) || Keyboard.isKeyDown(Keyboard.KEY_RSHIFT))) {
+                    final @NotNull String @NotNull[] split = this.text.split("\n", -1);
+                    int[] cursorPos = getCursor();
+                    if (cursorPos[1] == 0) return;
+                    int realPos = this.editor.getCursor();
+                    realPos -= cursorPos[0];
+                    cursorPos[1]--;
+                    realPos -= split[cursorPos[1]].length() + 1;
+                    cursorPos[0] = MathHelper.clamp(cursorPos[0], 0, split[cursorPos[1]].length());
+                    realPos += cursorPos[0];
+                    this.editor.setCursor(realPos);
+                } else if (!HISTORY.isEmpty()) {
+                    if (this.historyIndex == 0) {
+                        this.originalText = getText();
+                    }
+                    if (this.historyIndex < HISTORY.size()) {
+                        this.historyIndex++;
+                        setText(HISTORY.get(HISTORY.size() - this.historyIndex));
+                    }
+                    this.editor.setCursor(10000);
+                }
+            } else if (key == Keyboard.KEY_DOWN) {
+                if (isMultiLine && !(Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) || Keyboard.isKeyDown(Keyboard.KEY_RSHIFT))) {
+                    final @NotNull String @NotNull[] split = this.text.split("\n", -1);
+                    int[] cursorPos = getCursor();
+                    if (cursorPos[1] == split.length - 1) return;
+                    int realPos = this.editor.getCursor();
+                    realPos -= cursorPos[0];
+                    realPos += split[cursorPos[1]].length() + 1;
+                    cursorPos[1]++;
+                    cursorPos[0] = MathHelper.clamp(cursorPos[0], 0, split[cursorPos[1]].length());
+                    realPos += cursorPos[0];
+                    this.editor.setCursor(realPos);
+                } else if (this.historyIndex > 0) {
+                    if (this.historyIndex - 1 == 0) {
+                        this.historyIndex--;
+                        setText(this.originalText);
+                    } else {
+                        this.historyIndex--;
+                        setText(HISTORY.get(HISTORY.size() - this.historyIndex));
+                    }
+                    this.editor.setCursor(10000);
+                }
+            } else if (character != '`') {
+                this.editor.handleInput(key, character);
+            }
+        }
+
+        public int getHeightLines() {
+            int c = 1;
+            for (int i = 0; i < this.text.length(); i++) {
+                if (this.text.charAt(i) == '\n') {
+                    c++;
+                }
+            }
+            return c;
+        }
+
+        @Override
+        public void setText(final @NotNull String text) {
+            this.text = text;
+            this.highlightedText = this.highlighter.highlight("java", text).getResult();
+        }
+
+        @Override
+        public @NotNull String getText() {
+            return this.text;
+        }
+
+        @Override
+        public int maxLength() {
+            return 1024;
+        }
+
+        private int @NotNull[] getCursor() {
+            if (this.font == null) {
+                return new int[2];
+            }
+
+            final @NotNull String @NotNull[] split = this.text.split("\n", -1);
+
+            int cursor = this.editor.getCursor();
+            int cursorLine = 0;
+            for (int i = 0; i < split.length; i++) {
+                if (cursor - (split[i].length() + 1) < 0) {
+                    cursorLine = i;
+                    break;
+                }
+                cursor -= split[i].length() + 1;
+            }
+            return new int[] { cursor, cursorLine };
+        }
+    }
+
+    private static final int LINES_TO_SCROLL = 6;
+    private static final int PADDING = 4;
+    private static final int LINE_HEIGHT = 12;
+
+    private static int heightLines = 12;
+
+    private boolean dragging = false;
+
+    private final @NotNull LogRenderer logRenderer;
+    private final @NotNull InputRenderer inputRenderer;
+
+    public ScreenConsole(final @Nullable Screen parent) {
+        super(parent);
+
+        this.logRenderer = new LogRenderer();
+        this.inputRenderer = new InputRenderer();
+    }
+
+    private int getMaxY() {
+        return PADDING + (heightLines - this.inputRenderer.getHeightLines()) * LINE_HEIGHT + PADDING + PADDING + this.inputRenderer.getHeightLines() * LINE_HEIGHT + PADDING;
+    }
+
+    @Override
+    public void tick() {
+        super.tick();
+
+        this.inputRenderer.tick();
+    }
+
+    @Override
+    public void init() {
+        if (this.parentScreen != null) {
+            if (this.parentScreen.width != width || this.parentScreen.height != height)
+            {
+                this.parentScreen.width = width;
+                this.parentScreen.height = height;
+                this.parentScreen.buttons.clear();
+                this.parentScreen.init();
+            }
+        }
+
+        super.init();
+        Keyboard.enableRepeatEvents(true);
+
+        this.logRenderer.init(this.mc, this.width - PADDING * 2);
+        this.inputRenderer.init(this.mc);
+    }
+
+    @Override
+    public void removed() {
+        super.removed();
+        Keyboard.enableRepeatEvents(false);
+    }
+
+    @Override
+    public void render(int mx, int my, float partialTick) {
+        super.render(mx, my, partialTick);
+
+        if (this.parentScreen != null) {
+            this.parentScreen.render(-1, -1, partialTick);
+        }
+
+        final int dWheel = Mouse.getDWheel();
+        if (dWheel != 0) {
+            this.logRenderer.scroll(dWheel * LINES_TO_SCROLL);
+        }
+
+        if (this.dragging) {
+            heightLines = Math.max(1, ((my - (PADDING + PADDING + PADDING + PADDING - (LINE_HEIGHT / 2))) / LINE_HEIGHT));
+        }
+
+        if (getMaxY() >= this.height - 1) {
+            heightLines = Math.max(1, ((this.height - (PADDING + PADDING + PADDING + PADDING)) / LINE_HEIGHT) - 1);
+        }
+
+        GL11.glDisable(GL11.GL_TEXTURE_2D);
+        drawRect(0, 0, this.width, getMaxY(), 0xDF_00_00_00);
+
+        this.logRenderer.render(PADDING, PADDING, heightLines - inputRenderer.getHeightLines());
+
+        final int inputRendererHeight = this.inputRenderer.getHeightLines() * LINE_HEIGHT;
+        this.inputRenderer.render(PADDING, getMaxY() - PADDING - inputRendererHeight);
+
+        drawRect(PADDING, getMaxY() - (PADDING + inputRendererHeight + PADDING) - 2, this.width - PADDING, getMaxY() - (PADDING + inputRendererHeight + PADDING) - 1, 0x7F_9F_9F_9F);
+
+        if (mouseOverGrabbyRegion(mx, my) || this.dragging) {
+            setDesiredCursor(CursorShape.VERTICAL_RESIZE);
+        } else if (mx > PADDING && my >= getMaxY() - (PADDING + inputRendererHeight) && my < getMaxY() - PADDING) {
+            setDesiredCursor(CursorShape.I_BEAM);
+        } else {
+            setDesiredCursor(CursorShape.ARROW);
+        }
+
+        drawRect(0, getMaxY(), this.width, getMaxY() + 1, (mouseOverGrabbyRegion(mx, my) || this.dragging) ? 0xFF_DF_DF_DF : 0xFF_9F_9F_9F);
+    }
+
+    @Override
+    public void keyPressed(char eventCharacter, int eventKey, int mx, int my) {
+        if (eventKey == this.mc.gameSettings.keyShowConsole.getKeyCode() || eventKey == Keyboard.KEY_ESCAPE) {
+            this.mc.displayScreen(this.parentScreen);
+        } else {
+            if (eventKey == Keyboard.KEY_RETURN) {
+                this.logRenderer.setScroll(0);
+            }
+            this.inputRenderer.handleInput(eventKey, eventCharacter);
+        }
+    }
+
+    @Override
+    public void mouseClicked(int mx, int my, int buttonNum) {
+        super.mouseClicked(mx, my, buttonNum);
+
+        final int inputRendererHeight = this.inputRenderer.getHeightLines() * LINE_HEIGHT;
+
+        if (buttonNum == 0 && mouseOverGrabbyRegion(mx, my)) {
+            this.dragging = true;
+        } else if (buttonNum == 0 && mx >= PADDING && my >= getMaxY() - PADDING - inputRendererHeight && my <= getMaxY() - PADDING) {
+            this.inputRenderer.click(mx - PADDING, my - (getMaxY() - PADDING - inputRendererHeight));
+        } else if (my > getMaxY()) {
+            this.mc.displayScreen(this.parentScreen);
+        }
+    }
+
+    @Override
+    public void mouseReleased(int mx, int my, int buttonNum) {
+        super.mouseReleased(mx, my, buttonNum);
+
+        if (buttonNum == 0) {
+            this.dragging = false;
+        }
+    }
+
+    private boolean mouseOverGrabbyRegion(final int mx, final int my) {
+        final int yMax = getMaxY();
+        return mx >= 0 && mx < this.width && my >= yMax - 1 && my <= yMax + 2;
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/gui/ScreenControllerSetup.java b/game/client/src/main/java/net/minecraft/client/gui/ScreenControllerSetup.java
index 0f9288eff..49512a6ab 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/ScreenControllerSetup.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/ScreenControllerSetup.java
@@ -5,9 +5,9 @@ import net.minecraft.client.input.InputType;
 import net.minecraft.client.input.controller.Button;
 import net.minecraft.client.input.controller.ControllerInput;
 import net.minecraft.client.input.controller.ControllerType;
-import net.minecraft.client.input.controller.Joystick;
 import net.minecraft.core.lang.I18n;
-import org.lwjgl.opengl.GL11;
+
+import java.util.Arrays;
 
 import java.util.Arrays;
 
diff --git a/game/client/src/main/java/net/minecraft/client/gui/ScreenCreateWorld.java b/game/client/src/main/java/net/minecraft/client/gui/ScreenCreateWorld.java
index bfcafe858..8cab41988 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/ScreenCreateWorld.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/ScreenCreateWorld.java
@@ -216,7 +216,9 @@ public class ScreenCreateWorld extends Screen {
         drawString(font, stringtranslate.translateKey("gui.create_world.label.world_name"), width / 2 - 100, 47, 0xa0a0a0);
         drawString(font, stringtranslate.translateKey("gui.create_world.label.seed"), width / 2 - 100, 88, 0xa0a0a0);
         textboxWorldName.drawTextBox();
+        textboxWorldName.updateCursor(mc, mx, my);
         textboxSeed.drawTextBox();
+        textboxSeed.updateCursor(mc, mx, my);
         super.render(mx, my, partialTick);
     }
 
diff --git a/game/client/src/main/java/net/minecraft/client/gui/ScreenHudDesigner.java b/game/client/src/main/java/net/minecraft/client/gui/ScreenHudDesigner.java
index 0c358845b..511d5a750 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/ScreenHudDesigner.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/ScreenHudDesigner.java
@@ -111,6 +111,7 @@ public class ScreenHudDesigner
         boolean didSnap = false;
         for (HudComponent component : HudComponents.INSTANCE.getComponents()) {
             if (component == heldComponent) continue;
+            if (!component.isEnabled(mc)) continue;
             Layout otherLayout = component.getLayout();
             boolean isNested = false;
             Layout nestedLayout = otherLayout;
@@ -292,6 +293,7 @@ public class ScreenHudDesigner
         }
         for (HudComponent component : HudComponents.INSTANCE.getComponents()) {
             if (component == heldComponent) continue;
+            if (!component.isEnabled(mc)) continue;
             Layout layout = component.getLayout();
             component.renderPreview(mc, this, layout, width, height - BOTTOM_SPACE);
             if (heldComponent == null) {
@@ -377,6 +379,7 @@ public class ScreenHudDesigner
     private void updateComponentsUnderMouse(int mouseX, int mouseY) {
         componentsUnderMouse.clear();
         for (HudComponent component : HudComponents.INSTANCE.getComponents()) {
+            if (!component.isEnabled(mc)) continue;
             Layout layout = component.getLayout();
             int cx = layout.getComponentX(mc, component, width);
             int cy = layout.getComponentY(mc, component, height - BOTTOM_SPACE);
diff --git a/game/client/src/main/java/net/minecraft/client/gui/ScreenLabelEditor.java b/game/client/src/main/java/net/minecraft/client/gui/ScreenLabelEditor.java
index 5f85f19fa..1a72d3bc1 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/ScreenLabelEditor.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/ScreenLabelEditor.java
@@ -1,28 +1,27 @@
 package net.minecraft.client.gui;
 
+import net.minecraft.client.gui.keyboard.ScreenKeyboard;
+import net.minecraft.client.gui.text.ITextField;
+import net.minecraft.client.gui.text.TextFieldEditor;
+import net.minecraft.client.input.InputType;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.lang.I18n;
 import net.minecraft.core.net.command.TextFormatting;
 import net.minecraft.core.net.packet.PacketSetItemName;
-import net.minecraft.core.util.helper.ChatAllowedCharacters;
 import org.lwjgl.input.Keyboard;
 import org.lwjgl.opengl.GL11;
 
-public class ScreenLabelEditor extends Screen
+public class ScreenLabelEditor extends Screen implements ITextField
 {
-    private static final String allowedCharacters;
-
-    static
-    {
-        allowedCharacters = ChatAllowedCharacters.ALLOWED_CHARACTERS;
-    }
     protected String screenTitle;
     protected ItemStack item;
     protected String text;
+    protected TextFieldEditor editor;
     protected byte color;
     protected int xSize;
     protected int ySize;
     protected int slot;
+    protected int cursorCounter;
 
     public ScreenLabelEditor(final ItemStack item, int slot)
     {
@@ -33,12 +32,21 @@ public class ScreenLabelEditor extends Screen
         } else {
         	this.text = "";
         }
+        this.editor = new TextFieldEditor(this);
         this.color = item.getDisplayColor();
         xSize = 176;
         ySize = 166;
         this.slot = slot;
     }
 
+    @Override
+    public void tick() {
+        cursorCounter++;
+        if ((mc.gameSettings.forceOnscreenKeyboard.value || mc.inputType == InputType.CONTROLLER) && !(mc.currentScreen instanceof ScreenKeyboard)) {
+            buttonClicked(buttons.get(0)); // Close out of gui when exiting keyboard screen
+        }
+    }
+
     @Override
     protected void buttonClicked(final ButtonElement button)
     {
@@ -72,6 +80,16 @@ public class ScreenLabelEditor extends Screen
 
         drawStringCentered(font, TextFormatting.get(color) + "> " + text + " <", width / 2, 100, 0xffffff);
 
+        boolean drawCursor = !text.isEmpty() && (cursorCounter / 6) % 2 == 0;
+        if (drawCursor)
+        {
+            int width = font.getStringWidth(editor.getText());
+            if (editor.getCursor() < editor.getText().length())
+            {
+                width = font.getStringWidth(editor.getText().substring(0, editor.getCursor()));
+            }
+            drawString(font, "_", this.width / 2 - font.getStringWidth(editor.getText())/2 + width, 101, 0xffffff);
+        }
         super.render(mx, my, f);
     }
 
@@ -81,23 +99,24 @@ public class ScreenLabelEditor extends Screen
         buttons.clear();
         Keyboard.enableRepeatEvents(true);
         buttons.add(new ButtonElement(0, width / 2 - 100, height / 4 + 120, I18n.getInstance().translateKey("gui.edit_label.button.done")));
+
+        if (mc.gameSettings.forceOnscreenKeyboard.value || mc.inputType == InputType.CONTROLLER) {
+            mc.displayScreen(new ScreenKeyboard(this, editor,
+                (s) -> {},
+                (c, k, s) -> {},
+                s -> {}));
+        }
     }
 
     @Override
 	public void keyPressed(final char eventCharacter, final int eventKey, int mx, int my)
     {
-        if (eventKey == Keyboard.KEY_BACK && text.length() > 0)
-        {
-            text = text.substring(0, text.length() - 1);
-        }
-        if (allowedCharacters.indexOf(eventCharacter) >= 0 && text.length() < 15)
-        {
-            text += eventCharacter;
-        }
         if (eventKey == Keyboard.KEY_RETURN || eventKey == Keyboard.KEY_ESCAPE)
         {
             buttonClicked(buttons.get(0));
+            return;
         }
+        editor.handleInput(eventKey, eventCharacter);
     }
 
     @Override
@@ -106,4 +125,18 @@ public class ScreenLabelEditor extends Screen
         Keyboard.enableRepeatEvents(false);
     }
 
+    @Override
+    public void setText(String text) {
+        this.text = text;
+    }
+
+    @Override
+    public String getText() {
+        return text;
+    }
+
+    @Override
+    public int maxLength() {
+        return 16;
+    }
 }
\ No newline at end of file
diff --git a/game/client/src/main/java/net/minecraft/client/gui/ScreenPhotoMode.java b/game/client/src/main/java/net/minecraft/client/gui/ScreenPhotoMode.java
index 2dbd276e9..0b7619178 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/ScreenPhotoMode.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/ScreenPhotoMode.java
@@ -512,6 +512,6 @@ public class ScreenPhotoMode extends Screen
 
     @Override
     public void guiSpecificControllerInput(ControllerInput controller) {
-
+        super.guiSpecificControllerInput(controller);
     }
 }
diff --git a/game/client/src/main/java/net/minecraft/client/gui/ScreenSignEditor.java b/game/client/src/main/java/net/minecraft/client/gui/ScreenSignEditor.java
index c30ab97cf..5f37c90d0 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/ScreenSignEditor.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/ScreenSignEditor.java
@@ -1,5 +1,9 @@
 package net.minecraft.client.gui;
 
+import net.minecraft.client.gui.keyboard.ScreenKeyboard;
+import net.minecraft.client.gui.modelviewer.elements.ListenerButtonElement;
+import net.minecraft.client.input.InputType;
+import net.minecraft.client.input.controller.ControllerInput;
 import net.minecraft.core.block.Block;
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.block.entity.TileEntitySign;
@@ -14,11 +18,12 @@ import org.lwjgl.opengl.GL11;
 
 public class ScreenSignEditor extends Screen
 {
-    public ScreenSignEditor(TileEntitySign tileentitysign)
+    private boolean inKeyboard = false;
+    public ScreenSignEditor(TileEntitySign sign)
     {
         screenTitle = I18n.getInstance().translateKey("gui.edit_sign.label.title");
         editLine = 0;
-        entitySign = tileentitysign;
+        entitySign = sign;
     }
 
     @Override
@@ -37,6 +42,17 @@ public class ScreenSignEditor extends Screen
         
         buttons.add(new ButtonElement(1, width / 2 - 20, 120 + 50 + yOffset, 20, 20, "<"));
         buttons.add(new ButtonElement(2, width / 2, 120 + 50 + yOffset, 20, 20, ">"));
+
+        add(new ListenerButtonElement(30, width/2 - 50, 12 * 0 + 67 + ((entitySign.getBlockId() == Blocks.SIGN_WALL_PLANKS_OAK.id()) ? 31 : 0), 100, 12, "")
+            .setActionListener(() -> {editLine = 0; inKeyboard = true;})).mute().hide();
+        add(new ListenerButtonElement(30, width/2 - 50, 12 * 1 + 67 + ((entitySign.getBlockId() == Blocks.SIGN_WALL_PLANKS_OAK.id()) ? 31 : 0), 100, 12, "")
+            .setActionListener(() -> {editLine = 1;inKeyboard = true;})).mute().hide();
+        add(new ListenerButtonElement(30, width/2 - 50, 12 * 2 + 67 + ((entitySign.getBlockId() == Blocks.SIGN_WALL_PLANKS_OAK.id()) ? 31 : 0), 100, 12, "")
+            .setActionListener(() -> {editLine = 2;inKeyboard = true;})).mute().hide();
+        add(new ListenerButtonElement(30, width/2 - 50, 12 * 3 + 67 + ((entitySign.getBlockId() == Blocks.SIGN_WALL_PLANKS_OAK.id()) ? 31 : 0), 100, 12, "")
+            .setActionListener(() -> {editLine = 3;inKeyboard = true;})).mute().hide();
+
+        inKeyboard = (mc.gameSettings.forceOnscreenKeyboard.value || mc.inputType == InputType.CONTROLLER) && !(mc.currentScreen instanceof ScreenKeyboard);
     }
 
     @Override
@@ -95,15 +111,15 @@ public class ScreenSignEditor extends Screen
             entitySign.setChanged();
     		mc.displayScreen(null);
     	}
-        if(eventKey == 200)
+        if(eventKey == Keyboard.KEY_UP)
         {
             editLine = editLine - 1 & 3;
         }
-        if(eventKey == 208 || eventKey == 28 || eventKey == 156)
+        if(eventKey == Keyboard.KEY_DOWN || eventKey == Keyboard.KEY_PRINT_SCREEN || eventKey == Keyboard.KEY_NUMPADENTER)
         {
             editLine = editLine + 1 & 3;
         }
-        if(eventKey == 14 && entitySign.signText[editLine].length() > 0)
+        if(eventKey == Keyboard.KEY_BACK && !entitySign.signText[editLine].isEmpty())
         {
             entitySign.signText[editLine] = entitySign.signText[editLine].substring(0, entitySign.signText[editLine].length() - 1);
         }
@@ -116,6 +132,24 @@ public class ScreenSignEditor extends Screen
     @Override
 	public void render(int mx, int my, float partialTick)
     {
+        if (inKeyboard && (mc.gameSettings.forceOnscreenKeyboard.value || mc.inputType == InputType.CONTROLLER) && !(mc.currentScreen instanceof ScreenKeyboard)) {
+            if (editLine < entitySign.signText.length) {
+                ScreenKeyboard keyboard = new ScreenKeyboard(this, null,
+                    (s) -> {
+                        entitySign.signText[editLine] = s;
+                        if (editLine < entitySign.signText.length - 1) {
+                            editLine++;
+                        } else {
+                            inKeyboard = false;
+                        }
+                    },
+                    (c, k, s) -> entitySign.signText[editLine] = s,
+                    s -> entitySign.signText[editLine] = s);
+                keyboard.editor.setText(entitySign.signText[editLine]);
+                keyboard.editor.setCursor(Integer.MAX_VALUE);
+                mc.displayScreen(keyboard);
+            }
+        }
         renderBackground();
         drawStringCentered(font, screenTitle, width / 2, 40, 0xffffff);
         GL11.glPushMatrix();
@@ -161,6 +195,17 @@ public class ScreenSignEditor extends Screen
         super.render(mx, my, partialTick);
     }
 
+    @Override
+    public void guiSpecificControllerInput(ControllerInput controller) {
+        super.guiSpecificControllerInput(controller);
+        if (controller.digitalPad.up.pressedThisFrame()) {
+            editLine = editLine - 1 & 3;
+        }
+        if (controller.digitalPad.down.pressedThisFrame()) {
+            editLine = editLine + 1 & 3;
+        }
+    }
+
     protected String screenTitle;
     private TileEntitySign entitySign;
     private int updateCounter;
diff --git a/game/client/src/main/java/net/minecraft/client/gui/TextFieldElement.java b/game/client/src/main/java/net/minecraft/client/gui/TextFieldElement.java
index e70d408e5..c5f4a9852 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/TextFieldElement.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/TextFieldElement.java
@@ -1,7 +1,11 @@
 package net.minecraft.client.gui;
 
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.keyboard.ScreenKeyboard;
 import net.minecraft.client.gui.text.TextFieldEditor;
+import net.minecraft.client.input.InputType;
 import net.minecraft.client.render.Font;
+import net.minecraft.client.render.window.CursorShape;
 import org.lwjgl.input.Keyboard;
 
 import net.minecraft.client.gui.text.ITextField;
@@ -86,13 +90,21 @@ public class TextFieldElement extends Gui implements ITextField
 
     public void mouseClicked(int x, int y, int mouseButton)
     {
-        boolean flag = isEnabled && x >= xPosition && x < xPosition + width && y >= yPosition && y < yPosition + height;
-        setFocused(flag);
-        if(flag && mouseButton == 1) {
-        	setText("");
+        boolean hovered = isHovered(x, y);
+        if (((Minecraft.getMinecraft().inputType == InputType.CONTROLLER || Minecraft.getMinecraft().gameSettings.forceOnscreenKeyboard.value) && hovered && mouseButton == 0)) {
+            parent.mc.displayScreen(new ScreenKeyboard(parent, editor, s -> {}, (c, k, workingString) -> {}, s -> {}));
+        } else {
+            setFocused(hovered);
+            if(hovered && mouseButton == 1) {
+                setText("");
+            }
         }
     }
 
+    public boolean isHovered(int x, int y) {
+        return isEnabled && x >= xPosition && x < xPosition + width && y >= yPosition && y < yPosition + height;
+    }
+
     public void setFocused(boolean flag)
     {
         if(flag && !isFocused)
@@ -103,6 +115,13 @@ public class TextFieldElement extends Gui implements ITextField
         isFocused = flag;
     }
 
+    public void updateCursor(Minecraft mc, int mx, int my) {
+        boolean hovered = isEnabled && mx >= xPosition && mx < xPosition + width && my >= yPosition && my < yPosition + height;
+        if (hovered) {
+            mc.currentScreen.setDesiredCursor(CursorShape.I_BEAM);
+        }
+    }
+
     public void drawTextBox()
     {
         if (drawBackground)
@@ -140,7 +159,7 @@ public class TextFieldElement extends Gui implements ITextField
                     // width of the text box - the width of the last character
                     width = (this.width-4) - font.getStringWidth(text.substring(text.length()-2,text.length()-1));
                 }
-                drawString(font, "_", xPosition + 4 + width, yPosition + (height - 8) / 2, 0xE0E0E0);
+                drawString(font, "_", xPosition + 4 + width, yPosition + (height - 8) / 2 + 1, 0xE0E0E0);
             }
         }
         else
diff --git a/game/client/src/main/java/net/minecraft/client/gui/chat/GuiElementChatSuggestions.java b/game/client/src/main/java/net/minecraft/client/gui/chat/GuiElementChatSuggestions.java
index 99c688928..f6082c1c2 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/chat/GuiElementChatSuggestions.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/chat/GuiElementChatSuggestions.java
@@ -13,6 +13,7 @@ import com.mojang.brigadier.suggestion.Suggestion;
 import com.mojang.brigadier.suggestion.Suggestions;
 import com.mojang.brigadier.tree.CommandNode;
 import com.mojang.brigadier.tree.LiteralCommandNode;
+import com.mojang.logging.LogUtils;
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.gui.Gui;
 import net.minecraft.client.gui.TooltipElement;
@@ -28,6 +29,7 @@ import net.minecraft.core.util.helper.MathHelper;
 import org.jetbrains.annotations.Nullable;
 import org.lwjgl.input.Keyboard;
 import org.lwjgl.input.Mouse;
+import org.slf4j.Logger;
 
 import java.awt.*;
 import java.util.ArrayList;
@@ -38,6 +40,7 @@ import java.util.concurrent.CompletableFuture;
 
 @SuppressWarnings({"OptionalGetWithoutIsPresent", "unused"})
 public class GuiElementChatSuggestions extends Gui {
+    private static final Logger LOGGER = LogUtils.getLogger();
     private final TextFieldEditor editor;
     private final ITextField textField;
     private final Minecraft mc;
@@ -396,7 +399,12 @@ public class GuiElementChatSuggestions extends Gui {
         } else if (serverSuggestions.has(CommandManagerPacketKeys.SUGGESTION_CONTEXT_START_POS)) {
             return serverSuggestions.get(CommandManagerPacketKeys.SUGGESTION_CONTEXT_START_POS).getAsInt();
         }
-        return this.parseResults != null ? this.parseResults.getContext().findSuggestionContext(this.tablessCursor).startPos : 0;
+        try {
+            return this.parseResults != null ? this.parseResults.getContext().findSuggestionContext(this.tablessCursor).startPos : 0;
+        } catch (IllegalStateException e) {
+            LOGGER.error("", e);
+            return 0;
+        }
     }
 
     public void setAnchor(ComponentAnchor anchor) {
diff --git a/game/client/src/main/java/net/minecraft/client/gui/chat/ScreenChat.java b/game/client/src/main/java/net/minecraft/client/gui/chat/ScreenChat.java
index 8fe6eb337..044ca43de 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/chat/ScreenChat.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/chat/ScreenChat.java
@@ -2,7 +2,9 @@ package net.minecraft.client.gui.chat;
 
 import net.minecraft.client.gui.Screen;
 import net.minecraft.client.gui.hud.component.ComponentAnchor;
+import net.minecraft.client.gui.keyboard.ScreenKeyboard;
 import net.minecraft.client.gui.text.TextFieldEditor;
+import net.minecraft.client.input.InputType;
 import net.minecraft.core.util.helper.MathHelper;
 import net.minecraft.core.net.ChatEmotes;
 import org.lwjgl.input.Keyboard;
@@ -52,6 +54,19 @@ public class ScreenChat extends Screen implements ITextField {
 			this.suggestionsElement.updateSuggestions();
 		}
 
+		if (mc.gameSettings.forceOnscreenKeyboard.value || mc.inputType == InputType.CONTROLLER) {
+			suggestionsElement.hidden = true;
+			mc.displayScreen(new ScreenKeyboard(this, editor,
+				(workingString) -> sendMessage(),
+				(c, k, workingString) -> {
+					suggestionsElement.hidden = !getText().startsWith("/");
+					suggestionsElement.keyTyped(c, k);
+				},
+                s -> {
+					suggestionsElement.hidden = !getText().startsWith("/");
+					suggestionsElement.updateSuggestions();
+				}));
+		}
 	}
 
 	@Override
@@ -62,6 +77,10 @@ public class ScreenChat extends Screen implements ITextField {
 
 	@Override
 	public void tick() {
+		if ((mc.gameSettings.forceOnscreenKeyboard.value || mc.inputType == InputType.CONTROLLER) && !(mc.currentScreen instanceof ScreenKeyboard)) {
+			mc.displayScreen(null); // Close out of gui when exiting keyboard screen
+			return;
+		}
 		updateCounter++;
 
 		int dWheel = Mouse.getDWheel();
@@ -142,11 +161,7 @@ public class ScreenChat extends Screen implements ITextField {
 			handled = true;
 		}
 		if (eventKey == Keyboard.KEY_RETURN) {
-			String s = message.trim();
-			if (s.length() > 0) {
-				String s1 = message.trim();
-				mc.thePlayer.sendChatMessage(s1);
-			}
+			sendMessage();
 			mc.displayScreen(null);
 			handled = true;
 		}
@@ -158,6 +173,14 @@ public class ScreenChat extends Screen implements ITextField {
 		}
 	}
 
+	public void sendMessage() {
+		String s = message.trim();
+		if (s.length() > 0) {
+			String s1 = message.trim();
+			mc.thePlayer.sendChatMessage(s1);
+		}
+	}
+
 	@Override
 	public void render(int mx, int my, float partialTick) {
 		// Draw emote picker button
@@ -174,9 +197,9 @@ public class ScreenChat extends Screen implements ITextField {
 		drawRect(16, height - 14, width - 2, height - 2, 0x80000000);
 
 		font.drawStringWithShadow(this.suggestionsElement.getSuggestionPreview(), 16 + suggestionsElement.getDefaultParameterPosition(), mc.resolution.getScaledHeightScreenCoords() - 12, 0xFFFFFF);
+		drawString(font, this.suggestionsElement.colorCodeText(message, true), 18, height - 12, 0xE0E0E0);
 		boolean drawCursor = (updateCounter / 6) % 2 == 0;
 		int cursor = editor.getCursor();
-		drawString(font, this.suggestionsElement.colorCodeText(message, true), 18, height - 12, 0xE0E0E0);
 		if (drawCursor)
 		{
 			int width = font.getStringWidth(message);
diff --git a/game/client/src/main/java/net/minecraft/client/gui/container/ScreenCrafting.java b/game/client/src/main/java/net/minecraft/client/gui/container/ScreenCrafting.java
index 7d79d3594..6b8c91681 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/container/ScreenCrafting.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/container/ScreenCrafting.java
@@ -39,6 +39,7 @@ public class ScreenCrafting extends ScreenContainerAbstract
     }
     @Override
     public void guiSpecificControllerInput(ControllerInput controller){
+        super.guiSpecificControllerInput(controller);
         controller.inventoryHandler.handleCrafting(this);
     }
 }
diff --git a/game/client/src/main/java/net/minecraft/client/gui/container/ScreenFurnace.java b/game/client/src/main/java/net/minecraft/client/gui/container/ScreenFurnace.java
index 3963a4d40..450a74d81 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/container/ScreenFurnace.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/container/ScreenFurnace.java
@@ -59,6 +59,7 @@ public class ScreenFurnace extends ScreenContainerAbstract
     }
     @Override
     public void guiSpecificControllerInput(ControllerInput controller){
+        super.guiSpecificControllerInput(controller);
         controller.inventoryHandler.handleFurnace(this);
     }
 
diff --git a/game/client/src/main/java/net/minecraft/client/gui/container/ScreenInventory.java b/game/client/src/main/java/net/minecraft/client/gui/container/ScreenInventory.java
index 02bdc5fac..72750dbf2 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/container/ScreenInventory.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/container/ScreenInventory.java
@@ -13,6 +13,7 @@ import net.minecraft.client.input.controller.ControllerInput;
 import net.minecraft.client.render.texture.stitcher.TextureRegistry;
 import net.minecraft.core.achievement.Achievements;
 import net.minecraft.core.entity.player.Player;
+import net.minecraft.core.enums.HumanArmorShape;
 import net.minecraft.core.lang.I18n;
 import net.minecraft.core.item.Items;
 import net.minecraft.core.item.ItemStack;
@@ -67,8 +68,8 @@ public class ScreenInventory extends ScreenContainerAbstract
     protected void checkForArmor(){
         boolean enableArmorButton = false;
 
-        for(int i=0; i < mc.thePlayer.inventory.armorInventory.length; i++) {
-            ItemStack itemStack = mc.thePlayer.inventory.armorItemInSlot(i);
+        for(int i = 0; i < mc.thePlayer.inventory.armorInventory.length; i++) {
+            ItemStack itemStack = mc.thePlayer.getItemInArmorSlot(HumanArmorShape.values()[i]);
             if(itemStack != null) {
                 enableArmorButton = true;
                 break;
@@ -211,7 +212,7 @@ public class ScreenInventory extends ScreenContainerAbstract
         	if(damageType.shouldDisplay()) {
         		int y2 = y + i * 10;
         		
-            	float protection = mc.thePlayer.inventory.getTotalProtectionAmount(damageType);
+            	float protection = mc.thePlayer.getTotalProtectionAmount(damageType);
             	if(protection > 1) protection = 1.0f;
 
                 int l = (int) (protection * 255);
@@ -237,7 +238,7 @@ public class ScreenInventory extends ScreenContainerAbstract
         glEnable(GL_TEXTURE_2D);
         
         if(hoveredDamageType != null) {
-        	int protection = Math.round(mc.thePlayer.inventory.getTotalProtectionAmount(hoveredDamageType) * 100);
+        	int protection = Math.round(mc.thePlayer.getTotalProtectionAmount(hoveredDamageType) * 100);
         	if(protection < 0) {
         		protection = 0;
         	}
@@ -303,6 +304,7 @@ public class ScreenInventory extends ScreenContainerAbstract
 
     @Override
     public void guiSpecificControllerInput(ControllerInput controller){
+        super.guiSpecificControllerInput(controller);
         controller.inventoryHandler.handleInventory(this);
     }
 
diff --git a/game/client/src/main/java/net/minecraft/client/gui/container/ScreenInventoryCreative.java b/game/client/src/main/java/net/minecraft/client/gui/container/ScreenInventoryCreative.java
index 896f42783..15040ad0b 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/container/ScreenInventoryCreative.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/container/ScreenInventoryCreative.java
@@ -2,6 +2,7 @@ package net.minecraft.client.gui.container;
 
 import net.minecraft.client.gui.ButtonElement;
 import net.minecraft.client.gui.TextFieldElement;
+import net.minecraft.client.gui.keyboard.ScreenKeyboard;
 import net.minecraft.client.input.controller.ControllerInput;
 import net.minecraft.core.InventoryAction;
 import net.minecraft.core.entity.player.Player;
@@ -130,9 +131,14 @@ public class ScreenInventoryCreative extends ScreenInventory implements ITextCha
     	
     	updatePageSwitcher();
     }
-	
-    
-    @Override
+
+	@Override
+	public void render(int mx, int my, float partialTick) {
+		super.render(mx, my, partialTick);
+		searchField.updateCursor(mc, mx, my);
+	}
+
+	@Override
 	public void mouseClicked(int mx, int my, int buttonNum)
     {
         super.mouseClicked(mx, my, buttonNum);
diff --git a/game/client/src/main/java/net/minecraft/client/gui/createworld/TextFieldComponent.java b/game/client/src/main/java/net/minecraft/client/gui/createworld/TextFieldComponent.java
index a35ff3e37..c4c869d85 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/createworld/TextFieldComponent.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/createworld/TextFieldComponent.java
@@ -6,6 +6,7 @@ import net.minecraft.client.gui.paged.CallbackComponent;
 import net.minecraft.client.gui.paged.OnValueChangedCallback;
 import net.minecraft.client.render.Font;
 import net.minecraft.client.render.tessellator.Tessellator;
+import net.minecraft.client.render.window.CursorShape;
 import net.minecraft.core.lang.I18n;
 import org.lwjgl.input.Keyboard;
 import org.lwjgl.opengl.GL11;
@@ -62,6 +63,7 @@ public class TextFieldComponent
         textField.xPosition = x + width - BUTTON_WIDTH;
         textField.yPosition = y + COMPONENT_SPACING + 1;
         textField.drawTextBox();
+        textField.updateCursor(mc, x + relativeMouseX, y + relativeMouseY);
     }
 
     @Override
diff --git a/game/client/src/main/java/net/minecraft/client/gui/guidebook/ScreenGuidebook.java b/game/client/src/main/java/net/minecraft/client/gui/guidebook/ScreenGuidebook.java
index 80f2f2dca..168366009 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/guidebook/ScreenGuidebook.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/guidebook/ScreenGuidebook.java
@@ -317,6 +317,7 @@ public class ScreenGuidebook
     }
 
     public void guiSpecificControllerInput(ControllerInput controller){
+        super.guiSpecificControllerInput(controller);
         if (controller.digitalPad.left.pressedThisFrame() || (controller.digitalPad.left.isPressed() && controller.digitalPad.left.getHoldTime() % 4 == 0)){
             guidebookPageManager.offsetPage(-2);
             updateTabList();
diff --git a/game/client/src/main/java/net/minecraft/client/gui/guidebook/mobs/MobInfoRegistry.java b/game/client/src/main/java/net/minecraft/client/gui/guidebook/mobs/MobInfoRegistry.java
index 4d8c02cea..cf2b49f1e 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/guidebook/mobs/MobInfoRegistry.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/guidebook/mobs/MobInfoRegistry.java
@@ -88,6 +88,13 @@ public abstract class MobInfoRegistry {
             -1,
             null
         );
+        register(MobButterfly.class,
+            "guidebook.section.mob.butterfly.name",
+            "guidebook.section.mob.butterfly.desc",
+            -1,
+            -1,
+            null
+        );
         register(MobZombie.class,
             "guidebook.section.mob.zombie.name",
             "guidebook.section.mob.zombie.desc",
diff --git a/game/client/src/main/java/net/minecraft/client/gui/guidebook/search/GuidebookPageSearch.java b/game/client/src/main/java/net/minecraft/client/gui/guidebook/search/GuidebookPageSearch.java
index 2fdab1459..bfc866176 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/guidebook/search/GuidebookPageSearch.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/guidebook/search/GuidebookPageSearch.java
@@ -26,6 +26,7 @@ public class GuidebookPageSearch extends GuidebookPage {
         searchClearButton.xPosition = searchField.xPosition + searchField.width + 5;
         searchClearButton.yPosition = searchField.yPosition;
         searchField.drawTextBox();
+        searchField.updateCursor(mc, mouseX, mouseY);
         searchClearButton.drawButton(mc,mouseX,mouseY);
 
         int results = 0;
diff --git a/game/client/src/main/java/net/minecraft/client/gui/hud/HudIngame.java b/game/client/src/main/java/net/minecraft/client/gui/hud/HudIngame.java
index 451f452ba..3091bc47e 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/hud/HudIngame.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/hud/HudIngame.java
@@ -18,6 +18,7 @@ import net.minecraft.client.gui.ScreenPhotoMode;
 import net.minecraft.client.render.block.model.BlockModelDispatcher;
 import net.minecraft.core.block.BlockLogicPortal;
 import net.minecraft.core.enums.ChatVisibility;
+import net.minecraft.core.enums.HumanArmorShape;
 import net.minecraft.core.item.IArmorItem;
 import net.minecraft.core.util.helper.Direction;
 import org.lwjgl.opengl.GL11;
@@ -105,7 +106,7 @@ public class HudIngame extends Gui
         }
 
         // Render pumpkin overlay
-        ItemStack headSlotItem = mc.thePlayer.inventory.armorItemInSlot(IArmorItem.PIECE_HEAD);
+        ItemStack headSlotItem = mc.thePlayer.getItemInArmorSlot(HumanArmorShape.HEAD);
         if (mc.gameSettings.thirdPersonView.value == 0 && headSlotItem != null && (headSlotItem.itemID == Blocks.PUMPKIN_CARVED_IDLE.id() || headSlotItem.itemID == Blocks.PUMPKIN_REDSTONE.id()))
         {
             renderPumpkinBlur(width, height);
@@ -514,9 +515,9 @@ public class HudIngame extends Gui
 
         if (mc.gameSettings.immersiveMode.drawOverlays() && mc.gameSettings.armorDurabilityOverlay.value)
         {
-            for (int i = 0; i < 4; i++)
+            for (int i = mc.thePlayer.getNumArmorSlots() - 1; i >= 0; i--)
             {
-                ItemStack stack = mc.thePlayer.inventory.armorInventory[IArmorItem.PIECE_HEAD - i];
+                ItemStack stack = mc.thePlayer.getItemInArmorSlot(HumanArmorShape.values()[i]);
 
                 if (stack != null)
                 {
diff --git a/game/client/src/main/java/net/minecraft/client/gui/hud/HudIngameDoom.java b/game/client/src/main/java/net/minecraft/client/gui/hud/HudIngameDoom.java
index f4c306854..5d31bc3be 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/hud/HudIngameDoom.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/hud/HudIngameDoom.java
@@ -4,6 +4,7 @@ import net.minecraft.client.Minecraft;
 import net.minecraft.client.gui.ScreenPhotoMode;
 import net.minecraft.client.render.texture.stitcher.TextureRegistry;
 import net.minecraft.core.block.Blocks;
+import net.minecraft.core.enums.HumanArmorShape;
 import net.minecraft.core.item.IArmorItem;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.net.command.TextFormatting;
@@ -46,7 +47,7 @@ public class HudIngameDoom
         }
 
         // Render pumpkin overlay
-        ItemStack headSlotItem = mc.thePlayer.inventory.armorItemInSlot(IArmorItem.PIECE_HEAD);
+        ItemStack headSlotItem = mc.thePlayer.getItemInArmorSlot(HumanArmorShape.HEAD);
         if (mc.gameSettings.thirdPersonView.value == 0 && headSlotItem != null && (headSlotItem.itemID == Blocks.PUMPKIN_CARVED_IDLE.id() || headSlotItem.itemID == Blocks.PUMPKIN_REDSTONE.id()))
         {
             renderPumpkinBlur(width, height);
diff --git a/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponent.java b/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponent.java
index d7c10821e..d4f529d98 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponent.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponent.java
@@ -24,6 +24,10 @@ public abstract class HudComponent {
 
     public abstract boolean isVisible(Minecraft mc);
 
+    public boolean isEnabled(Minecraft mc) {
+        return true;
+    }
+
     public String getKey() {
         return key;
     }
diff --git a/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentHotbar.java b/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentHotbar.java
index 76ff3117a..4a0cedc82 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentHotbar.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentHotbar.java
@@ -64,7 +64,6 @@ public class HudComponentHotbar
         // Render hotbar
         hud.drawGuiIcon(x + 5, y, 182, 22, TextureRegistry.getTexture("minecraft:gui/hud/hotbar"));
 
-
         // Render hotbar switcher and dots
         int selectedHotbar = ((inv.getHotbarOffset() + 27) % 36) / 9;
         IconCoordinate coordinate = TextureRegistry.getTexture("minecraft:gui/hud/hotbar_selector" + selectedHotbar);
diff --git a/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentLog.java b/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentLog.java
new file mode 100644
index 000000000..7686689ee
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentLog.java
@@ -0,0 +1,177 @@
+package net.minecraft.client.gui.hud.component;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.console.LogMessage;
+import net.minecraft.client.console.LogMessageReceiver;
+import net.minecraft.client.gui.Gui;
+import net.minecraft.client.gui.hud.HudIngame;
+import net.minecraft.client.gui.hud.component.layout.Layout;
+import net.minecraft.client.gui.hud.component.layout.LayoutAbsolute;
+import net.minecraft.client.gui.hud.component.layout.LayoutSnap;
+import net.minecraft.client.render.Font;
+import net.minecraft.core.net.command.TextFormatting;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.lwjgl.opengl.GL11;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class HudComponentLog extends HudComponentMovable {
+    private static final class LogEntry {
+        public final @NotNull String message;
+        public final long addedTime;
+
+        public LogEntry(final @NotNull String message) {
+            this.message = message;
+            this.addedTime = System.currentTimeMillis();
+        }
+
+        public float getOpacity() {
+            final long currentTime = System.currentTimeMillis();
+            final long fadeoutStartTime = this.addedTime + MESSAGE_LIFETIME - MESSAGE_FADE_TIME;
+            if (currentTime < fadeoutStartTime) {
+                return 1.0f;
+            } else {
+                return 1.0f - (float) (currentTime - fadeoutStartTime) / MESSAGE_FADE_TIME;
+            }
+        }
+    }
+
+    private static final int HEIGHT_LINES = 4;
+    private static final int LINE_HEIGHT = 10;
+    private static final int PADDING = 2;
+    private static final int WIDTH = 240;
+    private static final long MESSAGE_LIFETIME = 5000;
+    private static final long MESSAGE_FADE_TIME = 100;
+
+    private final @NotNull Map<LogMessage.@NotNull Level, @NotNull TextFormatting> preludes = new HashMap<>();
+    private final @NotNull List<@NotNull LogEntry> logEntries = new ArrayList<>(HEIGHT_LINES);
+
+    private boolean callbackAdded = false;
+
+    public HudComponentLog(final @NotNull String key, final @NotNull Layout layout) {
+        super(key, PADDING + WIDTH + PADDING, PADDING + HEIGHT_LINES * LINE_HEIGHT + PADDING, layout);
+
+        this.preludes.put(LogMessage.Level.ERROR, TextFormatting.RED);
+        this.preludes.put(LogMessage.Level.WARN, TextFormatting.YELLOW);
+        this.preludes.put(LogMessage.Level.INFO, TextFormatting.WHITE);
+        this.preludes.put(LogMessage.Level.DEBUG, TextFormatting.LIGHT_BLUE);
+        this.preludes.put(LogMessage.Level.TRACE, TextFormatting.GRAY);
+    }
+
+    @Override
+    public boolean isEnabled(final @NotNull Minecraft mc) {
+        return mc.gameSettings.developerMode.value;
+    }
+
+    @Override
+    public boolean isVisible(final @NotNull Minecraft mc) {
+        return true;
+    }
+
+    @Override
+    public void render(final @NotNull Minecraft mc, final @NotNull HudIngame hud, final int xSizeScreen, final int ySizeScreen, final float partialTick) {
+        if (!this.callbackAdded) {
+            LogMessageReceiver.addCallback((message) -> {
+                if (message.getMessage().startsWith("[CHAT] ")) return;
+
+                final @NotNull String ellipsis = "...";
+                final int ellipsisWidth = mc.font.getStringWidth(ellipsis);
+
+                @NotNull String line = message.getMessage().replace("\n", "");
+                int acc = 0;
+                int c = 0;
+                for (; c < line.length(); c++) {
+                    final int cWidth = mc.font.getCharWidth(line.charAt(c));
+                    if (acc + cWidth + ellipsisWidth > WIDTH) {
+                        break;
+                    }
+                    acc += cWidth;
+                }
+                if (c < line.length()) {
+                    line = line.substring(0, c) + ellipsis;
+                }
+
+                if (this.logEntries.size() == HEIGHT_LINES) {
+                    this.logEntries.remove(0);
+                }
+                if (message.getLevel() != null) {
+                    this.logEntries.add(new LogEntry(this.preludes.get(message.getLevel()) + line));
+                } else {
+                    this.logEntries.add(new LogEntry(line));
+                }
+            });
+            callbackAdded = true;
+        }
+
+        int x = this.getLayout().getComponentX(mc, this, xSizeScreen);
+        int y = this.getLayout().getComponentY(mc, this, ySizeScreen);
+
+        // Remove dead entries
+        for (int i = 0; i < this.logEntries.size(); i++) {
+            if (this.logEntries.get(i).getOpacity() <= 0.0f) {
+                this.logEntries.remove(0);
+                i--;
+            }
+        }
+
+        final @NotNull String @NotNull[] messages = new String[this.logEntries.size()];
+        final float @NotNull[] opacities = new float[this.logEntries.size()];
+        for (int i = 0; i < this.logEntries.size(); i++) {
+            messages[i] = this.logEntries.get(i).message;
+            opacities[i] = this.logEntries.get(i).getOpacity();
+        }
+
+        @Nullable ComponentAnchor anchor = null;
+        if (layout instanceof LayoutAbsolute) {
+            anchor = ((LayoutAbsolute) layout).getAnchor();
+        } else if (layout instanceof LayoutSnap) {
+            anchor = ((LayoutSnap) layout).getAnchor();
+        }
+
+        render(mc.font, x, y, messages, opacities, anchor);
+    }
+
+    @Override
+    public void renderPreview(final @NotNull Minecraft mc, final @NotNull Gui gui, final @NotNull Layout layout, final int xSizeScreen, final int ySizeScreen) {
+        int x = layout.getComponentX(mc, this, xSizeScreen);
+        int y = layout.getComponentY(mc, this, ySizeScreen);
+
+        final @NotNull String @NotNull[] lines = {
+            this.preludes.get(LogMessage.Level.DEBUG) + "This is a debug line!",
+            this.preludes.get(LogMessage.Level.INFO) + "This is an info line!",
+            this.preludes.get(LogMessage.Level.WARN) + "This is a warning line!",
+            this.preludes.get(LogMessage.Level.ERROR) + "This is an error line!",
+        };
+
+        @Nullable ComponentAnchor anchor = null;
+        if (layout instanceof LayoutAbsolute) {
+            anchor = ((LayoutAbsolute) layout).getAnchor();
+        } else if (layout instanceof LayoutSnap) {
+            anchor = ((LayoutSnap) layout).getAnchor();
+        }
+
+        render(mc.font, x, y, lines, new float[] { 1.0f, 1.0f, 1.0f, 1.0f }, anchor);
+    }
+
+    private void render(final @NotNull Font font, final int x, final int y, final @NotNull String @NotNull[] lines, final float @NotNull[] opacities, final @Nullable ComponentAnchor anchor) {
+        GL11.glEnable(GL11.GL_BLEND);
+        GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
+        for (int i = 0; i < lines.length; i++) {
+            @NotNull String line = lines[i];
+
+            final int lineWidth = font.getStringWidth(line);
+            if (anchor == null || anchor == ComponentAnchor.TOP_LEFT || anchor == ComponentAnchor.CENTER_LEFT || anchor == ComponentAnchor.BOTTOM_LEFT) {
+                font.drawStringWithShadow(line, x + PADDING, y + PADDING + i * LINE_HEIGHT, 0x00_FF_FF_FF + ((int)(opacities[i] * 0xFF) << 24));
+            } else if (anchor == ComponentAnchor.TOP_CENTER || anchor == ComponentAnchor.CENTER || anchor == ComponentAnchor.BOTTOM_CENTER) {
+                font.drawStringWithShadow(line, x + PADDING + WIDTH / 2 - lineWidth / 2, y + PADDING + i * LINE_HEIGHT, 0x00_FF_FF_FF + ((int)(opacities[i] * 0xFF) << 24));
+            } else {
+                font.drawStringWithShadow(line, x + PADDING + WIDTH - lineWidth, y + PADDING + i * LINE_HEIGHT, 0x00_FF_FF_FF + ((int)(opacities[i] * 0xFF) << 24));
+            }
+        }
+        GL11.glDisable(GL11.GL_BLEND);
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponents.java b/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponents.java
index 4ae51fb46..4573a52df 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponents.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponents.java
@@ -66,4 +66,5 @@ public class HudComponents {
     public static final HudComponent FIRE_BAR = register(new HudComponentFireBar("fire_bar", new LayoutSnap(OXYGEN_BAR, ComponentAnchor.TOP_LEFT, ComponentAnchor.BOTTOM_LEFT)));
     public static final HudComponent ROTATION_LOCK = register(new HudComponentRotationLock("rotation_lock", new LayoutSnap(CROSSHAIR, ComponentAnchor.CENTER_RIGHT, ComponentAnchor.CENTER_LEFT)));
     public static final HudComponent SUBTITLES = register(new HubComponentSubtitles("subtitles_box", 100, 50, new LayoutAbsolute(1.0f, 0.75f, ComponentAnchor.BOTTOM_RIGHT)));
+    public static final HudComponent LOG = register(new HudComponentLog("log", new LayoutAbsolute(1.0f, 0.0f, ComponentAnchor.TOP_RIGHT)));
 }
diff --git a/game/client/src/main/java/net/minecraft/client/gui/keyboard/KeyboardLayout.java b/game/client/src/main/java/net/minecraft/client/gui/keyboard/KeyboardLayout.java
new file mode 100644
index 000000000..1875630ae
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/gui/keyboard/KeyboardLayout.java
@@ -0,0 +1,255 @@
+package net.minecraft.client.gui.keyboard;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.google.gson.stream.JsonReader;
+import net.minecraft.client.gui.keyboard.keys.BackspaceKey;
+import net.minecraft.client.gui.keyboard.keys.CharKey;
+import net.minecraft.client.gui.keyboard.keys.CursorKey;
+import net.minecraft.client.gui.keyboard.keys.EnterKey;
+import net.minecraft.client.gui.keyboard.keys.Key;
+import net.minecraft.client.gui.keyboard.keys.GroupKey;
+import net.minecraft.client.gui.keyboard.keys.ModifierKey;
+import net.minecraft.core.lang.I18n;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.List;
+
+public class KeyboardLayout {
+    private static final boolean GENERATE_NEW = false;
+    private static final Gson gson = getGson().create();
+
+    // This exists to better facilitate mods adding custom key types
+    private static GsonBuilder getGson() {
+        GsonBuilder builder = new GsonBuilder();
+        builder.setPrettyPrinting();
+        builder.registerTypeAdapter(Key.class, new Key.Serializer());
+        builder.registerTypeAdapter(CharKey.class, new CharKey.Serializer());
+        builder.registerTypeAdapter(GroupKey.class, new GroupKey.Serializer());
+        builder.registerTypeAdapter(CursorKey.class, new CursorKey.Serializer());
+        builder.registerTypeAdapter(EnterKey.class, new EnterKey.Serializer());
+        builder.registerTypeAdapter(BackspaceKey.class, new BackspaceKey.Serializer());
+        builder.registerTypeAdapter(ModifierKey.class, new ModifierKey.Serializer());
+        return builder;
+    }
+
+    public static KeyboardLayout defaultLayout = new KeyboardLayout();
+    private Key[] keys;
+    private int minX;
+    private int maxX;
+    private int minY;
+    private int maxY;
+
+    private int highestGroup = 0;
+
+    private int width;
+    private int height;
+
+    public KeyboardLayout() {
+        loadFromStream(I18n.getResourceAsStream("/lang/en_US/keyboard.json"));
+    }
+
+    private void loadFromStream(InputStream stream) {
+        minX = Integer.MAX_VALUE;
+        maxX = Integer.MIN_VALUE;
+        minY = Integer.MAX_VALUE;
+        maxY = Integer.MIN_VALUE;
+        List<Key> keyList = new ArrayList<>();
+
+        if (GENERATE_NEW) {
+
+            int x = 0;
+            int xShift = 0;
+            int y = 0;
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y).addGroup('1').build());
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y).addGroup('2').build());
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y).addGroup('3').build());
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y).addGroup('4').build());
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y).addGroup('5').build());
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y).addGroup('6').build());
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y).addGroup('7').build());
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y).addGroup('8').build());
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y).addGroup('9').build());
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y).addGroup('0').build());
+            x = 0;
+            xShift = 0;
+            y++;
+
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y).addGroup('q').addGroup('!').build());
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y).addGroup('w').addGroup('@').build());
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y)
+                .addGroup(new char[][]{{'e'}, {'ė'}, {'ę'}, {'ě'}, {'ĕ'}, {'ə'}, {'è'}, {'é'}, {'ê'}, {'ë'}, {'ē'}})
+                .addGroup('#').build());
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y)
+                .addGroup(new char[][]{{'r'}, {'ŕ'}, {'ř'}})
+                .addGroup('$').build());
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y)
+                .addGroup(new char[][]{{'t'}, {'þ'}, {'ť'}, {'ț'}, {'ţ'}})
+                .addGroup('%').build());
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y)
+                .addGroup(new char[][]{{'y'}, {'ý'}})
+                .addGroup('^').build());
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y)
+                .addGroup(new char[][]{{'u'}, {'ų'}, {'ű'}, {'ů'}, {'ū'}, {'ü'}, {'û'}, {'ú'}, {'ù'}})
+                .addGroup('&').build());
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y)
+                .addGroup(new char[][]{{'i'}, {'ı'}, {'į'}, {'ī'}, {'ï'}, {'í'}, {'í'}, {'ì'}})
+                .addGroup('*').build());
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y)
+                .addGroup(new char[][]{{'o'}, {'œ'}, {'ő'}, {'ō'}, {'ø'}, {'ö'}, {'õ'}, {'ô'}, {'ó'}, {'ò'}})
+                .addGroup('(').build());
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y).addGroup('p').addGroup(')').build());
+            x = 0;
+            xShift = 0;
+            y++;
+
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y)
+                .addGroup(new char[][]{{'a'}, {'å'}, {'æ'}, {'ā'}, {'ă'}, {'ą'}, {'ª'}, {'à'}, {'á'}, {'â'}, {'ã'}, {'ä'}})
+                .addGroup('~').build());
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y)
+                .addGroup(new char[][]{{'s'}, {'ß'}, {'ś'}, {'š'}, {'ş'}})
+                .addGroup('\'').build());
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y)
+                .addGroup(new char[][]{{'d'}, {'ď'}, {'đ'}})
+                .addGroup('"').build());
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y).addGroup('f').addGroup('{').build());
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y)
+                .addGroup(new char[][]{{'g'}, {'ģ'}, {'ğ'}})
+                .addGroup('}').build());
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y).addGroup('h').addGroup('[').build());
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y).addGroup('j').addGroup(']').build());
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y)
+                .addGroup(new char[][]{{'k'}, {'ķ'}})
+                .addGroup(':').build());
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y)
+                .addGroup(new char[][]{{'l'}, {'ĺ'}, {'ļ'}, {'ľ'}, {'ł'}})
+                .addGroup(';').build());
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y).addGroup('/').addGroup('\\').build());
+            x = 0;
+            xShift = 0;
+            y++;
+
+            keyList.add(new ModifierKey(18 * x++, 18 * y, 18, 18, ModifierKey.ID_MODIFIER_SHIFT, false).setControllerButton(Key.ButtonEnum.BUTTON_TRIGGER_LEFT));
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y)
+                .addGroup(new char[][]{{'z'}, {'ź'}, {'ż'}, {'ž'}})
+                .addGroup('`').build());
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y).addGroup('x').addGroup('|').build());
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y)
+                .addGroup(new char[][]{{'c'}, {'ç'}, {'ć'}, {'č'}})
+                .addGroup('§').build());
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y).addGroup('v').addGroup('+').build());
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y).addGroup('b').addGroup('-').build());
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y)
+                .addGroup(new char[][]{{'n'}, {'ñ'}, {'ń'}, {'ņ'}, {'ň'}})
+                .addGroup('<').build());
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y).addGroup('m').addGroup('>').build());
+            keyList.add(new CharKey.CharKeyBuilder(18 * x++ + xShift, 18 * y).addGroup('.').addGroup('?').build());
+            keyList.add(new EnterKey(18 * x++ + xShift, 18 * y, 18, 18).setControllerButton(Key.ButtonEnum.BUTTON_START));
+            x = 0;
+            xShift = 0;
+            y++;
+
+            keyList.add(new GroupKey(0, 18 * y, 25, 18, GroupKey.Mode.TOGGLE, 0, 1)
+                .setControllerButton(Key.ButtonEnum.BUTTON_STICK_LEFT)
+                .setTextures(
+                    "minecraft:gui/widgets/keyboard/key_alt/up",
+                    "minecraft:gui/widgets/keyboard/key_alt/down",
+                    "minecraft:gui/widgets/keyboard/key_alt/up_hovered",
+                    "minecraft:gui/widgets/keyboard/key_alt/down_hovered")
+            );
+            keyList.add(
+                new CursorKey(25 + 1, 18 * y, 18, 18, -1)
+                    .setTextures(
+                        GroupKey.Serializer.DEFAULT_TEXTURE_LAST_UP,
+                        GroupKey.Serializer.DEFAULT_TEXTURE_LAST_DOWN,
+                        GroupKey.Serializer.DEFAULT_TEXTURE_LAST_UP_HOVERED,
+                        GroupKey.Serializer.DEFAULT_TEXTURE_LAST_DOWN_HOVERED)
+                    .setControllerButton(Key.ButtonEnum.BUTTON_SHOULDER_LEFT));
+            keyList.add(new CharKey.CharKeyBuilder(25 + 1 + 18 + 1, 18 * y).setWidth(90).addGroup(' ').setButton(Key.ButtonEnum.BUTTON_Y).build());
+            keyList.add(new CursorKey(25 + 1 + 18 + 1 + 90 + 1, 18 * y, 18, 18, 1)
+                .setTextures(
+                    GroupKey.Serializer.DEFAULT_TEXTURE_NEXT_UP,
+                    GroupKey.Serializer.DEFAULT_TEXTURE_NEXT_DOWN,
+                    GroupKey.Serializer.DEFAULT_TEXTURE_NEXT_UP_HOVERED,
+                    GroupKey.Serializer.DEFAULT_TEXTURE_NEXT_DOWN_HOVERED)
+                .setControllerButton(Key.ButtonEnum.BUTTON_SHOULDER_RIGHT));
+            keyList.add(new BackspaceKey(25 + 1 + 18 + 1 + 90 + 1 + 18 + 1, 18 * y, 25, 18).setControllerButton(Key.ButtonEnum.BUTTON_B));
+
+            JsonObject layoutObject = new JsonObject();
+            JsonArray keyArray = new JsonArray(keyList.size());
+            for (Key key : keyList) {
+                keyArray.add(gson.toJsonTree(key));
+            }
+            layoutObject.add("keys", keyArray);
+
+            File out = new File("../core/src/main/resources/lang/en_US/keyboard.json");
+            out.delete();
+            try (BufferedWriter writer = Files.newBufferedWriter(out.toPath())) {
+                out.createNewFile();
+                gson.toJson(layoutObject, writer);
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        } else {
+            JsonObject layoutObject = JsonParser.parseReader(new JsonReader(new InputStreamReader(stream))).getAsJsonObject();
+            JsonArray keys = layoutObject.getAsJsonArray("keys");
+            for (JsonElement e : keys) {
+                keyList.add(gson.fromJson(e, Key.class));
+            }
+        }
+
+        keys = keyList.toArray(new Key[0]);
+        for (int i = 0; i < keys.length; i++) {
+            Key key = keys[i];
+            minX = Math.min(minX, key.getX());
+            minY = Math.min(minY, key.getY());
+            maxX = Math.max(maxX, key.getX() + key.getWidth());
+            maxY = Math.max(maxY, key.getY() + key.getHeight());
+            highestGroup = Math.max(highestGroup, key.highestGroup());
+        }
+        width = maxX - minX;
+        height = maxY - minY;
+    }
+
+    public Key[] getKeys() {
+        return keys;
+    }
+
+    public int getMinX() {
+        return minX;
+    }
+
+    public int getMaxX() {
+        return maxX;
+    }
+
+    public int getMinY() {
+        return minY;
+    }
+
+    public int getMaxY() {
+        return maxY;
+    }
+
+    public int getWidth() {
+        return width;
+    }
+
+    public int getHeight() {
+        return height;
+    }
+
+    public int getHighestGroup() {
+        return highestGroup;
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/gui/keyboard/ScreenKeyboard.java b/game/client/src/main/java/net/minecraft/client/gui/keyboard/ScreenKeyboard.java
new file mode 100644
index 000000000..a698d9b3b
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/gui/keyboard/ScreenKeyboard.java
@@ -0,0 +1,408 @@
+package net.minecraft.client.gui.keyboard;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.ControllerGlyphElement;
+import net.minecraft.client.gui.Screen;
+import net.minecraft.client.gui.keyboard.keys.Key;
+import net.minecraft.client.gui.text.ITextField;
+import net.minecraft.client.gui.text.TextFieldEditor;
+import net.minecraft.client.input.controller.ControllerInput;
+import net.minecraft.client.render.texture.stitcher.TextureRegistry;
+import net.minecraft.core.sound.SoundCategory;
+import net.minecraft.core.util.helper.ChatAllowedCharacters;
+import net.minecraft.core.util.helper.MathHelper;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.lwjgl.input.Keyboard;
+import org.lwjgl.opengl.GL11;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+public class ScreenKeyboard extends Screen implements ITextField {
+    private static final int HORIZONTAL_BUFFER = 5;
+
+    public final @Nullable Screen parent;
+    public final @Nullable StringConsumer resultStringAction;
+    public final @Nullable StringConsumer backspaceAction;
+    public final @Nullable KeyAction keyAction;
+
+    private final KeyboardLayout keyboardLayout;
+
+    private @NotNull String workingString = "";
+    public final @NotNull TextFieldEditor editor;
+    private int cursorCounter = 0;
+
+    private final boolean wasRepeating;
+
+    public final Map<String, Boolean> activeModifiers = new HashMap<>();
+    public final Map<String, Boolean> activeLocks = new HashMap<>();
+
+    public ControllerGlyphElement glyphRenderer;
+
+    private int group = 0;
+
+    public ScreenKeyboard(@Nullable Screen parent, @Nullable TextFieldEditor editor, @Nullable StringConsumer enterAction, @Nullable KeyAction keyAction, @Nullable StringConsumer backspaceAction) {
+        this.parent = parent;
+        this.resultStringAction = enterAction;
+        this.backspaceAction = backspaceAction;
+        this.keyAction = keyAction;
+        this.editor = editor == null ? new TextFieldEditor(this) : editor;
+        this.wasRepeating = Keyboard.areRepeatEventsEnabled();
+
+
+        this.keyboardLayout = KeyboardLayout.defaultLayout;
+        Arrays.stream(keyboardLayout.getKeys()).forEach(key -> key.onOpen(mc, this));
+    }
+
+    @Override
+    public void opened(Minecraft mc, int width, int height) {
+        Keyboard.enableRepeatEvents(true);
+        if (parent != null) {
+            parent.mc = mc;
+            parent.font = mc.font;
+            parent.width = width;
+            parent.height = height;
+        }
+        this.glyphRenderer = new ControllerGlyphElement(mc);
+        super.opened(mc, width, height);
+    }
+
+    @Override
+    public boolean isPauseScreen() {
+        if (parent != null) {
+            return parent.isPauseScreen();
+        }
+        return super.isPauseScreen();
+    }
+
+    @Override
+    public void tick() {
+        cursorCounter++;
+        if (parent != null) {
+            parent.tick();
+        }
+    }
+
+    @Override
+    public void keyPressed(char eventCharacter, int eventKey, int mx, int my) {
+        if (eventKey == Keyboard.KEY_ESCAPE || eventKey == Keyboard.KEY_RETURN) {
+            close();
+            return;
+        }
+        typeChar(eventKey, eventCharacter);
+    }
+
+    public void typeChar(int key, char chr) {
+        if (editor.handleInput(key, chr)) {
+            if (keyAction != null) {
+                keyAction.apply(chr, key, editor.getText());
+            }
+
+            for (String id : activeModifiers.keySet()) {
+                if (!isModifierLocked(id)) {
+                    setModifierState(id, false);
+                }
+            }
+        }
+    }
+
+    public void backspace() {
+        editor.deleteCharsBeforeCursor(1);
+        if (backspaceAction != null) {
+            backspaceAction.apply(editor.getText());
+        }
+    }
+
+    public void setModifierState(String id, boolean flag) {
+        activeModifiers.put(id, flag);
+        activeLocks.putIfAbsent(id, false);
+    }
+
+    public void setModifierLock(String id, boolean flag) {
+        activeModifiers.putIfAbsent(id, false);
+        activeLocks.put(id, flag);
+    }
+
+    public boolean isModifierActive(String id) {
+        return activeModifiers.getOrDefault(id, false) || isModifierLocked(id);
+    }
+
+    public boolean isModifierLocked(String id) {
+        return activeLocks.getOrDefault(id, false);
+    }
+
+    @Override
+    public void removed() {
+        if (mc.controllerInput != null) {
+            mc.controllerInput.lockCursor = false;
+            mc.controllerInput.hideCursor = false;
+        }
+        if (resultStringAction != null) {
+            resultStringAction.apply(editor.getText());
+        }
+        Keyboard.enableRepeatEvents(wasRepeating);
+    }
+
+    public void close()
+    {
+        this.removed();
+        mc.currentScreen = parent;
+    }
+
+    public void setGroup(int group) {
+        group = MathHelper.clamp(group, 0, keyboardLayout.getHighestGroup());
+        this.group = group;
+    }
+
+    public void shiftGroup(int amount) {
+        setGroup(getShiftedGroup(amount));
+    }
+
+    public int getShiftedGroup(int amount) {
+        int group = this.group + amount;
+        return (group + keyboardLayout.getHighestGroup() + 1) % (keyboardLayout.getHighestGroup() + 1);
+    }
+
+    public int getGroup() {
+        return group;
+    }
+
+    @Override
+    public void mouseClicked(int mx, int my, int buttonNum) {
+        if (!mouseOnKeyboard(mx, my)) {
+            close();
+            return;
+        }
+        int _x = ((width - keyboardLayout.getWidth()) / 2);
+        int _y = height - keyboardLayout.getHeight() - 5;
+        Arrays.stream(keyboardLayout.getKeys()).forEach(key -> key.onClick(mc, this, _x, _y, mx, my, buttonNum));
+    }
+
+    @Override
+    public void mouseReleased(int mx, int my, int buttonNum) {
+        int _x = ((width - keyboardLayout.getWidth()) / 2);
+        int _y = height - keyboardLayout.getHeight() - 5;
+        Arrays.stream(keyboardLayout.getKeys()).forEach(key -> key.onRelease(mc, this, _x, _y, mx, my, buttonNum));
+    }
+
+    public boolean mouseOnKeyboard(int mx, int my) {
+        int keyboardWidth = keyboardLayout.getWidth();
+        int keyboardHeight = keyboardLayout.getHeight();
+
+        int _x = ((width - keyboardWidth) / 2) - HORIZONTAL_BUFFER;
+        int _y = height - keyboardHeight - 5 - 34;
+        return
+            _x <= mx && mx < _x + (keyboardWidth + HORIZONTAL_BUFFER * 2) &&
+            _y <= my && my < _y + (keyboardHeight + 34 + 5);
+    }
+
+    @Override
+    public void render(int mx, int my, float partialTick) {
+        if (parent != null) {
+            GL11.glPushMatrix();
+            GL11.glTranslatef(0, 0, -100);
+            parent.render(mx, my, partialTick);
+            GL11.glPopMatrix();
+        }
+        drawRect(0, 0, width, height, 0x90_00_00_00);
+        GL11.glColor4f(1f, 1f, 1f, 1f);
+
+        int keyboardWidth = keyboardLayout.getWidth();
+        int keyboardHeight = keyboardLayout.getHeight();
+
+        int _x = ((width - keyboardWidth) / 2);
+        int _y = height - keyboardHeight - 5;
+
+        drawGuiIcon(
+            _x - 5 - HORIZONTAL_BUFFER,
+            _y - 4 - 30,
+            keyboardWidth + 10 + HORIZONTAL_BUFFER * 2,
+            36,
+            TextureRegistry.getTexture("minecraft:gui/screen/keyboard/keyboard_background_embedded"));
+
+        drawGuiIcon(
+            _x - 5 - HORIZONTAL_BUFFER,
+            _y - 4,
+            keyboardWidth + 10 + HORIZONTAL_BUFFER * 2,
+            keyboardHeight + 8,
+            TextureRegistry.getTexture("minecraft:gui/screen/keyboard/keyboard_background"));
+
+        Arrays.stream(keyboardLayout.getKeys()).forEach(key -> key.render(mc, this, _x, _y, mx, my));
+
+        drawStringCentered(mc.font, editor.getText(), _x + keyboardWidth/2, _y - 20, 0xFFFFFF);
+
+        boolean drawCursor = (cursorCounter / 6) % 2 == 0;
+        if (drawCursor)
+        {
+            int width = font.getStringWidth(editor.getText());
+            if (editor.getCursor() < editor.getText().length())
+            {
+                width = font.getStringWidth(editor.getText().substring(0, editor.getCursor()));
+            }
+            drawString(font, "_", _x + keyboardWidth/2 - font.getStringWidth(editor.getText())/2 + width, _y - 20 + 1, 0xE0E0E0);
+        }
+
+
+//        drawString(mc.font, String.valueOf(page), _x + 2, _y - 32, 0xFFFFFF);
+    }
+
+    @Override
+    public void guiSpecificControllerInput(ControllerInput controller) {
+        int keyboardWidth = keyboardLayout.getWidth();
+        int keyboardHeight = keyboardLayout.getHeight();
+
+        int _x = ((width - keyboardWidth) / 2);
+        int _y = height - keyboardHeight - 5;
+
+        if(controller.buttonA.pressedThisFrame()) {
+            this.mouseClicked((int)controller.cursorX, (int)controller.cursorY, 0);
+        }
+        if(controller.buttonA.releasedThisFrame()) {
+            this.mouseReleased((int)controller.cursorX, (int)controller.cursorY, 0);
+        }
+
+        if (!controller.lockCursor) {
+            if (controller.digitalPad.up.pressedThisFrame()) {
+                Key hovered = getHoveredKey((int)controller.cursorX, (int)controller.cursorY);
+
+                final int stepSize = 9;
+                int x = hovered != null ? (_x + hovered.getX() + hovered.getWidth()/2): MathHelper.clamp((int) controller.cursorX, keyboardLayout.getMinX() + _x, keyboardLayout.getMaxX() + _x);
+                int y = hovered != null ? (_y + hovered.getY() + hovered.getHeight()/2) : MathHelper.clamp((int) controller.cursorY, keyboardLayout.getMinY() + _y, keyboardLayout.getMaxY() + _y);
+                for (int i = 0; i < keyboardHeight/9; i++) {
+                    int newY = y - stepSize * i;
+                    while (newY < _y) {
+                        newY += keyboardHeight;
+                    }
+                    while (newY > height - 5) {
+                        newY -= keyboardHeight;
+                    }
+                    Key newHover = getHoveredKey(x, newY);
+                    if (newHover != null && newHover != hovered) {
+                        controller.cursorX = _x + newHover.getX() + newHover.getWidth()/2f;
+                        controller.cursorY = _y + newHover.getY() + newHover.getHeight()/2f;
+                        break;
+                    }
+                }
+            }
+            if (controller.digitalPad.down.pressedThisFrame()) {
+                Key hovered = getHoveredKey((int)controller.cursorX, (int)controller.cursorY);
+
+                final int stepSize = 9;
+                int x = hovered != null ? (_x + hovered.getX() + hovered.getWidth()/2): MathHelper.clamp((int) controller.cursorX, keyboardLayout.getMinX() + _x, keyboardLayout.getMaxX() + _x);
+                int y = hovered != null ? (_y + hovered.getY() + hovered.getHeight()/2) : MathHelper.clamp((int) controller.cursorY, keyboardLayout.getMinY() + _y, keyboardLayout.getMaxY() + _y);
+                for (int i = 0; i < keyboardHeight/9; i++) {
+                    int newY = y + stepSize * i;
+                    while (newY < _y) {
+                        newY += keyboardHeight;
+                    }
+                    while (newY > height - 5) {
+                        newY -= keyboardHeight;
+                    }
+                    Key newHover = getHoveredKey(x, newY);
+                    if (newHover != null && newHover != hovered) {
+                        controller.cursorX = _x + newHover.getX() + newHover.getWidth()/2f;
+                        controller.cursorY = _y + newHover.getY() + newHover.getHeight()/2f;
+                        break;
+                    }
+                }
+            }
+            if (controller.digitalPad.left.pressedThisFrame()) {
+                Key hovered = getHoveredKey((int)controller.cursorX, (int)controller.cursorY);
+
+                final int stepSize = 9;
+                int x = hovered != null ? (_x + hovered.getX() + hovered.getWidth()/2): MathHelper.clamp((int) controller.cursorX, keyboardLayout.getMinX() + _x, keyboardLayout.getMaxX() + _x);
+                int y = hovered != null ? (_y + hovered.getY() + hovered.getHeight()/2) : MathHelper.clamp((int) controller.cursorY, keyboardLayout.getMinY() + _y, keyboardLayout.getMaxY() + _y);
+                for (int i = 0; i < keyboardWidth/9; i++) {
+                    int newX = x - stepSize * i;
+                    while (newX < _x) {
+                        newX += keyboardWidth;
+                    }
+                    while (newX > _x + keyboardWidth) {
+                        newX -= keyboardWidth;
+                    }
+                    Key newHover = getHoveredKey(newX, y);
+                    if (newHover != null && newHover != hovered) {
+                        controller.cursorX = _x + newHover.getX() + newHover.getWidth()/2f;
+                        controller.cursorY = _y + newHover.getY() + newHover.getHeight()/2f;
+                        break;
+                    }
+                }
+            }
+            if (controller.digitalPad.right.pressedThisFrame()) {
+                Key hovered = getHoveredKey((int)controller.cursorX, (int)controller.cursorY);
+
+                final int stepSize = 9;
+                int x = hovered != null ? (_x + hovered.getX() + hovered.getWidth()/2): MathHelper.clamp((int) controller.cursorX, keyboardLayout.getMinX() + _x, keyboardLayout.getMaxX() + _x);
+                int y = hovered != null ? (_y + hovered.getY() + hovered.getHeight()/2) : MathHelper.clamp((int) controller.cursorY, keyboardLayout.getMinY() + _y, keyboardLayout.getMaxY() + _y);
+                for (int i = 0; i < keyboardWidth/9; i++) {
+                    int newX = x + stepSize * i;
+                    while (newX < _x) {
+                        newX += keyboardWidth;
+                    }
+                    while (newX > _x + keyboardWidth) {
+                        newX -= keyboardWidth;
+                    }
+                    Key newHover = getHoveredKey(newX, y);
+                    if (newHover != null && newHover != hovered) {
+                        controller.cursorX = _x + newHover.getX() + newHover.getWidth()/2f;
+                        controller.cursorY = _y + newHover.getY() + newHover.getHeight()/2f;
+                        break;
+                    }
+                }
+            }
+        }
+
+        Arrays.stream(keyboardLayout.getKeys()).forEach(key -> key.onButtonUse(mc, this, _x, _y));
+    }
+
+    @Override
+    public void playControllerButtonSound(ControllerInput controller) {
+        if (controller.buttonStart.pressedThisFrame()) {
+            mc.sndManager.playSound("ui.ui_click", SoundCategory.GUI_SOUNDS, 1.0f, 1.0f);
+        }
+        if (controller.buttonB.pressedThisFrame()) {
+            mc.sndManager.playSound("ui.ui_back", SoundCategory.GUI_SOUNDS, 1.0f, 1.0f);
+        }
+    }
+
+    public @Nullable Key getHoveredKey(int mx, int my) {
+        int keyboardWidth = keyboardLayout.getWidth();
+        int keyboardHeight = keyboardLayout.getHeight();
+
+        int _x = ((width - keyboardWidth) / 2);
+        int _y = height - keyboardHeight - 5;
+        for (Key key : keyboardLayout.getKeys()) {
+            if (key.isHovered(_x, _y, mx, my)) return key;
+        }
+        return null;
+    }
+
+    @Override
+    public void setText(String text) {
+        this.workingString = text;
+    }
+
+    @Override
+    public String getText() {
+        return workingString;
+    }
+
+    @Override
+    public int maxLength() {
+        return Integer.MAX_VALUE;
+    }
+
+    public boolean isCharacterAllowed(char c) {
+        return ChatAllowedCharacters.ALLOWED_CHARACTERS.indexOf(c) >= 0 || Character.isLetterOrDigit(c);
+    }
+
+    public interface KeyAction {
+        void apply(char c, int key, String workingString);
+    }
+
+    public interface StringConsumer {
+        void apply(String s);
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/gui/keyboard/keys/BackspaceKey.java b/game/client/src/main/java/net/minecraft/client/gui/keyboard/keys/BackspaceKey.java
new file mode 100644
index 000000000..97163a2b3
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/gui/keyboard/keys/BackspaceKey.java
@@ -0,0 +1,97 @@
+package net.minecraft.client.gui.keyboard.keys;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.keyboard.ScreenKeyboard;
+import net.minecraft.core.sound.SoundCategory;
+
+import java.lang.reflect.Type;
+import java.util.Locale;
+
+public class BackspaceKey extends Key{
+
+    public BackspaceKey(int x, int y, int width, int height) {
+        super(x, y, width, height);
+        setTextures(
+            "minecraft:gui/widgets/keyboard/key_backspace/up",
+            "minecraft:gui/widgets/keyboard/key_backspace/down",
+            "minecraft:gui/widgets/keyboard/key_backspace/up_hovered",
+            "minecraft:gui/widgets/keyboard/key_backspace/down_hovered");
+    }
+
+    @Override
+    public void pressAction(Minecraft mc, ScreenKeyboard keyboard, int originX, int originY, int mouseX, int mouseY) {
+        keyboard.backspace();
+        mc.sndManager.playSound("ui.ui_back", SoundCategory.GUI_SOUNDS, 1.0f, 1.0f);
+    }
+
+    public static class Serializer implements JsonSerializer<BackspaceKey>, JsonDeserializer<BackspaceKey> {
+        public static final int DEFAULT_WIDTH = 25;
+        public static final int DEFAULT_HEIGHT = 18;
+
+        public static final String DEFAULT_TEXTURE_UP = "minecraft:gui/widgets/keyboard/key_backspace/up";
+        public static final String DEFAULT_TEXTURE_DOWN = "minecraft:gui/widgets/keyboard/key_backspace/down";
+        public static final String DEFAULT_TEXTURE_UP_HOVERED = "minecraft:gui/widgets/keyboard/key_backspace/up_hovered";
+        public static final String DEFAULT_TEXTURE_DOWN_HOVERED = "minecraft:gui/widgets/keyboard/key_backspace/down_hovered";
+
+        @Override
+        public BackspaceKey deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
+            JsonObject obj = json.getAsJsonObject();
+            int x = obj.get("x").getAsInt();
+            int y = obj.get("y").getAsInt();
+            int width = obj.has("width") ? obj.get("width").getAsInt() : DEFAULT_WIDTH;
+            int height = obj.has("height") ? obj.get("height").getAsInt() : DEFAULT_HEIGHT;
+            ButtonEnum button = obj.has("button") ? Key.ButtonEnum.valueOf(obj.get("button").getAsString().toUpperCase(Locale.ROOT)) : null;
+
+            String texUp = DEFAULT_TEXTURE_UP;
+            String texDown = DEFAULT_TEXTURE_DOWN;
+            String texUpHovered = DEFAULT_TEXTURE_UP_HOVERED;
+            String texDownHovered = DEFAULT_TEXTURE_DOWN_HOVERED;
+
+            if (obj.has("textures")) {
+                JsonObject textures = obj.getAsJsonObject("textures");
+                if (textures.has("up")) texUp = textures.get("up").getAsString();
+                if (textures.has("down")) texDown = textures.get("down").getAsString();
+                if (textures.has("up_hovered")) texUpHovered = textures.get("up_hovered").getAsString();
+                if (textures.has("down_hovered")) texDownHovered = textures.get("down_hovered").getAsString();
+            }
+            BackspaceKey key = new BackspaceKey(x, y, width, height);
+            key.setTextures(texUp, texDown, texUpHovered, texDownHovered);
+            key.setControllerButton(button);
+            return key;
+        }
+
+        @Override
+        public JsonElement serialize(BackspaceKey src, Type typeOfSrc, JsonSerializationContext context) {
+            JsonObject object = new JsonObject();
+            object.addProperty("type", Key.Serializer.KEY_CLASS_TO_ID.get(src.getClass()));
+
+            object.addProperty("x", src.x);
+            object.addProperty("y", src.y);
+            if (src.width != DEFAULT_WIDTH) object.addProperty("width", src.width);
+            if (src.height != DEFAULT_HEIGHT) object.addProperty("height", src.height);
+            if (src.controllerButton != null) object.addProperty("button", src.controllerButton.name().toLowerCase(Locale.ROOT));
+
+            boolean ku = !src.keyUp.sourceNamespaceId.toString().equals(DEFAULT_TEXTURE_UP);
+            boolean kd = !src.keyDown.sourceNamespaceId.toString().equals(DEFAULT_TEXTURE_DOWN);
+            boolean kuh = !src.keyUpHovered.sourceNamespaceId.toString().equals(DEFAULT_TEXTURE_UP_HOVERED);
+            boolean kdh = !src.keyDownHovered.sourceNamespaceId.toString().equals(DEFAULT_TEXTURE_DOWN_HOVERED);
+            if (ku || kd || kuh || kdh) {
+                JsonObject textureObject = new JsonObject();
+                if (ku) textureObject.addProperty("up", src.keyUp.sourceNamespaceId.toString());
+                if (kd) textureObject.addProperty("down", src.keyDown.sourceNamespaceId.toString());
+                if (kuh) textureObject.addProperty("up_hovered", src.keyUpHovered.sourceNamespaceId.toString());
+                if (kdh) textureObject.addProperty("down_hovered", src.keyDownHovered.sourceNamespaceId.toString());
+                object.add("textures", textureObject);
+            }
+
+            return object;
+        }
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/gui/keyboard/keys/CharKey.java b/game/client/src/main/java/net/minecraft/client/gui/keyboard/keys/CharKey.java
new file mode 100644
index 000000000..c6d8c92c1
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/gui/keyboard/keys/CharKey.java
@@ -0,0 +1,389 @@
+package net.minecraft.client.gui.keyboard.keys;
+
+import com.b100.utils.StringUtils;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.keyboard.ScreenKeyboard;
+import net.minecraft.client.input.InputType;
+import net.minecraft.client.render.texture.stitcher.TextureRegistry;
+import net.minecraft.core.util.helper.MathHelper;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.lwjgl.input.Keyboard;
+import org.lwjgl.opengl.GL11;
+
+import java.lang.reflect.Type;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+public class CharKey extends Key{
+    private static int EXTRAS_HOLD_LENGTH = 300;
+    protected boolean renderChar = true;
+    protected int lastRadial = 0;
+    protected final CharGroup @NotNull [] groups;
+
+    protected CharKey(int x, int y, int width, int height, boolean renderChar, CharGroup @NotNull [] groups) {
+        super(x, y, width, height);
+        this.renderChar = renderChar;
+        this.groups = groups;
+    }
+
+    @Override
+    public void render(Minecraft mc, ScreenKeyboard keyboard, int originX, int originY, int mouseX, int mouseY) {
+        super.render(mc, keyboard, originX, originY, mouseX, mouseY);
+        if (!renderChar) return;
+        boolean hovered = isHovered(originX, originY, mouseX, mouseY);
+        int texColor = hovered ? 0xFFFFA0 : 0xE0E0E0;
+
+        CharGroup currentGroup = getGroup(keyboard.getGroup());
+        {
+            CharGroup nextGroup = getGroup(keyboard.getShiftedGroup(1));
+            CharGroup lastGroup = getGroup(keyboard.getShiftedGroup(-1));
+
+            if (lastGroup != currentGroup && lastGroup != nextGroup) {
+                GL11.glPushMatrix();
+                GL11.glColor4f(0.75f, 0.75f, 0.75f, 1f);
+                GL11.glTranslatef(x + originX + 4, y + originY + 2, 0);
+                GL11.glScalef(0.75f, 0.75f, 1);
+                drawStringCentered(mc.font, String.valueOf(keyboard.isModifierActive(ModifierKey.ID_MODIFIER_SHIFT) ? lastGroup.collections[0].upper : lastGroup.collections[0].lower), 0, 0, 0xE0E0E0);
+                GL11.glPopMatrix();
+            }
+
+            if (nextGroup != currentGroup) {
+                GL11.glPushMatrix();
+                GL11.glColor4f(0.75f, 0.75f, 0.75f, 1f);
+                GL11.glTranslatef(x + width + originX - 4, y + originY + 2, 0);
+                GL11.glScalef(0.75f, 0.75f, 1);
+                drawStringCentered(mc.font, String.valueOf(keyboard.isModifierActive(ModifierKey.ID_MODIFIER_SHIFT) ? nextGroup.collections[0].upper : nextGroup.collections[0].lower), 0, 0, 0xE0E0E0);
+                GL11.glPopMatrix();
+            }
+        }
+//        if (isHovered) {
+//            drawStringCentered(mc.font, String.valueOf(getHoldTime()), x + width/2 + originX, y + mc.font.fontHeight/2 + 4 + originY - 10, texColor);
+//        }
+        if (getHoldTime() > EXTRAS_HOLD_LENGTH) {
+            double animProgress = MathHelper.clamp((getHoldTime() - EXTRAS_HOLD_LENGTH)/100d, 0, 1);
+            GL11.glPushMatrix();
+            GL11.glEnable(GL11.GL_BLEND);
+            GL11.glEnable(GL11.GL_DEPTH_TEST);
+            GL11.glTranslatef(0, 0, 10);
+
+            final int size = (int) (64 * animProgress);
+            drawTexturedIcon(
+                x + (width - size)/2 + originX,
+                y + (height - size)/2  + originY + 2,
+                size,
+                size,
+                TextureRegistry.getTexture("minecraft:gui/screen/keyboard/radial_background"));
+
+            drawStringCentered(mc.font, String.valueOf(getActiveChar(mc, keyboard, originX, originY, mouseX, mouseY)), x + width/2 + originX, y + mc.font.fontHeight/2 + 4 + originY, texColor);
+
+            final double dx;
+            final double dy;
+            if (mc.inputType == InputType.CONTROLLER && mc.controllerInput != null) {
+                dx = mc.controllerInput.joyLeft.getX() * 20;
+                dy = mc.controllerInput.joyLeft.getY() * 20;
+            } else {
+                dx = mouseX - (x + width/2d + originX);
+                dy = mouseY - (y + height/2d + originY);
+            }
+
+            int rIndex = getRadialIndex(mc, keyboard, dx, dy);
+
+            double r = (Math.PI * 2) / currentGroup.collections.length;
+            for (int i = 0; i < currentGroup.collections.length; i++) {
+                CharCollection collection = currentGroup.collections[i];
+
+                int charX = MathHelper.floor(x + originX + width/2d + Math.cos(r * i - Math.PI/2) * 20 * animProgress);
+                int charY = MathHelper.floor(y + originY + height/2d + Math.sin(r * i - Math.PI/2) * 20 * animProgress) - 3;
+
+                drawStringCentered(
+                    mc.font,
+                    String.valueOf(keyboard.isModifierActive(ModifierKey.ID_MODIFIER_SHIFT) ? collection.upper : collection.lower),
+                    charX,
+                    charY,
+                    i == rIndex ? 0xFFFFA0 : 0xE0E0E0);
+            }
+            GL11.glDisable(GL11.GL_BLEND);
+            GL11.glPopMatrix();
+        } else {
+            drawStringCentered(mc.font, String.valueOf(getActiveChar(mc, keyboard, originX, originY, mouseX, mouseY)), x + width/2 + originX, y + mc.font.fontHeight/2 + 4 + originY, texColor);
+        }
+        GL11.glColor4f(1f, 1f, 1f, 1f);
+    }
+
+    protected int getRadialIndex(Minecraft mc, ScreenKeyboard keyboard, double dx, double dy) {
+        if (getHoldTime() < EXTRAS_HOLD_LENGTH) {
+            lastRadial = 0;
+            return 0;
+        }
+
+        final int deadzone = 7;
+        if (Math.hypot(dx, dy) < deadzone) {
+            return lastRadial;
+        }
+
+        CharGroup currentGroup = getGroup(keyboard.getGroup());
+        double r = (Math.PI * 2) / currentGroup.collections.length;
+
+        double mouseR = Math.atan2(dy, dx) + Math.PI/2 + r/2;
+        if (mouseR < 0) mouseR += Math.PI * 2;
+
+        int radial = MathHelper.floor(mouseR / r) % currentGroup.collections.length;
+        lastRadial = radial;
+        return radial;
+    }
+
+    @Override
+    public void pressAction(Minecraft mc, ScreenKeyboard keyboard, int originX, int originY, int mouseX, int mouseY) {
+        super.pressAction(mc, keyboard, originX, originY, mouseX, mouseY);
+    }
+
+    @Override
+    public void releaseAction(Minecraft mc, ScreenKeyboard keyboard, int originX, int originY, int mouseX, int mouseY) {
+        super.releaseAction(mc, keyboard, originX, originY, mouseX, mouseY);
+        keyboard.typeChar(Keyboard.KEY_NONE, getActiveChar(mc, keyboard, originX, originY, mouseX, mouseY));
+    }
+
+    protected char getActiveChar(Minecraft mc, ScreenKeyboard keyboard, int originX, int originY, int mouseX, int mouseY) {
+        CharGroup group = getGroup(keyboard.getGroup());
+
+        final double dx;
+        final double dy;
+        if (mc.inputType == InputType.CONTROLLER && mc.controllerInput != null) {
+            dx = mc.controllerInput.joyLeft.getX() * 20;
+            dy = mc.controllerInput.joyLeft.getY() * 20;
+        } else {
+            dx = mouseX - (x + width/2d + originX);
+            dy = mouseY - (y + height/2d + originY);
+        }
+
+        int rI = getRadialIndex(mc, keyboard, dx, dy);
+        return keyboard.isModifierActive(ModifierKey.ID_MODIFIER_SHIFT) ? group.collections[rI].upper : group.collections[rI].lower;
+    }
+
+    protected CharGroup getGroup(int group) {
+        return group < groups.length ? groups[group] : groups[0];
+    }
+
+    @Override
+    public boolean isDown(Minecraft mc, ScreenKeyboard keyboard) {
+        return super.isDown(mc, keyboard);
+    }
+
+    @Override
+    public int highestGroup() {
+        return groups.length - 1;
+    }
+
+    protected static class CharGroup {
+        protected final CharCollection @NotNull [] collections;
+
+        public CharGroup(CharCollection @NotNull [] collections) {
+            this.collections = collections;
+        }
+    }
+
+    protected static class CharCollection {
+        public char lower;
+        public char upper;
+        public CharCollection(char lower, char upper) {
+            this.lower = lower;
+            this.upper = upper;
+        }
+    }
+
+    public static class Serializer implements JsonSerializer<CharKey>, JsonDeserializer<CharKey> {
+        public static final int DEFAULT_WIDTH = 18;
+        public static final int DEFAULT_HEIGHT = 18;
+
+        public static final boolean DEFAULT_RENDER_CHAR = true;
+
+        public static final String DEFAULT_TEXTURE_UP = "minecraft:gui/widgets/keyboard/key/up";
+        public static final String DEFAULT_TEXTURE_DOWN = "minecraft:gui/widgets/keyboard/key/down";
+        public static final String DEFAULT_TEXTURE_UP_HOVERED = "minecraft:gui/widgets/keyboard/key/up_hovered";
+        public static final String DEFAULT_TEXTURE_DOWN_HOVERED = "minecraft:gui/widgets/keyboard/key/down_hovered";
+
+        @Override
+        public CharKey deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
+            JsonObject obj = json.getAsJsonObject();
+            CharKeyBuilder charKeyBuilder = new CharKeyBuilder(obj.get("x").getAsInt(), obj.get("y").getAsInt());
+            charKeyBuilder.setWidth(obj.has("width") ? obj.get("width").getAsInt() : DEFAULT_WIDTH);
+            charKeyBuilder.setHeight(obj.has("height") ? obj.get("height").getAsInt() : DEFAULT_HEIGHT);
+            charKeyBuilder.setButton(obj.has("button") ? Key.ButtonEnum.valueOf(obj.get("button").getAsString().toUpperCase(Locale.ROOT)) : null);
+            charKeyBuilder.setRenderChar(obj.has("renderChar") ? obj.get("renderChar").getAsBoolean() : DEFAULT_RENDER_CHAR);
+
+            String texUp = DEFAULT_TEXTURE_UP;
+            String texDown = DEFAULT_TEXTURE_DOWN;
+            String texUpHovered = DEFAULT_TEXTURE_UP_HOVERED;
+            String texDownHovered = DEFAULT_TEXTURE_DOWN_HOVERED;
+
+            if (obj.has("textures")) {
+                JsonObject textures = obj.getAsJsonObject("textures");
+                if (textures.has("up")) texUp = textures.get("up").getAsString();
+                if (textures.has("down")) texDown = textures.get("down").getAsString();
+                if (textures.has("up_hovered")) texUpHovered = textures.get("up_hovered").getAsString();
+                if (textures.has("down_hovered")) texDownHovered = textures.get("down_hovered").getAsString();
+            }
+            charKeyBuilder.setTextures(
+                texUp,
+                texDown,
+                texUpHovered,
+                texDownHovered);
+
+            JsonArray groups = obj.getAsJsonArray("groups");
+            for (JsonElement e : groups) {
+                JsonArray collections = e.getAsJsonArray();
+                CharCollection[] charCollections = new CharCollection[collections.size()];
+                int i = 0;
+                for (JsonElement r : collections) {
+                    JsonObject collection = r.getAsJsonObject();
+                    char lower = (char) collection.get("lower").getAsInt();
+                    char upper = collection.has("upper") ? (char) collection.get("upper").getAsInt() : Character.toUpperCase(lower);
+                    charCollections[i++] = new CharCollection(lower, upper);
+                }
+                charKeyBuilder.addGroup(new CharGroup(charCollections));
+            }
+
+            return charKeyBuilder.build();
+        }
+
+        @Override
+        public JsonElement serialize(CharKey src, Type typeOfSrc, JsonSerializationContext context) {
+            JsonObject object = new JsonObject();
+            object.addProperty("type", Key.Serializer.KEY_CLASS_TO_ID.get(src.getClass()));
+
+            object.addProperty("x", src.x);
+            object.addProperty("y", src.y);
+            if (src.width != DEFAULT_WIDTH) object.addProperty("width", src.width);
+            if (src.height != DEFAULT_HEIGHT) object.addProperty("height", src.height);
+            if (src.controllerButton != null) object.addProperty("button", src.controllerButton.name().toLowerCase(Locale.ROOT));
+            if (src.renderChar != DEFAULT_RENDER_CHAR) object.addProperty("renderChar", src.renderChar);
+
+            boolean ku = !src.keyUp.sourceNamespaceId.toString().equals(DEFAULT_TEXTURE_UP);
+            boolean kd = !src.keyDown.sourceNamespaceId.toString().equals(DEFAULT_TEXTURE_DOWN);
+            boolean kuh = !src.keyUpHovered.sourceNamespaceId.toString().equals(DEFAULT_TEXTURE_UP_HOVERED);
+            boolean kdh = !src.keyDownHovered.sourceNamespaceId.toString().equals(DEFAULT_TEXTURE_DOWN_HOVERED);
+            if (ku || kd || kuh || kdh) {
+                JsonObject textureObject = new JsonObject();
+                if (ku) textureObject.addProperty("up", src.keyUp.sourceNamespaceId.toString());
+                if (kd) textureObject.addProperty("down", src.keyDown.sourceNamespaceId.toString());
+                if (kuh) textureObject.addProperty("up_hovered", src.keyUpHovered.sourceNamespaceId.toString());
+                if (kdh) textureObject.addProperty("down_hovered", src.keyDownHovered.sourceNamespaceId.toString());
+                object.add("textures", textureObject);
+            }
+
+            JsonArray groups = new JsonArray(src.groups.length);
+            for (CharGroup group : src.groups) {
+                JsonArray collections = new JsonArray(group.collections.length);
+                for (CharCollection collection : group.collections) {
+                    JsonObject o = new JsonObject();
+                    o.addProperty("lower", (int)collection.lower);
+                    o.addProperty("upper", (int)collection.upper);
+                    collections.add(o);
+                }
+                groups.add(collections);
+            }
+            object.add("groups", groups);
+            return object;
+        }
+    }
+
+    public static class CharKeyBuilder {
+        private final int x;
+        private final int y;
+        private int width = Serializer.DEFAULT_WIDTH;
+        private int height = Serializer.DEFAULT_HEIGHT;
+
+        private ButtonEnum controllerButton = null;
+        private boolean renderChar = Serializer.DEFAULT_RENDER_CHAR;
+
+        private String texUp = Serializer.DEFAULT_TEXTURE_UP;
+        private String texDown = Serializer.DEFAULT_TEXTURE_DOWN;
+        private String texUpHovered = Serializer.DEFAULT_TEXTURE_UP_HOVERED;
+        private String texDownHovered = Serializer.DEFAULT_TEXTURE_DOWN_HOVERED;
+
+        private final List<CharGroup> groups = new ArrayList<>();
+
+        public CharKeyBuilder(int x, int y) {
+            this.x = x;
+            this.y = y;
+        }
+
+        public CharKeyBuilder setWidth(int width) {
+            this.width = width;
+            return this;
+        }
+
+        public CharKeyBuilder setHeight(int height) {
+            this.height = height;
+            return this;
+        }
+
+        public CharKeyBuilder setButton(@Nullable ButtonEnum button) {
+            this.controllerButton = button;
+            return this;
+        }
+
+        public CharKeyBuilder setRenderChar(boolean renderChar) {
+            this.renderChar = renderChar;
+            return this;
+        }
+
+        public CharKeyBuilder setTextures(String keyUp, String keyDown, String keyUpHovered, String keyDownHovered) {
+            this.texUp = keyUp;
+            this.texDown = keyDown;
+            this.texUpHovered = keyUpHovered;
+            this.texDownHovered = keyDownHovered;
+            return this;
+        }
+
+        public CharKeyBuilder addGroup(char c) {
+            CharCollection collection = new CharCollection(c, Character.toUpperCase(c));
+            groups.add(new CharGroup(new CharCollection[]{collection}));
+            return this;
+        }
+
+        public CharKeyBuilder addGroup(char lower, char upper) {
+            CharCollection collection = new CharCollection(lower, upper);
+            groups.add(new CharGroup(new CharCollection[]{collection}));
+            return this;
+        }
+
+        public CharKeyBuilder addGroup(char[] ... group) {
+            CharCollection[] collections = new CharCollection[group.length];
+            for (int i = 0; i < group.length; i++) {
+                char[] arr = group[i];
+                if (arr.length > 1) {
+                    collections[i] = new CharCollection(arr[0], arr[1]);
+                } else {
+                    collections[i] = new CharCollection(arr[0], Character.toUpperCase(arr[0]));
+                }
+            }
+            groups.add(new CharGroup(collections));
+            return this;
+        }
+
+        protected CharKeyBuilder addGroup(CharGroup group) {
+            groups.add(group);
+            return this;
+        }
+
+        public CharKey build() {
+            CharGroup[] compiledGroups = groups.toArray(new CharGroup[0]);
+            CharKey key = new CharKey(x, y, width, height, renderChar, compiledGroups);
+            key.setControllerButton(controllerButton).setTextures(texUp, texDown, texUpHovered, texDownHovered);
+            return key;
+        }
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/gui/keyboard/keys/CursorKey.java b/game/client/src/main/java/net/minecraft/client/gui/keyboard/keys/CursorKey.java
new file mode 100644
index 000000000..7a52a755a
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/gui/keyboard/keys/CursorKey.java
@@ -0,0 +1,100 @@
+package net.minecraft.client.gui.keyboard.keys;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.keyboard.ScreenKeyboard;
+
+import java.lang.reflect.Type;
+import java.util.Locale;
+
+public class CursorKey extends Key{
+    protected final int cursorOffset;
+    public CursorKey(int x, int y, int width, int height, int cursorOffset) {
+        super(x, y, width, height);
+        this.cursorOffset = cursorOffset;
+    }
+
+    @Override
+    public void pressAction(Minecraft mc, ScreenKeyboard keyboard, int originX, int originY, int mouseX, int mouseY) {
+        super.pressAction(mc, keyboard, originX, originY, mouseX, mouseY);
+        keyboard.editor.moveCursor(cursorOffset);
+    }
+
+    public static class Serializer implements JsonSerializer<CursorKey>, JsonDeserializer<CursorKey> {
+        public static final int DEFAULT_WIDTH = 25;
+        public static final int DEFAULT_HEIGHT = 18;
+
+        public static final String DEFAULT_TEXTURE_NEXT_UP = "minecraft:gui/widgets/keyboard/key_page_next/up";
+        public static final String DEFAULT_TEXTURE_NEXT_DOWN = "minecraft:gui/widgets/keyboard/key_page_next/down";
+        public static final String DEFAULT_TEXTURE_NEXT_UP_HOVERED = "minecraft:gui/widgets/keyboard/key_page_next/up_hovered";
+        public static final String DEFAULT_TEXTURE_NEXT_DOWN_HOVERED = "minecraft:gui/widgets/keyboard/key_page_next/down_hovered";
+
+        public static final String DEFAULT_TEXTURE_LAST_UP = "minecraft:gui/widgets/keyboard/key_page_last/up";
+        public static final String DEFAULT_TEXTURE_LAST_DOWN = "minecraft:gui/widgets/keyboard/key_page_last/down";
+        public static final String DEFAULT_TEXTURE_LAST_UP_HOVERED = "minecraft:gui/widgets/keyboard/key_page_last/up_hovered";
+        public static final String DEFAULT_TEXTURE_LAST_DOWN_HOVERED = "minecraft:gui/widgets/keyboard/key_page_last/down_hovered";
+
+        @Override
+        public CursorKey deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
+            JsonObject obj = json.getAsJsonObject();
+            int x = obj.get("x").getAsInt();
+            int y = obj.get("y").getAsInt();
+            int width = obj.has("width") ? obj.get("width").getAsInt() : DEFAULT_WIDTH;
+            int height = obj.has("height") ? obj.get("height").getAsInt() : DEFAULT_HEIGHT;
+            ButtonEnum button = obj.has("button") ? Key.ButtonEnum.valueOf(obj.get("button").getAsString().toUpperCase(Locale.ROOT)) : null;
+
+            int amount = obj.get("offset").getAsInt();
+
+            String texUp = amount >= 0 ? DEFAULT_TEXTURE_NEXT_UP : DEFAULT_TEXTURE_LAST_UP;
+            String texDown = amount >= 0 ? DEFAULT_TEXTURE_NEXT_DOWN : DEFAULT_TEXTURE_LAST_DOWN;
+            String texUpHovered = amount >= 0 ? DEFAULT_TEXTURE_NEXT_UP_HOVERED : DEFAULT_TEXTURE_LAST_UP_HOVERED;
+            String texDownHovered = amount >= 0 ? DEFAULT_TEXTURE_NEXT_DOWN_HOVERED : DEFAULT_TEXTURE_LAST_DOWN_HOVERED;
+
+            if (obj.has("textures")) {
+                JsonObject textures = obj.getAsJsonObject("textures");
+                if (textures.has("up")) texUp = textures.get("up").getAsString();
+                if (textures.has("down")) texDown = textures.get("down").getAsString();
+                if (textures.has("up_hovered")) texUpHovered = textures.get("up_hovered").getAsString();
+                if (textures.has("down_hovered")) texDownHovered = textures.get("down_hovered").getAsString();
+            }
+            CursorKey key = new CursorKey(x, y, width, height, amount);
+            key.setTextures(texUp, texDown, texUpHovered, texDownHovered);
+            key.setControllerButton(button);
+            return key;
+        }
+
+        @Override
+        public JsonElement serialize(CursorKey src, Type typeOfSrc, JsonSerializationContext context) {
+            JsonObject object = new JsonObject();
+            object.addProperty("type", Key.Serializer.KEY_CLASS_TO_ID.get(src.getClass()));
+
+            object.addProperty("x", src.x);
+            object.addProperty("y", src.y);
+            if (src.width != DEFAULT_WIDTH) object.addProperty("width", src.width);
+            if (src.height != DEFAULT_HEIGHT) object.addProperty("height", src.height);
+            if (src.controllerButton != null) object.addProperty("button", src.controllerButton.name().toLowerCase(Locale.ROOT));
+
+            boolean ku = true;
+            boolean kd = true;
+            boolean kuh = true;
+            boolean kdh = true;
+            if (ku || kd || kuh || kdh) {
+                JsonObject textureObject = new JsonObject();
+                if (ku) textureObject.addProperty("up", src.keyUp.sourceNamespaceId.toString());
+                if (kd) textureObject.addProperty("down", src.keyDown.sourceNamespaceId.toString());
+                if (kuh) textureObject.addProperty("up_hovered", src.keyUpHovered.sourceNamespaceId.toString());
+                if (kdh) textureObject.addProperty("down_hovered", src.keyDownHovered.sourceNamespaceId.toString());
+                object.add("textures", textureObject);
+            }
+
+            object.addProperty("offset", src.cursorOffset);
+            return object;
+        }
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/gui/keyboard/keys/EnterKey.java b/game/client/src/main/java/net/minecraft/client/gui/keyboard/keys/EnterKey.java
new file mode 100644
index 000000000..db15ae8e6
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/gui/keyboard/keys/EnterKey.java
@@ -0,0 +1,96 @@
+package net.minecraft.client.gui.keyboard.keys;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.keyboard.ScreenKeyboard;
+
+import java.lang.reflect.Type;
+import java.util.Locale;
+
+public class EnterKey extends Key{
+
+    public EnterKey(int x, int y, int width, int height) {
+        super(x, y, width, height);
+        setTextures(
+            "minecraft:gui/widgets/keyboard/key_enter/up",
+            "minecraft:gui/widgets/keyboard/key_enter/down",
+            "minecraft:gui/widgets/keyboard/key_enter/up_hovered",
+            "minecraft:gui/widgets/keyboard/key_enter/down_hovered");
+    }
+
+    @Override
+    public void pressAction(Minecraft mc, ScreenKeyboard keyboard, int originX, int originY, int mouseX, int mouseY) {
+        super.pressAction(mc, keyboard, originX, originY, mouseX, mouseY);
+        keyboard.close();
+    }
+
+    public static class Serializer implements JsonSerializer<EnterKey>, JsonDeserializer<EnterKey> {
+        public static final int DEFAULT_WIDTH = 25;
+        public static final int DEFAULT_HEIGHT = 18;
+
+        public static final String DEFAULT_TEXTURE_UP = "minecraft:gui/widgets/keyboard/key_enter/up";
+        public static final String DEFAULT_TEXTURE_DOWN = "minecraft:gui/widgets/keyboard/key_enter/down";
+        public static final String DEFAULT_TEXTURE_UP_HOVERED = "minecraft:gui/widgets/keyboard/key_enter/up_hovered";
+        public static final String DEFAULT_TEXTURE_DOWN_HOVERED = "minecraft:gui/widgets/keyboard/key_enter/down_hovered";
+
+        @Override
+        public EnterKey deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
+            JsonObject obj = json.getAsJsonObject();
+            int x = obj.get("x").getAsInt();
+            int y = obj.get("y").getAsInt();
+            int width = obj.has("width") ? obj.get("width").getAsInt() : DEFAULT_WIDTH;
+            int height = obj.has("height") ? obj.get("height").getAsInt() : DEFAULT_HEIGHT;
+            ButtonEnum button = obj.has("button") ? Key.ButtonEnum.valueOf(obj.get("button").getAsString().toUpperCase(Locale.ROOT)) : null;
+
+            String texUp = DEFAULT_TEXTURE_UP;
+            String texDown = DEFAULT_TEXTURE_DOWN;
+            String texUpHovered = DEFAULT_TEXTURE_UP_HOVERED;
+            String texDownHovered = DEFAULT_TEXTURE_DOWN_HOVERED;
+
+            if (obj.has("textures")) {
+                JsonObject textures = obj.getAsJsonObject("textures");
+                if (textures.has("up")) texUp = textures.get("up").getAsString();
+                if (textures.has("down")) texDown = textures.get("down").getAsString();
+                if (textures.has("up_hovered")) texUpHovered = textures.get("up_hovered").getAsString();
+                if (textures.has("down_hovered")) texDownHovered = textures.get("down_hovered").getAsString();
+            }
+            EnterKey key = new EnterKey(x, y, width, height);
+            key.setTextures(texUp, texDown, texUpHovered, texDownHovered);
+            key.setControllerButton(button);
+            return key;
+        }
+
+        @Override
+        public JsonElement serialize(EnterKey src, Type typeOfSrc, JsonSerializationContext context) {
+            JsonObject object = new JsonObject();
+            object.addProperty("type", Key.Serializer.KEY_CLASS_TO_ID.get(src.getClass()));
+
+            object.addProperty("x", src.x);
+            object.addProperty("y", src.y);
+            if (src.width != DEFAULT_WIDTH) object.addProperty("width", src.width);
+            if (src.height != DEFAULT_HEIGHT) object.addProperty("height", src.height);
+            if (src.controllerButton != null) object.addProperty("button", src.controllerButton.name().toLowerCase(Locale.ROOT));
+
+            boolean ku = !src.keyUp.sourceNamespaceId.toString().equals(DEFAULT_TEXTURE_UP);
+            boolean kd = !src.keyDown.sourceNamespaceId.toString().equals(DEFAULT_TEXTURE_DOWN);
+            boolean kuh = !src.keyUpHovered.sourceNamespaceId.toString().equals(DEFAULT_TEXTURE_UP_HOVERED);
+            boolean kdh = !src.keyDownHovered.sourceNamespaceId.toString().equals(DEFAULT_TEXTURE_DOWN_HOVERED);
+            if (ku || kd || kuh || kdh) {
+                JsonObject textureObject = new JsonObject();
+                if (ku) textureObject.addProperty("up", src.keyUp.sourceNamespaceId.toString());
+                if (kd) textureObject.addProperty("down", src.keyDown.sourceNamespaceId.toString());
+                if (kuh) textureObject.addProperty("up_hovered", src.keyUpHovered.sourceNamespaceId.toString());
+                if (kdh) textureObject.addProperty("down_hovered", src.keyDownHovered.sourceNamespaceId.toString());
+                object.add("textures", textureObject);
+            }
+
+            return object;
+        }
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/gui/keyboard/keys/GroupKey.java b/game/client/src/main/java/net/minecraft/client/gui/keyboard/keys/GroupKey.java
new file mode 100644
index 000000000..982f43015
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/gui/keyboard/keys/GroupKey.java
@@ -0,0 +1,153 @@
+package net.minecraft.client.gui.keyboard.keys;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.keyboard.ScreenKeyboard;
+
+import java.lang.reflect.Type;
+import java.util.Locale;
+
+public class GroupKey extends Key{
+    protected final Mode mode;
+    protected final int amount;
+    protected final int amount2;
+    public GroupKey(int x, int y, int width, int height, Mode mode, int amount, int amount2) {
+        super(x, y, width, height);
+        this.mode = mode;
+        this.amount = amount;
+        this.amount2 = amount2;
+    }
+
+    @Override
+    public void pressAction(Minecraft mc, ScreenKeyboard keyboard, int originX, int originY, int mouseX, int mouseY) {
+        super.pressAction(mc, keyboard, originX, originY, mouseX, mouseY);
+        switch (mode) {
+            case OFFSET:
+                keyboard.shiftGroup(amount);
+                break;
+            case SET:
+                keyboard.setGroup(amount);
+                break;
+            case TOGGLE:
+                if (keyboard.getGroup() == amount) {
+                    keyboard.setGroup(amount2);
+                } else {
+                    keyboard.setGroup(amount);
+                }
+        }
+    }
+
+    public enum Mode {
+        SET,
+        OFFSET,
+        TOGGLE;
+    }
+
+    public static class Serializer implements JsonSerializer<GroupKey>, JsonDeserializer<GroupKey> {
+        public static final int DEFAULT_WIDTH = 18;
+        public static final int DEFAULT_HEIGHT = 18;
+
+        public static final String DEFAULT_TEXTURE_NEXT_UP = "minecraft:gui/widgets/keyboard/key_page_next/up";
+        public static final String DEFAULT_TEXTURE_NEXT_DOWN = "minecraft:gui/widgets/keyboard/key_page_next/down";
+        public static final String DEFAULT_TEXTURE_NEXT_UP_HOVERED = "minecraft:gui/widgets/keyboard/key_page_next/up_hovered";
+        public static final String DEFAULT_TEXTURE_NEXT_DOWN_HOVERED = "minecraft:gui/widgets/keyboard/key_page_next/down_hovered";
+
+        public static final String DEFAULT_TEXTURE_LAST_UP = "minecraft:gui/widgets/keyboard/key_page_last/up";
+        public static final String DEFAULT_TEXTURE_LAST_DOWN = "minecraft:gui/widgets/keyboard/key_page_last/down";
+        public static final String DEFAULT_TEXTURE_LAST_UP_HOVERED = "minecraft:gui/widgets/keyboard/key_page_last/up_hovered";
+        public static final String DEFAULT_TEXTURE_LAST_DOWN_HOVERED = "minecraft:gui/widgets/keyboard/key_page_last/down_hovered";
+
+        public static final Mode DEFAULT_MODE = Mode.OFFSET;
+
+        @Override
+        public GroupKey deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
+            JsonObject obj = json.getAsJsonObject();
+            int x = obj.get("x").getAsInt();
+            int y = obj.get("y").getAsInt();
+            int width = obj.has("width") ? obj.get("width").getAsInt() : DEFAULT_WIDTH;
+            int height = obj.has("height") ? obj.get("height").getAsInt() : DEFAULT_HEIGHT;
+            ButtonEnum button = obj.has("button") ? Key.ButtonEnum.valueOf(obj.get("button").getAsString().toUpperCase(Locale.ROOT)) : null;
+
+            Mode mode = obj.has("mode") ? Mode.valueOf(obj.get("mode").getAsString().toUpperCase(Locale.ROOT)) : DEFAULT_MODE;
+
+            int lowest;
+            int highest;
+
+            switch (mode) {
+                case TOGGLE:
+                    lowest = obj.get("page1").getAsInt();
+                    highest = obj.get("page2").getAsInt();
+                    break;
+                case SET:
+                case OFFSET:
+                default:
+                    highest = lowest = obj.get("amount").getAsInt();;
+                    break;
+            }
+
+            String texUp = lowest >= 0 ? DEFAULT_TEXTURE_NEXT_UP : DEFAULT_TEXTURE_LAST_UP;
+            String texDown = lowest >= 0 ? DEFAULT_TEXTURE_NEXT_DOWN : DEFAULT_TEXTURE_LAST_DOWN;
+            String texUpHovered = lowest >= 0 ? DEFAULT_TEXTURE_NEXT_UP_HOVERED : DEFAULT_TEXTURE_LAST_UP_HOVERED;
+            String texDownHovered = lowest >= 0 ? DEFAULT_TEXTURE_NEXT_DOWN_HOVERED : DEFAULT_TEXTURE_LAST_DOWN_HOVERED;
+
+            if (obj.has("textures")) {
+                JsonObject textures = obj.getAsJsonObject("textures");
+                if (textures.has("up")) texUp = textures.get("up").getAsString();
+                if (textures.has("down")) texDown = textures.get("down").getAsString();
+                if (textures.has("up_hovered")) texUpHovered = textures.get("up_hovered").getAsString();
+                if (textures.has("down_hovered")) texDownHovered = textures.get("down_hovered").getAsString();
+            }
+            GroupKey key = new GroupKey(x, y, width, height, mode, lowest, highest);
+            key.setTextures(texUp, texDown, texUpHovered, texDownHovered);
+            key.setControllerButton(button);
+            return key;
+        }
+
+        @Override
+        public JsonElement serialize(GroupKey src, Type typeOfSrc, JsonSerializationContext context) {
+            JsonObject object = new JsonObject();
+            object.addProperty("type", Key.Serializer.KEY_CLASS_TO_ID.get(src.getClass()));
+
+            object.addProperty("x", src.x);
+            object.addProperty("y", src.y);
+            if (src.width != DEFAULT_WIDTH) object.addProperty("width", src.width);
+            if (src.height != DEFAULT_HEIGHT) object.addProperty("height", src.height);
+            if (src.controllerButton != null) object.addProperty("button", src.controllerButton.name().toLowerCase(Locale.ROOT));
+
+            boolean ku = true;
+            boolean kd = true;
+            boolean kuh = true;
+            boolean kdh = true;
+            if (ku || kd || kuh || kdh) {
+                JsonObject textureObject = new JsonObject();
+                if (ku) textureObject.addProperty("up", src.keyUp.sourceNamespaceId.toString());
+                if (kd) textureObject.addProperty("down", src.keyDown.sourceNamespaceId.toString());
+                if (kuh) textureObject.addProperty("up_hovered", src.keyUpHovered.sourceNamespaceId.toString());
+                if (kdh) textureObject.addProperty("down_hovered", src.keyDownHovered.sourceNamespaceId.toString());
+                object.add("textures", textureObject);
+            }
+
+            object.addProperty("mode", src.mode.name().toLowerCase(Locale.ROOT));
+
+            switch (src.mode) {
+                case TOGGLE:
+                    object.addProperty("page1", src.amount);
+                    object.addProperty("page2", src.amount2);
+                    break;
+                case SET:
+                case OFFSET:
+                default:
+                    object.addProperty("amount", src.amount);
+                    break;
+            }
+
+            return object;
+        }
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/gui/keyboard/keys/Key.java b/game/client/src/main/java/net/minecraft/client/gui/keyboard/keys/Key.java
new file mode 100644
index 000000000..671b6cca5
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/gui/keyboard/keys/Key.java
@@ -0,0 +1,290 @@
+package net.minecraft.client.gui.keyboard.keys;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.ControllerGlyphElement;
+import net.minecraft.client.gui.Gui;
+import net.minecraft.client.gui.GuiElement;
+import net.minecraft.client.gui.keyboard.ScreenKeyboard;
+import net.minecraft.client.input.InputType;
+import net.minecraft.client.input.controller.ControllerInput;
+import net.minecraft.client.input.controller.IButton;
+import net.minecraft.client.render.texture.stitcher.IconCoordinate;
+import net.minecraft.client.render.texture.stitcher.TextureRegistry;
+import net.minecraft.core.sound.SoundCategory;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.lwjgl.opengl.GL11;
+
+import java.lang.reflect.Type;
+import java.util.HashMap;
+import java.util.Map;
+
+public abstract class Key extends Gui implements GuiElement {
+    protected IconCoordinate keyUp = TextureRegistry.getTexture("minecraft:gui/widgets/keyboard/key/up");
+    protected IconCoordinate keyDown = TextureRegistry.getTexture("minecraft:gui/widgets/keyboard/key/down");
+    protected IconCoordinate keyUpHovered = TextureRegistry.getTexture("minecraft:gui/widgets/keyboard/key/up_hovered");
+    protected IconCoordinate keyDownHovered = TextureRegistry.getTexture("minecraft:gui/widgets/keyboard/key/down_hovered");
+
+    protected int x;
+    protected int y;
+    protected int width;
+    protected int height;
+
+    protected boolean isDown = false;
+    protected boolean isHovered = false;
+    protected long holdStart = 0;
+
+    protected @Nullable ButtonEnum controllerButton = null;
+
+    public Key() {
+
+    }
+
+    public Key(int x, int y, int width, int height) {
+        setPosition(x, y, width, height);
+    }
+
+    public void onOpen(Minecraft mc, ScreenKeyboard keyboard) {
+        isDown = false;
+        isHovered = false;
+    }
+    public void render(Minecraft mc, ScreenKeyboard keyboard, int originX, int originY, int mouseX, int mouseY) {
+        boolean hovered = isHovered(originX, originY, mouseX, mouseY);
+        if (!isHovered && hovered) {
+            onFocus(mc, keyboard, originX, originY, mouseX, mouseY);
+        }
+        isHovered = hovered;
+
+        boolean down = isDown(mc, keyboard);
+        if (down) {
+            if (hovered) {
+                drawGuiIcon(x + originX, y + originY, width, height, keyDownHovered);
+            } else {
+                drawGuiIcon(x + originX, y + originY, width, height, keyDown);
+            }
+        } else {
+            if (hovered) {
+                drawGuiIcon(x + originX, y + originY, width, height, keyUpHovered);
+            } else {
+                drawGuiIcon(x + originX, y + originY, width, height, keyUp);
+            }
+        }
+        if (mc.inputType == InputType.CONTROLLER && controllerButton != null) {
+            GL11.glPushMatrix();
+            GL11.glEnable(GL11.GL_BLEND);
+            GL11.glTranslatef(x + originX + width - 8, y + originY + 1, 0);
+            GL11.glColor4f(1, 1, 1, 0.5f);
+            GL11.glScalef(0.5f, 0.5f, 1);
+            keyboard.glyphRenderer.drawGlyph(mc.controllerInput, controllerButton.glyph, 0, 0);
+            GL11.glDisable(GL11.GL_BLEND);
+            GL11.glPopMatrix();
+        }
+    }
+
+    public void onFocus(Minecraft mc, ScreenKeyboard keyboard, int originX, int originY, int mouseX, int mouseY) {
+        mc.sndManager.playSound("ui.ui_focus", SoundCategory.GUI_SOUNDS, 1.0f, 1.0f);
+    }
+
+    public void onClick(Minecraft mc, ScreenKeyboard keyboard, int originX, int originY, int mouseX, int mouseY, int mouseButton) {
+        if (mouseButton == 0 && isHovered(originX, originY, mouseX, mouseY)) {
+            pressAction(mc, keyboard, originX, originY, mouseX, mouseY);
+            holdStart = System.currentTimeMillis();
+            isDown = true;
+        }
+    }
+
+    public void pressAction(Minecraft mc, ScreenKeyboard keyboard, int originX, int originY, int mouseX, int mouseY) {
+        mc.sndManager.playSound("ui.ui_click", SoundCategory.GUI_SOUNDS, 1.0f, 1.0f);
+        if (mc.inputType == InputType.CONTROLLER && mc.controllerInput != null) {
+            mc.controllerInput.lockCursor = true;
+            mc.controllerInput.hideCursor = true;
+        }
+    }
+
+    public void releaseAction(Minecraft mc, ScreenKeyboard keyboard, int originX, int originY, int mouseX, int mouseY) {
+        if (mc.inputType == InputType.CONTROLLER && mc.controllerInput != null) {
+            mc.controllerInput.lockCursor = false;
+            mc.controllerInput.hideCursor = false;
+        }
+    }
+
+    public void onRelease(Minecraft mc, ScreenKeyboard keyboard, int originX, int originY, int mouseX, int mouseY, int mouseButton) {
+        if (isDown && mouseButton == 0) {
+            releaseAction(mc, keyboard, originX, originY, mouseX, mouseY);
+            isDown = false;
+        }
+    }
+
+    public void onButtonUse(Minecraft mc, ScreenKeyboard keyboard, int originX, int originY) {
+        if (mc.inputType == InputType.CONTROLLER && mc.controllerInput != null && controllerButton != null) {
+            IButton button = controllerButton.getButton(mc.controllerInput);
+            if (button.pressedThisFrame()) {
+                pressAction(mc, keyboard, originX, originY, (int) mc.controllerInput.cursorX, (int) mc.controllerInput.cursorY);
+                holdStart = System.currentTimeMillis();
+                isDown = true;
+            }
+            if (button.releasedThisFrame()) {
+                releaseAction(mc, keyboard, originX, originY, (int) mc.controllerInput.cursorX, (int) mc.controllerInput.cursorY);
+                isDown = false;
+            }
+        }
+    }
+
+    public Key setTextures(String keyUp, String keyDown, String keyUpHovered, String keyDownHovered) {
+        this.keyUp = TextureRegistry.getTexture(keyUp);
+        this.keyDown = TextureRegistry.getTexture(keyDown);
+        this.keyUpHovered = TextureRegistry.getTexture(keyUpHovered);
+        this.keyDownHovered = TextureRegistry.getTexture(keyDownHovered);
+        return this;
+    }
+
+    public Key setControllerButton(ButtonEnum button) {
+        this.controllerButton = button;
+        return this;
+    }
+
+    public long getHoldTime() {
+        return isDown ? System.currentTimeMillis() - holdStart : 0;
+    }
+
+    @Override
+    public void setX(int x) {
+        this.x = x;
+    }
+
+    @Override
+    public void setY(int y) {
+        this.y = y;
+    }
+
+    @Override
+    public void setWidth(int width) {
+        this.width = width;
+    }
+
+    @Override
+    public void setHeight(int height) {
+        this.height = height;
+    }
+
+    @Override
+    public int getX() {
+        return x;
+    }
+
+    @Override
+    public int getY() {
+        return y;
+    }
+
+    @Override
+    public int getWidth() {
+        return width;
+    }
+
+    @Override
+    public int getHeight() {
+        return height;
+    }
+
+    public boolean isHovered(int originX, int originY, int mx, int my) {
+        mx -= originX;
+        my -= originY;
+        return
+            x <= mx && mx < x + width &&
+            y <= my && my < y + height;
+    }
+
+    public boolean isDown(Minecraft mc, ScreenKeyboard keyboard) {
+        return isDown || isButtonPressed(mc, keyboard);
+    }
+
+    public boolean isButtonPressed(Minecraft mc, ScreenKeyboard keyboard) {
+        return mc.inputType == InputType.CONTROLLER && controllerButton != null && mc.controllerInput != null && controllerButton.getButton(mc.controllerInput).isPressed();
+    }
+
+    public int highestGroup() {
+        return 0;
+    }
+
+    public static class Serializer implements JsonSerializer<Key>, JsonDeserializer<Key> {
+        public static final Map<String, Class<? extends Key>> ID_TO_KEY_CLASS = new HashMap<>();
+        public static final Map<Class<? extends Key>, String> KEY_CLASS_TO_ID = new HashMap<>();
+        static {
+            registerKeyClass("key_char", CharKey.class);
+            registerKeyClass("key_back", BackspaceKey.class);
+            registerKeyClass("key_enter", EnterKey.class);
+            registerKeyClass("key_mod", ModifierKey.class);
+            registerKeyClass("key_group", GroupKey.class);
+            registerKeyClass("key_cursor", CursorKey.class);
+        }
+
+        public static void registerKeyClass(String id, Class<? extends Key> keyClass) {
+            assert !ID_TO_KEY_CLASS.containsKey(id) : "Id '" + id + "' is already assigned to class '" + ID_TO_KEY_CLASS.get(id).getSimpleName() + "'!";
+            assert !KEY_CLASS_TO_ID.containsKey(keyClass) : "Class '" + keyClass.getSimpleName() + "' is already assigned to id '" + KEY_CLASS_TO_ID.get(keyClass) + "'!";
+            ID_TO_KEY_CLASS.put(id, keyClass);
+            KEY_CLASS_TO_ID.put(keyClass, id);
+        }
+
+        @Override
+        public Key deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
+            String type = json.getAsJsonObject().get("type").getAsString();
+            Class<? extends Key> keyClass = ID_TO_KEY_CLASS.get(type);
+            if (keyClass == null) {
+                throw new JsonParseException("No assigned key class for type '" + type + "'!");
+            }
+            return context.deserialize(json, keyClass);
+        }
+
+        @Override
+        public JsonElement serialize(Key src, Type typeOfSrc, JsonSerializationContext context) {
+            return null;
+        }
+    }
+
+    public enum ButtonEnum {
+        BUTTON_A(ControllerGlyphElement.Glyph.BUTTON_A, c -> c.buttonA),
+        BUTTON_B(ControllerGlyphElement.Glyph.BUTTON_B, c -> c.buttonB),
+        BUTTON_X(ControllerGlyphElement.Glyph.BUTTON_X, c -> c.buttonX),
+        BUTTON_Y(ControllerGlyphElement.Glyph.BUTTON_Y, c -> c.buttonY),
+
+        BUTTON_DPAD_UP(ControllerGlyphElement.Glyph.D_PAD_UP,c -> c.digitalPad.up),
+        BUTTON_DPAD_LEFT(ControllerGlyphElement.Glyph.D_PAD_LEFT,c -> c.digitalPad.left),
+        BUTTON_DPAD_DOWN(ControllerGlyphElement.Glyph.D_PAD_DOWN,c -> c.digitalPad.down),
+        BUTTON_DPAD_RIGHT(ControllerGlyphElement.Glyph.D_PAD_RIGHT,c -> c.digitalPad.right),
+
+        BUTTON_SHOULDER_LEFT(ControllerGlyphElement.Glyph.SHOULDER_LEFT,c -> c.buttonLeftShoulder),
+        BUTTON_SHOULDER_RIGHT(ControllerGlyphElement.Glyph.SHOULDER_RIGHT,c -> c.buttonRightShoulder),
+
+        BUTTON_STICK_LEFT(ControllerGlyphElement.Glyph.STICK_LEFT_PRESS,c -> c.joyLeft.button),
+        BUTTON_STICK_RIGHT(ControllerGlyphElement.Glyph.STICK_RIGHT_PRESS,c -> c.joyRight.button),
+
+        BUTTON_TRIGGER_LEFT(ControllerGlyphElement.Glyph.TRIGGER_LEFT,c -> c.buttonLeftTrigger),
+        BUTTON_TRIGGER_RIGHT(ControllerGlyphElement.Glyph.TRIGGER_RIGHT,c -> c.buttonRightTrigger),
+
+        BUTTON_START(ControllerGlyphElement.Glyph.BUTTON_START,c -> c.buttonStart),
+        BUTTON_BACK(ControllerGlyphElement.Glyph.BUTTON_BACK, c -> c.buttonBack);
+
+        public final @NotNull ControllerGlyphElement.Glyph glyph;
+        private final @NotNull ButtonGetter buttonGetter;
+
+        ButtonEnum(@NotNull ControllerGlyphElement.Glyph glyph, @NotNull ButtonGetter buttonGetter) {
+            this.glyph = glyph;
+            this.buttonGetter = buttonGetter;
+        }
+
+        public @NotNull IButton getButton(ControllerInput controller) {
+            return buttonGetter.apply(controller);
+        }
+
+        private interface ButtonGetter {
+            @NotNull IButton apply(@NotNull ControllerInput controller);
+        }
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/gui/keyboard/keys/ModifierKey.java b/game/client/src/main/java/net/minecraft/client/gui/keyboard/keys/ModifierKey.java
new file mode 100644
index 000000000..758ea48f0
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/gui/keyboard/keys/ModifierKey.java
@@ -0,0 +1,118 @@
+package net.minecraft.client.gui.keyboard.keys;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.keyboard.ScreenKeyboard;
+
+import java.lang.reflect.Type;
+import java.util.Locale;
+
+public class ModifierKey extends Key{
+    public static final String ID_MODIFIER_SHIFT = "mod_shift";
+
+    protected final boolean locking;
+    protected final String modifierId;
+    public ModifierKey(int x, int y, int width, int height, String modifierId, boolean locking) {
+        super(x, y, width, height);
+        this.locking = locking;
+        this.modifierId = modifierId;
+        setTextures(
+            "minecraft:gui/widgets/keyboard/key_shift/up",
+            "minecraft:gui/widgets/keyboard/key_shift/down",
+            "minecraft:gui/widgets/keyboard/key_shift/up_hovered",
+            "minecraft:gui/widgets/keyboard/key_shift/down_hovered");
+    }
+
+    @Override
+    public void pressAction(Minecraft mc, ScreenKeyboard keyboard, int originX, int originY, int mouseX, int mouseY) {
+        super.pressAction(mc, keyboard, originX, originY, mouseX, mouseY);
+        if (locking) {
+            keyboard.setModifierLock(modifierId, !keyboard.isModifierLocked(modifierId));
+        } else {
+            keyboard.setModifierState(modifierId, !keyboard.isModifierActive(modifierId));
+        }
+    }
+
+    @Override
+    public boolean isDown(Minecraft mc, ScreenKeyboard keyboard) {
+        return super.isDown(mc, keyboard) || (locking ? keyboard.isModifierLocked(modifierId) : keyboard.activeModifiers.getOrDefault(modifierId, false));
+    }
+
+    public static class Serializer implements JsonSerializer<ModifierKey>, JsonDeserializer<ModifierKey> {
+        public static final int DEFAULT_WIDTH = 25;
+        public static final int DEFAULT_HEIGHT = 18;
+
+        public static final boolean DEFAULT_LOCKING = false;
+        public static final String DEFAULT_MODIFIER_ID = ID_MODIFIER_SHIFT;
+
+        public static final String DEFAULT_TEXTURE_UP = "minecraft:gui/widgets/keyboard/key_shift/up";
+        public static final String DEFAULT_TEXTURE_DOWN = "minecraft:gui/widgets/keyboard/key_shift/down";
+        public static final String DEFAULT_TEXTURE_UP_HOVERED = "minecraft:gui/widgets/keyboard/key_shift/up_hovered";
+        public static final String DEFAULT_TEXTURE_DOWN_HOVERED = "minecraft:gui/widgets/keyboard/key_shift/down_hovered";
+
+        @Override
+        public ModifierKey deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
+            JsonObject obj = json.getAsJsonObject();
+            int x = obj.get("x").getAsInt();
+            int y = obj.get("y").getAsInt();
+            int width = obj.has("width") ? obj.get("width").getAsInt() : DEFAULT_WIDTH;
+            int height = obj.has("height") ? obj.get("height").getAsInt() : DEFAULT_HEIGHT;
+            ButtonEnum button = obj.has("button") ? Key.ButtonEnum.valueOf(obj.get("button").getAsString().toUpperCase(Locale.ROOT)) : null;
+
+            boolean locking = obj.has("toggle") ? obj.get("toggle").getAsBoolean() : DEFAULT_LOCKING;
+            String modId = obj.has("modifier") ? obj.get("modifier").getAsString() : DEFAULT_MODIFIER_ID;
+
+            String texUp = DEFAULT_TEXTURE_UP;
+            String texDown = DEFAULT_TEXTURE_DOWN;
+            String texUpHovered = DEFAULT_TEXTURE_UP_HOVERED;
+            String texDownHovered = DEFAULT_TEXTURE_DOWN_HOVERED;
+
+            if (obj.has("textures")) {
+                JsonObject textures = obj.getAsJsonObject("textures");
+                if (textures.has("up")) texUp = textures.get("up").getAsString();
+                if (textures.has("down")) texDown = textures.get("down").getAsString();
+                if (textures.has("up_hovered")) texUpHovered = textures.get("up_hovered").getAsString();
+                if (textures.has("down_hovered")) texDownHovered = textures.get("down_hovered").getAsString();
+            }
+            ModifierKey key = new ModifierKey(x, y, width, height, modId, locking);
+            key.setTextures(texUp, texDown, texUpHovered, texDownHovered);
+            key.setControllerButton(button);
+            return key;
+        }
+
+        @Override
+        public JsonElement serialize(ModifierKey src, Type typeOfSrc, JsonSerializationContext context) {
+            JsonObject object = new JsonObject();
+            object.addProperty("type", Key.Serializer.KEY_CLASS_TO_ID.get(src.getClass()));
+
+            object.addProperty("x", src.x);
+            object.addProperty("y", src.y);
+            if (src.width != DEFAULT_WIDTH) object.addProperty("width", src.width);
+            if (src.height != DEFAULT_HEIGHT) object.addProperty("height", src.height);
+            if (src.controllerButton != null) object.addProperty("button", src.controllerButton.name().toLowerCase(Locale.ROOT));
+
+            boolean ku = !src.keyUp.sourceNamespaceId.toString().equals(DEFAULT_TEXTURE_UP);
+            boolean kd = !src.keyDown.sourceNamespaceId.toString().equals(DEFAULT_TEXTURE_DOWN);
+            boolean kuh = !src.keyUpHovered.sourceNamespaceId.toString().equals(DEFAULT_TEXTURE_UP_HOVERED);
+            boolean kdh = !src.keyDownHovered.sourceNamespaceId.toString().equals(DEFAULT_TEXTURE_DOWN_HOVERED);
+            if (ku || kd || kuh || kdh) {
+                JsonObject textureObject = new JsonObject();
+                if (ku) textureObject.addProperty("up", src.keyUp.sourceNamespaceId.toString());
+                if (kd) textureObject.addProperty("down", src.keyDown.sourceNamespaceId.toString());
+                if (kuh) textureObject.addProperty("up_hovered", src.keyUpHovered.sourceNamespaceId.toString());
+                if (kdh) textureObject.addProperty("down_hovered", src.keyDownHovered.sourceNamespaceId.toString());
+                object.add("textures", textureObject);
+            }
+
+            if (!src.modifierId.equals(DEFAULT_MODIFIER_ID)) object.addProperty("modifier", src.modifierId);
+            if (src.locking != DEFAULT_LOCKING) object.addProperty("toggle", src.locking);
+            return object;
+        }
+    }
+}
\ No newline at end of file
diff --git a/game/client/src/main/java/net/minecraft/client/gui/options/ScreenOptions.java b/game/client/src/main/java/net/minecraft/client/gui/options/ScreenOptions.java
index e65fe006f..2731a54a4 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/options/ScreenOptions.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/options/ScreenOptions.java
@@ -537,6 +537,24 @@ public class ScreenOptions
 
     @Override
     public void guiSpecificControllerInput(ControllerInput controller) {
+        if(controller.buttonStart.pressedThisFrame()) {
+            this.keyPressed((char)0, Keyboard.KEY_ESCAPE, -1, -1);
+        }
+        if(controller.buttonA.pressedThisFrame()) {
+            this.mouseClicked((int)controller.cursorX, (int)controller.cursorY, 0);
+        }
+        if(controller.buttonA.releasedThisFrame()) {
+            this.mouseReleased((int)controller.cursorX, (int)controller.cursorY, 0);
+        }
+        if(controller.buttonY.pressedThisFrame()) {
+            this.mouseClicked((int)controller.cursorX, (int)controller.cursorY, 1);
+        }
+        if(controller.buttonY.releasedThisFrame()) {
+            this.mouseReleased((int)controller.cursorX, (int)controller.cursorY, 1);
+        }
+        if(controller.buttonX.pressedThisFrame()) {
+            this.mouseClicked((int)controller.cursorX, (int)controller.cursorY, 10);
+        }
         if (controller.buttonB.pressedThisFrame()) {
             mc.displayScreen(getParentScreen());
         }
diff --git a/game/client/src/main/java/net/minecraft/client/gui/options/components/BooleanOptionComponent.java b/game/client/src/main/java/net/minecraft/client/gui/options/components/BooleanOptionComponent.java
index 8d8f00351..588159501 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/options/components/BooleanOptionComponent.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/options/components/BooleanOptionComponent.java
@@ -3,6 +3,7 @@ package net.minecraft.client.gui.options.components;
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.gui.SwitchElement;
 import net.minecraft.client.option.OptionBoolean;
+import net.minecraft.client.render.window.CursorShape;
 
 public class BooleanOptionComponent
     extends ButtonComponent
@@ -59,5 +60,9 @@ public class BooleanOptionComponent
         this.button.height = buttonHeight;
 
         this.button.drawButton(mc, x + relativeMouseX, y + relativeMouseY);
+
+        if (relativeMouseX >= relativeButtonX && relativeMouseX < relativeButtonX + buttonWidth && relativeMouseY >= relativeButtonY && relativeButtonY < relativeButtonY + buttonHeight) {
+            mc.currentScreen.setDesiredCursor(CursorShape.HAND);
+        }
     }
 }
diff --git a/game/client/src/main/java/net/minecraft/client/gui/options/components/ButtonComponent.java b/game/client/src/main/java/net/minecraft/client/gui/options/components/ButtonComponent.java
index 6188b9862..762dff32c 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/options/components/ButtonComponent.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/options/components/ButtonComponent.java
@@ -4,6 +4,7 @@ import net.minecraft.client.Minecraft;
 import net.minecraft.client.gui.ButtonElement;
 import net.minecraft.client.render.Font;
 import net.minecraft.client.render.tessellator.Tessellator;
+import net.minecraft.client.render.window.CursorShape;
 import net.minecraft.core.lang.I18n;
 import net.minecraft.core.sound.SoundCategory;
 import org.lwjgl.opengl.GL11;
@@ -99,6 +100,10 @@ public abstract class ButtonComponent
             resetButton.yPosition = y + relativeButtonY;
 
             resetButton.drawButton(mc, x + relativeMouseX, y + relativeMouseY);
+
+            if (relativeMouseX >= relativeButtonX && relativeMouseX < relativeButtonX + buttonWidth && relativeMouseY >= relativeButtonY && relativeButtonY < relativeButtonY + buttonHeight) {
+                mc.currentScreen.setDesiredCursor(CursorShape.HAND);
+            }
         }
     }
 
diff --git a/game/client/src/main/java/net/minecraft/client/gui/options/components/DifficultyOptionComponent.java b/game/client/src/main/java/net/minecraft/client/gui/options/components/DifficultyOptionComponent.java
index 62ced22b3..64924c587 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/options/components/DifficultyOptionComponent.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/options/components/DifficultyOptionComponent.java
@@ -3,6 +3,7 @@ package net.minecraft.client.gui.options.components;
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.gui.IntegerSliderElement;
 import net.minecraft.client.option.OptionEnum;
+import net.minecraft.client.render.window.CursorShape;
 import net.minecraft.core.enums.Difficulty;
 import net.minecraft.core.lang.I18n;
 
@@ -49,6 +50,10 @@ public class DifficultyOptionComponent extends ButtonComponent {
         this.slider.height = buttonHeight;
 
         this.slider.drawButton(mc, x + relativeMouseX, y + relativeMouseY);
+
+        if (relativeMouseX >= relativeButtonX && relativeMouseX < relativeButtonX + buttonWidth && relativeMouseY >= relativeButtonY && relativeButtonY < relativeButtonY + buttonHeight) {
+            mc.currentScreen.setDesiredCursor(CursorShape.HAND);
+        }
     }
     @Override
     public void init(Minecraft mc){
diff --git a/game/client/src/main/java/net/minecraft/client/gui/options/components/FloatOptionComponent.java b/game/client/src/main/java/net/minecraft/client/gui/options/components/FloatOptionComponent.java
index 158ae6230..f7626ed11 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/options/components/FloatOptionComponent.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/options/components/FloatOptionComponent.java
@@ -3,6 +3,7 @@ package net.minecraft.client.gui.options.components;
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.gui.SliderElement;
 import net.minecraft.client.option.OptionFloat;
+import net.minecraft.client.render.window.CursorShape;
 
 public class FloatOptionComponent
         extends ButtonComponent
@@ -52,6 +53,10 @@ public class FloatOptionComponent
         this.slider.height = buttonHeight;
 
         this.slider.drawButton(mc, x + relativeMouseX, y + relativeMouseY);
+
+        if (relativeMouseX >= relativeButtonX && relativeMouseX < relativeButtonX + buttonWidth && relativeMouseY >= relativeButtonY && relativeButtonY < relativeButtonY + buttonHeight) {
+            mc.currentScreen.setDesiredCursor(CursorShape.HAND);
+        }
     }
 
     @Override
diff --git a/game/client/src/main/java/net/minecraft/client/gui/options/components/FramerateOptionComponent.java b/game/client/src/main/java/net/minecraft/client/gui/options/components/FramerateOptionComponent.java
index da9ca3938..c4973a5d8 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/options/components/FramerateOptionComponent.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/options/components/FramerateOptionComponent.java
@@ -2,6 +2,7 @@ package net.minecraft.client.gui.options.components;
 
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.option.OptionRange;
+import net.minecraft.client.render.window.CursorShape;
 import net.minecraft.client.render.window.GameWindowGLFW;
 
 public class FramerateOptionComponent extends ToggleableOptionComponent<Integer>{
@@ -29,6 +30,10 @@ public class FramerateOptionComponent extends ToggleableOptionComponent<Integer>
         this.slider.height = buttonHeight;
 
         this.slider.drawButton(mc, x + relativeMouseX, y + relativeMouseY);
+
+        if (relativeMouseX >= relativeButtonX && relativeMouseX < relativeButtonX + buttonWidth && relativeMouseY >= relativeButtonY && relativeButtonY < relativeButtonY + buttonHeight) {
+            mc.currentScreen.setDesiredCursor(CursorShape.HAND);
+        }
     }
     @Override
     public void init(Minecraft mc){
diff --git a/game/client/src/main/java/net/minecraft/client/gui/options/components/IntegerOptionComponent.java b/game/client/src/main/java/net/minecraft/client/gui/options/components/IntegerOptionComponent.java
index a4f336d7a..6fe4a74c8 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/options/components/IntegerOptionComponent.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/options/components/IntegerOptionComponent.java
@@ -3,6 +3,7 @@ package net.minecraft.client.gui.options.components;
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.gui.ButtonElement;
 import net.minecraft.client.option.OptionInteger;
+import net.minecraft.client.render.window.CursorShape;
 
 public class IntegerOptionComponent
     extends ButtonComponent
@@ -37,6 +38,10 @@ public class IntegerOptionComponent
         this.button.height = buttonHeight;
 
         this.button.drawButton(mc, x + relativeMouseX, y + relativeMouseY);
+
+        if (relativeMouseX >= relativeButtonX && relativeMouseX < relativeButtonX + buttonWidth && relativeMouseY >= relativeButtonY && relativeButtonY < relativeButtonY + buttonHeight) {
+            mc.currentScreen.setDesiredCursor(CursorShape.HAND);
+        }
     }
 
     @Override
diff --git a/game/client/src/main/java/net/minecraft/client/gui/options/components/KeyBindingComponent.java b/game/client/src/main/java/net/minecraft/client/gui/options/components/KeyBindingComponent.java
index fcdb14628..9e835cea8 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/options/components/KeyBindingComponent.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/options/components/KeyBindingComponent.java
@@ -3,6 +3,7 @@ package net.minecraft.client.gui.options.components;
 import net.minecraft.client.gui.ButtonElement;
 import net.minecraft.client.gui.options.ScreenOptions;
 import net.minecraft.client.option.KeyBinding;
+import net.minecraft.client.render.window.CursorShape;
 
 public class KeyBindingComponent
         extends ButtonComponent
@@ -43,11 +44,19 @@ public class KeyBindingComponent
 
         this.keyButton.drawButton(mc, x + relativeMouseX, y + relativeMouseY);
 
+        if (relativeMouseX >= relativeButtonX && relativeMouseX < relativeButtonX + buttonWidth - this.clearButton.width && relativeMouseY >= relativeButtonY && relativeButtonY < relativeButtonY + buttonHeight) {
+            mc.currentScreen.setDesiredCursor(CursorShape.HAND);
+        }
+
         this.clearButton.xPosition = x + relativeButtonX + this.keyButton.width;
         this.clearButton.yPosition = y + relativeButtonY;
         this.clearButton.enabled = this.keyBinding.isBound();
 
         this.clearButton.drawButton(mc, x + relativeMouseX, y + relativeMouseY);
+
+        if (this.clearButton.enabled && relativeMouseX >= relativeButtonX + this.keyButton.width && relativeMouseX < relativeButtonX + buttonWidth && relativeMouseY >= relativeButtonY && relativeButtonY < relativeButtonY + buttonHeight) {
+            mc.currentScreen.setDesiredCursor(CursorShape.HAND);
+        }
     }
 
     public void update() {
diff --git a/game/client/src/main/java/net/minecraft/client/gui/options/components/SearchFieldComponent.java b/game/client/src/main/java/net/minecraft/client/gui/options/components/SearchFieldComponent.java
index 1bf5b9293..748edd253 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/options/components/SearchFieldComponent.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/options/components/SearchFieldComponent.java
@@ -61,6 +61,7 @@ public class SearchFieldComponent
         textField.yPosition = y + PADDING;
 
         textField.drawTextBox();
+        textField.updateCursor(mc, x + relativeMouseX, y + relativeMouseY);
 
         clearButton.xPosition = x + width / 2 + textField.width / 2 + 1;
         clearButton.yPosition = y + PADDING - 1;
diff --git a/game/client/src/main/java/net/minecraft/client/gui/options/components/ToggleableOptionComponent.java b/game/client/src/main/java/net/minecraft/client/gui/options/components/ToggleableOptionComponent.java
index 5807490ca..1c6785694 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/options/components/ToggleableOptionComponent.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/options/components/ToggleableOptionComponent.java
@@ -3,6 +3,7 @@ package net.minecraft.client.gui.options.components;
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.gui.IntegerSliderElement;
 import net.minecraft.client.option.OptionToggleable;
+import net.minecraft.client.render.window.CursorShape;
 
 public class ToggleableOptionComponent<E>
         extends ButtonComponent
@@ -57,6 +58,10 @@ public class ToggleableOptionComponent<E>
         this.slider.height = buttonHeight;
 
         this.slider.drawButton(mc, x + relativeMouseX, y + relativeMouseY);
+
+        if (relativeMouseX >= relativeButtonX && relativeMouseX < relativeButtonX + buttonWidth && relativeMouseY >= relativeButtonY && relativeButtonY < relativeButtonY + buttonHeight) {
+            mc.currentScreen.setDesiredCursor(CursorShape.HAND);
+        }
     }
     @Override
     public void init(Minecraft mc){
diff --git a/game/client/src/main/java/net/minecraft/client/gui/options/components/TooltipPickerComponent.java b/game/client/src/main/java/net/minecraft/client/gui/options/components/TooltipPickerComponent.java
index 4f4ec53ef..fd86d799c 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/options/components/TooltipPickerComponent.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/options/components/TooltipPickerComponent.java
@@ -3,6 +3,7 @@ package net.minecraft.client.gui.options.components;
 import net.minecraft.client.gui.TooltipElement;
 import net.minecraft.client.option.OptionToggleable;
 import net.minecraft.client.option.enums.TooltipStyle;
+import net.minecraft.client.render.window.CursorShape;
 import org.lwjgl.opengl.GL11;
 
 public class TooltipPickerComponent<E>
@@ -30,5 +31,9 @@ public class TooltipPickerComponent<E>
         stringWidth += padding * 2;
         GL11.glColor4f(1, 1, 1, 1);
         this.tooltip.render(this.option.getDisplayStringValue(), x + relativeButtonX + buttonWidth / 2 - stringWidth / 2, y + relativeButtonY + 3 + yOffset, 0, 0);
+
+        if (relativeMouseX >= relativeButtonX && relativeMouseX < relativeButtonX + buttonWidth && relativeMouseY >= relativeButtonY && relativeButtonY < relativeButtonY + buttonHeight) {
+            mc.currentScreen.setDesiredCursor(CursorShape.HAND);
+        }
     }
 }
diff --git a/game/client/src/main/java/net/minecraft/client/gui/options/data/OptionsPages.java b/game/client/src/main/java/net/minecraft/client/gui/options/data/OptionsPages.java
index 95aa3d727..130580447 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/options/data/OptionsPages.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/options/data/OptionsPages.java
@@ -1,6 +1,5 @@
 package net.minecraft.client.gui.options.data;
 
-import net.betterthanadventure.utils.BiomeFinder;
 import net.betterthanadventure.utils.DataDumper;
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.enums.EnumGPUVendor;
@@ -17,13 +16,10 @@ import net.minecraft.client.world.WorldClient;
 import net.minecraft.core.Global;
 import net.minecraft.core.achievement.stat.Stat;
 import net.minecraft.core.achievement.stat.StatList;
-import net.minecraft.core.block.Block;
 import net.minecraft.core.block.Blocks;
-import net.minecraft.core.item.Item;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.item.Items;
 import net.minecraft.core.util.helper.Utils;
-import net.minecraft.core.world.World;
 import org.useless.seedviewer.SeedViewerMain;
 import paulscode.sound.SoundSystem;
 
@@ -191,6 +187,7 @@ public abstract class OptionsPages
                 .withComponent(new KeyBindingComponent(mc.gameSettings.keySprint))
             )
             .withComponent(new OptionsCategory("gui.options.page.controls.category.user_interface")
+                .withComponent(new BooleanOptionComponent(mc.gameSettings.forceOnscreenKeyboard))
                 .withComponent(new KeyBindingComponent(mc.gameSettings.keyInventory))
                 .withComponent(new KeyBindingComponent(mc.gameSettings.keyChat))
                 .withComponent(new KeyBindingComponent(mc.gameSettings.keyCommand))
@@ -236,6 +233,7 @@ public abstract class OptionsPages
                 .withComponent(new KeyBindingComponent(mc.gameSettings.keyToggleGamma))
                 .withComponent(new KeyBindingComponent(mc.gameSettings.keyLockRotation))
                 .withComponent(new KeyBindingComponent(mc.gameSettings.keyScreenshot))
+                .withComponent(new KeyBindingComponent(mc.gameSettings.keyShowConsole))
             )
         );
         TEXTURE_PACKS = register(new OptionsPage("gui.options.page.texture_packs.title", new ItemStack(Items.PAINTING))
@@ -285,6 +283,7 @@ public abstract class OptionsPages
                 .withComponent(new KeyBindingComponent(mc.gameSettings.keyLoadChunksQuickly))
             )
             .withComponent(new OptionsCategory("gui.options.page.debug.category.settings")
+                .withComponent(new BooleanOptionComponent(mc.gameSettings.developerMode))
                 .withComponent(new BooleanOptionComponent(mc.gameSettings.alphaMenu))
                 .withComponent(new BooleanOptionComponent(mc.gameSettings.lockCamera))
                 .withComponent(new BooleanOptionComponent(mc.gameSettings.showDebugScreen))
diff --git a/game/client/src/main/java/net/minecraft/client/gui/popup/PopupScreen.java b/game/client/src/main/java/net/minecraft/client/gui/popup/PopupScreen.java
index b7554f350..dd77fcaec 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/popup/PopupScreen.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/popup/PopupScreen.java
@@ -164,7 +164,9 @@ public final class PopupScreen
         int popupX = width / 2 - popupWidth / 2;
         int popupY = height / 2 - popupHeight / 2;
 
-        parent.render(-1, -1, partialTick);
+        if (this.parent != null) {
+            parent.render(-1, -1, partialTick);
+        }
 
         drawPopupBackground();
 
diff --git a/game/client/src/main/java/net/minecraft/client/gui/popup/TextInputComponent.java b/game/client/src/main/java/net/minecraft/client/gui/popup/TextInputComponent.java
index 16672f051..82aa528a4 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/popup/TextInputComponent.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/popup/TextInputComponent.java
@@ -40,6 +40,7 @@ public class TextInputComponent
         this.textField.yPosition = y;
 
         this.textField.drawTextBox();
+        this.textField.updateCursor(Minecraft.getMinecraft(), mouseX, mouseY);
     }
 
     @Override
diff --git a/game/client/src/main/java/net/minecraft/client/gui/text/TextFieldEditor.java b/game/client/src/main/java/net/minecraft/client/gui/text/TextFieldEditor.java
index 5ae069048..5e9722250 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/text/TextFieldEditor.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/text/TextFieldEditor.java
@@ -31,11 +31,17 @@ public class TextFieldEditor
 
     private final ITextField textField;
     private int cursor;
+    private final boolean allowNewline;
 
-    public TextFieldEditor(ITextField textField)
+    public TextFieldEditor(ITextField textField, final boolean allowNewline)
     {
         this.textField = textField;
         this.cursor = textField.getText().length();
+        this.allowNewline = allowNewline;
+    }
+
+    public TextFieldEditor(ITextField textField) {
+        this(textField, false);
     }
 
     public int getCursor()
@@ -177,7 +183,7 @@ public class TextFieldEditor
 
         int remaining = textField.maxLength() - currentText.length();
         if (remaining == 0) return;
-        if (!textField.isCharacterAllowed(c)) return;
+        if (!textField.isCharacterAllowed(c) && (!allowNewline || c != '\n')) return;
         if (cursor > currentText.length()) setCursor(currentText.length());
         if (cursor < 0) setCursor(0);
 
diff --git a/game/client/src/main/java/net/minecraft/client/input/controller/ControllerInput.java b/game/client/src/main/java/net/minecraft/client/input/controller/ControllerInput.java
index 87ca7b7f4..02a035d99 100644
--- a/game/client/src/main/java/net/minecraft/client/input/controller/ControllerInput.java
+++ b/game/client/src/main/java/net/minecraft/client/input/controller/ControllerInput.java
@@ -36,6 +36,8 @@ public class ControllerInput {
     public Button[] buttons;
     public Joystick[] joysticks;
 
+    public boolean hideCursor = false;
+    public boolean lockCursor = false;
     public double cursorX;
     public double cursorY;
 
@@ -114,6 +116,7 @@ public class ControllerInput {
     }
 
     public void moveCursor() {
+        if (lockCursor) return;
         double speed = 300 * Time.delta * (minecraft.gameSettings.controllerMenuSensitivity.value + 0.5f);
 
         cursorX += ((Math.pow(joyLeft.getX(), 3.0f) + joyLeft.getX()) / 2.0f) * speed;
diff --git a/game/client/src/main/java/net/minecraft/client/input/controller/IButton.java b/game/client/src/main/java/net/minecraft/client/input/controller/IButton.java
index eaac271f6..00e2bfe88 100644
--- a/game/client/src/main/java/net/minecraft/client/input/controller/IButton.java
+++ b/game/client/src/main/java/net/minecraft/client/input/controller/IButton.java
@@ -1,5 +1,7 @@
 package net.minecraft.client.input.controller;
 
+import net.minecraft.client.gui.ControllerGlyphElement;
+
 public interface IButton {
 	
 	public boolean isPressed();
@@ -12,5 +14,5 @@ public interface IButton {
 	
 	public void cancelButtonPress();
 	public int getHoldTime();
-	
+
 }
diff --git a/game/client/src/main/java/net/minecraft/client/net/thread/ThreadDownloadResources.java b/game/client/src/main/java/net/minecraft/client/net/thread/ThreadDownloadResources.java
index 1dc07b9a4..6dc270bb9 100644
--- a/game/client/src/main/java/net/minecraft/client/net/thread/ThreadDownloadResources.java
+++ b/game/client/src/main/java/net/minecraft/client/net/thread/ThreadDownloadResources.java
@@ -19,6 +19,7 @@ import com.b100.xml.element.XmlContentTag;
 import com.b100.xml.element.XmlTag;
 import com.mojang.logging.LogUtils;
 import net.minecraft.client.Minecraft;
+import net.minecraft.client.sound.SoundRepository;
 import org.slf4j.Logger;
 
 public class ThreadDownloadResources extends Thread {
@@ -122,6 +123,7 @@ public class ThreadDownloadResources extends Thread {
                 throw new RuntimeException("Error downloading sound file: " + soundEntry.name, e);
             }
         }
+        SoundRepository.reload();
 
         downloading = false;
     }
diff --git a/game/client/src/main/java/net/minecraft/client/option/GameSettings.java b/game/client/src/main/java/net/minecraft/client/option/GameSettings.java
index 9d181f2df..baf5382db 100644
--- a/game/client/src/main/java/net/minecraft/client/option/GameSettings.java
+++ b/game/client/src/main/java/net/minecraft/client/option/GameSettings.java
@@ -100,6 +100,7 @@ public class GameSettings {
 	public OptionBoolean showChunkBorders = new OptionBoolean(this, "showChunkBorders", false);
 	public OptionBoolean showCollisionBoxes = new OptionBoolean(this, "showCollisionBoxes", false);
 	public OptionBoolean swapCraftingButtons = new OptionBoolean(this, "swapCraftingButtons", false);
+	public OptionBoolean forceOnscreenKeyboard = new OptionBoolean(this, "forceOnscreenKeyboard", false);
     public OptionBoolean forceUnicodeFont = new OptionBoolean(this, "forceUnicodeFont", false);
 	public OptionEnum<PlacementMode> placementMode = new OptionEnum<>(this, "placementMode", PlacementMode.class, PlacementMode.DEFAULT).setIsSlider(false);
 	public OptionBoolean discordRichPresence = new OptionBoolean(this, "discordRichPresence", false);
@@ -168,6 +169,7 @@ public class GameSettings {
 	public OptionBoolean mobVariants = new OptionBoolean(this, "mobVariants", true);
 	public OptionEnum<Colorblindness> colorblindnessFix = new OptionEnum<>(this, "colorblindnessFix", Colorblindness.class, Colorblindness.NONE);
 	public OptionBoolean subtitles = new OptionBoolean(this, "subtitles", false);
+	public OptionBoolean developerMode = new OptionBoolean(this, "developerMode", false);
 	public OptionBoolean enableHolidayTextures = new OptionBoolean(this, "enableHolidayTextures", true);
 
 	public KeyBinding keyAttack = new KeyBinding("key.attack").setDefault(InputDevice.mouse, 0);
@@ -222,6 +224,7 @@ public class GameSettings {
 	public KeyBinding keyToggleOverlay = new KeyBinding("key.toggleOverlay").setDefault(InputDevice.keyboard, KEY_F4);
 	public KeyBinding keyOpenAchievements = new KeyBinding("key.openAchievements");
 	public KeyBinding keyOpenStatistics = new KeyBinding("key.openStatistics");
+	public KeyBinding keyShowConsole = new KeyBinding("key.showConsole").setDefault(InputDevice.keyboard, KEY_GRAVE);
 	public List<KeyBinding> debugsKeys = new ArrayList<>();
 	public KeyBinding keyToggleDebug = new KeyBinding("key.toggleDebug").setDefault(InputDevice.keyboard, KEY_F3);
 	public KeyBinding keyReload = new KeyBinding("key.debug.reload").setDefault(InputDevice.keyboard, KEY_R);
diff --git a/game/client/src/main/java/net/minecraft/client/render/BlockEntityRenderDispatcher.java b/game/client/src/main/java/net/minecraft/client/render/BlockEntityRenderDispatcher.java
index 01789de76..ff3db3266 100644
--- a/game/client/src/main/java/net/minecraft/client/render/BlockEntityRenderDispatcher.java
+++ b/game/client/src/main/java/net/minecraft/client/render/BlockEntityRenderDispatcher.java
@@ -22,7 +22,9 @@ public class BlockEntityRenderDispatcher
         renderers.put(TileEntityMobSpawner.class, new TileEntityRendererMobSpawner());
         renderers.put(TileEntityMovingPistonBlock.class, new TileEntityRendererMovingPiston());
         renderers.put(TileEntityFlowerJar.class, new TileEntityRendererFlowerJar());
+        renderers.put(TileEntityJarButterfly.class, new TileEntityRendererJarButterfly());
         renderers.put(TileEntityMeshGold.class, new TileEntityRendererMeshGold());
+        renderers.put(TileEntityStatue.class, new TileEntityRendererStatue());
 
         for (TileEntityRenderer<?> tileEntityRenderer : renderers.values())
         {
diff --git a/game/client/src/main/java/net/minecraft/client/render/EntityRenderDispatcher.java b/game/client/src/main/java/net/minecraft/client/render/EntityRenderDispatcher.java
index 6b807c251..b033462b0 100644
--- a/game/client/src/main/java/net/minecraft/client/render/EntityRenderDispatcher.java
+++ b/game/client/src/main/java/net/minecraft/client/render/EntityRenderDispatcher.java
@@ -3,6 +3,7 @@ package net.minecraft.client.render;
 import java.util.HashMap;
 import java.util.Map;
 
+import net.minecraft.client.render.entity.*;
 import net.minecraft.client.render.model.ModelBiped;
 import net.minecraft.client.render.model.ModelChicken;
 import net.minecraft.client.render.model.ModelCow;
@@ -14,6 +15,7 @@ import net.minecraft.core.entity.EntityItem;
 import net.minecraft.core.entity.EntityLightning;
 import net.minecraft.core.entity.EntityPainting;
 import net.minecraft.core.entity.Mob;
+import net.minecraft.core.entity.animal.MobButterfly;
 import net.minecraft.core.entity.animal.MobChicken;
 import net.minecraft.core.entity.animal.MobFireflyCluster;
 import net.minecraft.core.entity.animal.MobWolf;
@@ -28,41 +30,11 @@ import net.minecraft.core.entity.projectile.ProjectileEgg;
 import net.minecraft.core.entity.projectile.ProjectileFireball;
 import net.minecraft.core.entity.projectile.ProjectileSnowball;
 import net.minecraft.core.entity.vehicle.EntityBoat;
+import net.minecraft.core.entity.vehicle.MobGhastVehicle;
 import net.minecraft.core.item.Items;
 import org.lwjgl.opengl.GL11;
 import net.minecraft.client.option.GameSettings;
 import net.minecraft.client.render.camera.ICamera;
-import net.minecraft.client.render.entity.MobRendererZombieArmored;
-import net.minecraft.client.render.entity.EntityRendererArrow;
-import net.minecraft.client.render.entity.EntityRendererBoat;
-import net.minecraft.client.render.entity.EntityRendererFishingBobber;
-import net.minecraft.client.render.entity.EntityRendererCannonball;
-import net.minecraft.client.render.entity.MobRendererChicken;
-import net.minecraft.client.render.entity.MobRendererCow;
-import net.minecraft.client.render.entity.MobRendererCreeper;
-import net.minecraft.client.render.entity.EntityRendererDefault;
-import net.minecraft.client.render.entity.EntityRenderer;
-import net.minecraft.client.render.entity.EntityRendererFallingBlock;
-import net.minecraft.client.render.entity.MobRendererFireflyCluster;
-import net.minecraft.client.render.entity.MobRendererGhast;
-import net.minecraft.client.render.entity.MobRendererGiant;
-import net.minecraft.client.render.entity.EntityRendererItem;
-import net.minecraft.client.render.entity.EntityRendererLightningBolt;
-import net.minecraft.client.render.entity.MobRenderer;
-import net.minecraft.client.render.entity.EntityRendererMinecart;
-import net.minecraft.client.render.entity.MobRendererBiped;
-import net.minecraft.client.render.entity.MobRendererHuman;
-import net.minecraft.client.render.entity.EntityRendererPainting;
-import net.minecraft.client.render.entity.MobRendererPig;
-import net.minecraft.client.render.entity.MobRendererPlayer;
-import net.minecraft.client.render.entity.MobRendererScorpion;
-import net.minecraft.client.render.entity.MobRendererSheep;
-import net.minecraft.client.render.entity.MobRendererSlime;
-import net.minecraft.client.render.entity.EntityRendererSprite;
-import net.minecraft.client.render.entity.MobRendererSpider;
-import net.minecraft.client.render.entity.MobRendererSquid;
-import net.minecraft.client.render.entity.EntityRendererTNT;
-import net.minecraft.client.render.entity.MobRendererWolf;
 import net.minecraft.client.render.model.ModelPig;
 import net.minecraft.client.render.model.ModelSheepOverlay;
 import net.minecraft.client.render.model.ModelSheepWool;
@@ -102,8 +74,8 @@ public class EntityRenderDispatcher
         renderers.put(MobWolf.class, new MobRendererWolf(new ModelWolf(0.0f), 0.5F));
         renderers.put(MobChicken.class, new MobRendererChicken(new ModelChicken(), 0.3F));
         renderers.put(MobCreeper.class, new MobRendererCreeper());
-        renderers.put(MobSkeleton.class, new MobRendererBiped<>(new ModelSkeleton(), 0.5F));
-        renderers.put(MobZombie.class, new MobRendererBiped<>(new ModelZombie(), 0.5F));
+        renderers.put(MobSkeleton.class, new MobRendererBipedArmored<>(new ModelSkeleton(), new ModelZombie(1.0f), new ModelZombie(0.5f), 0.5F));
+        renderers.put(MobZombie.class, new MobRendererBipedArmored<>(new ModelZombie(), new ModelZombie(1.0f), new ModelZombie(0.5f), 0.5F));
         renderers.put(MobSnowman.class, new MobRendererBiped<>(new ModelZombie(), 0.5F));
         renderers.put(MobZombieArmored.class, new MobRendererZombieArmored(new ModelZombie(), 0.5F));
         renderers.put(MobSlime.class, new MobRendererSlime(new ModelSlime(16), new ModelSlime(0), 0.25F));
@@ -111,6 +83,7 @@ public class EntityRenderDispatcher
         renderers.put(MobHuman.class, new MobRendererHuman());
         renderers.put(MobGiant.class, new MobRendererGiant(new ModelZombie(), 0.5F, 6F));
         renderers.put(MobGhast.class, new MobRendererGhast());
+        renderers.put(MobGhastVehicle.class, new MobRendererGhast());
         renderers.put(MobSquid.class, new MobRendererSquid(new ModelSquid(), 0.7F));
         renderers.put(Mob.class, new MobRenderer<>(new ModelBiped(), 0.5F));
         renderers.put(Entity.class, new EntityRendererDefault());
@@ -129,7 +102,7 @@ public class EntityRenderDispatcher
         renderers.put(EntityLightning.class, new EntityRendererLightningBolt());
         renderers.put(ProjectileCannonball.class, new EntityRendererCannonball());
         renderers.put(ProjectilePebble.class, new EntityRendererSprite<ProjectilePebble>(Items.AMMO_PEBBLE));
-
+        renderers.put(MobButterfly.class, new MobRendererButterfly());
 
         for (EntityRenderer<?> renderer : renderers.values())
         {
diff --git a/game/client/src/main/java/net/minecraft/client/render/Font.java b/game/client/src/main/java/net/minecraft/client/render/Font.java
index f6e52fb5d..45af56f0d 100644
--- a/game/client/src/main/java/net/minecraft/client/render/Font.java
+++ b/game/client/src/main/java/net/minecraft/client/render/Font.java
@@ -23,7 +23,9 @@ import java.util.regex.Pattern;
 
 public class Font {
     private static final Pattern FORMATTING_CODE_REGEX = Pattern.compile("(?i)\\u00A7[0-9A-FK-OR]");
+    private static final int MONOSPACE_WIDTH = 10;
 
+    private final boolean monospace;
     private final GameSettings gameSettings;
     private final Minecraft mc;
     private final byte[] charWidth;
@@ -51,6 +53,10 @@ public class Font {
     private boolean strikethroughStyle;
 
     public Font(Minecraft minecraft, GameSettings gameSettings, TextureManager textureManager) {
+        this(minecraft, gameSettings, textureManager, false);
+    }
+
+    public Font(Minecraft minecraft, GameSettings gameSettings, TextureManager textureManager, boolean monospace) {
         this.mc = minecraft;
         this.gameSettings = gameSettings;
         this.charWidth = new byte[65536];
@@ -67,13 +73,19 @@ public class Font {
         this.underlineStyle = false;
         this.strikethroughStyle = false;
         this.textureManager = textureManager;
+        this.monospace = monospace;
 
-        try (InputStream glyphStream = mc.texturePackList.getResourceAsStream("/font/glyph_sizes.bin");
-             InputStream glyphStreamDefault = mc.texturePackList.getResourceAsStream("/font/glyph_sizes_default.bin")) {
-            glyphStream.read(this.glyphWidth);
-            glyphStreamDefault.read(this.charWidth);
-        } catch (IOException e) {
-            throw new RuntimeException(e);
+        if (monospace) {
+            Arrays.fill(this.glyphWidth, (byte) MONOSPACE_WIDTH);
+            Arrays.fill(this.charWidth, (byte) MONOSPACE_WIDTH);
+        } else {
+            try (InputStream glyphStream = mc.texturePackList.getResourceAsStream("/font/glyph_sizes.bin");
+                 InputStream glyphStreamDefault = mc.texturePackList.getResourceAsStream("/font/glyph_sizes_default.bin")) {
+                glyphStream.read(this.glyphWidth);
+                glyphStreamDefault.read(this.charWidth);
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
         }
 
         for (int i = 0; i < 32; ++i) {
@@ -95,7 +107,7 @@ public class Font {
 
     private float renderCharAtPos(int formatCode, char c, boolean italic) {
         float charWidth;
-        if (c == 32)
+        if (c == 32 && !this.monospace)
         {
             charWidth = 4.0F;
         }
@@ -117,10 +129,19 @@ public class Font {
 
     private boolean loadDefaultTexture(int id) {
         boolean success = true;
-        String fontPath = String.format("/font/font_%02X.png", id);
+
+        String fontPath;
+        if (this.monospace) {
+            fontPath = String.format("/font/mono_%02X.png", id);
+        } else {
+            fontPath = String.format("/font/font_%02X.png", id);
+        }
         String glyphPath = String.format("/font/glyph_%02X.png", id);
 
         Texture fontTexture = this.textureManager.loadTextureNoDefault(fontPath);
+        if (fontTexture == null && this.monospace) {
+            fontTexture = this.textureManager.loadTextureNoDefault(String.format("/font/font_%02X.png", id));
+        }
         if (fontTexture == null) {
             fontTexture = this.textureManager.loadTextureNoDefault(glyphPath);
         }
@@ -580,6 +601,9 @@ public class Font {
     }
 
     public int getCharWidth(char c) {
+        if (this.monospace) {
+            return MONOSPACE_WIDTH / 2 + 1;
+        }
         /*if (c == '§') {
             return -1;
         } else */if (c == ' ') {
@@ -697,7 +721,7 @@ public class Font {
         } else {
             String var4 = s.substring(0, var3);
             char var5 = s.charAt(var3);
-            boolean var6 = var5 == 32 || var5 == 10;
+            boolean var6 = (var5 == 32 && !this.monospace) || var5 == 10;
             String var7 = getFormatFromString(var4) + s.substring(var3 + (var6 ? 1 : 0));
             return var4 + "\n" + this.wrapFormattedStringToWidth(var7, maxWidth);
         }
@@ -716,7 +740,9 @@ public class Font {
                     --var5;
                     break;
                 case ' ':
-                    var6 = var5;
+                    if (!monospace) {
+                        var6 = var5;
+                    }
                 default:
                     var4 += this.getCharWidth(var8);
                     if(var7) {
diff --git a/game/client/src/main/java/net/minecraft/client/render/LightmapHelper.java b/game/client/src/main/java/net/minecraft/client/render/LightmapHelper.java
index 671a831f6..a41faa6bf 100644
--- a/game/client/src/main/java/net/minecraft/client/render/LightmapHelper.java
+++ b/game/client/src/main/java/net/minecraft/client/render/LightmapHelper.java
@@ -48,7 +48,7 @@ public class LightmapHelper {
 			
 			String path = "/assets/minecraft/textures/environment/lightmap" + dimension + ".png";
 
-            LOGGER.info("Loading custom lightmap '{}'", path);
+            LOGGER.info("Attempting to load custom lightmap '{}'", path);
 			customLightmap = mc.textureManager.getImage(path);
 			
 			if(customLightmap == Textures.missingTexture) {
@@ -58,7 +58,7 @@ public class LightmapHelper {
 			if(customLightmap != null) {
 				LOGGER.info("Loaded custom lightmap!");
 			}else {
-				LOGGER.info("No custom lightmap found");
+				LOGGER.warn("No custom lightmap found.");
 			}
 		}else {
 			customLightmap = null;
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 9450e1ac9..b42777ffd 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
@@ -25,12 +25,15 @@ public class OpenGLHelper {
 	public static boolean gl31 = false;
 	
     public static void testCapabilities(Minecraft minecraft) {
+		LOGGER.info("");
+		LOGGER.info("=== GPU VENDOR INFO ===");
         LOGGER.info(glGetString(GL_VERSION));
         LOGGER.info(glGetString(GL_VENDOR));
         LOGGER.info(glGetString(GL_RENDERER));
-        
-        ContextCapabilities capabilities = GLContext.getCapabilities();
 
+		LOGGER.info("");
+
+        ContextCapabilities capabilities = GLContext.getCapabilities();
         LOGGER.info("GL_ARB_framebuffer_object: {}", capabilities.GL_ARB_framebuffer_object);
         LOGGER.info("GL_ARB_occlusion_query: {}", capabilities.GL_ARB_occlusion_query);
         LOGGER.info("GL_NV_fog_distance: {}", capabilities.GL_NV_fog_distance);
@@ -39,16 +42,19 @@ public class OpenGLHelper {
         enableSphericalFog = capabilities.GL_NV_fog_distance;
         gl20 = capabilities.OpenGL20;
         gl31 = capabilities.OpenGL31;
+
+		LOGGER.info("");
         
 		if(minecraft.gameSettings.disableShaders.value) {
 			Shaders.enableShaders = false;
-			LOGGER.info("Shaders disabled in options file!");
+			LOGGER.warn("Shaders disabled in options file!");
 			return;
 		}
 		
 		Shaders.enableShaders = capabilities.GL_ARB_framebuffer_object && capabilities.OpenGL20;
 
-        LOGGER.info("Enable Shaders: {}", Shaders.enableShaders);
+        LOGGER.info("Shaders enabled: {}", Shaders.enableShaders);
+		LOGGER.info("");
     }
 	
 	public static void checkError(String info) {
diff --git a/game/client/src/main/java/net/minecraft/client/render/PostProcessingManager.java b/game/client/src/main/java/net/minecraft/client/render/PostProcessingManager.java
index d51333690..55c1347e1 100644
--- a/game/client/src/main/java/net/minecraft/client/render/PostProcessingManager.java
+++ b/game/client/src/main/java/net/minecraft/client/render/PostProcessingManager.java
@@ -63,20 +63,29 @@ public class PostProcessingManager
         float surfaceness = adjustedY / hToQRange;
         surfaceness = MathHelper.clamp(surfaceness, 0.0f, 1.0f);
 
-        double temperature = mc.currentWorld.getBlockTemperature((int)mc.thePlayer.x, (int)mc.thePlayer.z); // Ranges from 0.0 to 1.0
-        if (temperature == Double.NEGATIVE_INFINITY) temperature = lastTemperature;
+        double t = mc.currentWorld.getBlockTemperature((int)mc.thePlayer.x, (int)mc.thePlayer.z); // Ranges from 0.0 to 1.0
+        if (t == Double.NEGATIVE_INFINITY) t = lastTemperature;
+        final double temperature = t;
         lastTemperature = temperature;
-        double humidity = mc.currentWorld.getBlockHumidity((int)mc.thePlayer.x, (int)mc.thePlayer.z); // Ranges from 0.0 to 1.0
-        if (humidity == Double.NEGATIVE_INFINITY) humidity = lastHumidity;
+        double h = mc.currentWorld.getBlockHumidity((int)mc.thePlayer.x, (int)mc.thePlayer.z); // Ranges from 0.0 to 1.0
+        if (h == Double.NEGATIVE_INFINITY) h = lastHumidity;
+        final double humidity = h;
         lastHumidity = humidity;
 
         // Perform base temperature change
         if (mc.currentWorld.dimension == Dimension.OVERWORLD)
         {
             lastTemperature = temperature;
-            temperature = (temperature * 2) - 1.0; // Ranges from -1.0 to 1.0
-            saturation += (float) (0.25 * temperature * surfaceness);
-            contrast += (float) (-0.1 * temperature * surfaceness);
+            double tt = (temperature * 2) - 1.0; // Ranges from -1.0 to 1.0
+            saturation += (float) (0.25 * tt * surfaceness);
+            contrast += (float) (-0.1 * tt * surfaceness);
+        }
+        if (mc.currentWorld.dimension == Dimension.NETHER) {
+            lastTemperature = temperature;
+            if (temperature < 0.5) {
+                double tt = temperature * 2; // Ranges from 0.0 to 1.0
+                saturation -= (float) (0.5 * (1.0 - tt));
+            }
         }
 
         // Perform season effects
@@ -142,15 +151,19 @@ public class PostProcessingManager
         }
 
         // Perform dimension effects
-        if (mc.currentWorld.getWorldType() == WorldTypes.NETHER_DEFAULT || mc.currentWorld.getWorldType() == WorldTypes.OVERWORLD_HELL)
+        if (mc.currentWorld.dimension == Dimension.NETHER || mc.currentWorld.getWorldType() == WorldTypes.OVERWORLD_HELL)
         {
-            rMod += 1.2f;
-            gMod += 0.3f;
-            saturation += 0.125f;
+            float scale = 1.0f;
+            if (mc.currentWorld.dimension == Dimension.NETHER && temperature < 0.5) {
+                scale = (float) temperature * 2.0f;
+            }
+            rMod += 1.2f * scale;
+            gMod += 0.3f * scale;
+            saturation += 0.125f * scale;
             exposure -= 0.125f;
             contrast -= 0.05f;
             brightness -= 0.05f;
-            heatHaze = 1f;
+            heatHaze = scale;
         }
 
         // Perform heatwave effects
diff --git a/game/client/src/main/java/net/minecraft/client/render/RenderBlocks.java b/game/client/src/main/java/net/minecraft/client/render/RenderBlocks.java
index a38c8bea9..959d7597a 100644
--- a/game/client/src/main/java/net/minecraft/client/render/RenderBlocks.java
+++ b/game/client/src/main/java/net/minecraft/client/render/RenderBlocks.java
@@ -32,6 +32,7 @@ public class RenderBlocks
         field_22352_G = 1;
         blockAccess = worldSource;
         renderBitMask = 0;
+        forceAO = false;
         this.mc = Minecraft.getMinecraft();
     }
 
@@ -49,6 +50,7 @@ public class RenderBlocks
         uvRotateBottom = 0;
         field_22352_G = 1;
         renderBitMask = 0;
+        forceAO = false;
         this.mc = Minecraft.getMinecraft();
     }
 
@@ -1047,6 +1049,7 @@ public class RenderBlocks
     public int lightmapCoordBottomRight;
     public int lightmapCoordTopRight; 
     public boolean overbright;
+    public boolean forceAO;
     public RenderBlockCache cache = new RenderBlockCache();
 
 }
diff --git a/game/client/src/main/java/net/minecraft/client/render/TextureManager.java b/game/client/src/main/java/net/minecraft/client/render/TextureManager.java
index 4735ca002..3f8597e1d 100644
--- a/game/client/src/main/java/net/minecraft/client/render/TextureManager.java
+++ b/game/client/src/main/java/net/minecraft/client/render/TextureManager.java
@@ -92,7 +92,8 @@ public class TextureManager {
                 addDynamicTexture(new DynamicTextureLavaStill(TextureRegistry.getTexture("minecraft:block/lava_still")));
                 addDynamicTexture(new DynamicTextureLavaFlowing(TextureRegistry.getTexture("minecraft:block/lava_flowing")));
 //                addDynamicTexture(new DynamicTexturePortal(TextureRegistry.getTexture("minecraft:block/portal_nether"),false));
-                addDynamicTexture(new DynamicTextureFire(TextureRegistry.getTexture("minecraft:block/fire")));
+                addDynamicTexture(new DynamicTextureFire(TextureRegistry.getTexture("minecraft:block/fire"), false));
+				addDynamicTexture(new DynamicTextureFire(TextureRegistry.getTexture("minecraft:block/fire_cold"), true));
 
                 String[] fireflycolors = new String[] {"blue", "orange", "green", "red"};
 				for (String color : fireflycolors) {
diff --git a/game/client/src/main/java/net/minecraft/client/render/WorldRenderer.java b/game/client/src/main/java/net/minecraft/client/render/WorldRenderer.java
index 7908c72c5..bb6ced0e5 100644
--- a/game/client/src/main/java/net/minecraft/client/render/WorldRenderer.java
+++ b/game/client/src/main/java/net/minecraft/client/render/WorldRenderer.java
@@ -6,6 +6,7 @@ import java.util.List;
 import java.util.Random;
 
 import com.mojang.logging.LogUtils;
+import net.minecraft.client.render.window.CursorShape;
 import net.minecraft.client.render.worldtype.WorldTypeFXDispatcher;
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.entity.Mob;
@@ -576,7 +577,11 @@ public class WorldRenderer
                 mouseX = (int) mc.controllerInput.cursorX;
                 mouseY = (int) mc.controllerInput.cursorY;
             }
+            mc.currentScreen.setDesiredCursor(CursorShape.ARROW);
             mc.currentScreen.render(mouseX, mouseY, partialTick);
+            if (mc.currentScreen != null && mc.gameWindow.getCursorShape() != mc.currentScreen.getDesiredCursor()) {
+                mc.gameWindow.setCursorShape(mc.currentScreen.getDesiredCursor());
+            }
             if(mc.inputType == InputType.CONTROLLER && mc.currentScreen != null) {
                 mc.currentScreen.drawControllerCursor();
             }
@@ -808,7 +813,7 @@ public class WorldRenderer
             }
             if(Blocks.blocksList[i2].getMaterial() == Material.lava)
             {
-                mc.particleEngine.add(new ParticleSmoke(world, (float)randX + f1, (double)((float)y + 0.1F) - Blocks.blocksList[i2].getBounds().minY, (float)randZ + f2, 0.0D, 0.0D, 0.0D));
+                mc.particleEngine.add(new ParticleSmoke(world, (float)randX + f1, (double)((float)y + 0.1F) - Blocks.blocksList[i2].getBounds().minY, (float)randZ + f2, 0.0D, 0.0D, 0.0D, 1.0f, 0));
                 continue;
             }
             if(random.nextInt(++l) == 0)
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/color/BlockColorCrystal.java b/game/client/src/main/java/net/minecraft/client/render/block/color/BlockColorCrystal.java
new file mode 100644
index 000000000..914bcdba7
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/render/block/color/BlockColorCrystal.java
@@ -0,0 +1,52 @@
+package net.minecraft.client.render.block.color;
+
+import net.minecraft.client.render.colorizer.Colorizer;
+import net.minecraft.core.world.WorldSource;
+import net.minecraft.core.world.noise.ImprovedNoise;
+
+import java.util.Random;
+
+public class BlockColorCrystal extends BlockColorCustom{
+
+    public BlockColorCrystal(Colorizer colorizer) {
+        super(colorizer);
+    }
+    protected static ImprovedNoise crystalPerlinNoise = new ImprovedNoise(new Random(0));
+    @Override
+    public int getWorldColor(WorldSource world, int x, int y, int z) {
+
+        int color = 0xc40e26;
+//        float offsetR = (float) (crystalPerlinNoise.getValue(x / 10f, z / 10f)) * 0.5f;
+//
+//        float r = ((color & 0xFF0000) >> 16) / 255F;
+//        float g = ((color & 0x00FF00) >> 8) / 255F;
+//        float b = (color & 0x0000FF) / 255F;
+//
+//        float newR = r + offsetR;
+//        float newG = g + offsetR;
+//        float newB = b + offsetR;
+//
+//        if(newR > 1.0f)
+//            newR = 1.0f;
+//        if(newR < 0.0f)
+//            newR = 0.0f;
+//
+//        if(newG > 1.0f)
+//            newG = 1.0f;
+//        if(newG < 0.0f)
+//            newG = 0.0f;
+//
+//        if(newB > 1.0f)
+//            newB = 1.0f;
+//        if(newB < 0.0f)
+//            newB = 0.0f;
+//
+//        int finalR = ((int) (newR * 255) & 0xFF) << 16;
+//        int finalG = ((int) (newG * 255) & 0xFF) << 8;
+//        int finalB = ((int) (newB * 255) & 0xFF);
+//
+//        color = finalR | finalG | finalB;
+
+        return color;
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/color/BlockColorDispatcher.java b/game/client/src/main/java/net/minecraft/client/render/block/color/BlockColorDispatcher.java
index 40deaa376..5cda20536 100644
--- a/game/client/src/main/java/net/minecraft/client/render/block/color/BlockColorDispatcher.java
+++ b/game/client/src/main/java/net/minecraft/client/render/block/color/BlockColorDispatcher.java
@@ -40,6 +40,9 @@ public final class BlockColorDispatcher
         addDispatch(Blocks.WIRE_REDSTONE, new BlockColorWireRedstone());
         addDispatch(Blocks.TALLGRASS, new BlockColorCustom(Colorizers.grass));
         addDispatch(Blocks.TALLGRASS_FERN, new BlockColorCustom(Colorizers.grass));
+        addDispatch(Blocks.CRYSTAL, new BlockColorCrystal(Colorizers.crystal));
+        addDispatch(Blocks.CRYSTAL_STALK, new BlockColorCrystal(Colorizers.crystal));
+        addDispatch(Blocks.NETHERRACK_CRYSTALLINE, new BlockColorCrystal(Colorizers.crystal));
     }
 
     @Override
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModel.java b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModel.java
index 03c18b0ff..283fbcc7a 100644
--- a/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModel.java
+++ b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModel.java
@@ -79,6 +79,7 @@ public abstract class BlockModel<T extends BlockLogic>
         renderBlocks.uvRotateBottom = 0;
         renderBlocks.field_22352_G = 1;
         renderBlocks.renderBitMask = 0;
+        renderBlocks.forceAO = false;
     }
     public void setRenderSide(Side side, boolean shouldRender){
         if (!shouldRender){
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelCobbledNetherrack.java b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelCobbledNetherrack.java
new file mode 100644
index 000000000..3ccfcec23
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelCobbledNetherrack.java
@@ -0,0 +1,25 @@
+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.block.material.Material;
+import net.minecraft.core.util.helper.Axis;
+import net.minecraft.core.util.helper.Side;
+import net.minecraft.core.world.WorldSource;
+
+public class BlockModelCobbledNetherrack<T extends BlockLogic> extends BlockModelStandard<T> {
+    private IconCoordinate ashSide = TextureRegistry.getTexture("minecraft:block/cobbled_netherrack/ashen_side");
+    public BlockModelCobbledNetherrack(Block<T> block) {
+        super(block);
+    }
+
+    @Override
+    public IconCoordinate getBlockTexture(WorldSource blockAccess, int x, int y, int z, Side side) {
+        Material material = blockAccess.getBlockMaterial(x, y + 1, z);
+        if ((material == Material.topAsh || material == Material.ash) && (side.getAxis() != Axis.Y)) {
+            return ashSide;
+        } else return super.getBlockTexture(blockAccess, x, y, z, side);
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelCrystal.java b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelCrystal.java
new file mode 100644
index 000000000..8a337f278
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelCrystal.java
@@ -0,0 +1,19 @@
+package net.minecraft.client.render.block.model;
+
+import net.minecraft.client.render.tessellator.Tessellator;
+import net.minecraft.core.block.Block;
+import net.minecraft.core.block.BlockLogic;
+
+public class BlockModelCrystal<T extends BlockLogic> extends BlockModelTransparent<T>{
+    public BlockModelCrystal(Block<T> block, boolean renderInside) {
+        super(block, renderInside);
+    }
+
+    @Override
+    public boolean render(Tessellator tessellator, int x, int y, int z) {
+        renderBlocks.forceAO = true;
+        boolean flag = super.render(tessellator, x, y, z);
+        renderBlocks.forceAO = false;
+        return flag;
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelCrystallineNetherrack.java b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelCrystallineNetherrack.java
new file mode 100644
index 000000000..ef7308b5d
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelCrystallineNetherrack.java
@@ -0,0 +1,74 @@
+package net.minecraft.client.render.block.model;
+
+import net.minecraft.client.render.RenderBlocks;
+import net.minecraft.client.render.block.color.BlockColorDispatcher;
+import net.minecraft.client.render.tessellator.Tessellator;
+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.Side;
+import net.minecraft.core.util.phys.AABB;
+import net.minecraft.core.world.WorldSource;
+import org.jetbrains.annotations.Nullable;
+import org.lwjgl.opengl.GL11;
+
+public class BlockModelCrystallineNetherrack<T extends BlockLogic> extends BlockModelStandard<T> {
+    protected IconCoordinate overlay;
+    public BlockModelCrystallineNetherrack(Block<T> block) {
+        super(block);
+        overlay = TextureRegistry.getTexture("minecraft:block/nether_crystal/overlay");
+    }
+
+    @Override
+    public boolean render(Tessellator tessellator, int x, int y, int z) {
+        AABB bounds = block.getBlockBoundsFromState(renderBlocks.blockAccess, x, y, z);
+        renderStandardBlock(tessellator, bounds, x, y, z, 1.0f, 1.0f, 1.0f);
+        renderBlocks.overrideBlockTexture = overlay;
+        renderStandardBlock(tessellator, bounds, x, y, z);
+        renderBlocks.overrideBlockTexture = null;
+        return true;
+    }
+    @Override
+    public void renderBlockOnInventory(Tessellator tessellator, int metadata, float brightness, float alpha, @Nullable Integer lightmapCoordinate) {
+        renderBlocks.useInventoryTint = false;
+        super.renderBlockOnInventory(tessellator, metadata, brightness, alpha, lightmapCoordinate);
+        renderBlocks.useInventoryTint = true;
+        GL11.glTranslatef(-0.5F, -0.5F, -0.5F);
+
+        int color = BlockColorDispatcher.getInstance().getDispatch(block).getFallbackColor(metadata);
+        float r = (float)(color >> 16 & 0xff) / 255F;
+        float g = (float)(color >> 8 & 0xff) / 255F;
+        float b = (float)(color & 0xff) / 255F;
+        GL11.glColor4f(r * brightness, g * brightness, b * brightness, alpha);
+
+        AABB bounds = block.getBounds();
+        IconCoordinate coord = overlay;
+
+        tessellator.startDrawingQuads();
+        tessellator.setNormal(0.0F, -1F, 0.0F);
+        renderBottomFace(tessellator, bounds, 0.0D, 0.0D, 0.0D, coord);
+        tessellator.draw();
+        tessellator.startDrawingQuads();
+        tessellator.setNormal(0.0F, 1.0F, 0.0F);
+        renderTopFace(tessellator, bounds, 0.0D, 0.0D, 0.0D, coord);
+        tessellator.draw();
+        tessellator.startDrawingQuads();
+        tessellator.setNormal(0.0F, 0.0F, -1F);
+        renderNorthFace(tessellator, bounds, 0.0D, 0.0D, 0.0D, coord);
+        tessellator.draw();
+        tessellator.startDrawingQuads();
+        tessellator.setNormal(0.0F, 0.0F, 1.0F);
+        renderSouthFace(tessellator, bounds, 0.0D, 0.0D, 0.0D, coord);
+        tessellator.draw();
+        tessellator.startDrawingQuads();
+        tessellator.setNormal(-1F, 0.0F, 0.0F);
+        renderWestFace(tessellator, bounds, 0.0D, 0.0D, 0.0D, coord);
+        tessellator.draw();
+        tessellator.startDrawingQuads();
+        tessellator.setNormal(1.0F, 0.0F, 0.0F);
+        renderEastFace(tessellator, bounds, 0.0D, 0.0D, 0.0D, coord);
+        tessellator.draw();
+        GL11.glTranslatef(0.5F, 0.5F, 0.5F);
+    }
+}
\ No newline at end of file
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 661b1477e..6159348c8 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
@@ -247,6 +247,7 @@ public class BlockModelDispatcher
 
         addDispatch(new BlockModelStandard<>(Blocks.SAND).setAllTextures(BLOCK_TEXTURES, "minecraft:block/sand"));
         addDispatch(new BlockModelStandard<>(Blocks.GRAVEL).setAllTextures(BLOCK_TEXTURES, "minecraft:block/gravel"));
+        addDispatch(new BlockModelStandard<>(Blocks.SULFUR).setAllTextures(BLOCK_TEXTURES, "minecraft:block/sulfur"));
 
         addDispatch(new BlockModelStandard<>(Blocks.BEDROCK).setAllTextures(BLOCK_TEXTURES, "minecraft:block/bedrock"));
         addDispatch(new BlockModelFullyRotatable<>(Blocks.BONESHALE)
@@ -396,6 +397,10 @@ public class BlockModelDispatcher
         addDispatch(new BlockModelStandard<>(Blocks.ORE_DIAMOND_GRANITE).setAllTextures(BLOCK_TEXTURES, "minecraft:block/ore/diamond/granite"));
         addDispatch(new BlockModelStandard<>(Blocks.ORE_DIAMOND_PERMAFROST).setAllTextures(BLOCK_TEXTURES, "minecraft:block/ore/diamond/permafrost"));
 
+        addDispatch(new BlockModelStandard<>(Blocks.CRYSTAL).setAllTextures(BLOCK_TEXTURES,"minecraft:block/nether_crystal/crystal"));
+        addDispatch(new BlockModelCrossedSquares<>(Blocks.CRYSTAL_STALK).setAllTextures(BLOCK_TEXTURES,"minecraft:block/nether_crystal/stalk"));
+        addDispatch(new BlockModelCrystallineNetherrack<>(Blocks.NETHERRACK_CRYSTALLINE).setAllTextures(BLOCK_TEXTURES,"minecraft:block/nether_crystal/crystal_netherrack"));
+
         addDispatch(new BlockModelStandard<>(Blocks.ORE_NETHERCOAL_NETHERRACK)
             .setAllTextures(BLOCK_TEXTURES, "minecraft:block/ore/nethercoal/netherrack")
             .setAllTextures(OVERBRIGHT_TEXTURES, "minecraft:block/ore/nethercoal/netherrack_overlay"));
@@ -579,6 +584,7 @@ public class BlockModelDispatcher
         addDispatch(new BlockModelBrazier<>(Blocks.BRAZIER_ACTIVE)
             .setTex(BLOCK_TEXTURES, "minecraft:block/brazier/base", S_TB)
             .setTex(BLOCK_TEXTURES, "minecraft:block/brazier/side", S_SIDES));
+        addDispatch(new BlockModelFire<>(Blocks.FIRE_COLD).setAllTextures(BLOCK_TEXTURES, "minecraft:block/fire_cold"));
 
         addDispatch(new BlockModelStandard<>(Blocks.MOBSPAWNER).setAllTextures(BLOCK_TEXTURES, "minecraft:block/mobspawner"));
         addDispatch(new BlockModelTransparent<>(Blocks.MOBSPAWNER_DEACTIVATED, true).setAllTextures(BLOCK_TEXTURES, "minecraft:block/mobspawner"));
@@ -642,11 +648,13 @@ public class BlockModelDispatcher
         addDispatch(new BlockModelLayer<>(Blocks.LAYER_SLATE)
             .setTex(BLOCK_TEXTURES, "minecraft:block/slate_top", S_TB)
             .setTex(BLOCK_TEXTURES, "minecraft:block/slate_side", S_SIDES));
+        addDispatch(new BlockModelLayer<>(Blocks.LAYER_ASH).setAllTextures(BLOCK_TEXTURES, "minecraft:block/block_ash"));
 
         addDispatch(new BlockModelIce<>(Blocks.ICE).onRenderLayer(1).setAllTextures(BLOCK_TEXTURES, "minecraft:block/ice"));
         addDispatch(new BlockModelStandard<>(Blocks.PERMAICE).setAllTextures(BLOCK_TEXTURES, "minecraft:block/permaice"));
 
         addDispatch(new BlockModelStandard<>(Blocks.BLOCK_SNOW).setAllTextures(BLOCK_TEXTURES, "minecraft:block/block_snow"));
+        addDispatch(new BlockModelStandard<>(Blocks.BLOCK_ASH).setAllTextures(BLOCK_TEXTURES, "minecraft:block/block_ash"));
 
         addDispatch(new BlockModelCactus<>(Blocks.CACTUS).withCustomItemBounds(0, 0, 0, 1, 1, 1)
             .setTex(BLOCK_TEXTURES, "minecraft:block/cactus/top", Side.TOP)
@@ -694,11 +702,11 @@ public class BlockModelDispatcher
             .setTex(OVERBRIGHT_TEXTURES, "minecraft:block/pumpkin_redstone/front_overlay", Side.NORTH));
 
 
-        addDispatch(new BlockModelStandard<>(Blocks.COBBLE_NETHERRACK).setAllTextures(BLOCK_TEXTURES, "minecraft:block/cobbled_netherrack"));
+        addDispatch(new BlockModelStandard<>(Blocks.COBBLE_NETHERRACK).setAllTextures(BLOCK_TEXTURES, "minecraft:block/cobbled_netherrack/normal"));
         addDispatch(new BlockModelStandard<>(Blocks.COBBLE_NETHERRACK_IGNEOUS)
-            .setAllTextures(BLOCK_TEXTURES, "minecraft:block/cobbled_netherrack_igneous")
-            .setAllTextures(OVERBRIGHT_TEXTURES, "minecraft:block/cobbled_netherrack_igneous_overlay"));
-        addDispatch(new BlockModelStandard<>(Blocks.COBBLE_NETHERRACK_MOSSY).setAllTextures(BLOCK_TEXTURES, "minecraft:block/cobbled_netherrack_mossy"));
+            .setAllTextures(BLOCK_TEXTURES, "minecraft:block/cobbled_netherrack/igneous")
+            .setAllTextures(OVERBRIGHT_TEXTURES, "minecraft:block/cobbled_netherrack/igneous_overlay"));
+        addDispatch(new BlockModelStandard<>(Blocks.COBBLE_NETHERRACK_MOSSY).setAllTextures(BLOCK_TEXTURES, "minecraft:block/cobbled_netherrack/mossy"));
         addDispatch(new BlockModelStandard<>(Blocks.NETHERRACK).setAllTextures(BLOCK_TEXTURES, "minecraft:block/netherrack"));
         addDispatch(new BlockModelStandard<>(Blocks.BRICK_NETHERRACK).setAllTextures(BLOCK_TEXTURES, "minecraft:block/brick_netherrack"));
         addDispatch(new BlockModelSlab<>(Blocks.SLAB_NETHERRACK_POLISHED));
@@ -735,6 +743,11 @@ public class BlockModelDispatcher
         addDispatch(new BlockModelLantern<>(Blocks.LANTERN_FIREFLY_RED).setAllTextures(BLOCK_TEXTURES, "minecraft:block/lantern_firefly/red"));
         addDispatch(new BlockModelJar<>(Blocks.JAR_GLASS));
 
+        addDispatch(new BlockModelJarButterfly<>(Blocks.JAR_BUTTERFLY_BLUE).withTextures("minecraft:block/jar_butterfly"));
+        addDispatch(new BlockModelJarButterfly<>(Blocks.JAR_BUTTERFLY_ORANGE).withTextures("minecraft:block/jar_butterfly"));
+        addDispatch(new BlockModelJarButterfly<>(Blocks.JAR_BUTTERFLY_PINK).withTextures("minecraft:block/jar_butterfly"));
+        addDispatch(new BlockModelJarButterfly<>(Blocks.JAR_BUTTERFLY_SILVER).withTextures("minecraft:block/jar_butterfly"));
+
         addDispatch(new BlockModelPebbles<>(Blocks.OVERLAY_PEBBLES));
 
         addDispatch(new BlockModelFenceThin<>(Blocks.FENCE_CHAINLINK,TextureRegistry.getTexture("minecraft:block/fence_chain/center"), null, TextureRegistry.getTexture("minecraft:block/fence_chain/top"), TextureRegistry.getTexture("minecraft:block/fence_chain/column")).setAllTextures(BLOCK_TEXTURES, "minecraft:block/fence_chain/center"));
@@ -746,5 +759,9 @@ public class BlockModelDispatcher
             .setTex(BLOCK_TEXTURES, "minecraft:block/basket/side", S_SIDES));
 
         addDispatch(new BlockModelStandard<>(Blocks.PAPER_WALL).setAllTextures(BLOCK_TEXTURES, "minecraft:block/paperwall"));
+
+        addDispatch(new BlockModelEmpty<>(Blocks.STATUE_STONE_LOWER).setAllTextures(BLOCK_TEXTURES, "minecraft:block/stone"));
+        addDispatch(new BlockModelEmpty<>(Blocks.STATUE_STONE_UPPER).setAllTextures(BLOCK_TEXTURES, "minecraft:block/stone"));
     }
 }
+
diff --git a/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelJarButterfly.java b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelJarButterfly.java
new file mode 100644
index 000000000..16ff1dd3c
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/render/block/model/BlockModelJarButterfly.java
@@ -0,0 +1,180 @@
+package net.minecraft.client.render.block.model;
+
+import net.minecraft.client.render.LightmapHelper;
+import net.minecraft.client.render.tessellator.Tessellator;
+import net.minecraft.client.render.texture.stitcher.IconCoordinate;
+import net.minecraft.core.block.Block;
+import net.minecraft.core.block.BlockLogic;
+import net.minecraft.core.block.BlockLogicJarButterfly;
+import net.minecraft.core.util.helper.Side;
+
+public class BlockModelJarButterfly<T extends BlockLogic> extends BlockModelStandard<T>{
+
+    public BlockModelJarButterfly(Block<T> block) {
+        super(block);
+    }
+
+    @Override
+    public boolean render(Tessellator tessellator, int x, int y, int z) {
+        int meta = renderBlocks.blockAccess.getBlockMetadata(x, y, z);
+        boolean isHanging = (meta & BlockLogicJarButterfly.MASK_HANGING) == 0;
+        boolean isAngled = (meta & BlockLogicJarButterfly.MASK_ANGLED) != 0;
+
+        float brightness = 1.0f;
+        if(LightmapHelper.isLightmapEnabled()) {
+            tessellator.setLightmapCoord(block.getLightmapCoord(renderBlocks.blockAccess, x, y, z));
+        }else {
+            brightness = getBlockBrightness(renderBlocks.blockAccess, x, y, z);
+        }
+        tessellator.setColorOpaque_F(brightness, brightness, brightness);
+
+        float yOffset = isHanging ? 0f : 0.375f;
+
+        float intMinX = x + 0.5f - (2f / 16f);
+        float intMinY = y + yOffset + (1f / 16f);
+        float intMinZ = z + 0.5f - (2f / 16f);
+        float intMaxX = x + 0.5f + (2f / 16f);
+        float intMaxY = y + (0.4375f + yOffset);
+        float intMaxZ = z + 0.5f + (2f / 16f);
+
+        float extMinX = x + 0.5f - (3f / 16f);
+        float extMinY = y + yOffset;
+        float extMinZ = z + 0.5f - (3f / 16f);
+        float extMaxX = x + 0.5f + (3f / 16f);
+        float extMaxY = y + (0.5f + yOffset);
+        float extMaxZ = z + 0.5f + (3f / 16f);
+
+        float hookMinX = x + 0.5f - (2f / 16f);
+        float hookMinZ = z + 0.5f - (2f / 16f);
+        float hookMaxX = x + 0.5f + (2f / 16f);
+        float hookMaxY = intMaxY + (4f / 16f);
+        float hookMaxZ = z + 0.5f + (2f / 16f);
+
+//        if (isAngled)
+//        {
+//            float angle = (float)(java.lang.Math.PI / 4);
+//            float centerX = x + 0.5f;
+//            float centerY = y;
+//            float centerZ = z + 0.5f;
+//
+//            float oldExtMaxY = extMaxY;
+//            float[] r = rotateAroundPoint(intMinX - centerX, intMinY - centerY, intMinZ - centerZ, 0, 0, angle);
+//            intMinX = r[0] + centerX;
+//            intMinY = r[1] + centerY;
+//            intMinZ = r[2] + centerZ;
+//
+//            r = rotateAroundPoint(intMaxX - centerX, intMaxY - centerY, intMaxZ - centerZ, 0, 0, angle);
+//            intMaxX = r[0] + centerX;
+//            intMaxY = r[1] + centerY;
+//            intMaxZ = r[2] + centerZ;
+//
+//            r = rotateAroundPoint(extMinX - centerX, extMinY - centerY, extMaxZ - centerZ, 0, 0, angle);
+//            extMinX = r[0] + centerX;
+//            extMinY = r[1] + centerY;
+//            extMinZ = r[2] + centerZ;
+//
+//            r = rotateAroundPoint(extMaxX - centerX, extMaxY - centerY, extMaxZ - centerZ, 0, 0, angle);
+//            extMaxX = r[0] + centerX;
+//            extMaxY = r[1] + centerY;
+//            extMaxZ = r[2] + centerZ;
+//
+//            r = rotateAroundPoint(hookMinX - centerX, oldExtMaxY - centerY, hookMinZ - centerZ, 0, 0, angle);
+//            hookMinX = r[0] + centerX;
+//            hookMinZ = r[2] + centerZ;
+//
+//            r = rotateAroundPoint(hookMaxX - centerX, hookMaxY - centerY, hookMaxZ - centerZ, 0, 0, angle);
+//            hookMaxX = r[0] + centerX;
+//            hookMaxY = r[1] + centerY;
+//            hookMaxZ = r[2] + centerZ;
+//        }
+
+        IconCoordinate texIndex = getBlockTextureFromSideAndMetadata(Side.BOTTOM, meta);
+
+        double texU = texIndex.getIconUMin();
+        double texV = texIndex.getIconVMin();
+
+        double onePix = 1f/16;
+
+        //exterior shell uvs
+        double extSideMinU = texU;
+        double extSideMaxU = texIndex.getSubIconU(onePix * 6);
+        double extSideMinV = texIndex.getSubIconV(onePix * 6);
+        double extSideMaxV = texIndex.getSubIconV(onePix * 14);
+
+        double extTopMinU = texU;
+        double extTopMaxU = texIndex.getSubIconU(onePix * 6);
+        double extTopMinV = texV;
+        double extTopMaxV = texIndex.getSubIconV(onePix * 6);
+
+        double extBottomMinU = texIndex.getSubIconU(onePix * 6);
+        double extBottomMaxU = texIndex.getSubIconU(onePix * 12);
+        double extBottomMinV = texV;
+        double extBottomMaxV = texIndex.getSubIconV(onePix * 6);
+
+        //hook uvs
+        double hookMinU = texIndex.getSubIconU(onePix * 10);
+        double hookMaxU = texIndex.getSubIconU(onePix * 16);
+        double hookMinV = texIndex.getSubIconV(onePix * 6);
+        double hookMaxV = texIndex.getSubIconV(onePix * 10);
+
+        // Render sides ext
+        tessellator.addVertexWithUV(extMaxX, extMinY, extMinZ, extSideMinU, extSideMaxV);
+        tessellator.addVertexWithUV(extMinX, extMinY, extMinZ, extSideMaxU, extSideMaxV);
+        tessellator.addVertexWithUV(extMinX, extMaxY, extMinZ, extSideMaxU, extSideMinV);
+        tessellator.addVertexWithUV(extMaxX, extMaxY, extMinZ, extSideMinU, extSideMinV);
+
+        tessellator.addVertexWithUV(extMinX, extMinY, extMaxZ, extSideMinU, extSideMaxV);
+        tessellator.addVertexWithUV(extMaxX, extMinY, extMaxZ, extSideMaxU, extSideMaxV);
+        tessellator.addVertexWithUV(extMaxX, extMaxY, extMaxZ, extSideMaxU, extSideMinV);
+        tessellator.addVertexWithUV(extMinX, extMaxY, extMaxZ, extSideMinU, extSideMinV);
+
+        tessellator.addVertexWithUV(extMaxX, extMinY, extMaxZ, extSideMinU, extSideMaxV);
+        tessellator.addVertexWithUV(extMaxX, extMinY, extMinZ, extSideMaxU, extSideMaxV);
+        tessellator.addVertexWithUV(extMaxX, extMaxY, extMinZ, extSideMaxU, extSideMinV);
+        tessellator.addVertexWithUV(extMaxX, extMaxY, extMaxZ, extSideMinU, extSideMinV);
+
+        tessellator.addVertexWithUV(extMinX, extMinY, extMinZ, extSideMinU, extSideMaxV);
+        tessellator.addVertexWithUV(extMinX, extMinY, extMaxZ, extSideMaxU, extSideMaxV);
+        tessellator.addVertexWithUV(extMinX, extMaxY, extMaxZ, extSideMaxU, extSideMinV);
+        tessellator.addVertexWithUV(extMinX, extMaxY, extMinZ, extSideMinU, extSideMinV);
+
+        // Render top ext
+        tessellator.addVertexWithUV(extMinX, extMaxY, extMaxZ, extTopMinU, extTopMaxV);
+        tessellator.addVertexWithUV(extMaxX, extMaxY, extMaxZ, extTopMaxU, extTopMaxV);
+        tessellator.addVertexWithUV(extMaxX, extMaxY, extMinZ, extTopMaxU, extTopMinV);
+        tessellator.addVertexWithUV(extMinX, extMaxY, extMinZ, extTopMinU, extTopMinV);
+
+        // Render bottom ext
+        tessellator.addVertexWithUV(extMinX, extMinY, extMinZ, extBottomMinU, extBottomMinV);
+        tessellator.addVertexWithUV(extMaxX, extMinY, extMinZ, extBottomMaxU, extBottomMinV);
+        tessellator.addVertexWithUV(extMaxX, extMinY, extMaxZ, extBottomMaxU, extBottomMaxV);
+        tessellator.addVertexWithUV(extMinX, extMinY, extMaxZ, extBottomMinU, extBottomMaxV);
+
+        // Render hook
+        tessellator.addVertexWithUV(hookMinX, intMaxY, hookMinZ, hookMinU, hookMaxV);
+        tessellator.addVertexWithUV(hookMaxX, intMaxY, hookMaxZ, hookMaxU, hookMaxV);
+        tessellator.addVertexWithUV(hookMaxX, hookMaxY, hookMaxZ, hookMaxU, hookMinV);
+        tessellator.addVertexWithUV(hookMinX, hookMaxY, hookMinZ, hookMinU, hookMinV);
+
+        tessellator.addVertexWithUV(hookMinX, hookMaxY, hookMinZ, hookMaxU, hookMinV);
+        tessellator.addVertexWithUV(hookMaxX, hookMaxY, hookMaxZ, hookMinU, hookMinV);
+        tessellator.addVertexWithUV(hookMaxX, intMaxY, hookMaxZ, hookMinU, hookMaxV);
+        tessellator.addVertexWithUV(hookMinX, intMaxY, hookMinZ, hookMaxU, hookMaxV);
+
+        tessellator.addVertexWithUV(hookMinX, intMaxY, intMaxZ, hookMinU, hookMaxV);
+        tessellator.addVertexWithUV(hookMaxX, intMaxY, intMinZ, hookMaxU, hookMaxV);
+        tessellator.addVertexWithUV(hookMaxX, hookMaxY, intMinZ, hookMaxU, hookMinV);
+        tessellator.addVertexWithUV(hookMinX, hookMaxY, intMaxZ, hookMinU, hookMinV);
+
+        tessellator.addVertexWithUV(hookMinX, hookMaxY, hookMaxZ, hookMaxU, hookMinV);
+        tessellator.addVertexWithUV(hookMaxX, hookMaxY, hookMinZ, hookMinU, hookMinV);
+        tessellator.addVertexWithUV(hookMaxX, intMaxY, hookMinZ, hookMinU, hookMaxV);
+        tessellator.addVertexWithUV(hookMinX, intMaxY, hookMaxZ, hookMaxU, hookMaxV);
+
+        return true;
+    }
+    @Override
+    public boolean shouldItemRender3d() {
+        return false;
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/render/blockentity/TileEntityRendererJarButterfly.java b/game/client/src/main/java/net/minecraft/client/render/blockentity/TileEntityRendererJarButterfly.java
new file mode 100644
index 000000000..3d54a0304
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/render/blockentity/TileEntityRendererJarButterfly.java
@@ -0,0 +1,13 @@
+package net.minecraft.client.render.blockentity;
+
+import net.minecraft.client.render.entity.MobRendererButterfly;
+import net.minecraft.client.render.tessellator.Tessellator;
+import net.minecraft.core.block.entity.TileEntityJarButterfly;
+import net.minecraft.core.util.helper.MathHelper;
+
+public class TileEntityRendererJarButterfly extends TileEntityRenderer<TileEntityJarButterfly>{
+    @Override
+    public void doRender(Tessellator tessellator, TileEntityJarButterfly tileEntity, double x, double y, double z, float partialTick) {
+        MobRendererButterfly.drawButterfly(tessellator, tileEntity.ticksRan, 0.70f, 0, x + 0.5, y - 0.125 + MathHelper.sin((tileEntity.ticksRan + partialTick)/20) * (0.0625), z + 0.5, partialTick, renderDispatcher.textureManager, "/assets/minecraft/textures/entity/butterfly/" + tileEntity.color.name + "/0_0.png");
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/render/blockentity/TileEntityRendererSign.java b/game/client/src/main/java/net/minecraft/client/render/blockentity/TileEntityRendererSign.java
index 519558e45..d9278d4a5 100644
--- a/game/client/src/main/java/net/minecraft/client/render/blockentity/TileEntityRendererSign.java
+++ b/game/client/src/main/java/net/minecraft/client/render/blockentity/TileEntityRendererSign.java
@@ -3,7 +3,10 @@ package net.minecraft.client.render.blockentity;
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.render.Font;
 import net.minecraft.client.render.LightmapHelper;
+import net.minecraft.client.render.model.ModelBase;
 import net.minecraft.client.render.model.ModelSign;
+import net.minecraft.client.render.model.ModelSkeleton;
+import net.minecraft.client.render.model.ModelZombie;
 import net.minecraft.client.render.tessellator.Tessellator;
 import net.minecraft.client.render.texture.stitcher.IconCoordinate;
 import net.minecraft.client.render.texture.stitcher.TextureRegistry;
@@ -18,8 +21,10 @@ import org.lwjgl.opengl.GL11;
 public class TileEntityRendererSign extends TileEntityRenderer<TileEntitySign>
 {
 
-	private final Minecraft mc = Minecraft.getMinecraft();
-	
+    private final Minecraft mc = Minecraft.getMinecraft();
+    private final ModelSign modelSign;
+
+
     public TileEntityRendererSign()
     {
         modelSign = new ModelSign();
@@ -79,7 +84,7 @@ public class TileEntityRendererSign extends TileEntityRenderer<TileEntitySign>
         EnumSignPicture picture = tileEntity.getPicture();
         drawTexturedModalRect(-48, -24, 48, 24, TextureRegistry.getTexture(picture.getTextureKey()));
         GL11.glPopMatrix();
-        
+
         // Render text
         float lightLevel = mc.currentWorld.getLightBrightness(tileEntity.x, tileEntity.y, tileEntity.z);
         float lightOffset = 0f;
@@ -91,7 +96,7 @@ public class TileEntityRendererSign extends TileEntityRenderer<TileEntitySign>
                 LightmapHelper.setLightmapCoord(LightmapHelper.getLightmapCoord(15, 15));
             }
         }
-        
+
         GL11.glPushMatrix();
         f4 = 0.01666667F * scale;
         GL11.glTranslatef(0.0F, 0.5F * scale, 0.065F * scale);
@@ -138,6 +143,4 @@ public class TileEntityRendererSign extends TileEntityRenderer<TileEntitySign>
         tessellator.draw();
     }
 
-
-    private final ModelSign modelSign;
 }
diff --git a/game/client/src/main/java/net/minecraft/client/render/blockentity/TileEntityRendererStatue.java b/game/client/src/main/java/net/minecraft/client/render/blockentity/TileEntityRendererStatue.java
new file mode 100644
index 000000000..be550f04f
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/render/blockentity/TileEntityRendererStatue.java
@@ -0,0 +1,121 @@
+package net.minecraft.client.render.blockentity;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.render.model.ModelBiped;
+import net.minecraft.client.render.model.ModelPlayer;
+import net.minecraft.client.render.tessellator.Tessellator;
+import net.minecraft.core.block.entity.TileEntityStatue;
+import net.minecraft.core.enums.HumanArmorShape;
+import net.minecraft.core.item.*;
+import org.lwjgl.opengl.GL11;
+
+public class TileEntityRendererStatue extends TileEntityRenderer<TileEntityStatue> {
+
+    private final Minecraft mc = Minecraft.getMinecraft();
+    private final ModelPlayer model;
+    private final ModelBiped modelArmorA;
+    private final ModelBiped modelArmorB;
+
+
+    public TileEntityRendererStatue() {
+        this.model = new ModelPlayer(0.0f);
+        this.modelArmorA = new ModelBiped(1.0f);
+        this.modelArmorB = new ModelBiped(0.5f);
+    }
+
+    public void renderAt(TileEntityStatue tileEntity, double x, double y, double z) {
+        TileEntityStatue.Pose pose = tileEntity.getPose();
+
+        final float scale = 0.0625f;
+        GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
+        loadTexture("/assets/minecraft/textures/entity/statue_stone.png");
+        GL11.glPushMatrix();
+        GL11.glTranslatef((float) x + 0.5f, (float) y + 1.5f, (float) z + 0.5f);
+        float rotation = (float)(tileEntity.getBlockMeta() * 360) / 16F;
+        GL11.glRotatef(-rotation, 0.0F, 1.0F, 0.0F);
+        GL11.glScalef(scale, -scale, -scale);
+        GL11.glDisable(GL11.GL_CULL_FACE);
+        renderModel(this.model, pose);
+        for (int i = 0; i < 4; i++) {
+            renderArmor(tileEntity, i, pose);
+        }
+        GL11.glEnable(GL11.GL_CULL_FACE);
+        GL11.glPopMatrix();
+    }
+
+    private void applyPose(TileEntityStatue.Pose pose, ModelBiped model) {
+        model.armLeft.xRot = pose.leftArmPitch;
+        model.armRight.xRot = pose.rightArmPitch;
+        model.legLeft.xRot = pose.leftLegPitch;
+        model.legRight.xRot = pose.rightLegPitch;
+        model.head.xRot = pose.headPitch;
+        model.head.yRot = pose.headYaw;
+    }
+
+    private void renderModel(ModelBiped model, TileEntityStatue.Pose pose) {
+        final float scale = 1.0f;
+
+        model.setupAnimation(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, scale);
+        applyPose(pose, model);
+
+        model.head.render(scale);
+        model.body.render(scale);
+        model.armRight.render(scale);
+        model.armLeft.render(scale);
+        model.legRight.render(scale);
+        model.legLeft.render(scale);
+        model.hair.render(scale);
+    }
+
+    @Override
+    public void doRender(Tessellator tessellator, TileEntityStatue tileEntity, double x, double y, double z,
+                         float partialTick) {
+        renderAt(tileEntity, x, y, z);
+    }
+
+    private boolean renderArmor(TileEntityStatue entity, int layer, TileEntityStatue.Pose pose) {
+        ItemStack itemstack = entity.getItemInArmorSlot(HumanArmorShape.values()[(entity.getNumArmorSlots() - 1) - layer]);
+        if (itemstack != null) {
+            Item item = itemstack.getItem();
+            ModelBiped toDraw = null;
+            if (item instanceof ItemQuiver) {
+                loadTexture("/assets/minecraft/textures/armor/quiver.png");
+                ModelBiped modelBiped = modelArmorA;
+                modelBiped.body.visible = layer == 1 || layer == 2;
+                toDraw = modelBiped;
+            }
+            if (item instanceof ItemQuiverEndless) {
+                loadTexture("/assets/minecraft/textures/armor/quiver_golden.png");
+                ModelBiped modelBiped = modelArmorA;
+                modelBiped.body.visible = layer == 1 || layer == 2;
+                toDraw = modelBiped;
+            }
+            if (item == Items.ARMOR_BOOTS_ICESKATES) {
+                loadTexture("/assets/minecraft/textures/armor/skates.png");
+                ModelBiped modelBiped = modelArmorA;
+                modelBiped.legRight.visible = layer == 2 || layer == 3;
+                modelBiped.legLeft.visible = layer == 2 || layer == 3;
+                toDraw = modelBiped;
+            }
+            if (item instanceof IArmorItem && ((IArmorItem) item).getArmorMaterial() != null) {
+                IArmorItem armorItem = (IArmorItem) item;
+                loadTexture(String.format("/assets/%s/textures/armor/%s_%d.png", armorItem.getArmorMaterial().identifier.namespace, armorItem.getArmorMaterial().identifier.value, (layer != 2 ? 1 : 2)));
+                ModelBiped modelBiped = layer != 2 ? modelArmorA : modelArmorB;
+                modelBiped.head.visible = layer == 0;
+                modelBiped.hair.visible = layer == 0;
+                modelBiped.body.visible = layer == 1 || layer == 2;
+                modelBiped.armRight.visible = layer == 1;
+                modelBiped.armLeft.visible = layer == 1;
+                modelBiped.legRight.visible = layer == 2 || layer == 3;
+                modelBiped.legLeft.visible = layer == 2 || layer == 3;
+                toDraw = modelBiped;
+            }
+
+            if (toDraw != null) {
+                renderModel(toDraw, pose);
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/render/colorizer/Colorizers.java b/game/client/src/main/java/net/minecraft/client/render/colorizer/Colorizers.java
index 47cd1d8a5..1d8363eeb 100644
--- a/game/client/src/main/java/net/minecraft/client/render/colorizer/Colorizers.java
+++ b/game/client/src/main/java/net/minecraft/client/render/colorizer/Colorizers.java
@@ -40,6 +40,7 @@ public class Colorizers {
 	public static Colorizer cacao;
 	public static Colorizer thorn;
 	public static Colorizer palm;
+	public static Colorizer crystal;
 
 	private static Dimension lastDimension;
 	private static Season lastSeason;
@@ -83,6 +84,7 @@ public class Colorizers {
 		cacao = add(new Colorizer("cacao"));
 		thorn = add(new Colorizer("thorn"));
 		palm = add(new Colorizer("palm"));
+		crystal = add(new Colorizer("crystal"));
 	}
 	
 	public static void update(boolean force) {
diff --git a/game/client/src/main/java/net/minecraft/client/render/dynamictexture/DynamicTextureFire.java b/game/client/src/main/java/net/minecraft/client/render/dynamictexture/DynamicTextureFire.java
index efc511ab2..9e5b5511a 100644
--- a/game/client/src/main/java/net/minecraft/client/render/dynamictexture/DynamicTextureFire.java
+++ b/game/client/src/main/java/net/minecraft/client/render/dynamictexture/DynamicTextureFire.java
@@ -4,15 +4,18 @@ import net.minecraft.client.render.texture.stitcher.IconCoordinate;
 import org.jetbrains.annotations.NotNull;
 
 public class DynamicTextureFire extends DynamicTexture{
-	
 	protected float[] arr1;
 	protected float[] arr2;
 
 	private int height;
 	private float scaleFactor;
 
-	public DynamicTextureFire(@NotNull IconCoordinate targetTexture) {
+	private final boolean cold;
+
+	public DynamicTextureFire(@NotNull IconCoordinate targetTexture, boolean cold) {
 		super(targetTexture);
+
+		this.cold = cold;
 	}
 
 	@Override
@@ -64,15 +67,21 @@ public class DynamicTextureFire extends DynamicTexture{
 				l = 0.0F;
 			}
 
-			int r = (int)(l * 155.0F + 100.0F);
-			int g = (int)(l * l * 255.0F);
-			int b = (int)(l * l * l * l * l * l * l * l * l * l * 255.0F);
+			int r, g, b;
+			if (cold) {
+				r = (int)(l * 155.0F + 100.0F);
+				g = (int)(l * 155.0F + 100.0F);
+				b = (int)(l * 155.0F + 100.0F);
+			} else {
+				r = (int)(l * 155.0F + 100.0F);
+				g = (int)(l * l * 255.0F);
+				b = (int)(l * l * l * l * l * l * l * l * l * l * 255.0F);
+			}
 			short a = 255;
 			if(l < 0.5F) {
 				a = 0;
 			}
 
-
 			this.imageData[i * 4 + 0] = (byte)r;
 			this.imageData[i * 4 + 1] = (byte)g;
 			this.imageData[i * 4 + 2] = (byte)b;
diff --git a/game/client/src/main/java/net/minecraft/client/render/entity/MobRenderer.java b/game/client/src/main/java/net/minecraft/client/render/entity/MobRenderer.java
index fe24d1e57..15553c76d 100644
--- a/game/client/src/main/java/net/minecraft/client/render/entity/MobRenderer.java
+++ b/game/client/src/main/java/net/minecraft/client/render/entity/MobRenderer.java
@@ -266,8 +266,7 @@ public class MobRenderer<T extends Mob> extends EntityRenderer<T>
         {
             bindTexture(entity.getDefaultEntityTexture());
         }
-        else
-        {
+        else {
             bindTexture(entity.getEntityTexture());
         }
     }
diff --git a/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererBiped.java b/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererBiped.java
index 1bdee29f8..d2be306e3 100644
--- a/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererBiped.java
+++ b/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererBiped.java
@@ -1,5 +1,6 @@
 package net.minecraft.client.render.entity;
 
+import net.minecraft.client.render.block.model.BlockModel;
 import net.minecraft.client.render.item.model.ItemModelDispatcher;
 import net.minecraft.client.render.model.ModelBiped;
 import net.minecraft.client.render.tessellator.Tessellator;
@@ -10,22 +11,25 @@ import org.lwjgl.opengl.GL11;
 
 public class MobRendererBiped<T extends Mob> extends MobRenderer<T>
 {
-    public MobRendererBiped(ModelBiped model, float shadowSize)
+	protected ModelBiped modelBipedMain;
+
+
+	public MobRendererBiped(ModelBiped model, float shadowSize)
     {
         super(model, shadowSize);
-        modelBipedMain = model;
-    }
+
+        this.modelBipedMain = model;
+	}
 
 	@Override
 	protected void renderAdditional(T entity, float f) {
 		ItemStack itemstack = entity.getHeldItem();
 		if(itemstack != null) {
 			GL11.glPushMatrix();
-			modelBipedMain.armRight.translateTo(0.0625F);
+			this.modelBipedMain.armRight.translateTo(0.0625F);
+			BlockModel.setRenderBlocks(this.renderDispatcher.itemRenderer.renderBlocksInstance);
 			ItemModelDispatcher.getInstance().getDispatch(itemstack.getItem()).renderItemThirdPerson(Tessellator.instance, renderDispatcher.itemRenderer, entity, itemstack, true);
 			GL11.glPopMatrix();
 		}
 	}
-
-    protected ModelBiped modelBipedMain;
 }
diff --git a/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererBipedArmored.java b/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererBipedArmored.java
new file mode 100644
index 000000000..c1fc9e3b2
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererBipedArmored.java
@@ -0,0 +1,134 @@
+package net.minecraft.client.render.entity;
+
+import net.minecraft.client.entity.player.PlayerLocal;
+import net.minecraft.client.render.block.model.BlockModelDispatcher;
+import net.minecraft.client.render.item.model.ItemModelDispatcher;
+import net.minecraft.client.render.model.ModelBiped;
+import net.minecraft.client.render.tessellator.Tessellator;
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.entity.IArmorWearing;
+import net.minecraft.core.entity.Mob;
+import net.minecraft.core.enums.HumanArmorShape;
+import net.minecraft.core.item.*;
+import org.jetbrains.annotations.Nullable;
+import org.lwjgl.opengl.GL11;
+
+public class MobRendererBipedArmored<T extends Mob & IArmorWearing<HumanArmorShape>> extends MobRendererBiped<T> {
+    protected ModelBiped modelArmorA;
+    protected ModelBiped modelArmorB;
+
+    public MobRendererBipedArmored(ModelBiped model, float shadowSize) {
+        this(model, new ModelBiped(1.0F), new ModelBiped(0.5f), shadowSize);
+    }
+
+    public MobRendererBipedArmored(ModelBiped model, ModelBiped modelArmorA, ModelBiped modelArmorB, float shadowSize) {
+        super(model, shadowSize);
+
+        this.modelArmorA = modelArmorA;
+        this.modelArmorB = modelArmorB;
+    }
+
+    @Override
+    protected void renderAdditional(T entity, float f) {
+        final @Nullable ItemStack stack = entity.getItemInArmorSlot(HumanArmorShape.HEAD);
+        if(stack != null) {
+            GL11.glPushMatrix();
+            this.modelBipedMain.head.translateTo(0.0625F);
+            if(stack.itemID < Blocks.blocksList.length && BlockModelDispatcher.getInstance().getDispatch(Blocks.getBlock(stack.itemID)).shouldItemRender3d())
+            {
+                float scale = 0.625F;
+                GL11.glTranslatef(0.0F, -0.25F, 0.0F);
+                GL11.glRotatef(180F, 0.0F, 1.0F, 0.0F);
+                GL11.glScalef(scale, -scale, scale);
+                ItemModelDispatcher.getInstance().getDispatch(stack).renderItem(Tessellator.instance, renderDispatcher.itemRenderer, entity, stack);
+            } else if (!(stack.getItem() instanceof IArmorItem)) {
+                GL11.glEnable(GL11.GL_CULL_FACE);
+                float scale = 0.625F;
+                GL11.glTranslatef(0.0F, -0.8F, 0.0F);
+                GL11.glRotatef(180, 0.0F, 0.0F, 1.0F);
+                GL11.glScalef(scale, scale, scale);
+                ItemModelDispatcher.getInstance().getDispatch(stack).renderItemInWorld(Tessellator.instance, entity, stack, entity.getBrightness(f), 1f, true);
+                GL11.glDisable(GL11.GL_CULL_FACE);
+            }
+            GL11.glPopMatrix();
+        }
+
+        super.renderAdditional(entity, f);
+    }
+
+    @Override
+    public void render(Tessellator tessellator, T entity, double x, double y, double z, float yaw, float partialTick) {
+        GL11.glEnable(GL11.GL_BLEND);
+        GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
+
+        ItemStack itemstack = entity.getHeldItem();
+
+        this.modelArmorA.holdingRightHand = this.modelArmorB.holdingRightHand = this.modelBipedMain.holdingRightHand |= itemstack != null;
+        this.modelArmorA.sneaking = this.modelArmorB.sneaking = this.modelBipedMain.sneaking = entity.isSneaking();
+        this.modelArmorA.isRiding = this.modelArmorB.isRiding = this.modelBipedMain.isRiding = entity.isPassenger();
+
+        double realY = y - (double)entity.heightOffset;
+        if(entity.isSneaking() && !(entity instanceof PlayerLocal)) {
+            realY -= 0.125D;
+        }
+
+        super.render(tessellator, entity, x, realY, z, yaw, partialTick);
+
+        this.modelArmorA.holdingLarge = this.modelArmorB.holdingLarge = this.modelBipedMain.holdingLarge = false;
+        this.modelArmorA.sneaking = this.modelArmorB.sneaking = this.modelBipedMain.sneaking = false;
+        this.modelArmorA.holdingRightHand = this.modelArmorB.holdingRightHand = this.modelBipedMain.holdingRightHand = false;
+        this.modelArmorA.holdingLeftHand = this.modelArmorB.holdingLeftHand = this.modelBipedMain.holdingLeftHand = false;
+
+        GL11.glDisable(GL11.GL_BLEND);
+    }
+
+    @Override
+    protected boolean prepareArmor(T entity, int layer, float partialTick) {
+        ItemStack itemstack = entity.getItemInArmorSlot(HumanArmorShape.values()[(entity.getNumArmorSlots() - 1) - layer]);
+        if(itemstack != null)
+        {
+            Item item = itemstack.getItem();
+            if(item instanceof ItemQuiver)
+            {
+                bindTexture("/assets/minecraft/textures/armor/quiver.png");
+                ModelBiped modelBiped = modelArmorA;
+                modelBiped.body.visible = layer == 1 || layer == 2;
+                setArmorModel(modelBiped);
+                return true;
+            }
+            if(item instanceof ItemQuiverEndless)
+            {
+                bindTexture("/assets/minecraft/textures/armor/quiver_golden.png");
+                ModelBiped modelBiped = modelArmorA;
+                modelBiped.body.visible = layer == 1 || layer == 2;
+                setArmorModel(modelBiped);
+                return true;
+            }
+            if(item == Items.ARMOR_BOOTS_ICESKATES)
+            {
+                bindTexture("/assets/minecraft/textures/armor/skates.png");
+                ModelBiped modelBiped = modelArmorA;
+                modelBiped.legRight.visible = layer == 2 || layer == 3;
+                modelBiped.legLeft.visible = layer == 2 || layer == 3;
+                setArmorModel(modelBiped);
+                return true;
+            }
+            if(item instanceof IArmorItem && ((IArmorItem) item).getArmorMaterial() != null)
+            {
+                IArmorItem armorItem = (IArmorItem)item;
+                bindTexture(String.format("/assets/%s/textures/armor/%s_%d.png", armorItem.getArmorMaterial().identifier.namespace, armorItem.getArmorMaterial().identifier.value, (layer != 2 ? 1 : 2)));
+                ModelBiped modelBiped = layer != 2 ? modelArmorA : modelArmorB;
+                modelBiped.head.visible = layer == 0;
+                modelBiped.hair.visible = layer == 0;
+                modelBiped.body.visible = layer == 1 || layer == 2;
+                modelBiped.armRight.visible = layer == 1;
+                modelBiped.armLeft.visible = layer == 1;
+                modelBiped.legRight.visible = layer == 2 || layer == 3;
+                modelBiped.legLeft.visible = layer == 2 || layer == 3;
+                setArmorModel(modelBiped);
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererButterfly.java b/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererButterfly.java
new file mode 100644
index 000000000..d782a3b58
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererButterfly.java
@@ -0,0 +1,81 @@
+package net.minecraft.client.render.entity;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.render.TextureManager;
+import net.minecraft.client.render.tessellator.Tessellator;
+import net.minecraft.core.entity.animal.MobButterfly;
+import net.minecraft.core.util.helper.MathHelper;
+import org.lwjgl.opengl.GL11;
+import org.lwjgl.opengl.GL12;
+
+import java.util.Random;
+
+public class MobRendererButterfly extends EntityRenderer<MobButterfly> {
+    private static final Random sizeRandom = new Random();
+    @Override
+    public void render(Tessellator tessellator, MobButterfly butterfly, double x, double y, double z, float yaw, float partialTick) {
+        // 0.45 -> 0.8
+        sizeRandom.setSeed((long) butterfly.getSkinVariant() << 16);
+        float scale = 0.45f + (sizeRandom.nextFloat() * 0.35f);
+        float yRot = MathHelper.lerp(butterfly.yRotO, butterfly.yRot, partialTick);
+        drawButterfly(tessellator, butterfly.tickCount, scale, yRot, x, y, z, partialTick, renderDispatcher.textureManager, getTexture(butterfly));
+    }
+
+    @Override
+    public void renderPreview(Tessellator tessellator, MobButterfly butterfly, double x, double y, double z, float yaw, float partialTick) {
+        float yRot = MathHelper.lerp(butterfly.yRotO, butterfly.yRot, partialTick);
+        drawButterfly(tessellator, butterfly.tickCount, 1.25f, -yRot, x, y + 0.5, z, partialTick, renderDispatcher.textureManager, getTexture(butterfly));
+    }
+
+    public static void drawButterfly(Tessellator tessellator, int ticksAlive, float scale, float yRot, double x, double y, double z, float partialTick, TextureManager textureManager, String texture) {
+        textureManager.bindTexture(textureManager.loadTexture(texture));
+        GL11.glPushMatrix();
+        GL11.glTranslatef((float) x, (float) y + 0.25f, (float) z);
+        GL11.glEnable(GL12.GL_RESCALE_NORMAL);
+        GL11.glDisable(GL11.GL_CULL_FACE);
+        GL11.glDisable(GL11.GL_LIGHTING);
+        GL11.glScalef(0.5F * scale, 0.5F * scale, 0.5F * scale);
+
+        GL11.glRotatef(yRot, 0, 1, 0);
+
+        GL11.glRotatef(-22.5f, 1, 0, 0);
+
+        float xOff = -0.5F;
+        float zOff = -0.5F;
+
+        float flapAngle = (MathHelper.sin(ticksAlive + partialTick) + 1) * 22.5f + 5;
+
+        GL11.glPushMatrix();
+        GL11.glRotatef(-flapAngle, 0, 0, 1);
+        tessellator.startDrawingQuads();
+        tessellator.addVertexWithUV(xOff,       0.0D, zOff,       0,   1);
+        tessellator.addVertexWithUV(xOff + 0.5, 0.0D, zOff,       0.5, 1);
+        tessellator.addVertexWithUV(xOff + 0.5, 0.0D, zOff + 1.0, 0.5, 0);
+        tessellator.addVertexWithUV(xOff,       0.0D, zOff + 1.0, 0,   0);
+        tessellator.draw();
+        GL11.glPopMatrix();
+
+        GL11.glPushMatrix();
+        GL11.glRotatef(flapAngle, 0, 0, 1);
+        tessellator.startDrawingQuads();
+        tessellator.addVertexWithUV(xOff + 0.5, 0.0D, zOff,       0.5, 1);
+        tessellator.addVertexWithUV(xOff + 1.0, 0.0D, zOff,       1.0, 1);
+        tessellator.addVertexWithUV(xOff + 1.0, 0.0D, zOff + 1.0, 1.0, 0);
+        tessellator.addVertexWithUV(xOff + 0.5, 0.0D, zOff + 1.0, 0.5, 0);
+        tessellator.draw();
+        GL11.glPopMatrix();
+
+        GL11.glDisable(GL12.GL_RESCALE_NORMAL);
+        GL11.glEnable(GL11.GL_CULL_FACE);
+        GL11.glEnable(GL11.GL_LIGHTING);
+        GL11.glPopMatrix();
+    }
+
+    public String getTexture(MobButterfly butterfly) {
+        if (!Minecraft.getMinecraft().gameSettings.mobVariants.value) {
+            return butterfly.getDefaultEntityTexture();
+        } else {
+            return butterfly.getEntityTexture();
+        }
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererPlayer.java b/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererPlayer.java
index da22957f8..c838aa0d5 100644
--- a/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererPlayer.java
+++ b/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererPlayer.java
@@ -2,7 +2,6 @@ package net.minecraft.client.render.entity;
 
 import net.minecraft.client.Minecraft;
 
-import net.minecraft.client.entity.player.PlayerLocal;
 import net.minecraft.client.render.BlockEntityRenderDispatcher;
 import net.minecraft.client.render.Font;
 import net.minecraft.client.render.Lighting;
@@ -13,30 +12,23 @@ import net.minecraft.client.render.block.model.BlockModel;
 import net.minecraft.client.render.block.model.BlockModelDispatcher;
 import net.minecraft.client.render.blockentity.TileEntityRenderer;
 import net.minecraft.client.render.camera.EntityCamera;
-import net.minecraft.client.render.item.model.ItemModelDispatcher;
-import net.minecraft.client.render.model.ModelBiped;
 import net.minecraft.client.render.model.ModelPlayer;
 import net.minecraft.client.render.model.ModelPlayerSlim;
 import net.minecraft.client.render.tessellator.Tessellator;
 import net.minecraft.client.render.texture.stitcher.TextureRegistry;
 import net.minecraft.core.Global;
-import net.minecraft.core.block.Blocks;
 import net.minecraft.core.block.entity.TileEntity;
 import net.minecraft.core.block.motion.CarriedBlock;
 import net.minecraft.core.entity.player.Player;
-import net.minecraft.core.item.*;
 import net.minecraft.core.player.gamemode.Gamemode;
 import net.minecraft.core.util.helper.MathHelper;
 import net.minecraft.core.world.BlocksContainer;
 import org.lwjgl.opengl.GL11;
 
-public class MobRendererPlayer extends MobRenderer<Player>
+public class MobRendererPlayer extends MobRendererBipedArmored<Player>
 {
-    private ModelBiped modelBipedMain;
     private final ModelPlayer modelThick;
     private final ModelPlayer modelSlim;
-    private final ModelBiped modelArmorChestplate;
-    private final ModelBiped modelArmor;
 
     private BlocksContainer container = null;
     private RenderBlocks containerRenderBlock = null;
@@ -44,94 +36,10 @@ public class MobRendererPlayer extends MobRenderer<Player>
     public MobRendererPlayer()
     {
         super(new ModelPlayer(0.0F), 0.5F);
-        modelBipedMain = (ModelBiped)mainModel;
-        modelArmorChestplate = new ModelBiped(1.0F);
-        modelArmor = new ModelBiped(0.5F);
         modelThick = new ModelPlayer(0.0f);
         modelSlim = new ModelPlayerSlim(0.0f);
     }
-    protected void renderSpecials(Player player, float partialTick)
-    {
-        if (Minecraft.getMinecraft().thePlayer.getGamemode() == Gamemode.spectator && player.getGamemode() == Gamemode.spectator){
-            GL11.glColor4f(1f, 1f, 1f, 0.25f);
-        } else if (player.getGamemode() == Gamemode.spectator) {
-            GL11.glColor4f(1f, 1f, 1f, 0.0f);
-        }
-        ItemStack itemstack = player.inventory.armorItemInSlot(IArmorItem.PIECE_HEAD);
-        if(itemstack != null)
-        {
-            GL11.glPushMatrix();
-            modelBipedMain.head.translateTo(0.0625F);
-            if(itemstack.itemID < Blocks.blocksList.length && BlockModelDispatcher.getInstance().getDispatch(Blocks.getBlock(itemstack.itemID)).shouldItemRender3d())
-            {
-                float scale = 0.625F;
-                GL11.glTranslatef(0.0F, -0.25F, 0.0F);
-                GL11.glRotatef(180F, 0.0F, 1.0F, 0.0F);
-                GL11.glScalef(scale, -scale, scale);
-                ItemModelDispatcher.getInstance().getDispatch(itemstack).renderItem(Tessellator.instance, renderDispatcher.itemRenderer, player, itemstack);
-            } else if (!(itemstack.getItem() instanceof IArmorItem)) {
-                GL11.glEnable(GL11.GL_CULL_FACE);
-                float scale = 0.625F;
-                GL11.glTranslatef(0.0F, -0.8F, 0.0F);
-                GL11.glRotatef(180, 0.0F, 0.0F, 1.0F);
-                GL11.glScalef(scale, scale, scale);
-                ItemModelDispatcher.getInstance().getDispatch(itemstack).renderItemInWorld(Tessellator.instance, player, itemstack, player.getBrightness(partialTick), 1f, true);
-                GL11.glDisable(GL11.GL_CULL_FACE);
-            }
-            GL11.glPopMatrix();
-        }
-
-        boolean renderCape = bindDownloadableTexture("https://api.betterthanadventure.net/capes?username=" + player.username, null, null);
-        if (!renderCape)
-        {
-            renderCape = bindDownloadableTexture(player.capeURL, null, null);
-        }
-    	
-    	if(renderCape) {
-            GL11.glPushMatrix();
-            float brightness = (Global.accessor.isFullbrightEnabled() || LightmapHelper.isLightmapEnabled()) ? 1 : player.getBrightness(partialTick);
-            GL11.glColor4f(brightness, brightness, brightness, 1f);
-            GL11.glTranslatef(0.0F, 0.0F, 0.125F);
-            float yawOff = player.yBodyRotO + (player.yBodyRot - player.yBodyRotO) * partialTick;
-            float bodyAngle = 5;
-            if(player.isSneaking())
-            {
-                bodyAngle += 25F;
-            }
-            double _xd = player.xdO + (player.xd - player.xdO) * partialTick;
-            double _yd = Math.min(player.ydO + (player.yd - player.ydO) * partialTick, 0);
-            double _zd = player.zd0 + (player.zd - player.zd0) * partialTick;
-            double vel = -1/(3 * Math.hypot(_xd, _zd) + 1) + 1;
-            double moveAng = Math.atan2(_xd, _zd);
-            double yawRad = Math.toRadians(yawOff);
-            double multiplier = 1 - Math.abs((((Math.cos(yawRad) + 1) - (Math.cos(moveAng) + 1) + 2)/2) -1); // Hell code, basically measures how similar the angle you're facing is to the angle you're moving, with it returning 1 if the same angle and 0 if opposite angles
-            player.wobbleTimer += (float) (((player.tickCount + partialTick) - player.lastRenderTick)/(30 - 29 * MathHelper.clamp(vel, 0, 1)));
-            player.lastRenderTick = player.tickCount;
-            double wobble = Math.sin(player.wobbleTimer) * (1.5 + 4.5 * vel * multiplier);
-            GL11.glRotatef((float) MathHelper.clamp((MathHelper.clamp(bodyAngle + vel * 100 * multiplier, bodyAngle, 100f) + wobble) - _yd * 60, 0, 180), 1.0F, 0.0F, 0.0F);
-            GL11.glRotatef(0, 0.0F, 0.0F, 1.0F);
-            GL11.glRotatef(0, 0.0F, 1.0F, 0.0F);
-            GL11.glRotatef(180F, 0.0F, 1.0F, 0.0F);
-            modelBipedMain.renderCloak(0.0625F);
-            GL11.glPopMatrix();
-    	}
 
-		ItemStack itemstack1 = player.inventory.getCurrentItem();
-		if(itemstack1 != null) {
-            GL11.glPushMatrix();
-            modelBipedMain.armRight.translateTo(0.0625F);
-            BlockModel.setRenderBlocks(renderDispatcher.itemRenderer.renderBlocksInstance);
-            ItemModelDispatcher.getInstance().getDispatch(itemstack1).renderItemThirdPerson(Tessellator.instance, renderDispatcher.itemRenderer, player, itemstack1, true);
-            GL11.glPopMatrix();
-		}
-
-        if (player.getHeldObject() != null) {
-            float walkSpeed = player.walkAnimSpeedO + (player.walkAnimSpeed - player.walkAnimSpeedO) * partialTick;
-            float walkProgress = player.walkAnimPos - player.walkAnimSpeed * (1.0F - partialTick);
-            GL11.glTranslatef(0, MathHelper.cos(walkProgress/2 * 2/3f)/26f * walkSpeed + 0.1f, 0);
-            drawHeldObject(player, partialTick);
-        }
-	}
     public void drawFirstPersonHand(Player player, boolean isLeft)
     {
         mainModel = player.slimModel ? modelSlim : modelThick;
@@ -288,59 +196,57 @@ public class MobRendererPlayer extends MobRenderer<Player>
     }
 
     @Override
-    protected boolean prepareArmor(Player entity, int layer, float partialTick) {
-        ItemStack itemstack = entity.inventory.armorItemInSlot(IArmorItem.PIECE_HEAD - layer);
-        if(itemstack != null)
+    protected void renderAdditional(Player player, float partialTick)
+    {
+        if (Minecraft.getMinecraft().thePlayer.getGamemode() == Gamemode.spectator && player.getGamemode() == Gamemode.spectator){
+            GL11.glColor4f(1f, 1f, 1f, 0.25f);
+        } else if (player.getGamemode() == Gamemode.spectator) {
+            GL11.glColor4f(1f, 1f, 1f, 0.0f);
+        }
+
+        super.renderAdditional(player, partialTick);
+
+        boolean renderCape = bindDownloadableTexture("https://api.betterthanadventure.net/capes?username=" + player.username, null, null);
+        if (!renderCape)
         {
-            Item item = itemstack.getItem();
-            if(item instanceof ItemQuiver)
-            {
-                bindTexture("/assets/minecraft/textures/armor/quiver.png");
-                ModelBiped modelBiped = modelArmorChestplate;
-                modelBiped.body.visible = layer == 1 || layer == 2;
-                setArmorModel(modelBiped);
-                return true;
-            }
-            if(item instanceof ItemQuiverEndless)
-            {
-                bindTexture("/assets/minecraft/textures/armor/quiver_golden.png");
-                ModelBiped modelBiped = modelArmorChestplate;
-                modelBiped.body.visible = layer == 1 || layer == 2;
-                setArmorModel(modelBiped);
-                return true;
-            }
-            if(item == Items.ARMOR_BOOTS_ICESKATES)
-            {
-                bindTexture("/assets/minecraft/textures/armor/skates.png");
-                ModelBiped modelBiped = modelArmorChestplate;
-                modelBiped.legRight.visible = layer == 2 || layer == 3;
-                modelBiped.legLeft.visible = layer == 2 || layer == 3;
-                setArmorModel(modelBiped);
-                return true;
-            }
-            if(item instanceof IArmorItem && ((IArmorItem) item).getArmorMaterial() != null)
+            renderCape = bindDownloadableTexture(player.capeURL, null, null);
+        }
+
+        if(renderCape) {
+            GL11.glPushMatrix();
+            float brightness = (Global.accessor.isFullbrightEnabled() || LightmapHelper.isLightmapEnabled()) ? 1 : player.getBrightness(partialTick);
+            GL11.glColor4f(brightness, brightness, brightness, 1f);
+            GL11.glTranslatef(0.0F, 0.0F, 0.125F);
+            float yawOff = player.yBodyRotO + (player.yBodyRot - player.yBodyRotO) * partialTick;
+            float bodyAngle = 5;
+            if(player.isSneaking())
             {
-                IArmorItem armorItem = (IArmorItem)item;
-                bindTexture(String.format("/assets/%s/textures/armor/%s_%d.png", armorItem.getArmorMaterial().identifier.namespace, armorItem.getArmorMaterial().identifier.value, (layer != 2 ? 1 : 2)));
-                ModelBiped modelBiped = layer != 2 ? modelArmorChestplate : modelArmor;
-                modelBiped.head.visible = layer == 0;
-                modelBiped.hair.visible = layer == 0;
-                modelBiped.body.visible = layer == 1 || layer == 2;
-                modelBiped.armRight.visible = layer == 1;
-                modelBiped.armLeft.visible = layer == 1;
-                modelBiped.legRight.visible = layer == 2 || layer == 3;
-                modelBiped.legLeft.visible = layer == 2 || layer == 3;
-                setArmorModel(modelBiped);
-                return true;
+                bodyAngle += 25F;
             }
+            double _xd = player.xdO + (player.xd - player.xdO) * partialTick;
+            double _yd = Math.min(player.ydO + (player.yd - player.ydO) * partialTick, 0);
+            double _zd = player.zd0 + (player.zd - player.zd0) * partialTick;
+            double vel = -1/(3 * Math.hypot(_xd, _zd) + 1) + 1;
+            double moveAng = Math.atan2(_xd, _zd);
+            double yawRad = Math.toRadians(yawOff);
+            double multiplier = 1 - Math.abs((((Math.cos(yawRad) + 1) - (Math.cos(moveAng) + 1) + 2)/2) -1); // Hell code, basically measures how similar the angle you're facing is to the angle you're moving, with it returning 1 if the same angle and 0 if opposite angles
+            player.wobbleTimer += (float) (((player.tickCount + partialTick) - player.lastRenderTick)/(30 - 29 * MathHelper.clamp(vel, 0, 1)));
+            player.lastRenderTick = player.tickCount;
+            double wobble = Math.sin(player.wobbleTimer) * (1.5 + 4.5 * vel * multiplier);
+            GL11.glRotatef((float) MathHelper.clamp((MathHelper.clamp(bodyAngle + vel * 100 * multiplier, bodyAngle, 100f) + wobble) - _yd * 60, 0, 180), 1.0F, 0.0F, 0.0F);
+            GL11.glRotatef(0, 0.0F, 0.0F, 1.0F);
+            GL11.glRotatef(0, 0.0F, 1.0F, 0.0F);
+            GL11.glRotatef(180F, 0.0F, 1.0F, 0.0F);
+            modelBipedMain.renderCloak(0.0625F);
+            GL11.glPopMatrix();
         }
-        return false;
-    }
 
-    @Override
-    protected void renderAdditional(Player entity, float partialTick)
-    {
-        renderSpecials(entity, partialTick);
+        if (player.getHeldObject() != null) {
+            float walkSpeed = player.walkAnimSpeedO + (player.walkAnimSpeed - player.walkAnimSpeedO) * partialTick;
+            float walkProgress = player.walkAnimPos - player.walkAnimSpeed * (1.0F - partialTick);
+            GL11.glTranslatef(0, MathHelper.cos(walkProgress/2 * 2/3f)/26f * walkSpeed + 0.1f, 0);
+            drawHeldObject(player, partialTick);
+        }
     }
 
     @Override
@@ -373,40 +279,17 @@ public class MobRendererPlayer extends MobRenderer<Player>
     public void render(Tessellator tessellator, Player entity, double x, double y, double z,
                        float yaw, float partialTick)
     {
-        GL11.glEnable(GL11.GL_BLEND);
-        GL11.glBlendFunc(770, 771);
-        mainModel = entity.slimModel ? modelSlim : modelThick;
-        modelBipedMain = entity.slimModel ? modelSlim : modelThick;
+        this.mainModel = entity.slimModel ? this.modelSlim : this.modelThick;
+        this.modelBipedMain = entity.slimModel ? this.modelSlim : this.modelThick;
+        this.modelArmorA.holdingLarge = this.modelArmorB.holdingLarge = this.modelBipedMain.holdingLarge = entity.getHeldObject() != null;
+        this.modelArmorA.holdingRightHand = this.modelArmorB.holdingRightHand = this.modelBipedMain.holdingRightHand |= entity.getHeldObject() != null;
+        this.modelArmorA.holdingLeftHand = this.modelArmorB.holdingLeftHand = this.modelBipedMain.holdingLeftHand |= entity.getHeldObject() != null;
 
-        ItemStack itemstack = entity.inventory.getCurrentItem();
-        modelArmorChestplate.holdingLarge = modelArmor.holdingLarge = modelBipedMain.holdingLarge = entity.getHeldObject() != null;
-
-        modelArmorChestplate.holdingRightHand = modelArmor.holdingRightHand = modelBipedMain.holdingRightHand = itemstack != null;
-        modelArmorChestplate.holdingRightHand = modelArmor.holdingRightHand = modelBipedMain.holdingRightHand |= entity.getHeldObject() != null;
-        modelArmorChestplate.holdingLeftHand = modelArmor.holdingLeftHand = modelBipedMain.holdingLeftHand |= entity.getHeldObject() != null;
-        modelArmorChestplate.sneaking = modelArmor.sneaking = modelBipedMain.sneaking = entity.isSneaking();
-        modelArmorChestplate.isRiding = modelArmor.isRiding = modelBipedMain.isRiding = entity.isPassenger();
-        double d3 = y - (double)entity.heightOffset;
-        if(entity.isSneaking() && !(entity instanceof PlayerLocal))
-        {
-            d3 -= 0.125D;
-        }
-        super.render(tessellator, entity, x, d3, z, yaw, partialTick);
-        modelArmorChestplate.holdingLarge = modelArmor.holdingLarge = modelBipedMain.holdingLarge = false;
-        modelArmorChestplate.sneaking = modelArmor.sneaking = modelBipedMain.sneaking = false;
-        modelArmorChestplate.holdingRightHand = modelArmor.holdingRightHand = modelBipedMain.holdingRightHand = false;
-        modelArmorChestplate.holdingLeftHand = modelArmor.holdingLeftHand = modelBipedMain.holdingLeftHand = false;
-        GL11.glDisable(GL11.GL_BLEND);
+        super.render(tessellator, entity, x, y, z, yaw, partialTick);
     }
 
     @Override
     public void loadEntityTexture(Player entity) {
         bindDownloadableTexture(entity.skinURL, entity.getEntityTexture(), PlayerSkinParser.instance);
     }
-
-    @Override
-    protected float limbSway(Player entity, float partialTick)
-    {
-        return super.limbSway(entity, partialTick);
-    }
 }
diff --git a/game/client/src/main/java/net/minecraft/client/render/item/model/ItemModelDispatcher.java b/game/client/src/main/java/net/minecraft/client/render/item/model/ItemModelDispatcher.java
index fdc7a9ff0..3a87550ae 100644
--- a/game/client/src/main/java/net/minecraft/client/render/item/model/ItemModelDispatcher.java
+++ b/game/client/src/main/java/net/minecraft/client/render/item/model/ItemModelDispatcher.java
@@ -1,6 +1,7 @@
 package net.minecraft.client.render.item.model;
 
 import net.minecraft.client.util.dispatch.Dispatcher;
+import net.minecraft.core.Global;
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.item.Item;
 import net.minecraft.core.item.Items;
@@ -229,6 +230,13 @@ public class ItemModelDispatcher extends Dispatcher<Item, ItemModel> {
         addDispatch(new ItemModelBlock((ItemBlock<?>) Blocks.PORTAL_PARADISE.asItem()).setFullBright());
         addDispatch(new ItemModelBlock((ItemBlock<?>) Blocks.PORTAL_NETHER.asItem()).setFullBright());
 
+        addDispatch(new ItemModelStandard(Items.JAR_BUTTERFLY_BLUE, "minecraft"));
+        addDispatch(new ItemModelStandard(Items.JAR_BUTTERFLY_ORANGE, "minecraft"));
+        addDispatch(new ItemModelStandard(Items.JAR_BUTTERFLY_PINK, "minecraft"));
+        addDispatch(new ItemModelStandard(Items.JAR_BUTTERFLY_SILVER, "minecraft"));
+
+        addDispatch(new ItemModelStandard(Items.STATUE_STONE, "minecraft"));
+
         for (int i = 0; i < Blocks.blocksList.length; i++) {
             if (Blocks.blocksList[i] == null) continue;
             Item item = Blocks.blocksList[i].asItem();
@@ -236,5 +244,12 @@ public class ItemModelDispatcher extends Dispatcher<Item, ItemModel> {
             if (!(item instanceof ItemBlock)) continue;
             addDispatch(new ItemModelBlock((ItemBlock<?>) item));
         }
+
+        if (Global.ASSERTIONS_ENABLED) {
+            for (Item item : Item.itemsList) {
+                if (item == null) continue;
+                assert dispatches.containsKey(item) : "Item '" + item.namespaceID + "' was not assigned an item model!";
+            }
+        }
     }
 }
diff --git a/game/client/src/main/java/net/minecraft/client/render/shader/Shaders.java b/game/client/src/main/java/net/minecraft/client/render/shader/Shaders.java
index f70129a94..732d52dd4 100644
--- a/game/client/src/main/java/net/minecraft/client/render/shader/Shaders.java
+++ b/game/client/src/main/java/net/minecraft/client/render/shader/Shaders.java
@@ -8,6 +8,7 @@ import net.minecraft.client.gui.ScreenPhotoMode;
 import net.minecraft.client.render.camera.CameraUtil;
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.block.material.Material;
+import net.minecraft.core.enums.HumanArmorShape;
 import net.minecraft.core.item.IArmorItem;
 import net.minecraft.core.item.ItemStack;
 
@@ -76,7 +77,7 @@ public abstract class Shaders {
 		shader.uniformInt("dimension", minecraft.currentWorld != null && minecraft.currentWorld.dimension != null ? minecraft.currentWorld.dimension.id : 0);
 		shader.uniformInt("cbCorrectionMode", minecraft.gameSettings.colorblindnessFix.value.ordinal());
 
-		ItemStack helmet = minecraft.thePlayer == null ? null : minecraft.thePlayer.inventory.armorItemInSlot(IArmorItem.PIECE_HEAD);
+		ItemStack helmet = minecraft.thePlayer == null ? null : minecraft.thePlayer.getItemInArmorSlot(HumanArmorShape.HEAD);
 		shader.uniformInt("shaderEffect", helmet != null && helmet.itemID == Blocks.PUMPKIN_REDSTONE.id() ? 1 : 0);
 
 		int isEyeInLiquid = 0;
diff --git a/game/client/src/main/java/net/minecraft/client/render/terrain/RenderList.java b/game/client/src/main/java/net/minecraft/client/render/terrain/RenderList.java
index 6e428881a..95afc0524 100644
--- a/game/client/src/main/java/net/minecraft/client/render/terrain/RenderList.java
+++ b/game/client/src/main/java/net/minecraft/client/render/terrain/RenderList.java
@@ -8,6 +8,8 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
+import com.mojang.logging.CategorizedLogger;
+import com.mojang.logging.LogUtils;
 import net.minecraft.client.render.terrain.VertexBuffer.Entry;
 import net.minecraft.core.util.helper.Buffer;
 
@@ -17,6 +19,7 @@ import net.minecraft.core.util.helper.Buffer;
  * <br> Entries can be toggled visible or invisible quickly
  */
 public class RenderList {
+	private static final CategorizedLogger LOGGER = LogUtils.getLogger();
 	
 	public final VertexBuffer vertexBuffer;
 	
@@ -150,7 +153,7 @@ public class RenderList {
 		if(buffer.capacity() < requiredCapacity) {
 			int newCapacity = Math.max(buffer.capacity() * 2, requiredCapacity);
 			
-			System.out.println("Expand RenderList IntBuffer " + buffer.capacity() + " -> " + newCapacity);
+			LOGGER.warn("Expanding RenderList IntBuffer from " + buffer.capacity() + " to " + newCapacity + " ints...");
 			
 			return Buffer.createBuffer(newCapacity << 2).asIntBuffer();
 		}
diff --git a/game/client/src/main/java/net/minecraft/client/render/terrain/VertexBuffer.java b/game/client/src/main/java/net/minecraft/client/render/terrain/VertexBuffer.java
index 655877eb9..6b2ed0a73 100644
--- a/game/client/src/main/java/net/minecraft/client/render/terrain/VertexBuffer.java
+++ b/game/client/src/main/java/net/minecraft/client/render/terrain/VertexBuffer.java
@@ -7,6 +7,8 @@ import java.nio.ByteOrder;
 import java.util.ArrayList;
 import java.util.List;
 
+import com.mojang.logging.CategorizedLogger;
+import com.mojang.logging.LogUtils;
 import org.lwjgl.opengl.GL31;
 
 import net.minecraft.client.render.OpenGLHelper;
@@ -17,7 +19,7 @@ import net.minecraft.client.render.OpenGLHelper;
  * <br> When the buffer is full, it will automatically expand
  */
 public class VertexBuffer {
-	
+	public static final CategorizedLogger LOGGER = LogUtils.getLogger();
 	private int vbo;
 	private long capacity;
 	
@@ -118,8 +120,8 @@ public class VertexBuffer {
 			// future proof
 			throw new RuntimeException();
 		}
-		
-		System.out.println("Expand VBO Buffer: " + newCapacity);
+
+		LOGGER.warn("Expand VBO Buffer: " + newCapacity);
 		
 		OpenGLHelper.checkError("pre expand buffer");
 		
diff --git a/game/client/src/main/java/net/minecraft/client/render/texture/stitcher/AtlasStitcher.java b/game/client/src/main/java/net/minecraft/client/render/texture/stitcher/AtlasStitcher.java
index 7d968f262..7caf870bd 100644
--- a/game/client/src/main/java/net/minecraft/client/render/texture/stitcher/AtlasStitcher.java
+++ b/game/client/src/main/java/net/minecraft/client/render/texture/stitcher/AtlasStitcher.java
@@ -47,11 +47,11 @@ public class AtlasStitcher extends Texture {
         this.mipmap = mipmap;
         this.defaultTexture = defaultTexture;
     }
-    protected IconCoordinate getTexture(@NotNull NamespaceID id){
+    protected IconCoordinate getTexture(@NotNull NamespaceID sourceId, @NotNull NamespaceID id){
         if (textureMap.containsKey(id)) return textureMap.get(id);
 
 
-        IconCoordinate iconCoordinate = new IconCoordinate(this, id, getSourceImagePath(id));
+        IconCoordinate iconCoordinate = new IconCoordinate(this, sourceId, id, getSourceImagePath(id));
         textureMap.put(id, iconCoordinate);
         return iconCoordinate;
     }
diff --git a/game/client/src/main/java/net/minecraft/client/render/texture/stitcher/IconCoordinate.java b/game/client/src/main/java/net/minecraft/client/render/texture/stitcher/IconCoordinate.java
index baa93cde0..d169f72d3 100644
--- a/game/client/src/main/java/net/minecraft/client/render/texture/stitcher/IconCoordinate.java
+++ b/game/client/src/main/java/net/minecraft/client/render/texture/stitcher/IconCoordinate.java
@@ -21,10 +21,12 @@ public class IconCoordinate implements Comparable<IconCoordinate> {
     protected final String sourceImage;
     protected final TextureMetaHandler metaHandler = new TextureMetaHandler();
     public final NamespaceID namespaceId;
+    public final NamespaceID sourceNamespaceId;
     public final AtlasStitcher parentAtlas;
     private BufferedImage _imageStorage;
-    public IconCoordinate(AtlasStitcher stitcher, NamespaceID namespaceId, String sourceImage){
+    public IconCoordinate(AtlasStitcher stitcher, NamespaceID sourceNamespaceId, NamespaceID namespaceId, String sourceImage){
         this.parentAtlas = stitcher;
+        this.sourceNamespaceId = sourceNamespaceId;
         this.namespaceId = namespaceId;
         this.sourceImage = sourceImage;
     }
diff --git a/game/client/src/main/java/net/minecraft/client/render/texture/stitcher/TextureRegistry.java b/game/client/src/main/java/net/minecraft/client/render/texture/stitcher/TextureRegistry.java
index d1da1f01c..58c39dfbb 100644
--- a/game/client/src/main/java/net/minecraft/client/render/texture/stitcher/TextureRegistry.java
+++ b/game/client/src/main/java/net/minecraft/client/render/texture/stitcher/TextureRegistry.java
@@ -27,6 +27,7 @@ import java.util.stream.Stream;
 public class TextureRegistry {
     private static final Logger LOGGER = LogUtils.getLogger();
     public static HashMap<String, AtlasStitcher> stitcherMap = new HashMap<>();
+    protected static HashMap<AtlasStitcher, String> stitcherMapReverse = new HashMap<>();
     @SuppressWarnings("unused")
     public static AtlasStitcher blockAtlas = register("block", new AtlasStitcher("textures/block", false, true, "/assets/minecraft/textures/block/texture_missing.png"));
     @SuppressWarnings("unused")
@@ -57,7 +58,7 @@ public class TextureRegistry {
             target.append("/").append(split2[i]);
         }
 
-        return atlas.getTexture(new NamespaceID(id.namespace, target.toString()));
+        return atlas.getTexture(id, new NamespaceID(id.namespace, target.toString()));
     }
     public static boolean hasTexture(@NotNull String key) {
         try {
@@ -80,6 +81,7 @@ public class TextureRegistry {
     }
     public static AtlasStitcher register(String id, AtlasStitcher atlas){
         stitcherMap.put(id, atlas);
+        stitcherMapReverse.put(atlas, id);
         return atlas;
     }
     static {
@@ -101,7 +103,8 @@ public class TextureRegistry {
 //        System.out.println("Printing " + path);
         for (String s : files) {
 //            System.out.println(s);
-            atlas.getTexture(new NamespaceID(namespace, s.replace(".png", "").replace("//", "/")));
+            NamespaceID id = new NamespaceID(namespace, s.replace(".png", "").replace("//", "/"));
+            atlas.getTexture(new NamespaceID(id.namespace, stitcherMapReverse.get(atlas) + "/" + id.value), id);
         }
     }
 
diff --git a/game/client/src/main/java/net/minecraft/client/render/texturepack/TexturePackList.java b/game/client/src/main/java/net/minecraft/client/render/texturepack/TexturePackList.java
index 30261446e..51ba2a7a7 100644
--- a/game/client/src/main/java/net/minecraft/client/render/texturepack/TexturePackList.java
+++ b/game/client/src/main/java/net/minecraft/client/render/texturepack/TexturePackList.java
@@ -2,6 +2,7 @@ package net.minecraft.client.render.texturepack;
 
 import com.mojang.logging.LogUtils;
 import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.keyboard.KeyboardLayout;
 import net.minecraft.client.render.Font;
 import net.minecraft.client.sound.SoundRepository;
 import org.jetbrains.annotations.Nullable;
@@ -96,6 +97,8 @@ public class TexturePackList
 		}
 		mc.textureManager.updateDynamicTextures(false);
 		SoundRepository.reload();
+
+		KeyboardLayout.defaultLayout = new KeyboardLayout();
 	}
 
 	public boolean updateAvailableTexturePacks() {
diff --git a/game/client/src/main/java/net/minecraft/client/render/window/CursorShape.java b/game/client/src/main/java/net/minecraft/client/render/window/CursorShape.java
new file mode 100644
index 000000000..6201887c1
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/render/window/CursorShape.java
@@ -0,0 +1,10 @@
+package net.minecraft.client.render.window;
+
+public enum CursorShape {
+    ARROW,
+    I_BEAM,
+    CROSSHAIR,
+    HAND,
+    HORIZONTAL_RESIZE,
+    VERTICAL_RESIZE
+}
diff --git a/game/client/src/main/java/net/minecraft/client/render/window/GameWindow.java b/game/client/src/main/java/net/minecraft/client/render/window/GameWindow.java
index f33aaaea4..a9b0e1cd8 100644
--- a/game/client/src/main/java/net/minecraft/client/render/window/GameWindow.java
+++ b/game/client/src/main/java/net/minecraft/client/render/window/GameWindow.java
@@ -5,32 +5,36 @@ import net.minecraft.core.UnexpectedThrowable;
 
 public interface GameWindow {
 	
-	public void init(Minecraft minecraft, int width, int height);
+	void init(Minecraft minecraft, int width, int height);
 	
-	public void toggleFullscreen();
+	void toggleFullscreen();
 	
-	public void updateWindowState();
+	void updateWindowState();
 	
-	public void displayCrashReport(UnexpectedThrowable unexpectedThrowable, int cause);
+	void displayCrashReport(UnexpectedThrowable unexpectedThrowable, int cause);
 	
-	public int getFrequency();
+	int getFrequency();
 	
-	public void update();
+	void update();
 	
-	public boolean shouldClose();
+	boolean shouldClose();
 	
-	public int getWidthScreenCoords();
+	int getWidthScreenCoords();
 	
-	public int getHeightScreenCoords();
+	int getHeightScreenCoords();
 
-	public int getWidthPixels();
+	int getWidthPixels();
 
-	public int getHeightPixels();
+	int getHeightPixels();
 	
-	public boolean isFocused();
+	boolean isFocused();
 	
-	public boolean wasResized();
+	boolean wasResized();
 	
-	public void setVsync(boolean vsync);
+	void setVsync(boolean vsync);
+
+	void setCursorShape(CursorShape shape);
+
+	CursorShape getCursorShape();
 
 }
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 ad24ed71f..adf316453 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
@@ -8,13 +8,18 @@ import java.awt.image.BufferedImage;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
 
 import javax.imageio.ImageIO;
 import javax.swing.JFrame;
 
+import com.mojang.logging.CategorizedLogger;
+import com.mojang.logging.LogUtils;
 import net.minecraft.core.Global;
 import net.minecraft.core.util.helper.Color;
 import org.lwjgl.PointerBuffer;
+import org.lwjgl.glfw.GLFW;
 import org.lwjgl.glfw.GLFWImage;
 import org.lwjgl.glfw.GLFWVidMode;
 import org.lwjgl.input.Keyboard;
@@ -32,6 +37,8 @@ import net.minecraft.client.render.PanelCrashReport;
 import net.minecraft.core.UnexpectedThrowable;
 
 public class GameWindowGLFW implements GameWindow {
+    public static final CategorizedLogger LOGGER = LogUtils.getLogger();
+
     public Minecraft mc;
 
     public long window;
@@ -51,13 +58,16 @@ public class GameWindowGLFW implements GameWindow {
     private int widthPixels;
     private int heightPixels;
 
+    private final Map<CursorShape, Long> cursors = new HashMap<>();
+    private CursorShape currentCursor = CursorShape.ARROW;
+
     @Override
     public void init(Minecraft minecraft, int width, int height) {
         mc = minecraft;
 
-        System.out.println("GLFW Init");
+        LOGGER.info("Initializing GLFW...");
         if (!glfwInit()) {
-            throw new RuntimeException("GLFW Init failed!");
+            throw new RuntimeException("GLFW initialization failed!");
         }
 
         // Set window hints
@@ -112,7 +122,7 @@ public class GameWindowGLFW implements GameWindow {
                 int res = resolutions[i];
                 ByteBuffer icon = getImageBuffer("/icon/" + res + ".png");
                 if (icon == null) {
-                    System.err.println("Icon at resolution '" + res + "' is null! Skipping icons!");
+                    LOGGER.error("Icon at resolution '" + res + "' is null! Skipping icons!");
                     gb.close();
                     break iconLabel;
                 }
@@ -160,6 +170,46 @@ public class GameWindowGLFW implements GameWindow {
         setVsync(mc.gameSettings.enableVsync.value);
     }
 
+    @Override
+    public void setCursorShape(CursorShape shape) {
+        final int shapeVal;
+        switch (shape) {
+            case ARROW:
+                shapeVal = GLFW_ARROW_CURSOR;
+                break;
+            case I_BEAM:
+                shapeVal = GLFW_IBEAM_CURSOR;
+                break;
+            case CROSSHAIR:
+                shapeVal = GLFW_CROSSHAIR_CURSOR;
+                break;
+            case HAND:
+                shapeVal = GLFW_HAND_CURSOR;
+                break;
+            case HORIZONTAL_RESIZE:
+                shapeVal = GLFW_HRESIZE_CURSOR;
+                break;
+            case VERTICAL_RESIZE:
+                shapeVal = GLFW_VRESIZE_CURSOR;
+                break;
+            default:
+                return;
+        }
+        if (this.cursors.containsKey(shape)) {
+            GLFW.glfwSetCursor(this.window, this.cursors.get(shape));
+        } else {
+            long cursor = GLFW.glfwCreateStandardCursor(shapeVal);
+            this.cursors.put(shape, cursor);
+            GLFW.glfwSetCursor(this.window, cursor);
+        }
+        this.currentCursor = shape;
+    }
+
+    @Override
+    public CursorShape getCursorShape() {
+        return this.currentCursor;
+    }
+
     @Override
     public void toggleFullscreen() {
         mc.gameSettings.fullscreen.value = !mc.gameSettings.fullscreen.value;
@@ -190,7 +240,7 @@ public class GameWindowGLFW implements GameWindow {
         glfwHideWindow(window);
 
         if (fullscreen && !borderless) {
-            System.out.println("Change to Fullscreen");
+            LOGGER.info("Setting window to FULLSCREEN...");
             int x = windowPosX + windowWidth / 2;
             int y = windowPosY + windowHeight / 2;
             MonitorInfo monitor = getMonitorAt(x, y);
@@ -203,7 +253,7 @@ public class GameWindowGLFW implements GameWindow {
             glfwSetWindowMonitor(window, 0, windowPosX, windowPosY, windowWidth, windowHeight, 0);
 
             if (fullscreen && borderless) {
-                System.out.println("Change to Borderless Fullscreen");
+                LOGGER.info("Setting window to BORDERLESS FULLSCREEN...");
                 int x = windowPosX + windowWidth / 2;
                 int y = windowPosY + windowHeight / 2;
                 MonitorInfo monitor = getMonitorAt(x, y);
@@ -213,7 +263,7 @@ public class GameWindowGLFW implements GameWindow {
                 glfwSetWindowSize(window, monitor.widthScreenCoords, monitor.heightScreenCoords);
                 isFullscreen = true;
             } else {
-                System.out.println("Change to Windowed");
+                LOGGER.info("Setting window to WINDOWED...");
                 glfwSetWindowAttrib(window, GLFW_DECORATED, 1);
                 isFullscreen = false;
             }
@@ -376,8 +426,7 @@ public class GameWindowGLFW implements GameWindow {
             buf.flip();
             return buf;
         } catch (Exception e) {
-            System.out.println("Couldn't open icon image...");
-            e.printStackTrace();
+            LOGGER.warn("Couldn't open icon image.", e);
             return null;
         }
     }
diff --git a/game/client/src/main/java/net/minecraft/client/render/worldtype/WorldTypeFX.java b/game/client/src/main/java/net/minecraft/client/render/worldtype/WorldTypeFX.java
index 6754f6a2f..d98f60e94 100644
--- a/game/client/src/main/java/net/minecraft/client/render/worldtype/WorldTypeFX.java
+++ b/game/client/src/main/java/net/minecraft/client/render/worldtype/WorldTypeFX.java
@@ -1,5 +1,6 @@
 package net.minecraft.client.render.worldtype;
 
+import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.util.phys.Vec3;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.type.WorldType;
@@ -72,4 +73,6 @@ public class WorldTypeFX {
     public float[] getSunriseColor(float timeOfDay, float partialTick) {
         return new float[4];
     }
+
+    public void playerAnimationTick(World world, Player player) { }
 }
diff --git a/game/client/src/main/java/net/minecraft/client/render/worldtype/WorldTypeFXNether.java b/game/client/src/main/java/net/minecraft/client/render/worldtype/WorldTypeFXNether.java
index 9c7e606a1..fbd35c2bb 100644
--- a/game/client/src/main/java/net/minecraft/client/render/worldtype/WorldTypeFXNether.java
+++ b/game/client/src/main/java/net/minecraft/client/render/worldtype/WorldTypeFXNether.java
@@ -1,9 +1,13 @@
 package net.minecraft.client.render.worldtype;
 
 import net.minecraft.client.render.colorizer.Colorizers;
+import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.util.helper.MathHelper;
 import net.minecraft.core.util.phys.Vec3;
 import net.minecraft.core.world.World;
+import net.minecraft.core.world.biome.Biome;
+import net.minecraft.core.world.biome.Biomes;
+import net.minecraft.core.world.biome.provider.BiomeProviderNether;
 import net.minecraft.core.world.type.WorldType;
 
 public class WorldTypeFXNether extends WorldTypeFX{
@@ -30,6 +34,47 @@ public class WorldTypeFXNether extends WorldTypeFX{
             float b = (color & 0xFF) / 255.0f;
             return Vec3.getTempVec3(r, g, b);
         }
-        return Vec3.getTempVec3(0.0, 0.0, 0.0);
+
+        float r = 60 / 255.0f;
+        float g = 0 / 255.0f;
+        float b = 0 / 255.0f;
+
+        double temp = ((BiomeProviderNether) world.getBiomeProvider()).getTemperature(x, z);
+
+        if (temp < 0.25) {
+            r = 0.0f;
+            g = 0.0f;
+            b = 0.0f;
+        } else if (temp < 0.5) {
+            double toLerp = temp - 0.25; // 0 - 0.25
+            toLerp *= 4; // 0 - 1
+            r = MathHelper.lerp(0, r, (float) toLerp);
+            g = MathHelper.lerp(0, g, (float) toLerp);
+            b = MathHelper.lerp(0, b, (float) toLerp);
+        }
+
+        return Vec3.getTempVec3(r, g, b);
+    }
+
+    @Override
+    public void playerAnimationTick(World world, Player player) {
+        final int radius = 12;
+
+        double x = player.x;
+        double y = player.y;
+        double z = player.z;
+
+        Biome localBiome = world.getBlockBiome((int) x, (int) y, (int) z);
+        if (localBiome == Biomes.NETHER_ASH_PLAINS || localBiome == Biomes.NETHER_BONEYARD) {
+            for (int i = 0; i < 5; i++) {
+                double rx = (x - radius) + (world.rand.nextDouble() * radius * 2);
+                double ry = (y - radius) + (world.rand.nextDouble() * radius * 2);
+                double rz = (z - radius) + (world.rand.nextDouble() * radius * 2);
+
+                if (world.getBlockId((int) rx, (int) ry, (int) rz) == 0) {
+                    world.spawnParticle("ashmote", rx, ry, rz, 0, 0, 0, 0);
+                }
+            }
+        }
     }
 }
diff --git a/game/client/src/main/resources/assets/minecraft/sounds/ui/ui_focus.ogg b/game/client/src/main/resources/assets/minecraft/sounds/ui/ui_focus.ogg
index f28ed5b5a..7e61011be 100644
Binary files a/game/client/src/main/resources/assets/minecraft/sounds/ui/ui_focus.ogg and b/game/client/src/main/resources/assets/minecraft/sounds/ui/ui_focus.ogg differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/block_ash.png b/game/client/src/main/resources/assets/minecraft/textures/block/block_ash.png
new file mode 100644
index 000000000..42c4727bc
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/block_ash.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack/ashen_side.png b/game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack/ashen_side.png
new file mode 100644
index 000000000..bca29a482
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack/ashen_side.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack_igneous.png b/game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack/igneous.png
similarity index 100%
rename from game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack_igneous.png
rename to game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack/igneous.png
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack_igneous_overlay.png b/game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack/igneous_overlay.png
similarity index 100%
rename from game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack_igneous_overlay.png
rename to game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack/igneous_overlay.png
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack_mossy.png b/game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack/mossy.png
similarity index 100%
rename from game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack_mossy.png
rename to game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack/mossy.png
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack.png b/game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack/normal.png
similarity index 100%
rename from game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack.png
rename to game/client/src/main/resources/assets/minecraft/textures/block/cobbled_netherrack/normal.png
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/fire_cold.png b/game/client/src/main/resources/assets/minecraft/textures/block/fire_cold.png
new file mode 100644
index 000000000..35bb7d1ea
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/fire_cold.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/jar_butterfly.png b/game/client/src/main/resources/assets/minecraft/textures/block/jar_butterfly.png
new file mode 100644
index 000000000..4f33041f3
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/jar_butterfly.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/crystal.png b/game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/crystal.png
new file mode 100644
index 000000000..c4c4aaae5
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/crystal.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/crystal_netherrack.png b/game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/crystal_netherrack.png
new file mode 100644
index 000000000..3a3cd1ba2
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/crystal_netherrack.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/overlay.png b/game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/overlay.png
new file mode 100644
index 000000000..20ca0a50f
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/overlay.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/stalk.png b/game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/stalk.png
new file mode 100644
index 000000000..acdf73430
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/stalk.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/top.png b/game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/top.png
new file mode 100644
index 000000000..0ce796538
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/nether_crystal/top.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/sulfur.png b/game/client/src/main/resources/assets/minecraft/textures/block/sulfur.png
new file mode 100644
index 000000000..439247795
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/block/sulfur.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/colormap/crystal/dead.png b/game/client/src/main/resources/assets/minecraft/textures/colormap/crystal/dead.png
new file mode 100644
index 000000000..be81e5d32
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/colormap/crystal/dead.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/colormap/crystal/default.png b/game/client/src/main/resources/assets/minecraft/textures/colormap/crystal/default.png
new file mode 100644
index 000000000..61a098718
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/colormap/crystal/default.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/colormap/water/nether.png b/game/client/src/main/resources/assets/minecraft/textures/colormap/water/nether.png
index a35b7f7c8..9033cac9e 100644
Binary files a/game/client/src/main/resources/assets/minecraft/textures/colormap/water/nether.png and b/game/client/src/main/resources/assets/minecraft/textures/colormap/water/nether.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/entity/butterfly/blue/0_0.png b/game/client/src/main/resources/assets/minecraft/textures/entity/butterfly/blue/0_0.png
new file mode 100644
index 000000000..7336efd8f
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/entity/butterfly/blue/0_0.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/entity/butterfly/blue/0_1.png b/game/client/src/main/resources/assets/minecraft/textures/entity/butterfly/blue/0_1.png
new file mode 100644
index 000000000..520b665d4
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/entity/butterfly/blue/0_1.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/entity/butterfly/orange/0_0.png b/game/client/src/main/resources/assets/minecraft/textures/entity/butterfly/orange/0_0.png
new file mode 100644
index 000000000..782df0cf2
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/entity/butterfly/orange/0_0.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/entity/butterfly/orange/0_1.png b/game/client/src/main/resources/assets/minecraft/textures/entity/butterfly/orange/0_1.png
new file mode 100644
index 000000000..9eca55e88
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/entity/butterfly/orange/0_1.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/entity/butterfly/pink/0_0.png b/game/client/src/main/resources/assets/minecraft/textures/entity/butterfly/pink/0_0.png
new file mode 100644
index 000000000..6513bf0ba
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/entity/butterfly/pink/0_0.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/entity/butterfly/pink/0_1.png b/game/client/src/main/resources/assets/minecraft/textures/entity/butterfly/pink/0_1.png
new file mode 100644
index 000000000..3a292908c
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/entity/butterfly/pink/0_1.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/entity/butterfly/silver/0_0.png b/game/client/src/main/resources/assets/minecraft/textures/entity/butterfly/silver/0_0.png
new file mode 100644
index 000000000..c1deb1e64
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/entity/butterfly/silver/0_0.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/entity/butterfly/silver/0_1.png b/game/client/src/main/resources/assets/minecraft/textures/entity/butterfly/silver/0_1.png
new file mode 100644
index 000000000..3e0b3405f
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/entity/butterfly/silver/0_1.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/entity/ghast_vehicle/0.png b/game/client/src/main/resources/assets/minecraft/textures/entity/ghast_vehicle/0.png
new file mode 100644
index 000000000..9ed4fbf0d
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/entity/ghast_vehicle/0.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/entity/statue_stone.pdn b/game/client/src/main/resources/assets/minecraft/textures/entity/statue_stone.pdn
new file mode 100644
index 000000000..55a93528c
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/entity/statue_stone.pdn differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/entity/statue_stone.png b/game/client/src/main/resources/assets/minecraft/textures/entity/statue_stone.png
new file mode 100644
index 000000000..d71316769
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/entity/statue_stone.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/container/keyboard/keyboard.png b/game/client/src/main/resources/assets/minecraft/textures/gui/container/keyboard/keyboard.png
new file mode 100644
index 000000000..7c7cfa7ba
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/container/keyboard/keyboard.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/screen/keyboard/keyboard_background.png b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/screen/keyboard/keyboard_background.png
new file mode 100644
index 000000000..13fe89dfb
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/screen/keyboard/keyboard_background.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/screen/keyboard/keyboard_background.png.mcmeta b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/screen/keyboard/keyboard_background.png.mcmeta
new file mode 100644
index 000000000..090d89c82
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/screen/keyboard/keyboard_background.png.mcmeta
@@ -0,0 +1,15 @@
+{
+    "gui": {
+        "scaling": {
+            "type": "nine_slice",
+            "width": 15,
+            "height": 15,
+            "border": {
+                "top": 5,
+                "left": 5,
+                "right": 5,
+                "bottom": 5
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/screen/keyboard/keyboard_background_embedded.png b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/screen/keyboard/keyboard_background_embedded.png
new file mode 100644
index 000000000..634eb37f2
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/screen/keyboard/keyboard_background_embedded.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/screen/keyboard/keyboard_background_embedded.png.mcmeta b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/screen/keyboard/keyboard_background_embedded.png.mcmeta
new file mode 100644
index 000000000..090d89c82
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/screen/keyboard/keyboard_background_embedded.png.mcmeta
@@ -0,0 +1,15 @@
+{
+    "gui": {
+        "scaling": {
+            "type": "nine_slice",
+            "width": 15,
+            "height": 15,
+            "border": {
+                "top": 5,
+                "left": 5,
+                "right": 5,
+                "bottom": 5
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/screen/keyboard/radial_background.png b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/screen/keyboard/radial_background.png
new file mode 100644
index 000000000..51fc68562
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/screen/keyboard/radial_background.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key/down.png b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key/down.png
new file mode 100644
index 000000000..cd6e50cf9
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key/down.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key/down.png.mcmeta b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key/down.png.mcmeta
new file mode 100644
index 000000000..6fbdb3c28
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key/down.png.mcmeta
@@ -0,0 +1,10 @@
+{
+    "gui": {
+        "scaling": {
+            "type": "nine_slice",
+            "width": 18,
+            "height": 18,
+            "border": 1
+        }
+    }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key/down_hovered.png b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key/down_hovered.png
new file mode 100644
index 000000000..0fd2ccf5e
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key/down_hovered.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key/down_hovered.png.mcmeta b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key/down_hovered.png.mcmeta
new file mode 100644
index 000000000..6fbdb3c28
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key/down_hovered.png.mcmeta
@@ -0,0 +1,10 @@
+{
+    "gui": {
+        "scaling": {
+            "type": "nine_slice",
+            "width": 18,
+            "height": 18,
+            "border": 1
+        }
+    }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key/up.png b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key/up.png
new file mode 100644
index 000000000..12a6341dd
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key/up.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key/up.png.mcmeta b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key/up.png.mcmeta
new file mode 100644
index 000000000..6fbdb3c28
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key/up.png.mcmeta
@@ -0,0 +1,10 @@
+{
+    "gui": {
+        "scaling": {
+            "type": "nine_slice",
+            "width": 18,
+            "height": 18,
+            "border": 1
+        }
+    }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key/up_hovered.png b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key/up_hovered.png
new file mode 100644
index 000000000..01701c2c1
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key/up_hovered.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key/up_hovered.png.mcmeta b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key/up_hovered.png.mcmeta
new file mode 100644
index 000000000..6fbdb3c28
--- /dev/null
+++ b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key/up_hovered.png.mcmeta
@@ -0,0 +1,10 @@
+{
+    "gui": {
+        "scaling": {
+            "type": "nine_slice",
+            "width": 18,
+            "height": 18,
+            "border": 1
+        }
+    }
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_alt/alt.xcf b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_alt/alt.xcf
new file mode 100644
index 000000000..38c005121
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_alt/alt.xcf differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_alt/down.png b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_alt/down.png
new file mode 100644
index 000000000..bf812a4a8
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_alt/down.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_alt/down_hovered.png b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_alt/down_hovered.png
new file mode 100644
index 000000000..6a0eea532
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_alt/down_hovered.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_alt/up.png b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_alt/up.png
new file mode 100644
index 000000000..6a42c4c0f
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_alt/up.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_alt/up_hovered.png b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_alt/up_hovered.png
new file mode 100644
index 000000000..c6ed48795
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_alt/up_hovered.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_backspace/backspace.xcf b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_backspace/backspace.xcf
new file mode 100644
index 000000000..c168c6000
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_backspace/backspace.xcf differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_backspace/down.png b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_backspace/down.png
new file mode 100644
index 000000000..c222a3c2f
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_backspace/down.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_backspace/down_hovered.png b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_backspace/down_hovered.png
new file mode 100644
index 000000000..cde82d09f
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_backspace/down_hovered.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_backspace/up.png b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_backspace/up.png
new file mode 100644
index 000000000..76ef429dd
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_backspace/up.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_backspace/up_hovered.png b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_backspace/up_hovered.png
new file mode 100644
index 000000000..edb70e534
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_backspace/up_hovered.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_enter/down.png b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_enter/down.png
new file mode 100644
index 000000000..d0b1e49f7
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_enter/down.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_enter/down_hovered.png b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_enter/down_hovered.png
new file mode 100644
index 000000000..c916c3a66
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_enter/down_hovered.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_enter/up.png b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_enter/up.png
new file mode 100644
index 000000000..3542675e0
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_enter/up.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_enter/up_hovered.png b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_enter/up_hovered.png
new file mode 100644
index 000000000..d139b95ea
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_enter/up_hovered.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_page_last/down.png b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_page_last/down.png
new file mode 100644
index 000000000..120ec8cec
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_page_last/down.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_page_last/down_hovered.png b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_page_last/down_hovered.png
new file mode 100644
index 000000000..80dd4c1c4
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_page_last/down_hovered.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_page_last/last.xcf b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_page_last/last.xcf
new file mode 100644
index 000000000..8e6c59cce
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_page_last/last.xcf differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_page_last/up.png b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_page_last/up.png
new file mode 100644
index 000000000..4298ff085
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_page_last/up.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_page_last/up_hovered.png b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_page_last/up_hovered.png
new file mode 100644
index 000000000..84d3eca1b
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_page_last/up_hovered.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_page_next/down.png b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_page_next/down.png
new file mode 100644
index 000000000..433eba72f
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_page_next/down.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_page_next/down_hovered.png b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_page_next/down_hovered.png
new file mode 100644
index 000000000..229d201da
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_page_next/down_hovered.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_page_next/next.xcf b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_page_next/next.xcf
new file mode 100644
index 000000000..076d4b499
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_page_next/next.xcf differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_page_next/up.png b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_page_next/up.png
new file mode 100644
index 000000000..73b1966ca
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_page_next/up.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_page_next/up_hovered.png b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_page_next/up_hovered.png
new file mode 100644
index 000000000..4cba85ec3
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_page_next/up_hovered.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_shift/down.png b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_shift/down.png
new file mode 100644
index 000000000..a94ea879d
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_shift/down.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_shift/down_hovered.png b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_shift/down_hovered.png
new file mode 100644
index 000000000..1f77f075d
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_shift/down_hovered.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_shift/shift.xcf b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_shift/shift.xcf
new file mode 100644
index 000000000..99e231782
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_shift/shift.xcf differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_shift/up.png b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_shift/up.png
new file mode 100644
index 000000000..2c1b1dab0
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_shift/up.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_shift/up_hovered.png b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_shift/up_hovered.png
new file mode 100644
index 000000000..906042c63
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/widgets/keyboard/key_shift/up_hovered.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/item/jar_butterfly_blue.png b/game/client/src/main/resources/assets/minecraft/textures/item/jar_butterfly_blue.png
new file mode 100644
index 000000000..05a4551e0
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/item/jar_butterfly_blue.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/item/jar_butterfly_orange.png b/game/client/src/main/resources/assets/minecraft/textures/item/jar_butterfly_orange.png
new file mode 100644
index 000000000..d630f3f05
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/item/jar_butterfly_orange.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/item/jar_butterfly_pink.png b/game/client/src/main/resources/assets/minecraft/textures/item/jar_butterfly_pink.png
new file mode 100644
index 000000000..808830042
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/item/jar_butterfly_pink.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/item/jar_butterfly_silver.png b/game/client/src/main/resources/assets/minecraft/textures/item/jar_butterfly_silver.png
new file mode 100644
index 000000000..f1cd7c0b6
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/item/jar_butterfly_silver.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/item/statue_stone.png b/game/client/src/main/resources/assets/minecraft/textures/item/statue_stone.png
new file mode 100644
index 000000000..f59edb0fc
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/item/statue_stone.png differ
diff --git a/game/client/src/main/resources/font/mono_00.png b/game/client/src/main/resources/font/mono_00.png
new file mode 100644
index 000000000..9682afd5f
Binary files /dev/null and b/game/client/src/main/resources/font/mono_00.png differ
diff --git a/game/client/src/main/resources/scripts/beanshell/commands/bind.bsh b/game/client/src/main/resources/scripts/beanshell/commands/bind.bsh
new file mode 100644
index 000000000..06da229b5
--- /dev/null
+++ b/game/client/src/main/resources/scripts/beanshell/commands/bind.bsh
@@ -0,0 +1,8 @@
+import net.minecraft.client.console.shell.BindManager;
+import net.minecraft.client.console.shell.RawOutputWrapper;
+import org.lwjgl.input.Keyboard;
+
+RawOutputWrapper bind(int key, Runnable action) {
+    BindManager.getInstance().bindKeyToAction(key, action);
+    return new RawOutputWrapper("Bound key to action.");
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/scripts/beanshell/commands/logDisable.bsh b/game/client/src/main/resources/scripts/beanshell/commands/logDisable.bsh
new file mode 100644
index 000000000..5d0b45cf9
--- /dev/null
+++ b/game/client/src/main/resources/scripts/beanshell/commands/logDisable.bsh
@@ -0,0 +1,7 @@
+import com.mojang.logging.CategorizedLogger;
+import net.minecraft.client.console.shell.RawOutputWrapper;
+
+RawOutputWrapper logDisable(String category) {
+    CategorizedLogger.setCategoryEnabled(category, false);
+    return new RawOutputWrapper("Disabled logging category " + category);
+}
diff --git a/game/client/src/main/resources/scripts/beanshell/commands/logEnable.bsh b/game/client/src/main/resources/scripts/beanshell/commands/logEnable.bsh
new file mode 100644
index 000000000..8c0af291e
--- /dev/null
+++ b/game/client/src/main/resources/scripts/beanshell/commands/logEnable.bsh
@@ -0,0 +1,7 @@
+import com.mojang.logging.CategorizedLogger;
+import net.minecraft.client.console.shell.RawOutputWrapper;
+
+RawOutputWrapper logEnable(String category) {
+    CategorizedLogger.setCategoryEnabled(category, true);
+    return new RawOutputWrapper("Enabled logging category " + category);
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/scripts/beanshell/commands/logList.bsh b/game/client/src/main/resources/scripts/beanshell/commands/logList.bsh
new file mode 100644
index 000000000..40e1323eb
--- /dev/null
+++ b/game/client/src/main/resources/scripts/beanshell/commands/logList.bsh
@@ -0,0 +1,12 @@
+import com.mojang.logging.CategorizedLogger;
+import net.minecraft.client.console.shell.RawOutputWrapper;
+
+RawOutputWrapper logList() {
+    Collection categories = CategorizedLogger.getKnownCategories();
+    if (categories.isEmpty()) return new RawOutputWrapper("No logging categories");
+    StringBuilder sb = new StringBuilder();
+    for (String str : categories) {
+        sb.append(" - ").append(str);
+    }
+    return new RawOutputWrapper(sb.toString());
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/scripts/beanshell/commands/mc.bsh b/game/client/src/main/resources/scripts/beanshell/commands/mc.bsh
new file mode 100644
index 000000000..c561b1bec
--- /dev/null
+++ b/game/client/src/main/resources/scripts/beanshell/commands/mc.bsh
@@ -0,0 +1,5 @@
+import net.minecraft.client.Minecraft;
+
+Minecraft mc() {
+    return Minecraft.getMinecraft();
+}
\ No newline at end of file
diff --git a/game/client/src/main/resources/scripts/beanshell/commands/unbind.bsh b/game/client/src/main/resources/scripts/beanshell/commands/unbind.bsh
new file mode 100644
index 000000000..5ff9b25b8
--- /dev/null
+++ b/game/client/src/main/resources/scripts/beanshell/commands/unbind.bsh
@@ -0,0 +1,7 @@
+import net.minecraft.client.console.shell.BindManager;
+import net.minecraft.client.console.shell.RawOutputWrapper;
+
+RawOutputWrapper unbind(int key) {
+    BindManager.getInstance().bindKeyToAction(key, null);
+    return new RawOutputWrapper("Unbound key.");
+{
\ No newline at end of file
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicAsh.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicAsh.java
new file mode 100644
index 000000000..4a2114544
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicAsh.java
@@ -0,0 +1,30 @@
+package net.minecraft.core.block;
+
+import net.minecraft.core.block.entity.TileEntity;
+import net.minecraft.core.block.material.Material;
+import net.minecraft.core.enums.EnumDropCause;
+import net.minecraft.core.item.ItemStack;
+import net.minecraft.core.world.World;
+
+public class BlockLogicAsh extends BlockLogic
+{
+
+	public BlockLogicAsh(Block<?> block)
+    {
+        super(block, Material.ash);
+    }
+
+    @Override
+    public ItemStack[] getBreakResult(World world, EnumDropCause dropCause, int meta, TileEntity tileEntity)
+    {
+        switch (dropCause)
+        {
+            case PICK_BLOCK:
+            case SILK_TOUCH:
+                return new ItemStack[] { new ItemStack(this) };
+            default:
+//                return new ItemStack[] { new ItemStack(Items.AMMO_SNOWBALL, 4) };
+                return null;
+        }
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicCrystal.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicCrystal.java
new file mode 100644
index 000000000..ea7486d79
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicCrystal.java
@@ -0,0 +1,18 @@
+package net.minecraft.core.block;
+
+import net.minecraft.core.block.material.Material;
+import net.minecraft.core.world.WorldSource;
+
+public class BlockLogicCrystal extends BlockLogicTransparent
+{
+
+    public BlockLogicCrystal(Block<?> block)
+    {
+        super(block, Material.glass);
+    }
+
+    @Override
+    public float getAmbientOcclusionStrength(WorldSource blockAccess, int x, int y, int z) {
+        return 1f;
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFarmland.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFarmland.java
index fee3177ac..ea9cf35b6 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFarmland.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFarmland.java
@@ -6,6 +6,7 @@ import net.minecraft.core.block.tag.BlockTags;
 import net.minecraft.core.entity.Entity;
 import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.enums.EnumDropCause;
+import net.minecraft.core.enums.HumanArmorShape;
 import net.minecraft.core.item.IArmorItem;
 import net.minecraft.core.item.IBonemealable;
 import net.minecraft.core.item.Items;
@@ -145,7 +146,7 @@ public class BlockLogicFarmland extends BlockLogic implements IBonemealable
 		{
 			if (entity instanceof Player)
 			{
-				if (((Player) entity).inventory.armorInventory[IArmorItem.PIECE_BOOTS] != null && ((Player) entity).inventory.armorInventory[IArmorItem.PIECE_BOOTS].getItem() == Items.ARMOR_BOOTS_LEATHER) {
+				if (((Player) entity).getItemInArmorSlot(HumanArmorShape.BOOTS) != null && ((Player) entity).getItemInArmorSlot(HumanArmorShape.BOOTS).getItem() == Items.ARMOR_BOOTS_LEATHER) {
 					return;
 				}
 				if(isWet(world.getBlockMetadata(x, y, z)))
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFire.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFire.java
index 65b9cc09c..87de3a52e 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFire.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFire.java
@@ -219,7 +219,7 @@ public class BlockLogicFire extends BlockLogic {
 		}
 	}
 
-	private boolean canNeighborCatchFire(World world, int x, int y, int z) {
+	protected boolean canNeighborCatchFire(World world, int x, int y, int z) {
 		if (canBurn(world, x + 1, y, z)) return true;
 		if (canBurn(world, x - 1, y, z)) return true;
 		if (canBurn(world, x, y - 1, z)) return true;
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicFireCold.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFireCold.java
new file mode 100644
index 000000000..1f3bdb28e
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicFireCold.java
@@ -0,0 +1,54 @@
+package net.minecraft.core.block;
+
+import net.minecraft.core.block.tag.BlockTags;
+import net.minecraft.core.data.gamerule.GameRules;
+import net.minecraft.core.world.World;
+
+import java.util.Random;
+
+public class BlockLogicFireCold extends BlockLogicFire {
+    public BlockLogicFireCold(Block<?> block) {
+        super(block);
+    }
+
+    @Override
+    public void updateTick(World world, int x, int y, int z, Random rand) {
+        Block blockUnder = world.getBlock(x, y - 1, z);
+        boolean infiniBurn = blockUnder != null && blockUnder.hasTag(BlockTags.INFINITE_BURN_COLD) || !world.getGameRuleValue(GameRules.DO_FIRE_SPREAD);
+
+        //Extinguish fire
+        if (!isValidFireLocation(world, x, y, z)) {
+            setBurnResult(world, x, y, z);
+        }
+
+        //Extinguish fire (Rain / Snow)
+        if (!infiniBurn && world.getCurrentWeather() != null && world.getCurrentWeather().isPrecipitation && (
+            world.canBlockBeRainedOn(x, y, z)
+                || world.canBlockBeRainedOn(x - 1, y, z)
+                || world.canBlockBeRainedOn(x + 1, y, z)
+                || world.canBlockBeRainedOn(x, y, z - 1)
+                || world.canBlockBeRainedOn(x, y, z + 1))) {
+            setBurnResult(world, x, y, z);
+            return;
+        }
+
+        int meta = world.getBlockMetadata(x, y, z);
+        if (meta < 15) {
+            world.setBlockMetadata(x, y, z, meta + rand.nextInt(3) / 2);
+        }
+        world.scheduleBlockUpdate(x, y, z, block.id(), tickDelay());
+
+
+        if (!infiniBurn && !canNeighborCatchFire(world, x, y, z)) {
+            if (!world.isBlockNormalCube(x, y - 1, z) || meta > 3) {
+                setBurnResult(world, x, y, z);
+            }
+            return;
+        }
+
+        if (!infiniBurn && !canBurn(world, x, y - 1, z) && meta == 15 && rand.nextInt(4) == 0) {
+            setBurnResult(world, x, y, z);
+            return;
+        }
+    }
+}
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 5d61e5660..c65f10328 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
@@ -194,15 +194,19 @@ public abstract class BlockLogicFluid extends BlockLogic
     @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)
         {
-            int data = world.getBlockMetadata(x, y, z) & 0x0F;
             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.lava && world.getBlockMaterial(x, y + 1, z) == Material.air && !world.isBlockOpaqueCube(x, y + 1, z) && rand.nextInt(100) == 0)
+        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;
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 9643fe9f3..02d06d26d 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
@@ -65,7 +65,7 @@ public class BlockLogicFluidFlowing extends BlockLogicFluid
                     newFlowDecay = flowDecayAbove + 8;
                 }
             }
-            if(maxCount >= 2 && material == Material.water)
+            if(maxCount >= 2 && (material == Material.water && world.dimension != Dimension.NETHER)) // Become source block
             {
                 if(world.getBlockMaterial(x, y - 1, z).isSolid())
                 {
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicJarButterfly.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicJarButterfly.java
new file mode 100644
index 000000000..bde6e498a
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicJarButterfly.java
@@ -0,0 +1,138 @@
+package net.minecraft.core.block;
+
+import net.minecraft.core.block.entity.TileEntity;
+import net.minecraft.core.block.entity.TileEntityJarButterfly;
+import net.minecraft.core.block.material.Material;
+import net.minecraft.core.block.tag.BlockTags;
+import net.minecraft.core.entity.animal.MobButterfly;
+import net.minecraft.core.entity.player.Player;
+import net.minecraft.core.enums.EnumDropCause;
+import net.minecraft.core.item.Item;
+import net.minecraft.core.item.ItemStack;
+import net.minecraft.core.util.helper.Side;
+import net.minecraft.core.util.phys.AABB;
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.WorldSource;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.function.Supplier;
+
+public class BlockLogicJarButterfly extends BlockLogic {
+    public static final int MASK_HANGING = 0b0000_0001;
+    public static final int MASK_ANGLED = 0b0000_0010;
+    private final Supplier<Item> itemSupplier;
+    private final MobButterfly.ButterflyEntry color;
+
+    public BlockLogicJarButterfly(Block<?> block, MobButterfly.ButterflyEntry color, @NotNull Supplier<Item> itemSupplier) {
+        super(block, Material.glass);
+        this.color = color;
+        this.itemSupplier = itemSupplier;
+        setBlockBounds(0.3125f, 0, 0.3125f, 1 - 0.3125f, 0.5f, 1 - 0.3125f);
+        block.withEntity(() -> new TileEntityJarButterfly(color));
+    }
+
+    @Override
+    public AABB getBlockBoundsFromState(WorldSource world, int x, int y, int z)
+    {
+        int metadata = world.getBlockMetadata(x, y, z);
+        float offset = 0.0F;
+        if ((metadata & MASK_HANGING) != 0)
+            offset = 0.3875F;
+        return AABB.getTemporaryBB(0.3125f, offset,  0.3125f, 1 - 0.3125f, 0.5F + offset, 1 - 0.3125f);
+    }
+
+    @Override
+    public boolean isSolidRender() {
+        return false;
+    }
+
+    @Override
+    public boolean isCubeShaped() {
+        return false;
+    }
+
+    @Override
+    public boolean onBlockRightClicked(World world, int x, int y, int z, Player player, Side side, double xHit, double yHit) {
+        world.setBlockRaw(x, y, z, 0);
+        world.playSoundAtEntity(player, player, "item.pickup", 1f, 1f);
+        if (!world.isClientSide) {
+            world.dropItem(x, y, z, new ItemStack(itemSupplier.get(), 1, 0));
+        }
+        world.markBlockDirty(x, y, z);
+
+        return true;
+    }
+
+    @Override
+    public void onBlockPlacedOnSide(World world, int x, int y, int z, @NotNull Side side, double xPlaced, double yPlaced) {
+        if ((side == Side.BOTTOM || (side != Side.TOP && yPlaced >= 0.5d)) && (world.isBlockNormalCube(x, y + 1, z) || Blocks.hasTag(world.getBlockId(x, y + 1, z), BlockTags.CAN_HANG_OFF))) {
+            world.setBlockMetadataWithNotify(x, y, z, MASK_HANGING);
+            return;
+        }
+        if ((side == Side.TOP || (side != Side.BOTTOM && yPlaced < 0.5d)) && (world.canPlaceOnSurfaceOfBlock(x, y - 1, z))) {
+            world.setBlockMetadataWithNotify(x, y, z, 0);
+            return;
+        }
+        if (world.isBlockNormalCube(x, y + 1, z) || Blocks.hasTag(world.getBlockId(x, y + 1, z), BlockTags.CAN_HANG_OFF)) {
+            world.setBlockMetadataWithNotify(x, y, z, MASK_HANGING);
+        } else world.setBlockMetadataWithNotify(x, y, z, 0);
+    }
+
+    @Override
+    public boolean canPlaceBlockAt(World world, int x, int y, int z) {
+        if (world.isBlockNormalCube(x, y + 1, z) || Blocks.hasTag(world.getBlockId(x, y + 1, z), BlockTags.CAN_HANG_OFF)) {
+            return true;
+        }
+
+        return world.canPlaceOnSurfaceOfBlock(x, y - 1, z);
+    }
+
+    @Override
+    public void onNeighborBlockChange(World world, int x, int y, int z, int blockId) {
+        if (!canBlockStay(world, x, y, z)) {
+            dropBlockWithCause(world, EnumDropCause.WORLD, x, y, z, world.getBlockMetadata(x, y, z), null, null);
+            world.setBlockWithNotify(x, y, z, 0);
+        }
+    }
+
+    @Override
+    public ItemStack[] getBreakResult(World world, EnumDropCause dropCause, int meta, TileEntity tileEntity) {
+        switch (dropCause) {
+            case PICK_BLOCK:
+            case SILK_TOUCH:
+            case WORLD:
+                return new ItemStack[]{new ItemStack(this.itemSupplier.get())};
+            default:
+                return null;
+        }
+    }
+
+    @Override
+    public boolean canBlockStay(World world, int x, int y, int z) {
+        int meta = world.getBlockMetadata(x, y, z);
+        if ((meta & MASK_HANGING) != 0) {
+            return world.isBlockNormalCube(x, y + 1, z) || Blocks.hasTag(world.getBlockId(x, y + 1, z), BlockTags.CAN_HANG_OFF);
+        }
+
+        return world.canPlaceOnSurfaceOfBlock(x, y - 1, z);
+    }
+
+    @Override
+    public void onBlockDestroyedByPlayer(World world, int x, int y, int z, Side side, int meta, Player player, Item item) {
+        if (item != null && item.isSilkTouch()) {
+            return;
+        }
+        if (!world.isClientSide) {
+            MobButterfly entity = new MobButterfly(world);
+            entity.setColor(color.id);
+            entity.setPos(x + 0.5f + (world.rand.nextFloat() - 0.5f) * 0.5f, y + world.rand.nextFloat() * 0.5f, z + 0.5f + (world.rand.nextFloat() - 0.5f) * 0.5f);
+            world.entityJoinedWorld(entity);
+        }
+    }
+
+    @Override
+    public int getPistonPushReaction(World world, int x, int y, int z) {
+        return Material.PISTON_DESTROY_ON_PUSH;
+    }
+
+}
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLanternFirefly.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLanternFirefly.java
index f6eb75ee8..2ca562e4b 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLanternFirefly.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLanternFirefly.java
@@ -70,6 +70,7 @@ public class BlockLogicLanternFirefly extends BlockLogic {
         {
             world.dropItem(x, y, z, new ItemStack(itemSupplier.get(), 1, 0));
         }
+        world.markBlockDirty(x, y, z);
 
         return true;
     }
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerAsh.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerAsh.java
new file mode 100644
index 000000000..6e05759f2
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicLayerAsh.java
@@ -0,0 +1,77 @@
+package net.minecraft.core.block;
+
+import net.minecraft.core.block.entity.TileEntity;
+import net.minecraft.core.block.material.Material;
+import net.minecraft.core.enums.EnumDropCause;
+import net.minecraft.core.item.ItemStack;
+import net.minecraft.core.world.World;
+
+public class BlockLogicLayerAsh extends BlockLogicLayerBase
+{
+
+	public BlockLogicLayerAsh(Block<?> block, Block<?> fullBlock, Material material)
+    {
+        super(block, fullBlock, material);
+        setBlockBounds(0.0F, 0.0F, 0.0F, 1.0F, 0.125F, 1.0F);
+        block.setTicking(true);
+    }
+
+    public void accumulate(World world, int x, int y, int z)
+    {
+    	int myMetadata = world.getBlockMetadata(x, y, z);
+    	if (myMetadata == 0x7) return;
+    	
+    	boolean posXValid = (world.isBlockOpaqueCube(x + 1, y, z)) || (world.getBlockId(x + 1, y, z) == this.id() && world.getBlockMetadata(x + 1, y, z) >= myMetadata);
+    	if (!posXValid) return;
+    	boolean posZValid = (world.isBlockOpaqueCube(x, y, z + 1)) || (world.getBlockId(x, y, z + 1) == this.id() && world.getBlockMetadata(x, y, z + 1) >= myMetadata);
+    	if (!posZValid) return;
+    	boolean negXValid = (world.isBlockOpaqueCube(x - 1, y, z)) || (world.getBlockId(x - 1, y, z) == this.id() && world.getBlockMetadata(x - 1, y, z) >= myMetadata);
+    	if (!negXValid) return;
+    	boolean negZValid = (world.isBlockOpaqueCube(x, y, z - 1)) || (world.getBlockId(x, y, z - 1) == this.id() && world.getBlockMetadata(x, y, z - 1) >= myMetadata);
+    	if (!negZValid) return;
+    	
+    	world.setBlockMetadata(x, y, z, myMetadata + 1);
+    	world.markBlockNeedsUpdate(x, y, z);
+    }
+
+    @Override
+	public boolean canPlaceBlockAt(World world, int x, int y, int z)
+    {
+        int l = world.getBlockId(x, y - 1, z);
+        if(l == 0 || !Blocks.blocksList[l].isSolidRender())
+        {
+        	return false;
+        }
+        else
+        {
+        	Material material = world.getBlockMaterial(x, y - 1, z);
+            return material.blocksMotion();
+        }
+    }
+
+    @Override
+	public void onNeighborBlockChange(World world, int x, int y, int z, int blockId)
+    {
+        // Remove if no longer valid
+        if (!canPlaceBlockAt(world, x, y, z)) {
+            dropBlockWithCause(world, EnumDropCause.WORLD, x, y, z, world.getBlockMetadata(x, y, z), null, null);
+            world.setBlockWithNotify(x, y, z, 0);
+        }
+    }
+    @Override
+    public ItemStack[] getBreakResult(World world, EnumDropCause dropCause, int meta, TileEntity tileEntity)
+    {
+        switch (dropCause)
+        {
+            case SILK_TOUCH:
+                return new ItemStack[] { new ItemStack(this, meta + 1) };
+            case PICK_BLOCK:
+                return new ItemStack[] { new ItemStack(this) };
+            case EXPLOSION:
+            case PROPER_TOOL:
+//                return new ItemStack[] { new ItemStack(Items.AMMO_SNOWBALL, meta + 1) };
+            default:
+                return null;
+        }
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicSnow.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicSnow.java
index 2f2b636bb..16ec10324 100644
--- a/game/core/src/main/java/net/minecraft/core/block/BlockLogicSnow.java
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicSnow.java
@@ -19,11 +19,6 @@ public class BlockLogicSnow extends BlockLogic
         block.setTicking(true);
     }
 
-    public int quantityDropped(int meta, Random rand)
-    {
-        return 4;
-    }
-
     @Override
 	public void updateTick(World world, int x, int y, int z, Random rand)
     {
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicStalk.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicStalk.java
new file mode 100644
index 000000000..8f369d1be
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicStalk.java
@@ -0,0 +1,47 @@
+package net.minecraft.core.block;
+
+import net.minecraft.core.block.material.Material;
+import net.minecraft.core.util.phys.AABB;
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.WorldSource;
+
+public class BlockLogicStalk extends BlockLogic {
+    public BlockLogicStalk(Block<?> block) {
+        super(block, Material.plant);
+        block.setTicking(true);
+        float offset = 0.2F;
+        setBlockBounds(0.5F - offset, 0.0F, 0.5F - offset, 0.5F + offset, offset * 3F, 0.5F + offset);
+    }
+
+    @Override
+    public boolean canPlaceBlockAt(World world, int x, int y, int z) {
+        return super.canPlaceBlockAt(world, x, y, z) && mayPlaceOn(world.getBlockId(x, y - 1, z));
+    }
+
+    protected boolean mayPlaceOn(int blockId)
+    {
+        return Blocks.solid[blockId];
+    }
+
+    @Override
+    public boolean isSolidRender() {
+        return false;
+    }
+
+    @Override
+    public boolean isCubeShaped() {
+        return false;
+    }
+
+    @Override
+    public AABB getCollisionBoundingBoxFromPool(WorldSource world, int x, int y, int z) {
+        return null;
+    }
+
+    @Override
+    public boolean canBlockStay(World world, int x, int y, int z)
+    {
+        return mayPlaceOn(world.getBlockId(x, y - 1, z));
+    }
+
+}
diff --git a/game/core/src/main/java/net/minecraft/core/block/BlockLogicStatue.java b/game/core/src/main/java/net/minecraft/core/block/BlockLogicStatue.java
new file mode 100644
index 000000000..12f9b7a20
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/block/BlockLogicStatue.java
@@ -0,0 +1,199 @@
+package net.minecraft.core.block;
+
+import net.minecraft.core.block.entity.TileEntity;
+import net.minecraft.core.block.entity.TileEntityStatue;
+import net.minecraft.core.block.material.Material;
+import net.minecraft.core.entity.player.Player;
+import net.minecraft.core.enums.EnumDropCause;
+import net.minecraft.core.enums.HumanArmorShape;
+import net.minecraft.core.item.IArmorItem;
+import net.minecraft.core.item.Item;
+import net.minecraft.core.item.ItemStack;
+import net.minecraft.core.util.helper.Side;
+import net.minecraft.core.util.phys.AABB;
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.WorldSource;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+
+public class BlockLogicStatue extends BlockLogic {
+    private final boolean isBottom;
+
+    public final @Nullable Supplier<@NotNull Item> droppedItem;
+
+    public BlockLogicStatue(Block<?> block, Material material, boolean isBottom, final @Nullable Supplier<Item> droppedItem) {
+        super(block, material);
+
+        this.isBottom = isBottom;
+        this.droppedItem = droppedItem;
+
+        if (isBottom) {
+            block.withEntity(TileEntityStatue::new);
+        }
+
+        final float bottom = this.isBottom ? 0.0f : -1.0f;
+        final float top = this.isBottom ? 2.0f : 1.0f;
+        setBlockBounds(0.0f, bottom, 0.0f, 1.0f, top, 1.0f);
+    }
+
+    @Override
+    public boolean onBlockRightClicked(World world, int x, int y, int z, Player player, Side side, double xHit, double yHit) {
+        final @NotNull HumanArmorShape armorShape;
+        if (side == Side.TOP || yHit >= 0.5f) {
+            if (this.isBottom) {
+                armorShape = HumanArmorShape.LEGS;
+            } else {
+                armorShape = HumanArmorShape.HEAD;
+            }
+        } else if (side == Side.BOTTOM || yHit < 0.5f) {
+            if (this.isBottom) {
+                armorShape = HumanArmorShape.BOOTS;
+            } else {
+                armorShape = HumanArmorShape.CHEST;
+            }
+        } else {
+            return false;
+        }
+
+        final @Nullable TileEntityStatue statueEntity = getTileEntity(world, x, y, z);
+        if (statueEntity == null) {
+            return false;
+        }
+        final @Nullable ItemStack storedItemStack = statueEntity.getItemInArmorSlot(armorShape);
+
+        boolean takeItem = false;
+        if (storedItemStack != null && storedItemStack.getItem() != null) {
+            takeItem = true;
+        }
+
+        boolean storeItem = false;
+        final @Nullable ItemStack heldItemStack = player.getHeldItem();
+        if (heldItemStack != null) {
+            final @Nullable Item heldItem = heldItemStack.getItem();
+            if (heldItem != null) {
+                if (heldItem instanceof IArmorItem<?> && ((IArmorItem<?>) heldItem).getArmorShape() == armorShape) {
+                    storeItem = true;
+                }
+            }
+        }
+
+        if (takeItem && storeItem) {
+            if (heldItemStack.consumeItem(player)) {
+                final @NotNull ItemStack newStack = heldItemStack.copy();
+                newStack.stackSize = 1;
+                player.inventory.insertItem(storedItemStack, true);
+                statueEntity.setItemInArmorSlot(armorShape, newStack);
+                return true;
+            } else {
+                return false;
+            }
+        } else if (takeItem) {
+            player.inventory.insertItem(storedItemStack, true);
+            statueEntity.setItemInArmorSlot(armorShape, null);
+            return true;
+        } else if (storeItem) {
+            if (heldItemStack.consumeItem(player)) {
+                final @NotNull ItemStack newStack = heldItemStack.copy();
+                newStack.stackSize = 1;
+                statueEntity.setItemInArmorSlot(armorShape, newStack);
+                return true;
+            } else {
+                return false;
+            }
+        } else {
+            if (heldItemStack == null || heldItemStack.getItem() == null) {
+                statueEntity.nextPose();
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public boolean isCubeShaped() {
+        return false;
+    }
+
+    @Override
+    public boolean isSolidRender() {
+        return false;
+    }
+
+    public @Nullable TileEntityStatue getTileEntity(final @NotNull World world, final int x, final int y, final int z) {
+        final @Nullable Block<?> block = world.getBlock(x, this.isBottom ? y : y - 1, z);
+        if (block == null || !(block.getLogic() instanceof BlockLogicStatue)) {
+            return null;
+        }
+
+        final @Nullable TileEntity tileEntity = world.getBlockEntity(x, this.isBottom ? y : y - 1, z);
+        if (!(tileEntity instanceof TileEntityStatue)) {
+            return null;
+        }
+
+        return (TileEntityStatue) tileEntity;
+    }
+
+    @Override
+    public void onBlockRemoved(World world, int x, int y, int z, int data) {
+        if (this.isBottom) {
+            List<ItemStack> stacks = new ArrayList<>();
+            final @Nullable TileEntity tileEntity = world.getBlockEntity(x, y, z);
+            if (tileEntity != null) {
+                TileEntityStatue statueEntity = (TileEntityStatue) tileEntity;
+                for (HumanArmorShape armorShape : HumanArmorShape.values()) {
+                    @Nullable ItemStack armorStack = statueEntity.getItemInArmorSlot(armorShape);
+                    if (armorStack != null) {
+                        stacks.add(armorStack);
+                    }
+                }
+            }
+
+            for (ItemStack stack : stacks) {
+                world.dropItem(x, y, z, stack);
+            }
+        }
+    }
+
+    @Override
+    public void onNeighborBlockChange(World world, int x, int y, int z, int blockId) {
+        final Block<?> otherBlock;
+        if (!this.isBottom) {
+            otherBlock = Blocks.blocksList[world.getBlockId(x, y - 1, z)];
+        } else {
+            otherBlock = Blocks.blocksList[world.getBlockId(x, y + 1, z)];
+        }
+        if (!(otherBlock != null && otherBlock.getLogic() instanceof BlockLogicStatue)) {
+            world.setBlockWithNotify(x, y, z, 0);
+        }
+    }
+
+    @Override
+    public ItemStack[] getBreakResult(World world, EnumDropCause dropCause, int x, int y, int z, int meta, TileEntity tileEntity) {
+        if (droppedItem == null) return null;
+        if (dropCause != EnumDropCause.IMPROPER_TOOL) {
+            return new ItemStack[] { new ItemStack(droppedItem.get()) };
+        }
+
+        return null;
+    }
+
+    @Override
+    public boolean canPlaceBlockAt(World world, int x, int y, int z) {
+        if (y >= world.getHeightBlocks() - 1) {
+            return false;
+        } else {
+            return super.canPlaceBlockAt(world, x, y, z) && super.canPlaceBlockAt(world, x, y + 1, z);
+        }
+    }
+
+    @Override
+    public int getPistonPushReaction(World world, int x, int y, int z) {
+        return Material.PISTON_DESTROY_ON_PUSH;
+    }
+
+}
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 836c549aa..0e8aedef7 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
@@ -11,6 +11,7 @@ import net.minecraft.core.block.tag.BlockTags;
 import net.minecraft.core.data.tag.Tag;
 import net.minecraft.core.entity.Entity;
 import net.minecraft.core.entity.Mob;
+import net.minecraft.core.entity.animal.MobButterfly;
 import net.minecraft.core.entity.animal.MobFireflyCluster;
 import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.item.Item;
@@ -21,6 +22,7 @@ import net.minecraft.core.item.block.ItemBlockPainted;
 import net.minecraft.core.item.block.ItemBlockStairsPainted;
 import net.minecraft.core.item.block.ItemBlockLamp;
 import net.minecraft.core.item.block.ItemBlockSlabPainted;
+import net.minecraft.core.sound.BlockSound;
 import net.minecraft.core.sound.BlockSounds;
 import net.minecraft.core.util.collection.NamespaceID;
 import net.minecraft.core.world.Dimension;
@@ -425,6 +427,9 @@ public final class Blocks {
     public static final Block<?> GRAVEL = register("gravel", "minecraft:block/gravel", 251, BlockLogicGravel::new)
         .withSound(BlockSounds.GRAVEL).withHardness(0.6F)
         .withTags(BlockTags.MINEABLE_BY_SHOVEL);
+    public static final Block<?> SULFUR = register("sulfur", "minecraft:block/sulfur", 252, (b) -> new BlockLogic(b, Material.sand))
+            .withSound(BlockSounds.SAND).withHardness(0.7F)
+        .withTags(BlockTags.MINEABLE_BY_SHOVEL);
 
     public static final Block<?> BEDROCK = register("bedrock", "minecraft:block/bedrock", 260, (b) -> new BlockLogicBedrock(b, Material.stone))
         .withSound(BlockSounds.STONE).withSetUnbreakable().withBlastResistance(6000000F).withImmovableFlagSet()
@@ -916,6 +921,9 @@ public final class Blocks {
     public static final Block<BlockLogicBrazier> BRAZIER_ACTIVE = register("brazier.active", "minecraft:block/brazier_active", 632, (BlockLogicSupplier<BlockLogicBrazier>)b -> new BlockLogicBrazier(b, true))
         .withSound(BlockSounds.FIRE).withHardness(1.5F).withLightEmission(1.0F).setStatParent(() -> BRAZIER_INACTIVE)
         .withTags(BlockTags.NOT_IN_CREATIVE_MENU, BlockTags.MINEABLE_BY_PICKAXE);
+    public static final Block<BlockLogicFireCold> FIRE_COLD = register("fire_cold", "minecraft:block/fire_cold", 633, BlockLogicFireCold::new)
+        .withSound(BlockSounds.FIRE).withHardness(0.0F).withLightEmission(1.0F).withDisabledStats().withDisabledNeighborNotifyOnMetadataChange()
+        .withTags(BlockTags.BROKEN_BY_FLUIDS, BlockTags.PLACE_OVERWRITES, BlockTags.NOT_IN_CREATIVE_MENU);
 
     public static final Block<?> MOBSPAWNER = register("mobspawner", "minecraft:block/mobspawner", 640, BlockLogicMobSpawner::new)
         .withSound(BlockSounds.METAL).withHardness(5F).withImmovableFlagSet()
@@ -995,6 +1003,9 @@ public final class Blocks {
     public static final Block<?> LAYER_SLATE = register("layer.slate", "minecraft:block/layer_slate", 722, (b) -> new BlockLogicLayerSlate(b, Blocks.SLATE, Material.slate))
         .withSound(BlockSounds.STONE).withHardness(1.5F).withBlastResistance(10F).withLitInteriorSurface(true)
         .withTags(BlockTags.MINEABLE_BY_PICKAXE);
+    public static final Block<BlockLogicLayerAsh> LAYER_ASH = register("layer.ash", "minecraft:block/layer_ash", 723, (BlockLogicSupplier<BlockLogicLayerAsh>)(b) -> new BlockLogicLayerAsh(b, Blocks.BLOCK_ASH, Material.topAsh))
+        .withSound(BlockSounds.CLOTH).withHardness(0.1F).withLitInteriorSurface(true)
+        .withTags(BlockTags.BROKEN_BY_FLUIDS, BlockTags.PLACE_OVERWRITES, BlockTags.MINEABLE_BY_SHOVEL, BlockTags.OVERRIDE_STEPSOUND);
 
     public static final Block<?> ICE = register("ice", "minecraft:block/ice", 730, BlockLogicIce::new)
         .withSound(BlockSounds.GLASS).withHardness(0.5F).withLightBlock(3)
@@ -1006,6 +1017,9 @@ public final class Blocks {
     public static final Block<?> BLOCK_SNOW = register("block.snow", "minecraft:block/block_snow", 740, BlockLogicSnow::new)
         .withSound(BlockSounds.CLOTH).withHardness(0.2F)
         .withTags(BlockTags.CAVES_CUT_THROUGH, BlockTags.MINEABLE_BY_SHOVEL, BlockTags.FIREFLIES_CAN_SPAWN);
+    public static final Block<?> BLOCK_ASH = register("block.ash", "minecraft:block/block_ash", 741, BlockLogicAsh::new)
+        .withSound(BlockSounds.CLOTH).withHardness(0.2F)
+        .withTags(BlockTags.CAVES_CUT_THROUGH, BlockTags.MINEABLE_BY_SHOVEL);
 
     public static final Block<?> CACTUS = register("cactus", "minecraft:block/cactus", 750, BlockLogicCactus::new)
         .withSound(BlockSounds.CLOTH).withHardness(0.4F)
@@ -1075,7 +1089,7 @@ public final class Blocks {
 
     public static final Block<?> SOULSAND = register("soulsand", "minecraft:block/soulsand", 810, BlockLogicSoulSand::new)
         .withSound(BlockSounds.SAND).withHardness(0.5F).withOverrideColor(MaterialColor.paintedBrown)
-        .withTags(BlockTags.MINEABLE_BY_SHOVEL, BlockTags.CAVES_CUT_THROUGH, BlockTags.NETHER_MOBS_SPAWN);
+        .withTags(BlockTags.MINEABLE_BY_SHOVEL, BlockTags.CAVES_CUT_THROUGH, BlockTags.NETHER_MOBS_SPAWN, BlockTags.INFINITE_BURN_COLD);
     public static final Block<?> SOULSCHIST = register("soulschist", "minecraft:block/soulschist", 811, (b) -> new BlockLogicCobble(b, Material.stone, () -> Blocks.SOULSAND))
         .withSound(BlockSounds.STONE).withHardness(2.0F).withBlastResistance(10F).withOverrideColor(MaterialColor.paintedBrown)
         .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.NETHER_MOBS_SPAWN);
@@ -1083,6 +1097,15 @@ public final class Blocks {
     public static final Block<?> GLOWSTONE = register("glowstone", "minecraft:block/glowstone", 820, (b) -> new BlockLogicGlowStone(b, Material.stone))
         .withSound(BlockSounds.GLASS).withHardness(0.3F).withLightEmission(1.0F).withOverrideColor(MaterialColor.paintedYellow)
         .withTags(BlockTags.MINEABLE_BY_PICKAXE);
+    public static final Block<?> CRYSTAL = register("crystal", "minecraft:block/crystal", 821, (b) -> new BlockLogicCrystal(b))
+        .withSound(BlockSounds.GLASS).withLightEmission(0.8F)
+        .withTags(BlockTags.MINEABLE_BY_PICKAXE);
+    public static final Block<?> CRYSTAL_STALK = register("crystal.stalk", "minecraft:block/crystal_stalk", 822, (b) -> new BlockLogicStalk(b))
+        .withSound(BlockSounds.GLASS).withDisabledNeighborNotifyOnMetadataChange().withLightEmission(0.8F)
+        .withTags(BlockTags.MINEABLE_BY_PICKAXE);
+    public static final Block<?> NETHERRACK_CRYSTALLINE = register("netherrack.crystalline", "minecraft:block/netherrack_crystalline", 823, (b) -> new BlockLogic(b, Material.stone))
+        .withSound(BlockSounds.STONE).withDisabledNeighborNotifyOnMetadataChange()
+        .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.INFINITE_BURN, BlockTags.CAVES_CUT_THROUGH, BlockTags.NETHER_MOBS_SPAWN);
 
     public static final Block<BlockLogicPortal> PORTAL_NETHER = register("portal.nether", "minecraft:block/portal_nether", 830, (BlockLogicSupplier<BlockLogicPortal>) b -> new BlockLogicPortal(b, Dimension.NETHER, Blocks.OBSIDIAN, Blocks.FIRE))
         .withSound(BlockSounds.GLASS).withHardness(-1F).withLightEmission(0.75F).withOverrideColor(MaterialColor.paintedPurple).withDisabledStats()
@@ -1156,6 +1179,19 @@ public final class Blocks {
         .withSound(BlockSounds.CLOTH).withHardness(0.7F)
         .withTags(BlockTags.MINEABLE_BY_SHEARS, BlockTags.FENCES_CONNECT, BlockTags.CAN_HANG_OFF, BlockTags.MINEABLE_BY_AXE);
 
+    public static final Block<?> JAR_BUTTERFLY_BLUE = register("jar.butterfly.blue", "minecraft:block/jar_butterfly_blue", 920, (b) -> new BlockLogicJarButterfly(b, MobButterfly.COLOR_BLUE, () -> Items.JAR_BUTTERFLY_BLUE))
+        .withSound(BlockSounds.GLASS).withHardness(0.1F).withDisabledNeighborNotifyOnMetadataChange().setStatParent(() -> Items.JAR_BUTTERFLY_BLUE)
+        .withTags(BlockTags.BROKEN_BY_FLUIDS, BlockTags.MINEABLE_BY_PICKAXE, BlockTags.NOT_IN_CREATIVE_MENU);
+    public static final Block<?> JAR_BUTTERFLY_ORANGE = register("jar.butterfly.orange", "minecraft:block/jar_butterfly_orange", 921, (b) -> new BlockLogicJarButterfly(b, MobButterfly.COLOR_ORANGE, () -> Items.JAR_BUTTERFLY_ORANGE))
+        .withSound(BlockSounds.GLASS).withHardness(0.1F).withDisabledNeighborNotifyOnMetadataChange().setStatParent(() -> Items.JAR_BUTTERFLY_ORANGE)
+        .withTags(BlockTags.BROKEN_BY_FLUIDS, BlockTags.MINEABLE_BY_PICKAXE, BlockTags.NOT_IN_CREATIVE_MENU);
+    public static final Block<?> JAR_BUTTERFLY_PINK = register("jar.butterfly.pink", "minecraft:block/jar_butterfly_pink", 922, (b) -> new BlockLogicJarButterfly(b, MobButterfly.COLOR_PINK, () -> Items.JAR_BUTTERFLY_PINK))
+        .withSound(BlockSounds.GLASS).withHardness(0.1F).withDisabledNeighborNotifyOnMetadataChange().setStatParent(() -> Items.JAR_BUTTERFLY_PINK)
+        .withTags(BlockTags.BROKEN_BY_FLUIDS, BlockTags.MINEABLE_BY_PICKAXE, BlockTags.NOT_IN_CREATIVE_MENU);
+    public static final Block<?> JAR_BUTTERFLY_SILVER = register("jar.butterfly.silver", "minecraft:block/jar_butterfly_silver", 923, (b) -> new BlockLogicJarButterfly(b, MobButterfly.COLOR_SILVER, () -> Items.JAR_BUTTERFLY_SILVER))
+        .withSound(BlockSounds.GLASS).withHardness(0.1F).withDisabledNeighborNotifyOnMetadataChange().setStatParent(() -> Items.JAR_BUTTERFLY_SILVER)
+        .withTags(BlockTags.BROKEN_BY_FLUIDS, BlockTags.MINEABLE_BY_PICKAXE, BlockTags.NOT_IN_CREATIVE_MENU);
+
     public static final Block<BlockLogicSlab> SLAB_BRICK_SANDSTONE = register("slab.brick.sandstone", "minecraft:block/slab_brick_sandstone", 1000, (BlockLogicSupplier<BlockLogicSlab>) b -> new BlockLogicSlab(b, Blocks.BRICK_SANDSTONE))
         .withSound(BlockSounds.STONE).withDisabledNeighborNotifyOnMetadataChange().withLitInteriorSurface(true)
         .withTags(BlockTags.MINEABLE_BY_PICKAXE);
@@ -1169,6 +1205,13 @@ public final class Blocks {
         .withSound(BlockSounds.PERMAFROST).withDisabledNeighborNotifyOnMetadataChange().withLitInteriorSurface(true)
         .withTags(BlockTags.MINEABLE_BY_PICKAXE);
 
+    public static final Block<BlockLogicStatue> STATUE_STONE_LOWER = register("statue.stone.lower", "minecraft:block/statue_lower", 1010, (BlockLogicSupplier<BlockLogicStatue>) b -> new BlockLogicStatue(b, Material.stone, true, () -> Items.STATUE_STONE))
+        .withSound(BlockSounds.STONE).withHardness(1.5F)
+        .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.NOT_IN_CREATIVE_MENU);
+    public static final Block<BlockLogicStatue> STATUE_STONE_UPPER = register("statue.stone.upper", "minecraft:block/statue_upper", 1011, (BlockLogicSupplier<BlockLogicStatue>) b -> new BlockLogicStatue(b, Material.stone, false, () -> Items.STATUE_STONE))
+        .withSound(BlockSounds.STONE).withHardness(1.5F)
+        .withTags(BlockTags.MINEABLE_BY_PICKAXE, BlockTags.NOT_IN_CREATIVE_MENU);
+
     public static <T extends BlockLogic> Block<T> register(String key, String namespaceId, int id, BlockLogicSupplier<T> logicSupplier) {
         Block<T> container = new Block<>(id, key, namespaceId, logicSupplier);
 
@@ -1250,6 +1293,9 @@ public final class Blocks {
     @Nullable
     public static Block<?> getBlock(int id)
     {
+        if (id < 0 || id >= Blocks.blocksList.length) {
+            return null;
+        }
         return Blocks.blocksList[id];
     }
 
diff --git a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityDispatcher.java b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityDispatcher.java
index 1ee070d85..a5a326a4d 100644
--- a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityDispatcher.java
+++ b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityDispatcher.java
@@ -83,7 +83,9 @@ public class TileEntityDispatcher {
         addMapping(TileEntityFlag.class, new NamespaceID("minecraft", "flag"));
         addMapping(TileEntitySeat.class, new NamespaceID("minecraft", "seat"));
         addMapping(TileEntityFlowerJar.class, new NamespaceID("minecraft", "jar_flower"));
+        addMapping(TileEntityJarButterfly.class, new NamespaceID("minecraft", "jar_butterfly"));
         addMapping(TileEntityMeshGold.class, new NamespaceID("minecraft", "mesh_gold"));
+        addMapping(TileEntityStatue.class, new NamespaceID("minecraft", "statue_stone"));
     }
 
     @Deprecated
diff --git a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityJarButterfly.java b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityJarButterfly.java
new file mode 100644
index 000000000..b7f10d695
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityJarButterfly.java
@@ -0,0 +1,38 @@
+package net.minecraft.core.block.entity;
+
+import com.mojang.nbt.tags.CompoundTag;
+import net.minecraft.core.entity.animal.MobButterfly;
+
+public class TileEntityJarButterfly extends TileEntity{
+    public MobButterfly.ButterflyEntry color;
+    public int ticksRan = 0;
+
+    public TileEntityJarButterfly() {
+
+    }
+
+    public TileEntityJarButterfly(MobButterfly.ButterflyEntry color) {
+        this.color = color;
+    }
+
+    @Override
+    public void tick()
+    {
+        if (ticksRan == 0){
+            ticksRan = worldObj.rand.nextInt(360);
+        }
+        ticksRan++;
+    }
+
+    @Override
+    public void readFromNBT(CompoundTag compoundTag) {
+        super.readFromNBT(compoundTag);
+        color = MobButterfly.colorEntries.getOrDefault(compoundTag.getByte("color") & 0xFF, MobButterfly.COLOR_BLUE);
+    }
+
+    @Override
+    public void writeToNBT(CompoundTag compoundTag) {
+        super.writeToNBT(compoundTag);
+        compoundTag.putByte("color", (byte) color.id);
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityStatue.java b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityStatue.java
new file mode 100644
index 000000000..232058293
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/block/entity/TileEntityStatue.java
@@ -0,0 +1,140 @@
+package net.minecraft.core.block.entity;
+
+import com.mojang.nbt.tags.ByteTag;
+import com.mojang.nbt.tags.CompoundTag;
+import com.mojang.nbt.tags.ListTag;
+import com.mojang.nbt.tags.Tag;
+import net.minecraft.core.entity.IArmorWearing;
+import net.minecraft.core.enums.HumanArmorShape;
+import net.minecraft.core.item.ItemStack;
+import net.minecraft.core.net.packet.Packet;
+import net.minecraft.core.net.packet.PacketBlockEntityData;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class TileEntityStatue extends TileEntity implements IArmorWearing<HumanArmorShape> {
+    public enum Pose {
+        DEFAULT(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f),
+        LEFT_ARM_UP(-2.2f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f),
+        RIGHT_ARM_UP(0.0f, -2.2f, 0.0f, 0.0f, 0.0f, 0.0f),
+        BOTH_ARMS_UP(-2.2f, -2.2f, 0.0f, 0.0f, 0.0f, 0.0f),
+        WALKING(0.75f, -0.75f, 0.5f, -0.5f, 0.0f, 0.0f),
+        LOOK_UP(0.0f, 0.0f, 0.0f, 0.0f, -0.3f, 0.0f),
+        LOOK_DOWN(0.0f, 0.0f, 0.0f, 0.0f, 0.3f, 0.0f),
+        LOOK_LEFT(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f),
+        LOOK_RIGHT(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f),
+        ;
+
+        public final float leftArmPitch;
+        public final float rightArmPitch;
+        public final float leftLegPitch;
+        public final float rightLegPitch;
+        public final float headPitch;
+        public final float headYaw;
+
+        Pose(final float leftArmPitch, final float rightArmPitch, final float leftLegPitch, final float rightLegPitch, final float headPitch, final float headYaw) {
+            this.leftArmPitch = leftArmPitch;
+            this.rightArmPitch = rightArmPitch;
+            this.leftLegPitch = leftLegPitch;
+            this.rightLegPitch = rightLegPitch;
+            this.headPitch = headPitch;
+            this.headYaw = headYaw;
+        }
+    }
+
+    private final @Nullable ItemStack @NotNull[] armorPieces = new ItemStack[HumanArmorShape.values().length];
+    private @NotNull Pose pose = Pose.DEFAULT;
+
+    @Override
+    public void writeToNBT(CompoundTag tag) {
+        super.writeToNBT(tag);
+
+        final @NotNull ListTag list = new ListTag();
+        for (int i = 0; i < getNumArmorSlots(); i++) {
+            final @Nullable HumanArmorShape slot = getArmorSlotByIndex(i);
+            if (slot != null) {
+                final @Nullable ItemStack itemInSlot = getItemInArmorSlot(slot);
+                if (itemInSlot != null) {
+                    final @NotNull CompoundTag armorTag = new CompoundTag();
+                    armorTag.putByte("Slot", (byte) i);
+                    itemInSlot.writeToNBT(armorTag);
+                    list.addTag(armorTag);
+                }
+            }
+        }
+        tag.putList("Armor", list);
+        tag.putByte("Pose", (byte) this.pose.ordinal());
+    }
+
+    @Override
+    public void readFromNBT(CompoundTag tag) {
+        super.readFromNBT(tag);
+
+        final @Nullable ListTag list = tag.getList("Armor");
+        if (list != null) {
+            for (Tag<?> t : list) {
+                if (t instanceof CompoundTag) {
+                    final @NotNull CompoundTag ct = (CompoundTag) t;
+                    final @Nullable Tag<?> t2 = ct.getTag("Slot");
+                    if (t2 instanceof ByteTag) {
+                        final @Nullable HumanArmorShape armorSlot = getArmorSlotByIndex(((ByteTag) t2).getValue());
+                        if (armorSlot != null) {
+                            final @Nullable ItemStack stack = ItemStack.readItemStackFromNbt(ct);
+                            if (stack != null) {
+                                setItemInArmorSlot(armorSlot, stack);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        byte pose = tag.getByteOrDefault("Pose", (byte) Pose.DEFAULT.ordinal());
+        if (pose < 0 || pose >= Pose.values().length) {
+            pose = (byte) Pose.DEFAULT.ordinal();
+        }
+        this.pose = Pose.values()[pose];
+    }
+
+    public @NotNull Pose getPose() {
+        return this.pose;
+    }
+
+    public void setPose(final @NotNull Pose pose) {
+        this.pose = pose;
+        setChanged();
+    }
+
+    public void nextPose() {
+        setPose(Pose.values()[(getPose().ordinal() + 1) % Pose.values().length]);
+    }
+
+    @Override
+    public @Nullable ItemStack getItemInArmorSlot(final @NotNull HumanArmorShape slot) {
+        return this.armorPieces[slot.getSlotIndex()];
+    }
+
+    @Override
+    public void setItemInArmorSlot(final @NotNull HumanArmorShape slot, final @Nullable ItemStack item) {
+        this.armorPieces[slot.getSlotIndex()] = item;
+        setChanged();
+    }
+
+    @Override
+    public int getNumArmorSlots() {
+        return HumanArmorShape.values().length;
+    }
+
+    @Override
+    public @Nullable HumanArmorShape getArmorSlotByIndex(final int index) {
+        if (index < 0 || index >= HumanArmorShape.values().length) {
+            return null;
+        }
+        return HumanArmorShape.values()[index];
+    }
+
+    @Override
+    public Packet getDescriptionPacket() {
+        return new PacketBlockEntityData(this);
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/block/material/Material.java b/game/core/src/main/java/net/minecraft/core/block/material/Material.java
index 69e27c55f..d46fa1686 100644
--- a/game/core/src/main/java/net/minecraft/core/block/material/Material.java
+++ b/game/core/src/main/java/net/minecraft/core/block/material/Material.java
@@ -171,6 +171,8 @@ public class Material
     public static final Material ice = new Material(MaterialColor.ice).notSolidBlocking();
     public static final Material topSnow = new MaterialDecoration(MaterialColor.snow).replaceable().notSolidBlocking().notAlwaysDestroyable().destroyOnPush();
     public static final Material snow = new Material(MaterialColor.snow).notAlwaysDestroyable();
+    public static final Material topAsh = new MaterialDecoration(MaterialColor.snow).replaceable().notSolidBlocking().notAlwaysDestroyable().destroyOnPush();
+    public static final Material ash = new Material(MaterialColor.snow).notAlwaysDestroyable();
     public static final Material cactus = new Material(MaterialColor.plant).notSolidBlocking().destroyOnPush();
     public static final Material clay = new Material(MaterialColor.clay);
     public static final Material vegetable = new Material(MaterialColor.plant).destroyOnPush();
diff --git a/game/core/src/main/java/net/minecraft/core/block/tag/BlockTags.java b/game/core/src/main/java/net/minecraft/core/block/tag/BlockTags.java
index 790e9c36f..e038508dc 100644
--- a/game/core/src/main/java/net/minecraft/core/block/tag/BlockTags.java
+++ b/game/core/src/main/java/net/minecraft/core/block/tag/BlockTags.java
@@ -148,6 +148,10 @@ public abstract class BlockTags
      * Block allows fire to burn forever on it {@link BlockLogicFire BlockFire}
      */
     public static Tag<Block<?>> INFINITE_BURN = Tag.of("infinite_burn");
+    /**
+     * Block allows fire to burn forever on it {@link BlockFire BlockFire}
+     */
+    public static Tag<Block<?>> INFINITE_BURN_COLD = Tag.of("infinite_burn_cold");
     /**
      * Block is broken on contact with an {@link EntityBoat EntityBoat}
      */
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 632ec58df..920602191 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
@@ -5,8 +5,11 @@ import java.util.Random;
 
 import net.minecraft.core.block.BlockLogicFluid;
 import net.minecraft.core.block.Blocks;
+import net.minecraft.core.entity.vehicle.EntityBoat;
+import net.minecraft.core.entity.vehicle.MobGhastVehicle;
 import net.minecraft.core.util.collection.NamespaceID;
 import net.minecraft.core.util.helper.DyeColor;
+import net.minecraft.core.world.Dimension;
 import net.minecraft.core.world.ICarriable;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -312,6 +315,10 @@ public abstract class Entity
         {
             lavaHurt();
         }
+        if (isInWater())
+        {
+            waterHurt();
+        }
 
         if (y < -64D)
         {
@@ -374,6 +381,12 @@ public abstract class Entity
         }
     }
 
+    public void waterHurt() {
+        if (world != null && world.dimension == Dimension.NETHER && !(vehicle instanceof EntityBoat)) {
+            hurt(null, 1, DamageType.FIRE);
+        }
+    }
+
     public void lavaHurt()
     {
         if(!fireImmune)
@@ -1215,7 +1228,7 @@ public abstract class Entity
         if (vehicle == null) {
             return;
         }
-        if(vehicle.isRemoved() || isSneaking())
+        if(vehicle.isRemoved() || (isSneaking() && !(vehicle instanceof MobGhastVehicle)))
         {
             vehicle.ejectRider();
             return;
diff --git a/game/core/src/main/java/net/minecraft/core/entity/EntityDispatcher.java b/game/core/src/main/java/net/minecraft/core/entity/EntityDispatcher.java
index bddf1ccfb..7fb4b9ae6 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/EntityDispatcher.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/EntityDispatcher.java
@@ -6,6 +6,7 @@ import net.minecraft.core.entity.monster.*;
 import net.minecraft.core.entity.projectile.*;
 import net.minecraft.core.entity.vehicle.EntityBoat;
 import net.minecraft.core.entity.vehicle.MinecartEntity;
+import net.minecraft.core.entity.vehicle.MobGhastVehicle;
 import com.mojang.nbt.tags.CompoundTag;
 import net.minecraft.core.util.collection.NamespaceID;
 import net.minecraft.core.world.World;
@@ -208,6 +209,7 @@ public final class EntityDispatcher
         addMapping(MobGiant.class, new NamespaceID("minecraft", "giant"));
         addMapping(MobSlime.class, new NamespaceID("minecraft", "slime"), "guidebook.section.mob.slime.name");
         addMapping(MobGhast.class, new NamespaceID("minecraft", "ghast"), "guidebook.section.mob.ghast.name");
+        addMapping(MobGhastVehicle.class, new NamespaceID("minecraft", "ghast_vehicle"), "guidebook.section.mob.ghast.name");
         addMapping(MobSnowman.class, new NamespaceID("minecraft", "snowman"), "guidebook.section.mob.snowman.name");
 
         // Passive
@@ -219,6 +221,7 @@ public final class EntityDispatcher
         addMapping(MobSquid.class, new NamespaceID("minecraft", "squid"), "guidebook.section.mob.squid.name");
         addMapping(MobWolf.class, new NamespaceID("minecraft", "wolf"), "guidebook.section.mob.wolf.name");
         addMapping(MobFireflyCluster.class, new NamespaceID("minecraft", "firefly_cluster"), "guidebook.section.mob.firefly_cluster.name");
+        addMapping(MobButterfly.class, new NamespaceID("minecraft", "butterfly"), "guidebook.section.mob.butterfly.name");
     }
 
     @Deprecated
diff --git a/game/core/src/main/java/net/minecraft/core/entity/IArmorWearing.java b/game/core/src/main/java/net/minecraft/core/entity/IArmorWearing.java
new file mode 100644
index 000000000..600bba331
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/entity/IArmorWearing.java
@@ -0,0 +1,84 @@
+package net.minecraft.core.entity;
+
+import net.minecraft.core.enums.IArmorShape;
+import net.minecraft.core.item.*;
+import net.minecraft.core.item.material.ArmorMaterial;
+import net.minecraft.core.util.helper.DamageType;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Objects;
+
+public interface IArmorWearing<T extends IArmorShape> {
+    @Nullable ItemStack getItemInArmorSlot(final @NotNull T slot);
+
+    void setItemInArmorSlot(final @NotNull T slot, final @Nullable ItemStack item);
+
+    int getNumArmorSlots();
+
+    @Nullable T getArmorSlotByIndex(final int index);
+
+    default float getTotalProtectionAmount(final @NotNull DamageType damageType) {
+        float protectionPercentage = 0.0f;
+
+        for (int i = 0; i < getNumArmorSlots(); i++) {
+            final @NotNull T armorSlot = Objects.requireNonNull(getArmorSlotByIndex(i));
+            final @Nullable ItemStack armorItem = getItemInArmorSlot(armorSlot);
+            if (armorItem != null && armorItem.getItem() instanceof IArmorItem<?>) {
+                final @NotNull IArmorItem<?> armor = (IArmorItem<?>) armorItem.getItem();
+                final @Nullable ArmorMaterial material = armor.getArmorMaterial();
+                if (material != null) {
+                    protectionPercentage += material.getProtection(damageType) * armor.getArmorPieceProtectionPercentage();
+                }
+            }
+        }
+
+        return protectionPercentage;
+    }
+
+    default void damageArmor(final int damage, final @NotNull T armorSlot) {
+        final @Nullable ItemStack armorItem = getItemInArmorSlot(armorSlot);
+        if (armorItem != null && armorItem.getItem() instanceof IArmorItem<?>) {
+            final @NotNull IArmorItem<?> armor = (IArmorItem<?>) armorItem.getItem();
+            final @Nullable ArmorMaterial material = armor.getArmorMaterial();
+            if (material != null) {
+                if (this instanceof Entity) {
+                    armorItem.damageItem(damage, (Entity) this);
+                } else {
+                    armorItem.damageItem(damage, null);
+                }
+                if (armorItem.stackSize <= 0) {
+                    setItemInArmorSlot(armorSlot, null);
+                }
+            }
+        }
+    }
+
+    default void damageArmor(final int damage) {
+        for (int i = 0; i < getNumArmorSlots(); i++) {
+            final @NotNull T armorSlot = Objects.requireNonNull(getArmorSlotByIndex(i));
+            damageArmor(damage, armorSlot);
+        }
+    }
+
+    default boolean canItemGoInArmorSlot(final @NotNull T slot, final @Nullable ItemStack item) {
+        if (item == null) {
+            return false;
+        }
+
+        return canItemGoInArmorSlot(slot, item.getItem());
+    }
+
+    default boolean canItemGoInArmorSlot(final @NotNull T slot, final @Nullable IItemConvertible item) {
+        if (item == null || item.asItem() == null) {
+            return false;
+        }
+
+        final @NotNull Item realItem = item.asItem();
+        if (!(realItem instanceof IArmorItem)) {
+            return false;
+        }
+
+        return ((IArmorItem<T>) realItem).getArmorShape() == slot;
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/entity/animal/MobButterfly.java b/game/core/src/main/java/net/minecraft/core/entity/animal/MobButterfly.java
new file mode 100644
index 000000000..dea7bdc18
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/entity/animal/MobButterfly.java
@@ -0,0 +1,324 @@
+package net.minecraft.core.entity.animal;
+
+import com.mojang.nbt.tags.CompoundTag;
+import net.minecraft.core.Global;
+import net.minecraft.core.WeightedRandomBag;
+import net.minecraft.core.block.Block;
+import net.minecraft.core.entity.Entity;
+import net.minecraft.core.entity.MobFlying;
+import net.minecraft.core.entity.SkinVariantList;
+import net.minecraft.core.entity.player.Player;
+import net.minecraft.core.item.Item;
+import net.minecraft.core.item.Items;
+import net.minecraft.core.util.collection.NamespaceID;
+import net.minecraft.core.util.helper.DamageType;
+import net.minecraft.core.util.helper.MathHelper;
+import net.minecraft.core.util.phys.AABB;
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.biome.Biome;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Supplier;
+
+public class MobButterfly extends MobFlying implements Creature {
+    public static final Map<Integer, ButterflyEntry> colorEntries = new HashMap<>();
+
+    public static final int DATA_COLOR_ID = 16;
+
+    public static final ButterflyEntry COLOR_BLUE;
+    public static final ButterflyEntry COLOR_ORANGE;
+    public static final ButterflyEntry COLOR_PINK;
+    public static final ButterflyEntry COLOR_SILVER;
+
+    public static ButterflyEntry addEntry(int id, String name, double weight, Supplier<Item> jarItem, Biome ... allowedBiomes) {
+        assert id >= 0 && id <= 0xFF : "ID is outside valid range!";
+        ButterflyEntry entry = new ButterflyEntry(id, name, weight, jarItem, allowedBiomes);
+        colorEntries.put(id, entry);
+        return entry;
+    }
+
+    static {
+        COLOR_BLUE = addEntry(0, "blue", 1, () -> Items.JAR_BUTTERFLY_BLUE);
+        COLOR_ORANGE = addEntry(1, "orange", 1, () -> Items.JAR_BUTTERFLY_ORANGE);
+        COLOR_PINK = addEntry(2, "pink", 1, () -> Items.JAR_BUTTERFLY_PINK);
+        COLOR_SILVER = addEntry(3, "silver", 1, () -> Items.JAR_BUTTERFLY_SILVER);
+    }
+
+    public int courseChangeCooldown = 0;
+    public double waypointX = 0;
+    public double waypointY = 0;
+    public double waypointZ = 0;
+
+    public MobButterfly(World world) {
+        super(world);
+        textureIdentifier = new NamespaceID("minecraft", "butterfly");
+        setSize(0.5f, 0.5f);
+    }
+
+    @Override
+    protected void defineSynchedData() {
+        super.defineSynchedData();
+        entityData.define(DATA_COLOR_ID, (byte)0, Byte.class);
+    }
+
+    @Override
+    protected boolean makeStepSound()
+    {
+        return false;
+    }
+
+    @Override
+    public boolean isPickable() {
+        return false;
+    }
+
+    @Override
+    public boolean isPushable() {
+        return false;
+    }
+
+    @Override
+    public boolean isSelectable() {
+        return false;
+    }
+
+    @Override
+    public String getLivingSound()
+    {
+        return "";
+    }
+
+    @Override
+    protected String getHurtSound()
+    {
+        return "";
+    }
+
+    @Override
+    protected String getDeathSound()
+    {
+        return "";
+    }
+
+    @Override
+    public boolean canRide() {
+        return false;
+    }
+
+    @Override
+    public void push(Entity entity) {
+    }
+
+    @Override
+    public boolean hurt(Entity attacker, int i, DamageType type)
+    {
+        if (type == null || type == DamageType.GENERIC || type == DamageType.BLAST || type == DamageType.FIRE || type == DamageType.DROWN) {
+            return super.hurt(attacker, i, type);
+        }
+        return false;
+    }
+
+
+    @Override
+    public void spawnInit() {
+        super.spawnInit();
+
+        Biome currentBiome = world.getBlockBiome(MathHelper.floor(x), MathHelper.floor(y), MathHelper.floor(z));
+
+        WeightedRandomBag<ButterflyEntry> colorBag = new WeightedRandomBag<>();
+        for (ButterflyEntry entry : colorEntries.values()) {
+            if (entry.isBiomeAllowed(currentBiome)) {
+                colorBag.addEntry(entry, entry.weight);
+            }
+        }
+
+        ButterflyEntry entry = colorBag.getRandom();
+        if (entry == null) entry = COLOR_BLUE;
+        setColor(entry.id);
+    }
+
+    private boolean isCourseTraversable(double waypointX, double waypointY, double waypointZ, double distanceFromEntity)
+    {
+
+        double dx = (waypointX - x) / distanceFromEntity;
+        double dy = (waypointY - y) / distanceFromEntity;
+        double dz = (waypointZ - z) / distanceFromEntity;
+        AABB entityBB = bb.copy();
+        for(int i = 0; (double)i < distanceFromEntity; i++)
+        {
+            entityBB.move(dx, dy, dz);
+            if(!world.getCubes(this, entityBB).isEmpty())
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    @Override
+    protected void updateAI()
+    {
+        entityAge++;
+        tryToDespawn();
+
+        Player closestPlayer = world.getClosestPlayerToEntity(this, 2);
+        if (closestPlayer != null) {
+            double distanceX = closestPlayer.x - x;
+//            double distanceY = closestPlayer.y - y;
+            double distanceZ = closestPlayer.z - z;
+
+            double distance = Math.hypot(distanceX, distanceZ);
+
+            waypointX = x + (distanceX / distance) * -5;
+            waypointY = y + 4;
+            waypointZ = z + (distanceZ / distance) * -5;
+        }
+
+        double distanceX = waypointX - x;
+        double distanceY = waypointY - y;
+        double distanceZ = waypointZ - z;
+
+        double distanceFromEntity = MathHelper.sqrt(distanceX * distanceX + distanceY * distanceY + distanceZ * distanceZ);
+
+        if (distanceFromEntity > 32) {
+            waypointX = x;
+            waypointY = y;
+            waypointZ = z;
+            return;
+        }
+
+        if (distanceFromEntity > 1D) {
+            xd += (distanceX / distanceFromEntity) * 0.01D;
+            yd += (distanceY / distanceFromEntity) * 0.01D;
+            zd += (distanceZ / distanceFromEntity) * 0.01D;
+            yRot = (float) ((Math.atan2(xd, zd) * 180F) / MathHelper.PI);
+        }
+
+        if(courseChangeCooldown-- <= 0) {
+            courseChangeCooldown += random.nextInt(40) + 20;
+            if (!(distanceFromEntity > 1D) || !isCourseTraversable(waypointX, waypointY, waypointZ, distanceFromEntity)) {
+                double nextX = x + (double) ((random.nextFloat() * 2.0F - 1.0F) * 16F);
+                double nextY = y + (double) ((random.nextFloat() * 2.0F - 1.0F) * 5F);
+                double nextZ = z + (double) ((random.nextFloat() * 2.0F - 1.0F) * 16F);
+
+                if (nextY > y) {
+                    if (isUpwardWaypointValid(MathHelper.floor(nextX), MathHelper.floor(nextY), MathHelper.floor(nextZ))) {
+                        waypointX = nextX;
+                        waypointY = nextY;
+                        waypointZ = nextZ;
+                    } else {
+                        waypointX = x;
+                        waypointY = y;
+                        waypointZ = z;
+                    }
+                } else {
+                    waypointX = nextX;
+                    waypointY = nextY;
+                    waypointZ = nextZ;
+                }
+            } /*else {
+                xd += (distanceX / distanceFromEntity) * 0.1D;
+                yd += (distanceY / distanceFromEntity) * 0.1D;
+                zd += (distanceZ / distanceFromEntity) * 0.1D;
+                yRot = (float) ((Math.atan2(xd, zd) * 180F) / MathHelper.PI);
+            }*/
+        }
+    }
+
+    public boolean isUpwardWaypointValid(int x, int y, int z) {
+        final byte range = 3;
+        for (int _y = y; _y > y - range; _y--) {
+            Block<?> block = world.getBlock(x, _y, z);
+            if (block == null/* || !block.hasTag(BlockTags.PASSIVE_MOBS_SPAWN)*/) continue;
+            return true;
+        }
+        return false;
+    }
+
+    public int getColor() {
+        return entityData.getByte(DATA_COLOR_ID) & 0xFF;
+    }
+
+    public void setColor(int color) {
+        assert color >= 0 && color <= 0xFF : "Color is outside valid range!";
+        entityData.set(DATA_COLOR_ID, (byte)color);
+    }
+
+    @Override
+    public void addAdditionalSaveData(CompoundTag tag) {
+        super.addAdditionalSaveData(tag);
+        tag.putByte("Color", (byte) getColor());
+    }
+
+    @Override
+    public void readAdditionalSaveData(CompoundTag tag) {
+        super.readAdditionalSaveData(tag);
+        setColor(tag.getByte("Color") & 0xFF);
+    }
+
+    @Override
+    public String getEntityTexture() {
+        String basePath = String.format("/assets/%s/textures/entity/%s/", textureIdentifier.namespace, textureIdentifier.value);
+        return basePath + getTextureReference() + "_0.png";
+//        if ((tickCount / 10) % 2 == 0) {
+//            return basePath + getTextureReference() + "_0.png";
+//        } else {
+//            return basePath + getTextureReference() + "_1.png";
+//        }
+    }
+
+    @Override
+    public String getTextureReference() {
+        SkinVariantList variantList = Global.accessor.getSkinVariantList();
+        String basePath = String.format("/assets/%s/textures/entity/%s/", textureIdentifier.namespace, textureIdentifier.value);
+        return colorEntries.get(getColor()).name + "/" + variantList.getSkinReference(basePath + "variants.json", "0", getSkinVariant());
+    }
+
+    public static class ButterflyEntry {
+        public final int id;
+        public final String name;
+        /** Biomes which the color can appear, null indicates any biome*/
+        private @Nullable Set<Biome> allowedBiomes;
+        public final Supplier<Item> jarItem;
+        public final double weight;
+
+        public ButterflyEntry(int id, String name, double weight, Supplier<Item> jarItem, Biome ... allowedBiomes) {
+            this.id = id;
+            this.name = name;
+            this.weight = weight;
+            this.jarItem = jarItem;
+
+            if (allowedBiomes.length > 0) {
+                this.allowedBiomes = new HashSet<>();
+                this.allowedBiomes.addAll(Arrays.asList(allowedBiomes));
+            }
+        }
+
+        public ButterflyEntry addAllowedBiome(Biome biome) {
+            if (allowedBiomes == null) allowedBiomes = new HashSet<>();
+            this.allowedBiomes.add(biome);
+            return this;
+        }
+
+        public ButterflyEntry removeAllowedBiome(Biome biome) {
+            if (allowedBiomes == null) allowedBiomes = new HashSet<>();
+            this.allowedBiomes.remove(biome);
+            return this;
+        }
+
+        public ButterflyEntry allowAllBiomes() {
+            this.allowedBiomes = null;
+            return this;
+        }
+
+        public boolean isBiomeAllowed(Biome biome) {
+            return allowedBiomes == null || allowedBiomes.contains(biome);
+        }
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/entity/monster/MobGhast.java b/game/core/src/main/java/net/minecraft/core/entity/monster/MobGhast.java
index 5bec715fd..cfc4e51e6 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/monster/MobGhast.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/monster/MobGhast.java
@@ -4,6 +4,7 @@ import net.minecraft.core.WeightedRandomLootObject;
 import net.minecraft.core.entity.MobFlying;
 import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.entity.projectile.ProjectileFireball;
+import net.minecraft.core.entity.vehicle.MobGhastVehicle;
 import net.minecraft.core.util.collection.NamespaceID;
 import net.minecraft.core.util.phys.Vec3;
 import net.minecraft.core.entity.Entity;
@@ -149,6 +150,9 @@ public class MobGhast extends MobFlying
             {
             	targetedEntity = null;
             }
+            if (targetedEntity != null && targetedEntity.vehicle instanceof MobGhastVehicle) {
+                targetedEntity = null;
+            }
             if(targetedEntity != null)
             {
                 aggroCooldown = 20;
diff --git a/game/core/src/main/java/net/minecraft/core/entity/monster/MobMonsterArmored.java b/game/core/src/main/java/net/minecraft/core/entity/monster/MobMonsterArmored.java
new file mode 100644
index 000000000..e73ef804b
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/entity/monster/MobMonsterArmored.java
@@ -0,0 +1,130 @@
+package net.minecraft.core.entity.monster;
+
+import com.mojang.nbt.tags.ByteTag;
+import com.mojang.nbt.tags.CompoundTag;
+import com.mojang.nbt.tags.ListTag;
+import com.mojang.nbt.tags.Tag;
+import net.minecraft.core.entity.IArmorWearing;
+import net.minecraft.core.enums.IArmorShape;
+import net.minecraft.core.item.ItemStack;
+import net.minecraft.core.util.helper.DamageType;
+import net.minecraft.core.world.World;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public abstract class MobMonsterArmored<T extends IArmorShape> extends MobMonster implements IArmorWearing<T> {
+    private static final int DATA_ARMOR_START = 24;
+
+    public MobMonsterArmored(final @Nullable World world) {
+        super(world);
+    }
+
+    @Override
+    protected void defineSynchedData() {
+        super.defineSynchedData();
+        for (int i = 0; i < getNumArmorSlots(); i++) {
+            this.entityData.define(DATA_ARMOR_START + i, null, ItemStack.class);
+        }
+    }
+
+    @Override
+    public @Nullable ItemStack getItemInArmorSlot(final @NotNull T slot) {
+        return this.entityData.getItemStack(DATA_ARMOR_START + slot.getSlotIndex());
+    }
+
+    @Override
+    public void setItemInArmorSlot(final @NotNull T slot, @Nullable ItemStack item) {
+        this.entityData.set(DATA_ARMOR_START + slot.getSlotIndex(), item);
+    }
+
+    @Override
+    public void addAdditionalSaveData(@NotNull CompoundTag tag) {
+        super.addAdditionalSaveData(tag);
+
+        final @NotNull ListTag list = new ListTag();
+        for (int i = 0; i < getNumArmorSlots(); i++) {
+            final @Nullable T slot = getArmorSlotByIndex(i);
+            if (slot != null) {
+                final @Nullable ItemStack itemInSlot = getItemInArmorSlot(slot);
+                if (itemInSlot != null) {
+                    final @NotNull CompoundTag armorTag = new CompoundTag();
+                    armorTag.putByte("Slot", (byte) i);
+                    itemInSlot.writeToNBT(armorTag);
+                    list.addTag(armorTag);
+                }
+            }
+        }
+        tag.putList("Armor", list);
+    }
+
+    @Override
+    public void readAdditionalSaveData(@NotNull CompoundTag tag) {
+        super.readAdditionalSaveData(tag);
+
+        final @Nullable ListTag list = tag.getList("Armor");
+        if (list != null) {
+            for (Tag<?> t : list) {
+                if (t instanceof CompoundTag) {
+                    final @NotNull CompoundTag ct = (CompoundTag) t;
+                    final @Nullable Tag<?> t2 = ct.getTag("Slot");
+                    if (t2 instanceof ByteTag) {
+                        final @Nullable T armorSlot = getArmorSlotByIndex(((ByteTag) t2).getValue());
+                        if (armorSlot != null) {
+                            final @Nullable ItemStack stack = ItemStack.readItemStackFromNbt(ct);
+                            if (stack != null) {
+                                setItemInArmorSlot(armorSlot, stack);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    protected void damageEntity(int damage, DamageType damageType) {
+        float protection = 1.0f;
+        if (damageType != null) {
+            protection -= getTotalProtectionAmount(damageType);
+        }
+        protection = Math.max(protection, 0.01f);
+
+        //If you take little damage and have lots of protection against that damage
+        //there is a high change you won't take any damage
+        double d = damage * protection;
+        int newDamage = (int) (random.nextFloat() > 0.5 ? Math.floor(d) : Math.ceil(d));
+
+        int preventedDamage = damage - newDamage;
+        if(damageType != null && damageType.shouldDamageArmor()){
+            //Reduce armor for big damage amounts, for example explosions
+            int armorDamage = (int) Math.ceil(preventedDamage / 4.0);
+            damageArmor(armorDamage);
+        }
+
+        super.damageEntity(damage, damageType);
+    }
+
+    @Override
+    public void lavaHurt() {
+        if(!fireImmune)
+        {
+            float protection = 1.0f - getTotalProtectionAmount(DamageType.FIRE);
+            protection = Math.max(protection, 0.01f);
+            hurt(null, 4, DamageType.FIRE);
+            remainingFireTicks = (int) (80 + 520 * protection);
+            maxFireTicks = remainingFireTicks;
+        }
+    }
+
+    @Override
+    public void fireHurt() {
+        if(!fireImmune)
+        {
+            float protection = 1.0f - getTotalProtectionAmount(DamageType.FIRE);
+            protection = Math.max(protection, 0.01f);
+            hurt(null, 1, DamageType.FIRE);
+            remainingFireTicks = (int) (40 + 260 * protection);
+            maxFireTicks = remainingFireTicks;
+        }
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/entity/monster/MobSkeleton.java b/game/core/src/main/java/net/minecraft/core/entity/monster/MobSkeleton.java
index bed83081e..b9b827d80 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/monster/MobSkeleton.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/monster/MobSkeleton.java
@@ -3,8 +3,11 @@ package net.minecraft.core.entity.monster;
 import net.minecraft.core.WeightedRandomLootObject;
 import net.minecraft.core.entity.Entity;
 import net.minecraft.core.entity.projectile.ProjectileArrow;
+import net.minecraft.core.enums.HumanArmorShape;
+import net.minecraft.core.item.Item;
 import net.minecraft.core.item.Items;
 import net.minecraft.core.item.ItemStack;
+import net.minecraft.core.item.material.ArmorMaterial;
 import net.minecraft.core.util.collection.NamespaceID;
 import net.minecraft.core.util.helper.MathHelper;
 import com.mojang.nbt.tags.CompoundTag;
@@ -12,18 +15,72 @@ import net.minecraft.core.world.World;
 import net.minecraft.core.world.weather.Weather;
 import net.minecraft.core.world.weather.Weathers;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
-public class MobSkeleton extends MobMonster
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+
+public class MobSkeleton extends MobMonsterArmored<HumanArmorShape>
 {
     public static final int DATA_ATTACK_TIME = 15;
 
-    public MobSkeleton(World world)
-    {
+    public MobSkeleton(World world) {
         super(world);
         textureIdentifier = new NamespaceID("minecraft", "skeleton");
         scoreValue = 500;
         mobDrops.add(new WeightedRandomLootObject(Items.AMMO_ARROW.getDefaultStack(), 0, 2));
         mobDrops.add(new WeightedRandomLootObject(Items.BONE.getDefaultStack(), 0, 2));
+
+        Map<ArmorMaterial, Map<HumanArmorShape, Item>> itemsMapMap = new HashMap<>();
+
+        Map<HumanArmorShape, Item> itemsMap = new HashMap<>();
+        itemsMap.put(HumanArmorShape.BOOTS, Items.ARMOR_BOOTS_LEATHER);
+        itemsMap.put(HumanArmorShape.LEGS, Items.ARMOR_LEGGINGS_LEATHER);
+        itemsMap.put(HumanArmorShape.CHEST, Items.ARMOR_CHESTPLATE_LEATHER);
+        itemsMap.put(HumanArmorShape.HEAD, Items.ARMOR_CHESTPLATE_LEATHER);
+        itemsMapMap.put(ArmorMaterial.LEATHER, itemsMap);
+
+        itemsMap = new HashMap<>();
+        itemsMap.put(HumanArmorShape.BOOTS, Items.ARMOR_BOOTS_IRON);
+        itemsMap.put(HumanArmorShape.LEGS, Items.ARMOR_LEGGINGS_IRON);
+        itemsMap.put(HumanArmorShape.CHEST, Items.ARMOR_CHESTPLATE_IRON);
+        itemsMap.put(HumanArmorShape.HEAD, Items.ARMOR_CHESTPLATE_IRON);
+        itemsMapMap.put(ArmorMaterial.IRON, itemsMap);
+
+        itemsMap = new HashMap<>();
+        itemsMap.put(HumanArmorShape.BOOTS, Items.ARMOR_BOOTS_GOLD);
+        itemsMap.put(HumanArmorShape.LEGS, Items.ARMOR_LEGGINGS_GOLD);
+        itemsMap.put(HumanArmorShape.CHEST, Items.ARMOR_CHESTPLATE_GOLD);
+        itemsMap.put(HumanArmorShape.HEAD, Items.ARMOR_CHESTPLATE_GOLD);
+        itemsMapMap.put(ArmorMaterial.GOLD, itemsMap);
+
+        itemsMap = new HashMap<>();
+        itemsMap.put(HumanArmorShape.BOOTS, Items.ARMOR_BOOTS_CHAINMAIL);
+        itemsMap.put(HumanArmorShape.LEGS, Items.ARMOR_LEGGINGS_CHAINMAIL);
+        itemsMap.put(HumanArmorShape.CHEST, Items.ARMOR_CHESTPLATE_CHAINMAIL);
+        itemsMap.put(HumanArmorShape.HEAD, Items.ARMOR_CHESTPLATE_CHAINMAIL);
+        itemsMapMap.put(ArmorMaterial.CHAINMAIL, itemsMap);
+
+        itemsMap = new HashMap<>();
+        itemsMap.put(HumanArmorShape.BOOTS, Items.ARMOR_BOOTS_DIAMOND);
+        itemsMap.put(HumanArmorShape.LEGS, Items.ARMOR_LEGGINGS_DIAMOND);
+        itemsMap.put(HumanArmorShape.CHEST, Items.ARMOR_CHESTPLATE_DIAMOND);
+        itemsMap.put(HumanArmorShape.HEAD, Items.ARMOR_CHESTPLATE_DIAMOND);
+        itemsMapMap.put(ArmorMaterial.DIAMOND, itemsMap);
+
+        itemsMap = new HashMap<>();
+        itemsMap.put(HumanArmorShape.BOOTS, Items.ARMOR_BOOTS_STEEL);
+        itemsMap.put(HumanArmorShape.LEGS, Items.ARMOR_LEGGINGS_STEEL);
+        itemsMap.put(HumanArmorShape.CHEST, Items.ARMOR_CHESTPLATE_STEEL);
+        itemsMap.put(HumanArmorShape.HEAD, Items.ARMOR_CHESTPLATE_STEEL);
+        itemsMapMap.put(ArmorMaterial.STEEL, itemsMap);
+
+        Random rand = new Random();
+        for (HumanArmorShape shape : HumanArmorShape.values()) {
+            ArmorMaterial mat = ArmorMaterial.getArmorMaterials().get(rand.nextInt(ArmorMaterial.getArmorMaterials().size()));
+            setItemInArmorSlot(shape, new ItemStack(itemsMapMap.get(mat).get(shape)));
+        }
     }
 
     @Override
@@ -97,26 +154,28 @@ public class MobSkeleton extends MobMonster
     }
 
     @Override
-	public void addAdditionalSaveData(@NotNull CompoundTag tag)
+	public ItemStack getHeldItem()
     {
-        super.addAdditionalSaveData(tag);
+        return defaultHeldItem;
     }
 
     @Override
-	public void readAdditionalSaveData(@NotNull CompoundTag tag)
-    {
-        super.readAdditionalSaveData(tag);
+    public int getNumArmorSlots() {
+        return HumanArmorShape.values().length;
     }
 
     @Override
-	public ItemStack getHeldItem()
-    {
-        return defaultHeldItem;
+    public @Nullable HumanArmorShape getArmorSlotByIndex(int index) {
+        if (index < 0 || index >= HumanArmorShape.values().length) {
+            return null;
+        }
+
+        return HumanArmorShape.values()[index];
     }
 
     private static final ItemStack defaultHeldItem;
 
-    static 
+    static
     {
         defaultHeldItem = new ItemStack(Items.TOOL_BOW, 1);
     }
diff --git a/game/core/src/main/java/net/minecraft/core/entity/monster/MobZombie.java b/game/core/src/main/java/net/minecraft/core/entity/monster/MobZombie.java
index 92a769a7c..cd9d8345e 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/monster/MobZombie.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/monster/MobZombie.java
@@ -1,24 +1,84 @@
 package net.minecraft.core.entity.monster;
 
+import com.mojang.nbt.tags.CompoundTag;
+import com.mojang.nbt.tags.ListTag;
 import net.minecraft.core.WeightedRandomLootObject;
+import net.minecraft.core.enums.HumanArmorShape;
+import net.minecraft.core.item.Item;
+import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.item.Items;
+import net.minecraft.core.item.material.ArmorMaterial;
 import net.minecraft.core.util.collection.NamespaceID;
 import net.minecraft.core.util.helper.MathHelper;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.weather.Weather;
 import net.minecraft.core.world.weather.Weathers;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
-public class MobZombie extends MobMonster
-{
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
 
-    public MobZombie(World world)
-    {
+public class MobZombie extends MobMonsterArmored<HumanArmorShape>
+{
+    public MobZombie(final World world) {
         super(world);
         textureIdentifier = new NamespaceID("minecraft", "zombie");
         moveSpeed = 0.5F;
         attackStrength = 5;
         scoreValue = 300;
         mobDrops.add(new WeightedRandomLootObject(Items.CLOTH.getDefaultStack(), 0, 2));
+
+        Map<ArmorMaterial, Map<HumanArmorShape, Item>> itemsMapMap = new HashMap<>();
+
+        Map<HumanArmorShape, Item> itemsMap = new HashMap<>();
+        itemsMap.put(HumanArmorShape.BOOTS, Items.ARMOR_BOOTS_LEATHER);
+        itemsMap.put(HumanArmorShape.LEGS, Items.ARMOR_LEGGINGS_LEATHER);
+        itemsMap.put(HumanArmorShape.CHEST, Items.ARMOR_CHESTPLATE_LEATHER);
+        itemsMap.put(HumanArmorShape.HEAD, Items.ARMOR_CHESTPLATE_LEATHER);
+        itemsMapMap.put(ArmorMaterial.LEATHER, itemsMap);
+
+        itemsMap = new HashMap<>();
+        itemsMap.put(HumanArmorShape.BOOTS, Items.ARMOR_BOOTS_IRON);
+        itemsMap.put(HumanArmorShape.LEGS, Items.ARMOR_LEGGINGS_IRON);
+        itemsMap.put(HumanArmorShape.CHEST, Items.ARMOR_CHESTPLATE_IRON);
+        itemsMap.put(HumanArmorShape.HEAD, Items.ARMOR_CHESTPLATE_IRON);
+        itemsMapMap.put(ArmorMaterial.IRON, itemsMap);
+
+        itemsMap = new HashMap<>();
+        itemsMap.put(HumanArmorShape.BOOTS, Items.ARMOR_BOOTS_GOLD);
+        itemsMap.put(HumanArmorShape.LEGS, Items.ARMOR_LEGGINGS_GOLD);
+        itemsMap.put(HumanArmorShape.CHEST, Items.ARMOR_CHESTPLATE_GOLD);
+        itemsMap.put(HumanArmorShape.HEAD, Items.ARMOR_CHESTPLATE_GOLD);
+        itemsMapMap.put(ArmorMaterial.GOLD, itemsMap);
+
+        itemsMap = new HashMap<>();
+        itemsMap.put(HumanArmorShape.BOOTS, Items.ARMOR_BOOTS_CHAINMAIL);
+        itemsMap.put(HumanArmorShape.LEGS, Items.ARMOR_LEGGINGS_CHAINMAIL);
+        itemsMap.put(HumanArmorShape.CHEST, Items.ARMOR_CHESTPLATE_CHAINMAIL);
+        itemsMap.put(HumanArmorShape.HEAD, Items.ARMOR_CHESTPLATE_CHAINMAIL);
+        itemsMapMap.put(ArmorMaterial.CHAINMAIL, itemsMap);
+
+        itemsMap = new HashMap<>();
+        itemsMap.put(HumanArmorShape.BOOTS, Items.ARMOR_BOOTS_DIAMOND);
+        itemsMap.put(HumanArmorShape.LEGS, Items.ARMOR_LEGGINGS_DIAMOND);
+        itemsMap.put(HumanArmorShape.CHEST, Items.ARMOR_CHESTPLATE_DIAMOND);
+        itemsMap.put(HumanArmorShape.HEAD, Items.ARMOR_CHESTPLATE_DIAMOND);
+        itemsMapMap.put(ArmorMaterial.DIAMOND, itemsMap);
+
+        itemsMap = new HashMap<>();
+        itemsMap.put(HumanArmorShape.BOOTS, Items.ARMOR_BOOTS_STEEL);
+        itemsMap.put(HumanArmorShape.LEGS, Items.ARMOR_LEGGINGS_STEEL);
+        itemsMap.put(HumanArmorShape.CHEST, Items.ARMOR_CHESTPLATE_STEEL);
+        itemsMap.put(HumanArmorShape.HEAD, Items.ARMOR_CHESTPLATE_STEEL);
+        itemsMapMap.put(ArmorMaterial.STEEL, itemsMap);
+
+        Random rand = new Random();
+        for (HumanArmorShape shape : HumanArmorShape.values()) {
+            ArmorMaterial mat = ArmorMaterial.getArmorMaterials().get(rand.nextInt(ArmorMaterial.getArmorMaterials().size()));
+            setItemInArmorSlot(shape, new ItemStack(itemsMapMap.get(mat).get(shape)));
+        }
     }
 
     @Override
@@ -55,4 +115,16 @@ public class MobZombie extends MobMonster
         return "mob.zombiedeath";
     }
 
+    @Override
+    public int getNumArmorSlots() {
+        return HumanArmorShape.values().length;
+    }
+
+    @Override
+    public @Nullable HumanArmorShape getArmorSlotByIndex(final int index) {
+        if (index < 0 || index >= HumanArmorShape.values().length) {
+            return null;
+        }
+        return HumanArmorShape.values()[index];
+    }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/entity/monster/MobZombieArmored.java b/game/core/src/main/java/net/minecraft/core/entity/monster/MobZombieArmored.java
index 124ae1261..035051a6f 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/monster/MobZombieArmored.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/monster/MobZombieArmored.java
@@ -55,6 +55,7 @@ public class MobZombieArmored extends MobZombie
 
     @Override
     public void defineSynchedData(){
+        super.defineSynchedData();
         entityData.define(DATA_BREAK_POINTS, 0, Integer.class);
         entityData.define(DATA_BREAK_ORDER, (short) 0, Short.class);
         entityData.define(DATA_GENERIC_FLAGS, (byte) 0, Byte.class);
diff --git a/game/core/src/main/java/net/minecraft/core/entity/player/Player.java b/game/core/src/main/java/net/minecraft/core/entity/player/Player.java
index 52be746e3..920ca6120 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/player/Player.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/player/Player.java
@@ -5,7 +5,7 @@ import com.mojang.nbt.tags.ListTag;
 import net.minecraft.core.block.BlockLogicBed;
 import net.minecraft.core.block.entity.TileEntityActivator;
 import net.minecraft.core.block.entity.TileEntitySign;
-import net.minecraft.core.entity.EntityItem;
+import net.minecraft.core.entity.*;
 import net.minecraft.core.entity.animal.MobPig;
 import net.minecraft.core.entity.animal.MobWolf;
 import net.minecraft.core.entity.monster.MobCreeper;
@@ -13,7 +13,7 @@ import net.minecraft.core.entity.monster.MobSlime;
 import net.minecraft.core.entity.monster.MobSpider;
 import net.minecraft.core.entity.monster.MobZombiePig;
 import net.minecraft.core.entity.projectile.ProjectileArrow;
-import net.minecraft.core.enums.Difficulty;
+import net.minecraft.core.enums.*;
 import net.minecraft.core.achievement.Achievements;
 import net.minecraft.core.achievement.stat.Stat;
 import net.minecraft.core.achievement.stat.StatList;
@@ -26,12 +26,6 @@ import net.minecraft.core.block.entity.TileEntityFurnace;
 import net.minecraft.core.block.entity.TileEntityTrommel;
 import net.minecraft.core.block.material.Material;
 import net.minecraft.core.data.gamerule.GameRules;
-import net.minecraft.core.entity.Entity;
-import net.minecraft.core.entity.EntityDispatcher;
-import net.minecraft.core.entity.EntityFishingBobber;
-import net.minecraft.core.entity.EntityLightning;
-import net.minecraft.core.entity.Mob;
-import net.minecraft.core.entity.EntityPrimedTNT;
 import net.minecraft.core.entity.monster.MobGhast;
 import net.minecraft.core.entity.monster.MobGiant;
 import net.minecraft.core.entity.monster.MobMonster;
@@ -43,10 +37,6 @@ import net.minecraft.core.entity.projectile.ProjectileFireball;
 import net.minecraft.core.entity.projectile.ProjectileArrowGolden;
 import net.minecraft.core.entity.vehicle.EntityBoat;
 import net.minecraft.core.entity.vehicle.MinecartEntity;
-import net.minecraft.core.enums.ArtType;
-import net.minecraft.core.enums.EnumDropCause;
-import net.minecraft.core.enums.EnumSleepStatus;
-import net.minecraft.core.item.IArmorItem;
 import net.minecraft.core.item.Item;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.item.Items;
@@ -77,7 +67,7 @@ import java.util.Comparator;
 import java.util.List;
 import java.util.UUID;
 
-public abstract class Player extends Mob
+public abstract class Player extends Mob implements IArmorWearing<HumanArmorShape>
 {
     public static final int DATA_UNKNOWN = 16; // I have no idea wtf this id is for
     public static final int DATA_SELECTED_ART = 17;
@@ -196,7 +186,12 @@ public abstract class Player extends Mob
     @Override
     public boolean canSkate() 
     {
-        return inventory.armorInventory[IArmorItem.PIECE_BOOTS] != null && inventory.armorInventory[IArmorItem.PIECE_BOOTS].itemID == Items.ARMOR_BOOTS_ICESKATES.id;
+        final @Nullable ItemStack armor = getItemInArmorSlot(HumanArmorShape.BOOTS);
+        if (armor == null) {
+            return false;
+        } else {
+            return armor.itemID == Items.ARMOR_BOOTS_ICESKATES.id;
+        }
     }
 
     @Override
@@ -946,7 +941,10 @@ public abstract class Player extends Mob
 
     @Override
 	protected void damageEntity(int damage, DamageType damageType) {
-    	float protection = 1.0f - inventory.getTotalProtectionAmount(damageType);
+    	float protection = 1.0f;
+        if (damageType != null) {
+            protection -= getTotalProtectionAmount(damageType);
+        }
     	protection = Math.max(protection, 0.01f);
     	
     	//If you take little damage and have lots of protection against that damage
@@ -958,7 +956,7 @@ public abstract class Player extends Mob
     	if(damageType != null && damageType.shouldDamageArmor()){
         	//Reduce armor for big damage amounts, for example explosions
         	int armorDamage = (int) Math.ceil(preventedDamage / 4.0);
-        	inventory.damageArmor(armorDamage);
+        	damageArmor(armorDamage);
     	}
     	
         super.damageEntity(newDamage, damageType);
@@ -968,7 +966,7 @@ public abstract class Player extends Mob
     {
         if(!fireImmune)
         {
-            float protection = 1.0f - inventory.getTotalProtectionAmount(DamageType.FIRE);
+            float protection = 1.0f - getTotalProtectionAmount(DamageType.FIRE);
             protection = Math.max(protection, 0.01f);
             hurt(null, 4, DamageType.FIRE);
             remainingFireTicks = (int) (80 + 520 * protection);
@@ -980,7 +978,7 @@ public abstract class Player extends Mob
     {
         if(!fireImmune)
         {
-            float protection = 1.0f - inventory.getTotalProtectionAmount(DamageType.FIRE);
+            float protection = 1.0f - getTotalProtectionAmount(DamageType.FIRE);
             protection = Math.max(protection, 0.01f);
             hurt(null, 1, DamageType.FIRE);
             remainingFireTicks = (int) (40 + 260 * protection);
@@ -1635,7 +1633,7 @@ public abstract class Player extends Mob
     }
     public Item getNextArrow(){
         Item nextArrow = null;
-        ItemStack quiverSlot = inventory.armorItemInSlot(IArmorItem.PIECE_CHEST);
+        ItemStack quiverSlot = getItemInArmorSlot(HumanArmorShape.CHEST);
         if(quiverSlot != null && quiverSlot.itemID == Items.ARMOR_QUIVER.id && quiverSlot.getMetadata() < quiverSlot.getMaxDamage()){
             nextArrow = Items.AMMO_ARROW;
         }
@@ -1661,6 +1659,33 @@ public abstract class Player extends Mob
         }
         return false;
     }
+
+    @Override
+    public @Nullable ItemStack getItemInArmorSlot(final @NotNull HumanArmorShape slot) {
+        return this.inventory.armorInventory[slot.getSlotIndex()];
+    }
+
+    @Override
+    public void setItemInArmorSlot(final @NotNull HumanArmorShape slot, final @Nullable ItemStack item) {
+        if (slot == HumanArmorShape.HEAD || canItemGoInArmorSlot(slot, item)) {
+            this.inventory.armorInventory[slot.getSlotIndex()] = item;
+        }
+    }
+
+    @Override
+    public int getNumArmorSlots() {
+        return HumanArmorShape.values().length;
+    }
+
+    @Override
+    public @Nullable HumanArmorShape getArmorSlotByIndex(int index) {
+        if (index < 0 || index >= HumanArmorShape.values().length) {
+            return null;
+        }
+
+        return HumanArmorShape.values()[index];
+    }
+
     public static class SortByUsername implements Comparator<Player> {
 
         @Override
diff --git a/game/core/src/main/java/net/minecraft/core/entity/vehicle/EntityBoat.java b/game/core/src/main/java/net/minecraft/core/entity/vehicle/EntityBoat.java
index 28511eb1b..14eac3f09 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/vehicle/EntityBoat.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/vehicle/EntityBoat.java
@@ -444,6 +444,11 @@ public class EntityBoat extends Entity
         }
     }
 
+    @Override
+    public void waterHurt() {
+        return;
+    }
+
     @Override
     public void addAdditionalSaveData(@NotNull CompoundTag tag)
     {
diff --git a/game/core/src/main/java/net/minecraft/core/entity/vehicle/MobGhastVehicle.java b/game/core/src/main/java/net/minecraft/core/entity/vehicle/MobGhastVehicle.java
new file mode 100644
index 000000000..8f88ac0fe
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/entity/vehicle/MobGhastVehicle.java
@@ -0,0 +1,444 @@
+package net.minecraft.core.entity.vehicle;
+
+import net.minecraft.core.block.Block;
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.block.entity.TileEntityFlag;
+import net.minecraft.core.block.material.Material;
+import net.minecraft.core.block.tag.BlockTags;
+import net.minecraft.core.entity.Entity;
+import net.minecraft.core.entity.monster.MobGhast;
+import net.minecraft.core.entity.player.Player;
+import net.minecraft.core.item.ItemStack;
+import net.minecraft.core.item.Items;
+import net.minecraft.core.player.gamemode.Gamemode;
+import net.minecraft.core.util.collection.NamespaceID;
+import net.minecraft.core.util.helper.BlockParticleHelper;
+import net.minecraft.core.util.helper.DamageType;
+import net.minecraft.core.util.helper.MathHelper;
+import net.minecraft.core.util.helper.Side;
+import net.minecraft.core.util.phys.AABB;
+import net.minecraft.core.world.World;
+
+import java.util.List;
+
+public class MobGhastVehicle extends MobGhast
+{
+    private static final double MAX_BOAT_SPEED = 1.2D;
+    private static final double ACCELERATION = 0.03D;
+    private static final float MAX_ROTATION_SPEED = 5f;
+    private static final float MIN_ROTATION_SPEED = 3f;
+    private static final double MAX_PACKET_SPEED_CHANGE = ACCELERATION * 8;
+    private static final double MAX_PACKET_ROTATION_CHANGE = MAX_ROTATION_SPEED * 4;
+    public int boatCurrentDamage;
+    public int boatTimeSinceHit;
+    public int boatRockDirection;
+    private int lerpTicks;
+    private double lerpX;
+    private double lerpY;
+    private double lerpZ;
+    private float lerpYRot;
+    private float boatPitch;
+    private double velocityX;
+    private double velocityY;
+    private double velocityZ;
+    private float pendingYRot;
+    private double pendingXDChange;
+    private double pendingYDChange;
+    private double pendingZDChange;
+
+
+    public MobGhastVehicle(World world)
+    {
+        super(world);
+        boatCurrentDamage = 0;
+        boatTimeSinceHit = 0;
+        boatRockDirection = 1;
+        blocksBuilding = true;
+        sendAdditionalData = true;
+        textureIdentifier = new NamespaceID("minecraft", "ghast_vehicle");
+    }
+
+    @Override
+    public void lookAt(Entity entity, float yRot, float xRot) {
+        return;
+    }
+
+    @Override
+    protected void updateAI() {
+        return;
+    }
+
+    @Override
+    public void onLivingUpdate() {
+        return;
+    }
+
+    @Override
+	protected boolean makeStepSound()
+    {
+        return false;
+    }
+
+    @Override
+	public AABB getBb()
+    {
+        return bb;
+    }
+
+    @Override
+	public boolean isPushable()
+    {
+        return true;
+    }
+
+    public MobGhastVehicle(World world, double d, double d1, double d2)
+    {
+        this(world);
+        setPos(d, d1 + (double) heightOffset, d2);
+        xd = 0.0D;
+        yd = 0.0D;
+        zd = 0.0D;
+        xo = d;
+        yo = d1;
+        zo = d2;
+    }
+
+    @Override
+	public double getRideHeight()
+    {
+        return (double) bbHeight - 0.3D;
+    }
+    @Override
+    public boolean hurt(Entity entity, int i, DamageType type)
+    {
+        int numParticles = random.nextInt(8) + 1;
+        for (int q = 0; q < numParticles; q++)
+        {
+            world.spawnParticle("block", x, y, z, xd, yd, zd, BlockParticleHelper.encodeBlockData(Blocks.PLANKS_OAK.id(), 0, Side.BOTTOM));
+        }
+        if(world.isClientSide || removed)
+        {
+            return true;
+        }
+        boatRockDirection = -boatRockDirection;
+        boatTimeSinceHit = 10;
+        boatCurrentDamage += i * 10;
+        markHurt();
+        if(boatCurrentDamage > 40)
+        {
+            if(passenger != null)
+            {
+                passenger.startRiding(this);
+            }
+            if(entity instanceof Player){
+                dropItem(Items.BOAT.id, 1, 0.0F);
+            }else {
+                for (int j = 0; j < 3; j++) {
+                    dropItem(Blocks.PLANKS_OAK.id(), 1, 0.0F);
+                }
+
+                for (int k = 0; k < 2; k++) {
+                    dropItem(Items.STICK.id, 1, 0.0F);
+                }
+            }
+
+            remove();
+        }
+        return true;
+    }
+
+    @Override
+    public void remove(){
+        Entity entity = passenger;
+
+        if (entity != null){
+            double bbCenterX = (bb.minX + bb.maxX)/2;
+            double bbCenterZ = (bb.minZ + bb.maxZ)/2;
+
+            this.passenger = null;
+            entity.vehicle = null;
+            entity.moveTo(bbCenterX, this.bb.maxY + 0.1, bbCenterZ, entity.yRot, entity.xRot);
+        }
+
+        super.remove();
+    }
+
+    @Override
+	public void animateHurt()
+    {
+        boatRockDirection = -boatRockDirection;
+        boatTimeSinceHit = 10;
+        boatCurrentDamage += boatCurrentDamage * 10;
+    }
+
+    @Override
+	public boolean isPickable()
+    {
+        return !removed;
+    }
+
+    @Override
+	public void lerpTo(double x, double y, double z, float yRot,
+                       float xRot, int i)
+    {
+        lerpX = x;
+        lerpY = y;
+        lerpZ = z;
+        boatPitch = xRot;
+        lerpTicks = i;
+        // Don't interpolate velocity from server when controlling the boat
+        if (passenger != null && !passenger.lerpVehicleMotion()) return;
+        lerpYRot = yRot;
+        xd = velocityX;
+        yd = velocityY;
+        zd = velocityZ;
+    }
+
+    @Override
+	public void lerpMotion(double xd, double yd, double zd)
+    {
+        // Don't interpolate velocity from server when controlling the boat
+        if (passenger != null && !passenger.lerpVehicleMotion()) return;
+        velocityX = this.xd = xd;
+        velocityY = this.yd = yd;
+        velocityZ = this.zd = zd;
+    }
+    @Override
+	public void tick()
+    {
+        setRot(yRot, xRot);
+        super.tick();
+        if(boatTimeSinceHit > 0)
+        {
+            boatTimeSinceHit--;
+        }
+        if(boatCurrentDamage > 0)
+        {
+            boatCurrentDamage--;
+        }
+        xo = x;
+        yo = y;
+        zo = z;
+        if(world.isClientSide)
+        {
+            if(lerpTicks > 0)
+            {
+                double lerpX = x + (this.lerpX - x) / (double) lerpTicks;
+                double lerpY = y + (this.lerpY - y) / (double) lerpTicks;
+                double lerpZ = z + (this.lerpZ - z) / (double) lerpTicks;
+                xRot += (boatPitch - xRot) / lerpTicks;
+                if (passenger == null || passenger.lerpVehicleMotion()){
+                    float lerpYRot;
+                    lerpYRot = MathHelper.normalizeRotation(this.lerpYRot - yRot);
+                    yRot += lerpYRot / lerpTicks;
+                }
+                lerpTicks--;
+                setPos(lerpX, lerpY, lerpZ);
+            }
+            boatMovement();
+            move(xd , yd, zd);
+            setRot(yRot, xRot);
+            xd *= 0.99D;
+            yd *= 0.99D;
+            zd *= 0.99D;
+            return;
+        }
+
+        boatMovement();
+
+        if (passenger == null){
+            float nextYRot = yRot;
+            if(Math.hypot(xd, zd) > 0.001D)
+            {
+                nextYRot = (float)Math.toDegrees(Math.atan2(zd, xd)) + 180;
+            }
+            float changeInYRot;
+            changeInYRot = MathHelper.normalizeRotation(nextYRot - yRot);
+            changeInYRot = MathHelper.clamp(changeInYRot, -20F, 20F);
+            yRot = MathHelper.normalizeRotation(yRot + changeInYRot);
+            setRot(yRot, xRot);
+        }
+
+        move(xd, yd, zd);
+
+        if (passenger == null || !passenger.deferVehicleBehavior()){
+            xd *= 0.99D;
+            yd *= 0.99D;
+            zd *= 0.99D;
+        }
+
+        xRot = 0.0F;
+
+        // Push entities in bounding box
+        List<Entity> list = world.getEntitiesWithinAABBExcludingEntity(this, bb.grow(0.2D, 0.0D, 0.2D));
+        if(list != null && !list.isEmpty())
+        {
+            for (Entity entity : list) {
+                if (entity != passenger && entity.isPushable() && (entity instanceof MobGhastVehicle)) {
+                    entity.push(this);
+                }
+            }
+        }
+
+        // Remove passenger is passenger removal is queued
+        if(passenger != null && passenger.removed)
+        {
+            passenger = null;
+        }
+
+        yBodyRot = yRot + 90;
+    }
+    public void boatMovement(){
+        double boatRad = Math.toRadians(yRot + 90);
+        double vectorRad = Math.atan2(xd, zd);
+        double currentBoatSpeed;
+        if (passenger != null){
+            boolean isBackwards = Math.cos(-boatRad - vectorRad) < 0;
+            double ang = vectorRad + boatRad + (isBackwards ? Math.PI : 0);
+
+            double _xd = xd;
+            xd = _xd * Math.cos(ang) - zd * Math.sin(ang);
+            zd = _xd * Math.sin(ang) + zd * Math.cos(ang);
+        }
+
+        double xdOff;
+        double ydOff;
+        double zdOff;
+        currentBoatSpeed = Math.hypot(xd, zd);
+        if(passenger != null) {
+            passenger.handleSpecialVehicleControl();
+        }
+        yRot += pendingYRot;
+        pendingYRot = 0;
+        xdOff = pendingXDChange;
+        ydOff = pendingYDChange;
+        zdOff = pendingZDChange;
+        pendingXDChange = 0;
+        pendingYDChange = 0;
+        pendingZDChange = 0;
+
+        double offsetMagnitude = Math.hypot(xdOff, zdOff);
+        double vecAngle = Math.acos((xd * xdOff + zd * zdOff)/ ((currentBoatSpeed + 0.00000000001D) * (offsetMagnitude + 0.00000000001D)));
+        double multiplier = (1 + (vecAngle/Math.PI)); // Increase acceleration if it runs opposite to the current motion vector
+        xdOff *= multiplier;
+        ydOff *= multiplier;
+        zdOff *= multiplier;
+
+//        System.out.printf("%f %f\n", xdOff, zdOff);
+
+        xd += xdOff;
+        yd += ydOff;
+        zd += zdOff;
+
+        if(passenger != null) {
+            passenger.sendSpecialVehiclePacket();
+        }
+
+        // Cap max speed properly
+        currentBoatSpeed = Math.hypot(xd, zd);
+        if (currentBoatSpeed > MAX_BOAT_SPEED){
+            xd = xd/currentBoatSpeed * MAX_BOAT_SPEED;
+            yd = yd/currentBoatSpeed * MAX_BOAT_SPEED;
+            zd = zd/currentBoatSpeed * MAX_BOAT_SPEED;
+        }
+
+        if(onGround)
+        {
+            xd *= 0.5D;
+            yd *= 0.5D;
+            zd *= 0.5D;
+        }
+    }
+
+    public void controlVehicle(float forward, float strafe, boolean jump, boolean sneak){
+        double currentBoatSpeed = Math.hypot(xd, zd);
+        pendingYRot = (float) (strafe * (MIN_ROTATION_SPEED + (MAX_ROTATION_SPEED - MIN_ROTATION_SPEED) * Math.max(((MAX_BOAT_SPEED - (currentBoatSpeed * 1.5))/ MAX_BOAT_SPEED), 0))); // I love interpolation
+        float velOff = (float) (forward * ACCELERATION);
+        pendingXDChange = Math.cos(Math.toRadians(yRot)) * velOff;
+        pendingZDChange = Math.sin(Math.toRadians(yRot)) * velOff;
+
+        if (jump && !sneak) {
+            pendingYDChange = ACCELERATION;
+        }
+        if (sneak && !jump) {
+            pendingYDChange = -ACCELERATION;
+        }
+    }
+
+    public void handleControlDirect(float xd, float zd, float yRot){
+        this.yRot = (float) MathHelper.clamp(yRot, this.yRot - MAX_PACKET_ROTATION_CHANGE, this.yRot + MAX_PACKET_ROTATION_CHANGE);
+        this.xd = MathHelper.clamp(xd, this.xd - MAX_PACKET_SPEED_CHANGE, this.xd + MAX_PACKET_SPEED_CHANGE);
+        this.zd = MathHelper.clamp(zd, this.zd - MAX_PACKET_SPEED_CHANGE, this.zd + MAX_PACKET_SPEED_CHANGE);
+    }
+
+    @Override
+	public void positionRider()
+    {
+        if (passenger != null) {
+            double d = Math.cos(((double) yRot * Math.PI) / 180D) * 0.4D;
+            double d1 = Math.sin(((double) yRot * Math.PI) / 180D) * 0.4D;
+            passenger.setPos(x + d, y + getRideHeight() + passenger.getRidingHeight(), z + d1);
+        }
+    }
+
+    @Override
+    public void waterHurt() {
+        return;
+    }
+
+    @Override
+	public float getShadowHeightOffs()
+    {
+        return 0.0F;
+    }
+
+    @Override
+	public boolean interact(Player entityplayer)
+    {
+        if(passenger != null && (passenger instanceof Player) && passenger != entityplayer)
+        {
+            return true;
+        }
+        if (passenger == entityplayer) {
+            ejectRider();
+            return true;
+        }
+        if(!world.isClientSide)
+        {
+            entityplayer.startRiding(this);
+        }
+        return true;
+    }
+    @Override
+    public Entity ejectRider() {
+        Entity entity = passenger;
+        if(entity == null) return null;
+
+        this.passenger = null;
+        entity.vehicle = null;
+
+        double bbCenterX = (bb.minX + bb.maxX)/2;
+        double bbCenterY = (bb.minY + bb.maxY)/2;
+        double bbCenterZ = (bb.minZ + bb.maxZ)/2;
+
+        boolean flag = false;
+        for (int i = 0; i < 4; i++) {
+            final double length = 1.75D;
+            double angle = Math.toRadians(yRot - 90) + (Math.PI/2) * i;
+            double checkX = bbCenterX + Math.cos(angle) * length;
+            double checkZ = bbCenterZ + Math.sin(angle) * length;
+            entity.moveTo(checkX, this.bb.maxY + 0.1, checkZ, entity.yRot, entity.xRot);
+            if (/*world.checkIfAABBIsClear(entity.bb) && */world.getCubes(entity, entity.bb).isEmpty()){
+                flag = true;
+                break;
+            }
+        }
+        if (!flag){
+            entity.moveTo(bbCenterX, this.bb.maxY + 0.1, bbCenterZ, entity.yRot, entity.xRot);
+        }
+        return entity;
+    }
+
+    @Override
+    protected void causeFallDamage(float distance){}
+}
diff --git a/game/core/src/main/java/net/minecraft/core/enums/HumanArmorShape.java b/game/core/src/main/java/net/minecraft/core/enums/HumanArmorShape.java
new file mode 100644
index 000000000..85653210e
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/enums/HumanArmorShape.java
@@ -0,0 +1,31 @@
+package net.minecraft.core.enums;
+
+public enum HumanArmorShape implements IArmorShape {
+    BOOTS(0.94f, 3),
+    LEGS(0.97f, 6),
+    CHEST(1.0f, 8),
+    HEAD(0.91f, 3);
+
+    private final float durabilityModifier;
+    private final int protectionValue;
+
+    HumanArmorShape(final float durabilityModifier, final int protectionValue) {
+        this.durabilityModifier = durabilityModifier;
+        this.protectionValue = protectionValue;
+    }
+
+    @Override
+    public int getSlotIndex() {
+        return this.ordinal();
+    }
+
+    @Override
+    public float getDurabilityModifier() {
+        return this.durabilityModifier;
+    }
+
+    @Override
+    public int getProtectionValue() {
+        return this.protectionValue;
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/enums/IArmorShape.java b/game/core/src/main/java/net/minecraft/core/enums/IArmorShape.java
new file mode 100644
index 000000000..656d6c0a8
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/enums/IArmorShape.java
@@ -0,0 +1,9 @@
+package net.minecraft.core.enums;
+
+public interface IArmorShape {
+    int getSlotIndex();
+
+    float getDurabilityModifier();
+
+    int getProtectionValue();
+}
diff --git a/game/core/src/main/java/net/minecraft/core/enums/WolfArmorShape.java b/game/core/src/main/java/net/minecraft/core/enums/WolfArmorShape.java
new file mode 100644
index 000000000..b0ad85d44
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/enums/WolfArmorShape.java
@@ -0,0 +1,20 @@
+package net.minecraft.core.enums;
+
+public enum WolfArmorShape implements IArmorShape {
+    BODY;
+
+    @Override
+    public int getSlotIndex() {
+        return 0;
+    }
+
+    @Override
+    public float getDurabilityModifier() {
+        return 1.0f;
+    }
+
+    @Override
+    public int getProtectionValue() {
+        return 8;
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/item/IArmorItem.java b/game/core/src/main/java/net/minecraft/core/item/IArmorItem.java
index f5e5cf4fd..c7f8a0148 100644
--- a/game/core/src/main/java/net/minecraft/core/item/IArmorItem.java
+++ b/game/core/src/main/java/net/minecraft/core/item/IArmorItem.java
@@ -1,26 +1,22 @@
 package net.minecraft.core.item;
 
+import net.minecraft.core.enums.IArmorShape;
 import net.minecraft.core.item.material.ArmorMaterial;
+import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-public interface IArmorItem {
-    int PIECE_HEAD = 3;
-    int PIECE_CHEST = 2;
-    int PIECE_LEGS = 1;
-    int PIECE_BOOTS = 0;
-    int[] ARMOR_PIECE_PROTECTION_VALUES = new int[] {3, 6, 8, 3};
+public interface IArmorItem<T extends IArmorShape> extends IItemConvertible {
+    @NotNull Item asItem();
+
+    @Nullable ArmorMaterial getArmorMaterial();
+
+    @NotNull T getArmorShape();
 
-    float[] ARMOR_PIECE_DURABILITY_MODIFIERS = new float[] {0.94f, 0.97f, 1.0f, 0.91f};
-    Item asItem(); // Ensure this has the same signature as Item's Item.asItem
-    @Nullable
-    ArmorMaterial getArmorMaterial();
-    int getArmorPiece();
     default int armorPieceProtection() {
-        return ARMOR_PIECE_PROTECTION_VALUES[getArmorPiece()];
+        return getArmorShape().getProtectionValue();
     }
 
     default float getArmorPieceProtectionPercentage() {
         return armorPieceProtection() / 20.0f;
     }
-
 }
diff --git a/game/core/src/main/java/net/minecraft/core/item/Item.java b/game/core/src/main/java/net/minecraft/core/item/Item.java
index 4c8041178..94f88a14f 100644
--- a/game/core/src/main/java/net/minecraft/core/item/Item.java
+++ b/game/core/src/main/java/net/minecraft/core/item/Item.java
@@ -331,7 +331,7 @@ public class Item implements ITaggable<Item>, IItemConvertible
     }
 
     @Override
-    public Item asItem() {
+    public @NotNull Item asItem() {
         return this;
     }
 
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemArmor.java b/game/core/src/main/java/net/minecraft/core/item/ItemArmor.java
index d7305ec5a..fca353a28 100644
--- a/game/core/src/main/java/net/minecraft/core/item/ItemArmor.java
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemArmor.java
@@ -1,27 +1,32 @@
 package net.minecraft.core.item;
 
+import net.minecraft.core.enums.IArmorShape;
 import net.minecraft.core.item.material.ArmorMaterial;
+import org.jetbrains.annotations.NotNull;
 
-public class ItemArmor extends Item implements IArmorItem {
+public class ItemArmor<T extends IArmorShape>
+	extends Item
+	implements IArmorItem<T> {
 	private final ArmorMaterial material;
-	private final int armorPiece;
-	public ItemArmor(String name, String namespaceId, int id, ArmorMaterial material, int armorPiece) {
+	private final T armorShape;
+
+	public ItemArmor(final @NotNull String name, final @NotNull String namespaceId, final int id, ArmorMaterial material, final @NotNull T armorShape) {
 		super(name, namespaceId, id);
 		this.material = material;
-		this.armorPiece = armorPiece;
-		
-		setMaxDamage((int)(ARMOR_PIECE_DURABILITY_MODIFIERS[armorPiece] * material.durability));
+		this.armorShape = armorShape;
+
+		setMaxDamage((int)(armorShape.getDurabilityModifier() * material.durability));
 		
-		maxStackSize = 1;
+		this.maxStackSize = 1;
 	}
+
 	@Override
 	public ArmorMaterial getArmorMaterial() {
-		return material;
+		return this.material;
 	}
 
 	@Override
-	public int getArmorPiece() {
-		return armorPiece;
+	public @NotNull T getArmorShape() {
+		return this.armorShape;
 	}
-
 }
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemBow.java b/game/core/src/main/java/net/minecraft/core/item/ItemBow.java
index b17f40bf4..753c3c630 100644
--- a/game/core/src/main/java/net/minecraft/core/item/ItemBow.java
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemBow.java
@@ -5,6 +5,7 @@ import net.minecraft.core.entity.projectile.ProjectileArrow;
 import net.minecraft.core.entity.projectile.ProjectileArrowGolden;
 import net.minecraft.core.entity.projectile.ProjectileArrowPurple;
 import net.minecraft.core.entity.player.Player;
+import net.minecraft.core.enums.HumanArmorShape;
 import net.minecraft.core.world.World;
 
 public class ItemBow extends Item
@@ -20,9 +21,9 @@ public class ItemBow extends Item
     @Override
     public ItemStack onUseItem(ItemStack itemstack, World world, Player entityplayer)
     {
-        ItemStack quiverSlot = entityplayer.inventory.armorItemInSlot(IArmorItem.PIECE_CHEST);
+        ItemStack quiverSlot = entityplayer.getItemInArmorSlot(HumanArmorShape.CHEST);
         if(quiverSlot != null && quiverSlot.itemID == Items.ARMOR_QUIVER.id && quiverSlot.getMetadata() < quiverSlot.getMaxDamage()){
-            entityplayer.inventory.armorItemInSlot(IArmorItem.PIECE_CHEST).damageItem(1, entityplayer);
+            quiverSlot.damageItem(1, entityplayer);
             itemstack.damageItem(1, entityplayer);
             world.playSoundAtEntity(entityplayer, entityplayer, "random.bow", 0.3f, 1.0F / (itemRand.nextFloat() * 0.4F + 0.8F));
             if (!world.isClientSide) {
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemBucket.java b/game/core/src/main/java/net/minecraft/core/item/ItemBucket.java
index 7bc7d50d4..2b6e2d6ed 100644
--- a/game/core/src/main/java/net/minecraft/core/item/ItemBucket.java
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemBucket.java
@@ -61,23 +61,16 @@ public class ItemBucket extends Item
 		}
 		
 		if(world.isAirBlock(x, y, z) || !world.getBlockMaterial(x, y, z).isSolid()) {
-			if(world.dimension == Dimension.NETHER && blockToPlace == Blocks.FLUID_WATER_FLOWING) {
-				world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS, z + 0.5, y + 0.5, x + 0.5, "random.fizz", 0.5F, 2.6F + (world.rand.nextFloat() - world.rand.nextFloat()) * 0.8F);
-				for (int l = 0; l < 8; l++) {
-					world.spawnParticle("largesmoke", x + Math.random(), y + Math.random(), z + Math.random(), 0.0D, 0.0D, 0.0D, 0);
-				}
-			} else {
-				if(blockToPlace == Blocks.FLUID_WATER_FLOWING) {
-					world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS, x + 0.5F, y + 0.5F, z + 0.5F, "liquid.splash", 0.5f, 1.0f);
-				}
-				player.swingItem();
-				Block<?> block1 = world.getBlock(x, y, z);
-				if(block1 != null) {
-					block1.dropBlockWithCause(world, EnumDropCause.WORLD, x, y, z, world.getBlockMetadata(x, y, z), null, null);
-				}
-				world.setBlockAndMetadataWithNotify(x, y, z, blockToPlace.id(), 0);
-			}
-			if(player.getGamemode().consumeBlocks()) {
+            if (blockToPlace == Blocks.FLUID_WATER_FLOWING) {
+                world.playSoundEffect(player, SoundCategory.WORLD_SOUNDS, x + 0.5F, y + 0.5F, z + 0.5F, "liquid.splash", 0.5f, 1.0f);
+            }
+            player.swingItem();
+            Block<?> block1 = world.getBlock(x, y, z);
+            if(block1 != null) {
+                block1.dropBlockWithCause(world, EnumDropCause.WORLD, x, y, z, world.getBlockMetadata(x, y, z), null, null);
+            }
+            world.setBlockAndMetadataWithNotify(x, y, z, blockToPlace.id(), 0);
+            if(player.getGamemode().consumeBlocks()) {
 				return new ItemStack(Items.BUCKET);
 			}
 		}
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemIceSkates.java b/game/core/src/main/java/net/minecraft/core/item/ItemIceSkates.java
index 7ceaf568b..7399d2bd2 100644
--- a/game/core/src/main/java/net/minecraft/core/item/ItemIceSkates.java
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemIceSkates.java
@@ -1,9 +1,12 @@
 package net.minecraft.core.item;
 
+import net.minecraft.core.enums.HumanArmorShape;
+import net.minecraft.core.enums.IArmorShape;
 import net.minecraft.core.item.material.ArmorMaterial;
+import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-public class ItemIceSkates extends Item implements IArmorItem{
+public class ItemIceSkates extends Item implements IArmorItem<HumanArmorShape> {
     public ItemIceSkates(String translationKey, String namespaceId, int id) {
         super(translationKey, namespaceId, id);
         setMaxStackSize(1);
@@ -15,7 +18,7 @@ public class ItemIceSkates extends Item implements IArmorItem{
     }
 
     @Override
-    public int getArmorPiece() {
-        return PIECE_BOOTS;
+    public @NotNull HumanArmorShape getArmorShape() {
+        return HumanArmorShape.BOOTS;
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemJar.java b/game/core/src/main/java/net/minecraft/core/item/ItemJar.java
index 34aa4b715..0aaedb7b0 100644
--- a/game/core/src/main/java/net/minecraft/core/item/ItemJar.java
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemJar.java
@@ -2,6 +2,7 @@ package net.minecraft.core.item;
 
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.entity.Entity;
+import net.minecraft.core.entity.animal.MobButterfly;
 import net.minecraft.core.entity.animal.MobFireflyCluster;
 import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.util.helper.Side;
@@ -69,6 +70,17 @@ public class ItemJar extends ItemPlaceable {
                     }
                 }
                 
+                return itemstack;
+            } else if (entity instanceof MobButterfly) {
+                MobButterfly butterfly = (MobButterfly) entity;
+                MobButterfly.ButterflyEntry color = MobButterfly.colorEntries.get(butterfly.getColor());
+
+                boolean didFillJar = fillJar(entityplayer, new ItemStack(color.jarItem.get(), 1));
+
+                if(!world.isClientSide && didFillJar) {
+                    butterfly.remove();
+                }
+
                 return itemstack;
             }
         }
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemQuiver.java b/game/core/src/main/java/net/minecraft/core/item/ItemQuiver.java
index 130250496..9044fab12 100644
--- a/game/core/src/main/java/net/minecraft/core/item/ItemQuiver.java
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemQuiver.java
@@ -1,11 +1,14 @@
 package net.minecraft.core.item;
 
 import net.minecraft.core.entity.player.Player;
+import net.minecraft.core.enums.HumanArmorShape;
+import net.minecraft.core.enums.IArmorShape;
 import net.minecraft.core.item.material.ArmorMaterial;
 import net.minecraft.core.player.inventory.slot.Slot;
+import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-public class ItemQuiver extends Item implements IArmorItem
+public class ItemQuiver extends Item implements IArmorItem<HumanArmorShape>
 {
 
     public ItemQuiver(String name, String namespaceId, int id)
@@ -100,7 +103,7 @@ public class ItemQuiver extends Item implements IArmorItem
 	}
 
 	@Override
-	public int getArmorPiece() {
-		return PIECE_CHEST;
+	public @NotNull HumanArmorShape getArmorShape() {
+		return HumanArmorShape.CHEST;
 	}
 }
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemQuiverEndless.java b/game/core/src/main/java/net/minecraft/core/item/ItemQuiverEndless.java
index 93874ca75..2d066f5cd 100644
--- a/game/core/src/main/java/net/minecraft/core/item/ItemQuiverEndless.java
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemQuiverEndless.java
@@ -1,8 +1,10 @@
 package net.minecraft.core.item;
 
+import net.minecraft.core.enums.HumanArmorShape;
 import net.minecraft.core.item.material.ArmorMaterial;
+import org.jetbrains.annotations.NotNull;
 
-public class ItemQuiverEndless extends Item implements IArmorItem
+public class ItemQuiverEndless extends Item implements IArmorItem<HumanArmorShape>
 {
 
     public ItemQuiverEndless(String name, String namespaceId, int id)
@@ -17,7 +19,8 @@ public class ItemQuiverEndless extends Item implements IArmorItem
     }
 
     @Override
-    public int getArmorPiece() {
-        return PIECE_CHEST;
+    public @NotNull HumanArmorShape getArmorShape() {
+        return HumanArmorShape.CHEST;
     }
+
 }
diff --git a/game/core/src/main/java/net/minecraft/core/item/ItemStatue.java b/game/core/src/main/java/net/minecraft/core/item/ItemStatue.java
new file mode 100644
index 000000000..67d5b3213
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/item/ItemStatue.java
@@ -0,0 +1,71 @@
+package net.minecraft.core.item;
+
+import net.minecraft.core.block.Block;
+import net.minecraft.core.block.entity.TileEntityActivator;
+import net.minecraft.core.entity.player.Player;
+import net.minecraft.core.enums.EnumBlockSoundEffectType;
+import net.minecraft.core.util.helper.Direction;
+import net.minecraft.core.util.helper.MathHelper;
+import net.minecraft.core.util.helper.Side;
+import net.minecraft.core.world.World;
+
+import java.util.Random;
+
+public class ItemStatue extends Item {
+	protected final Block<?> statueBlockBottom;
+	protected final Block<?> statueBlockTop;
+
+	public ItemStatue(String name, String namespaceId, int id, Block<?> statueBlockBottom, Block<?> statueBlockTop) {
+		super(name, namespaceId, id);
+		maxStackSize = 64;
+        this.statueBlockBottom = statueBlockBottom;
+        this.statueBlockTop = statueBlockTop;
+    }
+
+	@Override
+	public boolean onUseItemOnBlock(ItemStack itemstack, Player player, World world, int blockX, int blockY, int blockZ, Side side, double xPlaced, double yPlaced) {
+		if(!world.canPlaceInsideBlock(blockX, blockY, blockZ)) {
+			blockX += side.getOffsetX();
+			blockY += side.getOffsetY();
+			blockZ += side.getOffsetZ();
+		}
+		if(!statueBlockBottom.canPlaceBlockAt(world, blockX, blockY, blockZ)) {
+			return false;
+		}
+
+		world.noNeighborUpdate = true;
+		world.setBlockAndMetadataWithNotify(blockX, blockY, blockZ, statueBlockBottom.id(), MathHelper.floor((double)(((player.yRot + 180F) * 16F) / 360F) + 0.5D) & 0xf);
+		world.setBlockAndMetadataWithNotify(blockX, blockY + 1, blockZ, statueBlockTop.id(), MathHelper.floor((double)(((player.yRot + 180F) * 16F) / 360F) + 0.5D) & 0xf);
+		world.noNeighborUpdate = false;
+		world.notifyBlocksOfNeighborChange(blockX, blockY, blockZ, statueBlockBottom.id());
+		world.notifyBlocksOfNeighborChange(blockX, blockY + 1, blockZ, statueBlockTop.id());
+		world.playBlockSoundEffect(player, blockX + 0.5F, blockY + 0.5F, blockZ + 0.5F, statueBlockBottom, EnumBlockSoundEffectType.PLACE);
+		statueBlockBottom.onBlockPlacedByMob(world, blockX, blockY, blockZ, side, player, xPlaced, yPlaced);
+		statueBlockTop.onBlockPlacedByMob(world, blockX, blockY + 1, blockZ, side, player, xPlaced, yPlaced);
+		itemstack.consumeItem(player);
+		return true;
+	}
+
+	@Override
+	public void onUseByActivator(ItemStack itemStack, TileEntityActivator activatorBlock, World world, Random random, int blockX, int blockY, int blockZ, double offX, double offY, double offZ, Direction direction) {
+		if(!world.canPlaceInsideBlock(blockX, blockY, blockZ)) {
+			blockX += direction.getOffsetX();
+			blockY += direction.getOffsetY();
+			blockZ += direction.getOffsetZ();
+		}
+		if(!statueBlockBottom.canPlaceBlockAt(world, blockX, blockY, blockZ)) {
+			return;
+		}
+
+		world.noNeighborUpdate = true;
+		world.setBlockWithNotify(blockX, blockY, blockZ, statueBlockBottom.id());
+		world.setBlockWithNotify(blockX, blockY + 1, blockZ, statueBlockTop.id());
+		world.noNeighborUpdate = false;
+		world.notifyBlocksOfNeighborChange(blockX, blockY, blockZ, statueBlockBottom.id());
+		world.notifyBlocksOfNeighborChange(blockX, blockY + 1, blockZ, statueBlockTop.id());
+		world.playBlockSoundEffect(null, blockX + 0.5F, blockY + 0.5F, blockZ + 0.5F, statueBlockBottom, EnumBlockSoundEffectType.PLACE);
+		statueBlockBottom.onBlockPlacedOnSide(world, blockX, blockY, blockZ, direction.getSide(), 0.5, 0.5);
+		statueBlockTop.onBlockPlacedOnSide(world, blockX, blockY + 1, blockZ, direction.getSide(), 0.5, 0.5);
+		itemStack.consumeItem(null);
+	}
+}
diff --git a/game/core/src/main/java/net/minecraft/core/item/Items.java b/game/core/src/main/java/net/minecraft/core/item/Items.java
index 9de4c78d8..4160f000e 100644
--- a/game/core/src/main/java/net/minecraft/core/item/Items.java
+++ b/game/core/src/main/java/net/minecraft/core/item/Items.java
@@ -4,6 +4,7 @@ import net.minecraft.core.achievement.stat.Stat;
 import net.minecraft.core.achievement.stat.StatList;
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.entity.vehicle.MinecartEntity;
+import net.minecraft.core.enums.HumanArmorShape;
 import net.minecraft.core.item.material.ArmorMaterial;
 import net.minecraft.core.item.material.ToolMaterial;
 import net.minecraft.core.item.tag.ItemTags;
@@ -189,7 +190,12 @@ public final class Items {
     public static Item WAND_MONSTER_SPAWNER;
     public static Item DOOR_STEEL;
     public static Item PAINTBRUSH;
-
+    public static Item JAR_BUTTERFLY_BLUE;
+    public static Item JAR_BUTTERFLY_ORANGE;
+    public static Item JAR_BUTTERFLY_PINK;
+    public static Item JAR_BUTTERFLY_SILVER;
+    public static Item STATUE_STONE;
+    
     private static boolean hasInit = false;
     public static void init(){
         if (hasInit) return;
@@ -268,30 +274,30 @@ public final class Items {
         WHEAT = (new Item("wheat", "minecraft:item/wheat", 16424)).withTags(ItemTags.COWS_FAVOURITE_ITEM);
         FOOD_BREAD = (new ItemFood("food.bread", "minecraft:item/food_bread", 16425, 5, 12, false, 8));
 
-        ARMOR_HELMET_LEATHER = (new ItemArmor("armor.helmet.leather", "minecraft:item/armor_helmet_leather", 16426, ArmorMaterial.LEATHER, IArmorItem.PIECE_HEAD));
-        ARMOR_CHESTPLATE_LEATHER = (new ItemArmor("armor.chestplate.leather", "minecraft:item/armor_chestplate_leather", 16427, ArmorMaterial.LEATHER, IArmorItem.PIECE_CHEST));
-        ARMOR_LEGGINGS_LEATHER = (new ItemArmor("armor.leggings.leather", "minecraft:item/armor_leggings_leather", 16428, ArmorMaterial.LEATHER, IArmorItem.PIECE_LEGS));
-        ARMOR_BOOTS_LEATHER = (new ItemArmor("armor.boots.leather", "minecraft:item/armor_boots_leather", 16429, ArmorMaterial.LEATHER, IArmorItem.PIECE_BOOTS));
+        ARMOR_HELMET_LEATHER = (new ItemArmor("armor.helmet.leather", "minecraft:item/armor_helmet_leather", 16426, ArmorMaterial.LEATHER, HumanArmorShape.HEAD));
+        ARMOR_CHESTPLATE_LEATHER = (new ItemArmor("armor.chestplate.leather", "minecraft:item/armor_chestplate_leather", 16427, ArmorMaterial.LEATHER, HumanArmorShape.CHEST));
+        ARMOR_LEGGINGS_LEATHER = (new ItemArmor("armor.leggings.leather", "minecraft:item/armor_leggings_leather", 16428, ArmorMaterial.LEATHER, HumanArmorShape.LEGS));
+        ARMOR_BOOTS_LEATHER = (new ItemArmor("armor.boots.leather", "minecraft:item/armor_boots_leather", 16429, ArmorMaterial.LEATHER, HumanArmorShape.BOOTS));
 
-        ARMOR_HELMET_CHAINMAIL = (new ItemArmor("armor.helmet.chainmail", "minecraft:item/armor_helmet_chainmail", 16430, ArmorMaterial.CHAINMAIL, IArmorItem.PIECE_HEAD));
-        ARMOR_CHESTPLATE_CHAINMAIL = (new ItemArmor("armor.chestplate.chainmail", "minecraft:item/armor_chestplate_chainmail", 16431, ArmorMaterial.CHAINMAIL, IArmorItem.PIECE_CHEST));
-        ARMOR_LEGGINGS_CHAINMAIL = (new ItemArmor("armor.leggings.chainmail", "minecraft:item/armor_leggings_chainmail", 16432, ArmorMaterial.CHAINMAIL, IArmorItem.PIECE_LEGS));
-        ARMOR_BOOTS_CHAINMAIL = (new ItemArmor("armor.boots.chainmail", "minecraft:item/armor_boots_chainmail", 16433, ArmorMaterial.CHAINMAIL, IArmorItem.PIECE_BOOTS));
+        ARMOR_HELMET_CHAINMAIL = (new ItemArmor("armor.helmet.chainmail", "minecraft:item/armor_helmet_chainmail", 16430, ArmorMaterial.CHAINMAIL, HumanArmorShape.HEAD));
+        ARMOR_CHESTPLATE_CHAINMAIL = (new ItemArmor("armor.chestplate.chainmail", "minecraft:item/armor_chestplate_chainmail", 16431, ArmorMaterial.CHAINMAIL, HumanArmorShape.CHEST));
+        ARMOR_LEGGINGS_CHAINMAIL = (new ItemArmor("armor.leggings.chainmail", "minecraft:item/armor_leggings_chainmail", 16432, ArmorMaterial.CHAINMAIL, HumanArmorShape.LEGS));
+        ARMOR_BOOTS_CHAINMAIL = (new ItemArmor("armor.boots.chainmail", "minecraft:item/armor_boots_chainmail", 16433, ArmorMaterial.CHAINMAIL, HumanArmorShape.BOOTS));
 
-        ARMOR_HELMET_IRON = (new ItemArmor("armor.helmet.iron", "minecraft:item/armor_helmet_iron", 16434, ArmorMaterial.IRON, IArmorItem.PIECE_HEAD));
-        ARMOR_CHESTPLATE_IRON = (new ItemArmor("armor.chestplate.iron", "minecraft:item/armor_chestplate_iron", 16435, ArmorMaterial.IRON, IArmorItem.PIECE_CHEST));
-        ARMOR_LEGGINGS_IRON = (new ItemArmor("armor.leggings.iron", "minecraft:item/armor_leggings_iron", 16436, ArmorMaterial.IRON, IArmorItem.PIECE_LEGS));
-        ARMOR_BOOTS_IRON = (new ItemArmor("armor.boots.iron", "minecraft:item/armor_boots_iron", 16437, ArmorMaterial.IRON, IArmorItem.PIECE_BOOTS));
+        ARMOR_HELMET_IRON = (new ItemArmor("armor.helmet.iron", "minecraft:item/armor_helmet_iron", 16434, ArmorMaterial.IRON, HumanArmorShape.HEAD));
+        ARMOR_CHESTPLATE_IRON = (new ItemArmor("armor.chestplate.iron", "minecraft:item/armor_chestplate_iron", 16435, ArmorMaterial.IRON, HumanArmorShape.CHEST));
+        ARMOR_LEGGINGS_IRON = (new ItemArmor("armor.leggings.iron", "minecraft:item/armor_leggings_iron", 16436, ArmorMaterial.IRON, HumanArmorShape.LEGS));
+        ARMOR_BOOTS_IRON = (new ItemArmor("armor.boots.iron", "minecraft:item/armor_boots_iron", 16437, ArmorMaterial.IRON, HumanArmorShape.BOOTS));
 
-        ARMOR_HELMET_DIAMOND = (new ItemArmor("armor.helmet.diamond", "minecraft:item/armor_helmet_diamond", 16438, ArmorMaterial.DIAMOND, IArmorItem.PIECE_HEAD));
-        ARMOR_CHESTPLATE_DIAMOND = (new ItemArmor("armor.chestplate.diamond", "minecraft:item/armor_chestplate_diamond", 16439, ArmorMaterial.DIAMOND, IArmorItem.PIECE_CHEST));
-        ARMOR_LEGGINGS_DIAMOND = (new ItemArmor("armor.leggings.diamond", "minecraft:item/armor_leggings_diamond", 16440, ArmorMaterial.DIAMOND, IArmorItem.PIECE_LEGS));
-        ARMOR_BOOTS_DIAMOND = (new ItemArmor("armor.boots.diamond", "minecraft:item/armor_boots_diamond", 16441, ArmorMaterial.DIAMOND, IArmorItem.PIECE_BOOTS));
+        ARMOR_HELMET_DIAMOND = (new ItemArmor("armor.helmet.diamond", "minecraft:item/armor_helmet_diamond", 16438, ArmorMaterial.DIAMOND, HumanArmorShape.HEAD));
+        ARMOR_CHESTPLATE_DIAMOND = (new ItemArmor("armor.chestplate.diamond", "minecraft:item/armor_chestplate_diamond", 16439, ArmorMaterial.DIAMOND, HumanArmorShape.CHEST));
+        ARMOR_LEGGINGS_DIAMOND = (new ItemArmor("armor.leggings.diamond", "minecraft:item/armor_leggings_diamond", 16440, ArmorMaterial.DIAMOND, HumanArmorShape.LEGS));
+        ARMOR_BOOTS_DIAMOND = (new ItemArmor("armor.boots.diamond", "minecraft:item/armor_boots_diamond", 16441, ArmorMaterial.DIAMOND, HumanArmorShape.BOOTS));
 
-        ARMOR_HELMET_GOLD = (new ItemArmor("armor.helmet.gold", "minecraft:item/armor_helmet_gold", 16442, ArmorMaterial.GOLD, IArmorItem.PIECE_HEAD));
-        ARMOR_CHESTPLATE_GOLD = (new ItemArmor("armor.chestplate.gold", "minecraft:item/armor_chestplate_gold", 16443, ArmorMaterial.GOLD, IArmorItem.PIECE_CHEST));
-        ARMOR_LEGGINGS_GOLD = (new ItemArmor("armor.leggings.gold", "minecraft:item/armor_leggings_gold", 16444, ArmorMaterial.GOLD, IArmorItem.PIECE_LEGS));
-        ARMOR_BOOTS_GOLD = (new ItemArmor("armor.boots.gold", "minecraft:item/armor_boots_gold", 16445, ArmorMaterial.GOLD, IArmorItem.PIECE_BOOTS));
+        ARMOR_HELMET_GOLD = (new ItemArmor("armor.helmet.gold", "minecraft:item/armor_helmet_gold", 16442, ArmorMaterial.GOLD, HumanArmorShape.HEAD));
+        ARMOR_CHESTPLATE_GOLD = (new ItemArmor("armor.chestplate.gold", "minecraft:item/armor_chestplate_gold", 16443, ArmorMaterial.GOLD, HumanArmorShape.CHEST));
+        ARMOR_LEGGINGS_GOLD = (new ItemArmor("armor.leggings.gold", "minecraft:item/armor_leggings_gold", 16444, ArmorMaterial.GOLD, HumanArmorShape.LEGS));
+        ARMOR_BOOTS_GOLD = (new ItemArmor("armor.boots.gold", "minecraft:item/armor_boots_gold", 16445, ArmorMaterial.GOLD, HumanArmorShape.BOOTS));
         FLINT = (new Item("flint", "minecraft:item/flint", 16446));
         FOOD_PORKCHOP_RAW = (new ItemFood("food.porkchop.raw", "minecraft:item/food_porkchop_raw", 16447, 3, 16, true, 4));
         FOOD_PORKCHOP_COOKED = (new ItemFood("food.porkchop.cooked", "minecraft:item/food_porkchop_cooked", 16448, 8, 16, true, 4));
@@ -347,10 +353,10 @@ public final class Items {
         RECORD_WAIT = (new ItemDiscMusic("record.wait", "minecraft:item/record_wait", 18394, "record.wait", "C418"));
         RECORD_DOG = (new ItemDiscMusic("record.dog", "minecraft:item/record_dog", 18395, "record.dog", "C418"));
         NETHERCOAL = (new Item("nethercoal", "minecraft:item/nethercoal", 16488));
-        ARMOR_HELMET_STEEL = (new ItemArmor("armor.helmet.steel", "minecraft:item/armor_helmet_steel", 16489, ArmorMaterial.STEEL, IArmorItem.PIECE_HEAD));
-        ARMOR_CHESTPLATE_STEEL = (new ItemArmor("armor.chestplate.steel", "minecraft:item/armor_chestplate_steel", 16490, ArmorMaterial.STEEL, IArmorItem.PIECE_CHEST));
-        ARMOR_LEGGINGS_STEEL = (new ItemArmor("armor.leggings.steel", "minecraft:item/armor_leggings_steel", 16491, ArmorMaterial.STEEL, IArmorItem.PIECE_LEGS));
-        ARMOR_BOOTS_STEEL = (new ItemArmor("armor.boots.steel", "minecraft:item/armor_boots_steel", 16492, ArmorMaterial.STEEL, IArmorItem.PIECE_BOOTS));
+        ARMOR_HELMET_STEEL = (new ItemArmor("armor.helmet.steel", "minecraft:item/armor_helmet_steel", 16489, ArmorMaterial.STEEL, HumanArmorShape.HEAD));
+        ARMOR_CHESTPLATE_STEEL = (new ItemArmor("armor.chestplate.steel", "minecraft:item/armor_chestplate_steel", 16490, ArmorMaterial.STEEL, HumanArmorShape.CHEST));
+        ARMOR_LEGGINGS_STEEL = (new ItemArmor("armor.leggings.steel", "minecraft:item/armor_leggings_steel", 16491, ArmorMaterial.STEEL, HumanArmorShape.LEGS));
+        ARMOR_BOOTS_STEEL = (new ItemArmor("armor.boots.steel", "minecraft:item/armor_boots_steel", 16492, ArmorMaterial.STEEL, HumanArmorShape.BOOTS));
 
         TOOL_SWORD_STEEL = (new ItemToolSword("tool.sword.steel", "minecraft:item/tool_sword_steel", 16493, ToolMaterial.steel)).withTags(ItemTags.PREVENT_CREATIVE_MINING);
         TOOL_SHOVEL_STEEL = (new ItemToolShovel("tool.shovel.steel", "minecraft:item/tool_shovel_steel", 16494, ToolMaterial.steel));
@@ -399,5 +405,10 @@ public final class Items {
         WAND_MONSTER_SPAWNER = (new ItemWandSpawner("wand.monster", "minecraft:item/wand_monster", 16536)).setMaxStackSize(1);
         DOOR_STEEL = (new ItemDoor("door.steel", "minecraft:item/door_steel", 16537, Blocks.DOOR_STEEL_BOTTOM, Blocks.DOOR_STEEL_TOP));
         PAINTBRUSH = new ItemPaintBrush("paintbrush", "minecraft:item/paintbrush", 16538);
+        JAR_BUTTERFLY_BLUE = new ItemPlaceable("jar.butterfly.blue", "minecraft:item/jar_butterfly_blue", 16539, Blocks.JAR_BUTTERFLY_BLUE);
+        JAR_BUTTERFLY_ORANGE = new ItemPlaceable("jar.butterfly.orange", "minecraft:item/jar_butterfly_orange", 16540, Blocks.JAR_BUTTERFLY_ORANGE);
+        JAR_BUTTERFLY_PINK = new ItemPlaceable("jar.butterfly.pink", "minecraft:item/jar_butterfly_pink", 16541, Blocks.JAR_BUTTERFLY_PINK);
+        JAR_BUTTERFLY_SILVER = new ItemPlaceable("jar.butterfly.silver", "minecraft:item/jar_butterfly_silver", 16542, Blocks.JAR_BUTTERFLY_SILVER);
+        STATUE_STONE = new ItemStatue("statue.stone", "minecraft:item/statue_stone", 16543, Blocks.STATUE_STONE_LOWER, Blocks.STATUE_STONE_UPPER);
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/lang/Language.java b/game/core/src/main/java/net/minecraft/core/lang/Language.java
index 5587d74b6..42aa60029 100644
--- a/game/core/src/main/java/net/minecraft/core/lang/Language.java
+++ b/game/core/src/main/java/net/minecraft/core/lang/Language.java
@@ -55,6 +55,7 @@ public class Language {
      * A list of loading screen tips provided by the language.
      */
     protected List<String> tips = null;
+
     /**
      * The world name generator for this language.
      */
diff --git a/game/core/src/main/java/net/minecraft/core/net/command/TextFormatting.java b/game/core/src/main/java/net/minecraft/core/net/command/TextFormatting.java
index c953a03c7..3da062e97 100644
--- a/game/core/src/main/java/net/minecraft/core/net/command/TextFormatting.java
+++ b/game/core/src/main/java/net/minecraft/core/net/command/TextFormatting.java
@@ -1,5 +1,7 @@
 package net.minecraft.core.net.command;
 
+import org.jetbrains.annotations.Nullable;
+
 public class TextFormatting {
 	public static final TextFormatting[] FORMATTINGS = new TextFormatting[22];
 	
@@ -26,6 +28,8 @@ public class TextFormatting {
     public static final TextFormatting ITALIC = new TextFormatting(20).setNames("Italic");
     public static final TextFormatting RESET = new TextFormatting(21).setNames("Reset");
 
+	public static final char SPECIAL_CHAR = '\247';
+
 	public final int id;
 	public final char code;
 	private String[] names;
@@ -35,6 +39,15 @@ public class TextFormatting {
 		this.id = id;
 		this.code = "0123456789abcdefklmnor".charAt(id);
 	}
+
+	public static @Nullable TextFormatting fromChar(final char c) {
+        for (TextFormatting formatting : FORMATTINGS) {
+            if (formatting.code == c) {
+                return formatting;
+            }
+        }
+		return null;
+	}
 	
 	public TextFormatting setNames(String... names) {
 		this.names = names;
@@ -82,7 +95,7 @@ public class TextFormatting {
 	
 	@Override
 	public String toString() {
-		return "\247" + code;
+		return "" + SPECIAL_CHAR + code;
 	}
 	
 	public static TextFormatting get(int id) {
diff --git a/game/core/src/main/java/net/minecraft/core/net/command/arguments/ArgumentTypeParticleId.java b/game/core/src/main/java/net/minecraft/core/net/command/arguments/ArgumentTypeParticleId.java
index 2b21364c5..4291996c8 100644
--- a/game/core/src/main/java/net/minecraft/core/net/command/arguments/ArgumentTypeParticleId.java
+++ b/game/core/src/main/java/net/minecraft/core/net/command/arguments/ArgumentTypeParticleId.java
@@ -47,6 +47,7 @@ public class ArgumentTypeParticleId implements ArgumentType<String> {
         particleIds.add("fireflyGreen");
         particleIds.add("fireflyRed");
         particleIds.add("splash");
+        particleIds.add("ashmote");
     }
 
     private static final List<String> EXAMPLES = Arrays.asList("fireflyGreen", "explode", "soulflame");
diff --git a/game/core/src/main/java/net/minecraft/core/player/inventory/container/ContainerInventory.java b/game/core/src/main/java/net/minecraft/core/player/inventory/container/ContainerInventory.java
index 34710a3f3..8fef34a37 100644
--- a/game/core/src/main/java/net/minecraft/core/player/inventory/container/ContainerInventory.java
+++ b/game/core/src/main/java/net/minecraft/core/player/inventory/container/ContainerInventory.java
@@ -9,6 +9,7 @@ import net.minecraft.core.block.Blocks;
 import net.minecraft.core.block.tag.BlockTags;
 import net.minecraft.core.entity.Entity;
 import net.minecraft.core.entity.player.Player;
+import net.minecraft.core.enums.IArmorShape;
 import net.minecraft.core.item.IArmorItem;
 import net.minecraft.core.item.Item;
 import net.minecraft.core.item.ItemStack;
@@ -397,9 +398,9 @@ public class ContainerInventory
         }
     }
 
-    public ItemStack armorItemInSlot(int i)
+    public ItemStack armorItemInSlot(IArmorShape shape)
     {
-        return armorInventory[i];
+        return armorInventory[shape.getSlotIndex()];
     }
 
     public int getTotalArmourPoints() {
@@ -419,52 +420,6 @@ public class ContainerInventory
     	return (int) (20 * (armorDurability / maxDurability));
     }
 
-    public float getTotalProtectionAmount(DamageType damageType) {
-    	float protectionPercentage = 0.0f;
-
-        for (ItemStack itemStack : armorInventory) {
-            if (itemStack != null && itemStack.getItem() instanceof IArmorItem) {
-                IArmorItem armor = (IArmorItem) itemStack.getItem();
-                ArmorMaterial material = armor.getArmorMaterial();
-                if (material != null) {
-                    protectionPercentage += material.getProtection(damageType) * armor.getArmorPieceProtectionPercentage();
-                }
-            }
-        }
-    	
-    	return protectionPercentage;
-    }
-
-    public void damageArmor(int damage)
-    {
-        for(int j = 0; j < armorInventory.length; j++)
-        {
-            if(armorInventory[j] == null || !(armorInventory[j].getItem() instanceof IArmorItem))
-            {
-                continue;
-            }
-            armorInventory[j].damageItem(damage, player);
-            if(armorInventory[j].stackSize <= 0)
-            {
-                armorInventory[j] = null;
-            }
-        }
-
-    }
-
-    public void damageArmor(int damage, int armorSlot)
-    {
-        if(armorInventory[armorSlot] == null)
-        {
-            return;
-        }
-        armorInventory[armorSlot].damageItem(damage, player);
-        if(armorInventory[armorSlot].stackSize <= 0)
-        {
-            armorInventory[armorSlot] = null;
-        }
-    }
-
     public void dropAllItems()
     {
         for(int i = 0; i < mainInventory.length; i++)
diff --git a/game/core/src/main/java/net/minecraft/core/player/inventory/menu/MenuAbstract.java b/game/core/src/main/java/net/minecraft/core/player/inventory/menu/MenuAbstract.java
index 8d57d074a..706d6c50c 100644
--- a/game/core/src/main/java/net/minecraft/core/player/inventory/menu/MenuAbstract.java
+++ b/game/core/src/main/java/net/minecraft/core/player/inventory/menu/MenuAbstract.java
@@ -7,6 +7,8 @@ import net.minecraft.core.InventoryAction;
 import net.minecraft.core.Global;
 import net.minecraft.core.crafting.ContainerListener;
 import net.minecraft.core.entity.player.Player;
+import net.minecraft.core.enums.HumanArmorShape;
+import net.minecraft.core.enums.IArmorShape;
 import net.minecraft.core.item.IArmorItem;
 import net.minecraft.core.item.Item;
 import net.minecraft.core.item.ItemStack;
@@ -382,17 +384,17 @@ public abstract class MenuAbstract
 		}
 		
 		Item itemInSlot = stackInSlot.getItem();
-		IArmorItem armorItem;
+		IArmorItem<HumanArmorShape> armorItem;
 		
 		// Check if item is armor
-		if(itemInSlot instanceof IArmorItem) {
-			armorItem = (IArmorItem) itemInSlot;
-		}else {
+		if(itemInSlot instanceof IArmorItem && ((IArmorItem<?>) itemInSlot).getArmorShape() instanceof HumanArmorShape) {
+			armorItem = (IArmorItem<HumanArmorShape>) itemInSlot;
+		} else {
 			return;
 		}
-		
-		int armorSlot = 3 - armorItem.getArmorPiece();
-		
+
+		HumanArmorShape armorShape = armorItem.getArmorShape();
+
 		// Take new armor
 		ItemStack newArmor = stackInSlot;
 		slot.set(null);
@@ -400,9 +402,9 @@ public abstract class MenuAbstract
 		slot.setChanged();
 		
 		// Equip new armor
-		ItemStack oldArmor = player.inventory.armorInventory[armorSlot];
-		player.inventory.armorInventory[armorSlot] = newArmor;
-		
+		ItemStack oldArmor = player.getItemInArmorSlot(armorShape);
+		player.setItemInArmorSlot(armorShape, newArmor);
+
 		// Put old armor in slot, or drop if not possible
 		mergeItems(oldArmor, slot.index);
 		storeOrDropItem(player, oldArmor);
diff --git a/game/core/src/main/java/net/minecraft/core/player/inventory/menu/MenuInventory.java b/game/core/src/main/java/net/minecraft/core/player/inventory/menu/MenuInventory.java
index 320838c4b..a69475996 100644
--- a/game/core/src/main/java/net/minecraft/core/player/inventory/menu/MenuInventory.java
+++ b/game/core/src/main/java/net/minecraft/core/player/inventory/menu/MenuInventory.java
@@ -3,6 +3,7 @@ package net.minecraft.core.player.inventory.menu;
 import net.minecraft.core.InventoryAction;
 import net.minecraft.core.data.registry.Registries;
 import net.minecraft.core.entity.player.Player;
+import net.minecraft.core.enums.HumanArmorShape;
 import net.minecraft.core.item.IArmorItem;
 import net.minecraft.core.item.ItemStack;
 import net.minecraft.core.player.inventory.container.Container;
@@ -39,9 +40,8 @@ public class MenuInventory extends MenuAbstract {
 			}
 		}
 		
-		for (int i = 0; i < 4; i++) {
-			int armorType = IArmorItem.PIECE_HEAD - i;
-			addSlot(new SlotArmor(this, inventory, inventory.getContainerSize() - 1 - i, 8, 8 + i * 18, armorType));
+		for (int i = 0; i < HumanArmorShape.values().length; i++) {
+			addSlot(new SlotArmor(this, inventory, inventory.getContainerSize() - 1 - i, 8, 8 + i * 18, HumanArmorShape.values()[HumanArmorShape.values().length - (i + 1)]));
 		}
 		
 		for (int i = 0; i < 3; i++) {
diff --git a/game/core/src/main/java/net/minecraft/core/player/inventory/slot/SlotArmor.java b/game/core/src/main/java/net/minecraft/core/player/inventory/slot/SlotArmor.java
index 140076801..f1b5492e3 100644
--- a/game/core/src/main/java/net/minecraft/core/player/inventory/slot/SlotArmor.java
+++ b/game/core/src/main/java/net/minecraft/core/player/inventory/slot/SlotArmor.java
@@ -2,6 +2,7 @@ package net.minecraft.core.player.inventory.slot;
 
 import net.minecraft.core.achievement.Achievements;
 import net.minecraft.core.entity.player.Player;
+import net.minecraft.core.enums.HumanArmorShape;
 import net.minecraft.core.item.IArmorItem;
 import net.minecraft.core.item.Item;
 import net.minecraft.core.item.Items;
@@ -23,14 +24,14 @@ public class SlotArmor extends Slot
         "minecraft:item/armor_helmet_outline"
     };
 
-    final int armorType;
+    final HumanArmorShape armorShape;
     final MenuInventory menu;
 
-    public SlotArmor(MenuInventory menu, Container container, int index, int x, int y, int armorType)
+    public SlotArmor(MenuInventory menu, Container container, int index, int x, int y, HumanArmorShape armorShape)
     {
         super(container, index, x, y);
         this.menu = menu;
-        this.armorType = armorType;
+        this.armorShape = armorShape;
     }
 
     @Override
@@ -44,9 +45,9 @@ public class SlotArmor extends Slot
     {
         if(itemstack.getItem() instanceof IArmorItem)
         {
-            return ((IArmorItem)itemstack.getItem()).getArmorPiece() == armorType;
+            return ((IArmorItem<?>) itemstack.getItem()).getArmorShape() == this.armorShape;
         }
-        return armorType == IArmorItem.PIECE_HEAD;
+        return this.armorShape == HumanArmorShape.HEAD;
     }
     
     @Override
@@ -98,6 +99,6 @@ public class SlotArmor extends Slot
     @Override
 	public String getItemIcon()
     {
-        return armorOutlines[armorType];
+        return armorOutlines[this.armorShape.getSlotIndex()];
     }
 }
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 2aefe2c52..4f0b272f8 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
@@ -2113,7 +2113,7 @@ public class World implements WorldSource {
         x += side.getOffsetX();
         y += side.getOffsetY();
         z += side.getOffsetZ();
-        if(getBlockId(x, y, z) == Blocks.FIRE.id())
+        if(getBlockId(x, y, z) == Blocks.FIRE.id() || getBlockId(x, y, z) == Blocks.FIRE_COLD.id())
         {
             playBlockEvent(entityplayer, LevelListener.EVENT_FIRE_EXTINGUISH, x, y, z, 0);
             setBlockWithNotify(x, y, z, 0);
@@ -2320,7 +2320,7 @@ public class World implements WorldSource {
             int maxLightingUpdates = 1000000;
             if(lightingToUpdate.size() > maxLightingUpdates)
             {
-                System.out.println("More than " + maxLightingUpdates + " updates, dropping first " + maxLightingUpdates / 2 + " lighting updates");
+                LOGGER.warn("More than " + maxLightingUpdates + " updates, dropping first " + maxLightingUpdates / 2 + " lighting updates");
                 lightingToUpdate.subList(0, maxLightingUpdates / 2).clear();
             }
         }
diff --git a/game/core/src/main/java/net/minecraft/core/world/biome/Biomes.java b/game/core/src/main/java/net/minecraft/core/world/biome/Biomes.java
index 109abd665..bf5205f60 100644
--- a/game/core/src/main/java/net/minecraft/core/world/biome/Biomes.java
+++ b/game/core/src/main/java/net/minecraft/core/world/biome/Biomes.java
@@ -2,34 +2,43 @@ package net.minecraft.core.world.biome;
 
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.data.registry.Registries;
+import net.minecraft.core.world.biome.nether.*;
+import net.minecraft.core.world.biome.overworld.*;
+import net.minecraft.core.world.biome.paradise.BiomeParadise;
 import net.minecraft.core.world.weather.Weathers;
 
 public class Biomes
 {
 
-    public static final Biome OVERWORLD_RAINFOREST = register("minecraft:overworld.rainforest", new BiomeRainforest("rainforest").setColor(0x1cf100).setBlockedWeathers(Weathers.OVERWORLD_SNOW));
-    public static final Biome OVERWORLD_SWAMPLAND = register("minecraft:overworld.swampland", new BiomeSwamp("swampland").setColor(0x00ff78));
-    public static final Biome OVERWORLD_SEASONAL_FOREST = register("minecraft:overworld.seasonal_forest", new BiomeSeasonalForest("seasonal_forest").setColor(0x64ce15));
-    public static final Biome OVERWORLD_FOREST = register("minecraft:overworld.forest", new BiomeForest("forest").setColor(0x26a615));
-    public static final Biome OVERWORLD_GRASSLANDS = register("minecraft:overworld.grasslands", new Biome("grasslands").setColor(0xf0f048).setBlockedWeathers(Weathers.OVERWORLD_SNOW));
-    public static final Biome OVERWORLD_OUTBACK = register("minecraft:overworld.outback", new BiomeOutback("outback").setColor(0xd95e20).setBlockedWeathers(Weathers.OVERWORLD_SNOW).setTopBlock(Blocks.DIRT_SCORCHED.id()).setFillerBlock(Blocks.DIRT_SCORCHED.id()));
-    public static final Biome OVERWORLD_SHRUBLAND = register("minecraft:overworld.shrubland", new BiomeShrubland("shrubland").setColor(0xbaba38));
-    public static final Biome OVERWORLD_TAIGA = register("minecraft:overworld.taiga", new BiomeTaiga("taiga").setColor(0x8abcff).setSurfaceSnow());
-    public static final Biome OVERWORLD_BOREAL_FOREST = register("minecraft:overworld.boreal_forest", new BiomeBorealForest("boreal_forest").setColor(0x148564));
-    public static final Biome OVERWORLD_DESERT = register("minecraft:overworld.desert", new BiomeDesert("desert").setColor(0xEDE26F).setBlockedWeathers(Weathers.OVERWORLD_RAIN, Weathers.OVERWORLD_SNOW, Weathers.OVERWORLD_STORM).setTopBlock(Blocks.SAND.id()).setFillerBlock(Blocks.SAND.id()));
-    public static final Biome OVERWORLD_PLAINS = register("minecraft:overworld.plains", new BiomePlains("plains").setColor(0xA3C649));
-    public static final Biome OVERWORLD_GLACIER = register("minecraft:overworld.glacier", new Biome("glacier").setColor(0xcbf6ff).setSurfaceSnow().setTopBlock(Blocks.BLOCK_SNOW.id()).setFillerBlock(Blocks.BLOCK_SNOW.id()));
-    public static final Biome OVERWORLD_TUNDRA = register("minecraft:overworld.tundra", new Biome("tundra").setColor(0x57e0ff).setSurfaceSnow());
-    public static final Biome OVERWORLD_MEADOW = register("minecraft:overworld.meadow", new BiomeBorealForest("meadow").setColor(0x8c8c22));
-    public static final Biome NETHER_NETHER = register("minecraft:nether.nether", new BiomeNether("nether").setColor(0xDD0000).setBlockedWeathers(Weathers.OVERWORLD_RAIN, Weathers.OVERWORLD_SNOW, Weathers.OVERWORLD_STORM, Weathers.OVERWORLD_FOG));
-    public static final Biome PARADISE_PARADISE = register("minecraft:paradise.paradise", new BiomeParadise("paradise").setColor(0x8080FF).setBlockedWeathers(Weathers.OVERWORLD_RAIN, Weathers.OVERWORLD_SNOW, Weathers.OVERWORLD_STORM, Weathers.OVERWORLD_FOG));
-    public static final Biome OVERWORLD_BIRCH_FOREST = register("minecraft:overworld.birch_forest", new BiomeBirchForest("birch_forest").setColor(0x4bcb5e));
-    public static final Biome OVERWORLD_RETRO = register("minecraft:overworld.retro", new BiomeRetro("retro").setColor(0x76B14A).setTopBlock(Blocks.GRASS.id()));
-    public static final Biome OVERWORLD_HELL = register("minecraft:overworld.hell", new Biome("hell").setColor(0xD96223).setTopBlock(Blocks.DIRT_SCORCHED.id()).setTopBlock(Blocks.DIRT_SCORCHED.id()));
-    public static final Biome OVERWORLD_SWAMPLAND_MUDDY = register("minecraft:overworld.swampland.muddy", new BiomeSwamp("swampland.muddy").setColor(0x522700));
-    public static final Biome OVERWORLD_OUTBACK_GRASSY = register("minecraft:overworld.outback.grassy", new BiomeOutback("outback.grassy").setColor(0xf09037).setBlockedWeathers(Weathers.OVERWORLD_SNOW).setTopBlock(Blocks.GRASS_SCORCHED.id()).setFillerBlock(Blocks.DIRT_SCORCHED.id()));
-    public static final Biome OVERWORLD_CAATINGA = register("minecraft:overworld.caatinga", new BiomeCaatinga("caatinga").setColor(0xdecda6).setTopBlock(Blocks.MUD_BAKED.id()).setFillerBlock(Blocks.MUD_BAKED.id()).setBlockedWeathers(Weathers.OVERWORLD_SNOW));
-    public static final Biome OVERWORLD_CAATINGA_PLAINS = register("minecraft:overworld.caatinga.plains", new Biome("caatinga.plains").setColor(0xa19477).setTopBlock(Blocks.MUD_BAKED.id()).setFillerBlock(Blocks.MUD_BAKED.id()).setBlockedWeathers(Weathers.OVERWORLD_SNOW));
+    public static final Biome OVERWORLD_RAINFOREST = register("minecraft:overworld.rainforest", new BiomeRainforest("overworld.rainforest").setColor(0x1cf100).setBlockedWeathers(Weathers.OVERWORLD_SNOW));
+    public static final Biome OVERWORLD_SWAMPLAND = register("minecraft:overworld.swampland", new BiomeSwamp("overworld.swampland").setColor(0x00ff78));
+    public static final Biome OVERWORLD_SEASONAL_FOREST = register("minecraft:overworld.seasonal_forest", new BiomeSeasonalForest("overworld.seasonal_forest").setColor(0x64ce15));
+    public static final Biome OVERWORLD_FOREST = register("minecraft:overworld.forest", new BiomeForest("overworld.forest").setColor(0x26a615));
+    public static final Biome OVERWORLD_GRASSLANDS = register("minecraft:overworld.grasslands", new Biome("overworld.grasslands").setColor(0xf0f048).setBlockedWeathers(Weathers.OVERWORLD_SNOW));
+    public static final Biome OVERWORLD_OUTBACK = register("minecraft:overworld.outback", new BiomeOutback("overworld.outback").setColor(0xd95e20).setBlockedWeathers(Weathers.OVERWORLD_SNOW).setTopBlock(Blocks.DIRT_SCORCHED.id()).setFillerBlock(Blocks.DIRT_SCORCHED.id()));
+    public static final Biome OVERWORLD_SHRUBLAND = register("minecraft:overworld.shrubland", new BiomeShrubland("overworld.shrubland").setColor(0xbaba38));
+    public static final Biome OVERWORLD_TAIGA = register("minecraft:overworld.taiga", new BiomeTaiga("overworld.taiga").setColor(0x8abcff).setSurfaceSnow());
+    public static final Biome OVERWORLD_BOREAL_FOREST = register("minecraft:overworld.boreal_forest", new BiomeBorealForest("overworld.boreal_forest").setColor(0x148564));
+    public static final Biome OVERWORLD_DESERT = register("minecraft:overworld.desert", new BiomeDesert("overworld.desert").setColor(0xEDE26F).setBlockedWeathers(Weathers.OVERWORLD_RAIN, Weathers.OVERWORLD_SNOW, Weathers.OVERWORLD_STORM).setTopBlock(Blocks.SAND.id()).setFillerBlock(Blocks.SAND.id()));
+    public static final Biome OVERWORLD_PLAINS = register("minecraft:overworld.plains", new BiomePlains("overworld.plains").setColor(0xA3C649));
+    public static final Biome OVERWORLD_GLACIER = register("minecraft:overworld.glacier", new Biome("overworld.glacier").setColor(0xcbf6ff).setSurfaceSnow().setTopBlock(Blocks.BLOCK_SNOW.id()).setFillerBlock(Blocks.BLOCK_SNOW.id()));
+    public static final Biome OVERWORLD_TUNDRA = register("minecraft:overworld.tundra", new Biome("overworld.tundra").setColor(0x57e0ff).setSurfaceSnow());
+    public static final Biome OVERWORLD_MEADOW = register("minecraft:overworld.meadow", new BiomeBorealForest("overworld.meadow").setColor(0x8c8c22));
+    public static final Biome PARADISE_PARADISE = register("minecraft:paradise.paradise", new BiomeParadise("overworld.paradise").setColor(0x8080FF).setBlockedWeathers(Weathers.OVERWORLD_RAIN, Weathers.OVERWORLD_SNOW, Weathers.OVERWORLD_STORM, Weathers.OVERWORLD_FOG));
+    public static final Biome OVERWORLD_BIRCH_FOREST = register("minecraft:overworld.birch_forest", new BiomeBirchForest("overworld.birch_forest").setColor(0x4bcb5e));
+    public static final Biome OVERWORLD_RETRO = register("minecraft:overworld.retro", new BiomeRetro("overworld.retro").setColor(0x76B14A).setTopBlock(Blocks.GRASS_RETRO.id()));
+    public static final Biome OVERWORLD_HELL = register("minecraft:overworld.hell", new Biome("overworld.hell").setColor(0xD96223).setTopBlock(Blocks.DIRT_SCORCHED.id()).setTopBlock(Blocks.DIRT_SCORCHED.id()));
+    public static final Biome OVERWORLD_SWAMPLAND_MUDDY = register("minecraft:overworld.swampland.muddy", new BiomeSwamp("overworld.swampland.muddy").setColor(0x522700));
+    public static final Biome OVERWORLD_OUTBACK_GRASSY = register("minecraft:overworld.outback.grassy", new BiomeOutback("overworld.outback.grassy").setColor(0xf09037).setBlockedWeathers(Weathers.OVERWORLD_SNOW).setTopBlock(Blocks.GRASS_SCORCHED.id()).setFillerBlock(Blocks.DIRT_SCORCHED.id()));
+    public static final Biome OVERWORLD_CAATINGA = register("minecraft:overworld.caatinga", new BiomeCaatinga("overworld.caatinga").setColor(0xdecda6).setTopBlock(Blocks.MUD_BAKED.id()).setFillerBlock(Blocks.MUD_BAKED.id()).setBlockedWeathers(Weathers.OVERWORLD_SNOW));
+    public static final Biome OVERWORLD_CAATINGA_PLAINS = register("minecraft:overworld.caatinga.plains", new Biome("overworld.caatinga.plains").setColor(0xa19477).setTopBlock(Blocks.MUD_BAKED.id()).setFillerBlock(Blocks.MUD_BAKED.id()).setBlockedWeathers(Weathers.OVERWORLD_SNOW));
+    public static final Biome NETHER_NETHER = register("minecraft:nether.nether", new BiomeNether("nether.nether").setColor(0xDD0000).setBlockedWeathers(Weathers.OVERWORLD_RAIN, Weathers.OVERWORLD_SNOW, Weathers.OVERWORLD_STORM, Weathers.OVERWORLD_FOG).setTopBlock(Blocks.COBBLE_NETHERRACK.id()).setFillerBlock(Blocks.COBBLE_NETHERRACK.id()));
+    public static final Biome NETHER_VOLCANIC_ISLANDS = register("minecraft:nether.volcanic_islands", new BiomeVolcanoIslands("nether.volcanic_islands").setColor(0xDD0000).setBlockedWeathers(Weathers.OVERWORLD_RAIN, Weathers.OVERWORLD_SNOW, Weathers.OVERWORLD_STORM, Weathers.OVERWORLD_FOG).setTopBlock(Blocks.COBBLE_NETHERRACK.id()).setFillerBlock(Blocks.COBBLE_NETHERRACK.id()));
+    public static final Biome NETHER_SULFUR_POOLS = register("minecraft:nether.sulfur_pools", new BiomeSulfurPools("nether.sulfur_pools").setColor(0xDD0000).setBlockedWeathers(Weathers.OVERWORLD_RAIN, Weathers.OVERWORLD_SNOW, Weathers.OVERWORLD_STORM, Weathers.OVERWORLD_FOG).setTopBlock(Blocks.COBBLE_NETHERRACK.id()).setFillerBlock(Blocks.COBBLE_NETHERRACK.id()));
+    public static final Biome NETHER_CRYSTAL_FOREST = register("minecraft:nether.crystal_forest", new BiomeCrystalForest("nether.crystal_forest").setColor(0xDD0000).setBlockedWeathers(Weathers.OVERWORLD_RAIN, Weathers.OVERWORLD_SNOW, Weathers.OVERWORLD_STORM, Weathers.OVERWORLD_FOG).setTopBlock(Blocks.COBBLE_NETHERRACK.id()).setFillerBlock(Blocks.COBBLE_NETHERRACK.id()));
+    public static final Biome NETHER_ASH_PLAINS = register("minecraft:nether.ash_plains", new BiomeAshPlains("nether.ash_plains").setColor(0xDD0000).setBlockedWeathers(Weathers.OVERWORLD_RAIN, Weathers.OVERWORLD_SNOW, Weathers.OVERWORLD_STORM, Weathers.OVERWORLD_FOG).setTopBlock(Blocks.SLATE.id()).setFillerBlock(Blocks.SLATE.id()));
+    public static final Biome NETHER_BONEYARD = register("minecraft:nether.boneyard", new BiomeBoneyard("nether.boneyard").setColor(0xDD0000).setBlockedWeathers(Weathers.OVERWORLD_RAIN, Weathers.OVERWORLD_SNOW, Weathers.OVERWORLD_STORM, Weathers.OVERWORLD_FOG).setTopBlock(Blocks.SOULSAND.id()).setFillerBlock(Blocks.SOULSAND.id()));
+
     public static Biome register(String key, Biome biome)
     {
         Registries.BIOMES.register(key, biome);
diff --git a/game/core/src/main/java/net/minecraft/core/world/biome/nether/BiomeAshPlains.java b/game/core/src/main/java/net/minecraft/core/world/biome/nether/BiomeAshPlains.java
new file mode 100644
index 000000000..e2dd96b1e
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/biome/nether/BiomeAshPlains.java
@@ -0,0 +1,22 @@
+package net.minecraft.core.world.biome.nether;
+
+import net.minecraft.core.entity.SpawnListEntry;
+import net.minecraft.core.entity.monster.MobGhast;
+import net.minecraft.core.entity.monster.MobZombiePig;
+import net.minecraft.core.world.biome.Biome;
+
+public class BiomeAshPlains
+    extends Biome
+{
+
+    public BiomeAshPlains(String key) {
+        super(key);
+        spawnableMonsterList.clear();
+        spawnableCreatureList.clear();
+        spawnableWaterCreatureList.clear();
+        spawnableAmbientCreatureList.clear();
+        spawnableMonsterList.add(new SpawnListEntry(MobGhast.class, 10));
+        spawnableMonsterList.add(new SpawnListEntry(MobZombiePig.class, 10));
+
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/world/biome/nether/BiomeBoneyard.java b/game/core/src/main/java/net/minecraft/core/world/biome/nether/BiomeBoneyard.java
new file mode 100644
index 000000000..715278b4f
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/biome/nether/BiomeBoneyard.java
@@ -0,0 +1,20 @@
+package net.minecraft.core.world.biome.nether;
+
+import net.minecraft.core.entity.SpawnListEntry;
+import net.minecraft.core.entity.monster.MobGhast;
+import net.minecraft.core.entity.monster.MobZombiePig;
+import net.minecraft.core.world.biome.Biome;
+
+public class BiomeBoneyard extends Biome {
+
+    public BiomeBoneyard(String key) {
+        super(key);
+        spawnableMonsterList.clear();
+        spawnableCreatureList.clear();
+        spawnableWaterCreatureList.clear();
+        spawnableAmbientCreatureList.clear();
+        spawnableMonsterList.add(new SpawnListEntry(MobGhast.class, 10));
+        spawnableMonsterList.add(new SpawnListEntry(MobZombiePig.class, 10));
+
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/world/biome/nether/BiomeCrystalForest.java b/game/core/src/main/java/net/minecraft/core/world/biome/nether/BiomeCrystalForest.java
new file mode 100644
index 000000000..382ef99c7
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/biome/nether/BiomeCrystalForest.java
@@ -0,0 +1,20 @@
+package net.minecraft.core.world.biome.nether;
+
+import net.minecraft.core.entity.SpawnListEntry;
+import net.minecraft.core.entity.monster.MobGhast;
+import net.minecraft.core.entity.monster.MobZombiePig;
+import net.minecraft.core.world.biome.Biome;
+
+public class BiomeCrystalForest
+    extends Biome {
+    public BiomeCrystalForest(String key) {
+        super(key);
+        spawnableMonsterList.clear();
+        spawnableCreatureList.clear();
+        spawnableWaterCreatureList.clear();
+        spawnableAmbientCreatureList.clear();
+        spawnableMonsterList.add(new SpawnListEntry(MobGhast.class, 10));
+        spawnableMonsterList.add(new SpawnListEntry(MobZombiePig.class, 10));
+
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/world/biome/BiomeNether.java b/game/core/src/main/java/net/minecraft/core/world/biome/nether/BiomeNether.java
similarity index 86%
rename from game/core/src/main/java/net/minecraft/core/world/biome/BiomeNether.java
rename to game/core/src/main/java/net/minecraft/core/world/biome/nether/BiomeNether.java
index cc6b7496a..ef76fe354 100644
--- a/game/core/src/main/java/net/minecraft/core/world/biome/BiomeNether.java
+++ b/game/core/src/main/java/net/minecraft/core/world/biome/nether/BiomeNether.java
@@ -1,8 +1,9 @@
-package net.minecraft.core.world.biome;
+package net.minecraft.core.world.biome.nether;
 
 import net.minecraft.core.entity.monster.MobGhast;
 import net.minecraft.core.entity.monster.MobZombiePig;
 import net.minecraft.core.entity.SpawnListEntry;
+import net.minecraft.core.world.biome.Biome;
 
 public class BiomeNether extends Biome
 {
diff --git a/game/core/src/main/java/net/minecraft/core/world/biome/nether/BiomeSulfurPools.java b/game/core/src/main/java/net/minecraft/core/world/biome/nether/BiomeSulfurPools.java
new file mode 100644
index 000000000..9ac8ded6e
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/biome/nether/BiomeSulfurPools.java
@@ -0,0 +1,19 @@
+package net.minecraft.core.world.biome.nether;
+
+import net.minecraft.core.entity.SpawnListEntry;
+import net.minecraft.core.entity.monster.MobGhast;
+import net.minecraft.core.entity.monster.MobZombiePig;
+import net.minecraft.core.world.biome.Biome;
+
+public class BiomeSulfurPools
+extends Biome {
+    public BiomeSulfurPools(String key) {
+        super(key);
+        spawnableMonsterList.clear();
+        spawnableCreatureList.clear();
+        spawnableWaterCreatureList.clear();
+        spawnableAmbientCreatureList.clear();
+        spawnableMonsterList.add(new SpawnListEntry(MobGhast.class, 10));
+        spawnableMonsterList.add(new SpawnListEntry(MobZombiePig.class, 10));
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/world/biome/nether/BiomeVolcanoIslands.java b/game/core/src/main/java/net/minecraft/core/world/biome/nether/BiomeVolcanoIslands.java
new file mode 100644
index 000000000..246774fab
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/biome/nether/BiomeVolcanoIslands.java
@@ -0,0 +1,19 @@
+package net.minecraft.core.world.biome.nether;
+
+import net.minecraft.core.entity.SpawnListEntry;
+import net.minecraft.core.entity.monster.MobGhast;
+import net.minecraft.core.entity.monster.MobZombiePig;
+import net.minecraft.core.world.biome.Biome;
+
+public class BiomeVolcanoIslands
+    extends Biome {
+    public BiomeVolcanoIslands(String key) {
+        super(key);
+        spawnableMonsterList.clear();
+        spawnableCreatureList.clear();
+        spawnableWaterCreatureList.clear();
+        spawnableAmbientCreatureList.clear();
+        spawnableMonsterList.add(new SpawnListEntry(MobGhast.class, 10));
+        spawnableMonsterList.add(new SpawnListEntry(MobZombiePig.class, 10));
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/world/biome/BiomeBirchForest.java b/game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomeBirchForest.java
similarity index 90%
rename from game/core/src/main/java/net/minecraft/core/world/biome/BiomeBirchForest.java
rename to game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomeBirchForest.java
index a69354919..13acfed9c 100644
--- a/game/core/src/main/java/net/minecraft/core/world/biome/BiomeBirchForest.java
+++ b/game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomeBirchForest.java
@@ -1,8 +1,9 @@
-package net.minecraft.core.world.biome;
+package net.minecraft.core.world.biome.overworld;
 
 import net.minecraft.core.entity.animal.MobWolf;
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.entity.SpawnListEntry;
+import net.minecraft.core.world.biome.Biome;
 import net.minecraft.core.world.generate.feature.tree.WorldFeatureTree;
 import net.minecraft.core.world.generate.feature.WorldFeature;
 import net.minecraft.core.world.generate.feature.tree.WorldFeatureTreeTall;
diff --git a/game/core/src/main/java/net/minecraft/core/world/biome/BiomeBorealForest.java b/game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomeBorealForest.java
similarity index 87%
rename from game/core/src/main/java/net/minecraft/core/world/biome/BiomeBorealForest.java
rename to game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomeBorealForest.java
index 48e72e0ad..a26de87fb 100644
--- a/game/core/src/main/java/net/minecraft/core/world/biome/BiomeBorealForest.java
+++ b/game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomeBorealForest.java
@@ -1,8 +1,9 @@
-package net.minecraft.core.world.biome;
+package net.minecraft.core.world.biome.overworld;
 
 import net.minecraft.core.entity.animal.MobWolf;
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.entity.SpawnListEntry;
+import net.minecraft.core.world.biome.Biome;
 import net.minecraft.core.world.generate.feature.tree.WorldFeatureTreeTaigaBushy;
 import net.minecraft.core.world.generate.feature.WorldFeature;
 
diff --git a/game/core/src/main/java/net/minecraft/core/world/biome/BiomeCaatinga.java b/game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomeCaatinga.java
similarity index 89%
rename from game/core/src/main/java/net/minecraft/core/world/biome/BiomeCaatinga.java
rename to game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomeCaatinga.java
index 033c563fa..793dba71d 100644
--- a/game/core/src/main/java/net/minecraft/core/world/biome/BiomeCaatinga.java
+++ b/game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomeCaatinga.java
@@ -2,9 +2,10 @@
 // Jad home page: http://www.kpdus.com/jad.html
 // Decompiler options: packimports(3) braces deadcode
 
-package net.minecraft.core.world.biome;
+package net.minecraft.core.world.biome.overworld;
 
 import net.minecraft.core.block.Blocks;
+import net.minecraft.core.world.biome.Biome;
 import net.minecraft.core.world.generate.feature.tree.*;
 import net.minecraft.core.world.generate.feature.WorldFeature;
 
diff --git a/game/core/src/main/java/net/minecraft/core/world/biome/BiomeDesert.java b/game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomeDesert.java
similarity index 84%
rename from game/core/src/main/java/net/minecraft/core/world/biome/BiomeDesert.java
rename to game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomeDesert.java
index b371b04ed..63df9e873 100644
--- a/game/core/src/main/java/net/minecraft/core/world/biome/BiomeDesert.java
+++ b/game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomeDesert.java
@@ -1,8 +1,9 @@
-package net.minecraft.core.world.biome;
+package net.minecraft.core.world.biome.overworld;
 
 import java.util.Random;
 
 import net.minecraft.core.block.Blocks;
+import net.minecraft.core.world.biome.Biome;
 import net.minecraft.core.world.generate.feature.WorldFeature;
 import net.minecraft.core.world.generate.feature.tree.WorldFeatureTreePalm;
 
diff --git a/game/core/src/main/java/net/minecraft/core/world/biome/BiomeForest.java b/game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomeForest.java
similarity index 92%
rename from game/core/src/main/java/net/minecraft/core/world/biome/BiomeForest.java
rename to game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomeForest.java
index 969bf77f6..458603e3e 100644
--- a/game/core/src/main/java/net/minecraft/core/world/biome/BiomeForest.java
+++ b/game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomeForest.java
@@ -1,8 +1,9 @@
-package net.minecraft.core.world.biome;
+package net.minecraft.core.world.biome.overworld;
 
 import net.minecraft.core.entity.animal.MobWolf;
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.entity.SpawnListEntry;
+import net.minecraft.core.world.biome.Biome;
 import net.minecraft.core.world.generate.feature.tree.WorldFeatureTree;
 import net.minecraft.core.world.generate.feature.tree.WorldFeatureTreeFancy;
 import net.minecraft.core.world.generate.feature.WorldFeature;
diff --git a/game/core/src/main/java/net/minecraft/core/world/biome/BiomeOutback.java b/game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomeOutback.java
similarity index 89%
rename from game/core/src/main/java/net/minecraft/core/world/biome/BiomeOutback.java
rename to game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomeOutback.java
index cbb17769a..956cc2e29 100644
--- a/game/core/src/main/java/net/minecraft/core/world/biome/BiomeOutback.java
+++ b/game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomeOutback.java
@@ -1,8 +1,9 @@
-package net.minecraft.core.world.biome;
+package net.minecraft.core.world.biome.overworld;
 
 import net.minecraft.core.entity.monster.MobSpider;
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.entity.SpawnListEntry;
+import net.minecraft.core.world.biome.Biome;
 import net.minecraft.core.world.generate.feature.tree.WorldFeatureTreeEucalyptus;
 import net.minecraft.core.world.generate.feature.WorldFeature;
 
diff --git a/game/core/src/main/java/net/minecraft/core/world/biome/BiomePlains.java b/game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomePlains.java
similarity index 84%
rename from game/core/src/main/java/net/minecraft/core/world/biome/BiomePlains.java
rename to game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomePlains.java
index 0d858c304..5bcc35fee 100644
--- a/game/core/src/main/java/net/minecraft/core/world/biome/BiomePlains.java
+++ b/game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomePlains.java
@@ -1,6 +1,7 @@
-package net.minecraft.core.world.biome;
+package net.minecraft.core.world.biome.overworld;
 
 import net.minecraft.core.block.Blocks;
+import net.minecraft.core.world.biome.Biome;
 import net.minecraft.core.world.generate.feature.WorldFeature;
 import net.minecraft.core.world.generate.feature.tree.WorldFeatureTreeEucalyptus;
 
diff --git a/game/core/src/main/java/net/minecraft/core/world/biome/BiomeRainforest.java b/game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomeRainforest.java
similarity index 94%
rename from game/core/src/main/java/net/minecraft/core/world/biome/BiomeRainforest.java
rename to game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomeRainforest.java
index 713a64d42..10944c5c0 100644
--- a/game/core/src/main/java/net/minecraft/core/world/biome/BiomeRainforest.java
+++ b/game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomeRainforest.java
@@ -1,8 +1,9 @@
-package net.minecraft.core.world.biome;
+package net.minecraft.core.world.biome.overworld;
 
 import java.util.Random;
 
 import net.minecraft.core.block.Blocks;
+import net.minecraft.core.world.biome.Biome;
 import net.minecraft.core.world.generate.feature.WorldFeature;
 import net.minecraft.core.world.generate.feature.tree.WorldFeatureTree;
 import net.minecraft.core.world.generate.feature.tree.WorldFeatureTreeFancyRainforest;
diff --git a/game/core/src/main/java/net/minecraft/core/world/biome/BiomeRetro.java b/game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomeRetro.java
similarity index 92%
rename from game/core/src/main/java/net/minecraft/core/world/biome/BiomeRetro.java
rename to game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomeRetro.java
index 217afe5fb..16284c0e2 100644
--- a/game/core/src/main/java/net/minecraft/core/world/biome/BiomeRetro.java
+++ b/game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomeRetro.java
@@ -1,9 +1,10 @@
-package net.minecraft.core.world.biome;
+package net.minecraft.core.world.biome.overworld;
 
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.entity.SpawnListEntry;
 import net.minecraft.core.entity.animal.*;
 import net.minecraft.core.entity.monster.*;
+import net.minecraft.core.world.biome.Biome;
 import net.minecraft.core.world.generate.feature.WorldFeature;
 import net.minecraft.core.world.generate.feature.tree.WorldFeatureTree;
 import net.minecraft.core.world.generate.feature.tree.WorldFeatureTreeFancy;
@@ -13,7 +14,7 @@ import java.util.Random;
 public class BiomeRetro
     extends Biome
 {
-    protected BiomeRetro(String key)
+    public BiomeRetro(String key)
     {
     	super(key);
         spawnableMonsterList.clear();
diff --git a/game/core/src/main/java/net/minecraft/core/world/biome/BiomeSeasonalForest.java b/game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomeSeasonalForest.java
similarity index 93%
rename from game/core/src/main/java/net/minecraft/core/world/biome/BiomeSeasonalForest.java
rename to game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomeSeasonalForest.java
index 04fae0399..a7dedb093 100644
--- a/game/core/src/main/java/net/minecraft/core/world/biome/BiomeSeasonalForest.java
+++ b/game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomeSeasonalForest.java
@@ -1,8 +1,9 @@
-package net.minecraft.core.world.biome;
+package net.minecraft.core.world.biome.overworld;
 
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.entity.SpawnListEntry;
 import net.minecraft.core.entity.animal.MobWolf;
+import net.minecraft.core.world.biome.Biome;
 import net.minecraft.core.world.generate.feature.tree.WorldFeatureTreeCherry;
 import net.minecraft.core.world.generate.feature.tree.WorldFeatureTree;
 import net.minecraft.core.world.generate.feature.tree.WorldFeatureTreeFancy;
diff --git a/game/core/src/main/java/net/minecraft/core/world/biome/BiomeShrubland.java b/game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomeShrubland.java
similarity index 83%
rename from game/core/src/main/java/net/minecraft/core/world/biome/BiomeShrubland.java
rename to game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomeShrubland.java
index 8d07d2ea1..8ed06dd22 100644
--- a/game/core/src/main/java/net/minecraft/core/world/biome/BiomeShrubland.java
+++ b/game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomeShrubland.java
@@ -1,6 +1,7 @@
-package net.minecraft.core.world.biome;
+package net.minecraft.core.world.biome.overworld;
 
 import net.minecraft.core.block.Blocks;
+import net.minecraft.core.world.biome.Biome;
 import net.minecraft.core.world.generate.feature.tree.WorldFeatureTreeShrub;
 import net.minecraft.core.world.generate.feature.WorldFeature;
 
diff --git a/game/core/src/main/java/net/minecraft/core/world/biome/BiomeSwamp.java b/game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomeSwamp.java
similarity index 83%
rename from game/core/src/main/java/net/minecraft/core/world/biome/BiomeSwamp.java
rename to game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomeSwamp.java
index 0ece7c0cc..783b8737d 100644
--- a/game/core/src/main/java/net/minecraft/core/world/biome/BiomeSwamp.java
+++ b/game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomeSwamp.java
@@ -1,6 +1,7 @@
-package net.minecraft.core.world.biome;
+package net.minecraft.core.world.biome.overworld;
 
 import net.minecraft.core.block.Blocks;
+import net.minecraft.core.world.biome.Biome;
 import net.minecraft.core.world.generate.feature.tree.WorldFeatureTreeShapeSwamp;
 import net.minecraft.core.world.generate.feature.WorldFeature;
 
diff --git a/game/core/src/main/java/net/minecraft/core/world/biome/BiomeTaiga.java b/game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomeTaiga.java
similarity index 90%
rename from game/core/src/main/java/net/minecraft/core/world/biome/BiomeTaiga.java
rename to game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomeTaiga.java
index be0f34a9a..b27ab4794 100644
--- a/game/core/src/main/java/net/minecraft/core/world/biome/BiomeTaiga.java
+++ b/game/core/src/main/java/net/minecraft/core/world/biome/overworld/BiomeTaiga.java
@@ -1,8 +1,9 @@
-package net.minecraft.core.world.biome;
+package net.minecraft.core.world.biome.overworld;
 
 import net.minecraft.core.entity.animal.MobWolf;
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.entity.SpawnListEntry;
+import net.minecraft.core.world.biome.Biome;
 import net.minecraft.core.world.generate.feature.tree.WorldFeatureTreeTaigaBushy;
 import net.minecraft.core.world.generate.feature.tree.WorldFeatureTreeTaigaTall;
 import net.minecraft.core.world.generate.feature.WorldFeature;
diff --git a/game/core/src/main/java/net/minecraft/core/world/biome/BiomeParadise.java b/game/core/src/main/java/net/minecraft/core/world/biome/paradise/BiomeParadise.java
similarity index 84%
rename from game/core/src/main/java/net/minecraft/core/world/biome/BiomeParadise.java
rename to game/core/src/main/java/net/minecraft/core/world/biome/paradise/BiomeParadise.java
index 2a21492b6..dbd136566 100644
--- a/game/core/src/main/java/net/minecraft/core/world/biome/BiomeParadise.java
+++ b/game/core/src/main/java/net/minecraft/core/world/biome/paradise/BiomeParadise.java
@@ -1,7 +1,8 @@
-package net.minecraft.core.world.biome;
+package net.minecraft.core.world.biome.paradise;
 
 import net.minecraft.core.entity.animal.MobChicken;
 import net.minecraft.core.entity.SpawnListEntry;
+import net.minecraft.core.world.biome.Biome;
 
 public class BiomeParadise extends Biome
 {	
diff --git a/game/core/src/main/java/net/minecraft/core/world/biome/provider/BiomeProviderNether.java b/game/core/src/main/java/net/minecraft/core/world/biome/provider/BiomeProviderNether.java
new file mode 100644
index 000000000..db17755be
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/biome/provider/BiomeProviderNether.java
@@ -0,0 +1,277 @@
+package net.minecraft.core.world.biome.provider;
+
+import net.minecraft.core.world.biome.Biome;
+import net.minecraft.core.world.biome.Biomes;
+import net.minecraft.core.world.noise.PerlinSimplexNoise;
+import net.minecraft.core.world.type.WorldTypes;
+
+import java.util.Arrays;
+import java.util.Random;
+
+public final class BiomeProviderNether
+    extends BiomeProvider
+{
+    public static class BiomeOverlapPair {
+        public boolean hasOverlap;
+        public Biome biomeA;
+        public Biome biomeB;
+        public float biomeAPercentage;
+        public float biomeBPercentage;
+    }
+
+    private static final Biome[] biomesOrder = {
+            Biomes.NETHER_BONEYARD,
+            Biomes.NETHER_ASH_PLAINS,
+            Biomes.NETHER_CRYSTAL_FOREST,
+            Biomes.NETHER_SULFUR_POOLS,
+            Biomes.NETHER_NETHER,
+            Biomes.NETHER_VOLCANIC_ISLANDS
+    };
+
+    private final PerlinSimplexNoise temperatureNoise;
+    private final PerlinSimplexNoise fuzzinessNoise;
+
+    private double temperatureXScale = 0.0125;
+    private double temperatureZScale = 0.0125;
+    private double temperatureExponent = 0.25;
+    private double temperatureFuzzPercentage = 0.01;
+
+    private double fuzzinessXScale = 0.25;
+    private double fuzzinessZScale = 0.25;
+    private double fuzzinessExponent = 1.0;
+
+    public BiomeProviderNether(long seed)
+    {
+        this.temperatureNoise = new PerlinSimplexNoise(new Random(seed * 5345L), 4);
+
+        this.fuzzinessNoise = new PerlinSimplexNoise(new Random(seed * 35864L), 2);
+    }
+
+    @Override
+    public Biome[] getBiomes(Biome[] biomes, double[] temperatures, double[] humidities, double[] varieties, int x, int y, int z, int xSize, int ySize, int zSize) {
+        if (biomes == null || biomes.length < xSize * ySize * zSize)
+        {
+            biomes = new Biome[xSize * ySize * zSize];
+        }
+        if (temperatures == null || temperatures.length < xSize * zSize)
+        {
+            temperatures = getTemperatures(temperatures, x, z, xSize, zSize);
+        }
+        if (humidities == null || humidities.length < xSize * zSize)
+        {
+            humidities = getHumidities(humidities, x, z, xSize, zSize);
+        }
+        if (varieties == null || varieties.length < xSize * zSize)
+        {
+            varieties = getVarieties(varieties, x, z, xSize, zSize);
+        }
+
+        for (int dx = 0; dx < xSize; dx++)
+        {
+            for (int dz = 0; dz < zSize; dz++)
+            {
+                double temperature = temperatures[dx * zSize + dz];
+                double humidity = humidities[dx * zSize + dz];
+                double variety = varieties[dx * zSize + dz];
+
+                for (int dy = 0; dy < ySize; dy++)
+                {
+                    double altitude = WorldTypes.NETHER_DEFAULT.getYPercentage((y + dy) << 3);
+                    biomes[dy * xSize * zSize + dz * xSize + dx] = lookupBiome(temperature, humidity, altitude, variety);
+                }
+            }
+        }
+
+        return biomes;
+
+    }
+
+    public double getTemperature(double x, double z) {
+        double[] tnResult = temperatureNoise.getValue(null, x, z, 1, 1, temperatureXScale, temperatureZScale, temperatureExponent);
+        double[] fnResult = fuzzinessNoise.getValue(null, x, z, 1, 1, fuzzinessXScale, fuzzinessZScale, fuzzinessExponent);
+
+        double fuzziness = fnResult[0] * 1.1 + 0.5;
+
+        double fuzzPctg = temperatureFuzzPercentage;
+        double valPctg = 1.0 - fuzzPctg;
+        double temperature = (tnResult[0] * 0.15 + 0.7) * valPctg + fuzziness * fuzzPctg;
+
+        if (temperature < 0.0)
+        {
+            temperature = 0.0;
+        }
+        if (temperature > 1.0)
+        {
+            temperature = 1.0;
+        }
+
+        return temperature;
+    }
+
+    @Override
+    public double[] getTemperatures(double[] temperatures, int x, int z, int xSize, int zSize) {
+        if (temperatures == null || temperatures.length < xSize * zSize)
+        {
+            temperatures = new double[xSize * zSize];
+        }
+
+        double[] tnResult = temperatureNoise.getValue(null, x, z, xSize, zSize, temperatureXScale, temperatureZScale, temperatureExponent);
+        double[] fnResult = fuzzinessNoise.getValue(null, x, z, xSize, zSize, fuzzinessXScale, fuzzinessZScale, fuzzinessExponent);
+
+        for (int dx = 0; dx < xSize; dx++)
+        {
+            for (int dz = 0; dz < zSize; dz++)
+            {
+                double fuzziness = fnResult[dx * zSize + dz] * 1.1 + 0.5;
+
+                double fuzzPctg = temperatureFuzzPercentage;
+                double valPctg = 1.0 - fuzzPctg;
+                double temperature = (tnResult[dx * zSize + dz] * 0.15 + 0.7) * valPctg + fuzziness * fuzzPctg;
+
+                if (temperature < 0.0)
+                {
+                    temperature = 0.0;
+                }
+                if (temperature > 1.0)
+                {
+                    temperature = 1.0;
+                }
+
+                temperatures[dx * zSize + dz] = temperature;
+            }
+        }
+
+        return temperatures;
+    }
+
+    @Override
+    public double[] getHumidities(double[] humidities, int x, int z, int xSize, int zSize) {
+        if (humidities == null || humidities.length < xSize * zSize)
+        {
+            humidities = new double[xSize * zSize];
+        }
+
+        return humidities;
+    }
+
+    @Override
+    public double[] getVarieties(double[] varieties, int x, int z, int xSize, int zSize) {
+        if (varieties == null || varieties.length < xSize * zSize)
+        {
+            varieties = new double[xSize * zSize];
+        }
+
+        return varieties;
+    }
+
+    @Override
+    public double[] getBiomenesses(double[] biomenesses, int x, int y, int z, int xSize, int ySize, int zSize) {
+        if (biomenesses == null || biomenesses.length < xSize * ySize * zSize)
+        {
+            biomenesses = new double[xSize * ySize * zSize];
+        }
+
+        Arrays.fill(biomenesses, 1.0f);
+
+        return biomenesses;
+    }
+
+    @Override
+    public Biome lookupBiome(double temperature, double humidity, double altitude, double variety) {
+        for (int i = 0; i < biomesOrder.length; i++) {
+            if (temperature <= (1.0 / biomesOrder.length) * (i + 1)) {
+                return biomesOrder[i];
+            }
+        }
+
+        return Biomes.NETHER_NETHER;
+    }
+
+    public void getOverlap(double temperature, BiomeOverlapPair toUpdate) {
+        final float overlapAmount = 1f / (float)(biomesOrder.length * 4);
+
+        for (int i = 0; i < biomesOrder.length; i++) {
+            // The *strict* bounds of the biome, as used by this::lookupBiome()
+            final float minStrict = (1.0f / biomesOrder.length) * i;
+            final float maxStrict = (1.0f / biomesOrder.length) * (i + 1);
+            // The outer bounds of the biome, i.e. strict bounds plus overlap amount. If in this range, guaranteed overlap
+            final float minOuter = minStrict - overlapAmount;
+            final float maxOuter = maxStrict + overlapAmount;
+            // The inner bounds of the biome, i.e. strict bounds minus overlap amount. If in this range, guaranteed no overlap
+            final float minInner = minStrict + overlapAmount;
+            final float maxInner = maxStrict - overlapAmount;
+
+            // Edge cases
+            if ((i == 0 && temperature < minInner) ||
+                (i == biomesOrder.length - 1 && temperature >= maxInner)
+            ) {
+                toUpdate.hasOverlap = false;
+                toUpdate.biomeA = biomesOrder[i];
+                toUpdate.biomeAPercentage = 1.0f;
+                return;
+            }
+
+            // Check if in inner bounds
+            if (temperature >= minInner && temperature < maxInner) {
+                // In inner bounds - only one biome is present here
+                toUpdate.hasOverlap = false;
+                toUpdate.biomeA = biomesOrder[i];
+                toUpdate.biomeAPercentage = 1.0f;
+                return;
+            }
+
+            // Check if in outer bounds
+            if (temperature >= minOuter && temperature < maxOuter) {
+                // Check if in min outer bound
+                if (temperature < minInner) {
+                    toUpdate.hasOverlap = true;
+
+                    // Define biomes
+                    toUpdate.biomeA = biomesOrder[i - 1];
+                    toUpdate.biomeB = biomesOrder[i];
+
+                    // Define percentages
+                    final float range = minInner - minOuter;
+                    // minOuter <= temperature < minInner
+                    temperature -= minOuter; // 0 <= temperature < range
+                    temperature /= range; // 0 <= temperature < 1
+
+                    // Biome A goes to 0.0 as temperature goes to 1.0
+                    // Biome B goes to 1.0 as temperature goes to 1.0
+                    toUpdate.biomeAPercentage = 1.0f - (float) temperature;
+                    toUpdate.biomeBPercentage = (float) temperature;
+
+                    return;
+                }
+
+                // Check if in max outer bound
+                if (temperature >= maxInner) {
+                    toUpdate.hasOverlap = true;
+
+                    // Define biomes
+                    toUpdate.biomeA = biomesOrder[i];
+                    toUpdate.biomeB = biomesOrder[i + 1];
+
+                    // Define percentages
+                    final float range = maxOuter - maxInner;
+                    // maxInner <= temperature < maxOuter
+                    temperature -= maxInner; // 0 <= temperature < range
+                    temperature /= range; // 0 <= temperature < 1
+
+                    // Biome A goes to 0.0 as temperature goes to 1.0
+                    // Biome B goes to 1.0 as temperature goes to 1.0
+                    toUpdate.biomeAPercentage = 1.0f - (float) temperature;
+                    toUpdate.biomeBPercentage = (float) temperature;
+
+                    return;
+                }
+            }
+        }
+
+        // We should never get here!
+        toUpdate.hasOverlap = false;
+        toUpdate.biomeA = Biomes.NETHER_NETHER;
+        toUpdate.biomeAPercentage = 1.0f;
+        return;
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/NetherBonesLargeFeature.java b/game/core/src/main/java/net/minecraft/core/world/generate/NetherBonesLargeFeature.java
new file mode 100644
index 000000000..4627c0df3
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/NetherBonesLargeFeature.java
@@ -0,0 +1,176 @@
+package net.minecraft.core.world.generate;
+
+import net.minecraft.core.block.Block;
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.block.tag.BlockTags;
+import net.minecraft.core.util.helper.MathHelper;
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.biome.Biome;
+import net.minecraft.core.world.biome.Biomes;
+import net.minecraft.core.world.chunk.Chunk;
+import net.minecraft.core.world.chunk.ChunkSection;
+import net.minecraft.core.world.generate.chunk.ChunkGeneratorResult;
+
+import java.util.Random;
+
+public class NetherBonesLargeFeature extends LargeFeature {
+    private final int minY;
+    private final int maxY;
+
+    public NetherBonesLargeFeature() {
+        minY = 0;
+        maxY = Chunk.CHUNK_SECTIONS * ChunkSection.SECTION_SIZE_Y;
+    }
+
+    public NetherBonesLargeFeature(int minY, int maxY) {
+        this.minY = minY;
+        this.maxY = maxY;
+    }
+
+    protected void generateHubRoom(long seed, int baseChunkX, int baseChunkZ, ChunkGeneratorResult result, double blockX, double blockY, double blockZ) {
+        generateCave(seed, baseChunkX, baseChunkZ, result, blockX, blockY, blockZ, 1.0F + rand.nextFloat() * 6F, 0.0F, 0.0F, -1, -1, 0.5D);
+    }
+
+    protected void generateCave(long seed, int baseChunkX, int baseChunkZ, ChunkGeneratorResult result, double blockX, double blockY,
+                                double blockZ, float initialRadius, float yRot, float xRot, int startPos, int endPos,
+                                double heightMod) {
+        double chunkMiddleX = baseChunkX * Chunk.CHUNK_SIZE_X + Chunk.CHUNK_SIZE_X / 2;
+        double chunkMiddleZ = baseChunkZ * Chunk.CHUNK_SIZE_Z + Chunk.CHUNK_SIZE_Z / 2;
+        float rotHorOffset = 0.0F;
+        float rotVerOffset = 0.0F;
+        Random random = new Random(seed);
+        if (endPos <= 0) {
+            int maxLength = radiusChunk * 16 - 16;
+            endPos = maxLength - random.nextInt(maxLength / 4);
+        }
+        boolean noBranches = false;
+        if (startPos == -1) {
+            startPos = endPos / 2;
+            noBranches = true;
+        }
+        boolean reversed = random.nextBoolean();
+        int branchPos = random.nextInt(endPos / 2) + endPos / 4;
+        for (; startPos < endPos; startPos++) {
+            double width = 1.5D + (MathHelper.sin((startPos * MathHelper.PI) / endPos) * initialRadius * 1.0F);
+            double height = width * heightMod;
+            float xzScale = MathHelper.cos(xRot);
+            float xOffset = MathHelper.sin(xRot);
+            blockX += xOffset;
+            blockY -= MathHelper.cos(yRot) * xzScale;
+            if (reversed) {
+                blockZ -= MathHelper.sin(yRot) * xzScale;
+            } else {
+                blockZ += MathHelper.sin(yRot) * xzScale;
+            }
+            yRot *= 0.92F;
+            xRot += rotVerOffset * 0.1F;
+            yRot += rotHorOffset * 0.1F;
+            rotVerOffset *= 0.75F;
+            rotHorOffset *= 0.9F;
+//            rotVerOffset += (random.nextFloat() - random.nextFloat()) * random.nextFloat() * 2.0F;
+//            rotHorOffset += (random.nextFloat() - random.nextFloat()) * random.nextFloat() * 4F;
+
+            // Do adjacent branches if necessary
+            if (false && !noBranches && startPos == branchPos && initialRadius > 1.0F) {
+                generateCave(random.nextLong(), baseChunkX, baseChunkZ, result, blockX, blockY, blockZ, random.nextFloat() * 0.5F + 0.5F, yRot - MathHelper.PI / 2F, xRot / 3F, startPos, endPos, 1.0D);
+                generateCave(random.nextLong(), baseChunkX, baseChunkZ, result, blockX, blockY, blockZ, random.nextFloat() * 0.5F + 0.5F, yRot + MathHelper.PI / 2F, xRot / 3F, startPos, endPos, 1.0D);
+                return;
+            }
+
+            // Check if we've gone far enough, and stop if so
+            double dxFromMiddle = blockX - chunkMiddleX;
+            double dzFromMiddle = blockZ - chunkMiddleZ;
+            double length = endPos - startPos;
+            double maxRadius = initialRadius + 2.0F + 16F;
+            if ((dxFromMiddle * dxFromMiddle + dzFromMiddle * dzFromMiddle) - length * length > maxRadius * maxRadius) {
+                return;
+            }
+            if (blockX < chunkMiddleX - Chunk.CHUNK_SIZE_X - width * 2D || blockZ < chunkMiddleZ - Chunk.CHUNK_SIZE_Z - width * 2D || blockX > chunkMiddleX + Chunk.CHUNK_SIZE_X + width * 2D || blockZ > chunkMiddleZ + Chunk.CHUNK_SIZE_Z + width * 2D) {
+                continue;
+            }
+
+            // Determine sizes of current section
+            int minX = MathHelper.floor(blockX - width) - baseChunkX * Chunk.CHUNK_SIZE_X - 1;
+            int maxX = (MathHelper.floor(blockX + width) - baseChunkX * Chunk.CHUNK_SIZE_X) + 1;
+            int minY = MathHelper.floor(blockY - height) - 1;
+            int maxY = MathHelper.floor(blockY + height) + 1;
+            int minZ = MathHelper.floor(blockZ - width) - baseChunkZ * Chunk.CHUNK_SIZE_Z - 1;
+            int maxZ = (MathHelper.floor(blockZ + width) - baseChunkZ * Chunk.CHUNK_SIZE_Z) + 1;
+            if (minX < 0) {
+                minX = 0;
+            }
+            if (maxX > Chunk.CHUNK_SIZE_X) {
+                maxX = Chunk.CHUNK_SIZE_X;
+            }
+            if (minY < this.minY + 1) {
+                minY = this.minY + 1;
+            }
+            if (maxY > this.maxY - (ChunkSection.SECTION_SIZE_Y / 2)) {
+                maxY = this.maxY - (ChunkSection.SECTION_SIZE_Y / 2);
+            }
+            if (minZ < 0) {
+                minZ = 0;
+            }
+            if (maxZ > Chunk.CHUNK_SIZE_Z) {
+                maxZ = Chunk.CHUNK_SIZE_Z;
+            }
+
+            // Place blocks in section
+            for (int x = minX; x < maxX; x++) {
+                double xPercentage = (((x + baseChunkX * Chunk.CHUNK_SIZE_X) + 0.5D) - blockX) / width;
+                zLabel:
+                for (int z = minZ; z < maxZ; z++) {
+                    double zPercentage = (((z + baseChunkZ * Chunk.CHUNK_SIZE_Z) + 0.5D) - blockZ) / width;
+                    int yIndex = maxY;
+                    if (xPercentage * xPercentage + zPercentage * zPercentage >= 1.0D) {
+                        continue;
+                    }
+                    int y = maxY - 1;
+                    do {
+                        if (y < minY) {
+                            continue zLabel;
+                        }
+                        double yPercentage = ((y + 0.5D) - blockY) / height;
+                        if (yPercentage > -0.7D && xPercentage * xPercentage + yPercentage * yPercentage + zPercentage * zPercentage < 1.0D) {
+                            int blockId = result.getBlock(x, yIndex, z);
+                            if (blockId == 0) {
+                                result.setBlock(x, yIndex, z, Blocks.BONESHALE.id());
+                            }
+                        }
+                        yIndex--;
+                        y--;
+                    } while (true);
+                }
+            }
+
+            if (noBranches) {
+                break;
+            }
+        }
+    }
+
+    @Override
+    protected void doGeneration(World world, int chunkX, int chunkZ, int baseChunkX, int baseChunkZ, ChunkGeneratorResult result) {
+        if (world.getBlockBiome(chunkX * Chunk.CHUNK_SIZE_X, 0, chunkZ * Chunk.CHUNK_SIZE_Z) != Biomes.NETHER_BONEYARD) {
+            return;
+        }
+
+        int cavesToGenerate = rand.nextInt(rand.nextInt(rand.nextInt(10) + 1) + 1);
+//        if (rand.nextInt(12) != 0) {
+//            cavesToGenerate = 0;
+//        }
+
+        final int yRange = maxY - minY;
+
+        for (int i = 0; i < cavesToGenerate; i++) {
+            double blockX = chunkX * Chunk.CHUNK_SIZE_X + rand.nextInt(Chunk.CHUNK_SIZE_X);
+            double blockY = minY + rand.nextInt(rand.nextInt(yRange - (ChunkSection.SECTION_SIZE_Y / 2)) + (ChunkSection.SECTION_SIZE_Y / 2));
+            double blockZ = chunkZ * Chunk.CHUNK_SIZE_Z + rand.nextInt(Chunk.CHUNK_SIZE_Z);
+
+            float yRot = rand.nextFloat() * MathHelper.PI * 2.0F; // 0 - 2Pi
+            float xRot = ((rand.nextFloat() - 0.5F) * 2.0F) / 8F; // -0.125 - 0.125
+            float initialRadius = rand.nextFloat() + rand.nextFloat(); // 0 - 3
+            generateCave(rand.nextLong(), baseChunkX, baseChunkZ, result, blockX, blockY, blockZ, initialRadius, yRot, xRot, 0, 0, 1.0f);
+        }
+    }
+}
\ No newline at end of file
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/ChunkGenerator.java b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/ChunkGenerator.java
index d2eb8aa71..404380849 100644
--- a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/ChunkGenerator.java
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/ChunkGenerator.java
@@ -1,11 +1,14 @@
 package net.minecraft.core.world.generate.chunk;
 
+import com.mojang.logging.CategorizedLogger;
+import com.mojang.logging.LogUtils;
 import net.minecraft.core.data.registry.Registries;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.biome.Biome;
 import net.minecraft.core.world.chunk.Chunk;
 import net.minecraft.core.world.chunk.ChunkSection;
 import net.minecraft.core.world.generate.feature.WorldFeature;
+import org.slf4j.Logger;
 
 import static net.minecraft.core.world.chunk.Chunk.CHUNK_SIZE_X;
 import static net.minecraft.core.world.chunk.Chunk.CHUNK_SIZE_Z;
@@ -18,6 +21,8 @@ import static net.minecraft.core.world.chunk.ChunkSection.SECTION_SIZE_Y;
  */
 public abstract class ChunkGenerator
 {
+    private static final CategorizedLogger LOGGER = LogUtils.getLogger();
+
     protected final World world;
     private final ChunkDecorator decorator;
 
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/TerrainGeneratorLerp.java b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/TerrainGeneratorLerp.java
index 11954ff36..95f030f9d 100644
--- a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/TerrainGeneratorLerp.java
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/TerrainGeneratorLerp.java
@@ -18,7 +18,7 @@ public abstract class TerrainGeneratorLerp
         this.rand = new Random();
     }
 
-    protected abstract int getBlockAt(int x, int y, int z, double density);
+    protected abstract int getBlockAt(Chunk chunk, int x, int y, int z, double density);
 
     @Override
     public ChunkGeneratorResult generateTerrain(Chunk chunk, double[] densityMap)
@@ -127,7 +127,7 @@ public abstract class TerrainGeneratorLerp
                                 int blockY = yInBlob + blobY * yBlocksPerBlob + world.worldType.getMinY();
                                 int blockZ = zInBlob + blobZ * zBlocksPerBlob;
 
-                                int id = getBlockAt(blockX, blockY, blockZ, zLerpDensity);
+                                int id = getBlockAt(chunk, blockX, blockY, blockZ, zLerpDensity);
 
                                 result.setBlock(blockX, blockY, blockZ, id);
 
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/ChunkDecoratorNether.java b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/ChunkDecoratorNether.java
index 191b5727d..4122b4bc5 100644
--- a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/ChunkDecoratorNether.java
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/ChunkDecoratorNether.java
@@ -1,96 +1,203 @@
-package net.minecraft.core.world.generate.chunk.perlin.nether;
-
-import net.minecraft.core.block.BlockLogicSand;
-import net.minecraft.core.block.Blocks;
-import net.minecraft.core.world.World;
-import net.minecraft.core.world.chunk.Chunk;
-import net.minecraft.core.world.generate.chunk.ChunkDecorator;
-import net.minecraft.core.world.generate.feature.*;
-
-import java.util.Random;
-
-public class ChunkDecoratorNether
-    implements ChunkDecorator
-{
-    private final World world;
-
-    public ChunkDecoratorNether(World world)
-    {
-        this.world = world;
-    }
-
-    @Override
-    public void decorate(Chunk chunk)
-    {
-        int chunkX = chunk.xPosition;
-        int chunkZ = chunk.zPosition;
-
-        int minY = world.getWorldType().getMinY();
-        int maxY = world.getWorldType().getMaxY();
-        int rangeY = (maxY + 1) - minY;
-
-        Random rand = new Random((long) chunkX * 0x4f9939f508L + (long) chunkZ * 0x1ef1565bd5L);
-
-        BlockLogicSand.fallInstantly = true;
-        int x = chunkX * 16;
-        int z = chunkZ * 16;
-        for(int i = 0; i < 8; i++)
-        {
-            int xf = x + rand.nextInt(16) + 8;
-            int yf = minY + rand.nextInt(rangeY - 8) + 4;
-            int zf = z + rand.nextInt(16) + 8;
-            new WorldFeatureNetherLava(Blocks.FLUID_LAVA_FLOWING.id()).place(world, rand, xf, yf, zf);
-        }
-
-        for(int i = 0; i < 10; i++)
-        {
-            int xf = x + rand.nextInt(16);
-            int yf = minY + rand.nextInt(rangeY - 8) + 4;
-            int zf = z + rand.nextInt(16);
-            new WorldFeatureOre(Blocks.ORE_NETHERCOAL_NETHERRACK.id(), 12).place(world, rand, xf, yf, zf);
-        }
-
-        int max = rand.nextInt(rand.nextInt(10) + 1);
-        for(int i = 0; i < max; i++)
-        {
-            int xf = x + rand.nextInt(16) + 8;
-            int yf = minY + rand.nextInt(rangeY - 8) + 4;
-            int zf = z + rand.nextInt(16) + 8;
-            new WorldFeatureFire().place(world, rand, xf, yf, zf);
-        }
-
-        max = rand.nextInt(rand.nextInt(10) + 1);
-        for(int i = 0; i < max; i++)
-        {
-            int xf = x + rand.nextInt(16) + 8;
-            int yf = minY + rand.nextInt(rangeY - 8) + 4;
-            int zf = z + rand.nextInt(16) + 8;
-            new WorldFeatureGlowstoneA().place(world, rand, xf, yf, zf);
-        }
-
-        for(int i = 0; i < 10; i++)
-        {
-            int xf = x + rand.nextInt(16) + 8;
-            int yf = minY + rand.nextInt(rangeY - 8) + 4;
-            int zf = z + rand.nextInt(16) + 8;
-            new WorldFeatureGlowstoneB().place(world, rand, xf, yf, zf);
-        }
-
-        if (rand.nextInt(8) == 0) {
-            int xf = x + rand.nextInt(16) + 8;
-            int yf = minY + rand.nextInt(rangeY - 16) + 8;
-            int zf = z + rand.nextInt(16) + 8;
-            new WorldFeatureLake(Blocks.FLUID_LAVA_STILL.id()).place(world, rand, xf, yf, zf);
-        }
-
-        for (int i = 0; i < /*20*/80; i++) {
-            int xf = x + rand.nextInt(16);
-            int yf = minY + rand.nextInt(rangeY);
-            int zf = z + rand.nextInt(16);
-            new WorldFeaturePumice(32).place(world, rand, xf, yf, zf);
-        }
-
-        BlockLogicSand.fallInstantly = false;
-
-    }
-}
+package net.minecraft.core.world.generate.chunk.perlin.nether;
+
+import net.minecraft.core.block.BlockLogicSand;
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.world.World;
+import net.minecraft.core.world.biome.Biome;
+import net.minecraft.core.world.biome.Biomes;
+import net.minecraft.core.world.chunk.Chunk;
+import net.minecraft.core.world.generate.chunk.ChunkDecorator;
+import net.minecraft.core.world.generate.feature.*;
+import net.minecraft.core.world.noise.PerlinNoise;
+
+import java.util.Random;
+
+public class ChunkDecoratorNether
+    implements ChunkDecorator
+{
+    private final World world;
+    private final PerlinNoise crystalDensityNoise;
+
+    public ChunkDecoratorNether(World world)
+    {
+        this.world = world;
+        crystalDensityNoise = new PerlinNoise(world.getRandomSeed(), 6, 74);
+    }
+
+    @Override
+    public void decorate(Chunk chunk)
+    {
+        int chunkX = chunk.xPosition;
+        int chunkZ = chunk.zPosition;
+
+        int minY = world.getWorldType().getMinY();
+        int maxY = world.getWorldType().getMaxY();
+        int rangeY = (maxY + 1) - minY;
+
+        Random rand = new Random((long) chunkX * 0x4f9939f508L + (long) chunkZ * 0x1ef1565bd5L);
+
+        BlockLogicSand.fallInstantly = true;
+        int x = chunkX * 16;
+        int z = chunkZ * 16;
+        Biome biome = world.getBlockBiome(x + 16, minY, z + 16);
+
+        if (biome == Biomes.NETHER_CRYSTAL_FOREST) {
+            double noiseScale = 0.5;
+
+            int density = (int) ((crystalDensityNoise.get(x * noiseScale, z * noiseScale) - 6D + 36D) / 6D);
+
+            for (int i = 0; i < density - 5; i++) {
+                int xf = x + rand.nextInt(16) + 8;
+                int yf = world.getWorldType().getOceanY() + rand.nextInt(5);
+                int zf = z + rand.nextInt(16) + 8;
+                boolean isCeiling = rand.nextInt(5) == 0;
+                new WorldFeatureCrystalChunk(isCeiling).place(world, rand, xf, yf, zf);
+            }
+
+
+            for (int i = 0; i < density - 5; i++) {
+                int xf = x + rand.nextInt(16) + 8;
+                int yf = world.getWorldType().getOceanY() + rand.nextInt(rangeY - 8) + 4;
+                int zf = z + rand.nextInt(16) + 8;
+                new WorldFeatureCrystalChunk(false, 15, 30, true).place(world, rand, xf, yf, zf);
+
+            }
+
+            for (int i = 0; i < 40; i++) {
+                int xf = x + rand.nextInt(16) + 8;
+                int yf = world.getWorldType().getOceanY() + rand.nextInt(rangeY - 8) + 4;
+                int zf = z + rand.nextInt(16) + 8;
+                (new WorldFeatureDeadBush(Blocks.CRYSTAL_STALK.id())).place(world, rand, xf, yf, zf);
+            }
+
+            for(int l18 = 0; l18 < 50; l18++)
+            {
+                int l21 = x + rand.nextInt(16) + 8;
+                int k23 = minY + rand.nextInt(rand.nextInt(rangeY - (rangeY / 16)) + (rangeY / 16));
+                int l24 = z + rand.nextInt(16) + 8;
+                (new WorldFeatureLiquid(Blocks.FLUID_WATER_FLOWING.id())).place(world, rand, l21, k23, l24);
+            }
+
+        }
+//
+//        if (biome == Biomes.NETHER_SULFUR_POOLS) {
+//            for (int i = 0; i < 2; i++) {
+//                int xf = x + rand.nextInt(16) + 8;
+//                int yf = world.getWorldType().getOceanY() + rand.nextInt(rangeY - 8) + 4;
+//                int zf = z + rand.nextInt(16) + 8;
+//                new WorldFeaturePillar(Blocks.COBBLE_NETHERRACK.id, true).place(world, rand, xf, yf, zf);
+//            }
+//        }
+
+        if (biome == Biomes.NETHER_ASH_PLAINS) {
+            if (rand.nextInt(2) == 0) {
+                int xf = x + rand.nextInt(16) + 8;
+                int yf = world.getWorldType().getOceanY() + rand.nextInt(rangeY - 8) + 4;
+                int zf = z + rand.nextInt(16) + 8;
+                new WorldFeaturePillar(Blocks.SLATE.id(), false).place(world, rand, xf, yf, zf);
+            }
+        }
+
+        if (biome != Biomes.NETHER_ASH_PLAINS && biome != Biomes.NETHER_BONEYARD) {
+            for(int i = 0; i < 8; i++)
+            {
+                int xf = x + rand.nextInt(16) + 8;
+                int yf = rangeY / 2 + rand.nextInt(rangeY - 8) + 4;
+                int zf = z + rand.nextInt(16) + 8;
+                (new WorldFeatureNetherLava(Blocks.FLUID_LAVA_FLOWING.id())).place(world, rand, xf, yf, zf);
+            }
+        }
+
+        for(int i = 0; i < 10; i++)
+        {
+            int xf = x + rand.nextInt(16);
+            int yf = rangeY / 2 + rand.nextInt(rangeY - 8) + 4;
+            int zf = z + rand.nextInt(16);
+            new WorldFeatureOre(Blocks.ORE_NETHERCOAL_NETHERRACK.id(), 12).place(world, rand, xf, yf, zf);
+        }
+
+        int max;
+        if (biome != Biomes.NETHER_ASH_PLAINS && biome != Biomes.NETHER_BONEYARD) {
+            max = rand.nextInt(rand.nextInt(10) + 1);
+            for(int i = 0; i < max; i++)
+            {
+                int xf = x + rand.nextInt(16) + 8;
+                int yf = world.getWorldType().getOceanY() + rand.nextInt(rangeY - 8) + 4;
+                int zf = z + rand.nextInt(16) + 8;
+                (new WorldFeatureFire()).place(world, rand, xf, yf, zf);
+            }
+        }
+
+        if (biome == Biomes.NETHER_BONEYARD) {
+            max = rand.nextInt(20) + 5;
+            for(int i = 0; i < max; i++)
+            {
+                int xf = x + rand.nextInt(16) + 8;
+                int yf = world.getWorldType().getOceanY() + rand.nextInt(rangeY - 8) + 4;
+                int zf = z + rand.nextInt(16) + 8;
+                (new WorldFeatureColdFire()).place(world, rand, xf, yf, zf);
+            }
+        }
+
+        max = rand.nextInt(rand.nextInt(10) + 1);
+        for(int i = 0; i < max; i++)
+        {
+            int xf = x + rand.nextInt(16) + 8;
+            int yf = world.getWorldType().getOceanY() + rand.nextInt(rangeY - 8) + 4;
+            int zf = z + rand.nextInt(16) + 8;
+            new WorldFeatureGlowstoneA().place(world, rand, xf, yf, zf);
+        }
+
+        for(int i = 0; i < 10; i++)
+        {
+            int xf = x + rand.nextInt(16) + 8;
+            int yf = world.getWorldType().getOceanY() + rand.nextInt(rangeY - 8) + 4;
+            int zf = z + rand.nextInt(16) + 8;
+            new WorldFeatureGlowstoneB().place(world, rand, xf, yf, zf);
+        }
+
+        if (biome != Biomes.NETHER_ASH_PLAINS && biome != Biomes.NETHER_BONEYARD) {
+            if (rand.nextInt(8) == 0) {
+                int xf = x + rand.nextInt(16) + 8;
+                int yf = rangeY / 2 + rand.nextInt(rangeY - 16) + 8;
+                int zf = z + rand.nextInt(16) + 8;
+                (new WorldFeatureLake(Blocks.FLUID_LAVA_STILL.id())).place(world, rand, xf, yf, zf);
+            }
+
+            for (int i = 0; i < /*20*/80; i++) {
+                int xf = x + rand.nextInt(16);
+                int yf = rangeY / 2 + rand.nextInt(rangeY);
+                int zf = z + rand.nextInt(16);
+                (new WorldFeaturePumice(32)).place(world, rand, xf, yf, zf);
+            }
+        }
+
+        int oceanY = world.getWorldType().getOceanY();
+        int midY = oceanY + (world.getWorldType().getMaxY() - oceanY) / 2;
+        for(int dx = x + 8; dx < x + 8 + 16; dx++)
+        {
+            for(int dz = z + 8; dz < z + 8 + 16; dz++)
+            {
+                int dy = midY;
+                while (world.getBlockId(dx, dy, dz) == 0) {
+                    dy--;
+                }
+                dy++;
+                Biome localBiome = world.getBlockBiome(dx, dy, dz);
+
+                if ((localBiome == Biomes.NETHER_ASH_PLAINS || localBiome == Biomes.NETHER_BONEYARD) && dy > 0 && dy < world.getHeightBlocks())
+                {
+                    if (world.isAirBlock(dx, dy, dz) && world.getBlockMaterial(dx, dy - 1, dz).blocksMotion() && world.getBlockId(dx, dy - 1, dz) != Blocks.OBSIDIAN.id())
+                    {
+                        world.setBlockWithNotify(dx, dy, dz, Blocks.LAYER_ASH.id());
+                    }
+                }
+            }
+
+        }
+
+
+        BlockLogicSand.fallInstantly = false;
+
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/ChunkGeneratorNether.java b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/ChunkGeneratorNether.java
index 25735df83..533a57c14 100644
--- a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/ChunkGeneratorNether.java
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/ChunkGeneratorNether.java
@@ -3,14 +3,24 @@ package net.minecraft.core.world.generate.chunk.perlin.nether;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.generate.CavesLargeFeature;
 import net.minecraft.core.world.generate.LargeFeature;
+import net.minecraft.core.world.generate.NetherBonesLargeFeature;
 import net.minecraft.core.world.generate.WarrensCavesLargeFeature;
 import net.minecraft.core.world.generate.chunk.perlin.ChunkGeneratorPerlin;
 
 public class ChunkGeneratorNether
-    extends ChunkGeneratorPerlin
-{
-    public ChunkGeneratorNether(World world)
-    {
-        super(world, new ChunkDecoratorNether(world), new TerrainGeneratorNether(world), new SurfaceGeneratorNether(world), new LargeFeature[] { new CavesLargeFeature(128, 256), new WarrensCavesLargeFeature(0, 128) });
+    extends ChunkGeneratorPerlin {
+    public ChunkGeneratorNether(World world) {
+        super(
+            world,
+            new ChunkDecoratorNether(world),
+            new TerrainGeneratorNether(world),
+            new SurfaceGeneratorNether(world),
+            new LargeFeature[]
+                {
+                    new CavesLargeFeature(128, 256),
+                    new WarrensCavesLargeFeature(0, 128),
+                    new NetherBonesLargeFeature(128, 256)
+                }
+        );
     }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/DensityGeneratorNether.java b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/DensityGeneratorNether.java
index c455882a6..a709f569e 100644
--- a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/DensityGeneratorNether.java
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/DensityGeneratorNether.java
@@ -1,18 +1,44 @@
 package net.minecraft.core.world.generate.chunk.perlin.nether;
 
+import net.minecraft.core.util.helper.MathHelper;
 import net.minecraft.core.world.World;
+import net.minecraft.core.world.biome.Biome;
+import net.minecraft.core.world.biome.Biomes;
+import net.minecraft.core.world.biome.provider.BiomeProviderNether;
 import net.minecraft.core.world.chunk.Chunk;
 import net.minecraft.core.world.generate.chunk.perlin.DensityGenerator;
+import net.minecraft.core.world.noise.BasePerlinNoise;
 import net.minecraft.core.world.noise.PerlinNoise;
+import net.minecraft.core.world.noise.PerlinSimplexNoise;
+import net.minecraft.core.world.type.WorldType;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
 
 public class DensityGeneratorNether
     implements DensityGenerator
 {
+    private interface BiomeDensityGenerator {
+        double[] generateDensityMap(Chunk chunk, int terrainHeight, int xSize, int ySize, int zSize, int x, int y, int z);
+    }
+
     private final World world;
 
     private final PerlinNoise minLimitNoise;
     private final PerlinNoise maxLimitNoise;
     private final PerlinNoise mainNoise;
+    private final PerlinNoise scaleNoise;
+    private final PerlinNoise depthNoise;
+
+    private final PerlinSimplexNoise poolNoiseOver;
+    private final PerlinSimplexNoise poolNoiseUnder;
+
+    private final PerlinSimplexNoise ravineNoise2;
+
+    private final PerlinSimplexNoise islandNoise;
+
+    private final Map<Biome, BiomeDensityGenerator> densityGeneratorMap = new HashMap<>();
 
     public DensityGeneratorNether(World world)
     {
@@ -21,21 +47,96 @@ public class DensityGeneratorNether
         this.minLimitNoise = new PerlinNoise(world.getRandomSeed(), 16, 0);
         this.maxLimitNoise = new PerlinNoise(world.getRandomSeed(), 16, 16);
         this.mainNoise = new PerlinNoise(world.getRandomSeed(), 8, 32);
+        this.scaleNoise = new PerlinNoise(world.getRandomSeed(), 10, 48);
+        this.depthNoise = new PerlinNoise(world.getRandomSeed(), 16, 58);
+
+        Random rand = new Random(world.getRandomSeed());
+
+        this.islandNoise = new PerlinSimplexNoise(rand, 6);
+        this.ravineNoise2 = new PerlinSimplexNoise(rand, 4);
+
+        this.poolNoiseOver = new PerlinSimplexNoise(rand, 4);
+        this.poolNoiseUnder = new PerlinSimplexNoise(rand, 1);
+
+        densityGeneratorMap.put(Biomes.NETHER_NETHER, this::generateDensityMapBiomeNether);
+        densityGeneratorMap.put(Biomes.NETHER_VOLCANIC_ISLANDS, this::generateDensityMapBiomeVolcanicIslands);
+        densityGeneratorMap.put(Biomes.NETHER_SULFUR_POOLS, this::generateDensityMapBiomeSulphurPools);
+        densityGeneratorMap.put(Biomes.NETHER_CRYSTAL_FOREST, this::generateDensityMapOcean);
+        densityGeneratorMap.put(Biomes.NETHER_ASH_PLAINS, this::generateDensityMapBiomeTest);
+        densityGeneratorMap.put(Biomes.NETHER_BONEYARD, this::generateDensityMapBiomeTest);
     }
 
     @Override
     public double[] generateDensityMap(Chunk chunk)
     {
-        int terrainHeight = (world.getWorldType().getMaxY() + 1) - world.getWorldType().getMinY();
-        int halfTerrainHeight = terrainHeight / 2;
+        WorldType worldType = world.getWorldType();
+
+        int terrainHeight = ((worldType.getMaxY() + 1) - worldType.getMinY());
         int xSize = 4 + 1;
         int ySize = (terrainHeight / 8) + 1;
-        int halfYSize = (halfTerrainHeight / 8) + 1;
         int zSize = 4 + 1;
         int x = chunk.xPosition * 4;
         int y = 0;
         int z = chunk.zPosition * 4;
 
+        int halfYSize = ((terrainHeight / 2) / 8) + 1;
+
+        double[] densityMapArray = new double[xSize * ySize * zSize];
+        Map<Biome, double[]> perBiomeDensityMaps = new HashMap<>();
+
+        BiomeProviderNether biomeProvider = (BiomeProviderNether) world.getBiomeProvider();
+        BiomeProviderNether.BiomeOverlapPair[] overlapPairs = new BiomeProviderNether.BiomeOverlapPair[xSize * zSize];
+
+        // Cache density maps for biome(s)
+        int xSizeScale = 16 / xSize;
+        for (int dx = 0; dx < xSize; dx++) {
+            int ix = dx * xSizeScale + xSizeScale / 2;
+            for (int dz = 0; dz < zSize; dz++) {
+                int iz = dz * xSizeScale + xSizeScale / 2;
+                double temperature = chunk.temperature[ix * 16 + iz];
+                BiomeProviderNether.BiomeOverlapPair overlapPair = overlapPairs[dx * xSize + dz] = new BiomeProviderNether.BiomeOverlapPair();
+                biomeProvider.getOverlap(temperature, overlapPair);
+
+                if (!perBiomeDensityMaps.containsKey(overlapPair.biomeA)) {
+                    perBiomeDensityMaps.put(overlapPair.biomeA, densityGeneratorMap.get(overlapPair.biomeA).generateDensityMap(chunk, terrainHeight / 2, xSize, halfYSize, zSize, x, y, z));
+                }
+                if (overlapPair.hasOverlap) {
+                    if (!perBiomeDensityMaps.containsKey(overlapPair.biomeB)) {
+                        perBiomeDensityMaps.put(overlapPair.biomeB, densityGeneratorMap.get(overlapPair.biomeB).generateDensityMap(chunk, terrainHeight / 2, xSize, halfYSize, zSize, x, y, z));
+                    }
+                }
+            }
+        }
+
+        // Merge density maps
+        for (int dx = 0; dx < xSize; dx++) {
+            for (int dz = 0; dz < zSize; dz++) {
+                BiomeProviderNether.BiomeOverlapPair overlapPair = overlapPairs[dx * xSize + dz];
+                double[] densityMapA = perBiomeDensityMaps.get(overlapPair.biomeA);
+                double[] densityMapB = null;
+                if (overlapPair.hasOverlap) {
+                    densityMapB = perBiomeDensityMaps.get(overlapPair.biomeB);
+                }
+
+                if (densityMapB != null) {
+                    for (int dy = 0; dy < halfYSize; dy++) {
+                        double densityA = densityMapA[(dx * zSize * halfYSize) + (dz * halfYSize) + dy];
+                        double densityB = densityMapB[(dx * zSize * halfYSize) + (dz * halfYSize) + dy];
+                        densityMapArray[(dx * zSize * ySize) + (dz * ySize) + (dy + halfYSize - 1)] = MathHelper.lerp(densityA, densityB, overlapPair.biomeBPercentage);
+                    }
+                } else {
+                    for (int dy = 0; dy < halfYSize; dy++) {
+                        double densityA = densityMapA[(dx * zSize * halfYSize) + (dz * halfYSize) + dy];
+                        densityMapArray[(dx * zSize * ySize) + (dz * ySize) + (dy + halfYSize - 1)] = densityA;
+                    }
+                }
+            }
+        }
+
+        return densityMapArray;
+    }
+
+    private double[] generateDensityMapBiomeNether(Chunk chunk, int terrainHeight, int xSize, int ySize, int zSize, int x, int y, int z) {
         double[] densityMapArray = new double[xSize * ySize * zSize];
 
         final double mainNoiseScaleX = 80.0;
@@ -48,17 +149,18 @@ public class DensityGeneratorNether
         final double upperLimitScale = 512.0;
         final double lowerLimitScale = 512.0;
 
-        double[] mainNoiseArray = mainNoise.get(null, x, y, z, xSize, halfYSize, zSize, coordScale / mainNoiseScaleX, heightScale / mainNoiseScaleY, coordScale / mainNoiseScaleZ);
-        double[] minLimitArray = minLimitNoise.get(null, x, y, z, xSize, halfYSize, zSize, coordScale, heightScale, coordScale);
-        double[] maxLimitArray = maxLimitNoise.get(null, x, y, z, xSize, halfYSize, zSize, coordScale, heightScale, coordScale);
-        double[] densityOffsetArray = new double[halfYSize];
-        for(int dy = 0; dy < halfYSize; dy++)
+        double[] mainNoiseArray = mainNoise.get(null, x, y, z, xSize, ySize, zSize, coordScale / mainNoiseScaleX, heightScale / mainNoiseScaleY, coordScale / mainNoiseScaleZ);
+        double[] minLimitArray = minLimitNoise.get(null, x, y, z, xSize, ySize, zSize, coordScale, heightScale, coordScale);
+        double[] maxLimitArray = maxLimitNoise.get(null, x, y, z, xSize, ySize, zSize, coordScale, heightScale, coordScale);
+        int mainIndex = 0;
+        double[] densityOffsetArray = new double[ySize];
+        for(int dy = 0; dy < ySize; dy++)
         {
-            densityOffsetArray[dy] = Math.cos(((double)dy * Math.PI * 6D) / (double)halfYSize) * 2D;
+            densityOffsetArray[dy] = Math.cos(((double)dy * Math.PI * 6D) / (double)ySize) * 2D;
             double densityOffset = dy;
-            if(dy > halfYSize / 2)
+            if(dy > ySize / 2)
             {
-                densityOffset = halfYSize - 1 - dy;
+                densityOffset = ySize - 1 - dy;
             }
             if(densityOffset < 4D)
             {
@@ -71,16 +173,13 @@ public class DensityGeneratorNether
         {
             for(int dz = 0; dz < zSize; dz++)
             {
-                for(int dy = 0; dy < halfYSize; dy++)
+                for(int dy = 0; dy < ySize; dy++)
                 {
-                    int halfIndex = (dx * halfYSize * zSize) + (dz * halfYSize) + dy;
-                    int offsetIndex = (dx * ySize * zSize) + (dz * ySize) + (dy + halfYSize - 1);
-
                     double density;
                     double densityOffset = densityOffsetArray[dy];
-                    double minDensity = minLimitArray[halfIndex] / upperLimitScale;
-                    double maxDensity = maxLimitArray[halfIndex] / lowerLimitScale;
-                    double mainDensity = (mainNoiseArray[halfIndex] / 10D + 1.0D) / 2D;
+                    double minDensity = minLimitArray[mainIndex] / upperLimitScale;
+                    double maxDensity = maxLimitArray[mainIndex] / lowerLimitScale;
+                    double mainDensity = (mainNoiseArray[mainIndex] / 10D + 1.0D) / 2D;
 
                     // Constrain density
                     if(mainDensity < 0.0D)
@@ -97,13 +196,14 @@ public class DensityGeneratorNether
                     }
                     density -= densityOffset;
 
-                    if(dy > halfYSize - 4)
+                    if(dy > ySize - 4)
                     {
-                        double d11 = (float)(dy - (halfYSize - 4)) / 3F;
-                        density = density * (1.0D - d11) + -10D * d11;
+                        double densityMod = (float)(dy - (ySize - 4)) / 3F;
+                        density = density * (1.0D - densityMod) + -10D * densityMod;
                     }
 
-                    densityMapArray[offsetIndex] = density;
+                    densityMapArray[mainIndex] = density;
+                    mainIndex++;
                 }
 
             }
@@ -112,4 +212,668 @@ public class DensityGeneratorNether
 
         return densityMapArray;
     }
+
+    private double[] generateDensityMapFractured(Chunk chunk, int terrainHeight, int xSize, int ySize, int zSize, int x, int y, int z) {
+        double[] densityMapArray = new double[xSize * ySize * zSize];
+
+        final double mainNoiseScaleX = 80;
+        final double mainNoiseScaleY = 160;
+        final double mainNoiseScaleZ = 80;
+
+        final double scaleNoiseScaleX = 1.121;
+        final double scaleNoiseScaleZ = 1.121;
+
+        final double depthNoiseScaleX = 200;
+        final double depthNoiseScaleZ = 200;
+
+        final double depthBaseSize = 3d;
+
+        final double coordScale = 684.412D;
+        final double heightScale = 684.412D;
+
+        final double heightStretch = 12;
+
+        final double upperLimitScale = 256;
+        final double lowerLimitScale = 512;
+
+        double ravineNoiseScale = 0.03D;
+
+        double[] scaleArray = scaleNoise.get(null, x, z, xSize, zSize, scaleNoiseScaleX, scaleNoiseScaleZ);
+        double[] depthArray = depthNoise.get(null, x, z, xSize, zSize, depthNoiseScaleX, depthNoiseScaleZ);
+        double[] mainNoiseArray = mainNoise.get(null, x, y, z, xSize, ySize, zSize, coordScale / mainNoiseScaleX, heightScale / mainNoiseScaleY, coordScale / mainNoiseScaleZ);
+        double[] minLimitArray = minLimitNoise.get(null, x, y, z, xSize, ySize, zSize, coordScale, heightScale, coordScale);
+        double[] maxLimitArray = maxLimitNoise.get(null, x, y, z, xSize, ySize, zSize, coordScale, heightScale, coordScale);
+
+        double[] ravineNoise = ravineNoise2.getValue(null, x, z, xSize, zSize, ravineNoiseScale, ravineNoiseScale, 3.0D, 3.0D);
+//        double[] ravineMod = ravineNoise2.getValue(null, x, z, xSize, zSize, ravineNoiseScale, ravineNoiseScale, 0.25D, 0.6D);
+
+        int mainIndex = 0;
+
+        int depthScaleIndex = 0;
+        for(int dx = 0; dx < xSize; dx++)
+        {
+            for(int dz = 0; dz < zSize; dz++)
+            {
+
+                // Calculate scale
+                double scale = (scaleArray[depthScaleIndex] + 256D) / 512D;
+                scale *= 0.5D;
+                if(scale > 1.0D)
+                {
+                    scale = 1.0D;
+                }
+
+                // Calculate depth
+                double depth = depthArray[depthScaleIndex] / 2000D;
+                if(depth < 0.0D)
+                {
+                    depth = -depth * 0.3D;
+                }
+                depth = depth * 3D - 2D;
+
+                if(depth < 0.0D)
+                {
+                    depth /= 2D;
+                    if(depth < -1D)
+                    {
+                        depth = -1D;
+                    }
+                    depth /= 1.4D;
+                    depth /= 2D;
+                    scale = 0.0D;
+                }
+                else
+                {
+                    if(depth > 1.0D)
+                    {
+                        depth = 1.0D;
+                    }
+                    depth /= 8D;
+                }
+
+                if(scale < 0.0D)
+                {
+                    scale = 0.0D;
+                }
+
+                scale += 0.5D;
+                depth = (depth * (depthBaseSize * 2)) / 16D;
+
+                double offsetY = depthBaseSize + depth * 4D;
+                depthScaleIndex++;
+
+                for(int dy = 0; dy < ySize; dy++)
+                {
+                    double density;
+                    double densityOffset = ((((double)dy - offsetY) * heightStretch) / scale);
+                    if(densityOffset < 0.0D)
+                    {
+                        densityOffset *= 4D;
+                    }
+                    double minDensity = minLimitArray[mainIndex] / upperLimitScale;
+                    double maxDensity = maxLimitArray[mainIndex] / lowerLimitScale;
+                    double mainDensity = (mainNoiseArray[mainIndex] / 10D + 1.0D) / 2D;
+
+                    // Constrain density
+                    if(mainDensity < 0.0D)
+                    {
+                        density = minDensity;
+                    }
+                    else if(mainDensity > 1.0D)
+                    {
+                        density = maxDensity;
+                    }
+                    else
+                    {
+                        density = minDensity + (maxDensity - minDensity) * mainDensity;
+                    }
+                    density -= densityOffset;
+
+                    double densityMod = (1.0f - (3F - dy)) / 3F;
+
+                    float val = (float) ravineNoise[dx * zSize + dz] * 60;
+//                    float mod = (float) ravineMod[dx * zSize + dz];
+                    if(val < 0.3){
+                        val = 0;
+                    }
+
+                    if(dy > 3) {
+                        density += (val * 6d);
+                    } else {
+                        density += (val * 4d) * (dy / (ySize / 2d)) * (dy / (ySize / 2d));
+
+                    }
+
+                    if(dy > ySize - 7)
+                    {
+                        densityMod = (float)(dy - (ySize - 7)) / 6F;
+                        density = density * (1.0D - densityMod) + 300D * densityMod;
+                    }
+
+                    densityMapArray[mainIndex] = density;
+                    mainIndex++;
+                }
+            }
+        }
+        return densityMapArray;
+    }
+
+    private double[] generateDensityMapOcean(Chunk chunk, int terrainHeight, int xSize, int ySize, int zSize, int x, int y, int z) {
+        double[] densityMapArray = new double[xSize * ySize * zSize];
+
+        final double mainNoiseScaleX = 80;
+        final double mainNoiseScaleY = 160;
+        final double mainNoiseScaleZ = 80;
+
+        final double scaleNoiseScaleX = 1.121;
+        final double scaleNoiseScaleZ = 1.121;
+
+        final double depthNoiseScaleX = 200;
+        final double depthNoiseScaleZ = 200;
+
+        final double depthBaseSize = 2.5D;
+
+        final double coordScale = 684.412D;
+        final double heightScale = 684.412D;
+
+        final double heightStretch = 12;
+
+        final double upperLimitScale = 512;
+        final double lowerLimitScale = 512;
+
+        final double noiseScale = 0.02;
+
+        double[] scaleArray = scaleNoise.get(null, x, z, xSize, zSize, scaleNoiseScaleX, scaleNoiseScaleZ);
+        double[] depthArray = depthNoise.get(null, x, z, xSize, zSize, depthNoiseScaleX, depthNoiseScaleZ);
+        double[] mainNoiseArray = mainNoise.get(null, x, y, z, xSize, ySize, zSize, coordScale / mainNoiseScaleX, heightScale / mainNoiseScaleY, coordScale / mainNoiseScaleZ);
+        double[] minLimitArray = minLimitNoise.get(null, x, y, z, xSize, ySize, zSize, coordScale, heightScale, coordScale);
+        double[] maxLimitArray = maxLimitNoise.get(null, x, y, z, xSize, ySize, zSize, coordScale, heightScale, coordScale);
+        double[] islandNoiseArray = islandNoise.getValue(null, x, z, xSize, zSize, noiseScale, noiseScale, 2, 2);
+
+        int mainIndex = 0;
+
+        int depthScaleIndex = 0;
+        for(int dx = 0; dx < xSize; dx++)
+        {
+            for(int dz = 0; dz < zSize; dz++)
+            {
+                // Calculate scale
+                double scale = (scaleArray[depthScaleIndex] + 256D) / 2048D;
+                if(scale > 1.0D)
+                {
+                    scale = 1.0D;
+                }
+
+                // Calculate depth
+                double depth = depthArray[depthScaleIndex] / 2000D;
+                if(depth < 0.0D)
+                {
+                    depth = -depth * 0.3D;
+                }
+                depth = depth * 3D - 2D;
+
+                if(depth < 0.0D)
+                {
+                    depth /= 2D;
+                    if(depth < -1D)
+                    {
+                        depth = -1D;
+                    }
+                    depth /= 1.4D;
+                    depth /= 2D;
+                    scale = 0.0D;
+                }
+                else
+                {
+                    if(depth > 1.0D)
+                    {
+                        depth = 1.0D;
+                    }
+                    depth /= 8D;
+                }
+
+                if(scale < 0.0D)
+                {
+                    scale = 0.0D;
+                }
+
+                scale += 0.5D;
+                depth = (depth * (depthBaseSize * 2)) / 16D;
+
+                double offsetY = depthBaseSize + depth * 4D;
+                depthScaleIndex++;
+                for(int dy = 0; dy < ySize; dy++)
+                {
+                    double density;
+                    double densityOffset = ((((double)dy - offsetY) * heightStretch) / scale);
+                    if(densityOffset < 0.0D)
+                    {
+                        densityOffset *= 4D;
+                    }
+                    double minDensity = minLimitArray[mainIndex] / upperLimitScale;
+                    double maxDensity = maxLimitArray[mainIndex] / lowerLimitScale;
+                    double mainDensity = (mainNoiseArray[mainIndex] / 10D + 1.0D) / 2D;
+
+                    // Constrain density
+                    if(mainDensity < 0.0D)
+                    {
+                        density = minDensity;
+                    }
+                    else if(mainDensity > 1.0D)
+                    {
+                        density = maxDensity;
+                    }
+                    else
+                    {
+                        density = minDensity + (maxDensity - minDensity) * mainDensity;
+                    }
+                    density -= densityOffset;
+
+                    //Generate islands
+                    float isl = (float) islandNoiseArray[dx * zSize + dz];
+                    if(isl > 0.5) isl = isl * 100;
+                    if(isl < -0.2) isl = -isl * 100;
+
+                    if(dy > ySize - 9)
+                    {
+                        double densityMod = (float)(dy - (ySize - 10)) / 6F;
+                        density = density * (1.0D - densityMod) + 100D * densityMod;
+                    }
+
+
+                    density += isl;
+
+
+                    densityMapArray[mainIndex] = density;
+                    mainIndex++;
+                }
+            }
+        }
+        return densityMapArray;
+    }
+
+    private double[] generateDensityMapBiomeTest(Chunk chunk, int terrainHeight, int xSize, int ySize, int zSize, int x, int y, int z) {
+        double[] densityMapArray = new double[xSize * ySize * zSize];
+
+        final double mainNoiseScaleX = 80;
+        final double mainNoiseScaleY = 160;
+        final double mainNoiseScaleZ = 80;
+
+        final double scaleNoiseScaleX = 1.121;
+        final double scaleNoiseScaleZ = 1.121;
+
+        final double depthNoiseScaleX = 200;
+        final double depthNoiseScaleZ = 200;
+
+        final double depthBaseSize = 4D;
+
+        final double coordScale = 684.412D;
+        final double heightScale = 684.412D;
+
+        final double heightStretch = 12;
+
+        final double upperLimitScale = 512;
+        final double lowerLimitScale = 512;
+
+        double[] scaleArray = scaleNoise.get(null, x, z, xSize, zSize, scaleNoiseScaleX, scaleNoiseScaleZ);
+        double[] depthArray = depthNoise.get(null, x, z, xSize, zSize, depthNoiseScaleX, depthNoiseScaleZ);
+        double[] mainNoiseArray = mainNoise.get(null, x, y, z, xSize, ySize, zSize, coordScale / mainNoiseScaleX, heightScale / mainNoiseScaleY, coordScale / mainNoiseScaleZ);
+        double[] minLimitArray = minLimitNoise.get(null, x, y, z, xSize, ySize, zSize, coordScale, heightScale, coordScale);
+        double[] maxLimitArray = maxLimitNoise.get(null, x, y, z, xSize, ySize, zSize, coordScale, heightScale, coordScale);
+        int mainIndex = 0;
+
+        int depthScaleIndex = 0;
+        for(int dx = 0; dx < xSize; dx++)
+        {
+            for(int dz = 0; dz < zSize; dz++)
+            {
+                // Calculate scale
+                double scale = (scaleArray[depthScaleIndex] + 256D) / 2048D;
+                if(scale > 1.0D)
+                {
+                    scale = 1.0D;
+                }
+
+                // Calculate depth
+                double depth = depthArray[depthScaleIndex] / 2000D;
+                if(depth < 0.0D)
+                {
+                    depth = -depth * 0.3D;
+                }
+                depth = depth * 3D - 2D;
+
+                if(depth < 0.0D)
+                {
+                    depth /= 2D;
+                    if(depth < -1D)
+                    {
+                        depth = -1D;
+                    }
+                    depth /= 1.4D;
+                    depth /= 2D;
+                    scale = 0.0D;
+                }
+                else
+                {
+                    if(depth > 1.0D)
+                    {
+                        depth = 1.0D;
+                    }
+                    depth /= 8D;
+                }
+
+                if(scale < 0.0D)
+                {
+                    scale = 0.0D;
+                }
+
+                scale += 0.5D;
+                depth = (depth * (depthBaseSize * 2)) / 16D;
+
+                double offsetY = depthBaseSize + depth * 4D;
+                depthScaleIndex++;
+                for(int dy = 0; dy < ySize; dy++)
+                {
+                    double density;
+                    double densityOffset = ((((double)dy - offsetY) * heightStretch) / scale);
+                    if(densityOffset < 0.0D)
+                    {
+                        densityOffset *= 4D;
+                    }
+                    double minDensity = minLimitArray[mainIndex] / upperLimitScale;
+                    double maxDensity = maxLimitArray[mainIndex] / lowerLimitScale;
+                    double mainDensity = (mainNoiseArray[mainIndex] / 10D + 1.0D) / 2D;
+
+                    // Constrain density
+                    if(mainDensity < 0.0D)
+                    {
+                        density = minDensity;
+                    }
+                    else if(mainDensity > 1.0D)
+                    {
+                        density = maxDensity;
+                    }
+                    else
+                    {
+                        density = minDensity + (maxDensity - minDensity) * mainDensity;
+                    }
+                    density -= densityOffset;
+
+                    if(dy > ySize - 7)
+                    {
+                        double densityMod = (float)(dy - (ySize - 7)) / 6F;
+                        density = density * (1.0D - densityMod) + 300D * densityMod;
+                    }
+
+                    densityMapArray[mainIndex] = density;
+                    mainIndex++;
+                }
+            }
+        }
+
+        return densityMapArray;
+    }
+
+    private double[] generateDensityMapBiomeSulphurPools(Chunk chunk, int terrainHeight, int xSize, int ySize, int zSize, int x, int y, int z) {
+        double[] densityMapArray = new double[xSize * ySize * zSize];
+
+        final double mainNoiseScaleX = 8;
+        final double mainNoiseScaleY = 160;
+        final double mainNoiseScaleZ = 8;
+
+        final double scaleNoiseScaleX = 1.121 * 8192;
+        final double scaleNoiseScaleZ = 1.121 * 8192;
+
+        final double poolNoiseScale = 0.2;
+
+        final double depthNoiseScaleX = 200;
+        final double depthNoiseScaleZ = 200;
+
+        final double depthBaseSize = 3.5D;
+
+        final double coordScale = 6844.412D;
+        final double heightScale = 684.412D;
+
+        final double heightStretch = 512;
+
+        final double upperLimitScale = 512;
+        final double lowerLimitScale = 512;
+
+        double[] scaleArray = scaleNoise.get(null, x, z, xSize, zSize, scaleNoiseScaleX, scaleNoiseScaleZ);
+        double[] depthArray = depthNoise.get(null, x, z, xSize, zSize, depthNoiseScaleX, depthNoiseScaleZ);
+        double[] mainNoiseArray = mainNoise.get(null, x, y, z, xSize, ySize, zSize, coordScale / mainNoiseScaleX, heightScale / mainNoiseScaleY, coordScale / mainNoiseScaleZ);
+        double[] minLimitArray = minLimitNoise.get(null, x, y, z, xSize, ySize, zSize, coordScale, heightScale, coordScale);
+        double[] maxLimitArray = maxLimitNoise.get(null, x, y, z, xSize, ySize, zSize, coordScale, heightScale, coordScale);
+        double[] poolOverArray = poolNoiseOver.getValue(null, x, z, xSize, zSize, poolNoiseScale, poolNoiseScale, 1, 0.2);
+        double[] poolUnderArray = poolNoiseUnder.getValue(null, x, z, xSize, zSize, poolNoiseScale, poolNoiseScale, 0.01, 1);
+        int mainIndex = 0;
+
+        int depthScaleIndex = 0;
+        for(int dx = 0; dx < xSize; dx++)
+        {
+            for(int dz = 0; dz < zSize; dz++)
+            {
+                // Calculate scale
+                double scale = (scaleArray[depthScaleIndex] + 256D) / 2048D;
+                if(scale > 1.0D)
+                {
+                    scale = 1.0D;
+                }
+
+                // Calculate depth
+                double depth = depthArray[depthScaleIndex] / 2000D;
+                if(depth < 0.0D)
+                {
+                    depth = -depth * 0.3D;
+                }
+                depth = depth * 3D - 2D;
+
+                if(depth < 0.0D)
+                {
+                    depth /= 2D;
+                    if(depth < -1D)
+                    {
+                        depth = -1D;
+                    }
+                    depth /= 1.4D;
+                    depth /= 2D;
+                    scale = 0.0D;
+                }
+                else
+                {
+                    if(depth > 1.0D)
+                    {
+                        depth = 1.0D;
+                    }
+                    depth /= 8D;
+                }
+
+                if(scale < 0.0D)
+                {
+                    scale = 0.0D;
+                }
+
+                scale += 0.5D;
+                depth = (depth * (depthBaseSize * 2)) / 16D;
+
+                double offsetY = depthBaseSize + depth * 4D;
+                depthScaleIndex++;
+                for(int dy = 0; dy < ySize; dy++)
+                {
+                    double density;
+                    double densityOffset = ((((double)dy - offsetY) * heightStretch) / scale);
+                    if(densityOffset < 0.0D)
+                    {
+                        densityOffset *= 4D;
+                    }
+                    double minDensity = minLimitArray[mainIndex] / upperLimitScale;
+                    double maxDensity = maxLimitArray[mainIndex] / lowerLimitScale;
+                    double mainDensity = (mainNoiseArray[mainIndex] / 10D + 1.0D) / 2D;
+
+                    // Constrain density
+                    if(mainDensity < 0.0D)
+                    {
+                        density = minDensity;
+                    }
+                    else if(mainDensity > 1.0D)
+                    {
+                        density = maxDensity;
+                    }
+                    else
+                    {
+                        density = minDensity + (maxDensity - minDensity) * mainDensity;
+                    }
+                    density -= densityOffset;
+
+                    if(dy > ySize - 12)
+                    {
+                        double densityMod = (float)(dy - (ySize - 12)) / 11F;
+                        density = density * (1.0D - densityMod) + 8000D * densityMod;
+                    }
+                    if (dy < 5) {
+                        double densityMod = (1.0f - (4 - dy)) / 4f;
+                        float val = (float) poolOverArray[dx * zSize + dz];
+                        float mod = (float) poolUnderArray[dx * zSize + dz];
+                        if (val < -20 - (20 * mod)) val = val * 80;
+                        else if (val > 20 + (20 * mod)) val = -val * 80;
+                        else val = 0;
+                        density = density * (1.0 - densityMod) + val * densityMod;
+                    }
+
+                    densityMapArray[mainIndex] = density;
+                    mainIndex++;
+                }
+            }
+        }
+
+        return densityMapArray;
+    }
+
+    private double[] generateDensityMapBiomeVolcanicIslands(Chunk chunk, int terrainHeight, int xSize, int ySize, int zSize, int x, int y, int z) {
+        double[] densityMapArray = new double[xSize * ySize * zSize];
+
+        final double mainNoiseScaleX = 80;
+        final double mainNoiseScaleY = 160;
+        final double mainNoiseScaleZ = 80;
+
+        final double scaleNoiseScaleX = 1.121 * 512;
+        final double scaleNoiseScaleZ = 1.121 * 512;
+
+        final double depthNoiseScaleX = 200;
+        final double depthNoiseScaleZ = 200;
+
+        final double depthBaseSize = 3.5D;
+
+        final double coordScale = 6844.412D;
+        final double heightScale = 684.412D;
+
+        final double heightStretch = 64;
+
+        final double upperLimitScale = 512;
+        final double lowerLimitScale = 512;
+
+        double[] scaleArray = scaleNoise.get(null, x, z, xSize, zSize, scaleNoiseScaleX, scaleNoiseScaleZ);
+        double[] depthArray = depthNoise.get(null, x, z, xSize, zSize, depthNoiseScaleX, depthNoiseScaleZ);
+        double[] mainNoiseArray = mainNoise.get(null, x, y, z, xSize, ySize, zSize, coordScale / mainNoiseScaleX, heightScale / mainNoiseScaleY, coordScale / mainNoiseScaleZ);
+        double[] minLimitArray = minLimitNoise.get(null, x, y, z, xSize, ySize, zSize, coordScale, heightScale, coordScale);
+        double[] maxLimitArray = maxLimitNoise.get(null, x, y, z, xSize, ySize, zSize, coordScale, heightScale, coordScale);
+        int mainIndex = 0;
+
+        int depthScaleIndex = 0;
+        for(int dx = 0; dx < xSize; dx++)
+        {
+            for(int dz = 0; dz < zSize; dz++)
+            {
+                // Calculate scale
+                double scale = (scaleArray[depthScaleIndex] + 256D) / 2048D;
+                if(scale > 1.0D)
+                {
+                    scale = 1.0D;
+                }
+
+                // Calculate depth
+                double depth = depthArray[depthScaleIndex] / 2000D;
+                if(depth < 0.0D)
+                {
+                    depth = -depth * 0.3D;
+                }
+                depth = depth * 3D - 2D;
+
+                if(depth < 0.0D)
+                {
+                    depth /= 2D;
+                    if(depth < -1D)
+                    {
+                        depth = -1D;
+                    }
+                    depth /= 1.4D;
+                    depth /= 2D;
+                    scale = 0.0D;
+                }
+                else
+                {
+                    if(depth > 1.0D)
+                    {
+                        depth = 1.0D;
+                    }
+                    depth /= 8D;
+                }
+
+                if(scale < 0.0D)
+                {
+                    scale = 0.0D;
+                }
+
+                scale += 0.5D;
+                depth = (depth * (depthBaseSize * 2)) / 16D;
+
+                double offsetY = depthBaseSize + depth * 4D;
+                depthScaleIndex++;
+                for(int dy = 0; dy < ySize; dy++)
+                {
+                    double density;
+                    double densityOffset = ((((double)dy - offsetY) * heightStretch) / scale);
+                    if(densityOffset < 0.0D)
+                    {
+                        densityOffset *= 4D;
+                    }
+                    double minDensity = minLimitArray[mainIndex] / upperLimitScale;
+                    double maxDensity = maxLimitArray[mainIndex] / lowerLimitScale;
+                    double mainDensity = (mainNoiseArray[mainIndex] / 10D + 1.0D) / 2D;
+
+                    // Constrain density
+                    if(mainDensity < 0.0D)
+                    {
+                        density = minDensity;
+                    }
+                    else if(mainDensity > 1.0D)
+                    {
+                        density = maxDensity;
+                    }
+                    else
+                    {
+                        density = minDensity + (maxDensity - minDensity) * mainDensity;
+                    }
+                    density -= densityOffset;
+
+                    if (dy < 4) {
+                        double densityMod = (1.0f - (3 - dy)) / 3f;
+                        density = density * (1.0 - densityMod) + scaleArray[dx * zSize + dz] * densityMod;
+                    }
+                    if(dy > ySize - 8)
+                    {
+                        double densityMod = (float)(dy - (ySize - 8)) / 7F;
+                        density = density * (1.0D - densityMod) + 8000D * densityMod;
+                    }
+
+                    densityMapArray[mainIndex] = density;
+                    mainIndex++;
+                }
+            }
+        }
+
+        return densityMapArray;
+    }
+
+
 }
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/SurfaceGeneratorNether.java b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/SurfaceGeneratorNether.java
index 1243dfa7a..49223206e 100644
--- a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/SurfaceGeneratorNether.java
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/SurfaceGeneratorNether.java
@@ -2,6 +2,8 @@ package net.minecraft.core.world.generate.chunk.perlin.nether;
 
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.world.World;
+import net.minecraft.core.world.biome.Biome;
+import net.minecraft.core.world.biome.Biomes;
 import net.minecraft.core.world.chunk.Chunk;
 import net.minecraft.core.world.generate.chunk.ChunkGeneratorResult;
 import net.minecraft.core.world.generate.chunk.perlin.SurfaceGenerator;
@@ -10,15 +12,13 @@ import net.minecraft.core.world.noise.PerlinNoise;
 import java.util.Random;
 
 public class SurfaceGeneratorNether
-    implements SurfaceGenerator
-{
+    implements SurfaceGenerator {
     private final World world;
 
     private final PerlinNoise beachNoise;
     private final PerlinNoise soilNoise;
 
-    public SurfaceGeneratorNether(World world)
-    {
+    public SurfaceGeneratorNether(World world) {
         this.world = world;
 
         beachNoise = new PerlinNoise(world.getRandomSeed(), 4, 40);
@@ -26,8 +26,7 @@ public class SurfaceGeneratorNether
     }
 
     @Override
-    public void generateSurface(Chunk chunk, ChunkGeneratorResult result)
-    {
+    public void generateSurface(Chunk chunk, ChunkGeneratorResult result) {
         int oceanY = world.getWorldType().getOceanY();
         int minY = world.getWorldType().getMinY();
         int maxY = world.getWorldType().getMaxY();
@@ -43,82 +42,84 @@ public class SurfaceGeneratorNether
         double[] sandBeachNoise = beachNoise.get(null, chunkX * 16, chunkZ * 16, 0.0D, 16, 16, 1, beachScale, beachScale, 1.0D);
         double[] gravelBeachNoise = beachNoise.get(null, chunkX * 16, 109.0134D, chunkZ * 16, 16, 1, 16, beachScale, 1.0D, beachScale);
         double[] soilThicknessNoise = soilNoise.get(null, chunkX * 16, chunkZ * 16, 0.0D, 16, 16, 1, beachScale * 2D, beachScale * 2D, beachScale * 2D);
-        for(int z = 0; z < 16; z++)
-        {
-            for(int x = 0; x < 16; x++)
-            {
+        for (int z = 0; z < 16; z++) {
+            for (int x = 0; x < 16; x++) {
                 boolean generateSandBeach = sandBeachNoise[z + x * 16] + rand.nextDouble() * 0.2D > 0.0D;
                 boolean generateGravelBeach = gravelBeachNoise[z + x * 16] + rand.nextDouble() * 0.2D > 0.0D;
-                int soilThickness = (int)(soilThicknessNoise[z + x * 16] / 3D + 3D + rand.nextDouble() * 0.25D);
+                int soilThickness = (int) (soilThicknessNoise[z + x * 16] / 3D + 3D + rand.nextDouble() * 0.25D);
 
                 int currentLayerDepth = -1;
-                short topBlock = (short) worldFillBlock;
-                short fillerBlock = (short) worldFillBlock;
+                short topBlock = -1;
+                short fillerBlock = -1;
+
+                Biome lastBiome = null;
+
+                for (int y = maxY; y >= minY; y--) {
+                    Biome biome = chunk.getBlockBiome(x, y, z);
+                    if (biome == null)
+                        biome = world.getBiomeProvider().getBiome(chunkX * Chunk.CHUNK_SIZE_X + x, y >> 3, chunkZ * Chunk.CHUNK_SIZE_Z + z);
 
-                for(int y = maxY; y >= minY; y--)
-                {
                     int block = result.getBlock(x, y, z);
 
-                    // reset the currently generating surface thickness to 0 if encountered air
-                    if(block == 0)
-                    {
+                    if ((biome != lastBiome || topBlock == -1 || fillerBlock == -1) && block == 0) {
+                        topBlock = biome.topBlock;
+                        fillerBlock = biome.fillerBlock;
+                    }
+                    lastBiome = biome;
+
+                    // reset the currently generating surface thickness to -1 if encountered air
+                    if (block == 0) {
                         currentLayerDepth = -1;
                         continue;
                     }
 
                     // will skip a generation loop if it encounters something other than netherrack
-                    if(block != worldFillBlock)
-                    {
+                    if (block != worldFillBlock) {
                         continue;
                     }
 
                     // if thickness == -1, find what block to place on layer level
-                    if(currentLayerDepth == -1)
-                    {
+                    if (currentLayerDepth == -1) {
                         // if soil thickness is below 0, generate a stone basin where there is no top block layer
-                        if(soilThickness <= 0)
-                        {
+                        if (soilThickness <= 0) {
                             topBlock = 0;
                             fillerBlock = (short) worldFillBlock;
-                        }
-                        else if(y >= minY + oceanY - 4 && y <= minY + oceanY + 1)
-                        {
-                            // Generate coastlines
-                            topBlock = (short) worldFillBlock;
-                            fillerBlock = (short) worldFillBlock;
-                            if(generateGravelBeach)
-                            {
-                                topBlock = (short) Blocks.GRAVEL.id();
-                            }
-                            if(generateGravelBeach)
-                            {
-                                fillerBlock = (short) Blocks.COBBLE_NETHERRACK.id();
-                            }
-                            if(generateSandBeach)
-                            {
-                                topBlock = (short) Blocks.SOULSAND.id();
-                            }
-                            if(generateSandBeach)
-                            {
-                                fillerBlock = (short) Blocks.SOULSAND.id();
+                        } else {
+                            boolean biomeGeneratesSulfur = biome == Biomes.NETHER_SULFUR_POOLS;
+
+                            if (y >= minY + oceanY - 4 && y <= minY + oceanY + 1) {
+                                // Generate coastlines
+                                topBlock = biome.topBlock;
+                                fillerBlock = biome.fillerBlock;
+                                if (biomeGeneratesSulfur) {
+                                    topBlock = (short) Blocks.SULFUR.id();
+                                    fillerBlock = (short) Blocks.SULFUR.id();
+                                } else if (generateGravelBeach) {
+                                    topBlock = (short) Blocks.GRAVEL.id();
+                                    fillerBlock = (short) Blocks.COBBLE_NETHERRACK.id();
+                                } else if (generateSandBeach) {
+                                    topBlock = (short) Blocks.SOULSAND.id();
+                                    fillerBlock = (short) Blocks.SOULSAND.id();
+                                }
+                            } else if (y <= oceanY) {
+                                if (biomeGeneratesSulfur) {
+                                    topBlock = (short) Blocks.SULFUR.id();
+                                    fillerBlock = (short) Blocks.SULFUR.id();
+                                }
                             }
                         }
 
                         // failsafe so that if a basin is generated under the ocean, the ocean is not replaced with air
-                        if(y < minY + oceanY && topBlock == 0)
-                        {
+                        if (y < minY + oceanY && topBlock == 0) {
                             topBlock = (short) oceanBlock;
                         }
                         // if a new surface layer has been chosen to be generated, set the current layer depth to the generated soil thickness level and begin generating downwards
                         currentLayerDepth = soilThickness;
 
                         // set block at index to designated surface block above ocean level
-                        if(y >= minY + oceanY - 1)
-                        {
+                        if (y >= minY + oceanY - 1) {
                             result.setBlock(x, y, z, topBlock);
-                        }
-                        else
-                        {
+                        } else {
                             // if the block is below ocean level, set it to the chosen filler block
                             result.setBlock(x, y, z, fillerBlock);
                         }
@@ -126,8 +127,7 @@ public class SurfaceGeneratorNether
                     }
 
                     // fill blocks with filler block until current layer level = -1 or air is encountered
-                    if(currentLayerDepth > 0)
-                    {
+                    if (currentLayerDepth > 0) {
                         currentLayerDepth--;
                         result.setBlock(x, y, z, fillerBlock);
                     }
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/TerrainGeneratorNether.java b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/TerrainGeneratorNether.java
index 6534b0bb8..03772c650 100644
--- a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/TerrainGeneratorNether.java
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/nether/TerrainGeneratorNether.java
@@ -2,25 +2,71 @@ package net.minecraft.core.world.generate.chunk.perlin.nether;
 
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.world.World;
+import net.minecraft.core.world.biome.Biome;
+import net.minecraft.core.world.biome.Biomes;
+import net.minecraft.core.world.chunk.Chunk;
 import net.minecraft.core.world.generate.chunk.perlin.DensityGenerator;
 import net.minecraft.core.world.generate.chunk.perlin.TerrainGeneratorLerp;
 import net.minecraft.core.world.type.WorldType;
 
+import java.util.HashMap;
+import java.util.Map;
+
 public class TerrainGeneratorNether
     extends TerrainGeneratorLerp
 {
     private final DensityGenerator densityGenerator;
 
+    private int lastX = -1;
+    private int lastZ = -1;
+    private Biome lastBiome = null;
+    private Biome lastBiomeNegX = null;
+    private Biome lastBiomePosX = null;
+    private Biome lastBiomeNegZ = null;
+    private Biome lastBiomePosZ = null;
+    private Map<Biome, Integer> fluidLookup = new HashMap<>();
+
     public TerrainGeneratorNether(World world)
     {
         super(world);
 
         this.densityGenerator = new DensityGeneratorNether(world);
+
+        this.fluidLookup.put(Biomes.NETHER_NETHER, Blocks.FLUID_LAVA_STILL.id());
+        this.fluidLookup.put(Biomes.NETHER_VOLCANIC_ISLANDS, Blocks.FLUID_LAVA_STILL.id());
+        this.fluidLookup.put(Biomes.NETHER_SULFUR_POOLS, Blocks.FLUID_WATER_STILL.id());
+        this.fluidLookup.put(Biomes.NETHER_CRYSTAL_FOREST, Blocks.FLUID_WATER_STILL.id());
+        this.fluidLookup.put(Biomes.NETHER_ASH_PLAINS, Blocks.OBSIDIAN.id());
+        this.fluidLookup.put(Biomes.NETHER_BONEYARD, Blocks.COBBLE_NETHERRACK.id());
     }
 
     @Override
-    protected int getBlockAt(int x, int y, int z, double density)
+    protected int getBlockAt(Chunk chunk, int x, int y, int z, double density)
     {
+        if (lastX != x || lastZ != z) {
+            lastBiome = chunk.getBlockBiome(x, 0, z);
+            if (x - 1 < 0) {
+                lastBiomeNegX = lastBiome;
+            } else {
+                lastBiomeNegX = chunk.getBlockBiome(x - 1, 0, z);
+            }
+            if (x + 1 >= Chunk.CHUNK_SIZE_X) {
+                lastBiomePosX = lastBiome;
+            } else {
+                lastBiomePosX = chunk.getBlockBiome(x + 1, 0, z);
+            }
+            if (z - 1 < 0) {
+                lastBiomeNegZ = lastBiome;
+            } else {
+                lastBiomeNegZ = chunk.getBlockBiome(x, 0, z - 1);
+            }
+            if (z + 1 >= Chunk.CHUNK_SIZE_Z) {
+                lastBiomePosZ = lastBiome;
+            } else {
+                lastBiomePosZ = chunk.getBlockBiome(x, 0, z + 1);
+            }
+        }
+
         WorldType type = world.getWorldType();
         int halfHeight = type.getMaxY() / 2;
 
@@ -54,7 +100,17 @@ public class TerrainGeneratorNether
         }
         else if (y >= halfHeight && y < type.getOceanY())
         {
-            return type.getOceanBlockId();
+            int biomeFluid = fluidLookup.getOrDefault(lastBiome, 0);
+            int biomeFluidNegX = fluidLookup.getOrDefault(lastBiomeNegX, 0);
+            int biomeFluidPosX = fluidLookup.getOrDefault(lastBiomePosX, 0);
+            int biomeFluidNegZ = fluidLookup.getOrDefault(lastBiomeNegZ, 0);
+            int biomeFluidPosZ = fluidLookup.getOrDefault(lastBiomePosZ, 0);
+
+            if (biomeFluid == biomeFluidNegX && biomeFluid == biomeFluidPosX && biomeFluid == biomeFluidNegZ && biomeFluid == biomeFluidPosZ) {
+                return biomeFluid;
+            }
+
+            return Blocks.OBSIDIAN.id();
         }
 
         return 0;
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/overworld/ChunkDecoratorOverworld.java b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/overworld/ChunkDecoratorOverworld.java
index b8e5eeb6d..f0228bf0d 100644
--- a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/overworld/ChunkDecoratorOverworld.java
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/overworld/ChunkDecoratorOverworld.java
@@ -15,7 +15,7 @@ import net.minecraft.core.world.World;
 import net.minecraft.core.world.biome.Biomes;
 import net.minecraft.core.world.type.WorldTypes;
 import net.minecraft.core.world.biome.Biome;
-import net.minecraft.core.world.biome.BiomeOutback;
+import net.minecraft.core.world.biome.overworld.BiomeOutback;
 import net.minecraft.core.world.chunk.Chunk;
 import net.minecraft.core.world.generate.chunk.ChunkDecorator;
 import net.minecraft.core.world.generate.feature.*;
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/overworld/SurfaceGeneratorOverworld.java b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/overworld/SurfaceGeneratorOverworld.java
index 18c2f207f..62086b02e 100644
--- a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/overworld/SurfaceGeneratorOverworld.java
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/overworld/SurfaceGeneratorOverworld.java
@@ -98,7 +98,6 @@ public class SurfaceGeneratorOverworld
                     limestoneThicknessLevel = (int)(stoneLayerNoiseLimestone[z + x] + rand.nextDouble() * 0.5D);
                 }
 
-
                 int currentLayerDepth = -1;
                 short topBlock = -1;
                 short fillerBlock = -1;
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/overworld/TerrainGeneratorOverworld.java b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/overworld/TerrainGeneratorOverworld.java
index 2f3f21a09..7ad3054d4 100644
--- a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/overworld/TerrainGeneratorOverworld.java
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/overworld/TerrainGeneratorOverworld.java
@@ -2,6 +2,7 @@ package net.minecraft.core.world.generate.chunk.perlin.overworld;
 
 import net.minecraft.core.block.Blocks;
 import net.minecraft.core.world.World;
+import net.minecraft.core.world.chunk.Chunk;
 import net.minecraft.core.world.generate.chunk.perlin.DensityGenerator;
 import net.minecraft.core.world.generate.chunk.perlin.TerrainGeneratorLerp;
 import net.minecraft.core.world.type.WorldType;
@@ -24,7 +25,7 @@ public class TerrainGeneratorOverworld
     }
 
     @Override
-    protected int getBlockAt(int x, int y, int z, double density)
+    protected int getBlockAt(Chunk chunk, int x, int y, int z, double density)
     {
         WorldType type = world.getWorldType();
 
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/paradise/TerrainGeneratorParadise.java b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/paradise/TerrainGeneratorParadise.java
index f2b0a228a..79f38ccdd 100644
--- a/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/paradise/TerrainGeneratorParadise.java
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/chunk/perlin/paradise/TerrainGeneratorParadise.java
@@ -1,6 +1,7 @@
 package net.minecraft.core.world.generate.chunk.perlin.paradise;
 
 import net.minecraft.core.world.World;
+import net.minecraft.core.world.chunk.Chunk;
 import net.minecraft.core.world.generate.chunk.perlin.DensityGenerator;
 import net.minecraft.core.world.generate.chunk.perlin.TerrainGeneratorLerp;
 import net.minecraft.core.world.type.WorldType;
@@ -24,7 +25,7 @@ public class TerrainGeneratorParadise
     }
 
     @Override
-    protected int getBlockAt(int x, int y, int z, double density)
+    protected int getBlockAt(Chunk chunk, int x, int y, int z, double density)
     {
         WorldType type = world.getWorldType();
 
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureColdFire.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureColdFire.java
new file mode 100644
index 000000000..48f6ffdd6
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureColdFire.java
@@ -0,0 +1,31 @@
+package net.minecraft.core.world.generate.feature;
+
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.world.World;
+
+import java.util.Random;
+
+public class WorldFeatureColdFire extends WorldFeature
+{
+
+    public WorldFeatureColdFire()
+    {
+    }
+
+    @Override
+	public boolean place(World world, Random random, int x, int y, int z)
+    {
+        for(int l = 0; l < 64; l++)
+        {
+            int i1 = (x + random.nextInt(8)) - random.nextInt(8);
+            int j1 = (y + random.nextInt(4)) - random.nextInt(4);
+            int k1 = (z + random.nextInt(8)) - random.nextInt(8);
+            if(world.isAirBlock(i1, j1, k1) && world.getBlockId(i1, j1 - 1, k1) == Blocks.SOULSAND.id())
+            {
+                world.setBlockWithNotify(i1, j1, k1, Blocks.FIRE_COLD.id());
+            }
+        }
+
+        return true;
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureCrystal.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureCrystal.java
new file mode 100644
index 000000000..7fab98906
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureCrystal.java
@@ -0,0 +1,105 @@
+package net.minecraft.core.world.generate.feature;
+
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.world.World;
+
+import java.util.Random;
+
+public class WorldFeatureCrystal extends WorldFeature
+{
+
+    final double[] RADIUS_MAP_1 = {1.5D, 1D, 0D};
+    @Override
+    public boolean place(World world, Random random, int x, int y, int z) {
+
+        int yf = y;
+            int checksForGround = 0;
+
+
+
+            boolean canGenerate = false;
+            while (true) {
+                int blockBelow = world.getBlockId(x, yf - 1, x);
+
+                if (checksForGround > 64 || yf <= 0) {
+                    break;
+                }
+
+                if (Blocks.solid[blockBelow] && blockBelow == Blocks.COBBLE_NETHERRACK.id()) {
+                    canGenerate = true;
+                    break;
+                }
+
+                yf--;
+                checksForGround++;
+            }
+
+            if (canGenerate && world.isAirBlock(x, yf, z)) {
+//                for (int xi = x1 - 1; xi <= x1 + 1; xi++) {
+//                    for (int zi = z1 - 1; zi <= z1 + 1; zi++) {
+//                        for (int yi = yf; yi <= yf + height; yi++) {
+//                            world.setBlock(xi, yi, zi, Blocks.CRYSTAL.id);
+//                        }
+//                    }
+//                }
+//                //cap crystal
+//
+//                for (int i = 0; i < radiusMap.length; i++) {
+//                    generateRadius(world, x1, z1, radiusMap[i], yf + 1 + height + i);
+//                }
+
+                for (int xi = x - 5; xi < x + 5; xi++) {
+                    for (int zi = x - 5; zi < x + 5; zi++) {
+                        for (int yi = yf - 3; yi < yf + 3; yi++) {
+                            if (world.getBlockId(xi, yi, zi) == Blocks.COBBLE_NETHERRACK.id() && world.getBlockId(xi, yi + 1, zi) == 0 && random.nextInt(5) != 0)
+                                world.setBlock(xi, yi, zi, Blocks.NETHERRACK_CRYSTALLINE.id());
+                        }
+                    }
+                }
+            }
+
+
+        return true;
+    }
+
+    private void plotBlocks(World world, int id, int xc, int zc, int x, int y, int z){
+        for(int xx = xc - x; xx <= xc + x; xx++) {
+            world.setBlock(xx, y, zc + z ,id);
+            world.setBlock(xx, y, zc - z, id);
+        }
+
+        for(int xx = xc - z; xx <= xc + z; xx++) {
+            world.setBlock(xx, y, zc + x, id);
+            world.setBlock(xx, y, zc - x, id);
+        }
+    }
+
+    private void generateRadius(World world, int xc, int zc, double r, int y){
+        int x = 0;
+        int z = (int) r;
+        double d = 3 - 2 * r;
+        plotBlocks(world, Blocks.CRYSTAL.id(), xc, zc, x, y, z);
+        while (z > x){
+            x++;
+            if (d > 0) {
+                z--;
+                d = d + 4 * (x - z) + 10;
+            }  else
+                d = d + 4 * x + 6;
+
+            plotBlocks(world, Blocks.CRYSTAL.id(), xc, zc, x, y, z);
+        }
+    }
+
+    private boolean hasSpace(World world, int xc, int y, int zc){
+        for (int xi = xc - 1; xi < xc + 1; xi++) {
+            for (int zi = zc - 1; zi < zc + 1; zi++) {
+                if (world.getBlockId(xi, y-1, zi) != Blocks.COBBLE_NETHERRACK.id())
+                    return false;
+            }
+        }
+        return true;
+    }
+
+
+}
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureCrystalCeiling.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureCrystalCeiling.java
new file mode 100644
index 000000000..a8876e06d
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureCrystalCeiling.java
@@ -0,0 +1,55 @@
+package net.minecraft.core.world.generate.feature;
+
+import net.minecraft.core.block.Block;
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.world.World;
+
+import java.util.Random;
+
+public class WorldFeatureCrystalCeiling extends WorldFeature
+{
+
+    @Override
+    public boolean place(World world, Random random, int x, int y, int z)
+    {
+        int x1 = (x + random.nextInt(2)) - random.nextInt(2);
+        int z1 = (z + random.nextInt(2)) - random.nextInt(2);
+        int yF = y;
+
+        double heightMod = 5D;
+
+        int checksForGround = 0;
+        boolean canGenerate = false;
+        while(true){
+            int blockBelow = world.getBlockId(x1, yF+1, z1);
+
+            if(checksForGround > 64 || yF >= world.getHeightBlocks()) {
+                break;
+            }
+
+            if(Blocks.solid[blockBelow]){
+                canGenerate = true;
+                break;
+            }
+
+            yF ++;
+            checksForGround++;
+        }
+
+        double currentHeight = heightMod + random.nextInt(10);
+
+        if(canGenerate && world.isAirBlock(x1, yF, z1))
+        {
+            for(int xi = x - 1; xi < x + 1; xi++){
+                for(int zi = z - 1; zi < z + 1; zi++){
+                    for(int yi = yF; yi > yF - currentHeight; yi--){
+                        world.setBlock(xi, yi, zi, Blocks.CRYSTAL.id());
+                    }
+                    currentHeight = (currentHeight * 0.25D) + random.nextInt(2);
+                }
+            }
+        }
+
+        return true;
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureCrystalChunk.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureCrystalChunk.java
new file mode 100644
index 000000000..7b7847676
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureCrystalChunk.java
@@ -0,0 +1,209 @@
+package net.minecraft.core.world.generate.feature;
+
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.util.helper.MathHelper;
+import net.minecraft.core.world.World;
+
+import java.util.Random;
+
+import static net.minecraft.core.util.helper.MathHelper.cos;
+import static net.minecraft.core.util.helper.MathHelper.sin;
+
+public class WorldFeatureCrystalChunk extends WorldFeature {
+    private final boolean isCeiling;
+    private final int minHeight;
+    private final int heightMod;
+    private final boolean decorateFloor;
+
+    public WorldFeatureCrystalChunk(boolean isCeiling) {
+        this.isCeiling = isCeiling;
+        this.minHeight = 30;
+        this.heightMod = 50;
+        this.decorateFloor = false;
+    }
+
+    public WorldFeatureCrystalChunk(boolean isCeiling, int minHeight, int heightMod, boolean decorateFloor) {
+        this.isCeiling = isCeiling;
+        this.minHeight = minHeight;
+        this.heightMod = heightMod;
+        this.decorateFloor = decorateFloor;
+    }
+
+    @Override
+    public boolean place(World world, Random random, int x, int y, int z) {
+        int x1 = (x + random.nextInt(2)) - random.nextInt(2);
+        int z1 = (z + random.nextInt(2)) - random.nextInt(2);
+        int yF = y;
+
+        int checksForGround = 0;
+        boolean canGenerate = false;
+        while(true){
+            if(isCeiling)
+                yF ++;
+            else
+                yF --;
+
+            if(checksForGround > 64 || yF < 0 || yF >= world.getHeightBlocks()) {
+                break;
+            }
+
+            int blockId = world.getBlockId(x1, yF, z1);
+
+            if(Blocks.solid[blockId]){
+                canGenerate = true;
+                break;
+            }
+
+            checksForGround++;
+        }
+
+        int blockId = world.getBlockId(x1, yF, z1);
+        if(canGenerate && (blockId == Blocks.COBBLE_NETHERRACK.id() || blockId == Blocks.SOULSAND.id())) {
+            if (isCeiling) {
+                yF += 5;
+            } else {
+                yF -= 5;
+            }
+
+            placeCrystal(world, random, x1, yF, z1);
+
+            return true;
+        }
+
+        return false;
+    }
+
+    private void placeCrystal(World world, Random random, final int x, int y, final int z) {
+        float r = random.nextFloat();
+        int length = minHeight + MathHelper.floor(Math.pow(r, 4) * heightMod);
+        int base = length / 4;
+        float rx = 0;
+        float ry = 0;
+        float rz = 0;
+
+        y = isCeiling ? y + (base/2) : y - (base/2);
+
+        if(!hasSpace(world, x, y, z, base)){
+            return;
+        }
+
+        if (isCeiling) {
+            rx = MathHelper.PI;
+        }
+        rx += (float) ((random.nextDouble() - 0.5) * (Math.PI / 2)) * 0.8f;
+        ry += (float) (random.nextDouble() * 2.0 * Math.PI);
+
+        float rxSin = sin(rx), rxCos = cos(rx);
+        float rySin = sin(ry), ryCos = cos(ry);
+        float rzSin = sin(rz), rzCos = cos(rz);
+
+        float[][] rxMatrix = {
+            {1, 0, 0},
+            {0, rxCos, -rxSin},
+            {0, rxSin, rxCos}
+        };
+        float[][] ryMatrix = {
+            {ryCos, 0, rySin},
+            {0, 1, 0},
+            {-rySin, 0, ryCos}
+        };
+        float[][] rzMatrix = {
+            {rzCos, -rzSin, 0},
+            {rzSin, rzCos, 0},
+            {0, 0, 1}
+        };
+
+        float[][] matrix = mult(ryMatrix, rxMatrix);
+
+        int i = 0;
+        for (int dy = 0; dy < length; dy++) {
+            for (int dx = 0; dx < base; dx++) {
+                for (int dz = 0; dz < base; dz++) {
+
+                    if (dy <= base + random.nextInt(4)) {
+                        place(world, Blocks.NETHERRACK_CRYSTALLINE.id(), x, y, z, dx + i + 0.5f, dy + 0.5f, dz + i + 0.5f, matrix);
+                    } else {
+                        place(world, Blocks.CRYSTAL.id(), x, y, z, dx + i + 0.5f, dy + 0.5f, dz + i + 0.5f, matrix);
+                    }
+                }
+            }
+            if (random.nextFloat() > 0.95) {
+                base -= 2;
+                i += 1;
+                if (base <= 1) {
+                    break;
+                }
+            }
+        }
+        int yy = length;
+        while (true) {
+            base -= 2;
+            i += 1;
+            if (base <= 0) {
+                break;
+            }
+            for (int dx = 0; dx < base; dx++) {
+                for (int dz = 0; dz < base; dz++) {
+                    place(world, Blocks.CRYSTAL.id(), x, y, z, dx + i, yy, dz + i, matrix);
+                }
+            }
+            yy++;
+        }
+    }
+
+    private float[][] mult(float[][] ma, float[][] mb) {
+        float a = ma[0][0], b = ma[0][1], c = ma[0][2],
+              d = ma[1][0], e = ma[1][1], f = ma[1][2],
+              g = ma[2][0], h = ma[2][1], i = ma[2][2];
+        float j = mb[0][0], k = mb[0][1], l = mb[0][2],
+              m = mb[1][0], n = mb[1][1], o = mb[1][2],
+              p = mb[2][0], q = mb[2][1], r = mb[2][2];
+
+        return new float[][] {
+            {a * j + b * m + c * p, a * k + b * n + c * q, a * l + b * o + c * r},
+            {d * j + e * m + f * p, d * k + e * n + f * q, d * l + e * o + f * r},
+            {g * j + h * m + i * p, g * k + h * n + i * q, g * l + h * o + i * r}
+        };
+    }
+
+    private float[] mult(float[][] m, float[] v) {
+        return new float[] {
+            m[0][0] * v[0] + m[0][1] * v[1] + m[0][2] * v[2],
+            m[1][0] * v[0] + m[1][1] * v[1] + m[1][2] * v[2],
+            m[2][0] * v[0] + m[2][1] * v[1] + m[2][2] * v[2]
+        };
+    }
+
+    private void place(World world, int id, int ox, int oy, int oz, float dx, float dy, float dz, float[][] m) {
+        float[] v = {dx, dy, dz};
+        float[] t = mult(m, v);
+
+        int x = ox + MathHelper.floor(t[0]);
+        int y = oy + MathHelper.floor(t[1]);
+        int z = oz + MathHelper.floor(t[2]);
+
+        world.setBlock(x, y, z, id);
+        world.setBlock(x + 1, y, z, id);
+        world.setBlock(x, y, z + 1, id);
+        world.setBlock(x + 1, y, z + 1, id);
+
+        world.setBlock(x, y + 1, z, id);
+        world.setBlock(x + 1, y + 1, z, id);
+        world.setBlock(x, y + 1, z + 1, id);
+        world.setBlock(x + 1, y + 1, z + 1, id);
+
+        world.markBlocksDirty(x, y, z, x + 1, y + 1, z + 1);
+    }
+
+    private boolean hasSpace(World world, int x, int y, int z, int base){
+        for (int yi = y; yi < y + base; yi++) {
+            for (int xi = x - base; xi < x + base; xi++) {
+                for (int zi = z - base; zi < z + base; zi++) {
+                    if (world.getBlockId(xi, yi, zi) == Blocks.CRYSTAL.id())
+                        return false;
+                }
+            }
+        }
+        return true;
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureCrystalCone.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureCrystalCone.java
new file mode 100644
index 000000000..c92dfa50d
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeatureCrystalCone.java
@@ -0,0 +1,168 @@
+package net.minecraft.core.world.generate.feature;
+
+import net.minecraft.core.block.Block;
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.util.helper.MathHelper;
+import net.minecraft.core.world.World;
+
+import java.util.Random;
+
+public class WorldFeatureCrystalCone extends WorldFeature
+{
+    private final boolean ceilingGen;
+
+    public WorldFeatureCrystalCone(boolean ceilingGen){
+        this.ceilingGen = ceilingGen;
+    }
+
+    protected void assignValue(int x, int y, int z, int id, int meta, World world) {
+        /* assign a block to the world */
+        world.setBlockAndMetadata(x, y, z, id, meta);
+    }
+
+    protected void crossSection(int[] center, double radius, int dirAxis, int id, int data, World world) {
+        int rad = MathHelper.floor(radius + 0.618);
+        int secidx1 = (dirAxis + 1) % 3;
+        int secidx2 = (dirAxis + 2) % 3;
+        int[] coord = new int[3];
+        for (int off1 = -rad; off1 < rad + 1; off1++) {
+            for (int off2 = -rad; off2 < rad + 1; off2++) {
+                double thisDist = Math.sqrt(Math.pow(Math.abs(off1) + 0.5, 2) + Math.pow(Math.abs(off2) + 0.5, 2));
+                if (thisDist > radius) {
+                    continue;
+                }
+                int pri = center[dirAxis];
+                int sec1 = center[secidx1] + off1;
+                int sec2 = center[secidx2] + off2;
+                coord[dirAxis] = pri;
+                coord[secidx1] = sec1;
+                coord[secidx2] = sec2;
+                assignValue(coord[0], coord[1], coord[2], id, data, world);
+            }
+        }
+    }
+
+    protected void taperedLimb(int[] start, int[] end, double startSize, double endSize, World world) {
+        // delta is the coordinate vector for the difference between
+        // start and end.
+        int[] delta = new int[] { end[0] - start[0], end[1] - start[1], end[2] - start[2] };
+        int maxDist = MathHelper.maxAbs(delta[0], MathHelper.maxAbs(delta[1], delta[2]));
+        if (maxDist == 0) {
+            return;
+        }
+        int primidx = 0;
+        for (int i = 0; i < 3; i++) {
+            if (delta[i] == maxDist) {
+                primidx = i;
+                break;
+            }
+        }
+        // secidx1 and secidx2 are the remaining indices out of [0,1,2].
+        int secidx1 = (primidx - 1) % 3;
+        if (secidx1 < 0) secidx1 += 3;
+        int secidx2 = (1 + primidx) % 3;
+        // primSign is the digit 1 or -1 depending on whether the limb is headed
+        // along the positive or negative primidx axis.
+        int primSign = delta[primidx] / Math.abs(delta[primidx]);
+        // secdelta1 and ...2 are the amount the associated values change
+        // for every step along the prime axis.
+        int secdelta1 = delta[secidx1];
+        float secfac1 = ((float)secdelta1) / delta[primidx];
+        int secdelta2 = delta[secidx2];
+        float secfac2 = ((float)secdelta2) / delta[primidx];
+        // Initialize coord
+        int[] coord = new int[3];
+        // Loop through each cross-section along the primary axis,
+        // from start to end.
+        int endOffset = delta[primidx] + primSign;
+        int endOffsetAbs = Math.abs(endOffset);
+        int iterator = Math.abs(primSign);
+        for (int primOffsetAbs = 0; primOffsetAbs < endOffsetAbs; primOffsetAbs += iterator) {
+            int primOffset = primOffsetAbs * primSign;
+            int primLoc = start[primidx] + primOffset;
+            int secloc1 = (int)(start[secidx1] + primOffset * secfac1);
+            int secloc2 = (int)(start[secidx2] + primOffset * secfac2);
+            coord[primidx] = primLoc;
+            coord[secidx1] = secloc1;
+            coord[secidx2] = secloc2;
+            int primDist = Math.abs(delta[primidx]);
+            double radius = endSize + (startSize - endSize) * Math.abs(delta[primidx] - primOffset) / primDist;
+            crossSection(coord, radius, primidx, Blocks.CRYSTAL.id(), 0, world);
+        }
+    }
+
+    @Override
+    public boolean place(World world, Random random, int x, int y, int z)
+    {
+        int x1 = (x + random.nextInt(2)) - random.nextInt(2);
+        int z1 = (z + random.nextInt(2)) - random.nextInt(2);
+        int yF = y;
+
+        int radius = 5;
+
+        int checksForGround = 0;
+        boolean canGenerate = false;
+        while(true){
+            int blockId;
+            if(ceilingGen)
+                blockId = world.getBlockId(x1, yF+1, z1);
+            else
+                blockId = world.getBlockId(x1, yF-1, z1);
+
+            if(checksForGround > 64 || yF <= 0 || yF >= world.getHeightBlocks()) {
+                break;
+            }
+
+            if(Blocks.solid[blockId]){
+                canGenerate = true;
+                break;
+            }
+
+            if(ceilingGen)
+                yF ++;
+            else
+                yF --;
+
+            checksForGround++;
+        }
+
+        if(canGenerate && world.isAirBlock(x1, yF, z1))
+        {
+            int rand = random.nextInt(4);
+            if(ceilingGen){
+                switch (rand) {
+                    case 0:
+                        taperedLimb(new int[]{x1, yF + 2, z1}, new int[]{x1 + 24, yF - 24, z1}, radius, 1, world);
+                        break;
+                    case 1:
+                        taperedLimb(new int[]{x1, yF + 2, z1}, new int[]{x1 - 24, yF - 24, z1}, radius, 1, world);
+                        break;
+                    case 2:
+                        taperedLimb(new int[]{x1, yF + 2, z1}, new int[]{x1, yF - 24, z1 + 24}, radius, 1, world);
+                        break;
+                    case 3:
+                        taperedLimb(new int[]{x1, yF + 2, z1}, new int[]{x1, yF - 24, z - 24}, radius, 1, world);
+                        break;
+                }
+            } else {
+                switch (rand) {
+                    case 0:
+                        taperedLimb(new int[]{x1, yF - 2, z1}, new int[]{x1 + 24, yF + 24, z1}, radius, 1, world);
+                        break;
+                    case 1:
+                        taperedLimb(new int[]{x1, yF - 2, z1}, new int[]{x1 - 24, yF + 24, z1}, radius, 1, world);
+                        break;
+                    case 2:
+                        taperedLimb(new int[]{x1, yF - 2, z1}, new int[]{x1, yF + 24, z1 + 24}, radius, 1, world);
+                        break;
+                    case 3:
+                        taperedLimb(new int[]{x1, yF - 2, z1}, new int[]{x1, yF + 24, z - 24}, radius, 1, world);
+                        break;
+                }
+            }
+
+        }
+
+        return true;
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeaturePillar.java b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeaturePillar.java
new file mode 100644
index 000000000..bcdf2cbff
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/world/generate/feature/WorldFeaturePillar.java
@@ -0,0 +1,173 @@
+package net.minecraft.core.world.generate.feature;
+
+import net.minecraft.core.block.Blocks;
+import net.minecraft.core.util.helper.MathHelper;
+import net.minecraft.core.world.World;
+
+import java.util.Arrays;
+import java.util.Random;
+
+public class WorldFeaturePillar
+    extends WorldFeature {
+    private static final int MAX_RADIUS = 8;
+    private static int diameter(final int radius) {
+        return radius * 2 + 1;
+    }
+    private static final int CIRCLE_SCALE = 4;
+
+    private final int blockId;
+    private final boolean doLava;
+
+    private boolean[] buffer = new boolean[diameter(MAX_RADIUS) * diameter(MAX_RADIUS)];
+
+    public WorldFeaturePillar(int blockId, boolean doLava) {
+        this.blockId = blockId;
+        this.doLava = doLava;
+    }
+
+    @Override
+    public boolean place(World world, Random random, int x, int y, int z) {
+        int y0 = y;
+
+        while (isEmpty(world, x, y0, z)) {
+            y0--;
+        }
+
+        int y1 = y;
+
+        while (isEmpty(world, x, y1, z)) {
+            y1++;
+        }
+
+        y0 -= 5;
+        y1 += 5;
+
+        int length = y1 - y0;
+
+        float r = random.nextFloat();
+        int initRadiusTop = 1 + (int) (r * (MAX_RADIUS - 2));
+        r = random.nextFloat();
+        int initRadiusBottom = initRadiusTop + (int) (r * (MAX_RADIUS - initRadiusTop));
+        int initRadiusMin = (int) (r * Math.min(initRadiusBottom, initRadiusTop) * 0.75f);
+
+        int randomSkip = random.nextInt(8);
+        if (randomSkip == 0) {
+            initRadiusTop = 0;
+            initRadiusMin = 0;
+        } else if (randomSkip == 1) {
+            initRadiusBottom = 0;
+            initRadiusMin = 0;
+        }
+
+        float bufferedRadius = initRadiusBottom;
+        bufferCircle(random, initRadiusBottom);
+        for (int dy = 0; dy <= length; dy++) {
+            float p = (float) dy / length;
+
+            if (randomSkip == 0 && p > 0.5f) {
+                break;
+            }
+            if (randomSkip == 1 && p < 0.5f) {
+                continue;
+            }
+
+            float lerpedRadius = MathHelper.lerp(initRadiusBottom, initRadiusTop, p);
+            float radMod = (MathHelper.cos((2f * p) * MathHelper.PI) / 2 + 0.5f) * 0.75f + 0.25f;
+            float currentRadius = ((int) (lerpedRadius * radMod * CIRCLE_SCALE)) / (float) CIRCLE_SCALE;
+            if (currentRadius < initRadiusMin) {
+                currentRadius = initRadiusMin;
+            }
+            if (currentRadius != bufferedRadius) {
+                bufferCircle(random, currentRadius);
+                bufferedRadius = currentRadius;
+            }
+
+            for (int dx = 0; dx < diameter(MAX_RADIUS); dx++) {
+                for (int dz = 0; dz < diameter(MAX_RADIUS); dz++) {
+                    if (getBufferPoint(dx, dz)) {
+                        world.setBlockWithNotify(x - MAX_RADIUS + dx, y0 + dy, z - MAX_RADIUS + dz, blockId);
+                    }
+                }
+            }
+        }
+
+        if (doLava) {
+            int largerRadius = Math.max(initRadiusTop, initRadiusBottom);
+            int numFeatures = 8 + random.nextInt(20);
+            for (int i = 0; i < numFeatures; i++) {
+                int rx = random.nextInt(diameter(largerRadius));
+                int rz = random.nextInt(diameter(largerRadius));
+                int ry = random.nextInt(length);
+                if (world.getBlockId(x - largerRadius + rx, y0 + ry, z - largerRadius + rz) == Blocks.COBBLE_NETHERRACK.id()) {
+                    world.setBlockWithNotify(x - largerRadius + rx, y0 + ry, z - largerRadius + rz, Blocks.FLUID_LAVA_FLOWING.id());
+                    world.scheduledUpdatesAreImmediate = true;
+                    Blocks.FLUID_LAVA_FLOWING.updateTick(world, x - largerRadius + rx, y0 + ry, z - largerRadius + rz, random);
+                    world.scheduledUpdatesAreImmediate = false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    private boolean isEmpty(World world, int x, int y, int z) {
+        if (y < 0 || y >= world.getHeightBlocks()) {
+            return false;
+        }
+        return !Blocks.solid[world.getBlockId(x, y, z)];
+    }
+
+    private void bufferCircle(Random rand, final float r) {
+        Arrays.fill(buffer, false);
+
+        if (r <= 0.5) {
+            return;
+        }
+
+        int ir = (int) (r * CIRCLE_SCALE);
+
+        final int x1 = MAX_RADIUS + 1;
+        final int y1 = x1;
+
+        int x = 0, y = ir;
+        int p = 1 - ir;
+
+        plotPoints(x1, y1, x / CIRCLE_SCALE, y / CIRCLE_SCALE);
+
+        while (x < y)
+        {
+            x++;
+            if (p < 0)
+                p += 2 * x + 1;
+            else
+            {
+                y--;
+                p += 2 * (x - y) + 1;
+            }
+
+            plotPoints(x1, y1, x / CIRCLE_SCALE, y / CIRCLE_SCALE);
+        }
+    }
+
+    private void drawLine(int x0, int x1, int y, boolean value) {
+        for (int x = x0; x <= x1; x++) {
+            setBufferPoint(x, y, value);
+        }
+    }
+
+    private void plotPoints(int cx, int cy, int x, int y) {
+        drawLine(cx - x, cx + x, cy - y, true);
+        drawLine(cx - x, cx + x, cy + y, true);
+        drawLine(cx - y, cx + y, cy - x, true);
+        drawLine(cx - y, cx + y, cy + x, true);
+    }
+
+    private void setBufferPoint(int x, int y, boolean value) {
+        buffer[x * diameter(MAX_RADIUS) + y] = value;
+    }
+
+    private boolean getBufferPoint(int x, int y) {
+        return buffer[x * diameter(MAX_RADIUS) + y];
+    }
+
+}
diff --git a/game/core/src/main/java/net/minecraft/core/world/noise/BasePerlinNoise.java b/game/core/src/main/java/net/minecraft/core/world/noise/BasePerlinNoise.java
index 2f28130a8..6657ee29f 100644
--- a/game/core/src/main/java/net/minecraft/core/world/noise/BasePerlinNoise.java
+++ b/game/core/src/main/java/net/minecraft/core/world/noise/BasePerlinNoise.java
@@ -6,19 +6,19 @@ import java.util.Random;
 public abstract class BasePerlinNoise<T extends BaseImprovedNoise>
     extends SurfaceNoise
 {
-    private final T[] noiseLevels;
-    private final int levels;
+    private final T[] octaves;
+    private final int numOctaves;
 
-    public BasePerlinNoise(long seed, int levels)
+    public BasePerlinNoise(long seed, int numOctaves)
     {
-        this(seed, levels, 0);
+        this(seed, numOctaves, 0);
     }
 
-    public BasePerlinNoise(long seed, int levels, int preLevels)
+    public BasePerlinNoise(long seed, int numOctaves, int preOctaves)
     {
         Random random = new Random(seed);
 
-        for (int i = 0; i < preLevels; i++)
+        for (int i = 0; i < preOctaves; i++)
         {
             random.nextDouble();
             random.nextDouble();
@@ -29,20 +29,20 @@ public abstract class BasePerlinNoise<T extends BaseImprovedNoise>
                 random.nextInt(256 - j);
             }
         }
-        this.levels = levels;
-        this.noiseLevels = newNoiseLevels(random, levels);
+        this.numOctaves = numOctaves;
+        this.octaves = newOctaves(random, numOctaves);
     }
 
-    protected abstract T[] newNoiseLevels(Random random, int numLevels);
+    protected abstract T[] newOctaves(Random random, int numOctaves);
 
     public double get(double x, double y)
     {
         double out = 0.0D;
         double levelScale = 1.0D;
 
-        for(int i = 0; i < levels; i++)
+        for(int i = 0; i < numOctaves; i++)
         {
-            out += noiseLevels[i].getValue(x * levelScale, y * levelScale) / levelScale;
+            out += octaves[i].getValue(x * levelScale, y * levelScale) / levelScale;
             levelScale /= 2;
         }
 
@@ -62,11 +62,11 @@ public abstract class BasePerlinNoise<T extends BaseImprovedNoise>
             Arrays.fill(noiseArray, 0.0D);
         }
 
-        double levelScale = 1.0D;
-        for(int i = 0; i < levels; i++)
+        double frequency = 1.0D;
+        for(int i = 0; i < numOctaves; i++)
         {
-            noiseLevels[i].add(noiseArray, x, y, z, xSize, ySize, zSize, scaleX * levelScale, scaleY * levelScale, scaleZ * levelScale, levelScale);
-            levelScale /= 2D;
+            octaves[i].add(noiseArray, x, y, z, xSize, ySize, zSize, scaleX * frequency, scaleY * frequency, scaleZ * frequency, frequency);
+            frequency /= 2D;
         }
 
         return noiseArray;
diff --git a/game/core/src/main/java/net/minecraft/core/world/noise/PerlinNoise.java b/game/core/src/main/java/net/minecraft/core/world/noise/PerlinNoise.java
index ac319d1e4..daeb826c7 100644
--- a/game/core/src/main/java/net/minecraft/core/world/noise/PerlinNoise.java
+++ b/game/core/src/main/java/net/minecraft/core/world/noise/PerlinNoise.java
@@ -16,11 +16,11 @@ public class PerlinNoise
     }
 
     @Override
-    protected ImprovedNoise[] newNoiseLevels(Random random, int numLevels)
+    protected ImprovedNoise[] newOctaves(Random random, int numOctaves)
     {
-        ImprovedNoise[] levels = new ImprovedNoise[numLevels];
+        ImprovedNoise[] levels = new ImprovedNoise[numOctaves];
 
-        for (int i = 0; i < numLevels; i++)
+        for (int i = 0; i < numOctaves; i++)
         {
             levels[i] = new ImprovedNoise(random);
         }
diff --git a/game/core/src/main/java/net/minecraft/core/world/noise/PerlinSimplexNoise.java b/game/core/src/main/java/net/minecraft/core/world/noise/PerlinSimplexNoise.java
index 9f39edaa8..92ac51555 100644
--- a/game/core/src/main/java/net/minecraft/core/world/noise/PerlinSimplexNoise.java
+++ b/game/core/src/main/java/net/minecraft/core/world/noise/PerlinSimplexNoise.java
@@ -5,16 +5,16 @@ import java.util.Random;
 
 public class PerlinSimplexNoise extends SurfaceNoise
 {
-    private final SimplexNoise[] noiseLevels;
-    private final int levels;
+    private final SimplexNoise[] octaves;
+    private final int numOctaves;
 
-    public PerlinSimplexNoise(Random random, int levels)
+    public PerlinSimplexNoise(Random random, int numOctaves)
     {
-        this.levels = levels;
-        noiseLevels = new SimplexNoise[levels];
-        for(int i = 0; i < levels; i++)
+        this.numOctaves = numOctaves;
+        octaves = new SimplexNoise[numOctaves];
+        for(int i = 0; i < numOctaves; i++)
         {
-            noiseLevels[i] = new SimplexNoise(random);
+            octaves[i] = new SimplexNoise(random);
         }
     }
 
@@ -23,27 +23,28 @@ public class PerlinSimplexNoise extends SurfaceNoise
         return getMaximumValue(0.5);
     }
 
-    // Gets the maximum value for a given value of d5. For those values, the range is from -getMaximumValue to getMaximumValue.
-    public double getMaximumValue(double d5)
+    // Gets the maximum value for a given value of amplitudeScale. For those values, the range is from -getMaximumValue to getMaximumValue.
+    public double getMaximumValue(double amplitudeScale)
     {
         double max = 0.0;
 
-        double scaleB = 1.0;
-        for (int l = 0; l < levels; l++)
+        double amplitude = 1.0;
+        for (int l = 0; l < numOctaves; l++)
         {
-            max += 1.0 * (0.55D / scaleB);
-            scaleB *= d5;
+            max += 0.55D / amplitude;
+            amplitude *= amplitudeScale;
         }
 
         return max;
     }
 
-    public double[] getValue(double[] out, double x, double z, int xSize, int zSize, double xScale, double zScale, double d4)
+    public double[] getValue(double[] out, double x, double z, int xSize, int zSize, double xScale, double zScale, double frequencyScale)
     {
-        return getValue(out, x, z, xSize, zSize, xScale, zScale, d4, 0.5D);
+        return getValue(out, x, z, xSize, zSize, xScale, zScale, frequencyScale, 0.5D);
     }
 
-    public double[] getValue(double[] out, double x, double z, int xSize, int zSize, double xScale, double zScale, double d4, double d5)
+    // Uses Fractal Brownian Motion to refine the noise.
+    public double[] getValue(double[] out, double x, double z, int xSize, int zSize, double xScale, double zScale, double frequencyScale, double amplitudeScale)
     {
         xScale /= 1.5D;
         zScale /= 1.5D;
@@ -57,13 +58,13 @@ public class PerlinSimplexNoise extends SurfaceNoise
             Arrays.fill(out, 0.0D);
         }
 
-        double scaleA = 1.0D;
-        double scaleB = 1.0D;
-        for(int l = 0; l < levels; l++)
+        double frequency = 1.0D;
+        double amplitude = 1.0D;
+        for(int l = 0; l < numOctaves; l++)
         {
-            noiseLevels[l].add(out, x, z, xSize, zSize, xScale * scaleA, zScale * scaleA, 0.55D / scaleB);
-            scaleA *= d4;
-            scaleB *= d5;
+            octaves[l].add(out, x, z, xSize, zSize, xScale * frequency, zScale * frequency, 0.55D / amplitude);
+            frequency *= frequencyScale;
+            amplitude *= amplitudeScale;
         }
 
         return out;
diff --git a/game/core/src/main/java/net/minecraft/core/world/noise/RetroPerlinNoise.java b/game/core/src/main/java/net/minecraft/core/world/noise/RetroPerlinNoise.java
index 5620888dc..7e1bd7b75 100644
--- a/game/core/src/main/java/net/minecraft/core/world/noise/RetroPerlinNoise.java
+++ b/game/core/src/main/java/net/minecraft/core/world/noise/RetroPerlinNoise.java
@@ -16,11 +16,11 @@ public class RetroPerlinNoise
 	}
 
 	@Override
-	protected RetroImprovedNoise[] newNoiseLevels(Random random, int numLevels)
+	protected RetroImprovedNoise[] newOctaves(Random random, int numOctaves)
 	{
-		RetroImprovedNoise[] levels = new RetroImprovedNoise[numLevels];
+		RetroImprovedNoise[] levels = new RetroImprovedNoise[numOctaves];
 
-		for (int i = 0; i < numLevels; i++)
+		for (int i = 0; i < numOctaves; i++)
 		{
 			levels[i] = new RetroImprovedNoise(random);
 		}
diff --git a/game/core/src/main/java/net/minecraft/core/world/noise/SimplexNoise.java b/game/core/src/main/java/net/minecraft/core/world/noise/SimplexNoise.java
index 6859b84f3..fd9bff937 100644
--- a/game/core/src/main/java/net/minecraft/core/world/noise/SimplexNoise.java
+++ b/game/core/src/main/java/net/minecraft/core/world/noise/SimplexNoise.java
@@ -49,8 +49,8 @@ public class SimplexNoise
         return (double)vec[0] * x + (double)vec[1] * y;
     }
 
-    // Values generated are guaranteed to be between -1.0 and 1.0, multiplied by valueScale.
-    public void add(double[] out, double xOffset, double zOffset, int xSize, int zSize, double xScale, double zScale, double valueScale)
+    // Values generated are guaranteed to be between -1.0 and 1.0, multiplied by amplitude.
+    public void add(double[] out, double xOffset, double zOffset, int xSize, int zSize, double xScale, double zScale, double amplitude)
     {
         int index = 0;
         for(int dx = 0; dx < xSize; dx++)
@@ -142,7 +142,7 @@ public class SimplexNoise
                 }
 
                 double value = 70 * (n0 + n1 + n2);
-                out[index++] += value * valueScale;
+                out[index++] += value * amplitude;
             }
         }
     }
diff --git a/game/core/src/main/java/net/minecraft/core/world/type/nether/WorldTypeNether.java b/game/core/src/main/java/net/minecraft/core/world/type/nether/WorldTypeNether.java
index 3d9c5dec3..3365e982b 100644
--- a/game/core/src/main/java/net/minecraft/core/world/type/nether/WorldTypeNether.java
+++ b/game/core/src/main/java/net/minecraft/core/world/type/nether/WorldTypeNether.java
@@ -4,6 +4,7 @@ import net.minecraft.core.block.Blocks;
 import net.minecraft.core.world.World;
 import net.minecraft.core.world.biome.Biomes;
 import net.minecraft.core.world.biome.provider.BiomeProvider;
+import net.minecraft.core.world.biome.provider.BiomeProviderNether;
 import net.minecraft.core.world.biome.provider.BiomeProviderSingleBiome;
 import net.minecraft.core.world.generate.chunk.ChunkGenerator;
 import net.minecraft.core.world.generate.chunk.perlin.nether.ChunkGeneratorNether;
@@ -26,7 +27,7 @@ public class WorldTypeNether
 
     private static float[] getLightRamp() {
         float[] brightnessRamp = new float[32];
-        float f = 0.1F;
+        float f = 0.15F;
         for (int i = 0; i <= 15; i++) {
             float f1 = 1.0F - (float) i / 15F;
             brightnessRamp[i] = ((1.0F - f1) / (f1 * 3F + 1.0F)) * (1.0F - f) + f;
@@ -43,7 +44,8 @@ public class WorldTypeNether
 
     @Override
     public BiomeProvider createBiomeProvider(World world) {
-        return new BiomeProviderSingleBiome(Biomes.NETHER_NETHER, 1.0, 0.0, 1.0);
+//        return new BiomeProviderSingleBiome(Biomes.NETHER_NETHER, 1.0, 0.0, 1.0);
+        return new BiomeProviderNether(world.getRandomSeed());
     }
 
     @Override
diff --git a/game/core/src/main/resources/lang/en_US/biomes.lang b/game/core/src/main/resources/lang/en_US/biomes.lang
index 4c56f2bb5..88866f1df 100644
--- a/game/core/src/main/resources/lang/en_US/biomes.lang
+++ b/game/core/src/main/resources/lang/en_US/biomes.lang
@@ -1,24 +1,29 @@
-biome.rainforest=Rainforest
-biome.swampland=Swamp
-biome.seasonal_forest=Seasonal Forest
-biome.forest=Forest
-biome.forest.dry=Dry Forest
-biome.grasslands=Grasslands
-biome.outback=Outback
-biome.shrubland=Shrubland
-biome.taiga=Taiga
-biome.boreal_forest=Boreal Forest
-biome.desert=Desert
-biome.plains=Plains
-biome.glacier=Glacier
-biome.tundra=Tundra
-biome.meadow=Meadow
-biome.nether=Nether
-biome.paradise=Paradise
-biome.birch_forest=Birch Forest
-biome.retro=Retro
-biome.hell=Hell
-biome.swampland.muddy=Muddy Swamp
-biome.outback.grassy=Grassy Outback
-biome.caatinga=Caatinga
-biome.caatinga.plains=Caatinga Plains
\ No newline at end of file
+biome.overworld.rainforest=Rainforest
+biome.overworld.swampland=Swamp
+biome.overworld.seasonal_forest=Seasonal Forest
+biome.overworld.forest=Forest
+biome.overworld.forest.dry=Dry Forest
+biome.overworld.grasslands=Grasslands
+biome.overworld.outback=Outback
+biome.overworld.shrubland=Shrubland
+biome.overworld.taiga=Taiga
+biome.overworld.boreal_forest=Boreal Forest
+biome.overworld.desert=Desert
+biome.overworld.plains=Plains
+biome.overworld.glacier=Glacier
+biome.overworld.tundra=Tundra
+biome.overworld.meadow=Meadow
+biome.overworld.paradise=Paradise
+biome.overworld.birch_forest=Birch Forest
+biome.overworld.retro=Retro
+biome.overworld.hell=Hell
+biome.overworld.swampland.muddy=Muddy Swamp
+biome.overworld.outback.grassy=Grassy Outback
+biome.overworld.caatinga=Caatinga
+biome.overworld.caatinga.plains=Caatinga Plains
+biome.nether.nether=Nether
+biome.nether.volcanic_islands=Volcanic Islands
+biome.nether.sulfur_pools=Sulfur Pools
+biome.nether.crystal_forest=Crystal Forest
+biome.nether.ash_plains=Ash Plains
+biome.nether.boneyard=Boneyard
\ No newline at end of file
diff --git a/game/core/src/main/resources/lang/en_US/guidebook.lang b/game/core/src/main/resources/lang/en_US/guidebook.lang
index 8c2ce2cd7..dcbbbf22b 100644
--- a/game/core/src/main/resources/lang/en_US/guidebook.lang
+++ b/game/core/src/main/resources/lang/en_US/guidebook.lang
@@ -40,6 +40,8 @@ guidebook.section.mob.wolf.name=Wolf
 guidebook.section.mob.wolf.desc=Wolves are a passive mob that can be tamed using bones.\nThey can be found in colder biomes, such as taigas and boreal forests.
 guidebook.section.mob.firefly_cluster.name=Firefly
 guidebook.section.mob.firefly_cluster.desc=Fireflies appear as a shimmering cloud of light, and can be caught in jars to create lanterns.\nThey can be found in many biomes.
+guidebook.section.mob.butterfly.name=Butterfly
+guidebook.section.mob.butterfly.desc=Butterflies are a small insect that flies around from flower to flower, and can be caught in jars.
 guidebook.section.mob.zombie.name=Zombie
 guidebook.section.mob.zombie.desc=Zombies are a common enemy that appear at night and in darkness. They pack a strong punch, so be careful!
 guidebook.section.mob.creeper.name=Creeper
diff --git a/game/core/src/main/resources/lang/en_US/item.lang b/game/core/src/main/resources/lang/en_US/item.lang
index cb71745cc..f48e92eb3 100644
--- a/game/core/src/main/resources/lang/en_US/item.lang
+++ b/game/core/src/main/resources/lang/en_US/item.lang
@@ -422,4 +422,16 @@ item.wand.monster.name=Summoning Wand
 item.wand.monster.desc=A magical tool allowing the wielder to manifest living entities.
 
 item.paintbrush.name=Paintbrush
-item.paintbrush.desc=Can be loaded with dye and used to paint blocks.
\ No newline at end of file
+item.paintbrush.desc=Can be loaded with dye and used to paint blocks.
+
+item.jar.butterfly.blue.name=Blue Butterfly Jar
+item.jar.butterfly.blue.desc=A glass jar containing a blue butterfly.
+item.jar.butterfly.orange.name=Orange Butterfly Jar
+item.jar.butterfly.orange.desc=A glass jar containing a orange butterfly.
+item.jar.butterfly.pink.name=Pink Butterfly Jar
+item.jar.butterfly.pink.desc=A glass jar containing a pink butterfly.
+item.jar.butterfly.silver.name=Silver Butterfly Jar
+item.jar.butterfly.silver.desc=A glass jar containing a silver butterfly.
+
+item.statue.stone.name=Stone Statue
+item.statue.stone.desc=A decorative stone statue for you to hang your armor.
diff --git a/game/core/src/main/resources/lang/en_US/keyboard.json b/game/core/src/main/resources/lang/en_US/keyboard.json
new file mode 100644
index 000000000..227f166df
--- /dev/null
+++ b/game/core/src/main/resources/lang/en_US/keyboard.json
@@ -0,0 +1,1045 @@
+{
+  "keys": [
+    {
+      "type": "key_char",
+      "x": 0,
+      "y": 0,
+      "groups": [
+        [
+          {
+            "lower": 49,
+            "upper": 49
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_char",
+      "x": 18,
+      "y": 0,
+      "groups": [
+        [
+          {
+            "lower": 50,
+            "upper": 50
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_char",
+      "x": 36,
+      "y": 0,
+      "groups": [
+        [
+          {
+            "lower": 51,
+            "upper": 51
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_char",
+      "x": 54,
+      "y": 0,
+      "groups": [
+        [
+          {
+            "lower": 52,
+            "upper": 52
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_char",
+      "x": 72,
+      "y": 0,
+      "groups": [
+        [
+          {
+            "lower": 53,
+            "upper": 53
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_char",
+      "x": 90,
+      "y": 0,
+      "groups": [
+        [
+          {
+            "lower": 54,
+            "upper": 54
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_char",
+      "x": 108,
+      "y": 0,
+      "groups": [
+        [
+          {
+            "lower": 55,
+            "upper": 55
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_char",
+      "x": 126,
+      "y": 0,
+      "groups": [
+        [
+          {
+            "lower": 56,
+            "upper": 56
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_char",
+      "x": 144,
+      "y": 0,
+      "groups": [
+        [
+          {
+            "lower": 57,
+            "upper": 57
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_char",
+      "x": 162,
+      "y": 0,
+      "groups": [
+        [
+          {
+            "lower": 48,
+            "upper": 48
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_char",
+      "x": 0,
+      "y": 18,
+      "groups": [
+        [
+          {
+            "lower": 113,
+            "upper": 81
+          }
+        ],
+        [
+          {
+            "lower": 33,
+            "upper": 33
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_char",
+      "x": 18,
+      "y": 18,
+      "groups": [
+        [
+          {
+            "lower": 119,
+            "upper": 87
+          }
+        ],
+        [
+          {
+            "lower": 64,
+            "upper": 64
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_char",
+      "x": 36,
+      "y": 18,
+      "groups": [
+        [
+          {
+            "lower": 101,
+            "upper": 69
+          },
+          {
+            "lower": 279,
+            "upper": 278
+          },
+          {
+            "lower": 281,
+            "upper": 280
+          },
+          {
+            "lower": 283,
+            "upper": 282
+          },
+          {
+            "lower": 277,
+            "upper": 276
+          },
+          {
+            "lower": 601,
+            "upper": 399
+          },
+          {
+            "lower": 232,
+            "upper": 200
+          },
+          {
+            "lower": 233,
+            "upper": 201
+          },
+          {
+            "lower": 234,
+            "upper": 202
+          },
+          {
+            "lower": 235,
+            "upper": 203
+          },
+          {
+            "lower": 275,
+            "upper": 274
+          }
+        ],
+        [
+          {
+            "lower": 35,
+            "upper": 35
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_char",
+      "x": 54,
+      "y": 18,
+      "groups": [
+        [
+          {
+            "lower": 114,
+            "upper": 82
+          },
+          {
+            "lower": 341,
+            "upper": 340
+          },
+          {
+            "lower": 345,
+            "upper": 344
+          }
+        ],
+        [
+          {
+            "lower": 36,
+            "upper": 36
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_char",
+      "x": 72,
+      "y": 18,
+      "groups": [
+        [
+          {
+            "lower": 116,
+            "upper": 84
+          },
+          {
+            "lower": 254,
+            "upper": 222
+          },
+          {
+            "lower": 357,
+            "upper": 356
+          },
+          {
+            "lower": 539,
+            "upper": 538
+          },
+          {
+            "lower": 355,
+            "upper": 354
+          }
+        ],
+        [
+          {
+            "lower": 37,
+            "upper": 37
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_char",
+      "x": 90,
+      "y": 18,
+      "groups": [
+        [
+          {
+            "lower": 121,
+            "upper": 89
+          },
+          {
+            "lower": 253,
+            "upper": 221
+          }
+        ],
+        [
+          {
+            "lower": 94,
+            "upper": 94
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_char",
+      "x": 108,
+      "y": 18,
+      "groups": [
+        [
+          {
+            "lower": 117,
+            "upper": 85
+          },
+          {
+            "lower": 371,
+            "upper": 370
+          },
+          {
+            "lower": 369,
+            "upper": 368
+          },
+          {
+            "lower": 367,
+            "upper": 366
+          },
+          {
+            "lower": 363,
+            "upper": 362
+          },
+          {
+            "lower": 252,
+            "upper": 220
+          },
+          {
+            "lower": 251,
+            "upper": 219
+          },
+          {
+            "lower": 250,
+            "upper": 218
+          },
+          {
+            "lower": 249,
+            "upper": 217
+          }
+        ],
+        [
+          {
+            "lower": 38,
+            "upper": 38
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_char",
+      "x": 126,
+      "y": 18,
+      "groups": [
+        [
+          {
+            "lower": 105,
+            "upper": 73
+          },
+          {
+            "lower": 305,
+            "upper": 73
+          },
+          {
+            "lower": 303,
+            "upper": 302
+          },
+          {
+            "lower": 299,
+            "upper": 298
+          },
+          {
+            "lower": 239,
+            "upper": 207
+          },
+          {
+            "lower": 237,
+            "upper": 205
+          },
+          {
+            "lower": 237,
+            "upper": 205
+          },
+          {
+            "lower": 236,
+            "upper": 204
+          }
+        ],
+        [
+          {
+            "lower": 42,
+            "upper": 42
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_char",
+      "x": 144,
+      "y": 18,
+      "groups": [
+        [
+          {
+            "lower": 111,
+            "upper": 79
+          },
+          {
+            "lower": 339,
+            "upper": 338
+          },
+          {
+            "lower": 337,
+            "upper": 336
+          },
+          {
+            "lower": 333,
+            "upper": 332
+          },
+          {
+            "lower": 248,
+            "upper": 216
+          },
+          {
+            "lower": 246,
+            "upper": 214
+          },
+          {
+            "lower": 245,
+            "upper": 213
+          },
+          {
+            "lower": 244,
+            "upper": 212
+          },
+          {
+            "lower": 243,
+            "upper": 211
+          },
+          {
+            "lower": 242,
+            "upper": 210
+          }
+        ],
+        [
+          {
+            "lower": 40,
+            "upper": 40
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_char",
+      "x": 162,
+      "y": 18,
+      "groups": [
+        [
+          {
+            "lower": 112,
+            "upper": 80
+          }
+        ],
+        [
+          {
+            "lower": 41,
+            "upper": 41
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_char",
+      "x": 0,
+      "y": 36,
+      "groups": [
+        [
+          {
+            "lower": 97,
+            "upper": 65
+          },
+          {
+            "lower": 229,
+            "upper": 197
+          },
+          {
+            "lower": 230,
+            "upper": 198
+          },
+          {
+            "lower": 257,
+            "upper": 256
+          },
+          {
+            "lower": 259,
+            "upper": 258
+          },
+          {
+            "lower": 261,
+            "upper": 260
+          },
+          {
+            "lower": 170,
+            "upper": 170
+          },
+          {
+            "lower": 224,
+            "upper": 192
+          },
+          {
+            "lower": 225,
+            "upper": 193
+          },
+          {
+            "lower": 226,
+            "upper": 194
+          },
+          {
+            "lower": 227,
+            "upper": 195
+          },
+          {
+            "lower": 228,
+            "upper": 196
+          }
+        ],
+        [
+          {
+            "lower": 126,
+            "upper": 126
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_char",
+      "x": 18,
+      "y": 36,
+      "groups": [
+        [
+          {
+            "lower": 115,
+            "upper": 83
+          },
+          {
+            "lower": 223,
+            "upper": 223
+          },
+          {
+            "lower": 347,
+            "upper": 346
+          },
+          {
+            "lower": 353,
+            "upper": 352
+          },
+          {
+            "lower": 351,
+            "upper": 350
+          }
+        ],
+        [
+          {
+            "lower": 39,
+            "upper": 39
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_char",
+      "x": 36,
+      "y": 36,
+      "groups": [
+        [
+          {
+            "lower": 100,
+            "upper": 68
+          },
+          {
+            "lower": 271,
+            "upper": 270
+          },
+          {
+            "lower": 273,
+            "upper": 272
+          }
+        ],
+        [
+          {
+            "lower": 34,
+            "upper": 34
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_char",
+      "x": 54,
+      "y": 36,
+      "groups": [
+        [
+          {
+            "lower": 102,
+            "upper": 70
+          }
+        ],
+        [
+          {
+            "lower": 123,
+            "upper": 123
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_char",
+      "x": 72,
+      "y": 36,
+      "groups": [
+        [
+          {
+            "lower": 103,
+            "upper": 71
+          },
+          {
+            "lower": 291,
+            "upper": 290
+          },
+          {
+            "lower": 287,
+            "upper": 286
+          }
+        ],
+        [
+          {
+            "lower": 125,
+            "upper": 125
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_char",
+      "x": 90,
+      "y": 36,
+      "groups": [
+        [
+          {
+            "lower": 104,
+            "upper": 72
+          }
+        ],
+        [
+          {
+            "lower": 91,
+            "upper": 91
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_char",
+      "x": 108,
+      "y": 36,
+      "groups": [
+        [
+          {
+            "lower": 106,
+            "upper": 74
+          }
+        ],
+        [
+          {
+            "lower": 93,
+            "upper": 93
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_char",
+      "x": 126,
+      "y": 36,
+      "groups": [
+        [
+          {
+            "lower": 107,
+            "upper": 75
+          },
+          {
+            "lower": 311,
+            "upper": 310
+          }
+        ],
+        [
+          {
+            "lower": 58,
+            "upper": 58
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_char",
+      "x": 144,
+      "y": 36,
+      "groups": [
+        [
+          {
+            "lower": 108,
+            "upper": 76
+          },
+          {
+            "lower": 314,
+            "upper": 313
+          },
+          {
+            "lower": 316,
+            "upper": 315
+          },
+          {
+            "lower": 318,
+            "upper": 317
+          },
+          {
+            "lower": 322,
+            "upper": 321
+          }
+        ],
+        [
+          {
+            "lower": 59,
+            "upper": 59
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_char",
+      "x": 162,
+      "y": 36,
+      "groups": [
+        [
+          {
+            "lower": 47,
+            "upper": 47
+          }
+        ],
+        [
+          {
+            "lower": 92,
+            "upper": 92
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_mod",
+      "x": 0,
+      "y": 54,
+      "width": 18,
+      "button": "button_trigger_left"
+    },
+    {
+      "type": "key_char",
+      "x": 18,
+      "y": 54,
+      "groups": [
+        [
+          {
+            "lower": 122,
+            "upper": 90
+          },
+          {
+            "lower": 378,
+            "upper": 377
+          },
+          {
+            "lower": 380,
+            "upper": 379
+          },
+          {
+            "lower": 382,
+            "upper": 381
+          }
+        ],
+        [
+          {
+            "lower": 96,
+            "upper": 96
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_char",
+      "x": 36,
+      "y": 54,
+      "groups": [
+        [
+          {
+            "lower": 120,
+            "upper": 88
+          }
+        ],
+        [
+          {
+            "lower": 124,
+            "upper": 124
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_char",
+      "x": 54,
+      "y": 54,
+      "groups": [
+        [
+          {
+            "lower": 99,
+            "upper": 67
+          },
+          {
+            "lower": 231,
+            "upper": 199
+          },
+          {
+            "lower": 263,
+            "upper": 262
+          },
+          {
+            "lower": 269,
+            "upper": 268
+          }
+        ],
+        [
+          {
+            "lower": 167,
+            "upper": 167
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_char",
+      "x": 72,
+      "y": 54,
+      "groups": [
+        [
+          {
+            "lower": 118,
+            "upper": 86
+          }
+        ],
+        [
+          {
+            "lower": 43,
+            "upper": 43
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_char",
+      "x": 90,
+      "y": 54,
+      "groups": [
+        [
+          {
+            "lower": 98,
+            "upper": 66
+          }
+        ],
+        [
+          {
+            "lower": 45,
+            "upper": 45
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_char",
+      "x": 108,
+      "y": 54,
+      "groups": [
+        [
+          {
+            "lower": 110,
+            "upper": 78
+          },
+          {
+            "lower": 241,
+            "upper": 209
+          },
+          {
+            "lower": 324,
+            "upper": 323
+          },
+          {
+            "lower": 326,
+            "upper": 325
+          },
+          {
+            "lower": 328,
+            "upper": 327
+          }
+        ],
+        [
+          {
+            "lower": 60,
+            "upper": 60
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_char",
+      "x": 126,
+      "y": 54,
+      "groups": [
+        [
+          {
+            "lower": 109,
+            "upper": 77
+          }
+        ],
+        [
+          {
+            "lower": 62,
+            "upper": 62
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_char",
+      "x": 144,
+      "y": 54,
+      "groups": [
+        [
+          {
+            "lower": 46,
+            "upper": 46
+          }
+        ],
+        [
+          {
+            "lower": 63,
+            "upper": 63
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_enter",
+      "x": 162,
+      "y": 54,
+      "width": 18,
+      "button": "button_start"
+    },
+    {
+      "type": "key_group",
+      "x": 0,
+      "y": 72,
+      "width": 25,
+      "button": "button_stick_left",
+      "textures": {
+        "up": "minecraft:gui/widgets/keyboard/key_alt/up",
+        "down": "minecraft:gui/widgets/keyboard/key_alt/down",
+        "up_hovered": "minecraft:gui/widgets/keyboard/key_alt/up_hovered",
+        "down_hovered": "minecraft:gui/widgets/keyboard/key_alt/down_hovered"
+      },
+      "mode": "toggle",
+      "page1": 0,
+      "page2": 1
+    },
+    {
+      "type": "key_cursor",
+      "x": 26,
+      "y": 72,
+      "width": 18,
+      "button": "button_shoulder_left",
+      "textures": {
+        "up": "minecraft:gui/widgets/keyboard/key_page_last/up",
+        "down": "minecraft:gui/widgets/keyboard/key_page_last/down",
+        "up_hovered": "minecraft:gui/widgets/keyboard/key_page_last/up_hovered",
+        "down_hovered": "minecraft:gui/widgets/keyboard/key_page_last/down_hovered"
+      },
+      "offset": -1
+    },
+    {
+      "type": "key_char",
+      "x": 45,
+      "y": 72,
+      "width": 90,
+      "button": "button_y",
+      "groups": [
+        [
+          {
+            "lower": 32,
+            "upper": 32
+          }
+        ]
+      ]
+    },
+    {
+      "type": "key_cursor",
+      "x": 136,
+      "y": 72,
+      "width": 18,
+      "button": "button_shoulder_right",
+      "textures": {
+        "up": "minecraft:gui/widgets/keyboard/key_page_next/up",
+        "down": "minecraft:gui/widgets/keyboard/key_page_next/down",
+        "up_hovered": "minecraft:gui/widgets/keyboard/key_page_next/up_hovered",
+        "down_hovered": "minecraft:gui/widgets/keyboard/key_page_next/down_hovered"
+      },
+      "offset": 1
+    },
+    {
+      "type": "key_back",
+      "x": 155,
+      "y": 72,
+      "button": "button_b"
+    }
+  ]
+}
\ No newline at end of file
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 29b517472..644440c4b 100644
--- a/game/core/src/main/resources/lang/en_US/options.lang
+++ b/game/core/src/main/resources/lang/en_US/options.lang
@@ -170,6 +170,8 @@ options.chatVisibility=Chat Visibility
 options.chatVisibility.show_all=Always Show
 options.chatVisibility.show_when_chatting=Only Show When Chatting
 options.chatVisibility.never_show=Never Show
+options.developerMode=Developer Mode
+options.forceOnscreenKeyboard=Force On Screen Keyboard
 options.enableHolidayTextures=Enable Holiday Textures
 
 options.tooltipStyle.default=Default
@@ -240,6 +242,7 @@ key.toggleOverlay=Toggle Overlays
 key.sortInventory=Sort Inventory
 key.openAchievements=Open Achievements Menu
 key.openStatistics=Open Statistics Menu
+key.showConsole=Show Console
 key.disabled=Disabled
 key.debug.textureAtlas=Show Texture Atlas
 key.debug.reload=Reload textures
diff --git a/game/core/src/main/resources/lang/en_US/stats.lang b/game/core/src/main/resources/lang/en_US/stats.lang
index 78ae41fe9..8543920a4 100644
--- a/game/core/src/main/resources/lang/en_US/stats.lang
+++ b/game/core/src/main/resources/lang/en_US/stats.lang
@@ -116,6 +116,6 @@ achievement.sleepNether.desc=Try to sleep in the Nether.
 achievement.lightSign=Glow in the Dark
 achievement.lightSign.desc=Apply Glowstone Dust to a sign.
 achievement.mostWanted=Most Wanted
-achievement.mostWanted.desc=Have more then 10 Zombie Pigmen attack you.
+achievement.mostWanted.desc=Have more than 10 Zombie Pigmen attack you.
 achievement.fastTravel=Dimensional Slingshot
-achievement.fastTravel.desc=Use the Nether to travel more then 8000 blocks in the Overworld.
\ No newline at end of file
+achievement.fastTravel.desc=Use the Nether to travel more than 8000 blocks in the Overworld.
\ No newline at end of file
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 064b46a9d..f637a568d 100644
--- a/game/core/src/main/resources/lang/en_US/tile.lang
+++ b/game/core/src/main/resources/lang/en_US/tile.lang
@@ -1 +1,1238 @@
-tile.stone.name=Stone
tile.stone.desc=A block of solid stone.
tile.basalt.name=Basalt
tile.basalt.desc=A block of solid basalt.
tile.limestone.name=Limestone
tile.limestone.desc=A block of solid limestone.
tile.granite.name=Granite
tile.granite.desc=A block of solid granite.
tile.marble.name=Marble
tile.marble.desc=A block of solid marble.
tile.slate.name=Slate
tile.slate.desc=A block of solid slate.
tile.permafrost.name=Permafrost
tile.permafrost.desc=A block of frozen stone.

tile.cobble.stone.name=Cobblestone
tile.cobble.stone.desc=Stone, smashed apart and reconstituted.
tile.cobble.stone.mossy.name=Mossy Cobblestone
tile.cobble.stone.mossy.desc=Cobblestone with added greenery.
tile.cobble.basalt.name=Cobbled Basalt
tile.cobble.basalt.desc=Basalt, smashed apart and reconstituted.
tile.cobble.limestone.name=Cobbled Limestone
tile.cobble.limestone.desc=Limestone, smashed apart and reconstituted.
tile.cobble.granite.name=Cobbled Granite
tile.cobble.granite.desc=Granite, smashed apart and reconstituted.
tile.cobble.permafrost.name=Cobbled Permafrost
tile.cobble.permafrost.desc=Permafrost, smashed apart and reconstituted.

tile.pillar.marble.name=Marble Pillar
tile.pillar.marble.desc=A block of marble engraved into a pillar.
tile.capstone.marble.name=Marble Capstone
tile.capstone.marble.desc=A pillar capstone made of solid marble.

tile.sandstone.name=Sandstone
tile.sandstone.desc=Compacted sand.

tile.stone.carved.name=Carved Stone
tile.stone.carved.desc=Polished Stone with lines hewn through the middle.
tile.granite.carved.name=Carved Granite
tile.granite.carved.desc=Polished Granite with lines hewn through the middle.
tile.limestone.carved.name=Carved Limestone
tile.limestone.carved.desc=Polished Limestone with lines hewn through the middle.
tile.basalt.carved.name=Carved Basalt
tile.basalt.carved.desc=Polished Basalt with lines hewn through the middle.
tile.permafrost.carved.name=Carved Permafrost
tile.permafrost.carved.desc=Polished Permafrost with lines hewn through the middle.
tile.netherrack.carved.name=Carved Netherrack
tile.netherrack.carved.desc=Polished Netherrack with lines hewn through the middle.

tile.planks.oak.name=Wooden Planks
tile.planks.oak.desc=Processed tree insides.
tile.planks.oak.painted.name=Mystery Planks
tile.planks.oak.painted.desc=How did you get this?!
tile.planks.oak.painted.black.name=Black Wooden Planks
tile.planks.oak.painted.black.desc=Wooden planks, stained black.
tile.planks.oak.painted.red.name=Red Wooden Planks
tile.planks.oak.painted.red.desc=Wooden planks, stained red.
tile.planks.oak.painted.green.name=Green Wooden Planks
tile.planks.oak.painted.green.desc=Wooden planks, stained green.
tile.planks.oak.painted.brown.name=Brown Wooden Planks
tile.planks.oak.painted.brown.desc=Wooden planks, stained brown.
tile.planks.oak.painted.blue.name=Blue Wooden Planks
tile.planks.oak.painted.blue.desc=Wooden planks, stained blue.
tile.planks.oak.painted.purple.name=Purple Wooden Planks
tile.planks.oak.painted.purple.desc=Wooden planks, stained purple.
tile.planks.oak.painted.cyan.name=Cyan Wooden Planks
tile.planks.oak.painted.cyan.desc=Wooden planks, stained cyan.
tile.planks.oak.painted.silver.name=Light Gray Wooden Planks
tile.planks.oak.painted.silver.desc=Wooden planks, stained light gray.
tile.planks.oak.painted.gray.name=Gray Wooden Planks
tile.planks.oak.painted.gray.desc=Wooden planks, stained gray.
tile.planks.oak.painted.pink.name=Pink Wooden Planks
tile.planks.oak.painted.pink.desc=Wooden planks, stained pink.
tile.planks.oak.painted.lime.name=Lime Wooden Planks
tile.planks.oak.painted.lime.desc=Wooden planks, stained lime.
tile.planks.oak.painted.yellow.name=Yellow Wooden Planks
tile.planks.oak.painted.yellow.desc=Wooden planks, stained yellow.
tile.planks.oak.painted.lightblue.name=Light Blue Wooden Planks
tile.planks.oak.painted.lightblue.desc=Wooden planks, stained light blue.
tile.planks.oak.painted.magenta.name=Magenta Wooden Planks
tile.planks.oak.painted.magenta.desc=Wooden planks, stained magenta.
tile.planks.oak.painted.orange.name=Orange Wooden Planks
tile.planks.oak.painted.orange.desc=Wooden planks, stained orange.
tile.planks.oak.painted.white.name=White Wooden Planks
tile.planks.oak.painted.white.desc=Wooden planks, stained white.

tile.torch.coal.name=Torch
tile.torch.coal.desc=Helps light the way.

tile.ladder.oak.name=Ladder
tile.ladder.oak.desc=For very rapid vertical movement.

tile.fence.planks.oak.name=Wooden Fence
tile.fence.planks.oak.desc=Too tall to jump over.

tile.fence.planks.oak.painted.name=Mystery Wooden Fence
tile.fence.planks.oak.painted.desc=How did you get this?!
tile.fence.planks.oak.painted.black.name=Black Wooden Fence
tile.fence.planks.oak.painted.black.desc=A wooden fence, stained black.
tile.fence.planks.oak.painted.red.name=Red Wooden Fence
tile.fence.planks.oak.painted.red.desc=A wooden fence, stained red.
tile.fence.planks.oak.painted.green.name=Green Wooden Fence
tile.fence.planks.oak.painted.green.desc=A wooden fence, stained green.
tile.fence.planks.oak.painted.brown.name=Brown Wooden Fence
tile.fence.planks.oak.painted.brown.desc=A wooden fence, stained brown.
tile.fence.planks.oak.painted.blue.name=Blue Wooden Fence
tile.fence.planks.oak.painted.blue.desc=A wooden fence, stained blue.
tile.fence.planks.oak.painted.purple.name=Purple Wooden Fence
tile.fence.planks.oak.painted.purple.desc=A wooden fence, stained purple.
tile.fence.planks.oak.painted.cyan.name=Cyan Wooden Fence
tile.fence.planks.oak.painted.cyan.desc=A wooden fence, stained cyan.
tile.fence.planks.oak.painted.silver.name=Light Gray Wooden Fence
tile.fence.planks.oak.painted.silver.desc=A wooden fence, stained light gray.
tile.fence.planks.oak.painted.gray.name=Gray Wooden Fence
tile.fence.planks.oak.painted.gray.desc=A wooden fence, stained gray.
tile.fence.planks.oak.painted.pink.name=Pink Wooden Fence
tile.fence.planks.oak.painted.pink.desc=A wooden fence, stained pink.
tile.fence.planks.oak.painted.lime.name=Lime Wooden Fence
tile.fence.planks.oak.painted.lime.desc=A wooden fence, stained lime.
tile.fence.planks.oak.painted.yellow.name=Yellow Wooden Fence
tile.fence.planks.oak.painted.yellow.desc=A wooden fence, stained yellow.
tile.fence.planks.oak.painted.lightblue.name=Light Blue Wooden Fence
tile.fence.planks.oak.painted.lightblue.desc=A wooden fence, stained light blue.
tile.fence.planks.oak.painted.magenta.name=Magenta Wooden Fence
tile.fence.planks.oak.painted.magenta.desc=A wooden fence, stained magenta.
tile.fence.planks.oak.painted.orange.name=Orange Wooden Fence
tile.fence.planks.oak.painted.orange.desc=A wooden fence, stained orange.
tile.fence.planks.oak.painted.white.name=White Wooden Fence
tile.fence.planks.oak.painted.white.desc=A wooden fence, stained white.

tile.fencegate.planks.oak.name=Wooden Fence Gate
tile.fencegate.planks.oak.desc=Like a door but for fences.

tile.fencegate.planks.oak.painted.name=Mystery Wooden Fence Gate
tile.fencegate.planks.oak.painted.desc=How did you get this?!
tile.fencegate.planks.oak.painted.black.name=Black Wooden Fence Gate
tile.fencegate.planks.oak.painted.black.desc=A wooden fence gate, stained black.
tile.fencegate.planks.oak.painted.red.name=Red Wooden Fence Gate
tile.fencegate.planks.oak.painted.red.desc=A wooden fence gate, stained red.
tile.fencegate.planks.oak.painted.green.name=Green Wooden Fence Gate
tile.fencegate.planks.oak.painted.green.desc=A wooden fence gate, stained green.
tile.fencegate.planks.oak.painted.brown.name=Brown Wooden Fence Gate
tile.fencegate.planks.oak.painted.brown.desc=A wooden fence gate, stained brown.
tile.fencegate.planks.oak.painted.blue.name=Blue Wooden Fence Gate
tile.fencegate.planks.oak.painted.blue.desc=A wooden fence gate, stained blue.
tile.fencegate.planks.oak.painted.purple.name=Purple Wooden Fence Gate
tile.fencegate.planks.oak.painted.purple.desc=A wooden fence gate, stained purple.
tile.fencegate.planks.oak.painted.cyan.name=Cyan Wooden Fence Gate
tile.fencegate.planks.oak.painted.cyan.desc=A wooden fence gate, stained cyan.
tile.fencegate.planks.oak.painted.silver.name=Light Gray Wooden Fence Gate
tile.fencegate.planks.oak.painted.silver.desc=A wooden fence gate, stained light gray.
tile.fencegate.planks.oak.painted.gray.name=Gray Wooden Fence Gate
tile.fencegate.planks.oak.painted.gray.desc=A wooden fence gate, stained gray.
tile.fencegate.planks.oak.painted.pink.name=Pink Wooden Fence Gate
tile.fencegate.planks.oak.painted.pink.desc=A wooden fence gate, stained pink.
tile.fencegate.planks.oak.painted.lime.name=Lime Wooden Fence Gate
tile.fencegate.planks.oak.painted.lime.desc=A wooden fence gate, stained lime.
tile.fencegate.planks.oak.painted.yellow.name=Yellow Wooden Fence Gate
tile.fencegate.planks.oak.painted.yellow.desc=A wooden fence gate, stained yellow.
tile.fencegate.planks.oak.painted.lightblue.name=Light Blue Wooden Fence Gate
tile.fencegate.planks.oak.painted.lightblue.desc=A wooden fence gate, stained light blue.
tile.fencegate.planks.oak.painted.magenta.name=Magenta Wooden Fence Gate
tile.fencegate.planks.oak.painted.magenta.desc=A wooden fence gate, stained magenta.
tile.fencegate.planks.oak.painted.orange.name=Orange Wooden Fence Gate
tile.fencegate.planks.oak.painted.orange.desc=A wooden fence gate, stained orange.
tile.fencegate.planks.oak.painted.white.name=White Wooden Fence Gate
tile.fencegate.planks.oak.painted.white.desc=A wooden fence gate, stained white.

tile.bookshelf.planks.oak.name=Bookshelf
tile.bookshelf.planks.oak.desc=A repository of knowledge.

tile.wool.name=Wool
tile.wool.desc=How did you get this?!
tile.wool.black.name=Black Wool
tile.wool.black.desc=Sheep's wool, dyed black.
tile.wool.red.name=Red Wool
tile.wool.red.desc=Sheep's wool, dyed red.
tile.wool.green.name=Green Wool
tile.wool.green.desc=Sheep's wool, dyed green.
tile.wool.brown.name=Brown Wool
tile.wool.brown.desc=Sheep's wool, dyed brown.
tile.wool.blue.name=Blue Wool
tile.wool.blue.desc=Sheep's wool, dyed blue.
tile.wool.purple.name=Purple Wool
tile.wool.purple.desc=Sheep's wool, dyed purple.
tile.wool.cyan.name=Cyan Wool
tile.wool.cyan.desc=Sheep's wool, dyed cyan.
tile.wool.silver.name=Light Gray Wool
tile.wool.silver.desc=Sheep's wool, dyed light gray.
tile.wool.gray.name=Gray Wool
tile.wool.gray.desc=Sheep's wool, dyed gray.
tile.wool.pink.name=Pink Wool
tile.wool.pink.desc=Sheep's wool, dyed pink.
tile.wool.lime.name=Lime Wool
tile.wool.lime.desc=Sheep's wool, dyed lime.
tile.wool.yellow.name=Yellow Wool
tile.wool.yellow.desc=Sheep's wool, dyed yellow.
tile.wool.lightblue.name=Light Blue Wool
tile.wool.lightblue.desc=Sheep's wool, dyed light blue.
tile.wool.magenta.name=Magenta Wool
tile.wool.magenta.desc=Sheep's wool, dyed magenta.
tile.wool.orange.name=Orange Wool
tile.wool.orange.desc=Sheep's wool, dyed orange.
tile.wool.white.name=White Wool
tile.wool.white.desc=Sheep's wool, dyed white.

tile.brick.clay.name=Clay Bricks
tile.brick.clay.desc=Stacked bricks of baked clay.
tile.brick.stone.polished.name=Polished Stone Bricks
tile.brick.stone.polished.desc=Stacked bricks of polished stone.
tile.brick.stone.polished.mossy.name=Mossy Polished Stone Bricks
tile.brick.stone.polished.mossy.desc=Stacked bricks of polished stone with added greenery.
tile.brick.sandstone.name=Sandstone Bricks
tile.brick.sandstone.desc=Stacked bricks of sandstone.
tile.brick.gold.name=Gold Bricks
tile.brick.gold.desc=Stacked bricks of gold.
tile.brick.lapis.name=Lapis Lazuli Bricks
tile.brick.lapis.desc=Stacked bricks of lapis lazuli.
tile.brick.basalt.name=Basalt Bricks
tile.brick.basalt.desc=Stacked bricks of basalt.
tile.brick.limestone.name=Limestone Bricks
tile.brick.limestone.desc=Stacked bricks of limestone.
tile.brick.granite.name=Granite Bricks
tile.brick.granite.desc=Stacked bricks of granite.
tile.brick.marble.name=Marble Bricks
tile.brick.marble.desc=Stacked bricks of marble.
tile.brick.slate.name=Slate Bricks
tile.brick.slate.desc=Stacked bricks of slate.
tile.brick.stone.name=Stone Bricks
tile.brick.stone.desc=Stacked bricks of stone.
tile.brick.permafrost.name=Permafrost Bricks
tile.brick.permafrost.desc=Stacked bricks of permafrost.
tile.brick.iron.name=Iron Bricks
tile.brick.iron.desc=Stacked bricks of iron.
tile.brick.steel.name=Steel Bricks
tile.brick.steel.desc=Stacked bricks of steel.

tile.slab.planks.oak.name=Wooden Slab
tile.slab.planks.oak.desc=A slab of wooden planks.
tile.slab.cobble.stone.name=Cobblestone Slab
tile.slab.cobble.stone.desc=A slab of cobblestone.
tile.slab.sandstone.name=Sandstone Slab
tile.slab.sandstone.desc=A slab of sandstone.
tile.slab.brick.stone.polished.name=Polished Stone Brick Slab
tile.slab.brick.stone.polished.desc=A slab of polished stone bricks.
tile.slab.stone.carved.name=Polished Stone Slab
tile.slab.stone.carved.desc=A slab of polished stone.
tile.slab.brick.marble.name=Marble Brick Slab
tile.slab.brick.marble.desc=A slab of marble bricks.
tile.slab.brick.clay.name=Clay Brick Slab
tile.slab.brick.clay.desc=A slab of clay bricks.
tile.slab.capstone.marble.name=Marble Capstone Slab
tile.slab.capstone.marble.desc=Marble sculpted into a capstone.
tile.slab.cobble.basalt.name=Cobbled Basalt Slab
tile.slab.cobble.basalt.desc=A slab of cobbled basalt.
tile.slab.cobble.limestone.name=Cobbled Limestone Slab
tile.slab.cobble.limestone.desc=A slab of cobbled limestone.
tile.slab.cobble.granite.name=Cobbled Granite Slab
tile.slab.cobble.granite.desc=A slab of cobbled granite.
tile.slab.brick.basalt.name=Basalt Brick Slab
tile.slab.brick.basalt.desc=A slab of basalt bricks.
tile.slab.brick.limestone.name=Limestone Brick Slab
tile.slab.brick.limestone.desc=A slab of limestone bricks.
tile.slab.brick.granite.name=Granite Brick Slab
tile.slab.brick.granite.desc=A slab of granite bricks.
tile.slab.brick.slate.name=Slate Brick Slab
tile.slab.brick.slate.desc=A slab of slate bricks.
tile.slab.brick.stone.name=Stone Brick Slab
tile.slab.brick.stone.desc=A slab of stone bricks.
tile.slab.granite.carved.name=Polished Granite Slab
tile.slab.granite.carved.desc=A slab of polished granite.
tile.slab.limestone.carved.name=Polished Limestone Slab
tile.slab.limestone.carved.desc=A slab of polished limestone.
tile.slab.basalt.carved.name=Polished Basalt Slab
tile.slab.basalt.carved.desc=A slab of polished basalt.
tile.slab.permafrost.carved.name=Polished Permafrost Slab
tile.slab.permafrost.carved.desc=A slab of polished permafrost.

tile.slab.planks.oak.painted.name=Mystery Wooden Slab
tile.slab.planks.oak.painted.desc=How did you get this?!
tile.slab.planks.oak.painted.black.name=Black Wooden Slab
tile.slab.planks.oak.painted.black.desc=A slab of wooden planks, stained black.
tile.slab.planks.oak.painted.red.name=Red Wooden Slab
tile.slab.planks.oak.painted.red.desc=A slab of wooden planks, stained red.
tile.slab.planks.oak.painted.green.name=Green Wooden Slab
tile.slab.planks.oak.painted.green.desc=A slab of wooden planks, stained green.
tile.slab.planks.oak.painted.brown.name=Brown Wooden Slab
tile.slab.planks.oak.painted.brown.desc=A slab of wooden planks, stained brown.
tile.slab.planks.oak.painted.blue.name=Blue Wooden Slab
tile.slab.planks.oak.painted.blue.desc=A slab of wooden planks, stained blue.
tile.slab.planks.oak.painted.purple.name=Purple Wooden Slab
tile.slab.planks.oak.painted.purple.desc=A slab of wooden planks, stained purple.
tile.slab.planks.oak.painted.cyan.name=Cyan Wooden Slab
tile.slab.planks.oak.painted.cyan.desc=A slab of wooden planks, stained cyan.
tile.slab.planks.oak.painted.silver.name=Light Gray Wooden Slab
tile.slab.planks.oak.painted.silver.desc=A slab of wooden planks, stained light gray.
tile.slab.planks.oak.painted.gray.name=Gray Wooden Slab
tile.slab.planks.oak.painted.gray.desc=A slab of wooden planks, stained gray.
tile.slab.planks.oak.painted.pink.name=Pink Wooden Slab
tile.slab.planks.oak.painted.pink.desc=A slab of wooden planks, stained pink.
tile.slab.planks.oak.painted.lime.name=Lime Wooden Slab
tile.slab.planks.oak.painted.lime.desc=A slab of wooden planks, stained lime.
tile.slab.planks.oak.painted.yellow.name=Yellow Wooden Slab
tile.slab.planks.oak.painted.yellow.desc=A slab of wooden planks, stained yellow.
tile.slab.planks.oak.painted.lightblue.name=Light Blue Wooden Slab
tile.slab.planks.oak.painted.lightblue.desc=A slab of wooden planks, stained light blue.
tile.slab.planks.oak.painted.magenta.name=Magenta Wooden Slab
tile.slab.planks.oak.painted.magenta.desc=A slab of wooden planks, stained magenta.
tile.slab.planks.oak.painted.orange.name=Orange Wooden Slab
tile.slab.planks.oak.painted.orange.desc=A slab of wooden planks, stained orange.
tile.slab.planks.oak.painted.white.name=White Wooden Slab
tile.slab.planks.oak.painted.white.desc=A slab of wooden planks, stained white.
tile.slab.cobble.permafrost.name=Cobbled Permafrost Slab
tile.slab.cobble.permafrost.desc=A slab of cobbled permafrost.
tile.slab.brick.sandstone.name=Sandstone Brick Slab
tile.slab.brick.sandstone.desc=A slab of sandstone bricks.
tile.slab.brick.permafrost.name=Permafrost Brick Slab
tile.slab.brick.permafrost.desc=A slab of permafrost bricks.

tile.stairs.planks.oak.name=Wooden Stairs
tile.stairs.planks.oak.desc=Wooden block used for rapid vertical movement.
tile.stairs.cobble.stone.name=Cobblestone Stairs
tile.stairs.cobble.stone.desc=Cobblestone block used for rapid vertical movement.
tile.stairs.brick.stone.polished.name=Polished Stone Brick Stairs
tile.stairs.brick.stone.polished.desc=Polished stone brick block used for rapid vertical movement.
tile.stairs.brick.marble.name=Marble Brick Stairs
tile.stairs.brick.marble.desc=Marble brick block used for rapid vertical movement.
tile.stairs.cobble.basalt.name=Cobbled Basalt Stairs
tile.stairs.cobble.basalt.desc=Cobbled basalt block used for rapid vertical movement.
tile.stairs.cobble.limestone.name=Cobbled Limestone Stairs
tile.stairs.cobble.limestone.desc=Cobbled limestone block used for rapid vertical movement.
tile.stairs.cobble.granite.name=Cobbled Granite Stairs
tile.stairs.cobble.granite.desc=Cobbled granite block used for rapid vertical movement.
tile.stairs.brick.basalt.name=Basalt Brick Stairs
tile.stairs.brick.basalt.desc=Basalt brick block used for rapid vertical movement.
tile.stairs.brick.limestone.name=Limestone Brick Stairs
tile.stairs.brick.limestone.desc=Limestone brick block used for rapid vertical movement.
tile.stairs.brick.granite.name=Granite Brick Stairs
tile.stairs.brick.granite.desc=Granite brick block used for rapid vertical movement.
tile.stairs.brick.clay.name=Clay Brick Stairs
tile.stairs.brick.clay.desc=Clay brick block used for rapid vertical movement.
tile.stairs.brick.slate.name=Slate Brick Stairs
tile.stairs.brick.slate.desc=Slate brick block used for rapid vertical movement.
tile.stairs.brick.stone.name=Stone Brick Stairs
tile.stairs.brick.stone.desc=Stone brick block used for rapid vertical movement.
tile.stairs.sandstone.name=Sandstone Stairs
tile.stairs.sandstone.desc=Sandstone block used for rapid vertical movement.
tile.stairs.cobble.permafrost.name=Cobbled Permafrost Stairs
tile.stairs.cobble.permafrost.desc=Cobbled permafrost block used for rapid vertical movement.
tile.stairs.brick.sandstone.name=Sandstone Brick Stairs
tile.stairs.brick.sandstone.desc=Sandstone brick block used for rapid vertical movement.
tile.stairs.brick.permafrost.name=Permafrost Brick Stairs
tile.stairs.brick.permafrost.desc=Permafrost brick block used for rapid vertical movement.

tile.stairs.planks.oak.painted.name=Mystery Wooden Stairs
tile.stairs.planks.oak.painted.desc=How did you get this?!
tile.stairs.planks.oak.painted.black.name=Black Wooden Stairs
tile.stairs.planks.oak.painted.black.desc=Wooden steps, stained black.
tile.stairs.planks.oak.painted.red.name=Red Wooden Stairs
tile.stairs.planks.oak.painted.red.desc=Wooden steps, stained red.
tile.stairs.planks.oak.painted.green.name=Green Wooden Stairs
tile.stairs.planks.oak.painted.green.desc=Wooden steps, stained green.
tile.stairs.planks.oak.painted.brown.name=Brown Wooden Stairs
tile.stairs.planks.oak.painted.brown.desc=Wooden steps, stained brown.
tile.stairs.planks.oak.painted.blue.name=Blue Wooden Stairs
tile.stairs.planks.oak.painted.blue.desc=Wooden steps, stained blue.
tile.stairs.planks.oak.painted.purple.name=Purple Wooden Stairs
tile.stairs.planks.oak.painted.purple.desc=Wooden steps, stained purple.
tile.stairs.planks.oak.painted.cyan.name=Cyan Wooden Stairs
tile.stairs.planks.oak.painted.cyan.desc=Wooden steps, stained cyan.
tile.stairs.planks.oak.painted.silver.name=Light Gray Wooden Stairs
tile.stairs.planks.oak.painted.silver.desc=Wooden steps, stained light gray.
tile.stairs.planks.oak.painted.gray.name=Gray Wooden Stairs
tile.stairs.planks.oak.painted.gray.desc=Wooden steps, stained gray.
tile.stairs.planks.oak.painted.pink.name=Pink Wooden Stairs
tile.stairs.planks.oak.painted.pink.desc=Wooden steps, stained pink.
tile.stairs.planks.oak.painted.lime.name=Lime Wooden Stairs
tile.stairs.planks.oak.painted.lime.desc=Wooden steps, stained lime.
tile.stairs.planks.oak.painted.yellow.name=Yellow Wooden Stairs
tile.stairs.planks.oak.painted.yellow.desc=Wooden steps, stained yellow.
tile.stairs.planks.oak.painted.lightblue.name=Light Blue Wooden Stairs
tile.stairs.planks.oak.painted.lightblue.desc=Wooden steps, stained light blue.
tile.stairs.planks.oak.painted.magenta.name=Magenta Wooden Stairs
tile.stairs.planks.oak.painted.magenta.desc=Wooden steps, stained magenta.
tile.stairs.planks.oak.painted.orange.name=Orange Wooden Stairs
tile.stairs.planks.oak.painted.orange.desc=Wooden steps, stained orange.
tile.stairs.planks.oak.painted.white.name=White Wooden Stairs
tile.stairs.planks.oak.painted.white.desc=Wooden steps, stained white.

tile.obsidian.name=Obsidian
tile.obsidian.desc=Rapidly cooled volcanic rock.

tile.glass.name=Glass
tile.glass.desc=Sand heated until it becomes translucent.
tile.glass.tinted.name=Tinted Glass
tile.glass.tinted.desc=Darkened glass that blocks light.

tile.grass.name=Grass
tile.grass.desc=Crisp, crunchy grass.
tile.grass.retro.name=Retro Grass
tile.grass.retro.desc=Crisp, crunchy grass. Smells old.
tile.grass.scorched.name=Scorched Grass
tile.grass.scorched.desc=Grass that has baked in the sun.

tile.path.dirt.name=Dirt Path
tile.path.dirt.desc=Dirt worn down by frequent travelling.

tile.dirt.name=Dirt
tile.dirt.desc=Crumbly brown dirt.
tile.dirt.scorched.name=Scorched Dirt
tile.dirt.scorched.desc=Dirt that has baked in the sun.
tile.dirt.scorched.rich.name=Rich Scorched Dirt
tile.dirt.scorched.rich.desc=Dirt that has baked in the sun, searing in minerals.

tile.mud.name=Mud
tile.mud.desc=Mud sodden to its core.
tile.mud.baked.name=Baked Mud
tile.mud.baked.desc=Mud that's been dried out and cracked.

tile.sponge.dry.name=Sponge
tile.sponge.dry.desc=A dry sponge, good for soaking up water.
tile.sponge.wet.name=Wet Sponge
tile.sponge.wet.desc=A sponge full of water.
tile.pumice.dry.name=Pumice
tile.pumice.dry.desc=A dry porous volcanic rock, good for soaking up lava.
tile.pumice.wet.name=Molten Pumice
tile.pumice.wet.desc=Rock filled with lava.

tile.moss.stone.name=Mossy Stone
tile.moss.stone.desc=Moss covering a block of stone.
tile.moss.basalt.name=Mossy Basalt
tile.moss.basalt.desc=Moss covering a block of basalt.
tile.moss.limestone.name=Mossy Limestone
tile.moss.limestone.desc=Moss covering a block of limestone.
tile.moss.granite.name=Mossy Granite
tile.moss.granite.desc=Moss covering a block of granite.

tile.sand.name=Sand
tile.sand.desc=Gets everywhere.
tile.gravel.name=Gravel
tile.gravel.desc=A loose collection of pebbles.

tile.bedrock.name=Bedrock
tile.bedrock.desc=The toughest substance known to man.
tile.boneshale.name=Bone Shale
tile.boneshale.desc=An incredibly tough white rock torn from the deepest depths.

tile.fluid.water.flowing.name=Flowing Water
tile.fluid.water.flowing.desc=Water that flows when placed.
tile.fluid.water.still.name=Still Water
tile.fluid.water.still.desc=Water that does not flow when placed.
tile.fluid.lava.flowing.name=Flowing Lava
tile.fluid.lava.flowing.desc=Lava that flows when placed.
tile.fluid.lava.still.name=Still Lava
tile.fluid.lava.still.desc=Lava that does not flow when placed.

tile.log.oak.name=Oak Log
tile.log.oak.desc=A log from an oak tree.
tile.log.pine.name=Pine Log
tile.log.pine.desc=A log from a pine tree.
tile.log.birch.name=Birch Log
tile.log.birch.desc=A log from a birch tree.
tile.log.cherry.name=Cherry Log
tile.log.cherry.desc=A log from a cherry tree.
tile.log.eucalyptus.name=Eucalyptus Log
tile.log.eucalyptus.desc=A log from a eucalyptus tree.
tile.log.oak.mossy.name=Mossy Oak Log
tile.log.oak.mossy.desc=A log from a mossy oak tree.
tile.log.thorn.name=Thorn Log
tile.log.thorn.desc=A log from a thorn tree.
tile.log.palm.name=Palm Log
tile.log.palm.desc=A log from a palm tree.

tile.leaves.oak.name=Oak Leaves
tile.leaves.oak.desc=Leaves from an oak tree.
tile.leaves.pine.name=Pine Leaves
tile.leaves.pine.desc=Leaves from a pine tree.
tile.leaves.birch.name=Birch Leaves
tile.leaves.birch.desc=Leaves from a birch tree.
tile.leaves.cherry.name=Cherry Leaves
tile.leaves.cherry.desc=Leaves from a cherry tree.
tile.leaves.eucalyptus.name=Eucalyptus Leaves
tile.leaves.eucalyptus.desc=Leaves from a eucalyptus tree.
tile.leaves.oak.retro.name=Retro Leaves
tile.leaves.oak.retro.desc=Leaves from long ago.
tile.leaves.shrub.name=Shrub Leaves
tile.leaves.shrub.desc=Leaves from a shrub.
tile.leaves.cherry.flowering.name=Flowering Cherry Leaves
tile.leaves.cherry.flowering.desc=Leaves from a cherry tree that are budding with fruit.
tile.leaves.cacao.name=Cacao Leaves
tile.leaves.cacao.desc=Leaves from a cacao tree.
tile.leaves.thorn.name=Thorn Leaves
tile.leaves.thorn.desc=Leaves from a thorn tree.
tile.leaves.palm.name=Palm Leaves
tile.leaves.palm.desc=Leaves from a palm tree.

tile.sapling.oak.name=Oak Sapling
tile.sapling.oak.desc=Plant to grow an oak tree.
tile.sapling.pine.name=Pine Sapling
tile.sapling.pine.desc=Plant to grow a pine tree.
tile.sapling.birch.name=Birch Sapling
tile.sapling.birch.desc=Plant to grow a birch tree.
tile.sapling.cherry.name=Cherry Sapling
tile.sapling.cherry.desc=Plant to grow a cherry tree.
tile.sapling.eucalyptus.name=Eucalyptus Sapling
tile.sapling.eucalyptus.desc=Plant to grow a eucalyptus tree.
tile.sapling.oak.retro.name=Retro Oak Sapling
tile.sapling.oak.retro.desc=Plant to grow a tree from long ago.
tile.sapling.shrub.name=Shrub Sapling
tile.sapling.shrub.desc=Plant to grow a shrub.
tile.sapling.cacao.name=Cacao Sapling
tile.sapling.cacao.desc=Plant to grow a cacao tree.
tile.sapling.thorn.name=Thorn Sapling
tile.sapling.thorn.desc=Plant to grow a thorn tree.
tile.sapling.palm.name=Palm Sapling
tile.sapling.palm.desc=Plant to grow a palm tree.

tile.tallgrass.name=Tall Grass
tile.tallgrass.desc=Lush, growing grass.
tile.tallgrass.fern.name=Fern
tile.tallgrass.fern.desc=A fern plant.
tile.deadbush.name=Dead Bush
tile.deadbush.desc=A dried and withered plant.
tile.spinifex.name=Spinifex
tile.spinifex.desc=A spindly, thin grass from dry regions.
tile.algae.name=Algae
tile.algae.desc=Gooey vegetation that floats on the water's surface.

tile.flower.yellow.name=Dandelion
tile.flower.yellow.desc=A beautiful yellow flower.
tile.flower.red.name=Rose
tile.flower.red.desc=A beautiful red flower.
tile.flower.purple.name=Heather
tile.flower.purple.desc=Sprigs of purple petals. Brings good luck.
tile.flower.lightblue.name=Bluebell
tile.flower.lightblue.desc=A sad-looking blue flower.
tile.flower.pink.name=Orchid
tile.flower.pink.desc=A beautiful pink flower.
tile.flower.orange.name=Marigold
tile.flower.orange.desc=A beautiful orange flower.

tile.mushroom.red.name=Red Mushroom
tile.mushroom.red.desc=A tasty red mushroom. Good for stew.
tile.mushroom.brown.name=Brown Mushroom
tile.mushroom.brown.desc=A tasty brown mushroom. Good for stew.

tile.ore.coal.stone.name=Stone Coal Ore
tile.ore.coal.stone.desc=A chunk of coal embedded in stone.
tile.ore.coal.basalt.name=Basalt Coal Ore
tile.ore.coal.basalt.desc=A chunk of coal embedded in basalt.
tile.ore.coal.limestone.name=Limestone Coal Ore
tile.ore.coal.limestone.desc=A chunk of coal embedded in limestone.
tile.ore.coal.granite.name=Granite Coal Ore
tile.ore.coal.granite.desc=A chunk of coal embedded in granite.
tile.ore.coal.permafrost.name=Permafrost Coal Ore
tile.ore.coal.permafrost.desc=A chunk of coal embedded in permafrost.

tile.ore.iron.stone.name=Stone Iron Ore
tile.ore.iron.stone.desc=Iron nuggets embedded in stone.
tile.ore.iron.basalt.name=Basalt Iron Ore
tile.ore.iron.basalt.desc=Iron nuggets embedded in basalt.
tile.ore.iron.limestone.name=Limestone Iron Ore
tile.ore.iron.limestone.desc=Iron nuggets embedded in limestone.
tile.ore.iron.granite.name=Granite Iron Ore
tile.ore.iron.granite.desc=Iron nuggets embedded in granite.
tile.ore.iron.permafrost.name=Permafrost Iron Ore
tile.ore.iron.permafrost.desc=Iron nuggets embedded in permafrost.

tile.ore.gold.stone.name=Stone Gold Ore
tile.ore.gold.stone.desc=Gold nuggets embedded in stone.
tile.ore.gold.basalt.name=Basalt Gold Ore
tile.ore.gold.basalt.desc=Gold nuggets embedded in basalt.
tile.ore.gold.limestone.name=Limestone Gold Ore
tile.ore.gold.limestone.desc=Gold nuggets embedded in limestone.
tile.ore.gold.granite.name=Granite Gold Ore
tile.ore.gold.granite.desc=Gold nuggets embedded in granite.
tile.ore.gold.permafrost.name=Permafrost Gold Ore
tile.ore.gold.permafrost.desc=Gold nuggets embedded in permafrost.

tile.ore.lapis.stone.name=Stone Lapis Lazuli Ore
tile.ore.lapis.stone.desc=Chunks of lapis lazuli embedded in stone.
tile.ore.lapis.basalt.name=Basalt Lapis Lazuli Ore
tile.ore.lapis.basalt.desc=Chunks of lapis lazuli embedded in basalt.
tile.ore.lapis.limestone.name=Limestone Lapis Lazuli Ore
tile.ore.lapis.limestone.desc=Chunks of lapis lazuli embedded in limestone.
tile.ore.lapis.granite.name=Granite Lapis Lazuli Ore
tile.ore.lapis.granite.desc=Chunks of lapis lazuli embedded in granite.
tile.ore.lapis.permafrost.name=Permafrost Lapis Lazuli Ore
tile.ore.lapis.permafrost.desc=Chunks of lapis lazuli embedded in permafrost.

tile.ore.redstone.stone.name=Stone Redstone Ore
tile.ore.redstone.stone.desc=Red dust embedded in stone.
tile.ore.redstone.basalt.name=Basalt Redstone Ore
tile.ore.redstone.basalt.desc=Red dust embedded in basalt.
tile.ore.redstone.limestone.name=Limestone Redstone Ore
tile.ore.redstone.limestone.desc=Red dust embedded in limestone.
tile.ore.redstone.granite.name=Granite Redstone Ore
tile.ore.redstone.granite.desc=Red dust embedded in granite.
tile.ore.redstone.permafrost.name=Permafrost Redstone Ore
tile.ore.redstone.permafrost.desc=Red dust embedded in permafrost.
tile.ore.redstone.glowing.stone.name=Glowing Stone Redstone Ore
tile.ore.redstone.glowing.stone.desc=Red dust embedded in stone. Emits a faint light.
tile.ore.redstone.glowing.basalt.name=Glowing Basalt Redstone Ore
tile.ore.redstone.glowing.basalt.desc=Red dust embedded in basalt. Emits a faint light.
tile.ore.redstone.glowing.limestone.name=Glowing Limestone Redstone Ore
tile.ore.redstone.glowing.limestone.desc=Red dust embedded in limestone. Emits a faint light.
tile.ore.redstone.glowing.granite.name=Glowing Granite Redstone Ore
tile.ore.redstone.glowing.granite.desc=Red dust embedded in granite. Emits a faint light.
tile.ore.redstone.glowing.permafrost.name=Glowing Permafrost Redstone Ore
tile.ore.redstone.glowing.permafrost.desc=Red dust embedded in permafrost. Emits a faint light.

tile.ore.diamond.stone.name=Stone Diamond Ore
tile.ore.diamond.stone.desc=A chunk of diamond embedded in stone.
tile.ore.diamond.basalt.name=Basalt Diamond Ore
tile.ore.diamond.basalt.desc=A chunk of diamond embedded in basalt.
tile.ore.diamond.limestone.name=Limestone Diamond Ore
tile.ore.diamond.limestone.desc=A chunk of diamond embedded in limestone.
tile.ore.diamond.granite.name=Granite Diamond Ore
tile.ore.diamond.granite.desc=A chunk of diamond embedded in granite.
tile.ore.diamond.permafrost.name=Permafrost Diamond Ore
tile.ore.diamond.permafrost.desc=A chunk of diamond embedded in permafrost.

tile.ore.nethercoal.netherrack.name=Nether Coal Ore
tile.ore.nethercoal.netherrack.desc=A lump of Nether Coal embedded in netherrack.

tile.block.coal.name=Block of Coal
tile.block.coal.desc=A solid block of coal.
tile.block.iron.name=Block of Iron
tile.block.iron.desc=A solid block of iron. Heavy!
tile.block.gold.name=Block of Gold
tile.block.gold.desc=A solid block of gold, for flaunting your bling.
tile.block.lapis.name=Block of Lapis Lazuli
tile.block.lapis.desc=A solid block of lapis lazuli.
tile.block.redstone.name=Block of Redstone
tile.block.redstone.desc=A solid block of redstone.
tile.block.diamond.name=Block of Diamond
tile.block.diamond.desc=A solid block of diamond. Well done you.
tile.block.nethercoal.name=Block of Nether Coal
tile.block.nethercoal.desc=A solid block of Nether Coal.
tile.block.steel.name=Block of Steel
tile.block.steel.desc=A solid block of steel.
tile.block.quartz.name=Block of Quartz
tile.block.quartz.desc=A solid block of quartz.
tile.block.olivine.name=Block of Olivine
tile.block.olivine.desc=A solid block of olivine.
tile.block.charcoal.name=Block of Charcoal
tile.block.charcoal.desc=A solid block of charcoal.

tile.wire.redstone.name=Redstone Wire
tile.wire.redstone.desc=A strand of redstone dust laid on the ground.

tile.torch.redstone.idle.name=Redstone Torch
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.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.

tile.pressureplate.stone.name=Stone Pressure Plate
tile.pressureplate.stone.desc=Emits a redstone signal when living creatures step on it.
tile.pressureplate.planks.oak.name=Wooden Pressure Plate
tile.pressureplate.planks.oak.desc=Emits a redstone signal when anything steps on it.
tile.pressureplate.cobble.stone.name=Cobblestone Pressure Plate
tile.pressureplate.cobble.stone.desc=Emits a redstone signal when players step 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
tile.motionsensor.active.desc=Emits a redstone signal when something moves past it.

tile.repeater.idle.name=Redstone Repeater
tile.repeater.idle.desc=Repeats redstone signals over a distance.
tile.repeater.active.name=Redstone Repeater
tile.repeater.active.desc=Repeats redstone signals over a distance.

tile.piston.base.name=Piston
tile.piston.base.desc=Pushes blocks but does not pull them.
tile.piston.base.sticky.name=Sticky Piston
tile.piston.base.sticky.desc=Pushes and pulls blocks.
tile.piston.head.name=Piston Head
tile.piston.head.desc=The pushy part of the piston.
tile.piston.moving.name=Piston Mover
tile.piston.moving.desc=The invisible moving part of the piston.
tile.piston.base.steel.name=Reinforced Piston
tile.piston.base.steel.desc=Pushes blocks and entities with immense force.
tile.piston.head.steel.name=Reinforced Piston Head
tile.piston.head.steel.desc=The pushy part of the steel piston.

tile.noteblock.name=Note Block
tile.noteblock.desc=Plays a musical note when powered.

tile.rail.name=Rail
tile.rail.desc=For rapid horizontal movement via minecart.
tile.rail.powered.name=Powered Rail
tile.rail.powered.desc=Makes minecarts go faster when powered.
tile.rail.detector.name=Detector Rail
tile.rail.detector.desc=Emits a redstone signal when a minecart crosses it.

tile.spikes.name=Spike Trap
tile.spikes.desc=Deactivates when powered. Technically a war crime.

tile.dispenser.cobble.stone.name=Dispenser
tile.dispenser.cobble.stone.desc=Fires items out of the hole in the front.
tile.activator.cobble.netherrack.name=Activator
tile.activator.cobble.netherrack.desc=Uses items for you.

tile.trapdoor.planks.oak.name=Wooden Trapdoor
tile.trapdoor.planks.oak.desc=Like a door, but flat.
tile.trapdoor.iron.name=Iron Trapdoor
tile.trapdoor.iron.desc=Like a door, but flat and made of iron.
tile.trapdoor.glass.name=Glass Trapdoor
tile.trapdoor.glass.desc=Useless at its job.

tile.tnt.name=TNT
tile.tnt.desc=Goes off with a bang.

tile.door.planks.oak.bottom.name=Bottom Wooden Door
tile.door.planks.oak.bottom.desc=/give Honeydew 64 46
tile.door.planks.oak.top.name=Top Wooden Door
tile.door.planks.oak.top.desc=Oh, wait, that's not TNT, that's half a door.
tile.door.iron.bottom.name=Bottom Iron Door
tile.door.iron.bottom.desc=Not much good on its own.
tile.door.iron.top.name=Top Iron Door
tile.door.iron.top.desc=Could use a friend.

tile.mesh.name=Mesh Block
tile.mesh.desc=Allows items to pass through but nothing else.
tile.mesh.gold.name=Gold Mesh Block
tile.mesh.gold.desc=Allows a specified item to pass through but nothing else.

tile.bed.name=Bed
tile.bed.desc=Bed!
tile.seat.name=Seat
tile.seat.desc=Sit down and relax.

tile.cobweb.name=Cobweb
tile.cobweb.desc=Sticky silky strands woven by spiders.

tile.fire.name=Fire
tile.fire.desc=Ouch, fire hot!
tile.brazier.inactive.name=Brazier
tile.brazier.inactive.desc=A safe place to keep a fire.
tile.brazier.active.name=Lit Brazier
tile.brazier.active.desc=A safe place to keep a fire.

tile.mobspawner.name=Mob Spawner
tile.mobspawner.desc=A cage filled with horror.
tile.mobspawner.deactivated.name=Deactivated Mob Spawner
tile.mobspawner.deactivated.desc=A cage no longer filled with horror.

tile.workbench.name=Workbench
tile.workbench.desc=For bigger projects.

tile.furnace.stone.idle.name=Furnace
tile.furnace.stone.idle.desc=For melting, heating, and cooking blocks and items.
tile.furnace.stone.active.name=Lit Furnace
tile.furnace.stone.active.desc=For melting, heating, and cooking blocks and items.
tile.furnace.blast.idle.name=Blast Furnace
tile.furnace.blast.idle.desc=Burns hotter than a normal furnace.
tile.furnace.blast.active.name=Lit Blast Furnace
tile.furnace.blast.active.desc=Burns hotter than a normal furnace.

tile.trommel.idle.name=Trommel
tile.trommel.idle.desc=A machine that separates dirt from other useful items.
tile.trommel.active.name=Lit Trommel
tile.trommel.active.desc=A machine that separates dirt from other useful items.

tile.chest.planks.oak.name=Wooden Chest
tile.chest.planks.oak.desc=A place to store your items.
tile.chest.planks.oak.painted.name=Mystery Wooden Chest
tile.chest.planks.oak.painted.desc=How did you get this?!
tile.chest.planks.oak.painted.black.name=Black Wooden Chest
tile.chest.planks.oak.painted.black.desc=A place to store your items.
tile.chest.planks.oak.painted.red.name=Red Wooden Chest
tile.chest.planks.oak.painted.red.desc=A place to store your items.
tile.chest.planks.oak.painted.green.name=Green Wooden Chest
tile.chest.planks.oak.painted.green.desc=A place to store your items.
tile.chest.planks.oak.painted.brown.name=Brown Wooden Chest
tile.chest.planks.oak.painted.brown.desc=A place to store your items.
tile.chest.planks.oak.painted.blue.name=Blue Wooden Chest
tile.chest.planks.oak.painted.blue.desc=A place to store your items.
tile.chest.planks.oak.painted.purple.name=Purple Wooden Chest
tile.chest.planks.oak.painted.purple.desc=A place to store your items.
tile.chest.planks.oak.painted.cyan.name=Cyan Wooden Chest
tile.chest.planks.oak.painted.cyan.desc=A place to store your items.
tile.chest.planks.oak.painted.silver.name=Light Gray Wooden Chest
tile.chest.planks.oak.painted.silver.desc=A place to store your items.
tile.chest.planks.oak.painted.gray.name=Gray Wooden Chest
tile.chest.planks.oak.painted.gray.desc=A place to store your items.
tile.chest.planks.oak.painted.pink.name=Pink Wooden Chest
tile.chest.planks.oak.painted.pink.desc=A place to store your items.
tile.chest.planks.oak.painted.lime.name=Lime Wooden Chest
tile.chest.planks.oak.painted.lime.desc=A place to store your items.
tile.chest.planks.oak.painted.yellow.name=Yellow Wooden Chest
tile.chest.planks.oak.painted.yellow.desc=A place to store your items.
tile.chest.planks.oak.painted.lightblue.name=Light Blue Wooden Chest
tile.chest.planks.oak.painted.lightblue.desc=A place to store your items.
tile.chest.planks.oak.painted.magenta.name=Magenta Wooden Chest
tile.chest.planks.oak.painted.magenta.desc=A place to store your items.
tile.chest.planks.oak.painted.orange.name=Orange Wooden Chest
tile.chest.planks.oak.painted.orange.desc=A place to store your items.
tile.chest.planks.oak.painted.white.name=White Wooden Chest
tile.chest.planks.oak.painted.white.desc=A place to store your items.
tile.chest.legacy.name=Legacy Chest

tile.crops.wheat.name=Wheat Crops
tile.crops.wheat.desc=Wheat, plucked from the ground.
tile.crops.pumpkin.name=Pumpkin Crops
tile.crops.pumpkin.desc=A ripening pumpkin, plucked from the ground.

tile.farmland.dirt.name=Farmland
tile.farmland.dirt.desc=Tilled soil for growing crops.

tile.sign.post.planks.oak.name=Oak Signpost
tile.sign.post.planks.oak.desc=A free-standing oak sign.
tile.sign.wall.planks.oak.name=Oak Wall Sign
tile.sign.wall.planks.oak.desc=An oak sign placed on a wall.

tile.layer.snow.name=Snow Layer
tile.layer.snow.desc=A flat layer of snow.
tile.layer.leaves.oak.name=Leaf Pile
tile.layer.leaves.oak.desc=A pile of leaves.
tile.layer.slate.name=Slate Layer
tile.layer.slate.desc=A flat sheet of slate.

tile.ice.name=Ice
tile.ice.desc=Slippery frozen water.
tile.ice.perma.name=Perma-Ice
tile.ice.perma.desc=Ice imbued with the essence of stone.

tile.block.snow.name=Block of Snow
tile.block.snow.desc=Lots of snow compacted into a block.

tile.cactus.name=Cactus
tile.cactus.desc=A spiky plant that grows in the desert.

tile.block.clay.name=Block of Clay
tile.block.clay.desc=A block of compacted clay.

tile.sugarcane.name=Sugarcane
tile.sugarcane.desc=Not as sweet as you.

tile.block.sugarcane.name=Block of Sugarcane
tile.block.sugarcane.desc=One very big sugar cube.
tile.block.sugarcane.baked.name=Baked Block of Sugarcane
tile.block.sugarcane.baked.desc=Smells absolutely delicious.

tile.jukebox.name=Jukebox
tile.jukebox.desc=Plays your favourite tunes, assuming you have the right record.

tile.pumpkin.name=Pumpkin
tile.pumpkin.desc=An orange squash that grows in the wild.
tile.pumpkin.carved.idle.name=Carved Pumpkin
tile.pumpkin.carved.idle.desc=An orange squash with a spooky face.
tile.pumpkin.carved.active.name=Jack 'o' Lantern
tile.pumpkin.carved.active.desc=An orange squash with a spooky face, lit by a torch.
tile.pumpkin.redstone.name=Redstone Jack 'o' Lantern
tile.pumpkin.redstone.desc=An orange squash with a spooky face, lit by a redstone torch.

tile.cobble.netherrack.name=Cobbled Netherrack
tile.cobble.netherrack.desc=Dark red rock that burns forever.
tile.cobble.netherrack.igneous.name=Igneous Cobbled Netherrack
tile.cobble.netherrack.igneous.desc=Extreme heat has made this rock glow from inside.
tile.cobble.netherrack.mossy.name=Mossy Cobbled Netherrack
tile.cobble.netherrack.mossy.desc=Cobbled netherrack, covered in something like moss.
tile.netherrack.name=Netherrack
tile.netherrack.desc=Dark red rock, smoothed by heat and time.
tile.brick.netherrack.name=Netherrack Bricks
tile.brick.netherrack.desc=Stacked bricks of netherrack.
tile.slab.netherrack.carved.name=Polished Netherrack Slab
tile.slab.netherrack.carved.desc=A slab of polished netherrack.
tile.slab.cobble.netherrack.name=Cobbled Netherrack Slab
tile.slab.cobble.netherrack.desc=A slab of cobbled netherrack.
tile.slab.brick.netherrack.name=Netherrack Brick Slab
tile.slab.brick.netherrack.desc=A slab of netherrack bricks.
tile.stairs.cobble.netherrack.name=Cobbled Netherrack Stairs
tile.stairs.cobble.netherrack.desc=Cobbled netherrack block used for rapid vertical movement.
tile.stairs.brick.netherrack.name=Netherrack Brick Stairs
tile.stairs.brick.netherrack.desc=Netherrack brick block used for rapid vertical movement.

tile.soulsand.name=Soul Sand
tile.soulsand.desc=Squishy brown sand that's tough to walk through.
tile.soulschist.name=Soul Schist
tile.soulschist.desc=A hideous medley of ancient bones, fused through heat, pressure, and time.

tile.glowstone.name=Block of Glowstone
tile.glowstone.desc=Constantly emits a strong yellowish light.

tile.portal.nether.name=Nether Portal
tile.portal.nether.desc=A portal to the Nether.
tile.portal.paradise.name=Paradise Portal
tile.portal.paradise.desc=A portal to Paradise.

tile.cake.name=Cake
tile.cake.desc=I just can't cake it any more.
tile.pumpkin_pie.name=Pumpkin Pie
tile.pumpkin_pie.desc=I don't wanna pie!

tile.lamp.idle.name=Lamp
tile.lamp.idle.desc=How did you get this?!
tile.lamp.idle.black.name=Black Lamp
tile.lamp.idle.black.desc=A redstone lamp that can emit light. Tinted black.
tile.lamp.idle.red.name=Red Lamp
tile.lamp.idle.red.desc=A redstone lamp that can emit light. Tinted red.
tile.lamp.idle.green.name=Green Lamp
tile.lamp.idle.green.desc=A redstone lamp that can emit light. Tinted green.
tile.lamp.idle.brown.name=Brown Lamp
tile.lamp.idle.brown.desc=A redstone lamp that can emit light. Tinted brown.
tile.lamp.idle.blue.name=Blue Lamp
tile.lamp.idle.blue.desc=A redstone lamp that can emit light. Tinted blue.
tile.lamp.idle.purple.name=Purple Lamp
tile.lamp.idle.purple.desc=A redstone lamp that can emit light. Tinted purple.
tile.lamp.idle.cyan.name=Cyan Lamp
tile.lamp.idle.cyan.desc=A redstone lamp that can emit light. Tinted cyan.
tile.lamp.idle.silver.name=Light Gray Lamp
tile.lamp.idle.silver.desc=A redstone lamp that can emit light. Tinted light gray.
tile.lamp.idle.gray.name=Gray Lamp
tile.lamp.idle.gray.desc=A redstone lamp that can emit light. Tinted gray.
tile.lamp.idle.pink.name=Pink Lamp
tile.lamp.idle.pink.desc=A redstone lamp that can emit light. Tinted pink.
tile.lamp.idle.lime.name=Lime Lamp
tile.lamp.idle.lime.desc=A redstone lamp that can emit light. Tinted lime.
tile.lamp.idle.yellow.name=Yellow Lamp
tile.lamp.idle.yellow.desc=A redstone lamp that can emit light. Tinted yellow.
tile.lamp.idle.lightblue.name=Light Blue Lamp
tile.lamp.idle.lightblue.desc=A redstone lamp that can emit light. Tinted light blue.
tile.lamp.idle.magenta.name=Magenta Lamp
tile.lamp.idle.magenta.desc=A redstone lamp that can emit light. Tinted magenta.
tile.lamp.idle.orange.name=Orange Lamp
tile.lamp.idle.orange.desc=A redstone lamp that can emit light. Tinted orange.
tile.lamp.idle.white.name=White Lamp
tile.lamp.idle.white.desc=A redstone lamp that can emit light.

tile.lamp.active.name=Lit Lamp
tile.lamp.active.desc=How did you get this?!
tile.lamp.active.black.name=Lit Black Lamp
tile.lamp.active.black.desc=A redstone lamp that emits light. Tinted black.
tile.lamp.active.red.name=Lit Red Lamp
tile.lamp.active.red.desc=A redstone lamp that emits light. Tinted red.
tile.lamp.active.green.name=Lit Green Lamp
tile.lamp.active.green.desc=A redstone lamp that emits light. Tinted green.
tile.lamp.active.brown.name=Lit Brown Lamp
tile.lamp.active.brown.desc=A redstone lamp that emits light. Tinted brown.
tile.lamp.active.blue.name=Lit Blue Lamp
tile.lamp.active.blue.desc=A redstone lamp that emits light. Tinted blue.
tile.lamp.active.purple.name=Lit Purple Lamp
tile.lamp.active.purple.desc=A redstone lamp that emits light. Tinted purple.
tile.lamp.active.cyan.name=Lit Cyan Lamp
tile.lamp.active.cyan.desc=A redstone lamp that emits light. Tinted cyan.
tile.lamp.active.silver.name=Lit Light Gray Lamp
tile.lamp.active.silver.desc=A redstone lamp that emits light. Tinted light gray.
tile.lamp.active.gray.name=Lit Gray Lamp
tile.lamp.active.gray.desc=A redstone lamp that emits light. Tinted gray.
tile.lamp.active.pink.name=Lit Pink Lamp
tile.lamp.active.pink.desc=A redstone lamp that emits light. Tinted pink.
tile.lamp.active.lime.name=Lit Lime Lamp
tile.lamp.active.lime.desc=A redstone lamp that emits light. Tinted lime.
tile.lamp.active.yellow.name=Lit Yellow Lamp
tile.lamp.active.yellow.desc=A redstone lamp that emits light. Tinted yellow.
tile.lamp.active.lightblue.name=Lit Light Blue Lamp
tile.lamp.active.lightblue.desc=A redstone lamp that emits light. Tinted light blue.
tile.lamp.active.magenta.name=Lit Magenta Lamp
tile.lamp.active.magenta.desc=A redstone lamp that emits light. Tinted magenta.
tile.lamp.active.orange.name=Lit Orange Lamp
tile.lamp.active.orange.desc=A redstone lamp that emits light. Tinted orange.
tile.lamp.active.white.name=Lit White Lamp
tile.lamp.active.white.desc=A redstone lamp that emits light.

tile.lamp.inverted.idle.name=Unlit Inverted Lamp
tile.lamp.inverted.idle.desc=How did you get this?!
tile.lamp.inverted.idle.black.name=Unlit Inverted Black Lamp
tile.lamp.inverted.idle.black.desc=A redstone lamp that can emit light. Tinted black.
tile.lamp.inverted.idle.red.name=Unlit Inverted Red Lamp
tile.lamp.inverted.idle.red.desc=A redstone lamp that can emit light. Tinted red.
tile.lamp.inverted.idle.green.name=Unlit Inverted Green Lamp
tile.lamp.inverted.idle.green.desc=A redstone lamp that can emit light. Tinted green.
tile.lamp.inverted.idle.brown.name=Unlit Inverted Brown Lamp
tile.lamp.inverted.idle.brown.desc=A redstone lamp that can emit light. Tinted brown.
tile.lamp.inverted.idle.blue.name=Unlit Inverted Blue Lamp
tile.lamp.inverted.idle.blue.desc=A redstone lamp that can emit light. Tinted blue.
tile.lamp.inverted.idle.purple.name=Unlit Inverted Purple Lamp
tile.lamp.inverted.idle.purple.desc=A redstone lamp that can emit light. Tinted purple.
tile.lamp.inverted.idle.cyan.name=Unlit Inverted Cyan Lamp
tile.lamp.inverted.idle.cyan.desc=A redstone lamp that can emit light. Tinted cyan.
tile.lamp.inverted.idle.silver.name=Unlit Inverted Light Gray Lamp
tile.lamp.inverted.idle.silver.desc=A redstone lamp that can emit light. Tinted light gray.
tile.lamp.inverted.idle.gray.name=Unlit Inverted Gray Lamp
tile.lamp.inverted.idle.gray.desc=A redstone lamp that can emit light. Tinted gray.
tile.lamp.inverted.idle.pink.name=Unlit Inverted Pink Lamp
tile.lamp.inverted.idle.pink.desc=A redstone lamp that can emit light. Tinted pink.
tile.lamp.inverted.idle.lime.name=Unlit Inverted Lime Lamp
tile.lamp.inverted.idle.lime.desc=A redstone lamp that can emit light. Tinted lime.
tile.lamp.inverted.idle.yellow.name=Unlit Inverted Yellow Lamp
tile.lamp.inverted.idle.yellow.desc=A redstone lamp that can emit light. Tinted yellow.
tile.lamp.inverted.idle.lightblue.name=Unlit Inverted Light Blue Lamp
tile.lamp.inverted.idle.lightblue.desc=A redstone lamp that can emit light. Tinted light blue.
tile.lamp.inverted.idle.magenta.name=Unlit Inverted Magenta Lamp
tile.lamp.inverted.idle.magenta.desc=A redstone lamp that can emit light. Tinted magenta.
tile.lamp.inverted.idle.orange.name=Unlit Inverted Orange Lamp
tile.lamp.inverted.idle.orange.desc=A redstone lamp that can emit light. Tinted orange.
tile.lamp.inverted.idle.white.name=Unlit Inverted White Lamp
tile.lamp.inverted.idle.white.desc=A redstone lamp that can emit light.

tile.lamp.inverted.active.name=Inverted Lamp
tile.lamp.inverted.active.desc=How did you get this?!
tile.lamp.inverted.active.black.name=Inverted Black Lamp
tile.lamp.inverted.active.black.desc=A redstone lamp that emits light. Tinted black.
tile.lamp.inverted.active.red.name=Inverted Red Lamp
tile.lamp.inverted.active.red.desc=A redstone lamp that emits light. Tinted red.
tile.lamp.inverted.active.green.name=Inverted Green Lamp
tile.lamp.inverted.active.green.desc=A redstone lamp that emits light. Tinted green.
tile.lamp.inverted.active.brown.name=Inverted Brown Lamp
tile.lamp.inverted.active.brown.desc=A redstone lamp that emits light. Tinted brown.
tile.lamp.inverted.active.blue.name=Inverted Blue Lamp
tile.lamp.inverted.active.blue.desc=A redstone lamp that emits light. Tinted blue.
tile.lamp.inverted.active.purple.name=Inverted Purple Lamp
tile.lamp.inverted.active.purple.desc=A redstone lamp that emits light. Tinted purple.
tile.lamp.inverted.active.cyan.name=Inverted Cyan Lamp
tile.lamp.inverted.active.cyan.desc=A redstone lamp that emits light. Tinted cyan.
tile.lamp.inverted.active.silver.name=Inverted Light Gray Lamp
tile.lamp.inverted.active.silver.desc=A redstone lamp that emits light. Tinted light gray.
tile.lamp.inverted.active.gray.name=Inverted Gray Lamp
tile.lamp.inverted.active.gray.desc=A redstone lamp that emits light. Tinted gray.
tile.lamp.inverted.active.pink.name=Inverted Pink Lamp
tile.lamp.inverted.active.pink.desc=A redstone lamp that emits light. Tinted pink.
tile.lamp.inverted.active.lime.name=Inverted Lime Lamp
tile.lamp.inverted.active.lime.desc=A redstone lamp that emits light. Tinted lime.
tile.lamp.inverted.active.yellow.name=Inverted Yellow Lamp
tile.lamp.inverted.active.yellow.desc=A redstone lamp that emits light. Tinted yellow.
tile.lamp.inverted.active.lightblue.name=Inverted Light Blue Lamp
tile.lamp.inverted.active.lightblue.desc=A redstone lamp that emits light. Tinted light blue.
tile.lamp.inverted.active.magenta.name=Inverted Magenta Lamp
tile.lamp.inverted.active.magenta.desc=A redstone lamp that emits light. Tinted magenta.
tile.lamp.inverted.active.orange.name=Inverted Orange Lamp
tile.lamp.inverted.active.orange.desc=A redstone lamp that emits light. Tinted orange.
tile.lamp.inverted.active.white.name=Inverted White Lamp
tile.lamp.inverted.active.white.desc=A redstone lamp that emits light.

tile.stone.polished.name=Polished Stone
tile.stone.polished.desc=A polished block of stone.
tile.granite.polished.name=Polished Granite
tile.granite.polished.desc=A polished block of granite.
tile.limestone.polished.name=Polished Limestone
tile.limestone.polished.desc=A polished block of limestone.
tile.basalt.polished.name=Polished Basalt
tile.basalt.polished.desc=A polished block of basalt.
tile.slate.polished.name=Polished Slate
tile.slate.polished.desc=A polished block of slate.
tile.permafrost.polished.name=Polished Permafrost
tile.permafrost.polished.desc=A polished block of permafrost.
tile.netherrack.polished.name=Polished Netherrack
tile.netherrack.polished.desc=A polished block of netherrack.

tile.candle.wax.soul.name=Soul Wax Candle
tile.candle.wax.soul.desc=A waxy brown candle that glows with an unnatural light.

tile.basket.name=Basket
tile.basket.desc=A wicker container for holding items.

tile.overlay.pebbles.name=Pebbles
tile.overlay.pebbles.desc=A cluster of pebbles.

tile.fence.chainlink.name=Chainlink Fence
tile.fence.chainlink.desc=A fence made of interconnected chain links. Can be climbed!
tile.fence.paper_wall.name=Paper Wall Fence
tile.fence.paper_wall.desc=A fence made of delicate paper. Be careful!
tile.fence.steel.name=Steel Fence
tile.fence.steel.desc=A fence made of tough steel. Intimidating!

tile.lantern.firefly.green.name=Green Firefly Lantern
tile.lantern.firefly.green.desc=A glass jar containing a swarm of green fireflies.
tile.lantern.firefly.orange.name=Orange Firefly Lantern
tile.lantern.firefly.orange.desc=A glass jar containing a swarm of orange fireflies.
tile.lantern.firefly.blue.name=Blue Firefly Lantern
tile.lantern.firefly.blue.desc=A glass jar containing a swarm of blue fireflies.
tile.lantern.firefly.red.name=Red Firefly Lantern
tile.lantern.firefly.red.desc=A glass jar containing a swarm of red fireflies.

tile.flag.name=Flag
tile.flag.desc=A flag to mark your territory.

tile.paper_wall.name=Paper Wall
tile.paper_wall.desc=A wall made of delicate paper. Be careful!

tile.trapdoor.planks.oak.painted.name=Painted Trapdoor
tile.trapdoor.planks.oak.painted.desc=How did you get this?!
tile.trapdoor.planks.oak.painted.black.name=Black Wooden Trapdoor
tile.trapdoor.planks.oak.painted.black.desc=Like a door, but flat and painted black.
tile.trapdoor.planks.oak.painted.red.name=Red Wooden Trapdoor
tile.trapdoor.planks.oak.painted.red.desc=Like a door, but flat and painted red.
tile.trapdoor.planks.oak.painted.green.name=Green Wooden Trapdoor
tile.trapdoor.planks.oak.painted.green.desc=Like a door, but flat and painted green.
tile.trapdoor.planks.oak.painted.brown.name=Brown Wooden Trapdoor
tile.trapdoor.planks.oak.painted.brown.desc=Like a door, but flat and painted brown.
tile.trapdoor.planks.oak.painted.blue.name=Blue Wooden Trapdoor
tile.trapdoor.planks.oak.painted.blue.desc=Like a door, but flat and painted blue.
tile.trapdoor.planks.oak.painted.purple.name=Purple Wooden Trapdoor
tile.trapdoor.planks.oak.painted.purple.desc=Like a door, but flat and painted purple.
tile.trapdoor.planks.oak.painted.cyan.name=Cyan Wooden Trapdoor
tile.trapdoor.planks.oak.painted.cyan.desc=Like a door, but flat and painted cyan.
tile.trapdoor.planks.oak.painted.silver.name=Light Gray Wooden Trapdoor
tile.trapdoor.planks.oak.painted.silver.desc=Like a door, but flat and painted light gray.
tile.trapdoor.planks.oak.painted.gray.name=Gray Wooden Trapdoor
tile.trapdoor.planks.oak.painted.gray.desc=Like a door, but flat and painted gray.
tile.trapdoor.planks.oak.painted.pink.name=Pink Wooden Trapdoor
tile.trapdoor.planks.oak.painted.pink.desc=Like a door, but flat and painted pink.
tile.trapdoor.planks.oak.painted.lime.name=Lime Wooden Trapdoor
tile.trapdoor.planks.oak.painted.lime.desc=Like a door, but flat and painted lime.
tile.trapdoor.planks.oak.painted.yellow.name=Yellow Wooden Trapdoor
tile.trapdoor.planks.oak.painted.yellow.desc=Like a door, but flat and painted yellow.
tile.trapdoor.planks.oak.painted.lightblue.name=Light Blue Wooden Trapdoor
tile.trapdoor.planks.oak.painted.lightblue.desc=Like a door, but flat and painted light blue.
tile.trapdoor.planks.oak.painted.magenta.name=Magenta Wooden Trapdoor
tile.trapdoor.planks.oak.painted.magenta.desc=Like a door, but flat and painted magenta.
tile.trapdoor.planks.oak.painted.orange.name=Orange Wooden Trapdoor
tile.trapdoor.planks.oak.painted.orange.desc=Like a door, but flat and painted orange.
tile.trapdoor.planks.oak.painted.white.name=White Wooden Trapdoor
tile.trapdoor.planks.oak.painted.white.desc=Like a door, but flat and painted white.

tile.door.planks.oak.painted.bottom.name=Bottom Painted Wooden Door
tile.door.planks.oak.painted.bottom.desc=How did you get this?!
tile.door.planks.oak.painted.bottom.black.name=Black Bottom Wooden Door
tile.door.planks.oak.painted.bottom.black.desc=How did you get this?!
tile.door.planks.oak.painted.bottom.red.name=Red Bottom Wooden Door
tile.door.planks.oak.painted.bottom.red.desc=How did you get this?!
tile.door.planks.oak.painted.bottom.green.name=Green Bottom Wooden Door
tile.door.planks.oak.painted.bottom.green.desc=How did you get this?!
tile.door.planks.oak.painted.bottom.brown.name=Brown Bottom Wooden Door
tile.door.planks.oak.painted.bottom.brown.desc=How did you get this?!
tile.door.planks.oak.painted.bottom.blue.name=Blue Bottom Wooden Door
tile.door.planks.oak.painted.bottom.blue.desc=How did you get this?!
tile.door.planks.oak.painted.bottom.purple.name=Purple Bottom Wooden Door
tile.door.planks.oak.painted.bottom.purple.desc=How did you get this?!
tile.door.planks.oak.painted.bottom.cyan.name=Cyan Bottom Wooden Door
tile.door.planks.oak.painted.bottom.cyan.desc=How did you get this?!
tile.door.planks.oak.painted.bottom.silver.name=Light Gray Bottom Wooden Door
tile.door.planks.oak.painted.bottom.silver.desc=How did you get this?!
tile.door.planks.oak.painted.bottom.gray.name=Gray Bottom Wooden Door
tile.door.planks.oak.painted.bottom.gray.desc=How did you get this?!
tile.door.planks.oak.painted.bottom.pink.name=Pink Bottom Wooden Door
tile.door.planks.oak.painted.bottom.pink.desc=How did you get this?!
tile.door.planks.oak.painted.bottom.lime.name=Lime Bottom Wooden Door
tile.door.planks.oak.painted.bottom.lime.desc=How did you get this?!
tile.door.planks.oak.painted.bottom.yellow.name=Yellow Bottom Wooden Door
tile.door.planks.oak.painted.bottom.yellow.desc=How did you get this?!
tile.door.planks.oak.painted.bottom.lightblue.name=Light Blue Bottom Wooden Door
tile.door.planks.oak.painted.bottom.lightblue.desc=How did you get this?!
tile.door.planks.oak.painted.bottom.magenta.name=Magenta Bottom Wooden Door
tile.door.planks.oak.painted.bottom.magenta.desc=How did you get this?!
tile.door.planks.oak.painted.bottom.orange.name=Orange Bottom Wooden Door
tile.door.planks.oak.painted.bottom.orange.desc=How did you get this?!
tile.door.planks.oak.painted.bottom.white.name=White Bottom Wooden Door
tile.door.planks.oak.painted.bottom.white.desc=How did you get this?!

tile.door.planks.oak.painted.top.name=Top Painted Wooden Door
tile.door.planks.oak.painted.top.desc=How did you get this?!
tile.door.planks.oak.painted.top.black.name=Black Top Wooden Door
tile.door.planks.oak.painted.top.black.desc=How did you get this?!
tile.door.planks.oak.painted.top.red.name=Red Top Wooden Door
tile.door.planks.oak.painted.top.red.desc=How did you get this?!
tile.door.planks.oak.painted.top.green.name=Green Top Wooden Door
tile.door.planks.oak.painted.top.green.desc=How did you get this?!
tile.door.planks.oak.painted.top.brown.name=Brown Top Wooden Door
tile.door.planks.oak.painted.top.brown.desc=How did you get this?!
tile.door.planks.oak.painted.top.blue.name=Blue Top Wooden Door
tile.door.planks.oak.painted.top.blue.desc=How did you get this?!
tile.door.planks.oak.painted.top.purple.name=Purple Top Wooden Door
tile.door.planks.oak.painted.top.purple.desc=How did you get this?!
tile.door.planks.oak.painted.top.cyan.name=Cyan Top Wooden Door
tile.door.planks.oak.painted.top.cyan.desc=How did you get this?!
tile.door.planks.oak.painted.top.silver.name=Light Gray Top Wooden Door
tile.door.planks.oak.painted.top.silver.desc=How did you get this?!
tile.door.planks.oak.painted.top.gray.name=Gray Top Wooden Door
tile.door.planks.oak.painted.top.gray.desc=How did you get this?!
tile.door.planks.oak.painted.top.pink.name=Pink Top Wooden Door
tile.door.planks.oak.painted.top.pink.desc=How did you get this?!
tile.door.planks.oak.painted.top.lime.name=Lime Top Wooden Door
tile.door.planks.oak.painted.top.lime.desc=How did you get this?!
tile.door.planks.oak.painted.top.yellow.name=Yellow Top Wooden Door
tile.door.planks.oak.painted.top.yellow.desc=How did you get this?!
tile.door.planks.oak.painted.top.lightblue.name=Light Blue Top Wooden Door
tile.door.planks.oak.painted.top.lightblue.desc=How did you get this?!
tile.door.planks.oak.painted.top.magenta.name=Magenta Top Wooden Door
tile.door.planks.oak.painted.top.magenta.desc=How did you get this?!
tile.door.planks.oak.painted.top.orange.name=Orange Top Wooden Door
tile.door.planks.oak.painted.top.orange.desc=How did you get this?!
tile.door.planks.oak.painted.top.white.name=White Top Wooden Door
tile.door.planks.oak.painted.top.white.desc=How did you get this?!

tile.door.glass.top.name=Top Glass Door
tile.door.glass.top.desc=You shouldn't be seeing this.
tile.door.glass.bottom.name=Bottom Glass Door
tile.door.glass.bottom.desc=And that's not just because it's glass.

tile.door.steel.top.name=Top Steel Door
tile.door.steel.top.desc=Don't take this without asking.
tile.door.steel.bottom.name=Bottom Steel Door
tile.door.steel.bottom.desc=Steeler, no steeling!

tile.trapdoor.steel.name=Steel Trapdoor
tile.trapdoor.steel.desc=Like a door, but flat and made of durable steel.

tile.glass.steel.name=Reinforced Glass
tile.glass.steel.desc=Like glass, but without the fragility.

tile.rope.name=Rope
tile.rope.desc=Rope, rope, rope your boat.
\ No newline at end of file
+tile.stone.name=Stone
+tile.stone.desc=A block of solid stone.
+tile.basalt.name=Basalt
+tile.basalt.desc=A block of solid basalt.
+tile.limestone.name=Limestone
+tile.limestone.desc=A block of solid limestone.
+tile.granite.name=Granite
+tile.granite.desc=A block of solid granite.
+tile.marble.name=Marble
+tile.marble.desc=A block of solid marble.
+tile.slate.name=Slate
+tile.slate.desc=A block of solid slate.
+tile.permafrost.name=Permafrost
+tile.permafrost.desc=A block of frozen stone.
+
+tile.cobble.stone.name=Cobblestone
+tile.cobble.stone.desc=Stone, smashed apart and reconstituted.
+tile.cobble.stone.mossy.name=Mossy Cobblestone
+tile.cobble.stone.mossy.desc=Cobblestone with added greenery.
+tile.cobble.basalt.name=Cobbled Basalt
+tile.cobble.basalt.desc=Basalt, smashed apart and reconstituted.
+tile.cobble.limestone.name=Cobbled Limestone
+tile.cobble.limestone.desc=Limestone, smashed apart and reconstituted.
+tile.cobble.granite.name=Cobbled Granite
+tile.cobble.granite.desc=Granite, smashed apart and reconstituted.
+tile.cobble.permafrost.name=Cobbled Permafrost
+tile.cobble.permafrost.desc=Permafrost, smashed apart and reconstituted.
+
+tile.pillar.marble.name=Marble Pillar
+tile.pillar.marble.desc=A block of marble engraved into a pillar.
+tile.capstone.marble.name=Marble Capstone
+tile.capstone.marble.desc=A pillar capstone made of solid marble.
+
+tile.sandstone.name=Sandstone
+tile.sandstone.desc=Compacted sand.
+
+tile.stone.carved.name=Carved Stone
+tile.stone.carved.desc=Polished Stone with lines hewn through the middle.
+tile.granite.carved.name=Carved Granite
+tile.granite.carved.desc=Polished Granite with lines hewn through the middle.
+tile.limestone.carved.name=Carved Limestone
+tile.limestone.carved.desc=Polished Limestone with lines hewn through the middle.
+tile.basalt.carved.name=Carved Basalt
+tile.basalt.carved.desc=Polished Basalt with lines hewn through the middle.
+tile.permafrost.carved.name=Carved Permafrost
+tile.permafrost.carved.desc=Polished Permafrost with lines hewn through the middle.
+tile.netherrack.carved.name=Carved Netherrack
+tile.netherrack.carved.desc=Polished Netherrack with lines hewn through the middle.
+
+tile.planks.oak.name=Wooden Planks
+tile.planks.oak.desc=Processed tree insides.
+tile.planks.oak.painted.name=Mystery Planks
+tile.planks.oak.painted.desc=How did you get this?!
+tile.planks.oak.painted.black.name=Black Wooden Planks
+tile.planks.oak.painted.black.desc=Wooden planks, stained black.
+tile.planks.oak.painted.red.name=Red Wooden Planks
+tile.planks.oak.painted.red.desc=Wooden planks, stained red.
+tile.planks.oak.painted.green.name=Green Wooden Planks
+tile.planks.oak.painted.green.desc=Wooden planks, stained green.
+tile.planks.oak.painted.brown.name=Brown Wooden Planks
+tile.planks.oak.painted.brown.desc=Wooden planks, stained brown.
+tile.planks.oak.painted.blue.name=Blue Wooden Planks
+tile.planks.oak.painted.blue.desc=Wooden planks, stained blue.
+tile.planks.oak.painted.purple.name=Purple Wooden Planks
+tile.planks.oak.painted.purple.desc=Wooden planks, stained purple.
+tile.planks.oak.painted.cyan.name=Cyan Wooden Planks
+tile.planks.oak.painted.cyan.desc=Wooden planks, stained cyan.
+tile.planks.oak.painted.silver.name=Light Gray Wooden Planks
+tile.planks.oak.painted.silver.desc=Wooden planks, stained light gray.
+tile.planks.oak.painted.gray.name=Gray Wooden Planks
+tile.planks.oak.painted.gray.desc=Wooden planks, stained gray.
+tile.planks.oak.painted.pink.name=Pink Wooden Planks
+tile.planks.oak.painted.pink.desc=Wooden planks, stained pink.
+tile.planks.oak.painted.lime.name=Lime Wooden Planks
+tile.planks.oak.painted.lime.desc=Wooden planks, stained lime.
+tile.planks.oak.painted.yellow.name=Yellow Wooden Planks
+tile.planks.oak.painted.yellow.desc=Wooden planks, stained yellow.
+tile.planks.oak.painted.lightblue.name=Light Blue Wooden Planks
+tile.planks.oak.painted.lightblue.desc=Wooden planks, stained light blue.
+tile.planks.oak.painted.magenta.name=Magenta Wooden Planks
+tile.planks.oak.painted.magenta.desc=Wooden planks, stained magenta.
+tile.planks.oak.painted.orange.name=Orange Wooden Planks
+tile.planks.oak.painted.orange.desc=Wooden planks, stained orange.
+tile.planks.oak.painted.white.name=White Wooden Planks
+tile.planks.oak.painted.white.desc=Wooden planks, stained white.
+
+tile.torch.coal.name=Torch
+tile.torch.coal.desc=Helps light the way.
+
+tile.ladder.oak.name=Ladder
+tile.ladder.oak.desc=For very rapid vertical movement.
+
+tile.fence.planks.oak.name=Wooden Fence
+tile.fence.planks.oak.desc=Too tall to jump over.
+
+tile.fence.planks.oak.painted.name=Mystery Wooden Fence
+tile.fence.planks.oak.painted.desc=How did you get this?!
+tile.fence.planks.oak.painted.black.name=Black Wooden Fence
+tile.fence.planks.oak.painted.black.desc=A wooden fence, stained black.
+tile.fence.planks.oak.painted.red.name=Red Wooden Fence
+tile.fence.planks.oak.painted.red.desc=A wooden fence, stained red.
+tile.fence.planks.oak.painted.green.name=Green Wooden Fence
+tile.fence.planks.oak.painted.green.desc=A wooden fence, stained green.
+tile.fence.planks.oak.painted.brown.name=Brown Wooden Fence
+tile.fence.planks.oak.painted.brown.desc=A wooden fence, stained brown.
+tile.fence.planks.oak.painted.blue.name=Blue Wooden Fence
+tile.fence.planks.oak.painted.blue.desc=A wooden fence, stained blue.
+tile.fence.planks.oak.painted.purple.name=Purple Wooden Fence
+tile.fence.planks.oak.painted.purple.desc=A wooden fence, stained purple.
+tile.fence.planks.oak.painted.cyan.name=Cyan Wooden Fence
+tile.fence.planks.oak.painted.cyan.desc=A wooden fence, stained cyan.
+tile.fence.planks.oak.painted.silver.name=Light Gray Wooden Fence
+tile.fence.planks.oak.painted.silver.desc=A wooden fence, stained light gray.
+tile.fence.planks.oak.painted.gray.name=Gray Wooden Fence
+tile.fence.planks.oak.painted.gray.desc=A wooden fence, stained gray.
+tile.fence.planks.oak.painted.pink.name=Pink Wooden Fence
+tile.fence.planks.oak.painted.pink.desc=A wooden fence, stained pink.
+tile.fence.planks.oak.painted.lime.name=Lime Wooden Fence
+tile.fence.planks.oak.painted.lime.desc=A wooden fence, stained lime.
+tile.fence.planks.oak.painted.yellow.name=Yellow Wooden Fence
+tile.fence.planks.oak.painted.yellow.desc=A wooden fence, stained yellow.
+tile.fence.planks.oak.painted.lightblue.name=Light Blue Wooden Fence
+tile.fence.planks.oak.painted.lightblue.desc=A wooden fence, stained light blue.
+tile.fence.planks.oak.painted.magenta.name=Magenta Wooden Fence
+tile.fence.planks.oak.painted.magenta.desc=A wooden fence, stained magenta.
+tile.fence.planks.oak.painted.orange.name=Orange Wooden Fence
+tile.fence.planks.oak.painted.orange.desc=A wooden fence, stained orange.
+tile.fence.planks.oak.painted.white.name=White Wooden Fence
+tile.fence.planks.oak.painted.white.desc=A wooden fence, stained white.
+
+tile.fencegate.planks.oak.name=Wooden Fence Gate
+tile.fencegate.planks.oak.desc=Like a door but for fences.
+
+tile.fencegate.planks.oak.painted.name=Mystery Wooden Fence Gate
+tile.fencegate.planks.oak.painted.desc=How did you get this?!
+tile.fencegate.planks.oak.painted.black.name=Black Wooden Fence Gate
+tile.fencegate.planks.oak.painted.black.desc=A wooden fence gate, stained black.
+tile.fencegate.planks.oak.painted.red.name=Red Wooden Fence Gate
+tile.fencegate.planks.oak.painted.red.desc=A wooden fence gate, stained red.
+tile.fencegate.planks.oak.painted.green.name=Green Wooden Fence Gate
+tile.fencegate.planks.oak.painted.green.desc=A wooden fence gate, stained green.
+tile.fencegate.planks.oak.painted.brown.name=Brown Wooden Fence Gate
+tile.fencegate.planks.oak.painted.brown.desc=A wooden fence gate, stained brown.
+tile.fencegate.planks.oak.painted.blue.name=Blue Wooden Fence Gate
+tile.fencegate.planks.oak.painted.blue.desc=A wooden fence gate, stained blue.
+tile.fencegate.planks.oak.painted.purple.name=Purple Wooden Fence Gate
+tile.fencegate.planks.oak.painted.purple.desc=A wooden fence gate, stained purple.
+tile.fencegate.planks.oak.painted.cyan.name=Cyan Wooden Fence Gate
+tile.fencegate.planks.oak.painted.cyan.desc=A wooden fence gate, stained cyan.
+tile.fencegate.planks.oak.painted.silver.name=Light Gray Wooden Fence Gate
+tile.fencegate.planks.oak.painted.silver.desc=A wooden fence gate, stained light gray.
+tile.fencegate.planks.oak.painted.gray.name=Gray Wooden Fence Gate
+tile.fencegate.planks.oak.painted.gray.desc=A wooden fence gate, stained gray.
+tile.fencegate.planks.oak.painted.pink.name=Pink Wooden Fence Gate
+tile.fencegate.planks.oak.painted.pink.desc=A wooden fence gate, stained pink.
+tile.fencegate.planks.oak.painted.lime.name=Lime Wooden Fence Gate
+tile.fencegate.planks.oak.painted.lime.desc=A wooden fence gate, stained lime.
+tile.fencegate.planks.oak.painted.yellow.name=Yellow Wooden Fence Gate
+tile.fencegate.planks.oak.painted.yellow.desc=A wooden fence gate, stained yellow.
+tile.fencegate.planks.oak.painted.lightblue.name=Light Blue Wooden Fence Gate
+tile.fencegate.planks.oak.painted.lightblue.desc=A wooden fence gate, stained light blue.
+tile.fencegate.planks.oak.painted.magenta.name=Magenta Wooden Fence Gate
+tile.fencegate.planks.oak.painted.magenta.desc=A wooden fence gate, stained magenta.
+tile.fencegate.planks.oak.painted.orange.name=Orange Wooden Fence Gate
+tile.fencegate.planks.oak.painted.orange.desc=A wooden fence gate, stained orange.
+tile.fencegate.planks.oak.painted.white.name=White Wooden Fence Gate
+tile.fencegate.planks.oak.painted.white.desc=A wooden fence gate, stained white.
+
+tile.bookshelf.planks.oak.name=Bookshelf
+tile.bookshelf.planks.oak.desc=A repository of knowledge.
+
+tile.wool.name=Wool
+tile.wool.desc=How did you get this?!
+tile.wool.black.name=Black Wool
+tile.wool.black.desc=Sheep's wool, dyed black.
+tile.wool.red.name=Red Wool
+tile.wool.red.desc=Sheep's wool, dyed red.
+tile.wool.green.name=Green Wool
+tile.wool.green.desc=Sheep's wool, dyed green.
+tile.wool.brown.name=Brown Wool
+tile.wool.brown.desc=Sheep's wool, dyed brown.
+tile.wool.blue.name=Blue Wool
+tile.wool.blue.desc=Sheep's wool, dyed blue.
+tile.wool.purple.name=Purple Wool
+tile.wool.purple.desc=Sheep's wool, dyed purple.
+tile.wool.cyan.name=Cyan Wool
+tile.wool.cyan.desc=Sheep's wool, dyed cyan.
+tile.wool.silver.name=Light Gray Wool
+tile.wool.silver.desc=Sheep's wool, dyed light gray.
+tile.wool.gray.name=Gray Wool
+tile.wool.gray.desc=Sheep's wool, dyed gray.
+tile.wool.pink.name=Pink Wool
+tile.wool.pink.desc=Sheep's wool, dyed pink.
+tile.wool.lime.name=Lime Wool
+tile.wool.lime.desc=Sheep's wool, dyed lime.
+tile.wool.yellow.name=Yellow Wool
+tile.wool.yellow.desc=Sheep's wool, dyed yellow.
+tile.wool.lightblue.name=Light Blue Wool
+tile.wool.lightblue.desc=Sheep's wool, dyed light blue.
+tile.wool.magenta.name=Magenta Wool
+tile.wool.magenta.desc=Sheep's wool, dyed magenta.
+tile.wool.orange.name=Orange Wool
+tile.wool.orange.desc=Sheep's wool, dyed orange.
+tile.wool.white.name=White Wool
+tile.wool.white.desc=Sheep's wool, dyed white.
+
+tile.brick.clay.name=Clay Bricks
+tile.brick.clay.desc=Stacked bricks of baked clay.
+tile.brick.stone.polished.name=Polished Stone Bricks
+tile.brick.stone.polished.desc=Stacked bricks of polished stone.
+tile.brick.stone.polished.mossy.name=Mossy Polished Stone Bricks
+tile.brick.stone.polished.mossy.desc=Stacked bricks of polished stone with added greenery.
+tile.brick.sandstone.name=Sandstone Bricks
+tile.brick.sandstone.desc=Stacked bricks of sandstone.
+tile.brick.gold.name=Gold Bricks
+tile.brick.gold.desc=Stacked bricks of gold.
+tile.brick.lapis.name=Lapis Lazuli Bricks
+tile.brick.lapis.desc=Stacked bricks of lapis lazuli.
+tile.brick.basalt.name=Basalt Bricks
+tile.brick.basalt.desc=Stacked bricks of basalt.
+tile.brick.limestone.name=Limestone Bricks
+tile.brick.limestone.desc=Stacked bricks of limestone.
+tile.brick.granite.name=Granite Bricks
+tile.brick.granite.desc=Stacked bricks of granite.
+tile.brick.marble.name=Marble Bricks
+tile.brick.marble.desc=Stacked bricks of marble.
+tile.brick.slate.name=Slate Bricks
+tile.brick.slate.desc=Stacked bricks of slate.
+tile.brick.stone.name=Stone Bricks
+tile.brick.stone.desc=Stacked bricks of stone.
+tile.brick.permafrost.name=Permafrost Bricks
+tile.brick.permafrost.desc=Stacked bricks of permafrost.
+tile.brick.iron.name=Iron Bricks
+tile.brick.iron.desc=Stacked bricks of iron.
+tile.brick.steel.name=Steel Bricks
+tile.brick.steel.desc=Stacked bricks of steel.
+
+tile.slab.planks.oak.name=Wooden Slab
+tile.slab.planks.oak.desc=A slab of wooden planks.
+tile.slab.cobble.stone.name=Cobblestone Slab
+tile.slab.cobble.stone.desc=A slab of cobblestone.
+tile.slab.sandstone.name=Sandstone Slab
+tile.slab.sandstone.desc=A slab of sandstone.
+tile.slab.brick.stone.polished.name=Polished Stone Brick Slab
+tile.slab.brick.stone.polished.desc=A slab of polished stone bricks.
+tile.slab.stone.carved.name=Polished Stone Slab
+tile.slab.stone.carved.desc=A slab of polished stone.
+tile.slab.brick.marble.name=Marble Brick Slab
+tile.slab.brick.marble.desc=A slab of marble bricks.
+tile.slab.brick.clay.name=Clay Brick Slab
+tile.slab.brick.clay.desc=A slab of clay bricks.
+tile.slab.capstone.marble.name=Marble Capstone Slab
+tile.slab.capstone.marble.desc=Marble sculpted into a capstone.
+tile.slab.cobble.basalt.name=Cobbled Basalt Slab
+tile.slab.cobble.basalt.desc=A slab of cobbled basalt.
+tile.slab.cobble.limestone.name=Cobbled Limestone Slab
+tile.slab.cobble.limestone.desc=A slab of cobbled limestone.
+tile.slab.cobble.granite.name=Cobbled Granite Slab
+tile.slab.cobble.granite.desc=A slab of cobbled granite.
+tile.slab.brick.basalt.name=Basalt Brick Slab
+tile.slab.brick.basalt.desc=A slab of basalt bricks.
+tile.slab.brick.limestone.name=Limestone Brick Slab
+tile.slab.brick.limestone.desc=A slab of limestone bricks.
+tile.slab.brick.granite.name=Granite Brick Slab
+tile.slab.brick.granite.desc=A slab of granite bricks.
+tile.slab.brick.slate.name=Slate Brick Slab
+tile.slab.brick.slate.desc=A slab of slate bricks.
+tile.slab.brick.stone.name=Stone Brick Slab
+tile.slab.brick.stone.desc=A slab of stone bricks.
+tile.slab.granite.carved.name=Polished Granite Slab
+tile.slab.granite.carved.desc=A slab of polished granite.
+tile.slab.limestone.carved.name=Polished Limestone Slab
+tile.slab.limestone.carved.desc=A slab of polished limestone.
+tile.slab.basalt.carved.name=Polished Basalt Slab
+tile.slab.basalt.carved.desc=A slab of polished basalt.
+tile.slab.permafrost.carved.name=Polished Permafrost Slab
+tile.slab.permafrost.carved.desc=A slab of polished permafrost.
+
+tile.slab.planks.oak.painted.name=Mystery Wooden Slab
+tile.slab.planks.oak.painted.desc=How did you get this?!
+tile.slab.planks.oak.painted.black.name=Black Wooden Slab
+tile.slab.planks.oak.painted.black.desc=A slab of wooden planks, stained black.
+tile.slab.planks.oak.painted.red.name=Red Wooden Slab
+tile.slab.planks.oak.painted.red.desc=A slab of wooden planks, stained red.
+tile.slab.planks.oak.painted.green.name=Green Wooden Slab
+tile.slab.planks.oak.painted.green.desc=A slab of wooden planks, stained green.
+tile.slab.planks.oak.painted.brown.name=Brown Wooden Slab
+tile.slab.planks.oak.painted.brown.desc=A slab of wooden planks, stained brown.
+tile.slab.planks.oak.painted.blue.name=Blue Wooden Slab
+tile.slab.planks.oak.painted.blue.desc=A slab of wooden planks, stained blue.
+tile.slab.planks.oak.painted.purple.name=Purple Wooden Slab
+tile.slab.planks.oak.painted.purple.desc=A slab of wooden planks, stained purple.
+tile.slab.planks.oak.painted.cyan.name=Cyan Wooden Slab
+tile.slab.planks.oak.painted.cyan.desc=A slab of wooden planks, stained cyan.
+tile.slab.planks.oak.painted.silver.name=Light Gray Wooden Slab
+tile.slab.planks.oak.painted.silver.desc=A slab of wooden planks, stained light gray.
+tile.slab.planks.oak.painted.gray.name=Gray Wooden Slab
+tile.slab.planks.oak.painted.gray.desc=A slab of wooden planks, stained gray.
+tile.slab.planks.oak.painted.pink.name=Pink Wooden Slab
+tile.slab.planks.oak.painted.pink.desc=A slab of wooden planks, stained pink.
+tile.slab.planks.oak.painted.lime.name=Lime Wooden Slab
+tile.slab.planks.oak.painted.lime.desc=A slab of wooden planks, stained lime.
+tile.slab.planks.oak.painted.yellow.name=Yellow Wooden Slab
+tile.slab.planks.oak.painted.yellow.desc=A slab of wooden planks, stained yellow.
+tile.slab.planks.oak.painted.lightblue.name=Light Blue Wooden Slab
+tile.slab.planks.oak.painted.lightblue.desc=A slab of wooden planks, stained light blue.
+tile.slab.planks.oak.painted.magenta.name=Magenta Wooden Slab
+tile.slab.planks.oak.painted.magenta.desc=A slab of wooden planks, stained magenta.
+tile.slab.planks.oak.painted.orange.name=Orange Wooden Slab
+tile.slab.planks.oak.painted.orange.desc=A slab of wooden planks, stained orange.
+tile.slab.planks.oak.painted.white.name=White Wooden Slab
+tile.slab.planks.oak.painted.white.desc=A slab of wooden planks, stained white.
+tile.slab.cobble.permafrost.name=Cobbled Permafrost Slab
+tile.slab.cobble.permafrost.desc=A slab of cobbled permafrost.
+tile.slab.brick.sandstone.name=Sandstone Brick Slab
+tile.slab.brick.sandstone.desc=A slab of sandstone bricks.
+tile.slab.brick.permafrost.name=Permafrost Brick Slab
+tile.slab.brick.permafrost.desc=A slab of permafrost bricks.
+
+tile.stairs.planks.oak.name=Wooden Stairs
+tile.stairs.planks.oak.desc=Wooden block used for rapid vertical movement.
+tile.stairs.cobble.stone.name=Cobblestone Stairs
+tile.stairs.cobble.stone.desc=Cobblestone block used for rapid vertical movement.
+tile.stairs.brick.stone.polished.name=Polished Stone Brick Stairs
+tile.stairs.brick.stone.polished.desc=Polished stone brick block used for rapid vertical movement.
+tile.stairs.brick.marble.name=Marble Brick Stairs
+tile.stairs.brick.marble.desc=Marble brick block used for rapid vertical movement.
+tile.stairs.cobble.basalt.name=Cobbled Basalt Stairs
+tile.stairs.cobble.basalt.desc=Cobbled basalt block used for rapid vertical movement.
+tile.stairs.cobble.limestone.name=Cobbled Limestone Stairs
+tile.stairs.cobble.limestone.desc=Cobbled limestone block used for rapid vertical movement.
+tile.stairs.cobble.granite.name=Cobbled Granite Stairs
+tile.stairs.cobble.granite.desc=Cobbled granite block used for rapid vertical movement.
+tile.stairs.brick.basalt.name=Basalt Brick Stairs
+tile.stairs.brick.basalt.desc=Basalt brick block used for rapid vertical movement.
+tile.stairs.brick.limestone.name=Limestone Brick Stairs
+tile.stairs.brick.limestone.desc=Limestone brick block used for rapid vertical movement.
+tile.stairs.brick.granite.name=Granite Brick Stairs
+tile.stairs.brick.granite.desc=Granite brick block used for rapid vertical movement.
+tile.stairs.brick.clay.name=Clay Brick Stairs
+tile.stairs.brick.clay.desc=Clay brick block used for rapid vertical movement.
+tile.stairs.brick.slate.name=Slate Brick Stairs
+tile.stairs.brick.slate.desc=Slate brick block used for rapid vertical movement.
+tile.stairs.brick.stone.name=Stone Brick Stairs
+tile.stairs.brick.stone.desc=Stone brick block used for rapid vertical movement.
+tile.stairs.sandstone.name=Sandstone Stairs
+tile.stairs.sandstone.desc=Sandstone block used for rapid vertical movement.
+tile.stairs.cobble.permafrost.name=Cobbled Permafrost Stairs
+tile.stairs.cobble.permafrost.desc=Cobbled permafrost block used for rapid vertical movement.
+tile.stairs.brick.sandstone.name=Sandstone Brick Stairs
+tile.stairs.brick.sandstone.desc=Sandstone brick block used for rapid vertical movement.
+tile.stairs.brick.permafrost.name=Permafrost Brick Stairs
+tile.stairs.brick.permafrost.desc=Permafrost brick block used for rapid vertical movement.
+
+tile.stairs.planks.oak.painted.name=Mystery Wooden Stairs
+tile.stairs.planks.oak.painted.desc=How did you get this?!
+tile.stairs.planks.oak.painted.black.name=Black Wooden Stairs
+tile.stairs.planks.oak.painted.black.desc=Wooden steps, stained black.
+tile.stairs.planks.oak.painted.red.name=Red Wooden Stairs
+tile.stairs.planks.oak.painted.red.desc=Wooden steps, stained red.
+tile.stairs.planks.oak.painted.green.name=Green Wooden Stairs
+tile.stairs.planks.oak.painted.green.desc=Wooden steps, stained green.
+tile.stairs.planks.oak.painted.brown.name=Brown Wooden Stairs
+tile.stairs.planks.oak.painted.brown.desc=Wooden steps, stained brown.
+tile.stairs.planks.oak.painted.blue.name=Blue Wooden Stairs
+tile.stairs.planks.oak.painted.blue.desc=Wooden steps, stained blue.
+tile.stairs.planks.oak.painted.purple.name=Purple Wooden Stairs
+tile.stairs.planks.oak.painted.purple.desc=Wooden steps, stained purple.
+tile.stairs.planks.oak.painted.cyan.name=Cyan Wooden Stairs
+tile.stairs.planks.oak.painted.cyan.desc=Wooden steps, stained cyan.
+tile.stairs.planks.oak.painted.silver.name=Light Gray Wooden Stairs
+tile.stairs.planks.oak.painted.silver.desc=Wooden steps, stained light gray.
+tile.stairs.planks.oak.painted.gray.name=Gray Wooden Stairs
+tile.stairs.planks.oak.painted.gray.desc=Wooden steps, stained gray.
+tile.stairs.planks.oak.painted.pink.name=Pink Wooden Stairs
+tile.stairs.planks.oak.painted.pink.desc=Wooden steps, stained pink.
+tile.stairs.planks.oak.painted.lime.name=Lime Wooden Stairs
+tile.stairs.planks.oak.painted.lime.desc=Wooden steps, stained lime.
+tile.stairs.planks.oak.painted.yellow.name=Yellow Wooden Stairs
+tile.stairs.planks.oak.painted.yellow.desc=Wooden steps, stained yellow.
+tile.stairs.planks.oak.painted.lightblue.name=Light Blue Wooden Stairs
+tile.stairs.planks.oak.painted.lightblue.desc=Wooden steps, stained light blue.
+tile.stairs.planks.oak.painted.magenta.name=Magenta Wooden Stairs
+tile.stairs.planks.oak.painted.magenta.desc=Wooden steps, stained magenta.
+tile.stairs.planks.oak.painted.orange.name=Orange Wooden Stairs
+tile.stairs.planks.oak.painted.orange.desc=Wooden steps, stained orange.
+tile.stairs.planks.oak.painted.white.name=White Wooden Stairs
+tile.stairs.planks.oak.painted.white.desc=Wooden steps, stained white.
+
+tile.obsidian.name=Obsidian
+tile.obsidian.desc=Rapidly cooled volcanic rock.
+
+tile.glass.name=Glass
+tile.glass.desc=Sand heated until it becomes translucent.
+tile.glass.tinted.name=Tinted Glass
+tile.glass.tinted.desc=Darkened glass that blocks light.
+
+tile.grass.name=Grass
+tile.grass.desc=Crisp, crunchy grass.
+tile.grass.retro.name=Retro Grass
+tile.grass.retro.desc=Crisp, crunchy grass. Smells old.
+tile.grass.scorched.name=Scorched Grass
+tile.grass.scorched.desc=Grass that has baked in the sun.
+
+tile.path.dirt.name=Dirt Path
+tile.path.dirt.desc=Dirt worn down by frequent travelling.
+
+tile.dirt.name=Dirt
+tile.dirt.desc=Crumbly brown dirt.
+tile.dirt.scorched.name=Scorched Dirt
+tile.dirt.scorched.desc=Dirt that has baked in the sun.
+tile.dirt.scorched.rich.name=Rich Scorched Dirt
+tile.dirt.scorched.rich.desc=Dirt that has baked in the sun, searing in minerals.
+
+tile.mud.name=Mud
+tile.mud.desc=Mud sodden to its core.
+tile.mud.baked.name=Baked Mud
+tile.mud.baked.desc=Mud that's been dried out and cracked.
+
+tile.sponge.dry.name=Sponge
+tile.sponge.dry.desc=A dry sponge, good for soaking up water.
+tile.sponge.wet.name=Wet Sponge
+tile.sponge.wet.desc=A sponge full of water.
+tile.pumice.dry.name=Pumice
+tile.pumice.dry.desc=A dry porous volcanic rock, good for soaking up lava.
+tile.pumice.wet.name=Molten Pumice
+tile.pumice.wet.desc=Rock filled with lava.
+
+tile.moss.stone.name=Mossy Stone
+tile.moss.stone.desc=Moss covering a block of stone.
+tile.moss.basalt.name=Mossy Basalt
+tile.moss.basalt.desc=Moss covering a block of basalt.
+tile.moss.limestone.name=Mossy Limestone
+tile.moss.limestone.desc=Moss covering a block of limestone.
+tile.moss.granite.name=Mossy Granite
+tile.moss.granite.desc=Moss covering a block of granite.
+
+tile.sand.name=Sand
+tile.sand.desc=Gets everywhere.
+tile.gravel.name=Gravel
+tile.gravel.desc=A loose collection of pebbles.
+
+tile.bedrock.name=Bedrock
+tile.bedrock.desc=The toughest substance known to man.
+tile.boneshale.name=Bone Shale
+tile.boneshale.desc=An incredibly tough white rock torn from the deepest depths.
+
+tile.fluid.water.flowing.name=Flowing Water
+tile.fluid.water.flowing.desc=Water that flows when placed.
+tile.fluid.water.still.name=Still Water
+tile.fluid.water.still.desc=Water that does not flow when placed.
+tile.fluid.lava.flowing.name=Flowing Lava
+tile.fluid.lava.flowing.desc=Lava that flows when placed.
+tile.fluid.lava.still.name=Still Lava
+tile.fluid.lava.still.desc=Lava that does not flow when placed.
+
+tile.log.oak.name=Oak Log
+tile.log.oak.desc=A log from an oak tree.
+tile.log.pine.name=Pine Log
+tile.log.pine.desc=A log from a pine tree.
+tile.log.birch.name=Birch Log
+tile.log.birch.desc=A log from a birch tree.
+tile.log.cherry.name=Cherry Log
+tile.log.cherry.desc=A log from a cherry tree.
+tile.log.eucalyptus.name=Eucalyptus Log
+tile.log.eucalyptus.desc=A log from a eucalyptus tree.
+tile.log.oak.mossy.name=Mossy Oak Log
+tile.log.oak.mossy.desc=A log from a mossy oak tree.
+tile.log.thorn.name=Thorn Log
+tile.log.thorn.desc=A log from a thorn tree.
+tile.log.palm.name=Palm Log
+tile.log.palm.desc=A log from a palm tree.
+
+tile.leaves.oak.name=Oak Leaves
+tile.leaves.oak.desc=Leaves from an oak tree.
+tile.leaves.pine.name=Pine Leaves
+tile.leaves.pine.desc=Leaves from a pine tree.
+tile.leaves.birch.name=Birch Leaves
+tile.leaves.birch.desc=Leaves from a birch tree.
+tile.leaves.cherry.name=Cherry Leaves
+tile.leaves.cherry.desc=Leaves from a cherry tree.
+tile.leaves.eucalyptus.name=Eucalyptus Leaves
+tile.leaves.eucalyptus.desc=Leaves from a eucalyptus tree.
+tile.leaves.oak.retro.name=Retro Leaves
+tile.leaves.oak.retro.desc=Leaves from long ago.
+tile.leaves.shrub.name=Shrub Leaves
+tile.leaves.shrub.desc=Leaves from a shrub.
+tile.leaves.cherry.flowering.name=Flowering Cherry Leaves
+tile.leaves.cherry.flowering.desc=Leaves from a cherry tree that are budding with fruit.
+tile.leaves.cacao.name=Cacao Leaves
+tile.leaves.cacao.desc=Leaves from a cacao tree.
+tile.leaves.thorn.name=Thorn Leaves
+tile.leaves.thorn.desc=Leaves from a thorn tree.
+tile.leaves.palm.name=Palm Leaves
+tile.leaves.palm.desc=Leaves from a palm tree.
+
+tile.sapling.oak.name=Oak Sapling
+tile.sapling.oak.desc=Plant to grow an oak tree.
+tile.sapling.pine.name=Pine Sapling
+tile.sapling.pine.desc=Plant to grow a pine tree.
+tile.sapling.birch.name=Birch Sapling
+tile.sapling.birch.desc=Plant to grow a birch tree.
+tile.sapling.cherry.name=Cherry Sapling
+tile.sapling.cherry.desc=Plant to grow a cherry tree.
+tile.sapling.eucalyptus.name=Eucalyptus Sapling
+tile.sapling.eucalyptus.desc=Plant to grow a eucalyptus tree.
+tile.sapling.oak.retro.name=Retro Oak Sapling
+tile.sapling.oak.retro.desc=Plant to grow a tree from long ago.
+tile.sapling.shrub.name=Shrub Sapling
+tile.sapling.shrub.desc=Plant to grow a shrub.
+tile.sapling.cacao.name=Cacao Sapling
+tile.sapling.cacao.desc=Plant to grow a cacao tree.
+tile.sapling.thorn.name=Thorn Sapling
+tile.sapling.thorn.desc=Plant to grow a thorn tree.
+tile.sapling.palm.name=Palm Sapling
+tile.sapling.palm.desc=Plant to grow a palm tree.
+
+tile.tallgrass.name=Tall Grass
+tile.tallgrass.desc=Lush, growing grass.
+tile.tallgrass.fern.name=Fern
+tile.tallgrass.fern.desc=A fern plant.
+tile.deadbush.name=Dead Bush
+tile.deadbush.desc=A dried and withered plant.
+tile.spinifex.name=Spinifex
+tile.spinifex.desc=A spindly, thin grass from dry regions.
+tile.algae.name=Algae
+tile.algae.desc=Gooey vegetation that floats on the water's surface.
+
+tile.flower.yellow.name=Dandelion
+tile.flower.yellow.desc=A beautiful yellow flower.
+tile.flower.red.name=Rose
+tile.flower.red.desc=A beautiful red flower.
+tile.flower.purple.name=Heather
+tile.flower.purple.desc=Sprigs of purple petals. Brings good luck.
+tile.flower.lightblue.name=Bluebell
+tile.flower.lightblue.desc=A sad-looking blue flower.
+tile.flower.pink.name=Orchid
+tile.flower.pink.desc=A beautiful pink flower.
+tile.flower.orange.name=Marigold
+tile.flower.orange.desc=A beautiful orange flower.
+
+tile.mushroom.red.name=Red Mushroom
+tile.mushroom.red.desc=A tasty red mushroom. Good for stew.
+tile.mushroom.brown.name=Brown Mushroom
+tile.mushroom.brown.desc=A tasty brown mushroom. Good for stew.
+
+tile.ore.coal.stone.name=Stone Coal Ore
+tile.ore.coal.stone.desc=A chunk of coal embedded in stone.
+tile.ore.coal.basalt.name=Basalt Coal Ore
+tile.ore.coal.basalt.desc=A chunk of coal embedded in basalt.
+tile.ore.coal.limestone.name=Limestone Coal Ore
+tile.ore.coal.limestone.desc=A chunk of coal embedded in limestone.
+tile.ore.coal.granite.name=Granite Coal Ore
+tile.ore.coal.granite.desc=A chunk of coal embedded in granite.
+tile.ore.coal.permafrost.name=Permafrost Coal Ore
+tile.ore.coal.permafrost.desc=A chunk of coal embedded in permafrost.
+
+tile.ore.iron.stone.name=Stone Iron Ore
+tile.ore.iron.stone.desc=Iron nuggets embedded in stone.
+tile.ore.iron.basalt.name=Basalt Iron Ore
+tile.ore.iron.basalt.desc=Iron nuggets embedded in basalt.
+tile.ore.iron.limestone.name=Limestone Iron Ore
+tile.ore.iron.limestone.desc=Iron nuggets embedded in limestone.
+tile.ore.iron.granite.name=Granite Iron Ore
+tile.ore.iron.granite.desc=Iron nuggets embedded in granite.
+tile.ore.iron.permafrost.name=Permafrost Iron Ore
+tile.ore.iron.permafrost.desc=Iron nuggets embedded in permafrost.
+
+tile.ore.gold.stone.name=Stone Gold Ore
+tile.ore.gold.stone.desc=Gold nuggets embedded in stone.
+tile.ore.gold.basalt.name=Basalt Gold Ore
+tile.ore.gold.basalt.desc=Gold nuggets embedded in basalt.
+tile.ore.gold.limestone.name=Limestone Gold Ore
+tile.ore.gold.limestone.desc=Gold nuggets embedded in limestone.
+tile.ore.gold.granite.name=Granite Gold Ore
+tile.ore.gold.granite.desc=Gold nuggets embedded in granite.
+tile.ore.gold.permafrost.name=Permafrost Gold Ore
+tile.ore.gold.permafrost.desc=Gold nuggets embedded in permafrost.
+
+tile.ore.lapis.stone.name=Stone Lapis Lazuli Ore
+tile.ore.lapis.stone.desc=Chunks of lapis lazuli embedded in stone.
+tile.ore.lapis.basalt.name=Basalt Lapis Lazuli Ore
+tile.ore.lapis.basalt.desc=Chunks of lapis lazuli embedded in basalt.
+tile.ore.lapis.limestone.name=Limestone Lapis Lazuli Ore
+tile.ore.lapis.limestone.desc=Chunks of lapis lazuli embedded in limestone.
+tile.ore.lapis.granite.name=Granite Lapis Lazuli Ore
+tile.ore.lapis.granite.desc=Chunks of lapis lazuli embedded in granite.
+tile.ore.lapis.permafrost.name=Permafrost Lapis Lazuli Ore
+tile.ore.lapis.permafrost.desc=Chunks of lapis lazuli embedded in permafrost.
+
+tile.ore.redstone.stone.name=Stone Redstone Ore
+tile.ore.redstone.stone.desc=Red dust embedded in stone.
+tile.ore.redstone.basalt.name=Basalt Redstone Ore
+tile.ore.redstone.basalt.desc=Red dust embedded in basalt.
+tile.ore.redstone.limestone.name=Limestone Redstone Ore
+tile.ore.redstone.limestone.desc=Red dust embedded in limestone.
+tile.ore.redstone.granite.name=Granite Redstone Ore
+tile.ore.redstone.granite.desc=Red dust embedded in granite.
+tile.ore.redstone.permafrost.name=Permafrost Redstone Ore
+tile.ore.redstone.permafrost.desc=Red dust embedded in permafrost.
+tile.ore.redstone.glowing.stone.name=Glowing Stone Redstone Ore
+tile.ore.redstone.glowing.stone.desc=Red dust embedded in stone. Emits a faint light.
+tile.ore.redstone.glowing.basalt.name=Glowing Basalt Redstone Ore
+tile.ore.redstone.glowing.basalt.desc=Red dust embedded in basalt. Emits a faint light.
+tile.ore.redstone.glowing.limestone.name=Glowing Limestone Redstone Ore
+tile.ore.redstone.glowing.limestone.desc=Red dust embedded in limestone. Emits a faint light.
+tile.ore.redstone.glowing.granite.name=Glowing Granite Redstone Ore
+tile.ore.redstone.glowing.granite.desc=Red dust embedded in granite. Emits a faint light.
+tile.ore.redstone.glowing.permafrost.name=Glowing Permafrost Redstone Ore
+tile.ore.redstone.glowing.permafrost.desc=Red dust embedded in permafrost. Emits a faint light.
+
+tile.ore.diamond.stone.name=Stone Diamond Ore
+tile.ore.diamond.stone.desc=A chunk of diamond embedded in stone.
+tile.ore.diamond.basalt.name=Basalt Diamond Ore
+tile.ore.diamond.basalt.desc=A chunk of diamond embedded in basalt.
+tile.ore.diamond.limestone.name=Limestone Diamond Ore
+tile.ore.diamond.limestone.desc=A chunk of diamond embedded in limestone.
+tile.ore.diamond.granite.name=Granite Diamond Ore
+tile.ore.diamond.granite.desc=A chunk of diamond embedded in granite.
+tile.ore.diamond.permafrost.name=Permafrost Diamond Ore
+tile.ore.diamond.permafrost.desc=A chunk of diamond embedded in permafrost.
+
+tile.ore.nethercoal.netherrack.name=Nether Coal Ore
+tile.ore.nethercoal.netherrack.desc=A lump of Nether Coal embedded in netherrack.
+
+tile.block.coal.name=Block of Coal
+tile.block.coal.desc=A solid block of coal.
+tile.block.iron.name=Block of Iron
+tile.block.iron.desc=A solid block of iron. Heavy!
+tile.block.gold.name=Block of Gold
+tile.block.gold.desc=A solid block of gold, for flaunting your bling.
+tile.block.lapis.name=Block of Lapis Lazuli
+tile.block.lapis.desc=A solid block of lapis lazuli.
+tile.block.redstone.name=Block of Redstone
+tile.block.redstone.desc=A solid block of redstone.
+tile.block.diamond.name=Block of Diamond
+tile.block.diamond.desc=A solid block of diamond. Well done you.
+tile.block.nethercoal.name=Block of Nether Coal
+tile.block.nethercoal.desc=A solid block of Nether Coal.
+tile.block.steel.name=Block of Steel
+tile.block.steel.desc=A solid block of steel.
+tile.block.quartz.name=Block of Quartz
+tile.block.quartz.desc=A solid block of quartz.
+tile.block.olivine.name=Block of Olivine
+tile.block.olivine.desc=A solid block of olivine.
+tile.block.charcoal.name=Block of Charcoal
+tile.block.charcoal.desc=A solid block of charcoal.
+
+tile.wire.redstone.name=Redstone Wire
+tile.wire.redstone.desc=A strand of redstone dust laid on the ground.
+
+tile.torch.redstone.idle.name=Redstone Torch
+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.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.
+
+tile.pressureplate.stone.name=Stone Pressure Plate
+tile.pressureplate.stone.desc=Emits a redstone signal when living creatures step on it.
+tile.pressureplate.planks.oak.name=Wooden Pressure Plate
+tile.pressureplate.planks.oak.desc=Emits a redstone signal when anything steps on it.
+tile.pressureplate.cobble.stone.name=Cobblestone Pressure Plate
+tile.pressureplate.cobble.stone.desc=Emits a redstone signal when players step 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
+tile.motionsensor.active.desc=Emits a redstone signal when something moves past it.
+
+tile.repeater.idle.name=Redstone Repeater
+tile.repeater.idle.desc=Repeats redstone signals over a distance.
+tile.repeater.active.name=Redstone Repeater
+tile.repeater.active.desc=Repeats redstone signals over a distance.
+
+tile.piston.base.name=Piston
+tile.piston.base.desc=Pushes blocks but does not pull them.
+tile.piston.base.sticky.name=Sticky Piston
+tile.piston.base.sticky.desc=Pushes and pulls blocks.
+tile.piston.head.name=Piston Head
+tile.piston.head.desc=The pushy part of the piston.
+tile.piston.moving.name=Piston Mover
+tile.piston.moving.desc=The invisible moving part of the piston.
+tile.piston.base.steel.name=Reinforced Piston
+tile.piston.base.steel.desc=Pushes blocks and entities with immense force.
+tile.piston.head.steel.name=Reinforced Piston Head
+tile.piston.head.steel.desc=The pushy part of the steel piston.
+
+tile.noteblock.name=Note Block
+tile.noteblock.desc=Plays a musical note when powered.
+
+tile.rail.name=Rail
+tile.rail.desc=For rapid horizontal movement via minecart.
+tile.rail.powered.name=Powered Rail
+tile.rail.powered.desc=Makes minecarts go faster when powered.
+tile.rail.detector.name=Detector Rail
+tile.rail.detector.desc=Emits a redstone signal when a minecart crosses it.
+
+tile.spikes.name=Spike Trap
+tile.spikes.desc=Deactivates when powered. Technically a war crime.
+
+tile.dispenser.cobble.stone.name=Dispenser
+tile.dispenser.cobble.stone.desc=Fires items out of the hole in the front.
+tile.activator.cobble.netherrack.name=Activator
+tile.activator.cobble.netherrack.desc=Uses items for you.
+
+tile.trapdoor.planks.oak.name=Wooden Trapdoor
+tile.trapdoor.planks.oak.desc=Like a door, but flat.
+tile.trapdoor.iron.name=Iron Trapdoor
+tile.trapdoor.iron.desc=Like a door, but flat and made of iron.
+tile.trapdoor.glass.name=Glass Trapdoor
+tile.trapdoor.glass.desc=Useless at its job.
+
+tile.tnt.name=TNT
+tile.tnt.desc=Goes off with a bang.
+
+tile.door.planks.oak.bottom.name=Bottom Wooden Door
+tile.door.planks.oak.bottom.desc=/give Honeydew 64 46
+tile.door.planks.oak.top.name=Top Wooden Door
+tile.door.planks.oak.top.desc=Oh, wait, that's not TNT, that's half a door.
+tile.door.iron.bottom.name=Bottom Iron Door
+tile.door.iron.bottom.desc=Not much good on its own.
+tile.door.iron.top.name=Top Iron Door
+tile.door.iron.top.desc=Could use a friend.
+
+tile.mesh.name=Mesh Block
+tile.mesh.desc=Allows items to pass through but nothing else.
+tile.mesh.gold.name=Gold Mesh Block
+tile.mesh.gold.desc=Allows a specified item to pass through but nothing else.
+
+tile.bed.name=Bed
+tile.bed.desc=Bed!
+tile.seat.name=Seat
+tile.seat.desc=Sit down and relax.
+
+tile.cobweb.name=Cobweb
+tile.cobweb.desc=Sticky silky strands woven by spiders.
+
+tile.fire.name=Fire
+tile.fire.desc=Ouch, fire hot!
+tile.brazier.inactive.name=Brazier
+tile.brazier.inactive.desc=A safe place to keep a fire.
+tile.brazier.active.name=Lit Brazier
+tile.brazier.active.desc=A safe place to keep a fire.
+
+tile.mobspawner.name=Mob Spawner
+tile.mobspawner.desc=A cage filled with horror.
+tile.mobspawner.deactivated.name=Deactivated Mob Spawner
+tile.mobspawner.deactivated.desc=A cage no longer filled with horror.
+
+tile.workbench.name=Workbench
+tile.workbench.desc=For bigger projects.
+
+tile.furnace.stone.idle.name=Furnace
+tile.furnace.stone.idle.desc=For melting, heating, and cooking blocks and items.
+tile.furnace.stone.active.name=Lit Furnace
+tile.furnace.stone.active.desc=For melting, heating, and cooking blocks and items.
+tile.furnace.blast.idle.name=Blast Furnace
+tile.furnace.blast.idle.desc=Burns hotter than a normal furnace.
+tile.furnace.blast.active.name=Lit Blast Furnace
+tile.furnace.blast.active.desc=Burns hotter than a normal furnace.
+
+tile.trommel.idle.name=Trommel
+tile.trommel.idle.desc=A machine that separates dirt from other useful items.
+tile.trommel.active.name=Lit Trommel
+tile.trommel.active.desc=A machine that separates dirt from other useful items.
+
+tile.chest.planks.oak.name=Wooden Chest
+tile.chest.planks.oak.desc=A place to store your items.
+tile.chest.planks.oak.painted.name=Mystery Wooden Chest
+tile.chest.planks.oak.painted.desc=How did you get this?!
+tile.chest.planks.oak.painted.black.name=Black Wooden Chest
+tile.chest.planks.oak.painted.black.desc=A place to store your items.
+tile.chest.planks.oak.painted.red.name=Red Wooden Chest
+tile.chest.planks.oak.painted.red.desc=A place to store your items.
+tile.chest.planks.oak.painted.green.name=Green Wooden Chest
+tile.chest.planks.oak.painted.green.desc=A place to store your items.
+tile.chest.planks.oak.painted.brown.name=Brown Wooden Chest
+tile.chest.planks.oak.painted.brown.desc=A place to store your items.
+tile.chest.planks.oak.painted.blue.name=Blue Wooden Chest
+tile.chest.planks.oak.painted.blue.desc=A place to store your items.
+tile.chest.planks.oak.painted.purple.name=Purple Wooden Chest
+tile.chest.planks.oak.painted.purple.desc=A place to store your items.
+tile.chest.planks.oak.painted.cyan.name=Cyan Wooden Chest
+tile.chest.planks.oak.painted.cyan.desc=A place to store your items.
+tile.chest.planks.oak.painted.silver.name=Light Gray Wooden Chest
+tile.chest.planks.oak.painted.silver.desc=A place to store your items.
+tile.chest.planks.oak.painted.gray.name=Gray Wooden Chest
+tile.chest.planks.oak.painted.gray.desc=A place to store your items.
+tile.chest.planks.oak.painted.pink.name=Pink Wooden Chest
+tile.chest.planks.oak.painted.pink.desc=A place to store your items.
+tile.chest.planks.oak.painted.lime.name=Lime Wooden Chest
+tile.chest.planks.oak.painted.lime.desc=A place to store your items.
+tile.chest.planks.oak.painted.yellow.name=Yellow Wooden Chest
+tile.chest.planks.oak.painted.yellow.desc=A place to store your items.
+tile.chest.planks.oak.painted.lightblue.name=Light Blue Wooden Chest
+tile.chest.planks.oak.painted.lightblue.desc=A place to store your items.
+tile.chest.planks.oak.painted.magenta.name=Magenta Wooden Chest
+tile.chest.planks.oak.painted.magenta.desc=A place to store your items.
+tile.chest.planks.oak.painted.orange.name=Orange Wooden Chest
+tile.chest.planks.oak.painted.orange.desc=A place to store your items.
+tile.chest.planks.oak.painted.white.name=White Wooden Chest
+tile.chest.planks.oak.painted.white.desc=A place to store your items.
+tile.chest.legacy.name=Legacy Chest
+
+tile.crops.wheat.name=Wheat Crops
+tile.crops.wheat.desc=Wheat, plucked from the ground.
+tile.crops.pumpkin.name=Pumpkin Crops
+tile.crops.pumpkin.desc=A ripening pumpkin, plucked from the ground.
+
+tile.farmland.dirt.name=Farmland
+tile.farmland.dirt.desc=Tilled soil for growing crops.
+
+tile.sign.post.planks.oak.name=Oak Signpost
+tile.sign.post.planks.oak.desc=A free-standing oak sign.
+tile.sign.wall.planks.oak.name=Oak Wall Sign
+tile.sign.wall.planks.oak.desc=An oak sign placed on a wall.
+
+tile.layer.snow.name=Snow Layer
+tile.layer.snow.desc=A flat layer of snow.
+tile.layer.leaves.oak.name=Leaf Pile
+tile.layer.leaves.oak.desc=A pile of leaves.
+tile.layer.slate.name=Slate Layer
+tile.layer.slate.desc=A flat sheet of slate.
+tile.layer.ash.name=Ash Layer
+tile.layer.ash.desc=A flat layer of ash.
+
+tile.ice.name=Ice
+tile.ice.desc=Slippery frozen water.
+tile.ice.perma.name=Perma-Ice
+tile.ice.perma.desc=Ice imbued with the essence of stone.
+
+tile.block.snow.name=Block of Snow
+tile.block.snow.desc=Lots of snow compacted into a block.
+tile.block.ash.name=Block of Ash
+tile.block.ash.desc=Lots of ash compacted into a block.
+
+tile.cactus.name=Cactus
+tile.cactus.desc=A spiky plant that grows in the desert.
+
+tile.block.clay.name=Block of Clay
+tile.block.clay.desc=A block of compacted clay.
+
+tile.sugarcane.name=Sugarcane
+tile.sugarcane.desc=Not as sweet as you.
+
+tile.block.sugarcane.name=Block of Sugarcane
+tile.block.sugarcane.desc=One very big sugar cube.
+tile.block.sugarcane.baked.name=Baked Block of Sugarcane
+tile.block.sugarcane.baked.desc=Smells absolutely delicious.
+
+tile.jukebox.name=Jukebox
+tile.jukebox.desc=Plays your favourite tunes, assuming you have the right record.
+
+tile.pumpkin.name=Pumpkin
+tile.pumpkin.desc=An orange squash that grows in the wild.
+tile.pumpkin.carved.idle.name=Carved Pumpkin
+tile.pumpkin.carved.idle.desc=An orange squash with a spooky face.
+tile.pumpkin.carved.active.name=Jack 'o' Lantern
+tile.pumpkin.carved.active.desc=An orange squash with a spooky face, lit by a torch.
+tile.pumpkin.redstone.name=Redstone Jack 'o' Lantern
+tile.pumpkin.redstone.desc=An orange squash with a spooky face, lit by a redstone torch.
+
+tile.cobble.netherrack.name=Cobbled Netherrack
+tile.cobble.netherrack.desc=Dark red rock that burns forever.
+tile.cobble.netherrack.igneous.name=Igneous Cobbled Netherrack
+tile.cobble.netherrack.igneous.desc=Extreme heat has made this rock glow from inside.
+tile.cobble.netherrack.mossy.name=Mossy Cobbled Netherrack
+tile.cobble.netherrack.mossy.desc=Cobbled netherrack, covered in something like moss.
+tile.netherrack.name=Netherrack
+tile.netherrack.desc=Dark red rock, smoothed by heat and time.
+tile.brick.netherrack.name=Netherrack Bricks
+tile.brick.netherrack.desc=Stacked bricks of netherrack.
+tile.slab.netherrack.carved.name=Polished Netherrack Slab
+tile.slab.netherrack.carved.desc=A slab of polished netherrack.
+tile.slab.cobble.netherrack.name=Cobbled Netherrack Slab
+tile.slab.cobble.netherrack.desc=A slab of cobbled netherrack.
+tile.slab.brick.netherrack.name=Netherrack Brick Slab
+tile.slab.brick.netherrack.desc=A slab of netherrack bricks.
+tile.stairs.cobble.netherrack.name=Cobbled Netherrack Stairs
+tile.stairs.cobble.netherrack.desc=Cobbled netherrack block used for rapid vertical movement.
+tile.stairs.brick.netherrack.name=Netherrack Brick Stairs
+tile.stairs.brick.netherrack.desc=Netherrack brick block used for rapid vertical movement.
+
+tile.soulsand.name=Soul Sand
+tile.soulsand.desc=Squishy brown sand that's tough to walk through.
+tile.soulschist.name=Soul Schist
+tile.soulschist.desc=A hideous medley of ancient bones, fused through heat, pressure, and time.
+
+tile.glowstone.name=Block of Glowstone
+tile.glowstone.desc=Constantly emits a strong yellowish light.
+
+tile.portal.nether.name=Nether Portal
+tile.portal.nether.desc=A portal to the Nether.
+tile.portal.paradise.name=Paradise Portal
+tile.portal.paradise.desc=A portal to Paradise.
+
+tile.cake.name=Cake
+tile.cake.desc=I just can't cake it any more.
+tile.pumpkin_pie.name=Pumpkin Pie
+tile.pumpkin_pie.desc=I don't wanna pie!
+
+tile.lamp.idle.name=Lamp
+tile.lamp.idle.desc=How did you get this?!
+tile.lamp.idle.black.name=Black Lamp
+tile.lamp.idle.black.desc=A redstone lamp that can emit light. Tinted black.
+tile.lamp.idle.red.name=Red Lamp
+tile.lamp.idle.red.desc=A redstone lamp that can emit light. Tinted red.
+tile.lamp.idle.green.name=Green Lamp
+tile.lamp.idle.green.desc=A redstone lamp that can emit light. Tinted green.
+tile.lamp.idle.brown.name=Brown Lamp
+tile.lamp.idle.brown.desc=A redstone lamp that can emit light. Tinted brown.
+tile.lamp.idle.blue.name=Blue Lamp
+tile.lamp.idle.blue.desc=A redstone lamp that can emit light. Tinted blue.
+tile.lamp.idle.purple.name=Purple Lamp
+tile.lamp.idle.purple.desc=A redstone lamp that can emit light. Tinted purple.
+tile.lamp.idle.cyan.name=Cyan Lamp
+tile.lamp.idle.cyan.desc=A redstone lamp that can emit light. Tinted cyan.
+tile.lamp.idle.silver.name=Light Gray Lamp
+tile.lamp.idle.silver.desc=A redstone lamp that can emit light. Tinted light gray.
+tile.lamp.idle.gray.name=Gray Lamp
+tile.lamp.idle.gray.desc=A redstone lamp that can emit light. Tinted gray.
+tile.lamp.idle.pink.name=Pink Lamp
+tile.lamp.idle.pink.desc=A redstone lamp that can emit light. Tinted pink.
+tile.lamp.idle.lime.name=Lime Lamp
+tile.lamp.idle.lime.desc=A redstone lamp that can emit light. Tinted lime.
+tile.lamp.idle.yellow.name=Yellow Lamp
+tile.lamp.idle.yellow.desc=A redstone lamp that can emit light. Tinted yellow.
+tile.lamp.idle.lightblue.name=Light Blue Lamp
+tile.lamp.idle.lightblue.desc=A redstone lamp that can emit light. Tinted light blue.
+tile.lamp.idle.magenta.name=Magenta Lamp
+tile.lamp.idle.magenta.desc=A redstone lamp that can emit light. Tinted magenta.
+tile.lamp.idle.orange.name=Orange Lamp
+tile.lamp.idle.orange.desc=A redstone lamp that can emit light. Tinted orange.
+tile.lamp.idle.white.name=White Lamp
+tile.lamp.idle.white.desc=A redstone lamp that can emit light.
+
+tile.lamp.active.name=Lit Lamp
+tile.lamp.active.desc=How did you get this?!
+tile.lamp.active.black.name=Lit Black Lamp
+tile.lamp.active.black.desc=A redstone lamp that emits light. Tinted black.
+tile.lamp.active.red.name=Lit Red Lamp
+tile.lamp.active.red.desc=A redstone lamp that emits light. Tinted red.
+tile.lamp.active.green.name=Lit Green Lamp
+tile.lamp.active.green.desc=A redstone lamp that emits light. Tinted green.
+tile.lamp.active.brown.name=Lit Brown Lamp
+tile.lamp.active.brown.desc=A redstone lamp that emits light. Tinted brown.
+tile.lamp.active.blue.name=Lit Blue Lamp
+tile.lamp.active.blue.desc=A redstone lamp that emits light. Tinted blue.
+tile.lamp.active.purple.name=Lit Purple Lamp
+tile.lamp.active.purple.desc=A redstone lamp that emits light. Tinted purple.
+tile.lamp.active.cyan.name=Lit Cyan Lamp
+tile.lamp.active.cyan.desc=A redstone lamp that emits light. Tinted cyan.
+tile.lamp.active.silver.name=Lit Light Gray Lamp
+tile.lamp.active.silver.desc=A redstone lamp that emits light. Tinted light gray.
+tile.lamp.active.gray.name=Lit Gray Lamp
+tile.lamp.active.gray.desc=A redstone lamp that emits light. Tinted gray.
+tile.lamp.active.pink.name=Lit Pink Lamp
+tile.lamp.active.pink.desc=A redstone lamp that emits light. Tinted pink.
+tile.lamp.active.lime.name=Lit Lime Lamp
+tile.lamp.active.lime.desc=A redstone lamp that emits light. Tinted lime.
+tile.lamp.active.yellow.name=Lit Yellow Lamp
+tile.lamp.active.yellow.desc=A redstone lamp that emits light. Tinted yellow.
+tile.lamp.active.lightblue.name=Lit Light Blue Lamp
+tile.lamp.active.lightblue.desc=A redstone lamp that emits light. Tinted light blue.
+tile.lamp.active.magenta.name=Lit Magenta Lamp
+tile.lamp.active.magenta.desc=A redstone lamp that emits light. Tinted magenta.
+tile.lamp.active.orange.name=Lit Orange Lamp
+tile.lamp.active.orange.desc=A redstone lamp that emits light. Tinted orange.
+tile.lamp.active.white.name=Lit White Lamp
+tile.lamp.active.white.desc=A redstone lamp that emits light.
+
+tile.lamp.inverted.idle.name=Unlit Inverted Lamp
+tile.lamp.inverted.idle.desc=How did you get this?!
+tile.lamp.inverted.idle.black.name=Unlit Inverted Black Lamp
+tile.lamp.inverted.idle.black.desc=A redstone lamp that can emit light. Tinted black.
+tile.lamp.inverted.idle.red.name=Unlit Inverted Red Lamp
+tile.lamp.inverted.idle.red.desc=A redstone lamp that can emit light. Tinted red.
+tile.lamp.inverted.idle.green.name=Unlit Inverted Green Lamp
+tile.lamp.inverted.idle.green.desc=A redstone lamp that can emit light. Tinted green.
+tile.lamp.inverted.idle.brown.name=Unlit Inverted Brown Lamp
+tile.lamp.inverted.idle.brown.desc=A redstone lamp that can emit light. Tinted brown.
+tile.lamp.inverted.idle.blue.name=Unlit Inverted Blue Lamp
+tile.lamp.inverted.idle.blue.desc=A redstone lamp that can emit light. Tinted blue.
+tile.lamp.inverted.idle.purple.name=Unlit Inverted Purple Lamp
+tile.lamp.inverted.idle.purple.desc=A redstone lamp that can emit light. Tinted purple.
+tile.lamp.inverted.idle.cyan.name=Unlit Inverted Cyan Lamp
+tile.lamp.inverted.idle.cyan.desc=A redstone lamp that can emit light. Tinted cyan.
+tile.lamp.inverted.idle.silver.name=Unlit Inverted Light Gray Lamp
+tile.lamp.inverted.idle.silver.desc=A redstone lamp that can emit light. Tinted light gray.
+tile.lamp.inverted.idle.gray.name=Unlit Inverted Gray Lamp
+tile.lamp.inverted.idle.gray.desc=A redstone lamp that can emit light. Tinted gray.
+tile.lamp.inverted.idle.pink.name=Unlit Inverted Pink Lamp
+tile.lamp.inverted.idle.pink.desc=A redstone lamp that can emit light. Tinted pink.
+tile.lamp.inverted.idle.lime.name=Unlit Inverted Lime Lamp
+tile.lamp.inverted.idle.lime.desc=A redstone lamp that can emit light. Tinted lime.
+tile.lamp.inverted.idle.yellow.name=Unlit Inverted Yellow Lamp
+tile.lamp.inverted.idle.yellow.desc=A redstone lamp that can emit light. Tinted yellow.
+tile.lamp.inverted.idle.lightblue.name=Unlit Inverted Light Blue Lamp
+tile.lamp.inverted.idle.lightblue.desc=A redstone lamp that can emit light. Tinted light blue.
+tile.lamp.inverted.idle.magenta.name=Unlit Inverted Magenta Lamp
+tile.lamp.inverted.idle.magenta.desc=A redstone lamp that can emit light. Tinted magenta.
+tile.lamp.inverted.idle.orange.name=Unlit Inverted Orange Lamp
+tile.lamp.inverted.idle.orange.desc=A redstone lamp that can emit light. Tinted orange.
+tile.lamp.inverted.idle.white.name=Unlit Inverted White Lamp
+tile.lamp.inverted.idle.white.desc=A redstone lamp that can emit light.
+
+tile.lamp.inverted.active.name=Inverted Lamp
+tile.lamp.inverted.active.desc=How did you get this?!
+tile.lamp.inverted.active.black.name=Inverted Black Lamp
+tile.lamp.inverted.active.black.desc=A redstone lamp that emits light. Tinted black.
+tile.lamp.inverted.active.red.name=Inverted Red Lamp
+tile.lamp.inverted.active.red.desc=A redstone lamp that emits light. Tinted red.
+tile.lamp.inverted.active.green.name=Inverted Green Lamp
+tile.lamp.inverted.active.green.desc=A redstone lamp that emits light. Tinted green.
+tile.lamp.inverted.active.brown.name=Inverted Brown Lamp
+tile.lamp.inverted.active.brown.desc=A redstone lamp that emits light. Tinted brown.
+tile.lamp.inverted.active.blue.name=Inverted Blue Lamp
+tile.lamp.inverted.active.blue.desc=A redstone lamp that emits light. Tinted blue.
+tile.lamp.inverted.active.purple.name=Inverted Purple Lamp
+tile.lamp.inverted.active.purple.desc=A redstone lamp that emits light. Tinted purple.
+tile.lamp.inverted.active.cyan.name=Inverted Cyan Lamp
+tile.lamp.inverted.active.cyan.desc=A redstone lamp that emits light. Tinted cyan.
+tile.lamp.inverted.active.silver.name=Inverted Light Gray Lamp
+tile.lamp.inverted.active.silver.desc=A redstone lamp that emits light. Tinted light gray.
+tile.lamp.inverted.active.gray.name=Inverted Gray Lamp
+tile.lamp.inverted.active.gray.desc=A redstone lamp that emits light. Tinted gray.
+tile.lamp.inverted.active.pink.name=Inverted Pink Lamp
+tile.lamp.inverted.active.pink.desc=A redstone lamp that emits light. Tinted pink.
+tile.lamp.inverted.active.lime.name=Inverted Lime Lamp
+tile.lamp.inverted.active.lime.desc=A redstone lamp that emits light. Tinted lime.
+tile.lamp.inverted.active.yellow.name=Inverted Yellow Lamp
+tile.lamp.inverted.active.yellow.desc=A redstone lamp that emits light. Tinted yellow.
+tile.lamp.inverted.active.lightblue.name=Inverted Light Blue Lamp
+tile.lamp.inverted.active.lightblue.desc=A redstone lamp that emits light. Tinted light blue.
+tile.lamp.inverted.active.magenta.name=Inverted Magenta Lamp
+tile.lamp.inverted.active.magenta.desc=A redstone lamp that emits light. Tinted magenta.
+tile.lamp.inverted.active.orange.name=Inverted Orange Lamp
+tile.lamp.inverted.active.orange.desc=A redstone lamp that emits light. Tinted orange.
+tile.lamp.inverted.active.white.name=Inverted White Lamp
+tile.lamp.inverted.active.white.desc=A redstone lamp that emits light.
+
+tile.stone.polished.name=Polished Stone
+tile.stone.polished.desc=A polished block of stone.
+tile.granite.polished.name=Polished Granite
+tile.granite.polished.desc=A polished block of granite.
+tile.limestone.polished.name=Polished Limestone
+tile.limestone.polished.desc=A polished block of limestone.
+tile.basalt.polished.name=Polished Basalt
+tile.basalt.polished.desc=A polished block of basalt.
+tile.slate.polished.name=Polished Slate
+tile.slate.polished.desc=A polished block of slate.
+tile.permafrost.polished.name=Polished Permafrost
+tile.permafrost.polished.desc=A polished block of permafrost.
+tile.netherrack.polished.name=Polished Netherrack
+tile.netherrack.polished.desc=A polished block of netherrack.
+
+tile.candle.wax.soul.name=Soul Wax Candle
+tile.candle.wax.soul.desc=A waxy brown candle that glows with an unnatural light.
+
+tile.basket.name=Basket
+tile.basket.desc=A wicker container for holding items.
+
+tile.overlay.pebbles.name=Pebbles
+tile.overlay.pebbles.desc=A cluster of pebbles.
+
+tile.fence.chainlink.name=Chainlink Fence
+tile.fence.chainlink.desc=A fence made of interconnected chain links. Can be climbed!
+tile.fence.paper_wall.name=Paper Wall Fence
+tile.fence.paper_wall.desc=A fence made of delicate paper. Be careful!
+tile.fence.steel.name=Steel Fence
+tile.fence.steel.desc=A fence made of tough steel. Intimidating!
+
+tile.lantern.firefly.green.name=Green Firefly Lantern
+tile.lantern.firefly.green.desc=A glass jar containing a swarm of green fireflies.
+tile.lantern.firefly.orange.name=Orange Firefly Lantern
+tile.lantern.firefly.orange.desc=A glass jar containing a swarm of orange fireflies.
+tile.lantern.firefly.blue.name=Blue Firefly Lantern
+tile.lantern.firefly.blue.desc=A glass jar containing a swarm of blue fireflies.
+tile.lantern.firefly.red.name=Red Firefly Lantern
+tile.lantern.firefly.red.desc=A glass jar containing a swarm of red fireflies.
+
+tile.flag.name=Flag
+tile.flag.desc=A flag to mark your territory.
+
+tile.paper_wall.name=Paper Wall
+tile.paper_wall.desc=A wall made of delicate paper. Be careful!
+
+tile.trapdoor.planks.oak.painted.name=Painted Trapdoor
+tile.trapdoor.planks.oak.painted.desc=How did you get this?!
+tile.trapdoor.planks.oak.painted.black.name=Black Wooden Trapdoor
+tile.trapdoor.planks.oak.painted.black.desc=Like a door, but flat and painted black.
+tile.trapdoor.planks.oak.painted.red.name=Red Wooden Trapdoor
+tile.trapdoor.planks.oak.painted.red.desc=Like a door, but flat and painted red.
+tile.trapdoor.planks.oak.painted.green.name=Green Wooden Trapdoor
+tile.trapdoor.planks.oak.painted.green.desc=Like a door, but flat and painted green.
+tile.trapdoor.planks.oak.painted.brown.name=Brown Wooden Trapdoor
+tile.trapdoor.planks.oak.painted.brown.desc=Like a door, but flat and painted brown.
+tile.trapdoor.planks.oak.painted.blue.name=Blue Wooden Trapdoor
+tile.trapdoor.planks.oak.painted.blue.desc=Like a door, but flat and painted blue.
+tile.trapdoor.planks.oak.painted.purple.name=Purple Wooden Trapdoor
+tile.trapdoor.planks.oak.painted.purple.desc=Like a door, but flat and painted purple.
+tile.trapdoor.planks.oak.painted.cyan.name=Cyan Wooden Trapdoor
+tile.trapdoor.planks.oak.painted.cyan.desc=Like a door, but flat and painted cyan.
+tile.trapdoor.planks.oak.painted.silver.name=Light Gray Wooden Trapdoor
+tile.trapdoor.planks.oak.painted.silver.desc=Like a door, but flat and painted light gray.
+tile.trapdoor.planks.oak.painted.gray.name=Gray Wooden Trapdoor
+tile.trapdoor.planks.oak.painted.gray.desc=Like a door, but flat and painted gray.
+tile.trapdoor.planks.oak.painted.pink.name=Pink Wooden Trapdoor
+tile.trapdoor.planks.oak.painted.pink.desc=Like a door, but flat and painted pink.
+tile.trapdoor.planks.oak.painted.lime.name=Lime Wooden Trapdoor
+tile.trapdoor.planks.oak.painted.lime.desc=Like a door, but flat and painted lime.
+tile.trapdoor.planks.oak.painted.yellow.name=Yellow Wooden Trapdoor
+tile.trapdoor.planks.oak.painted.yellow.desc=Like a door, but flat and painted yellow.
+tile.trapdoor.planks.oak.painted.lightblue.name=Light Blue Wooden Trapdoor
+tile.trapdoor.planks.oak.painted.lightblue.desc=Like a door, but flat and painted light blue.
+tile.trapdoor.planks.oak.painted.magenta.name=Magenta Wooden Trapdoor
+tile.trapdoor.planks.oak.painted.magenta.desc=Like a door, but flat and painted magenta.
+tile.trapdoor.planks.oak.painted.orange.name=Orange Wooden Trapdoor
+tile.trapdoor.planks.oak.painted.orange.desc=Like a door, but flat and painted orange.
+tile.trapdoor.planks.oak.painted.white.name=White Wooden Trapdoor
+tile.trapdoor.planks.oak.painted.white.desc=Like a door, but flat and painted white.
+
+tile.door.planks.oak.painted.bottom.name=Bottom Painted Wooden Door
+tile.door.planks.oak.painted.bottom.desc=How did you get this?!
+tile.door.planks.oak.painted.bottom.black.name=Black Bottom Wooden Door
+tile.door.planks.oak.painted.bottom.black.desc=How did you get this?!
+tile.door.planks.oak.painted.bottom.red.name=Red Bottom Wooden Door
+tile.door.planks.oak.painted.bottom.red.desc=How did you get this?!
+tile.door.planks.oak.painted.bottom.green.name=Green Bottom Wooden Door
+tile.door.planks.oak.painted.bottom.green.desc=How did you get this?!
+tile.door.planks.oak.painted.bottom.brown.name=Brown Bottom Wooden Door
+tile.door.planks.oak.painted.bottom.brown.desc=How did you get this?!
+tile.door.planks.oak.painted.bottom.blue.name=Blue Bottom Wooden Door
+tile.door.planks.oak.painted.bottom.blue.desc=How did you get this?!
+tile.door.planks.oak.painted.bottom.purple.name=Purple Bottom Wooden Door
+tile.door.planks.oak.painted.bottom.purple.desc=How did you get this?!
+tile.door.planks.oak.painted.bottom.cyan.name=Cyan Bottom Wooden Door
+tile.door.planks.oak.painted.bottom.cyan.desc=How did you get this?!
+tile.door.planks.oak.painted.bottom.silver.name=Light Gray Bottom Wooden Door
+tile.door.planks.oak.painted.bottom.silver.desc=How did you get this?!
+tile.door.planks.oak.painted.bottom.gray.name=Gray Bottom Wooden Door
+tile.door.planks.oak.painted.bottom.gray.desc=How did you get this?!
+tile.door.planks.oak.painted.bottom.pink.name=Pink Bottom Wooden Door
+tile.door.planks.oak.painted.bottom.pink.desc=How did you get this?!
+tile.door.planks.oak.painted.bottom.lime.name=Lime Bottom Wooden Door
+tile.door.planks.oak.painted.bottom.lime.desc=How did you get this?!
+tile.door.planks.oak.painted.bottom.yellow.name=Yellow Bottom Wooden Door
+tile.door.planks.oak.painted.bottom.yellow.desc=How did you get this?!
+tile.door.planks.oak.painted.bottom.lightblue.name=Light Blue Bottom Wooden Door
+tile.door.planks.oak.painted.bottom.lightblue.desc=How did you get this?!
+tile.door.planks.oak.painted.bottom.magenta.name=Magenta Bottom Wooden Door
+tile.door.planks.oak.painted.bottom.magenta.desc=How did you get this?!
+tile.door.planks.oak.painted.bottom.orange.name=Orange Bottom Wooden Door
+tile.door.planks.oak.painted.bottom.orange.desc=How did you get this?!
+tile.door.planks.oak.painted.bottom.white.name=White Bottom Wooden Door
+tile.door.planks.oak.painted.bottom.white.desc=How did you get this?!
+
+tile.door.planks.oak.painted.top.name=Top Painted Wooden Door
+tile.door.planks.oak.painted.top.desc=How did you get this?!
+tile.door.planks.oak.painted.top.black.name=Black Top Wooden Door
+tile.door.planks.oak.painted.top.black.desc=How did you get this?!
+tile.door.planks.oak.painted.top.red.name=Red Top Wooden Door
+tile.door.planks.oak.painted.top.red.desc=How did you get this?!
+tile.door.planks.oak.painted.top.green.name=Green Top Wooden Door
+tile.door.planks.oak.painted.top.green.desc=How did you get this?!
+tile.door.planks.oak.painted.top.brown.name=Brown Top Wooden Door
+tile.door.planks.oak.painted.top.brown.desc=How did you get this?!
+tile.door.planks.oak.painted.top.blue.name=Blue Top Wooden Door
+tile.door.planks.oak.painted.top.blue.desc=How did you get this?!
+tile.door.planks.oak.painted.top.purple.name=Purple Top Wooden Door
+tile.door.planks.oak.painted.top.purple.desc=How did you get this?!
+tile.door.planks.oak.painted.top.cyan.name=Cyan Top Wooden Door
+tile.door.planks.oak.painted.top.cyan.desc=How did you get this?!
+tile.door.planks.oak.painted.top.silver.name=Light Gray Top Wooden Door
+tile.door.planks.oak.painted.top.silver.desc=How did you get this?!
+tile.door.planks.oak.painted.top.gray.name=Gray Top Wooden Door
+tile.door.planks.oak.painted.top.gray.desc=How did you get this?!
+tile.door.planks.oak.painted.top.pink.name=Pink Top Wooden Door
+tile.door.planks.oak.painted.top.pink.desc=How did you get this?!
+tile.door.planks.oak.painted.top.lime.name=Lime Top Wooden Door
+tile.door.planks.oak.painted.top.lime.desc=How did you get this?!
+tile.door.planks.oak.painted.top.yellow.name=Yellow Top Wooden Door
+tile.door.planks.oak.painted.top.yellow.desc=How did you get this?!
+tile.door.planks.oak.painted.top.lightblue.name=Light Blue Top Wooden Door
+tile.door.planks.oak.painted.top.lightblue.desc=How did you get this?!
+tile.door.planks.oak.painted.top.magenta.name=Magenta Top Wooden Door
+tile.door.planks.oak.painted.top.magenta.desc=How did you get this?!
+tile.door.planks.oak.painted.top.orange.name=Orange Top Wooden Door
+tile.door.planks.oak.painted.top.orange.desc=How did you get this?!
+tile.door.planks.oak.painted.top.white.name=White Top Wooden Door
+tile.door.planks.oak.painted.top.white.desc=How did you get this?!
+
+tile.door.glass.top.name=Top Glass Door
+tile.door.glass.top.desc=You shouldn't be seeing this.
+tile.door.glass.bottom.name=Bottom Glass Door
+tile.door.glass.bottom.desc=And that's not just because it's glass.
+
+tile.door.steel.top.name=Top Steel Door
+tile.door.steel.top.desc=Don't take this without asking.
+tile.door.steel.bottom.name=Bottom Steel Door
+tile.door.steel.bottom.desc=Steeler, no steeling!
+
+tile.trapdoor.steel.name=Steel Trapdoor
+tile.trapdoor.steel.desc=Like a door, but flat and made of durable steel.
+
+tile.glass.steel.name=Reinforced Glass
+tile.glass.steel.desc=Like glass, but without the fragility.
+
+tile.rope.name=Rope
+tile.rope.desc=Rope, rope, rope your boat.
+
+tile.crystal.name=Nether Crystal
+tile.crystal.desc=A bright, iridescent stone that grows under intense heat.
+
+tile.crystal.stalk.name=Nether Crystal Stalk
+tile.crystal.stalk.desc=A stalk of crystal, branching out in many directions.
+
+tile.netherrack.crystalline.name=Crystalline Netherrack
+tile.netherrack.crystalline.desc=Cobbled Netherrack, embedded with crystals.
+
+tile.jar.butterfly.blue.name=Blue Butterfly Jar
+tile.jar.butterfly.blue.desc=A glass jar containing a blue butterfly.
+tile.jar.butterfly.orange.name=Orange Butterfly Jar
+tile.jar.butterfly.orange.desc=A glass jar containing a orange butterfly.
+tile.jar.butterfly.pink.name=Pink Butterfly Jar
+tile.jar.butterfly.pink.desc=A glass jar containing a pink butterfly.
+tile.jar.butterfly.silver.name=Silver Butterfly Jar
+tile.jar.butterfly.silver.desc=A glass jar containing a silver butterfly.
+
+tile.statue.stone.lower.name=Stone Statue (Lower)
+tile.statue.stone.lower.desc=
+tile.statue.stone.upper.name=Stone Statue (Upper)
+tile.statue.stone.upper.desc=
\ No newline at end of file
diff --git a/game/core/src/main/resources/log4j2.xml b/game/core/src/main/resources/log4j2.xml
index e4cf2345c..a32f30bc0 100644
--- a/game/core/src/main/resources/log4j2.xml
+++ b/game/core/src/main/resources/log4j2.xml
@@ -5,9 +5,14 @@
             <PatternLayout pattern="[%d{HH:mm:ss}] [%t/%level]: %msg{nolookups}%n" />
         </Console>
         <Queue name="ServerGuiConsole">
+            <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
             <PatternLayout pattern="[%d{HH:mm:ss} %level]: %msg{nolookups}%n" />
         </Queue>
+        <Queue name="IngameConsole">
+            <PatternLayout pattern="%level: %msg{nolookups}" />
+        </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" />
             <Policies>
                 <TimeBasedTriggeringPolicy />
@@ -23,6 +28,7 @@
             <AppenderRef ref="SysOut"/>
             <AppenderRef ref="File"/>
             <AppenderRef ref="ServerGuiConsole"/>
+            <AppenderRef ref="IngameConsole"/>
         </Root>
     </Loggers>
 </Configuration>
diff --git a/gradle.properties b/gradle.properties
index b7ac11372..268d0615b 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,5 +1,9 @@
+org.gradle.parallel = true
+org.gradle.daemon = true
+org.gradle.caching = true
+
 bta.group = net.minecraft
-bta.version = 7.3
+bta.version = 7.4
 bta.texturePackFormat = 3
 bta.buildChannel = BuildChannel.DEVELOPMENT
 
@@ -11,4 +15,4 @@ bta.buildChannel = BuildChannel.DEVELOPMENT
 # 4th nibble: Prerelease version or patch version
 # For example, 7.1_01 would be encoded as 0x7101
 # 7.1 prerelease 1 would be encoded as 0x7111
-bta.mpProtocolVersion = 0x7320
\ No newline at end of file
+bta.mpProtocolVersion = 0x7420
\ No newline at end of file
diff --git a/util/logging/src/main/java/com/mojang/logging/CategorizedLogger.java b/util/logging/src/main/java/com/mojang/logging/CategorizedLogger.java
new file mode 100644
index 000000000..c8d2fa0fb
--- /dev/null
+++ b/util/logging/src/main/java/com/mojang/logging/CategorizedLogger.java
@@ -0,0 +1,463 @@
+package com.mojang.logging;
+
+import org.jetbrains.annotations.NotNull;
+import org.slf4j.Logger;
+import org.slf4j.Marker;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+public final class CategorizedLogger implements Logger {
+    private static final @NotNull Map<@NotNull String, @NotNull Boolean> CATEGORIES_ENABLED = new HashMap<>();
+    public static void setCategoryEnabled(final @NotNull String category, final boolean enabled) {
+        CATEGORIES_ENABLED.put(category, enabled);
+    }
+
+    public static boolean isCategoryEnabled(final @NotNull String category) {
+        if (!CATEGORIES_ENABLED.containsKey(category)) {
+            CATEGORIES_ENABLED.put(category, false);
+            return false;
+        }
+        return CATEGORIES_ENABLED.getOrDefault(category, false);
+    }
+
+    public static Collection<String> getKnownCategories() {
+        return CATEGORIES_ENABLED.keySet();
+    }
+
+    private final @NotNull Logger delegate;
+
+    public CategorizedLogger(final @NotNull Logger logger) {
+        this.delegate = logger;
+    }
+
+    private @NotNull String prependCategory(@NotNull String category, @NotNull String message) {
+        return category + ": " + message;
+    }
+
+    @Override
+    public String getName() {
+        return this.delegate.getName();
+    }
+
+    @Override
+    public boolean isTraceEnabled() {
+        return this.delegate.isTraceEnabled();
+    }
+
+    @Override
+    public void trace(String msg) {
+        this.delegate.trace(msg);
+    }
+
+    @Override
+    public void trace(String format, Object arg) {
+        this.delegate.trace(format, arg);
+    }
+
+    @Override
+    public void trace(String format, Object arg1, Object arg2) {
+        this.delegate.trace(format, arg1, arg2);
+    }
+
+    @Override
+    public void trace(String format, Object... arguments) {
+        this.delegate.trace(format, arguments);
+    }
+
+    @Override
+    public void trace(String msg, Throwable t) {
+        this.delegate.trace(msg, t);
+    }
+
+    @Override
+    public boolean isTraceEnabled(Marker marker) {
+        return this.delegate.isTraceEnabled(marker);
+    }
+
+    @Override
+    public void trace(Marker marker, String msg) {
+        this.delegate.trace(marker, msg);
+    }
+
+    @Override
+    public void trace(Marker marker, String format, Object arg) {
+        this.delegate.trace(marker, format, arg);
+    }
+
+    @Override
+    public void trace(Marker marker, String format, Object arg1, Object arg2) {
+        this.delegate.trace(marker, format, arg1, arg2);
+    }
+
+    @Override
+    public void trace(Marker marker, String format, Object... argArray) {
+        this.delegate.trace(marker, format, argArray);
+    }
+
+    @Override
+    public void trace(Marker marker, String msg, Throwable t) {
+        this.delegate.trace(marker, msg, t);
+    }
+
+    public void trace(String category, String msg) {
+        if (!isCategoryEnabled(category)) return;
+        this.delegate.trace(prependCategory(category, msg));
+    }
+
+    public void trace(String category, String format, Object arg) {
+        if (!isCategoryEnabled(category)) return;
+        this.delegate.trace(prependCategory(category, format), arg);
+    }
+
+    public void trace(String category, String format, Object arg1, Object arg2) {
+        if (!isCategoryEnabled(category)) return;
+        this.delegate.trace(prependCategory(category, format), arg1, arg2);
+    }
+
+    public void trace(String category, String format, Object... arguments) {
+        if (!isCategoryEnabled(category)) return;
+        this.delegate.trace(prependCategory(category, format), arguments);
+    }
+
+    public void trace(String category, String msg, Throwable t) {
+        if (!isCategoryEnabled(category)) return;
+        this.delegate.trace(prependCategory(category, msg), t);
+    }
+
+    @Override
+    public boolean isDebugEnabled() {
+        return this.delegate.isDebugEnabled();
+    }
+
+    @Override
+    public void debug(String msg) {
+        this.delegate.debug(msg);
+    }
+
+    @Override
+    public void debug(String format, Object arg) {
+        this.delegate.debug(format, arg);
+    }
+
+    @Override
+    public void debug(String format, Object arg1, Object arg2) {
+        this.delegate.debug(format, arg1, arg2);
+    }
+
+    @Override
+    public void debug(String format, Object... arguments) {
+        this.delegate.debug(format, arguments);
+    }
+
+    @Override
+    public void debug(String msg, Throwable t) {
+        this.delegate.debug(msg, t);
+    }
+
+    @Override
+    public boolean isDebugEnabled(Marker marker) {
+        return this.delegate.isDebugEnabled(marker);
+    }
+
+    @Override
+    public void debug(Marker marker, String msg) {
+        this.delegate.debug(marker, msg);
+    }
+
+    @Override
+    public void debug(Marker marker, String format, Object arg) {
+        this.delegate.debug(marker, format, arg);
+    }
+
+    @Override
+    public void debug(Marker marker, String format, Object arg1, Object arg2) {
+        this.delegate.debug(marker, format, arg1, arg2);
+    }
+
+    @Override
+    public void debug(Marker marker, String format, Object... arguments) {
+        this.delegate.debug(marker, format, arguments);
+    }
+
+    @Override
+    public void debug(Marker marker, String msg, Throwable t) {
+        this.delegate.debug(marker, msg, t);
+    }
+
+    public void debug(String category, String msg) {
+        if (!isCategoryEnabled(category)) return;
+        this.delegate.debug(prependCategory(category, msg));
+    }
+
+    public void debug(String category, String format, Object arg) {
+        if (!isCategoryEnabled(category)) return;
+        this.delegate.debug(prependCategory(category, format), arg);
+    }
+
+    public void debug(String category, String format, Object arg1, Object arg2) {
+        if (!isCategoryEnabled(category)) return;
+        this.delegate.debug(prependCategory(category, format), arg1, arg2);
+    }
+
+    public void debug(String category, String format, Object... arguments) {
+        if (!isCategoryEnabled(category)) return;
+        this.delegate.debug(prependCategory(category, format), arguments);
+    }
+
+    @Override
+    public boolean isInfoEnabled() {
+        return this.delegate.isInfoEnabled();
+    }
+
+    @Override
+    public void info(String msg) {
+        this.delegate.info(msg);
+    }
+
+    @Override
+    public void info(String format, Object arg) {
+        this.delegate.info(format, arg);
+    }
+
+    @Override
+    public void info(String format, Object arg1, Object arg2) {
+        this.delegate.info(format, arg1, arg2);
+    }
+
+    @Override
+    public void info(String format, Object... arguments) {
+        this.delegate.info(format, arguments);
+    }
+
+    @Override
+    public void info(String msg, Throwable t) {
+        this.delegate.info(msg, t);
+    }
+
+    @Override
+    public boolean isInfoEnabled(Marker marker) {
+        return this.delegate.isInfoEnabled(marker);
+    }
+
+    @Override
+    public void info(Marker marker, String msg) {
+        this.delegate.info(marker, msg);
+    }
+
+    @Override
+    public void info(Marker marker, String format, Object arg) {
+        this.delegate.info(marker, format, arg);
+    }
+
+    @Override
+    public void info(Marker marker, String format, Object arg1, Object arg2) {
+        this.delegate.info(marker, format, arg1, arg2);
+    }
+
+    @Override
+    public void info(Marker marker, String format, Object... arguments) {
+        this.delegate.info(marker, format, arguments);
+    }
+
+    @Override
+    public void info(Marker marker, String msg, Throwable t) {
+        this.delegate.info(marker, msg, t);
+    }
+
+    public void info(String category, String msg) {
+        if (!isCategoryEnabled(category)) return;
+        this.delegate.info(prependCategory(category, msg));
+    }
+
+    public void info(String category, String format, Object arg) {
+        if (!isCategoryEnabled(category)) return;
+        this.delegate.info(prependCategory(category, format), arg);
+    }
+
+    public void info(String category, String format, Object arg1, Object arg2) {
+        if (!isCategoryEnabled(category)) return;
+        this.delegate.info(prependCategory(category, format), arg1, arg2);
+    }
+
+    public void info(String category, String format, Object... arguments) {
+        if (!isCategoryEnabled(category)) return;
+        this.delegate.info(prependCategory(category, format), arguments);
+    }
+
+    public void info(String category, String msg, Throwable t) {
+        if (!isCategoryEnabled(category)) return;
+        this.delegate.info(prependCategory(category, msg), t);
+    }
+
+    @Override
+    public boolean isWarnEnabled() {
+        return this.delegate.isWarnEnabled();
+    }
+
+    @Override
+    public void warn(String msg) {
+        this.delegate.warn(msg);
+    }
+
+    @Override
+    public void warn(String format, Object arg) {
+        this.delegate.warn(format, arg);
+    }
+
+    @Override
+    public void warn(String format, Object... arguments) {
+        this.delegate.warn(format, arguments);
+    }
+
+    @Override
+    public void warn(String format, Object arg1, Object arg2) {
+        this.delegate.warn(format, arg1, arg2);
+    }
+
+    @Override
+    public void warn(String msg, Throwable t) {
+        this.delegate.warn(msg, t);
+    }
+
+    @Override
+    public boolean isWarnEnabled(Marker marker) {
+        return this.delegate.isWarnEnabled(marker);
+    }
+
+    @Override
+    public void warn(Marker marker, String msg) {
+        this.delegate.warn(marker, msg);
+    }
+
+    @Override
+    public void warn(Marker marker, String format, Object arg) {
+        this.delegate.warn(marker, format, arg);
+    }
+
+    @Override
+    public void warn(Marker marker, String format, Object arg1, Object arg2) {
+        this.delegate.warn(marker, format, arg1, arg2);
+    }
+
+    @Override
+    public void warn(Marker marker, String format, Object... arguments) {
+        this.delegate.warn(marker, format, arguments);
+    }
+
+    @Override
+    public void warn(Marker marker, String msg, Throwable t) {
+        this.delegate.warn(marker, msg, t);
+    }
+
+    public void warn(String category, String msg) {
+        if (!isCategoryEnabled(category)) return;
+        this.delegate.warn(prependCategory(category, msg));
+    }
+
+    public void warn(String category, String format, Object arg) {
+        if (!isCategoryEnabled(category)) return;
+        this.delegate.warn(prependCategory(category, format), arg);
+    }
+
+    public void warn(String category, String format, Object... arguments) {
+        if (!isCategoryEnabled(category)) return;
+        this.delegate.warn(prependCategory(category, format), arguments);
+    }
+
+    public void warn(String category, String format, Object arg1, Object arg2) {
+        if (!isCategoryEnabled(category)) return;
+        this.delegate.warn(prependCategory(category, format), arg1, arg2);
+    }
+
+    public void warn(String category, String msg, Throwable t) {
+        if (!isCategoryEnabled(category)) return;
+        this.delegate.warn(prependCategory(category, msg), t);
+    }
+
+    @Override
+    public boolean isErrorEnabled() {
+        return this.delegate.isErrorEnabled();
+    }
+
+    @Override
+    public void error(String msg) {
+        this.delegate.error(msg);
+    }
+
+    @Override
+    public void error(String format, Object arg) {
+        this.delegate.error(format, arg);
+    }
+
+    @Override
+    public void error(String format, Object arg1, Object arg2) {
+        this.delegate.error(format, arg1, arg2);
+    }
+
+    @Override
+    public void error(String format, Object... arguments) {
+        this.delegate.error(format, arguments);
+    }
+
+    @Override
+    public void error(String msg, Throwable t) {
+        this.delegate.error(msg, t);
+    }
+
+    @Override
+    public boolean isErrorEnabled(Marker marker) {
+        return this.delegate.isErrorEnabled(marker);
+    }
+
+    @Override
+    public void error(Marker marker, String msg) {
+        this.delegate.error(marker, msg);
+    }
+
+    @Override
+    public void error(Marker marker, String format, Object arg) {
+        this.delegate.error(marker, format, arg);
+    }
+
+    @Override
+    public void error(Marker marker, String format, Object arg1, Object arg2) {
+        this.delegate.error(marker, format, arg1, arg2);
+    }
+
+    @Override
+    public void error(Marker marker, String format, Object... arguments) {
+        this.delegate.error(marker, format, arguments);
+    }
+
+    @Override
+    public void error(Marker marker, String msg, Throwable t) {
+        this.delegate.error(marker, msg, t);
+    }
+
+    public void error(String category, String msg) {
+        if (!isCategoryEnabled(category)) return;
+        this.delegate.error(prependCategory(category, msg));
+    }
+
+    public void error(String category, String format, Object arg) {
+        if (!isCategoryEnabled(category)) return;
+        this.delegate.error(prependCategory(category, format), arg);
+    }
+
+    public void error(String category, String format, Object arg1, Object arg2) {
+        if (!isCategoryEnabled(category)) return;
+        this.delegate.error(prependCategory(category, format), arg1, arg2);
+    }
+
+    public void error(String category, String format, Object... arguments) {
+        if (!isCategoryEnabled(category)) return;
+        this.delegate.error(prependCategory(category, format), arguments);
+    }
+
+    public void error(String category, String msg, Throwable t) {
+        if (!isCategoryEnabled(category)) return;
+        this.delegate.error(prependCategory(category, msg), t);
+    }
+}
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 521a5ab35..e5e7a9ce6 100644
--- a/util/logging/src/main/java/com/mojang/logging/LogUtils.java
+++ b/util/logging/src/main/java/com/mojang/logging/LogUtils.java
@@ -67,8 +67,8 @@ public class LogUtils
         return new ToString();
     }
     
-    public static Logger getLogger() {
-        return LoggerFactory.getLogger(getCallerClass());
+    public static CategorizedLogger getLogger() {
+        return new CategorizedLogger(LoggerFactory.getLogger(getCallerClass()));
     }
 
     private static Class<?> getCallerClass() { // Hacky way to replace the STACK_WALKER since STACK_WALKER uses java 9 language features
