From 03b9a8dbcc1ebd5f8c39e4733a741a4092ab0a1d Mon Sep 17 00:00:00 2001
From: BuildTools <james.jenour@protonmail.com>
Date: Sat, 27 Mar 2021 07:40:53 +0800
Subject: PRE26

---
 .../notenoughupdates/ItemPriceInformation.java     |    7 +-
 .../notenoughupdates/NEUEventListener.java         |  181 +-
 .../moulberry/notenoughupdates/NEUManager.java     |  139 +-
 .../moulberry/notenoughupdates/NEUOverlay.java     |  124 +-
 .../notenoughupdates/NotEnoughUpdates.java         |   57 +-
 .../notenoughupdates/core/GuiElementTextField.java |   17 +-
 .../notenoughupdates/cosmetics/CapeManager.java    |    7 +-
 .../notenoughupdates/cosmetics/CapeNode.java       |   60 +-
 .../notenoughupdates/cosmetics/GuiCosmetics.java   |    2 +-
 .../notenoughupdates/cosmetics/NEUCape.java        |  817 ++++--
 .../notenoughupdates/infopanes/HTMLInfoPane.java   |   83 +-
 .../miscfeatures/CustomItemEffects.java            |    5 +
 .../miscfeatures/EnchantingSolvers.java            |   13 +-
 .../notenoughupdates/miscfeatures/MiningStuff.java |   35 -
 .../miscfeatures/NullzeeSphere.java                |  234 ++
 .../miscfeatures/PetInfoOverlay.java               |  257 +-
 .../notenoughupdates/miscgui/CalendarOverlay.java  |   97 +-
 .../miscgui/GuiInvButtonEditor.java                |  677 +++++
 .../mixins/GuiContainerAccessor.java               |   22 +
 .../notenoughupdates/mixins/MixinContainer.java    |    2 +-
 .../notenoughupdates/mixins/MixinGuiContainer.java |   16 +
 .../notenoughupdates/mixins/MixinGuiInventory.java |   22 +
 .../notenoughupdates/mixins/MixinItemStack.java    |    4 +
 .../mixins/MixinLayerCreeperCharge.java            |   26 -
 .../mixins/MixinNetHandlerPlayClient.java          |    6 +-
 .../mixins/MixinPlayerControllerMP.java            |    4 +-
 .../notenoughupdates/mixins/MixinRenderItem.java   |   82 +
 .../notenoughupdates/options/NEUConfig.java        |  181 +-
 .../notenoughupdates/overlays/TimersOverlay.java   |    7 +-
 .../profileviewer/GuiProfileViewer.java            |    3 +-
 .../profileviewer/ProfileViewer.java               |    2 +-
 .../assets/notenoughupdates/capes/alexxoffi.png    |  Bin 0 -> 588408 bytes
 .../notenoughupdates/capes/alexxoffi_preview.png   |  Bin 0 -> 135662 bytes
 .../assets/notenoughupdates/capes/dsm.png          |  Bin 62154 -> 56800 bytes
 .../assets/notenoughupdates/capes/dsm_preview.png  |  Bin 0 -> 42820 bytes
 .../assets/notenoughupdates/capes/furf.png         |  Bin 15699 -> 8145 bytes
 .../assets/notenoughupdates/capes/furf_preview.png |  Bin 11892 -> 3360 bytes
 .../assets/notenoughupdates/capes/jakethybro.png   |  Bin 0 -> 151284 bytes
 .../notenoughupdates/capes/jakethybro_preview.png  |  Bin 0 -> 111454 bytes
 .../assets/notenoughupdates/capes/parallax.png     |  Bin 0 -> 357618 bytes
 .../assets/notenoughupdates/capes/zera.png         |  Bin 0 -> 325454 bytes
 .../assets/notenoughupdates/capes/zera_preview.png |  Bin 0 -> 156882 bytes
 .../assets/notenoughupdates/invbuttons/editor.png  |  Bin 0 -> 1806 bytes
 .../invbuttons/expanded_inventory.png              |  Bin 0 -> 2494 bytes
 .../notenoughupdates/invbuttons/extraicons.json    |   18 +
 .../invbuttons/extraicons/accessory.png            |  Bin 0 -> 1778 bytes
 .../invbuttons/extraicons/accessory_gold.png       |  Bin 0 -> 1859 bytes
 .../invbuttons/extraicons/armor.png                |  Bin 0 -> 1782 bytes
 .../invbuttons/extraicons/armor_gold.png           |  Bin 0 -> 1917 bytes
 .../invbuttons/extraicons/baubles.png              |  Bin 0 -> 776 bytes
 .../invbuttons/extraicons/baubles_gold.png         |  Bin 0 -> 810 bytes
 .../invbuttons/extraicons/cross.png                |  Bin 0 -> 1747 bytes
 .../invbuttons/extraicons/green_check.png          |  Bin 0 -> 1637 bytes
 .../notenoughupdates/invbuttons/extraicons/pet.png |  Bin 0 -> 1826 bytes
 .../invbuttons/extraicons/pet_gold.png             |  Bin 0 -> 1919 bytes
 .../invbuttons/extraicons/question.png             |  Bin 0 -> 1517 bytes
 .../invbuttons/extraicons/recipe.png               |  Bin 0 -> 2176 bytes
 .../invbuttons/extraicons/search.png               |  Bin 0 -> 2018 bytes
 .../invbuttons/extraicons/settings.png             |  Bin 0 -> 3962 bytes
 .../invbuttons/extraicons/skyblock_menu.png        |  Bin 0 -> 2108 bytes
 .../invbuttons/extraicons/white_check.png          |  Bin 0 -> 1684 bytes
 .../notenoughupdates/invbuttons/presets.json       | 2729 ++++++++++++++++++++
 .../assets/notenoughupdates/search_bar.png         |  Bin 0 -> 2964 bytes
 .../assets/notenoughupdates/search_bar_gold.png    |  Bin 0 -> 4159 bytes
 .../shaders/capes/planets/planets.frag             |  185 ++
 .../shaders/capes/planets/planets.vert             |   14 +
 .../shaders/capes/tunnel/tunnel.frag               |   50 +
 .../shaders/capes/tunnel/tunnel.vert               |   12 +
 src/main/resources/mixins.notenoughupdates.json    |    5 +-
 69 files changed, 5568 insertions(+), 634 deletions(-)
 create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/NullzeeSphere.java
 create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/miscgui/GuiInvButtonEditor.java
 create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/mixins/GuiContainerAccessor.java
 create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinGuiInventory.java
 delete mode 100644 src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinLayerCreeperCharge.java
 create mode 100644 src/main/resources/assets/notenoughupdates/capes/alexxoffi.png
 create mode 100644 src/main/resources/assets/notenoughupdates/capes/alexxoffi_preview.png
 create mode 100644 src/main/resources/assets/notenoughupdates/capes/dsm_preview.png
 create mode 100644 src/main/resources/assets/notenoughupdates/capes/jakethybro.png
 create mode 100644 src/main/resources/assets/notenoughupdates/capes/jakethybro_preview.png
 create mode 100644 src/main/resources/assets/notenoughupdates/capes/parallax.png
 create mode 100644 src/main/resources/assets/notenoughupdates/capes/zera.png
 create mode 100644 src/main/resources/assets/notenoughupdates/capes/zera_preview.png
 create mode 100644 src/main/resources/assets/notenoughupdates/invbuttons/editor.png
 create mode 100644 src/main/resources/assets/notenoughupdates/invbuttons/expanded_inventory.png
 create mode 100644 src/main/resources/assets/notenoughupdates/invbuttons/extraicons.json
 create mode 100644 src/main/resources/assets/notenoughupdates/invbuttons/extraicons/accessory.png
 create mode 100644 src/main/resources/assets/notenoughupdates/invbuttons/extraicons/accessory_gold.png
 create mode 100644 src/main/resources/assets/notenoughupdates/invbuttons/extraicons/armor.png
 create mode 100644 src/main/resources/assets/notenoughupdates/invbuttons/extraicons/armor_gold.png
 create mode 100644 src/main/resources/assets/notenoughupdates/invbuttons/extraicons/baubles.png
 create mode 100644 src/main/resources/assets/notenoughupdates/invbuttons/extraicons/baubles_gold.png
 create mode 100644 src/main/resources/assets/notenoughupdates/invbuttons/extraicons/cross.png
 create mode 100644 src/main/resources/assets/notenoughupdates/invbuttons/extraicons/green_check.png
 create mode 100644 src/main/resources/assets/notenoughupdates/invbuttons/extraicons/pet.png
 create mode 100644 src/main/resources/assets/notenoughupdates/invbuttons/extraicons/pet_gold.png
 create mode 100644 src/main/resources/assets/notenoughupdates/invbuttons/extraicons/question.png
 create mode 100644 src/main/resources/assets/notenoughupdates/invbuttons/extraicons/recipe.png
 create mode 100644 src/main/resources/assets/notenoughupdates/invbuttons/extraicons/search.png
 create mode 100644 src/main/resources/assets/notenoughupdates/invbuttons/extraicons/settings.png
 create mode 100644 src/main/resources/assets/notenoughupdates/invbuttons/extraicons/skyblock_menu.png
 create mode 100644 src/main/resources/assets/notenoughupdates/invbuttons/extraicons/white_check.png
 create mode 100644 src/main/resources/assets/notenoughupdates/invbuttons/presets.json
 create mode 100644 src/main/resources/assets/notenoughupdates/search_bar.png
 create mode 100644 src/main/resources/assets/notenoughupdates/search_bar_gold.png
 create mode 100644 src/main/resources/assets/notenoughupdates/shaders/capes/planets/planets.frag
 create mode 100644 src/main/resources/assets/notenoughupdates/shaders/capes/planets/planets.vert
 create mode 100644 src/main/resources/assets/notenoughupdates/shaders/capes/tunnel/tunnel.frag
 create mode 100644 src/main/resources/assets/notenoughupdates/shaders/capes/tunnel/tunnel.vert

(limited to 'src')

diff --git a/src/main/java/io/github/moulberry/notenoughupdates/ItemPriceInformation.java b/src/main/java/io/github/moulberry/notenoughupdates/ItemPriceInformation.java
index 58efa371..ff618f10 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/ItemPriceInformation.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/ItemPriceInformation.java
@@ -3,6 +3,7 @@ package io.github.moulberry.notenoughupdates;
 import com.google.gson.JsonObject;
 import io.github.moulberry.notenoughupdates.auction.APIManager;
 import io.github.moulberry.notenoughupdates.util.Constants;
+import io.github.moulberry.notenoughupdates.util.Utils;
 import net.minecraft.item.ItemStack;
 import net.minecraft.nbt.NBTTagCompound;
 import net.minecraft.util.EnumChatFormatting;
@@ -15,6 +16,10 @@ import java.util.Locale;
 public class ItemPriceInformation {
 
     public static boolean addToTooltip(List<String> tooltip, String internalname, ItemStack stack) {
+        return addToTooltip(tooltip, internalname, stack, true);
+    }
+
+    public static boolean addToTooltip(List<String> tooltip, String internalname, ItemStack stack, boolean useStackSize) {
         JsonObject auctionInfo = NotEnoughUpdates.INSTANCE.manager.auctionManager.getItemAuctionInfo(internalname);
         JsonObject bazaarInfo = NotEnoughUpdates.INSTANCE.manager.auctionManager.getBazaarInfo(internalname);
         float lowestBinAvg = NotEnoughUpdates.INSTANCE.manager.auctionManager.getItemAvgBin(internalname);
@@ -35,7 +40,7 @@ public class ItemPriceInformation {
             boolean shiftPressed = Keyboard.isKeyDown(Keyboard.KEY_LSHIFT);
 
             int stackMultiplier = 1;
-            int shiftStackMultiplier = 64;
+            int shiftStackMultiplier = useStackSize && stack.stackSize > 1 ? stack.stackSize : 64;
             if(shiftPressed) {
                 stackMultiplier = shiftStackMultiplier;
             }
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/NEUEventListener.java b/src/main/java/io/github/moulberry/notenoughupdates/NEUEventListener.java
index 838f0b0a..8219c903 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/NEUEventListener.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/NEUEventListener.java
@@ -1,5 +1,6 @@
 package io.github.moulberry.notenoughupdates;
 
+import com.google.common.collect.Lists;
 import com.google.gson.JsonArray;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
@@ -16,6 +17,8 @@ import io.github.moulberry.notenoughupdates.dungeons.DungeonWin;
 import io.github.moulberry.notenoughupdates.gamemodes.SBGamemodes;
 import io.github.moulberry.notenoughupdates.miscfeatures.*;
 import io.github.moulberry.notenoughupdates.miscgui.*;
+import io.github.moulberry.notenoughupdates.mixins.GuiContainerAccessor;
+import io.github.moulberry.notenoughupdates.options.NEUConfig;
 import io.github.moulberry.notenoughupdates.overlays.*;
 import io.github.moulberry.notenoughupdates.profileviewer.GuiProfileViewer;
 import io.github.moulberry.notenoughupdates.util.*;
@@ -27,6 +30,7 @@ import net.minecraft.client.gui.ScaledResolution;
 import net.minecraft.client.gui.inventory.GuiChest;
 import net.minecraft.client.gui.inventory.GuiContainer;
 import net.minecraft.client.gui.inventory.GuiEditSign;
+import net.minecraft.client.gui.inventory.GuiInventory;
 import net.minecraft.client.network.NetworkPlayerInfo;
 import net.minecraft.client.renderer.GlStateManager;
 import net.minecraft.entity.player.EntityPlayer;
@@ -41,6 +45,7 @@ import net.minecraft.nbt.NBTTagCompound;
 import net.minecraft.nbt.NBTTagList;
 import net.minecraft.nbt.NBTUtil;
 import net.minecraft.util.*;
+import net.minecraftforge.client.ClientCommandHandler;
 import net.minecraftforge.client.event.*;
 import net.minecraftforge.event.entity.player.EntityInteractEvent;
 import net.minecraftforge.event.entity.player.ItemTooltipEvent;
@@ -204,7 +209,6 @@ public class NEUEventListener {
             CrystalOverlay.tick();
             DwarvenMinesTextures.tick();
             FairySouls.tick();
-            MiningStuff.tick();
             XPInformation.getInstance().tick();
             ProfileApiSyncer.getInstance().tick();
             DamageCommas.tick();
@@ -734,6 +738,8 @@ public class NEUEventListener {
         }
     }
 
+    public static boolean drawingGuiScreen = false;
+
     /**
      * Sets hoverInv and focusInv variables, representing whether the NEUOverlay should render behind the inventory when
      * (hoverInv == true) and whether mouse/kbd inputs shouldn't be sent to NEUOverlay (focusInv == true).
@@ -756,10 +762,10 @@ public class NEUEventListener {
 
             if(event.gui instanceof GuiContainer) {
                 try {
-                    int xSize = (int) Utils.getField(GuiContainer.class, event.gui, "xSize", "field_146999_f");
-                    int ySize = (int) Utils.getField(GuiContainer.class, event.gui, "ySize", "field_147000_g");
-                    int guiLeft = (int) Utils.getField(GuiContainer.class, event.gui, "guiLeft", "field_147003_i");
-                    int guiTop = (int) Utils.getField(GuiContainer.class, event.gui, "guiTop", "field_147009_r");
+                    int xSize = ((GuiContainerAccessor)event.gui).getXSize();
+                    int ySize = ((GuiContainerAccessor)event.gui).getYSize();
+                    int guiLeft = ((GuiContainerAccessor)event.gui).getGuiLeft();
+                    int guiTop = ((GuiContainerAccessor)event.gui).getGuiTop();
 
                     hoverInv = event.getMouseX() > guiLeft && event.getMouseX() < guiLeft + xSize &&
                             event.getMouseY() > guiTop && event.getMouseY() < guiTop + ySize;
@@ -797,10 +803,16 @@ public class NEUEventListener {
                 }
             }
         }
+
+        drawingGuiScreen = true;
     }
 
+    private boolean doInventoryButtons = false;
+
     @SubscribeEvent
     public void onGuiScreenDrawPre(GuiScreenEvent.DrawScreenEvent.Pre event) {
+        doInventoryButtons = false;
+
         if(AuctionSearchOverlay.shouldReplace()) {
             AuctionSearchOverlay.render();
             event.setCanceled(true);
@@ -839,6 +851,49 @@ public class NEUEventListener {
                 }
             }
         }
+
+        if(CalendarOverlay.isEnabled() || event.isCanceled()) return;
+        if(NotEnoughUpdates.INSTANCE.hasSkyblockScoreboard() && shouldRenderOverlay(event.gui)
+                && event.gui instanceof GuiContainer) {
+            doInventoryButtons = true;
+
+            int zOffset = 50;
+
+            GlStateManager.translate(0, 0, zOffset);
+
+            int xSize = ((GuiContainerAccessor)event.gui).getXSize();
+            int ySize = ((GuiContainerAccessor)event.gui).getYSize();
+            int guiLeft = ((GuiContainerAccessor)event.gui).getGuiLeft();
+            int guiTop = ((GuiContainerAccessor)event.gui).getGuiTop();
+
+            for(NEUConfig.InventoryButton button : NotEnoughUpdates.INSTANCE.config.hidden.inventoryButtons) {
+                if(!button.isActive()) continue;
+                if(button.playerInvOnly && !(event.gui instanceof GuiInventory)) continue;
+
+                int x = guiLeft+button.x;
+                int y = guiTop+button.y;
+                if(button.anchorRight) {
+                    x += xSize;
+                }
+                if(button.anchorBottom) {
+                    y += ySize;
+                }
+
+                GlStateManager.color(1, 1, 1, 1f);
+
+                GlStateManager.enableDepth();
+                GlStateManager.enableAlpha();
+                Minecraft.getMinecraft().getTextureManager().bindTexture(EDITOR);
+                Utils.drawTexturedRect(x, y, 18, 18,
+                        button.backgroundIndex*18/256f, (button.backgroundIndex*18+18)/256f,
+                        18/256f, 36/256f, GL11.GL_NEAREST);
+
+                if(button.icon != null && !button.icon.trim().isEmpty()) {
+                    GuiInvButtonEditor.renderIcon(button.icon, x+1, y+1);
+                }
+            }
+            GlStateManager.translate(0, 0, -zOffset);
+        }
     }
 
     private static boolean shouldRenderOverlay(Gui gui) {
@@ -854,6 +909,11 @@ public class NEUEventListener {
         return validGui;
     }
 
+    private static final ResourceLocation EDITOR = new ResourceLocation("notenoughupdates:invbuttons/editor.png");
+    private NEUConfig.InventoryButton buttonHovered = null;
+    private long buttonHoveredMillis = 0;
+    public static boolean disableCraftingText = false;
+
     /**
      * Will draw the NEUOverlay over the inventory if focusInv == false. (z-translation of 300 is so that NEUOverlay
      * will draw over Items in the inventory (which render at a z value of about 250))
@@ -861,6 +921,9 @@ public class NEUEventListener {
      */
     @SubscribeEvent
     public void onGuiScreenDrawPost(GuiScreenEvent.DrawScreenEvent.Post event) {
+        drawingGuiScreen = false;
+        disableCraftingText = false;
+
         if(!(TradeWindow.tradeWindowActive() || event.gui instanceof CustomAHGui ||
                 neu.manager.auctionManager.customAH.isRenderOverAuctionView())) {
             if(shouldRenderOverlay(event.gui) && neu.isOnSkyblock()) {
@@ -870,7 +933,6 @@ public class NEUEventListener {
                     neu.overlay.render(hoverInv && focusInv);
                     GL11.glTranslatef(0, 0, -300);
                 }
-                neu.overlay.renderOverlay();
                 GlStateManager.popMatrix();
             }
         }
@@ -881,6 +943,56 @@ public class NEUEventListener {
                 AccessoryBagOverlay.renderOverlay();
             }
         }
+
+        boolean hoveringButton = false;
+        if(!doInventoryButtons) return;
+        if(NotEnoughUpdates.INSTANCE.hasSkyblockScoreboard() && shouldRenderOverlay(event.gui) &&
+                event.gui instanceof GuiContainer) {
+            int xSize = ((GuiContainerAccessor)event.gui).getXSize();
+            int ySize = ((GuiContainerAccessor)event.gui).getYSize();
+            int guiLeft = ((GuiContainerAccessor)event.gui).getGuiLeft();
+            int guiTop = ((GuiContainerAccessor)event.gui).getGuiTop();
+
+            for(NEUConfig.InventoryButton button : NotEnoughUpdates.INSTANCE.config.hidden.inventoryButtons) {
+                if(!button.isActive()) continue;
+                if(button.playerInvOnly && !(event.gui instanceof GuiInventory)) continue;
+
+                int x = guiLeft+button.x;
+                int y = guiTop+button.y;
+                if(button.anchorRight) {
+                    x += xSize;
+                }
+                if(button.anchorBottom) {
+                    y += ySize;
+                }
+
+                if(x-guiLeft >= 85 && x-guiLeft <= 115 && y-guiTop >= 4 && y-guiTop <= 25) {
+                    disableCraftingText = true;
+                }
+
+                if(event.mouseX >= x && event.mouseX <= x+18 &&
+                        event.mouseY >= y && event.mouseY <= y+18) {
+                    hoveringButton = true;
+                    long currentTime = System.currentTimeMillis();
+
+                    if(buttonHovered != button) {
+                        buttonHoveredMillis = currentTime;
+                        buttonHovered = button;
+                    }
+
+                    if(currentTime - buttonHoveredMillis > 600) {
+                        String command = button.command.trim();
+                        if(!command.startsWith("/")) {
+                            command = "/" + command;
+                        }
+
+                        Utils.drawHoveringText(Lists.newArrayList("\u00a77"+command), event.mouseX, event.mouseY,
+                                event.gui.width, event.gui.height, -1, Minecraft.getMinecraft().fontRendererObj);
+                    }
+                }
+            }
+        }
+        if(!hoveringButton) buttonHovered = null;
     }
 
     private void renderDungeonChestOverlay(GuiScreen gui) {
@@ -888,10 +1000,10 @@ public class NEUEventListener {
 
         if(gui instanceof GuiChest && neu.config.dungeons.profitDisplayLoc != 2) {
             try {
-                int xSize = (int) Utils.getField(GuiContainer.class, gui, "xSize", "field_146999_f");
-                int ySize = (int) Utils.getField(GuiContainer.class, gui, "ySize", "field_147000_g");
-                int guiLeft = (int) Utils.getField(GuiContainer.class, gui, "guiLeft", "field_147003_i");
-                int guiTop = (int) Utils.getField(GuiContainer.class, gui, "guiTop", "field_147009_r");
+                int xSize = ((GuiContainerAccessor)gui).getXSize();
+                int ySize = ((GuiContainerAccessor)gui).getYSize();
+                int guiLeft = ((GuiContainerAccessor)gui).getGuiLeft();
+                int guiTop = ((GuiContainerAccessor)gui).getGuiTop();
 
                 GuiChest eventGui = (GuiChest) gui;
                 ContainerChest cc = (ContainerChest) eventGui.inventorySlots;
@@ -1083,7 +1195,7 @@ public class NEUEventListener {
      * Will also cancel the event if if NEUOverlay#mouseInput returns true.
      * @param event
      */
-    @SubscribeEvent
+    @SubscribeEvent(priority = EventPriority.LOW)
     public void onGuiScreenMouse(GuiScreenEvent.MouseInputEvent.Pre event) {
         if(!event.isCanceled()) {
             Utils.scrollTooltip(Mouse.getEventDWheel());
@@ -1121,6 +1233,53 @@ public class NEUEventListener {
                 }
             }
         }
+        if(event.isCanceled()) return;
+        if(!doInventoryButtons) return;
+        if(NotEnoughUpdates.INSTANCE.hasSkyblockScoreboard() && shouldRenderOverlay(event.gui) && Mouse.getEventButton() >= 0
+                && event.gui instanceof GuiContainer) {
+            int xSize = ((GuiContainerAccessor)event.gui).getXSize();
+            int ySize = ((GuiContainerAccessor)event.gui).getYSize();
+            int guiLeft = ((GuiContainerAccessor)event.gui).getGuiLeft();
+            int guiTop = ((GuiContainerAccessor)event.gui).getGuiTop();
+
+            final ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft());
+            final int scaledWidth = scaledresolution.getScaledWidth();
+            final int scaledHeight = scaledresolution.getScaledHeight();
+            int mouseX = Mouse.getX() * scaledWidth / Minecraft.getMinecraft().displayWidth;
+            int mouseY = scaledHeight - Mouse.getY() * scaledHeight / Minecraft.getMinecraft().displayHeight - 1;
+
+            for(NEUConfig.InventoryButton button : NotEnoughUpdates.INSTANCE.config.hidden.inventoryButtons) {
+                if(!button.isActive()) continue;
+                if(button.playerInvOnly && !(event.gui instanceof GuiInventory)) continue;
+
+                int x = guiLeft+button.x;
+                int y = guiTop+button.y;
+                if(button.anchorRight) {
+                    x += xSize;
+                }
+                if(button.anchorBottom) {
+                    y += ySize;
+                }
+
+                if(mouseX >= x && mouseX <= x+18 && mouseY >= y && mouseY <= y+18) {
+                    if(Minecraft.getMinecraft().thePlayer.inventory.getItemStack() == null) {
+                        int clickType = NotEnoughUpdates.INSTANCE.config.inventoryButtons.clickType;
+                        if((clickType == 0 && Mouse.getEventButtonState()) || (clickType == 1 && !Mouse.getEventButtonState())) {
+                            String command = button.command.trim();
+                            if(!command.startsWith("/")) {
+                                command = "/" + command;
+                            }
+                            if(ClientCommandHandler.instance.executeCommand(Minecraft.getMinecraft().thePlayer, command) == 0) {
+                                NotEnoughUpdates.INSTANCE.sendChatMessage(command);
+                            }
+                        }
+                    } else {
+                        event.setCanceled(true);
+                    }
+                    return;
+                }
+            }
+        }
     }
 
     ScheduledExecutorService ses = Executors.newScheduledThreadPool(1);
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/NEUManager.java b/src/main/java/io/github/moulberry/notenoughupdates/NEUManager.java
index 061d6774..4f392ae8 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/NEUManager.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/NEUManager.java
@@ -17,6 +17,7 @@ import net.minecraft.item.ItemStack;
 import net.minecraft.nbt.*;
 import net.minecraft.network.play.client.C0DPacketCloseWindow;
 import net.minecraft.util.ResourceLocation;
+import org.apache.commons.io.FileUtils;
 import org.lwjgl.input.Keyboard;
 import org.lwjgl.opengl.Display;
 
@@ -26,6 +27,8 @@ import java.net.URL;
 import java.net.URLConnection;
 import java.nio.charset.StandardCharsets;
 import java.util.*;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipInputStream;
 
@@ -57,10 +60,10 @@ public class NEUManager {
     private String currentProfileBackup = "";
     public final HypixelApi hypixelApi = new HypixelApi();
 
-    private ResourceLocation wkZip = new ResourceLocation("notenoughupdates:wkhtmltox.zip");
     private Map<String, ItemStack> itemstackCache = new HashMap<>();
 
-    //private static final String AUCTIONS_PRICE_URL = "https://moulberry.github.io/files/auc_avg_jsons/average_3day.json.gz";
+    private ExecutorService repoLoaderES = Executors.newSingleThreadExecutor();
+
     private static final String GIT_COMMITS_URL = "https://api.github.com/repos/Moulberry/NotEnoughUpdates-REPO/commits/master";
 
     private HashMap<String, Set<String>> usagesMap = new HashMap<>();
@@ -88,15 +91,6 @@ public class NEUManager {
         if(itemRenameJson == null) {
             itemRenameJson = new JsonObject();
         }
-
-        File wkShell = new File(configLocation, "wkhtmltox/bin/wkhtmltoimage");
-        if(!wkShell.exists()) {
-            try {
-                InputStream is = Minecraft.getMinecraft().getResourceManager().getResource(wkZip).getInputStream();
-                unzip(is, configLocation);
-            } catch (IOException e) {
-            }
-        }
     }
 
     public void setCurrentProfile(String currentProfile) {
@@ -163,7 +157,7 @@ public class NEUManager {
 
         }*/
 
-        Thread thread = new Thread(() -> {
+        repoLoaderES.submit(() -> {
             JDialog dialog = null;
             try {
                 if(NotEnoughUpdates.INSTANCE.config.hidden.autoupdate) {
@@ -211,12 +205,23 @@ public class NEUManager {
                     } catch (IOException e) {
                         return;
                     }
+
+
                     URL url = new URL(dlUrl);
                     URLConnection urlConnection = url.openConnection();
                     urlConnection.setConnectTimeout(15000);
-                    urlConnection.setReadTimeout(20000);
-                    try (BufferedInputStream inStream = new BufferedInputStream(urlConnection.getInputStream());
-                         FileOutputStream fileOutputStream = new FileOutputStream(itemsZip)) {
+                    urlConnection.setReadTimeout(30000);
+
+                    try(InputStream is = urlConnection.getInputStream()) {
+                        FileUtils.copyInputStreamToFile(is, itemsZip);
+                    } catch (IOException e) {
+                        dialog.dispose();
+                        e.printStackTrace();
+                        System.err.println("Failed to download NEU Repo! Please report this issue to the mod creator");
+                        return;
+                    }
+                    /*try (BufferedInputStream inStream = new BufferedInputStream(urlConnection.getInputStream());
+                            FileOutputStream fileOutputStream = new FileOutputStream(itemsZip)) {
                         byte dataBuffer[] = new byte[1024];
                         int bytesRead;
                         while ((bytesRead = inStream.read(dataBuffer, 0, 1024)) != -1) {
@@ -225,7 +230,7 @@ public class NEUManager {
                     } catch (IOException e) {
                         dialog.dispose();
                         return;
-                    }
+                    }*/
 
                     pane.setMessage("Unzipping NEU Master Archive.");
                     dialog.pack();
@@ -242,6 +247,8 @@ public class NEUManager {
                         } catch (IOException e) {
                         }
                     }
+
+                    Constants.reload();
                 }
             } catch(Exception e) {
                 e.printStackTrace();
@@ -249,31 +256,40 @@ public class NEUManager {
                 if(dialog != null) dialog.dispose();
             }
 
+            System.err.println("First load");
+
             File items = new File(repoLocation, "items");
             if(items.exists()) {
                 File[] itemFiles = new File(repoLocation, "items").listFiles();
                 if(itemFiles != null) {
                     for(File f : itemFiles) {
                         String internalname = f.getName().substring(0, f.getName().length()-5);
-                        if(!getItemInformation().keySet().contains(internalname)) {
-                            loadItem(internalname);
+                        synchronized(itemMap) {
+                            if(!itemMap.keySet().contains(internalname)) {
+                                loadItem(internalname);
+                            }
                         }
                     }
                 }
             }
         });
 
+        System.err.println("Second load");
+
         File items = new File(repoLocation, "items");
         if(items.exists()) {
             File[] itemFiles = new File(repoLocation, "items").listFiles();
             if(itemFiles != null) {
                 for(File f : itemFiles) {
                     String internalname = f.getName().substring(0, f.getName().length()-5);
-                    loadItem(internalname);
+                    synchronized(itemMap) {
+                        if(!itemMap.keySet().contains(internalname)) {
+                            loadItem(internalname);
+                        }
+                    }
                 }
             }
         }
-        thread.start();
     }
 
     /**
@@ -301,56 +317,69 @@ public class NEUManager {
             itemMap.put(internalName, json);
 
             if(json.has("recipe")) {
-                JsonObject recipe = json.get("recipe").getAsJsonObject();
-
-                String[] x = {"1","2","3"};
-                String[] y = {"A","B","C"};
-                for(int i=0; i<9; i++) {
-                    String name = y[i/3]+x[i%3];
-                    String itemS = recipe.get(name).getAsString();
-                    if(itemS != null && itemS.split(":").length == 2) {
-                        itemS = itemS.split(":")[0];
-                    }
+                synchronized(usagesMap) {
+                    JsonObject recipe = json.get("recipe").getAsJsonObject();
+
+                    String[] x = {"1","2","3"};
+                    String[] y = {"A","B","C"};
+                    for(int i=0; i<9; i++) {
+                        String name = y[i/3]+x[i%3];
+                        String itemS = recipe.get(name).getAsString();
+                        if(itemS != null && itemS.split(":").length == 2) {
+                            itemS = itemS.split(":")[0];
+                        }
 
-                    if(!usagesMap.containsKey(itemS)) {
-                        usagesMap.put(itemS, new HashSet<>());
+                        if(!usagesMap.containsKey(itemS)) {
+                            usagesMap.put(itemS, new HashSet<>());
+                        }
+                        usagesMap.get(itemS).add(internalName);
                     }
-                    usagesMap.get(itemS).add(internalName);
                 }
             }
 
             if(json.has("displayname")) {
-                int wordIndex=0;
-                for(String str : json.get("displayname").getAsString().split(" ")) {
-                    str = clean(str);
-                    if(!titleWordMap.containsKey(str)) {
-                        titleWordMap.put(str, new HashMap<>());
-                    }
-                    if(!titleWordMap.get(str).containsKey(internalName)) {
-                        titleWordMap.get(str).put(internalName, new ArrayList<>());
+                synchronized(titleWordMap) {
+                    int wordIndex=0;
+                    for(String str : json.get("displayname").getAsString().split(" ")) {
+                        str = clean(str);
+                        if(!titleWordMap.containsKey(str)) {
+                            titleWordMap.put(str, new HashMap<>());
+                        }
+                        if(!titleWordMap.get(str).containsKey(internalName)) {
+                            titleWordMap.get(str).put(internalName, new ArrayList<>());
+                        }
+                        titleWordMap.get(str).get(internalName).add(wordIndex);
+                        wordIndex++;
                     }
-                    titleWordMap.get(str).get(internalName).add(wordIndex);
-                    wordIndex++;
                 }
             }
 
             if(json.has("lore")) {
-                int wordIndex=0;
-                for(JsonElement element : json.get("lore").getAsJsonArray()) {
-                    for(String str : element.getAsString().split(" ")) {
-                        str = clean(str);
-                        if(!loreWordMap.containsKey(str)) {
-                            loreWordMap.put(str, new HashMap<>());
-                        }
-                        if(!loreWordMap.get(str).containsKey(internalName)) {
-                            loreWordMap.get(str).put(internalName, new ArrayList<>());
+                synchronized(loreWordMap) {
+                    int wordIndex=0;
+                    for(JsonElement element : json.get("lore").getAsJsonArray()) {
+                        for(String str : element.getAsString().split(" ")) {
+                            str = clean(str);
+                            if(!loreWordMap.containsKey(str)) {
+                                loreWordMap.put(str, new HashMap<>());
+                            }
+                            if(!loreWordMap.get(str).containsKey(internalName)) {
+                                loreWordMap.get(str).put(internalName, new ArrayList<>());
+                            }
+                            loreWordMap.get(str).get(internalName).add(wordIndex);
+                            wordIndex++;
                         }
-                        loreWordMap.get(str).get(internalName).add(wordIndex);
-                        wordIndex++;
                     }
                 }
             }
         } catch(Exception e) {
+            synchronized(loreWordMap) {
+                System.out.println("loreWordMap is : " + loreWordMap);
+            }
+            synchronized(titleWordMap) {
+                System.out.println("titleWordMap is : " + titleWordMap);
+            }
+            System.out.println("internalName is : " + internalName);
             e.printStackTrace();
         }
     }
@@ -1037,7 +1066,7 @@ public class NEUManager {
     /**
      * Modified from https://www.journaldev.com/960/java-unzip-file-example
      */
-    private static void unzip(InputStream src, File dest) {
+    public static void unzip(InputStream src, File dest) {
         //buffer for read and write data to file
         byte[] buffer = new byte[1024];
         try {
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/NEUOverlay.java b/src/main/java/io/github/moulberry/notenoughupdates/NEUOverlay.java
index 689a98f1..d11d21fa 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/NEUOverlay.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/NEUOverlay.java
@@ -64,6 +64,8 @@ import static io.github.moulberry.notenoughupdates.util.GuiTextures.*;
 public class NEUOverlay extends Gui {
 
     private static final ResourceLocation SUPERGEHEIMNISVERMOGEN = new ResourceLocation("notenoughupdates:supersecretassets/bald.png");
+    private static final ResourceLocation SEARCH_BAR = new ResourceLocation("notenoughupdates:search_bar.png");
+    private static final ResourceLocation SEARCH_BAR_GOLD = new ResourceLocation("notenoughupdates:search_bar_gold.png");
 
     private NEUManager manager;
 
@@ -116,12 +118,15 @@ public class NEUOverlay extends Gui {
     private LerpingInteger itemPaneTabOffset = new LerpingInteger(20, 50);
     private LerpingFloat infoPaneOffsetFactor = new LerpingFloat(0);
 
-    private boolean searchMode = false;
+    public boolean searchMode = false;
     private long millisLastLeftClick = 0;
     private long millisLastMouseMove = 0;
     private int lastMouseX = 0;
     private int lastMouseY = 0;
 
+    public static final int overlayColourDark = new Color(0, 0, 0, 120).getRGB();
+    public static final int overlayColourLight = new Color(255, 255, 255, 120).getRGB();
+
     boolean mouseDown = false;
 
     private boolean redrawItems = false;
@@ -199,14 +204,51 @@ public class NEUOverlay extends Gui {
                 FontRenderer fr = Minecraft.getMinecraft().fontRendererObj;
                 int paddingUnscaled = getPaddingUnscaled();
 
-                //Search bar background
-                drawRect((int)x, (int)y,
-                        (int)x + getWidth(), (int)y + getHeight(),
-                        searchMode ? Color.YELLOW.getRGB() : Color.WHITE.getRGB());
+                GlStateManager.color(1, 1, 1, 1);
+                if(searchMode) {
+                    Minecraft.getMinecraft().getTextureManager().bindTexture(SEARCH_BAR_GOLD);
+                } else {
+                    Minecraft.getMinecraft().getTextureManager().bindTexture(SEARCH_BAR);
+                }
+
+                int w = getWidth();
+                int h = getHeight();
+
+                for(int yIndex=0; yIndex<=2; yIndex++) {
+                    for(int xIndex=0; xIndex<=2; xIndex++) {
+                        float uMin = 0;
+                        float uMax = 4/20f;
+                        int partX = (int)x;
+                        int partW = 4;
+                        if(xIndex == 1) {
+                            partX += 4;
+                            uMin = 4/20f;
+                            uMax = 16/20f;
+                            partW = w-8;
+                        } else if(xIndex == 2) {
+                            partX += w-4;
+                            uMin = 16/20f;
+                            uMax = 20/20f;
+                        }
 
-                drawRect((int)x + paddingUnscaled, (int)y + paddingUnscaled,
-                        (int)x - paddingUnscaled + getWidth(), (int)y - paddingUnscaled + getHeight(),
-                        Color.BLACK.getRGB());
+                        float vMin = 0;
+                        float vMax = 4/20f;
+                        int partY = (int)y;
+                        int partH = 4;
+                        if(yIndex == 1) {
+                            partY += 4;
+                            vMin = 4/20f;
+                            vMax = 16/20f;
+                            partH = h-8;
+                        } else if(yIndex == 2) {
+                            partY += h-4;
+                            vMin = 16/20f;
+                            vMax = 20/20f;
+                        }
+
+                        Utils.drawTexturedRect(partX, partY, partW, partH, uMin, uMax, vMin, vMax, GL11.GL_NEAREST);
+                    }
+                }
 
                 //Search bar text
                 fr.drawString(textField.getText(), (int)x + 5,
@@ -791,6 +833,10 @@ public class NEUOverlay extends Gui {
         return paddingUnscaled;
     }
 
+    public GuiTextField getTextField() {
+        return textField;
+    }
+
     /**
      * Returns searchBarXSize, scaled by 0.8 if gui scale == AUTO.
      */
@@ -1521,52 +1567,6 @@ public class NEUOverlay extends Gui {
         GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
     }
 
-    /**
-     * Renders black squares over the inventory to indicate items that do not match a specific search. (When searchMode
-     * is enabled)
-     */
-    public void renderOverlay() {
-        int width = Utils.peekGuiScale().getScaledWidth();
-        int height = Utils.peekGuiScale().getScaledHeight();
-        int mouseX = Mouse.getX() * width / Minecraft.getMinecraft().displayWidth;
-        int mouseY = height - Mouse.getY() * height / Minecraft.getMinecraft().displayHeight - 1;
-
-        if(searchMode && textField.getText().length() > 0) {
-            if(Minecraft.getMinecraft().currentScreen instanceof GuiContainer) {
-                GuiContainer inv = (GuiContainer) Minecraft.getMinecraft().currentScreen;
-                int guiLeftI = (int)Utils.getField(GuiContainer.class, inv, "guiLeft", "field_147003_i");
-                int guiTopI = (int)Utils.getField(GuiContainer.class, inv, "guiTop", "field_147009_r");
-
-                GL11.glTranslatef(0, 0, 260);
-                int overlay = new Color(0, 0, 0, 100).getRGB();
-                for(Slot slot : inv.inventorySlots.inventorySlots) {
-                    boolean matches = false;
-                    for(String search : textField.getText().split("\\|")) {
-                        matches |= slot.getStack() != null && manager.doesStackMatchSearch(slot.getStack(), search.trim());
-                    }
-                    if(!matches) {
-                        drawRect(guiLeftI+slot.xDisplayPosition, guiTopI+slot.yDisplayPosition,
-                                guiLeftI+slot.xDisplayPosition+16, guiTopI+slot.yDisplayPosition+16,
-                                overlay);
-                    }
-                }
-                if(Utils.getSlotUnderMouse(inv) != null) {
-                    ItemStack stack = Utils.getSlotUnderMouse(inv).getStack();
-                    //Minecraft.getMinecraft().currentScreen.renderToolTip(stack, mouseX, mouseY);
-                    Class<?>[] params = new Class[]{ItemStack.class, int.class, int.class};
-                    Method renderToolTip = Utils.getMethod(GuiScreen.class, params, "renderToolTip", "func_146285_a");
-                    if(renderToolTip != null) {
-                        renderToolTip.setAccessible(true);
-                        try {
-                            renderToolTip.invoke(Minecraft.getMinecraft().currentScreen, stack, mouseX, mouseY);
-                        } catch(Exception e) {}
-                    }
-                }
-                GL11.glTranslatef(0, 0, -260);
-            }
-        }
-    }
-
     Shader blurShaderHorz = null;
     Framebuffer blurOutputHorz = null;
     Shader blurShaderVert = null;
@@ -1729,13 +1729,15 @@ public class NEUOverlay extends Gui {
         //Atomic reference used so that below lambda doesn't complain about non-effectively-final variable
         AtomicReference<JsonObject> tooltipToDisplay = new AtomicReference<>(null);
         if(itemPaneOffsetFactor.getValue() < 1) {
-            BackgroundBlur.renderBlurredBackground(NotEnoughUpdates.INSTANCE.config.itemlist.bgBlurFactor,
-                    width, height,
-                    leftSide+getBoxPadding()-5, getBoxPadding()-5,
-                    paneWidth-getBoxPadding()*2+10, height-getBoxPadding()*2+10, true);
-            Gui.drawRect(leftSide+getBoxPadding()-5, getBoxPadding()-5,
-                    leftSide+getBoxPadding()-5+paneWidth-getBoxPadding()*2+10,
-                    getBoxPadding()-5+height-getBoxPadding()*2+10, 0xc8101010);
+            if(NotEnoughUpdates.INSTANCE.config.itemlist.bgBlurFactor > 0.5) {
+                BackgroundBlur.renderBlurredBackground(NotEnoughUpdates.INSTANCE.config.itemlist.bgBlurFactor,
+                        width, height,
+                        leftSide+getBoxPadding()-5, getBoxPadding()-5,
+                        paneWidth-getBoxPadding()*2+10, height-getBoxPadding()*2+10, true);
+                Gui.drawRect(leftSide+getBoxPadding()-5, getBoxPadding()-5,
+                        leftSide+getBoxPadding()-5+paneWidth-getBoxPadding()*2+10,
+                        getBoxPadding()-5+height-getBoxPadding()*2+10, 0xc8101010);
+            }
 
             drawRect(leftSide+getBoxPadding()-5, getBoxPadding()-5,
                     leftSide+paneWidth-getBoxPadding()+5, height-getBoxPadding()+5, bg.getRGB());
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java b/src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java
index 59c821b0..2da79f84 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java
@@ -18,10 +18,7 @@ import io.github.moulberry.notenoughupdates.gamemodes.GuiGamemodes;
 import io.github.moulberry.notenoughupdates.gamemodes.SBGamemodes;
 import io.github.moulberry.notenoughupdates.infopanes.CollectionLogInfoPane;
 import io.github.moulberry.notenoughupdates.miscfeatures.*;
-import io.github.moulberry.notenoughupdates.miscgui.CalendarOverlay;
-import io.github.moulberry.notenoughupdates.miscgui.GuiEnchantColour;
-import io.github.moulberry.notenoughupdates.miscgui.HelpGUI;
-import io.github.moulberry.notenoughupdates.miscgui.NEUOverlayPlacements;
+import io.github.moulberry.notenoughupdates.miscgui.*;
 import io.github.moulberry.notenoughupdates.options.NEUConfig;
 import io.github.moulberry.notenoughupdates.options.NEUConfigEditor;
 import io.github.moulberry.notenoughupdates.overlays.FuelBar;
@@ -35,6 +32,7 @@ import io.github.moulberry.notenoughupdates.util.Utils;
 import io.github.moulberry.notenoughupdates.util.XPInformation;
 import net.minecraft.block.material.MapColor;
 import net.minecraft.client.Minecraft;
+import net.minecraft.client.entity.EntityPlayerSP;
 import net.minecraft.client.gui.GuiScreen;
 import net.minecraft.client.gui.inventory.GuiContainer;
 import net.minecraft.client.gui.inventory.GuiInventory;
@@ -123,6 +121,32 @@ public class NotEnoughUpdates {
         }
     });
 
+    SimpleCommand nullzeeSphereCommand = new SimpleCommand("neuzeesphere", new SimpleCommand.ProcessCommandRunnable() {
+        public void processCommand(ICommandSender sender, String[] args) {
+            if(args.length != 1) {
+                sender.addChatMessage(new ChatComponentText(EnumChatFormatting.RED+"Usage: /neuzeesphere [on/off] or /neuzeesphere (radius) or /neuzeesphere setCenter"));
+                return;
+            }
+            if(args[0].equalsIgnoreCase("on")) {
+                NullzeeSphere.enabled = true;
+            } else if(args[0].equalsIgnoreCase("off")) {
+                NullzeeSphere.enabled = false;
+            } else if(args[0].equalsIgnoreCase("setCenter")) {
+                EntityPlayerSP p = ((EntityPlayerSP)sender);
+                NullzeeSphere.centerPos = new BlockPos(p.posX, p.posY, p.posZ);
+                NullzeeSphere.overlayVBO = null;
+            } else {
+                try {
+                    float radius = Float.parseFloat(args[0]);
+                    NullzeeSphere.size = radius;
+                    NullzeeSphere.overlayVBO = null;
+                } catch(Exception e) {
+                    sender.addChatMessage(new ChatComponentText(EnumChatFormatting.RED+"Can't parse radius: " + args[0]));
+                }
+            }
+        }
+    });
+
     SimpleCommand itemRenameCommand = new SimpleCommand("neurename", new SimpleCommand.ProcessCommandRunnable() {
         public void processCommand(ICommandSender sender, String[] args) {
             if(args.length == 0) {
@@ -215,6 +239,12 @@ public class NotEnoughUpdates {
         }
     });
 
+    SimpleCommand buttonsCommand = new SimpleCommand("neubuttons", new SimpleCommand.ProcessCommandRunnable() {
+        public void processCommand(ICommandSender sender, String[] args) {
+            openGui = new GuiInvButtonEditor();
+        }
+    });
+
     SimpleCommand enchantColourCommand = new SimpleCommand("neuec", new SimpleCommand.ProcessCommandRunnable() {
         public void processCommand(ICommandSender sender, String[] args) {
             openGui = new GuiEnchantColour();
@@ -250,6 +280,13 @@ public class NotEnoughUpdates {
                 }
             }
             Constants.reload();
+
+            configFile = new File(neuDir, "configNew.json");
+            if(configFile.exists()) {
+                try(BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(configFile), StandardCharsets.UTF_8))) {
+                    config = gson.fromJson(reader, NEUConfig.class);
+                } catch(Exception e) { }
+            }
         }
     });
 
@@ -690,7 +727,14 @@ public class NotEnoughUpdates {
                 Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.RED+devFailStrings[devFailIndex++]));
                 return;
             }
-            if(args.length == 1 && args[0].equalsIgnoreCase("dev")) NotEnoughUpdates.INSTANCE.config.hidden.dev = true;
+            if(args.length == 1 && args[0].equalsIgnoreCase("dev")) {
+                NotEnoughUpdates.INSTANCE.config.hidden.dev = true;
+                return;
+            }
+            if(args.length == 1 && args[0].equalsIgnoreCase("saveconfig")) {
+                saveConfig();
+                return;
+            }
             Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.GREEN+"Executing dubious code"));
             /*Minecraft.getMinecraft().thePlayer.rotationYaw = 0;
             Minecraft.getMinecraft().thePlayer.rotationPitch = 0;
@@ -991,15 +1035,18 @@ public class NotEnoughUpdates {
         MinecraftForge.EVENT_BUS.register(XPInformation.getInstance());
         MinecraftForge.EVENT_BUS.register(OverlayManager.petInfoOverlay);
         MinecraftForge.EVENT_BUS.register(OverlayManager.timersOverlay);
+        MinecraftForge.EVENT_BUS.register(new NullzeeSphere());
 
         if(Minecraft.getMinecraft().getResourceManager() instanceof IReloadableResourceManager) {
             ((IReloadableResourceManager)Minecraft.getMinecraft().getResourceManager()).registerReloadListener(CustomSkulls.getInstance());
         }
 
         ClientCommandHandler.instance.registerCommand(collectionLogCommand);
+        ClientCommandHandler.instance.registerCommand(nullzeeSphereCommand);
         ClientCommandHandler.instance.registerCommand(cosmeticsCommand);
         ClientCommandHandler.instance.registerCommand(linksCommand);
         ClientCommandHandler.instance.registerCommand(gamemodesCommand);
+        ClientCommandHandler.instance.registerCommand(buttonsCommand);
         ClientCommandHandler.instance.registerCommand(resetRepoCommand);
         ClientCommandHandler.instance.registerCommand(reloadRepoCommand);
         ClientCommandHandler.instance.registerCommand(itemRenameCommand);
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/core/GuiElementTextField.java b/src/main/java/io/github/moulberry/notenoughupdates/core/GuiElementTextField.java
index b2f947d6..111803e8 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/core/GuiElementTextField.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/core/GuiElementTextField.java
@@ -2,6 +2,7 @@ package io.github.moulberry.notenoughupdates.core;
 
 import io.github.moulberry.notenoughupdates.core.util.StringUtils;
 import io.github.moulberry.notenoughupdates.core.util.render.TextRenderUtils;
+import io.github.moulberry.notenoughupdates.util.Utils;
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.gui.Gui;
 import net.minecraft.client.gui.GuiScreen;
@@ -462,14 +463,21 @@ public class GuiElementTextField {
 
         String selectedText = textField.getSelectedText();
         if(!selectedText.isEmpty()) {
+            System.out.println("Start");
             int leftIndex = Math.min(textField.getCursorPosition()+prependText.length(), textField.getSelectionEnd()+prependText.length());
             int rightIndex = Math.max(textField.getCursorPosition()+prependText.length(), textField.getSelectionEnd()+prependText.length());
 
             float texX = 0;
             int texY = 0;
             boolean sectionSignPrev = false;
+            boolean ignoreNext = false;
             boolean bold = false;
             for(int i=0; i<textNoColor.length(); i++) {
+                if(ignoreNext) {
+                    ignoreNext = false;
+                    continue;
+                }
+
                 char c = textNoColor.charAt(i);
                 if(sectionSignPrev) {
                     if(c != 'k' && c != 'K'
@@ -478,9 +486,13 @@ public class GuiElementTextField {
                             && c != 'o' && c != 'O') {
                         bold = c == 'l' || c == 'L';
                     }
+                    sectionSignPrev = false;
+                    if(i < prependText.length()) continue;
+                }
+                if(c == '\u00B6') {
+                    sectionSignPrev = true;
+                    if(i < prependText.length()) continue;
                 }
-                sectionSignPrev = false;
-                if(c == '\u00B6') sectionSignPrev = true;
 
                 if(c == '\n') {
                     if(i >= leftIndex && i < rightIndex) {
@@ -497,6 +509,7 @@ public class GuiElementTextField {
 
                 //String c2 = bold ? EnumChatFormatting.BOLD.toString() : "" + c;
 
+                System.out.println("Adding len for char:"+c+":"+Integer.toHexString(c));
                 int len = Minecraft.getMinecraft().fontRendererObj.getStringWidth(String.valueOf(c));
                 if(bold) len++;
                 if(i >= leftIndex && i < rightIndex) {
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/cosmetics/CapeManager.java b/src/main/java/io/github/moulberry/notenoughupdates/cosmetics/CapeManager.java
index 963b91c3..21f6baaf 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/cosmetics/CapeManager.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/cosmetics/CapeManager.java
@@ -48,8 +48,11 @@ public class CapeManager {
     public JsonObject lastJsonSync = null;
 
     private String[] capes = new String[]{"patreon1", "patreon2", "fade", "contrib", "nullzee",
-            "gravy", "space", "mcworld", "lava", "packshq", "mbstaff", "thebakery", "negative", "void", "ironmoon", "krusty", "furf", "soldier", "dsm" };
-    public Boolean[] specialCapes = new Boolean[]{ true, true, false, true, true, true, false, false, false, true, true, true, false, false, true, false, true, true, true };
+            "gravy", "space", "mcworld", "lava", "packshq", "mbstaff", "thebakery", "negative",
+            "void", "ironmoon", "krusty", "furf", "soldier", "dsm", "zera", "tunnel", "alexxoffi", "parallax", "jakethybro", "planets" };
+    public Boolean[] specialCapes = new Boolean[]{ true, true, false, true, true,
+            true, false, false, false, true, true, true, false,
+            false, true, false, true, true, true, true, false, true, true, true, false };
 
     public static CapeManager getInstance() {
         return INSTANCE;
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/cosmetics/CapeNode.java b/src/main/java/io/github/moulberry/notenoughupdates/cosmetics/CapeNode.java
index b01365d3..06bc6db4 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/cosmetics/CapeNode.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/cosmetics/CapeNode.java
@@ -31,6 +31,10 @@ public class CapeNode {
     public Vector3f sideNormal = null;
     public boolean fixed = false;
 
+    public static final int DRAW_MASK_FRONT = 0b1;
+    public static final int DRAW_MASK_BACK = 0b10;
+    public static final int DRAW_MASK_SIDES = 0b100;
+
     public HashMap<NEUCape.Offset, CapeNode> neighbors = new HashMap<>();
 
     public float texU = 0;
@@ -279,6 +283,10 @@ public class CapeNode {
     }
 
     public void renderNode() {
+        renderNode(DRAW_MASK_FRONT | DRAW_MASK_BACK | DRAW_MASK_SIDES);
+    }
+
+    public void renderNode(int mask) {
         CapeNode nodeLeft = getNeighbor(new NEUCape.Offset(NEUCape.Direction.LEFT, 1));
         CapeNode nodeUp = getNeighbor(new NEUCape.Offset(NEUCape.Direction.UP, 1));
         CapeNode nodeDown = getNeighbor(new NEUCape.Offset(NEUCape.Direction.DOWN, 1));
@@ -290,36 +298,42 @@ public class CapeNode {
 
         if(nodeDown != null && nodeRight != null && nodeDownRight != null) {
             //Back
-            worldrenderer.begin(GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION_TEX_NORMAL);
-            for(CapeNode node : new CapeNode[]{this, nodeDown, nodeRight, nodeDownRight}) {
-                Vector3f nodeNorm = node.normal();
-                worldrenderer.pos(node.renderPosition.x, node.renderPosition.y, node.renderPosition.z)
-                        .tex(1-node.texU, node.texV)
-                        .normal(-nodeNorm.x, -nodeNorm.y, -nodeNorm.z).endVertex();
+            if((mask & DRAW_MASK_BACK) != 0) {
+                worldrenderer.begin(GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION_TEX_NORMAL);
+                for(CapeNode node : new CapeNode[]{this, nodeDown, nodeRight, nodeDownRight}) {
+                    Vector3f nodeNorm = node.normal();
+                    worldrenderer.pos(node.renderPosition.x, node.renderPosition.y, node.renderPosition.z)
+                            .tex(1-node.texU, node.texV)
+                            .normal(-nodeNorm.x, -nodeNorm.y, -nodeNorm.z).endVertex();
+                }
+                tessellator.draw();
             }
-            tessellator.draw();
 
             //Front (Offset by normal)
-            worldrenderer.begin(GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION_TEX_NORMAL);
-            for(CapeNode node : new CapeNode[]{this, nodeDown, nodeRight, nodeDownRight}) {
-                Vector3f nodeNorm = node.normal();
-                worldrenderer.pos(node.renderPosition.x+nodeNorm.x*0.05f, node.renderPosition.y+nodeNorm.y*0.05f, node.renderPosition.z+nodeNorm.z*0.05f)
-                        .tex(node.texU, node.texV)
-                        .normal(nodeNorm.x, nodeNorm.y, nodeNorm.z).endVertex();
+            if((mask & DRAW_MASK_FRONT) != 0) {
+                worldrenderer.begin(GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION_TEX_NORMAL);
+                for(CapeNode node : new CapeNode[]{nodeDownRight, nodeDown, nodeRight, this}) {
+                    Vector3f nodeNorm = node.normal();
+                    worldrenderer.pos(node.renderPosition.x+nodeNorm.x*0.05f, node.renderPosition.y+nodeNorm.y*0.05f, node.renderPosition.z+nodeNorm.z*0.05f)
+                            .tex(node.texU, node.texV)
+                            .normal(nodeNorm.x, nodeNorm.y, nodeNorm.z).endVertex();
+                }
+                tessellator.draw();
             }
-            tessellator.draw();
         }
 
-        if(nodeLeft == null || nodeRight == null) {
-            //Render left/right edge
-            if(nodeDown != null) {
-                renderEdge(nodeDown, true);
+        if((mask & DRAW_MASK_SIDES) != 0) {
+            if(nodeLeft == null || nodeRight == null) {
+                //Render left/right edge
+                if(nodeDown != null) {
+                    renderEdge(nodeDown, true);
+                }
             }
-        }
-        if(nodeUp == null || nodeDown == null) {
-            //Render up/down edge
-            if(nodeRight != null) {
-                renderEdge(nodeRight, false);
+            if(nodeUp == null || nodeDown == null) {
+                //Render up/down edge
+                if(nodeRight != null) {
+                    renderEdge(nodeRight, false);
+                }
             }
         }
     }
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/cosmetics/GuiCosmetics.java b/src/main/java/io/github/moulberry/notenoughupdates/cosmetics/GuiCosmetics.java
index d0651075..164dfeb8 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/cosmetics/GuiCosmetics.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/cosmetics/GuiCosmetics.java
@@ -269,7 +269,7 @@ public class GuiCosmetics extends GuiScreen {
                 if(mouseY > guiTop+sizeY+5 && mouseY < guiTop+sizeY+25) {
                     if(System.currentTimeMillis() - lastCapeEquip > 20*1000) {
                         CapeManager.INSTANCE.setCape(Minecraft.getMinecraft().thePlayer.getUniqueID().toString().replace("-", ""),
-                                null, true);
+                                wantToEquipCape, true);
 
                         lastCapeEquip = System.currentTimeMillis();
 
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/cosmetics/NEUCape.java b/src/main/java/io/github/moulberry/notenoughupdates/cosmetics/NEUCape.java
index b2d220c8..163b14dd 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/cosmetics/NEUCape.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/cosmetics/NEUCape.java
@@ -1,7 +1,9 @@
 package io.github.moulberry.notenoughupdates.cosmetics;
 
 import io.github.moulberry.notenoughupdates.core.util.lerp.LerpUtils;
+import io.github.moulberry.notenoughupdates.util.ReverseWorldRenderer;
 import io.github.moulberry.notenoughupdates.util.TexLoc;
+import io.github.moulberry.notenoughupdates.util.Utils;
 import net.minecraft.block.Block;
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.renderer.GlStateManager;
@@ -9,6 +11,8 @@ import net.minecraft.client.renderer.OpenGlHelper;
 import net.minecraft.client.renderer.Tessellator;
 import net.minecraft.client.renderer.WorldRenderer;
 import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
+import net.minecraft.client.renderer.vertex.VertexFormat;
+import net.minecraft.client.renderer.vertex.VertexFormatElement;
 import net.minecraft.entity.Entity;
 import net.minecraft.entity.EntityLivingBase;
 import net.minecraft.entity.player.EntityPlayer;
@@ -16,6 +20,7 @@ import net.minecraft.potion.Potion;
 import net.minecraft.util.BlockPos;
 import net.minecraft.util.MathHelper;
 import net.minecraft.util.ResourceLocation;
+import net.minecraft.util.Vec3;
 import net.minecraft.world.gen.NoiseGeneratorSimplex;
 import net.minecraftforge.client.event.RenderPlayerEvent;
 import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
@@ -27,6 +32,7 @@ import org.lwjgl.opengl.*;
 import org.lwjgl.util.vector.Vector2f;
 import org.lwjgl.util.vector.Vector3f;
 
+import java.nio.ByteBuffer;
 import java.nio.FloatBuffer;
 import java.nio.IntBuffer;
 import java.security.Key;
@@ -45,7 +51,7 @@ public class NEUCape {
     private static int ANIM_MODE_PINGPONG = 1;
     private int animMode = ANIM_MODE_LOOP;
 
-    private List<List<CapeNode>> nodes = null;
+    private CapeNode[] nodes = null;
 
     private Random random = new Random();
 
@@ -60,7 +66,7 @@ public class NEUCape {
     public static final int HORZ_NODES = 6;
     public static final int VERT_NODES = 22;
 
-    public static float targetDist = 1/20f;
+    public static float targetDist = 1 / 20f;
 
     private EntityPlayer currentPlayer;
     private boolean keepCurrentPlayer = false;
@@ -93,11 +99,15 @@ public class NEUCape {
             shaderName = "negative";
         } else if(capeName.equalsIgnoreCase("void")) {
             shaderName = "void";
+        } else if(capeName.equalsIgnoreCase("tunnel")) {
+            shaderName = "tunnel";
+        } else if(capeName.equalsIgnoreCase("planets")) {
+            shaderName = "planets";
         } else {
             shaderName = "shiny_cape";
         }
 
-        ResourceLocation staticCapeTex = new ResourceLocation("notenoughupdates:capes/"+capeName+".png");
+        ResourceLocation staticCapeTex = new ResourceLocation("notenoughupdates:capes/" + capeName + ".png");
         capeTextures = new ResourceLocation[1];
         capeTextures[0] = staticCapeTex;
 
@@ -128,17 +138,17 @@ public class NEUCape {
             if(CapeManager.getInstance().backgroundFramebuffer != null) {
                 CapeManager.getInstance().backgroundFramebuffer.bindFramebufferTexture();
             }
-        } else if(capeTextures != null && capeTextures.length>0) {
+        } else if(capeTextures != null && capeTextures.length > 0) {
             long currentTime = System.currentTimeMillis();
             if(currentTime - lastFrameUpdate > 100) {
-                lastFrameUpdate = currentTime/100*100;
+                lastFrameUpdate = currentTime / 100 * 100;
                 currentFrame++;
 
                 if(animMode == ANIM_MODE_PINGPONG) {
                     if(capeTextures.length == 1) {
                         currentFrame = displayFrame = 0;
                     } else {
-                        int frameCount = 2*capeTextures.length-2;
+                        int frameCount = 2 * capeTextures.length - 2;
                         currentFrame %= frameCount;
                         displayFrame = currentFrame;
                         if(currentFrame >= capeTextures.length) {
@@ -154,80 +164,72 @@ public class NEUCape {
         }
     }
 
-    public boolean rlExists(ResourceLocation loc) {
-        try {
-            return !Minecraft.getMinecraft().getResourceManager().getAllResources(loc).isEmpty();
-        } catch(Exception e) {
-            return false;
-        }
+    private CapeNode getNode(int x, int y) {
+        return nodes[x + y * HORZ_NODES];
     }
 
     public void createCapeNodes(EntityPlayer player) {
-        nodes = new ArrayList<>();
+        nodes = new CapeNode[HORZ_NODES * VERT_NODES];
 
-        float pX = (float)player.posX % 7789;
-        float pY = (float)player.posY;
-        float pZ = (float)player.posZ % 7789;
+        float pX = (float) player.posX % 7789;
+        float pY = (float) player.posY;
+        float pZ = (float) player.posZ % 7789;
 
-        float uMinTop = 48/1024f;
-        float uMaxTop = 246/1024f;
-        float uMinBottom = 0/1024f;
-        float uMaxBottom = 293/1024f;
+        float uMinTop = 48 / 1024f;
+        float uMaxTop = 246 / 1024f;
+        float uMinBottom = 0 / 1024f;
+        float uMaxBottom = 293 / 1024f;
 
-        float vMaxSide = 404/1024f;
-        float vMaxCenter = 419/1024f;
+        float vMaxSide = 404 / 1024f;
+        float vMaxCenter = 419 / 1024f;
 
-        for(int i=0; i<VERT_NODES; i++) {
-            float uMin = uMinTop + (uMinBottom - uMinTop) * i/(float)(VERT_NODES-1);
-            float uMax = uMaxTop + (uMaxBottom - uMaxTop) * i/(float)(VERT_NODES-1);
+        for(int i = 0; i < VERT_NODES; i++) {
+            float uMin = uMinTop + (uMinBottom - uMinTop) * i / (float) (VERT_NODES - 1);
+            float uMax = uMaxTop + (uMaxBottom - uMaxTop) * i / (float) (VERT_NODES - 1);
 
-            List<CapeNode> row = new ArrayList<>();
-            for(int j=0; j<HORZ_NODES; j++) {
+            for(int j = 0; j < HORZ_NODES; j++) {
                 float vMin = 0f;
-                float centerMult = 1-Math.abs(j-(HORZ_NODES-1)/2f)/((HORZ_NODES-1)/2f);//0-(horzCapeNodes)  -> 0-1-0
+                float centerMult = 1 - Math.abs(j - (HORZ_NODES - 1) / 2f) / ((HORZ_NODES - 1) / 2f);//0-(horzCapeNodes)  -> 0-1-0
                 float vMax = vMaxSide + (vMaxCenter - vMaxSide) * centerMult;
 
                 CapeNode node = new CapeNode(pX, pY, pZ);//pX-1, pY+2-i*targetDist, pZ+(j-(horzCapeNodes-1)/2f)*targetDist*2
-                node.texU = uMin + (uMax - uMin) * j/(float)(HORZ_NODES-1);
-                node.texV = vMin + (vMax - vMin) * i/(float)(VERT_NODES-1);
+                node.texU = uMin + (uMax - uMin) * j / (float) (HORZ_NODES - 1);
+                node.texV = vMin + (vMax - vMin) * i / (float) (VERT_NODES - 1);
 
-                node.horzDistMult = 2f+1f*i/(float)(VERT_NODES-1);
+                node.horzDistMult = 2f + 1f * i / (float) (VERT_NODES - 1);
 
-                if(j == 0 || j == HORZ_NODES-1) {
-                    node.horzSideTexU = 406/1024f * i/(float)(VERT_NODES-1);
+                if(j == 0 || j == HORZ_NODES - 1) {
+                    node.horzSideTexU = 406 / 1024f * i / (float) (VERT_NODES - 1);
                     if(j == 0) {
-                        node.horzSideTexVTop = 1 - 20/1024f;
+                        node.horzSideTexVTop = 1 - 20 / 1024f;
                     } else {
-                        node.horzSideTexVTop = 1 - 40/1024f;
+                        node.horzSideTexVTop = 1 - 40 / 1024f;
                     }
                 }
                 if(i == 0) {
-                    node.vertSideTexU = 198/1024f * j/(float)(HORZ_NODES-1);
-                    node.vertSideTexVTop = 1 - 60/1024f;
-                } else if(i == VERT_NODES-1) {
-                    node.vertSideTexU = 300/1024f * j/(float)(HORZ_NODES-1);
-                    node.vertSideTexVTop = 1 - 80/1024f;
+                    node.vertSideTexU = 198 / 1024f * j / (float) (HORZ_NODES - 1);
+                    node.vertSideTexVTop = 1 - 60 / 1024f;
+                } else if(i == VERT_NODES - 1) {
+                    node.vertSideTexU = 300 / 1024f * j / (float) (HORZ_NODES - 1);
+                    node.vertSideTexVTop = 1 - 80 / 1024f;
                 }
-                row.add(node);
+                nodes[j + i * HORZ_NODES] = node;
             }
-
-            nodes.add(row);
         }
-        for(int y=0; y<nodes.size(); y++) {
-            int xSize = nodes.get(y).size();
-            for(int x=0; x<xSize; x++) {
-                CapeNode node = nodes.get(y).get(x);
+        for(int y = 0; y < VERT_NODES; y++) {
+            for(int x = 0; x < HORZ_NODES; x++) {
+                CapeNode node = nodes[x + y * HORZ_NODES];
 
                 for(Direction dir : Direction.values()) {
-                    for(int i=1; i<=2; i++) {
+                    for(int i = 1; i <= 2; i++) {
                         Offset offset = new Offset(dir, i);
 
-                        int xNeighbor = x+offset.getXOffset();
-                        int yNeighbor = y+offset.getYOffset();
+                        int xNeighbor = x + offset.getXOffset();
+                        int yNeighbor = y + offset.getYOffset();
 
-                        if(xNeighbor >= 0 && xNeighbor < nodes.get(y).size()
-                                && yNeighbor >= 0 && yNeighbor < nodes.size()) {
-                            CapeNode neighbor = nodes.get(yNeighbor).get(xNeighbor);
+                        if(xNeighbor >= 0 && xNeighbor < HORZ_NODES
+                                && yNeighbor >= 0 && yNeighbor < VERT_NODES) {
+                            CapeNode neighbor = nodes[xNeighbor + yNeighbor * HORZ_NODES];
                             node.setNeighbor(offset, neighbor);
                         }
                     }
@@ -291,11 +293,11 @@ public class NEUCape {
         }
 
         public int getXOffset() {
-            return direction.xOff*steps;
+            return direction.xOff * steps;
         }
 
         public int getYOffset() {
-            return direction.yOff*steps;
+            return direction.yOff * steps;
         }
 
         public boolean equals(Object obj) {
@@ -308,27 +310,27 @@ public class NEUCape {
 
         @Override
         public int hashCode() {
-            return 13*direction.ordinal() + 7*steps;
+            return 13 * direction.ordinal() + 7 * steps;
         }
     }
 
     private void loadShaderUniforms(ShaderManager shaderManager) {
-        String shaderId = "capes/"+shaderName+"/"+shaderName;
-        if(shaderName.equalsIgnoreCase("fade_cape")) {
-            shaderManager.loadData(shaderId, "millis", (int)(System.currentTimeMillis()-startTime));
+        String shaderId = "capes/" + shaderName + "/" + shaderName;
+        if(shaderName.equalsIgnoreCase("fade_cape") || shaderName.equalsIgnoreCase("planets")) {
+            shaderManager.loadData(shaderId, "millis", (int) (System.currentTimeMillis() - startTime));
         } else if(shaderName.equalsIgnoreCase("space_cape")) {
-            shaderManager.loadData(shaderId, "millis", (int)(System.currentTimeMillis()-startTime));
-            shaderManager.loadData(shaderId, "eventMillis", (int)(System.currentTimeMillis()-eventMillis));
+            shaderManager.loadData(shaderId, "millis", (int) (System.currentTimeMillis() - startTime));
+            shaderManager.loadData(shaderId, "eventMillis", (int) (System.currentTimeMillis() - eventMillis));
             shaderManager.loadData(shaderId, "eventRand", eventRandom);
         } else if(shaderName.equalsIgnoreCase("mcworld_cape")) {
             shaderManager.loadData(shaderId, "millis", (int) (System.currentTimeMillis() - startTime));
         } else if(shaderName.equalsIgnoreCase("lava_cape")) {
             shaderManager.loadData(shaderId, "millis", (int) (System.currentTimeMillis() - startTime));
-        } else if(shaderName.equalsIgnoreCase("lightning_cape")) {
+        } else if(shaderName.equalsIgnoreCase("tunnel")) {
             shaderManager.loadData(shaderId, "millis", (int) (System.currentTimeMillis() - startTime));
         } else if(shaderName.equalsIgnoreCase("biscuit_cape") || shaderName.equalsIgnoreCase("shiny_cape")) {
             shaderManager.loadData(shaderId, "millis", (int) (System.currentTimeMillis() - startTime));
-            shaderManager.loadData(shaderId, "eventMillis", (int)(System.currentTimeMillis()-eventMillis));
+            shaderManager.loadData(shaderId, "eventMillis", (int) (System.currentTimeMillis() - eventMillis));
         } else if(shaderName.equalsIgnoreCase("negative")) {
             shaderManager.loadData(shaderId, "screensize", new Vector2f(
                     Minecraft.getMinecraft().displayWidth,
@@ -344,6 +346,7 @@ public class NEUCape {
     }
 
     long lastRender = 0;
+
     public void onRenderPlayer(RenderPlayerEvent.Post e) {
         EntityPlayer player = e.entityPlayer;
 
@@ -378,21 +381,23 @@ public class NEUCape {
             GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
         }
 
-        GL11.glTranslatef(-(float)viewerX, -(float)viewerY, -(float)viewerZ);
+        GL11.glTranslatef(-(float) viewerX, -(float) viewerY, -(float) viewerZ);
 
         ShaderManager shaderManager = ShaderManager.getInstance();
-        shaderManager.loadShader("capes/"+shaderName+"/"+shaderName);
+        shaderManager.loadShader("capes/" + shaderName + "/" + shaderName);
         loadShaderUniforms(shaderManager);
 
         renderCape(player, e.partialRenderTick);
 
-        GL11.glTranslatef((float)viewerX, (float)viewerY, (float)viewerZ);
+        GL11.glTranslatef((float) viewerX, (float) viewerY, (float) viewerZ);
 
         GL20.glUseProgram(0);
 
         GlStateManager.enableCull();
         GlStateManager.enableTexture2D();
         GlStateManager.disableBlend();
+        GlStateManager.enableDepth();
+        GlStateManager.enableLighting();
         GlStateManager.popMatrix();
 
         lastRender = System.currentTimeMillis();
@@ -412,9 +417,9 @@ public class NEUCape {
 
             ensureCapeNodesCreated(player);
 
-            for(int y=0; y<nodes.size(); y++) {
-                for(int x=0; x<nodes.get(y).size(); x++) {
-                    CapeNode node = nodes.get(y).get(x);
+            for(int y = 0; y < VERT_NODES; y++) {
+                for(int x = 0; x < HORZ_NODES; x++) {
+                    CapeNode node = nodes[x + y * HORZ_NODES];
                     node.lastPosition.x = node.position.x;
                     node.lastPosition.y = node.position.y;
                     node.lastPosition.z = node.position.z;
@@ -429,9 +434,11 @@ public class NEUCape {
     private static double interpolateRotation(float a, float b, float amount) {
         double f;
 
-        for (f = b - a; f < -180.0F; f += 360.0F) {;}
+        for(f = b - a; f < -180.0F; f += 360.0F) {
+            ;
+        }
 
-        while (f >= 180.0F) {
+        while(f >= 180.0F) {
             f -= 360.0F;
         }
 
@@ -475,22 +482,21 @@ public class NEUCape {
         double vertOffset2 = vertOffset + (player.isSneaking() ? -0.22f : 0) + (player.getCurrentArmor(2) != null ? 0.06f : 0);
         double shoulderWidth2 = shoulderWidth + (player.getCurrentArmor(2) != null ? 0.08f : 0);
 
-        float xOff = (float)(Math.cos(angle)*shoulderLength);
-        float zOff = (float)(Math.sin(angle)*shoulderLength);
+        float xOff = (float) (Math.cos(angle) * shoulderLength);
+        float zOff = (float) (Math.sin(angle) * shoulderLength);
 
         float totalDX = 0;
         float totalDY = 0;
         float totalDZ = 0;
         int totalMovements = 0;
 
-        int xSize = nodes.get(0).size();
-        for(int i=0; i<xSize; i++) {
-            float mult = 1 - 2f*i/(xSize-1); //1 -> -1
-            float widthMult = 1.25f-(1.414f*i/(xSize-1) - 0.707f)*(1.414f*i/(xSize-1) - 0.707f);
-            CapeNode node = nodes.get(0).get(i);
-            float x = (float)pX+(float)(xOff*mult-widthMult*Math.cos(angle+Math.PI/2)*shoulderWidth2);
-            float y = (float)pY+(float)(vertOffset2);
-            float z = (float)pZ+(float)(zOff*mult-widthMult*Math.sin(angle+Math.PI/2)*shoulderWidth2);
+        for(int i = 0; i < HORZ_NODES; i++) {
+            float mult = 1 - 2f * i / (HORZ_NODES - 1); //1 -> -1
+            float widthMult = 1.25f - (1.414f * i / (HORZ_NODES - 1) - 0.707f) * (1.414f * i / (HORZ_NODES - 1) - 0.707f);
+            CapeNode node = nodes[i];
+            float x = (float) pX + (float) (xOff * mult - widthMult * Math.cos(angle + Math.PI / 2) * shoulderWidth2);
+            float y = (float) pY + (float) (vertOffset2);
+            float z = (float) pZ + (float) (zOff * mult - widthMult * Math.sin(angle + Math.PI / 2) * shoulderWidth2);
             totalDX += x - node.position.x;
             totalDY += y - node.position.y;
             totalDZ += z - node.position.z;
@@ -501,9 +507,9 @@ public class NEUCape {
             node.fixed = true;
         }
 
-        float avgDX = totalDX/totalMovements;
-        float avgDY = totalDY/totalMovements;
-        float avgDZ = totalDZ/totalMovements;
+        float avgDX = totalDX / totalMovements;
+        float avgDY = totalDY / totalMovements;
+        float avgDZ = totalDZ / totalMovements;
 
         return new Vector3f(avgDX, avgDY, avgDZ);
     }
@@ -517,17 +523,16 @@ public class NEUCape {
         double vertOffset2 = vertOffset + (player.isSneaking() ? -0.22f : 0) + (player.getCurrentArmor(2) != null ? 0.06f : 0);
         double shoulderWidth2 = shoulderWidth + (player.getCurrentArmor(2) != null ? 0.08f : 0);
 
-        float xOff = (float)(Math.cos(angle)*shoulderLength);
-        float zOff = (float)(Math.sin(angle)*shoulderLength);
-
-        int xSize = nodes.get(0).size();
-        for(int i=0; i<xSize; i++) {
-            float mult = 1 - 2f*i/(xSize-1); //1 -> -1
-            float widthMult = 1.25f-(1.414f*i/(xSize-1) - 0.707f)*(1.414f*i/(xSize-1) - 0.707f);
-            CapeNode node = nodes.get(0).get(i);
-            node.renderPosition.x = (float)pX+(float)(xOff*mult-widthMult*Math.cos(angle+Math.PI/2)*shoulderWidth2);
-            node.renderPosition.y = (float)pY+(float)(vertOffset2);
-            node.renderPosition.z = (float)pZ+(float)(zOff*mult-widthMult*Math.sin(angle+Math.PI/2)*shoulderWidth2);
+        float xOff = (float) (Math.cos(angle) * shoulderLength);
+        float zOff = (float) (Math.sin(angle) * shoulderLength);
+
+        for(int i = 0; i < HORZ_NODES; i++) {
+            float mult = 1 - 2f * i / (HORZ_NODES - 1); //1 -> -1
+            float widthMult = 1.25f - (1.414f * i / (HORZ_NODES - 1) - 0.707f) * (1.414f * i / (HORZ_NODES - 1) - 0.707f);
+            CapeNode node = nodes[i];
+            node.renderPosition.x = (float) pX + (float) (xOff * mult - widthMult * Math.cos(angle + Math.PI / 2) * shoulderWidth2);
+            node.renderPosition.y = (float) pY + (float) (vertOffset2);
+            node.renderPosition.z = (float) pZ + (float) (zOff * mult - widthMult * Math.sin(angle + Math.PI / 2) * shoulderWidth2);
             node.fixed = true;
         }
     }
@@ -536,57 +541,58 @@ public class NEUCape {
     private double oldPlayerAngle;
     private int crouchTicks = 0;
     long startTime = 0;
+
     private void updateCape(EntityPlayer player) {
         Vector3f capeTranslation = updateFixedCapeNodes(player);
 
         if(shaderName.equals("space_cape")) {
             long currentTime = System.currentTimeMillis();
-            if(currentTime-startTime > eventMillis-startTime+eventLength) {
+            if(currentTime - startTime > eventMillis - startTime + eventLength) {
                 eventMillis = currentTime;
-                eventLength = random.nextFloat()*2000+4000;
+                eventLength = random.nextFloat() * 2000 + 4000;
                 eventRandom = random.nextFloat();
             }
         } else if(shaderName.equals("biscuit_cape") || shaderName.equals("shiny_cape")) {
             long currentTime = System.currentTimeMillis();
-            if(currentTime-startTime > eventMillis-startTime+eventLength) {
+            if(currentTime - startTime > eventMillis - startTime + eventLength) {
                 eventMillis = currentTime;
-                eventLength = random.nextFloat()*3000+3000;
+                eventLength = random.nextFloat() * 3000 + 3000;
             }
         }
 
         double playerAngle = getPlayerRenderAngle(player, 0);
         double deltaAngle = playerAngle - oldPlayerAngle;
         if(deltaAngle > Math.PI) {
-            deltaAngle = 2*Math.PI - deltaAngle;
+            deltaAngle = 2 * Math.PI - deltaAngle;
         }
         if(deltaAngle < -Math.PI) {
-            deltaAngle = 2*Math.PI + deltaAngle;
+            deltaAngle = 2 * Math.PI + deltaAngle;
         }
         deltaAngleAccum *= 0.5f;
         deltaAngleAccum += deltaAngle;
 
-        float dX = (float)Math.cos(playerAngle+Math.PI/2f);
-        float dZ = (float)Math.sin(playerAngle+Math.PI/2f);
+        float dX = (float) Math.cos(playerAngle + Math.PI / 2f);
+        float dZ = (float) Math.sin(playerAngle + Math.PI / 2f);
 
-        float factor = (float)(deltaAngleAccum*deltaAngleAccum);
+        float factor = (float) (deltaAngleAccum * deltaAngleAccum);
 
         float capeTransLength = capeTranslation.length();
 
         float capeTranslationFactor = 0f;
         if(capeTransLength > 0.5f) {
-            capeTranslationFactor = (capeTransLength-0.5f)/capeTransLength;
+            capeTranslationFactor = (capeTransLength - 0.5f) / capeTransLength;
         }
         Vector3f lookDir = new Vector3f(dX, 0, dZ);
         Vector3f lookDirNorm = lookDir.normalise(null);
         float dot = Vector3f.dot(capeTranslation, lookDirNorm);
         if(dot < 0) { //Moving backwards
-            for(int y=0; y<nodes.size(); y++) {
-                for(int x=0; x<nodes.get(y).size(); x++) {
-                    CapeNode node = nodes.get(y).get(x);
+            for(int y = 0; y < VERT_NODES; y++) {
+                for(int x = 0; x < HORZ_NODES; x++) {
+                    CapeNode node = nodes[x + y * HORZ_NODES];
                     if(!node.fixed) {
-                        node.position.x += lookDirNorm.x*dot;
-                        node.position.y += lookDirNorm.y*dot;
-                        node.position.z += lookDirNorm.z*dot;
+                        node.position.x += lookDirNorm.x * dot;
+                        node.position.y += lookDirNorm.y * dot;
+                        node.position.z += lookDirNorm.z * dot;
                     }
                 }
             }
@@ -595,21 +601,21 @@ public class NEUCape {
         }
 
         if(factor > 0) {
-            for(int y=0; y<nodes.size(); y++) {
-                for(int x=0; x<nodes.get(y).size(); x++) {
-                    nodes.get(y).get(x).applyForce(-dX*factor, 0, -dZ*factor);
+            for(int y = 0; y < VERT_NODES; y++) {
+                for(int x = 0; x < HORZ_NODES; x++) {
+                    nodes[x + y * HORZ_NODES].applyForce(-dX * factor, 0, -dZ * factor);
                 }
             }
         }
 
         if(capeTranslationFactor > 0f) {
-            float capeDX = capeTranslation.x*capeTranslationFactor;
-            float capeDY = capeTranslation.y*capeTranslationFactor;
-            float capeDZ = capeTranslation.z*capeTranslationFactor;
+            float capeDX = capeTranslation.x * capeTranslationFactor;
+            float capeDY = capeTranslation.y * capeTranslationFactor;
+            float capeDZ = capeTranslation.z * capeTranslationFactor;
 
-            for(int y=0; y<nodes.size(); y++) {
-                for(int x=0; x<nodes.get(y).size(); x++) {
-                    CapeNode node = nodes.get(y).get(x);
+            for(int y = 0; y < VERT_NODES; y++) {
+                for(int x = 0; x < HORZ_NODES; x++) {
+                    CapeNode node = nodes[x + y * HORZ_NODES];
                     if(!node.fixed) {
                         node.position.x += capeDX;
                         node.position.y += capeDY;
@@ -620,15 +626,15 @@ public class NEUCape {
         }
 
         //Wind
-        float currTime = (System.currentTimeMillis()-startTime)/1000f;
-        float windRandom = Math.abs((float)(0.5f*Math.sin(0.22f*currTime)+Math.sin(0.44f*currTime)*Math.sin(0.47f*currTime)));
-        double windDir = playerAngle+Math.PI/4f*Math.sin(0.2f*currTime);
-
-        float windDX = (float)Math.cos(windDir+Math.PI/2f);
-        float windDZ = (float)Math.sin(windDir+Math.PI/2f);
-        for(int y=0; y<nodes.size(); y++) {
-            for(int x=0; x<nodes.get(y).size(); x++) {
-                nodes.get(y).get(x).applyForce(-windDX*windRandom*0.01f, 0, -windDZ*windRandom*0.01f);
+        float currTime = (System.currentTimeMillis() - startTime) / 1000f;
+        float windRandom = Math.abs((float) (0.5f * Math.sin(0.22f * currTime) + Math.sin(0.44f * currTime) * Math.sin(0.47f * currTime)));
+        double windDir = playerAngle + Math.PI / 4f * Math.sin(0.2f * currTime);
+
+        float windDX = (float) Math.cos(windDir + Math.PI / 2f);
+        float windDZ = (float) Math.sin(windDir + Math.PI / 2f);
+        for(int y = 0; y < VERT_NODES; y++) {
+            for(int x = 0; x < HORZ_NODES; x++) {
+                nodes[x + y * HORZ_NODES].applyForce(-windDX * windRandom * 0.01f, 0, -windDZ * windRandom * 0.01f);
             }
         }
 
@@ -638,9 +644,9 @@ public class NEUCape {
             if(crouchTicks < 5) {
                 mult = 2f;
             }
-            for(int y=0; y<8; y++) {
-                for(int x=0; x<nodes.get(y).size(); x++) {
-                    nodes.get(y).get(x).applyForce(-dX*mult, 0, -dZ*mult);
+            for(int y = 0; y < 8; y++) {
+                for(int x = 0; x < HORZ_NODES; x++) {
+                    nodes[x + y * HORZ_NODES].applyForce(-dX * mult, 0, -dZ * mult);
                 }
             }
         } else {
@@ -648,13 +654,13 @@ public class NEUCape {
         }
 
         Vector3f avgPosition = avgFixedPosition();
-        for(int y=0; y<nodes.size(); y++) {
-            for(int x=0; x<nodes.get(y).size(); x++) {
-                CapeNode node = nodes.get(y).get(x);
+        for(int y = 0; y < VERT_NODES; y++) {
+            for(int x = 0; x < HORZ_NODES; x++) {
+                CapeNode node = nodes[x + y * HORZ_NODES];
 
                 Vector3f delta = Vector3f.sub(node.position, avgPosition, null);
 
-                if(delta.lengthSquared() > 5*5) {
+                if(delta.lengthSquared() > 5 * 5) {
                     Vector3f norm = delta.normalise(null);
                     node.position = Vector3f.add(avgPosition, norm, null);
                 }
@@ -663,32 +669,33 @@ public class NEUCape {
 
         oldPlayerAngle = playerAngle;
 
-        for(int y=0; y<nodes.size(); y++) {
-            for(int x=0; x<nodes.get(y).size(); x++) {
-                nodes.get(y).get(x).update();
+        for(int y = 0; y < VERT_NODES; y++) {
+            for(int x = 0; x < HORZ_NODES; x++) {
+                nodes[x + y * HORZ_NODES].update();
             }
         }
         int updates = player == Minecraft.getMinecraft().thePlayer ? 50 : 50;
-        for(int i=0; i<updates; i++) {
-            for(int y=0; y<nodes.size(); y++) {
-                for(int x=0; x<nodes.get(y).size(); x++) {
-                    nodes.get(y).get(x).resolveAll(2+1f*y/nodes.size(), false);
+        for(int i = 0; i < updates; i++) {
+            for(int y = 0; y < VERT_NODES; y++) {
+                for(int x = 0; x < HORZ_NODES; x++) {
+                    nodes[x + y * HORZ_NODES].resolveAll(2 + 1f * y / VERT_NODES, false);
                 }
             }
         }
     }
 
     private int ssbo = -1;
+
     private void generateSSBO() {
         ssbo = GL15.glGenBuffers();
         loadSBBO();
     }
 
     private void loadSBBO() {
-        FloatBuffer buff = BufferUtils.createFloatBuffer(CapeNode.FLOAT_NUM*HORZ_NODES*VERT_NODES);
-        for(int y=0; y<VERT_NODES; y++) {
-            for(int x=0; x<HORZ_NODES; x++) {
-                nodes.get(y).get(x).loadIntoBuffer(buff);
+        FloatBuffer buff = BufferUtils.createFloatBuffer(CapeNode.FLOAT_NUM * HORZ_NODES * VERT_NODES);
+        for(int y = 0; y < VERT_NODES; y++) {
+            for(int x = 0; x < HORZ_NODES; x++) {
+                nodes[x + y * HORZ_NODES].loadIntoBuffer(buff);
             }
         }
         buff.flip();
@@ -714,32 +721,64 @@ public class NEUCape {
 
         GL20.glUseProgram(program);
 
-        for(int i=0; i<30; i++) {
+        for(int i = 0; i < 30; i++) {
             GL43.glDispatchCompute(VERT_NODES, 1, 1);
             GL42.glMemoryBarrier(GL43.GL_SHADER_STORAGE_BARRIER_BIT);
         }
 
         GL20.glUseProgram(0);
 
-        FloatBuffer buff = BufferUtils.createFloatBuffer(CapeNode.FLOAT_NUM*HORZ_NODES*VERT_NODES);
+        FloatBuffer buff = BufferUtils.createFloatBuffer(CapeNode.FLOAT_NUM * HORZ_NODES * VERT_NODES);
 
         GL15.glBindBuffer(GL43.GL_SHADER_STORAGE_BUFFER, ssbo);
         GL15.glGetBufferSubData(GL43.GL_SHADER_STORAGE_BUFFER, 0, buff);
         GL15.glBindBuffer(GL43.GL_SHADER_STORAGE_BUFFER, 0);
 
-        for(int y=0; y<VERT_NODES; y++) {
-            for(int x=0; x<HORZ_NODES; x++) {
-                nodes.get(y).get(x).readFromBuffer(buff);
+        for(int y = 0; y < VERT_NODES; y++) {
+            for(int x = 0; x < HORZ_NODES; x++) {
+                nodes[x + y * HORZ_NODES].readFromBuffer(buff);
+            }
+        }
+    }
+
+    private Vector3f avgRenderPosition() {
+        Vector3f accum = new Vector3f();
+        int num = 0;
+        for(int y = 0; y < VERT_NODES; y++) {
+            for(int x = 0; x < HORZ_NODES; x++) {
+                CapeNode node = nodes[x + y * HORZ_NODES];
+                Vector3f.add(accum, node.renderPosition, accum);
+                num++;
+            }
+        }
+        if(num != 0) {
+            accum.scale(1f / num);
+        }
+        return accum;
+    }
+
+    private Vector3f avgNormal() {
+        Vector3f accum = new Vector3f();
+        int num = 0;
+        for(int y = 0; y < VERT_NODES; y++) {
+            for(int x = 0; x < HORZ_NODES; x++) {
+                CapeNode node = nodes[x + y * HORZ_NODES];
+                Vector3f.add(accum, node.normal(), accum);
+                num++;
             }
         }
+        if(num != 0) {
+            accum.scale(1f / num);
+        }
+        return accum;
     }
 
     private Vector3f avgFixedRenderPosition() {
         Vector3f accum = new Vector3f();
         int numFixed = 0;
-        for(int y=0; y<nodes.size(); y++) {
-            for(int x=0; x<nodes.get(y).size(); x++) {
-                CapeNode node = nodes.get(y).get(x);
+        for(int y = 0; y < VERT_NODES; y++) {
+            for(int x = 0; x < HORZ_NODES; x++) {
+                CapeNode node = nodes[x + y * HORZ_NODES];
                 if(node.fixed) {
                     Vector3f.add(accum, node.renderPosition, accum);
                     numFixed++;
@@ -747,7 +786,7 @@ public class NEUCape {
             }
         }
         if(numFixed != 0) {
-            accum.scale(1f/numFixed);
+            accum.scale(1f / numFixed);
         }
         return accum;
     }
@@ -755,9 +794,9 @@ public class NEUCape {
     private Vector3f avgFixedPosition() {
         Vector3f accum = new Vector3f();
         int numFixed = 0;
-        for(int y=0; y<nodes.size(); y++) {
-            for(int x=0; x<nodes.get(y).size(); x++) {
-                CapeNode node = nodes.get(y).get(x);
+        for(int y = 0; y < VERT_NODES; y++) {
+            for(int x = 0; x < HORZ_NODES; x++) {
+                CapeNode node = nodes[x + y * HORZ_NODES];
                 if(node.fixed) {
                     Vector3f.add(accum, node.position, accum);
                     numFixed++;
@@ -765,11 +804,429 @@ public class NEUCape {
             }
         }
         if(numFixed != 0) {
-            accum.scale(1f/numFixed);
+            accum.scale(1f / numFixed);
         }
         return accum;
     }
 
+    private void renderBackAndDoFrontStencil() {
+        for(int y = 0; y < VERT_NODES; y++) {
+            for(int x = 0; x < HORZ_NODES; x++) {
+                nodes[x + y * HORZ_NODES].renderNode(CapeNode.DRAW_MASK_BACK | CapeNode.DRAW_MASK_SIDES);
+            }
+        }
+
+        if(!Minecraft.getMinecraft().getFramebuffer().isStencilEnabled())
+            Minecraft.getMinecraft().getFramebuffer().enableStencil();
+
+        GL11.glEnable(GL11.GL_STENCIL_TEST);
+        GL11.glStencilFunc(GL11.GL_ALWAYS, 1, 0xFF);
+        GL11.glStencilOp(GL11.GL_ZERO, GL11.GL_ZERO, GL11.GL_REPLACE);
+        GL11.glStencilMask(0xFF);
+        GL11.glClear(GL11.GL_STENCIL_BUFFER_BIT);
+        GlStateManager.enableDepth();
+
+        GL11.glColorMask(false, false, false, false);
+        for(int y = 0; y < VERT_NODES; y++) {
+            for(int x = 0; x < HORZ_NODES; x++) {
+                nodes[x + y * HORZ_NODES].renderNode(CapeNode.DRAW_MASK_FRONT);
+            }
+        }
+        GL11.glColorMask(true, true, true, true);
+
+        // Only pass stencil test if equal to 1
+        GL11.glStencilMask(0x00);
+        GL11.glStencilFunc(GL11.GL_EQUAL, 1, 0xFF);
+    }
+
+    private Vector3f getPoint(Vector3f point, Vector3f... vectors) {
+        Vector3f res = new Vector3f(point);
+        for(Vector3f vec : vectors) Vector3f.add(res, vec, res);
+        return res;
+    }
+
+    private static void renderVBO(WorldRenderer worldRenderer) {
+        if(worldRenderer != null && worldRenderer.getVertexCount() > 0) {
+            VertexFormat vertexformat = worldRenderer.getVertexFormat();
+            int stride = vertexformat.getNextOffset();
+            ByteBuffer bytebuffer = worldRenderer.getByteBuffer();
+            List<VertexFormatElement> list = vertexformat.getElements();
+
+            for(int index = 0; index < list.size(); index++) {
+                VertexFormatElement vertexformatelement = list.get(index);
+                vertexformatelement.getUsage().preDraw(vertexformat, index, stride, bytebuffer);
+            }
+
+            GL11.glDrawArrays(worldRenderer.getDrawMode(), 0, worldRenderer.getVertexCount());
+
+            for(int index = 0; index < list.size(); index++) {
+                VertexFormatElement vertexformatelement = list.get(index);
+                vertexformatelement.getUsage().postDraw(vertexformat, index, stride, bytebuffer);
+            }
+        }
+    }
+
+    private static WorldRenderer sphereVBO = null;
+
+    private void renderNodes() {
+        if(capeName.equalsIgnoreCase("planets")) {
+            renderBackAndDoFrontStencil();
+
+            Vector3f pointNorm = avgNormal();
+            Vector3f capeAvgPos = avgRenderPosition();
+
+            pointNorm.scale(0.5f / pointNorm.length());
+            pointNorm.scale(1 - pointNorm.y / 1.3f);
+            Vector3f point = Vector3f.sub(capeAvgPos, pointNorm, null);
+
+            if(sphereVBO == null || Keyboard.isKeyDown(Keyboard.KEY_K)) {
+                if(sphereVBO != null) sphereVBO.reset();
+
+                int arcSegments = 24;
+                int rotationSegments = 24;
+                double arcAngleDelta = Math.PI / (arcSegments - 1);
+
+                float xScale = 0.95f;
+
+                double diameterUnitArcLen = 0;
+
+                double arcAngle = 0;
+                for(int i = 0; i < arcSegments; i++) {
+                    diameterUnitArcLen += Math.sin(arcAngle);
+                    arcAngle += arcAngleDelta;
+                }
+                double arcLength = 2f / diameterUnitArcLen;
+
+                List<List<Vector3f>> arcs = new ArrayList<>();
+                for(int angleI = 0; angleI < rotationSegments; angleI++) {
+                    double angle = Math.PI * 2 * angleI / rotationSegments;
+
+                    List<Vector3f> arc = new ArrayList<>();
+
+                    Vector3f arcPos = new Vector3f(0, 0, -1);
+
+                    arc.add(arcPos);
+
+                    arcAngle = 0;
+                    for(int segmentI = 0; segmentI < arcSegments; segmentI++) {
+
+                        double deltaZ = Math.sin(arcAngle) * arcLength;
+                        double deltaY = Math.cos(arcAngle) * Math.cos(angle) * arcLength;
+                        double deltaX = Math.cos(arcAngle) * Math.sin(angle) * arcLength * xScale;
+
+                        arcPos = new Vector3f(arcPos);
+                        arcPos.z += deltaZ;
+                        arcPos.y += deltaY;
+                        arcPos.x += deltaX;
+                        arcPos.normalise();
+                        arc.add(arcPos);
+
+                        arcAngle += arcAngleDelta;
+                    }
+
+                    arcs.add(arc);
+                }
+
+                sphereVBO = new WorldRenderer(8 * 4 * rotationSegments * arcSegments);
+                sphereVBO.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_NORMAL);
+
+                double maxXYRad = 0;
+                for(int angleI = 0; angleI < rotationSegments; angleI++) {
+                    for(int segmentI = 0; segmentI <= arcSegments; segmentI++) {
+                        List<Vector3f> thisArc = arcs.get(angleI);
+                        Vector3f point1 = thisArc.get(segmentI);
+                        double rad = Math.sqrt(point1.x * point1.x + point1.y * point1.y);
+                        maxXYRad = Math.max(maxXYRad, rad);
+                    }
+                }
+
+                for(int angleI = 0; angleI < rotationSegments; angleI++) {
+
+                    int nextAngleI = angleI + 1;
+                    if(angleI == rotationSegments - 1) {
+                        nextAngleI = 0;
+                    }
+
+                    float v = 0.5f * (angleI) / (rotationSegments);
+                    float v2 = 0.5f * (angleI + 1) / (rotationSegments);
+                    //if(v2 == 0) v2 = 0.5f;
+
+                    List<Vector3f> thisArc = arcs.get(angleI);
+                    List<Vector3f> nextArc = arcs.get(nextAngleI);
+
+                    for(int segmentI = 1; segmentI <= arcSegments; segmentI++) {
+                        Vector3f point1 = thisArc.get(segmentI);
+                        Vector3f point2 = thisArc.get(segmentI - 1);
+                        Vector3f point3 = nextArc.get(segmentI - 1);
+                        Vector3f point4 = nextArc.get(segmentI);
+
+                        double u1 = 0.5f * segmentI / arcSegments;
+                        double u2 = 0.5f * (segmentI - 1) / arcSegments;
+
+                        sphereVBO.pos(point4.x, point4.y, point4.z)
+                                .tex(u1, v2).normal(-point4.x, -point4.y, -point4.z).endVertex();
+                        sphereVBO.pos(point3.x, point3.y, point3.z)
+                                .tex(u2, v2).normal(-point3.x, -point3.y, -point3.z).endVertex();
+                        sphereVBO.pos(point2.x, point2.y, point2.z)
+                                .tex(u2, v).normal(-point2.x, -point2.y, -point2.z).endVertex();
+                        sphereVBO.pos(point1.x, point1.y, point1.z)
+                                .tex(u1, v).normal(-point1.x, -point1.y, -point1.z).endVertex();
+                    }
+                }
+            }
+
+            String shaderId = "capes/" + shaderName + "/" + shaderName;
+            double mercuryAngle = Math.PI * 2 * ((System.currentTimeMillis() - startTime) / 10000f % 1);
+            double mercuryX = Math.sin(mercuryAngle) * 0.3;
+            double mercuryZ = Math.cos(mercuryAngle) * 0.3;
+
+            double earthAngle = Math.PI * 2 * ((System.currentTimeMillis() - startTime) / 30000f % 1);
+            double earthSlant = Math.PI * 0.1;
+            double earthX = Math.sin(earthAngle) * Math.cos(earthSlant) * 0.6;
+            double earthY = Math.sin(earthAngle) * Math.sin(earthSlant) * 0.6;
+            double earthZ = Math.cos(earthAngle) * Math.cos(earthSlant) * 0.6;
+
+            float sunDist = Vector3f.sub(point, capeAvgPos, null).lengthSquared();
+            float mercuryDist = Vector3f.sub(new Vector3f(point.x + (float) mercuryX, point.y, point.z + (float) mercuryZ),
+                    capeAvgPos, null).lengthSquared();
+            float earthDist = Vector3f.sub(new Vector3f(point.x + (float) earthX, point.y + (float) earthY, point.z + (float) earthZ),
+                    capeAvgPos, null).lengthSquared();
+
+            double jupiterAngle = Math.PI * 2 * ((System.currentTimeMillis() - startTime) / 200000f % 1);
+            double jupiterSlant = Math.PI * -0.08;
+            double jupiterX = Math.sin(jupiterAngle) * Math.cos(jupiterSlant) * 1.5;
+            double jupiterY = Math.sin(jupiterAngle) * Math.sin(jupiterSlant) * 1.5;
+            double jupiterZ = Math.cos(jupiterAngle) * Math.cos(jupiterSlant) * 1.5;
+            float jupiterDist = Vector3f.sub(new Vector3f(point.x + (float) jupiterX, point.y + (float) jupiterY, point.z + (float) jupiterZ),
+                    capeAvgPos, null).lengthSquared();
+
+            double neptuneX = -Math.sin(earthAngle) * Math.cos(earthSlant);
+            double neptuneY = -Math.sin(earthAngle) * Math.sin(earthSlant);
+            double neptuneZ = -Math.cos(earthAngle) * Math.cos(earthSlant);
+
+            float neptuneDist = Vector3f.sub(new Vector3f(point.x + (float) neptuneX, point.y + (float) neptuneY, point.z + (float) neptuneZ),
+                    capeAvgPos, null).lengthSquared();
+
+            TreeMap<Float, Integer> orbitals = new TreeMap<>();
+            orbitals.put(sunDist, 0);
+            orbitals.put(earthDist, 1);
+            orbitals.put(mercuryDist, 2);
+
+            double delta = Minecraft.getMinecraft().getRenderViewEntity().getRotationYawHead() % 360;
+            while(delta < 0) delta += 360;
+
+            double jupDelta = (delta + Math.toDegrees(jupiterAngle)) % 360;
+            while(jupDelta < 0) jupDelta += 360;
+            if(jupDelta > 250 || jupDelta < 110) orbitals.put(jupiterDist, 3);
+
+            double nepDelta = (delta + Math.toDegrees(-earthAngle)) % 360;
+            while(nepDelta < 0) nepDelta += 360;
+            if(nepDelta > 250 || nepDelta < 110) orbitals.put(neptuneDist, 4);
+
+            GlStateManager.disableDepth();
+            GlStateManager.enableCull();
+
+            for(int planetId : orbitals.descendingMap().values()) {
+                GlStateManager.pushMatrix();
+                switch(planetId) {
+                    case 0: {
+                        GlStateManager.translate(point.x, point.y, point.z);
+                        GlStateManager.scale(0.2f, 0.2f, 0.2f);
+                        break;
+                    }
+                    case 1: {
+                        Vector3f sunVec = new Vector3f((float) earthX,  (float) earthY, (float) earthZ);
+                        ShaderManager.getInstance().loadData(shaderId, "sunVec", sunVec);
+                        GlStateManager.translate(point.x + earthX, point.y + earthY, point.z + earthZ);
+                        GlStateManager.scale(0.1f, 0.1f, 0.1f);
+                        break;
+                    }
+                    case 2: {
+                        Vector3f sunVec = new Vector3f((float) mercuryX, 0, (float) mercuryZ);
+                        ShaderManager.getInstance().loadData(shaderId, "sunVec", sunVec);
+                        GlStateManager.translate(point.x + mercuryX, point.y, point.z + mercuryZ);
+                        GlStateManager.scale(0.05f, 0.05f, 0.05f);
+                        break;
+                    }
+                    case 3: {
+                        Vector3f sunVec = new Vector3f((float) jupiterX, (float) jupiterY, (float) jupiterZ);
+                        ShaderManager.getInstance().loadData(shaderId, "sunVec", sunVec);
+                        GlStateManager.translate(point.x + jupiterX, point.y + jupiterY, point.z + jupiterZ);
+                        GlStateManager.scale(0.3f, 0.3f, 0.3f);
+                        break;
+                    }
+                    case 4: {
+                        Vector3f sunVec = new Vector3f((float) neptuneX, (float) neptuneY, (float) neptuneZ);
+                        ShaderManager.getInstance().loadData(shaderId, "sunVec", sunVec);
+                        GlStateManager.translate(point.x + neptuneX, point.y + neptuneY, point.z + neptuneZ);
+                        GlStateManager.scale(0.15f, 0.15f, 0.15f);
+                        break;
+                    }
+                }
+                ShaderManager.getInstance().loadData(shaderId, "planetType", planetId);
+                renderVBO(sphereVBO);
+                GlStateManager.popMatrix();
+            }
+
+
+            GlStateManager.disableCull();
+            GlStateManager.enableDepth();
+
+            GL11.glDisable(GL11.GL_STENCIL_TEST);
+        } else if(capeName.equalsIgnoreCase("parallax")) {
+            renderBackAndDoFrontStencil();
+
+            Vector3f pointNorm = avgNormal();
+            pointNorm.scale(-0.2f / pointNorm.length());
+            Vector3f negPointNorm = new Vector3f(pointNorm);
+            negPointNorm.scale(-1);
+            //pointNorm.scale(1 - pointNorm.y/1.3f);
+            Vector3f point = Vector3f.add(avgRenderPosition(), pointNorm, null);
+            Vector3f fixedPoint = Vector3f.add(avgFixedRenderPosition(), pointNorm, null);
+
+            Vector3f up = Vector3f.sub(fixedPoint, point, null);
+            float halfUp = up.length();
+
+            Vector3f down = new Vector3f(up);
+            down.scale(-1);
+
+            Vector3f left = Vector3f.cross(up, pointNorm, null);
+            left.scale(halfUp * 522f / 341f / left.length());
+            Vector3f right = new Vector3f(left);
+            right.scale(-1);
+
+            Vector3f point1 = getPoint(point, left);
+            Vector3f point2 = getPoint(point, left, down, down);
+            Vector3f point3 = getPoint(point, right, down, down);
+            Vector3f point4 = getPoint(point, right);
+
+            Vector3f point2Edge = getPoint(point2, negPointNorm, negPointNorm);
+            Vector3f point3Edge = getPoint(point3, negPointNorm, negPointNorm);
+
+            GlStateManager.disableDepth();
+            GlStateManager.disableCull();
+
+            GlStateManager.color(1, 1, 1, 1);
+
+            Tessellator tessellator = Tessellator.getInstance();
+            WorldRenderer worldrenderer = tessellator.getWorldRenderer();
+            worldrenderer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
+
+            worldrenderer.pos(point1.x, point1.y, point1.z)
+                    .tex(0, 943 / 1024f).endVertex();
+            worldrenderer.pos(point2.x, point2.y, point2.z)
+                    .tex(280 / 1024f, 943 / 1024f).endVertex();
+            worldrenderer.pos(point3.x, point3.y, point3.z)
+                    .tex(280 / 1024f, 421 / 1024f).endVertex();
+            worldrenderer.pos(point4.x, point4.y, point4.z)
+                    .tex(0, 421 / 1024f).endVertex();
+
+            tessellator.draw();
+
+            worldrenderer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);
+
+            worldrenderer.pos(point2.x, point2.y, point2.z)
+                    .tex(280 / 1024f, 943 / 1024f).endVertex();
+            worldrenderer.pos(point2Edge.x, point2Edge.y, point2Edge.z)
+                    .tex(341 / 1024f, 943 / 1024f).endVertex();
+            worldrenderer.pos(point3Edge.x, point3Edge.y, point3Edge.z)
+                    .tex(341 / 1024f, 421 / 1024f).endVertex();
+            worldrenderer.pos(point3.x, point3.y, point3.z)
+                    .tex(280 / 1024f, 421 / 1024f).endVertex();
+
+            tessellator.draw();
+
+
+            GlStateManager.disableCull();
+            GlStateManager.enableDepth();
+
+            GL11.glDisable(GL11.GL_STENCIL_TEST);
+        } else if(capeName.equalsIgnoreCase("tunnel")) {
+            renderBackAndDoFrontStencil();
+
+            Vector3f pointNorm = avgNormal();
+
+            pointNorm.scale(0.7f / pointNorm.length());
+            pointNorm.scale(1 - pointNorm.y / 1.3f);
+            Vector3f point = Vector3f.sub(avgRenderPosition(), pointNorm, null);
+
+            List<CapeNode> edgeNodes = new ArrayList<>();
+            List<Vector2f> edgeCoords = new ArrayList<>();
+
+            //Left edge
+            for(int y = 0; y < VERT_NODES; y++) {
+                edgeNodes.add(nodes[y * HORZ_NODES]);
+                edgeCoords.add(new Vector2f(0, (float) y / (VERT_NODES - 1)));
+            }
+            edgeNodes.add(null);
+            edgeCoords.add(null);
+            //Bottom edge
+            int bottomIndex = VERT_NODES - 1;
+            int botSize = HORZ_NODES;
+            for(int x = 0; x < botSize; x++) {
+                edgeNodes.add(getNode(x, bottomIndex));
+                edgeCoords.add(new Vector2f((float) x / (botSize - 1), 1));
+            }
+            edgeNodes.add(null);
+            edgeCoords.add(null);
+            //Right edge
+            for(int y = VERT_NODES - 1; y >= 0; y--) {
+                edgeNodes.add(getNode(HORZ_NODES - 1, y));
+                edgeCoords.add(new Vector2f(1, (float) y / VERT_NODES));
+            }
+            edgeNodes.add(null);
+            edgeCoords.add(null);
+            //Top edge
+            int topSize = HORZ_NODES;
+            for(int x = topSize - 1; x >= 0; x--) {
+                edgeNodes.add(getNode(x, 0));
+                edgeCoords.add(new Vector2f((float) x / (topSize - 1), 0));
+            }
+
+            GlStateManager.disableDepth();
+            GlStateManager.enableCull();
+            CapeNode last = null;
+            for(int i = 0; i < edgeNodes.size(); i++) {
+                CapeNode node = edgeNodes.get(i);
+                if(last != null && node != null) {
+                    Vector2f lastCoord = edgeCoords.get(i - 1);
+                    Vector2f coord = edgeCoords.get(i);
+
+                    Tessellator tessellator = Tessellator.getInstance();
+                    WorldRenderer worldrenderer = tessellator.getWorldRenderer();
+                    worldrenderer.begin(GL11.GL_TRIANGLES, DefaultVertexFormats.POSITION_TEX_NORMAL);
+
+                    Vector3f lastNodeNorm = last.normal();
+                    worldrenderer.pos(last.renderPosition.x + lastNodeNorm.x * 0.05f, last.renderPosition.y + lastNodeNorm.y * 0.05f, last.renderPosition.z + lastNodeNorm.z * 0.05f)
+                            .tex(lastCoord.x * 300f / 1024f, lastCoord.y * 420f / 1024f)
+                            .normal(-lastNodeNorm.x, -lastNodeNorm.y, -lastNodeNorm.z).endVertex();
+
+                    Vector3f nodeNorm = node.normal();
+                    worldrenderer.pos(node.renderPosition.x + nodeNorm.x * 0.05f, node.renderPosition.y + nodeNorm.y * 0.05f, node.renderPosition.z + nodeNorm.z * 0.05f)
+                            .tex(coord.x * 300f / 1024f, coord.y * 420f / 1024f)
+                            .normal(-nodeNorm.x, -nodeNorm.y, -nodeNorm.z).endVertex();
+
+                    worldrenderer.pos(point.x, point.y, point.z)
+                            .tex(150f / 1024f, 210f / 1024f)
+                            .normal(-pointNorm.x, -pointNorm.y, -pointNorm.z).endVertex();
+
+                    tessellator.draw();
+                }
+                last = node;
+            }
+            GlStateManager.disableCull();
+            GlStateManager.enableDepth();
+
+            GL11.glDisable(GL11.GL_STENCIL_TEST);
+        } else {
+            for(int y = 0; y < VERT_NODES; y++) {
+                for(int x = 0; x < HORZ_NODES; x++) {
+                    nodes[x + y * HORZ_NODES].renderNode();
+                }
+            }
+        }
+    }
+
     private void renderCape(EntityPlayer player, float partialRenderTick) {
         ensureCapeNodesCreated(player);
 
@@ -781,9 +1238,9 @@ public class NEUCape {
         if(delta.lengthSquared() > 9) {
             updateFixedCapeNodes(player);
 
-            for(int y=0; y<nodes.size(); y++) {
-                for(int x=0; x<nodes.get(y).size(); x++) {
-                    CapeNode node = nodes.get(y).get(x);
+            for(int y = 0; y < VERT_NODES; y++) {
+                for(int x = 0; x < HORZ_NODES; x++) {
+                    CapeNode node = nodes[x + y * HORZ_NODES];
                     if(!node.fixed) {
                         Vector3f.add(node.renderPosition, delta, node.renderPosition);
                         node.position.set(node.renderPosition);
@@ -794,17 +1251,13 @@ public class NEUCape {
                 }
             }
 
-            for(int y=0; y<nodes.size(); y++) {
-                for(int x=0; x<nodes.get(y).size(); x++) {
-                    nodes.get(y).get(x).renderNode();
-                }
-            }
+            renderNodes();
             return;
         }
 
-        for(int y=0; y<nodes.size(); y++) {
-            for(int x=0; x<nodes.get(y).size(); x++) {
-                CapeNode node = nodes.get(y).get(x);
+        for(int y = 0; y < VERT_NODES; y++) {
+            for(int x = 0; x < HORZ_NODES; x++) {
+                CapeNode node = nodes[x + y * HORZ_NODES];
 
                 node.resetNormal();
 
@@ -821,31 +1274,31 @@ public class NEUCape {
                 if(fps < 50) {
                     length = 2;
                 } else if(fps < 100) {
-                    length = 2 + (int)((fps-50)/50f*3);
+                    length = 2 + (int) ((fps - 50) / 50f * 3);
                 }
 
-                if(node.oldRenderPosition[length-1] == null) {
+                if(node.oldRenderPosition[length - 1] == null) {
                     Arrays.fill(node.oldRenderPosition, Vector3f.sub(newPosition, avgPositionFixed, null));
                     node.renderPosition = newPosition;
                 } else {
                     Vector3f accum = new Vector3f();
-                    for(int i=0; i<length; i++) {
+                    for(int i = 0; i < length; i++) {
                         Vector3f.add(accum, node.oldRenderPosition[i], accum);
                         Vector3f.add(accum, avgPositionFixed, accum);
                     }
-                    accum.scale(1/(float)(length));
+                    accum.scale(1 / (float) (length));
 
-                    float blendFactor = 0.5f+0.3f*y/(float)(nodes.size()-1); //0.5/0.5 -> 0.8/0.2 //0-1
+                    float blendFactor = 0.5f + 0.3f * y / (float) (VERT_NODES - 1); //0.5/0.5 -> 0.8/0.2 //0-1
                     accum.scale(blendFactor);
-                    newPosition.scale(1-blendFactor);
+                    newPosition.scale(1 - blendFactor);
                     Vector3f.add(accum, newPosition, accum);
                     node.renderPosition = accum;
                 }
 
                 if(!Minecraft.getMinecraft().isGamePaused()) {
-                    for(int i=node.oldRenderPosition.length-1; i>=0; i--) {
+                    for(int i = node.oldRenderPosition.length - 1; i >= 0; i--) {
                         if(i > 0) {
-                            node.oldRenderPosition[i] = node.oldRenderPosition[i-1];
+                            node.oldRenderPosition[i] = node.oldRenderPosition[i - 1];
                         } else {
                             node.oldRenderPosition[i] = Vector3f.sub(node.renderPosition, avgPositionFixed, null);
                         }
@@ -853,11 +1306,7 @@ public class NEUCape {
                 }
             }
         }
-        for(int y=0; y<nodes.size(); y++) {
-            for(int x=0; x<nodes.get(y).size(); x++) {
-                nodes.get(y).get(x).renderNode();
-            }
-        }
+        renderNodes();
     }
 
 }
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/infopanes/HTMLInfoPane.java b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/HTMLInfoPane.java
index 9981e6f9..df7069d4 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/infopanes/HTMLInfoPane.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/HTMLInfoPane.java
@@ -19,11 +19,15 @@ import net.minecraft.client.renderer.GlStateManager;
 import net.minecraft.client.renderer.texture.DynamicTexture;
 import net.minecraft.util.EnumChatFormatting;
 import net.minecraft.util.ResourceLocation;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.SystemUtils;
 
 import javax.imageio.ImageIO;
 import java.awt.*;
 import java.awt.image.BufferedImage;
 import java.io.*;
+import java.net.URL;
+import java.net.URLConnection;
 import java.nio.charset.CharsetEncoder;
 import java.nio.charset.StandardCharsets;
 import java.util.concurrent.ExecutorService;
@@ -43,6 +47,8 @@ public class HTMLInfoPane extends TextInfoPane {
     private int imageHeight = 0;
     private int imageWidth = 0;
 
+    private static boolean hasAttemptedDownload = false;
+
     /**
      * Creates a wiki model and sets the configuration to work with hypixel-skyblock wikia.
      */
@@ -140,6 +146,9 @@ public class HTMLInfoPane extends TextInfoPane {
         return str.replace(" ", "\\ ");
     }
 
+    private static final ExecutorService wkDownloadES = Executors.newSingleThreadExecutor();
+    private static final ExecutorService rendererES = Executors.newCachedThreadPool();
+
     /**
      * Uses the wkhtmltoimage command-line tool to generate an image from the HTML code. This
      * generation is done asynchronously as sometimes it can take up to 10 seconds for more
@@ -149,8 +158,68 @@ public class HTMLInfoPane extends TextInfoPane {
         super(overlay, manager, name, "");
         this.title = name;
 
+        String osId;
+        if(SystemUtils.IS_OS_WINDOWS) {
+            osId = "win";
+        } else if(SystemUtils.IS_OS_MAC) {
+            osId = "mac";
+        } else if(SystemUtils.IS_OS_LINUX) {
+            osId = "linux";
+        } else {
+            text = EnumChatFormatting.RED+"Unsupported operating system.";
+            return;
+        }
+
         File cssFile = new File(manager.configLocation, "wikia.css");
-        File wkHtmlToImage = new File(manager.configLocation, "wkhtmltox/bin/wkhtmltoimage");
+        File wkHtmlToImage = new File(manager.configLocation, "wkhtmltox-"+osId+"/bin/wkhtmltoimage");
+
+        //Use old binary folder
+        if(new File(manager.configLocation, "wkhtmltox/bin/wkhtmltoimage").exists() && SystemUtils.IS_OS_WINDOWS) {
+            wkHtmlToImage = new File(manager.configLocation, "wkhtmltox/bin/wkhtmltoimage");
+        }
+
+        Runtime runtime = Runtime.getRuntime();
+        String[] chmodCommand = new String[]{ "chmod", "-R", "777", new File(manager.configLocation, "wkhtmltox-"+osId).getAbsolutePath() };
+        try {
+            Process p = runtime.exec(chmodCommand);
+            p.waitFor();
+        } catch(IOException | InterruptedException e) {}
+
+        if(!wkHtmlToImage.exists()) {
+            if(hasAttemptedDownload) {
+                text = EnumChatFormatting.RED+"Downloading web renderer failed? Or still downloading? Not sure what to do";
+                return;
+            } else {
+                hasAttemptedDownload = true;
+                Utils.recursiveDelete(new File(manager.configLocation, "wkhtmltox-"+osId));
+                wkDownloadES.submit(() -> {
+                    try {
+                        File itemsZip = new File(manager.configLocation, "wkhtmltox-"+osId+".zip");
+                        if(!itemsZip.exists()) {
+                            URL url = new URL("https://moulberry.codes/wkhtmltox/wkhtmltox-"+osId+".zip");
+                            URLConnection urlConnection = url.openConnection();
+                            urlConnection.setConnectTimeout(15000);
+                            urlConnection.setReadTimeout(60000);
+
+                            FileUtils.copyInputStreamToFile(urlConnection.getInputStream(), itemsZip);
+                        }
+
+                        try(InputStream is = new FileInputStream(itemsZip)) {
+                            manager.unzip(is, manager.configLocation);
+                        }
+
+                        itemsZip.delete();
+                        itemsZip.deleteOnExit();
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    }
+                });
+
+                text = EnumChatFormatting.YELLOW+"Downloading web renderer... try again soon";
+                return;
+            }
+        }
+
         File input = new File(manager.configLocation, "tmp/input.html");
         String outputFileName = filename.replaceAll("(?i)\\u00A7.", "")
                 .replaceAll("[^a-zA-Z0-9_\\-]", "_");
@@ -179,7 +248,7 @@ public class HTMLInfoPane extends TextInfoPane {
         } else {
             html = "<div id=\"mw-content-text\" lang=\"en\" dir=\"ltr\" class=\"mw-content-ltr mw-content-text\">"+html+"</div>";
             html = "<div id=\"WikiaArticle\" class=\"WikiaArticle\">"+html+"</div>";
-            html = "<link rel=\"stylesheet\" href=\"file:///"+cssFile.getAbsolutePath()+"\">\n"+html;
+            html = "<link rel=\"stylesheet\" href=\"file:///"+cssFile.getAbsolutePath().replaceAll("^/", "")+"\">\n"+html;
 
             try(PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(
                     new FileOutputStream(input), StandardCharsets.UTF_8)), false)) {
@@ -187,16 +256,12 @@ public class HTMLInfoPane extends TextInfoPane {
                 out.println(encodeNonAscii(html));
             } catch(IOException e) {}
 
-
-            ExecutorService ste = Executors.newSingleThreadExecutor();
             try {
                 text = EnumChatFormatting.GRAY+"Rendering webpage (" + name + EnumChatFormatting.RESET+
                         EnumChatFormatting.GRAY+"), please wait...";
 
-                Runtime runtime = Runtime.getRuntime();
-
                 String[] wkCommand = new String[]{ wkHtmlToImage.getAbsolutePath(), "--width", ""+IMAGE_WIDTH*ZOOM_FACTOR,
-                        "--transparent", "--zoom", ""+ZOOM_FACTOR, input.getAbsolutePath(), output.getAbsolutePath()};
+                        "--transparent", "--allow", manager.configLocation.getAbsolutePath(), "--zoom", ""+ZOOM_FACTOR, input.getAbsolutePath(), output.getAbsolutePath()};
                 Process p = runtime.exec(wkCommand);
                 /*Process p = runtime.exec(spaceEscape(wkHtmlToImage.getAbsolutePath()) + " --width "+
                         IMAGE_WIDTH*ZOOM_FACTOR+" --transparent --zoom "+ZOOM_FACTOR + " " + spaceEscape(input.getAbsolutePath()) +
@@ -207,7 +272,7 @@ public class HTMLInfoPane extends TextInfoPane {
                 /*Process p2 = runtime.exec("\""+wkHtmlToImage.getAbsolutePath() + "\" --width "+
                         (IMAGE_WIDTH+EXT_WIDTH)*ZOOM_FACTOR+" --transparent --zoom "+ZOOM_FACTOR+" \"" + input.getAbsolutePath() +
                         "\" \"" + outputExt.getAbsolutePath() + "\"");*/
-                ste.submit(() -> {
+                rendererES.submit(() -> {
                     try {
                         if(p.waitFor(15, TimeUnit.SECONDS)) {
                             //if(p2.waitFor(5, TimeUnit.SECONDS)) {
@@ -260,8 +325,6 @@ public class HTMLInfoPane extends TextInfoPane {
             } catch(IOException e) {
                 e.printStackTrace();
                 text = EnumChatFormatting.RED+"Failed to exec webpage renderer.";
-            } finally {
-                ste.shutdown();
             }
         }
     }
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CustomItemEffects.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CustomItemEffects.java
index 7e073bbd..20915503 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CustomItemEffects.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/CustomItemEffects.java
@@ -10,6 +10,7 @@ import net.minecraft.block.BlockPackedIce;
 import net.minecraft.block.material.Material;
 import net.minecraft.block.state.IBlockState;
 import net.minecraft.client.Minecraft;
+import net.minecraft.client.audio.PositionedSoundRecord;
 import net.minecraft.client.entity.EntityPlayerSP;
 import net.minecraft.client.gui.ScaledResolution;
 import net.minecraft.client.renderer.*;
@@ -408,6 +409,9 @@ public class CustomItemEffects {
         cropBlocksZapper.add(Blocks.melon_stem);
         cropBlocksZapper.add(Blocks.cactus);
         cropBlocksZapper.add(Blocks.reeds);
+        cropBlocksZapper.add(Blocks.nether_wart);
+        cropBlocksZapper.add(Blocks.tallgrass);
+        cropBlocksZapper.add(Blocks.double_plant);
 
         otherBannedBlocksZapper.add(Blocks.farmland);
     }
@@ -436,6 +440,7 @@ public class CustomItemEffects {
                     zapperDirty = false;
 
                     zapperBlocks.clear();
+
                     LinkedList<BlockPos> returnablePositions = new LinkedList<>();
 
                     BlockPos pos = event.target.getBlockPos();
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/EnchantingSolvers.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/EnchantingSolvers.java
index eed1ec60..26e87ae9 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/EnchantingSolvers.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/EnchantingSolvers.java
@@ -311,8 +311,7 @@ public class EnchantingSolvers {
 
                         if(chronomatronReplayIndex < chronomatronOrder.size()) {
                             String chronomatronCurrent = chronomatronOrder.get(chronomatronReplayIndex);
-                            if(!NotEnoughUpdates.INSTANCE.config.enchantingSolvers.preventMisclicks ||
-                                    chronomatronCurrent.equals(displayName)) {
+                            if(true) {
                                 chronomatronReplayIndex++;
                                 Minecraft.getMinecraft().playerController.windowClick(windowId, slotId,
                                         2, mode, Minecraft.getMinecraft().thePlayer);
@@ -334,9 +333,7 @@ public class EnchantingSolvers {
                             return true;
                         }
                         long currentTime = System.currentTimeMillis();
-                        if(currentTime - millisLastClick > 150 &&
-                                (!NotEnoughUpdates.INSTANCE.config.enchantingSolvers.preventMisclicks ||
-                                current.containerIndex == slotId)) {
+                        if(currentTime - millisLastClick > 150) {
                             ultrasequencerReplayIndex++;
                             Minecraft.getMinecraft().playerController.windowClick(windowId, slotId,
                                     2, mode, Minecraft.getMinecraft().thePlayer);
@@ -354,7 +351,9 @@ public class EnchantingSolvers {
         return false;
     }
 
-    public static void processInventoryContents() {
+    public static void processInventoryContents(boolean fromTick) {
+        if(currentSolver != SolverType.CHRONOMATRON && !fromTick) return;
+
         if(!NotEnoughUpdates.INSTANCE.config.enchantingSolvers.enableEnchantingSolvers) {
             return;
         }
@@ -536,7 +535,7 @@ public class EnchantingSolvers {
             return;
         }
 
-        processInventoryContents();
+        processInventoryContents(true);
     }
 
 }
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/MiningStuff.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/MiningStuff.java
index d01a173f..6f706b5c 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/MiningStuff.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/MiningStuff.java
@@ -104,26 +104,6 @@ public class MiningStuff {
         }
     }
 
-    public static void tick() {
-        if(SBInfo.getInstance().getLocation() == null) return;
-        if(!SBInfo.getInstance().getLocation().equals("mining_3")) return;
-        if(Minecraft.getMinecraft().theWorld == null) return;
-
-        for(Entity entity : Minecraft.getMinecraft().theWorld.loadedEntityList) {
-            if(entity instanceof EntityCreeper) {
-                EntityCreeper creeper = (EntityCreeper) entity;
-                if(creeper.isInvisible() && creeper.getPowered()) {
-
-                    BlockPos below = creeper.getPosition().down();
-                    IBlockState state = Minecraft.getMinecraft().theWorld.getBlockState(below);
-                    if(state != null && state.getBlock() == Blocks.stained_glass) {
-                        creeper.setInvisible(!NotEnoughUpdates.INSTANCE.config.mining.revealMistCreepers);
-                    }
-                }
-            }
-        }
-    }
-
     @SubscribeEvent
     public void renderWorldLast(RenderWorldLastEvent event) {
         if(overlayLoc == null) return;
@@ -186,19 +166,4 @@ public class MiningStuff {
         }
     }
 
-    public static boolean blockClicked(BlockPos loc) {
-        if(loc.equals(overlayLoc)) {
-            overlayLoc = null;
-        }
-        IBlockState state = Minecraft.getMinecraft().theWorld.getBlockState(loc);
-        if(NotEnoughUpdates.INSTANCE.config.mining.dontMineStone &&
-                state != null && SBInfo.getInstance().getLocation() != null &&
-                SBInfo.getInstance().getLocation().startsWith("mining_") &&
-                (state.getBlock() == Blocks.stone && state.getValue(BlockStone.VARIANT) == BlockStone.EnumType.STONE ||
-                        state.getBlock() == Blocks.cobblestone)) {
-            return true;
-        }
-        return false;
-    }
-
 }
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/NullzeeSphere.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/NullzeeSphere.java
new file mode 100644
index 00000000..7e297c58
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/NullzeeSphere.java
@@ -0,0 +1,234 @@
+package io.github.moulberry.notenoughupdates.miscfeatures;
+
+import io.github.moulberry.notenoughupdates.util.ReverseWorldRenderer;
+import io.github.moulberry.notenoughupdates.util.SpecialColour;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.entity.EntityPlayerSP;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
+import net.minecraft.client.renderer.vertex.VertexFormat;
+import net.minecraft.client.renderer.vertex.VertexFormatElement;
+import net.minecraft.entity.Entity;
+import net.minecraft.util.AxisAlignedBB;
+import net.minecraft.util.BlockPos;
+import net.minecraftforge.client.event.RenderWorldLastEvent;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+import net.minecraftforge.fml.common.gameevent.TickEvent;
+import org.lwjgl.opengl.GL11;
+
+import java.awt.*;
+import java.nio.ByteBuffer;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class NullzeeSphere {
+
+    public static boolean enabled = false;
+    public static float size = 20;
+    public static BlockPos centerPos = new BlockPos(0, 0, 0);
+
+    public static ReverseWorldRenderer overlayVBO = null;
+
+    public ReverseWorldRenderer getOverlayVBO() {
+        if(overlayVBO != null) return overlayVBO;
+
+        EntityPlayerSP p = Minecraft.getMinecraft().thePlayer;
+        if(p == null) return null;
+
+        //per vertex = 6
+        //per size = 4
+        //per block = 8
+        //total per block = 196
+
+        Set<BlockPos> circleOffsets = getCircleOffsets(size);
+
+        ReverseWorldRenderer worldRenderer = new ReverseWorldRenderer(196*circleOffsets.size());
+        worldRenderer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_COLOR);
+
+        String col = SpecialColour.special(0, 180, 0xffff9900);
+        for(BlockPos offset : circleOffsets) {
+            BlockPos overlayPos = new BlockPos(offset.getX(), offset.getY(), offset.getZ());
+
+            AxisAlignedBB bb = new AxisAlignedBB(
+                    overlayPos.getX(),
+                    overlayPos.getY(),
+                    overlayPos.getZ(),
+                    overlayPos.getX()+1,
+                    overlayPos.getY()+1,
+                    overlayPos.getZ()+1
+            ).expand(0.001f, 0.001f, 0.001f);
+            uploadFilledBoundingBox(bb, 1f, col, worldRenderer);
+        }
+
+        overlayVBO = worldRenderer;
+        return overlayVBO;
+    }
+
+    public Set<BlockPos> getCircleOffsets(float radius) {
+        Set<BlockPos> circleOffsets = new HashSet<>();
+
+        int radiusI = (int)Math.ceil(radius)+1;
+        for(int x=-radiusI; x<=radiusI; x++) {
+            for(int y=-radiusI; y<=radiusI; y++) {
+                for(int z=-radiusI; z<=radiusI; z++) {
+                    float distSq = x*x + y*y + z*z;
+                    if(distSq >= (radius-0.5)*(radius-0.5) && distSq <= (radius+0.5)*(radius+0.5)) {
+                        circleOffsets.add(new BlockPos(x, y, z));
+                    }
+                }
+            }
+        }
+
+        return circleOffsets;
+    }
+
+    long lastUpdate = 0;
+
+    private static double posLastUpdateX;
+    private static double posLastUpdateY;
+    private static double posLastUpdateZ;
+
+    @SubscribeEvent
+    public void onTick(TickEvent.ClientTickEvent event) {
+        if(!enabled) return;
+
+        EntityPlayerSP p = Minecraft.getMinecraft().thePlayer;
+        if(p == null) return;
+
+        if(event.phase == TickEvent.Phase.START) {
+            double dX = p.posX - posLastUpdateX;
+            double dY = p.posY - posLastUpdateY;
+            double dZ = p.posZ - posLastUpdateZ;
+
+            if(dX*dX + dY*dY + dZ*dZ < 1) {
+                return;
+            }
+
+            posLastUpdateX = p.posX;
+            posLastUpdateY = p.posY;
+            posLastUpdateZ = p.posZ;
+
+            long currentTime = System.currentTimeMillis();
+            if(currentTime - lastUpdate < 250) {
+                return;
+            }
+            lastUpdate = currentTime;
+
+            ReverseWorldRenderer worldRenderer = getOverlayVBO();
+            if(worldRenderer != null) {
+                worldRenderer.setTranslation(0, 0,0 );
+                worldRenderer.sortVertexData(
+                        (float)p.posX-centerPos.getX(),
+                        (float)p.posY-centerPos.getY(),
+                        (float)p.posZ-centerPos.getZ());
+
+            }
+        }
+    }
+
+    @SubscribeEvent
+    public void onRenderLast(RenderWorldLastEvent event) {
+        if(!enabled) return;
+
+        Entity viewer = Minecraft.getMinecraft().getRenderViewEntity();
+        double viewerX = viewer.lastTickPosX + (viewer.posX - viewer.lastTickPosX) * event.partialTicks;
+        double viewerY = viewer.lastTickPosY + (viewer.posY - viewer.lastTickPosY) * event.partialTicks;
+        double viewerZ = viewer.lastTickPosZ + (viewer.posZ - viewer.lastTickPosZ) * event.partialTicks;
+
+        GlStateManager.enableBlend();
+        GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0);
+        GlStateManager.disableTexture2D();
+
+        GlStateManager.translate(-viewerX, -viewerY, -viewerZ);
+
+        GL11.glPolygonOffset(5, 5);
+        ReverseWorldRenderer worldRenderer = getOverlayVBO();
+        if(worldRenderer != null && worldRenderer.getVertexCount() > 0) {
+            GlStateManager.translate(centerPos.getX(), centerPos.getY(), centerPos.getZ());
+
+            VertexFormat vertexformat = worldRenderer.getVertexFormat();
+            int stride = vertexformat.getNextOffset();
+            ByteBuffer bytebuffer = worldRenderer.getByteBuffer();
+            List<VertexFormatElement> list = vertexformat.getElements();
+
+            for (int index = 0; index < list.size(); index++) {
+                VertexFormatElement vertexformatelement = list.get(index);
+                vertexformatelement.getUsage().preDraw(vertexformat, index, stride, bytebuffer);
+            }
+
+            GL11.glDrawArrays(worldRenderer.getDrawMode(), 0, worldRenderer.getVertexCount());
+
+            for (int index = 0; index < list.size(); index++) {
+                VertexFormatElement vertexformatelement = list.get(index);
+                vertexformatelement.getUsage().postDraw(vertexformat, index, stride, bytebuffer);
+            }
+
+            GlStateManager.translate(-centerPos.getX(), -centerPos.getY(), -centerPos.getZ());
+        }
+        GL11.glPolygonOffset(0, 0);
+
+        GlStateManager.translate(viewerX, viewerY, viewerZ);
+
+        GlStateManager.enableTexture2D();
+    }
+
+    public static void uploadFilledBoundingBox(AxisAlignedBB p_181561_0_, float alpha, String special, ReverseWorldRenderer worldrenderer) {
+        Color c = new Color(SpecialColour.specialToChromaRGB(special), true);
+
+        //vertical
+        worldrenderer.pos(p_181561_0_.minX, p_181561_0_.minY, p_181561_0_.minZ)
+                .color(c.getRed()/255f, c.getGreen()/255f, c.getBlue()/255f, c.getAlpha()/255f*alpha).endVertex();
+        worldrenderer.pos(p_181561_0_.maxX, p_181561_0_.minY, p_181561_0_.minZ)
+                .color(c.getRed()/255f, c.getGreen()/255f, c.getBlue()/255f, c.getAlpha()/255f*alpha).endVertex();
+        worldrenderer.pos(p_181561_0_.maxX, p_181561_0_.minY, p_181561_0_.maxZ)
+                .color(c.getRed()/255f, c.getGreen()/255f, c.getBlue()/255f, c.getAlpha()/255f*alpha).endVertex();
+        worldrenderer.pos(p_181561_0_.minX, p_181561_0_.minY, p_181561_0_.maxZ)
+                .color(c.getRed()/255f, c.getGreen()/255f, c.getBlue()/255f, c.getAlpha()/255f*alpha).endVertex();
+        worldrenderer.pos(p_181561_0_.minX, p_181561_0_.maxY, p_181561_0_.maxZ)
+                .color(c.getRed()/255f, c.getGreen()/255f, c.getBlue()/255f, c.getAlpha()/255f*alpha).endVertex();
+        worldrenderer.pos(p_181561_0_.maxX, p_181561_0_.maxY, p_181561_0_.maxZ)
+                .color(c.getRed()/255f, c.getGreen()/255f, c.getBlue()/255f, c.getAlpha()/255f*alpha).endVertex();
+        worldrenderer.pos(p_181561_0_.maxX, p_181561_0_.maxY, p_181561_0_.minZ)
+                .color(c.getRed()/255f, c.getGreen()/255f, c.getBlue()/255f, c.getAlpha()/255f*alpha).endVertex();
+        worldrenderer.pos(p_181561_0_.minX, p_181561_0_.maxY, p_181561_0_.minZ)
+                .color(c.getRed()/255f, c.getGreen()/255f, c.getBlue()/255f, c.getAlpha()/255f*alpha).endVertex();
+
+        //x
+        worldrenderer.pos(p_181561_0_.minX, p_181561_0_.minY, p_181561_0_.maxZ)
+                .color(c.getRed()/255f*0.8f, c.getGreen()/255f*0.8f, c.getBlue()/255f*0.8f, c.getAlpha()/255f*alpha).endVertex();
+        worldrenderer.pos(p_181561_0_.minX, p_181561_0_.maxY, p_181561_0_.maxZ)
+                .color(c.getRed()/255f*0.8f, c.getGreen()/255f*0.8f, c.getBlue()/255f*0.8f, c.getAlpha()/255f*alpha).endVertex();
+        worldrenderer.pos(p_181561_0_.minX, p_181561_0_.maxY, p_181561_0_.minZ)
+                .color(c.getRed()/255f*0.8f, c.getGreen()/255f*0.8f, c.getBlue()/255f*0.8f, c.getAlpha()/255f*alpha).endVertex();
+        worldrenderer.pos(p_181561_0_.minX, p_181561_0_.minY, p_181561_0_.minZ)
+                .color(c.getRed()/255f*0.8f, c.getGreen()/255f*0.8f, c.getBlue()/255f*0.8f, c.getAlpha()/255f*alpha).endVertex();
+        worldrenderer.pos(p_181561_0_.maxX, p_181561_0_.minY, p_181561_0_.minZ)
+                .color(c.getRed()/255f*0.8f, c.getGreen()/255f*0.8f, c.getBlue()/255f*0.8f, c.getAlpha()/255f*alpha).endVertex();
+        worldrenderer.pos(p_181561_0_.maxX, p_181561_0_.maxY, p_181561_0_.minZ)
+                .color(c.getRed()/255f*0.8f, c.getGreen()/255f*0.8f, c.getBlue()/255f*0.8f, c.getAlpha()/255f*alpha).endVertex();
+        worldrenderer.pos(p_181561_0_.maxX, p_181561_0_.maxY, p_181561_0_.maxZ)
+                .color(c.getRed()/255f*0.8f, c.getGreen()/255f*0.8f, c.getBlue()/255f*0.8f, c.getAlpha()/255f*alpha).endVertex();
+        worldrenderer.pos(p_181561_0_.maxX, p_181561_0_.minY, p_181561_0_.maxZ)
+                .color(c.getRed()/255f*0.8f, c.getGreen()/255f*0.8f, c.getBlue()/255f*0.8f, c.getAlpha()/255f*alpha).endVertex();
+
+        //z
+        worldrenderer.pos(p_181561_0_.minX, p_181561_0_.maxY, p_181561_0_.minZ)
+                .color(c.getRed()/255f*0.9f, c.getGreen()/255f*0.9f, c.getBlue()/255f*0.9f, c.getAlpha()/255f*alpha).endVertex();
+        worldrenderer.pos(p_181561_0_.maxX, p_181561_0_.maxY, p_181561_0_.minZ)
+                .color(c.getRed()/255f*0.9f, c.getGreen()/255f*0.9f, c.getBlue()/255f*0.9f, c.getAlpha()/255f*alpha).endVertex();
+        worldrenderer.pos(p_181561_0_.maxX, p_181561_0_.minY, p_181561_0_.minZ)
+                .color(c.getRed()/255f*0.9f, c.getGreen()/255f*0.9f, c.getBlue()/255f*0.9f, c.getAlpha()/255f*alpha).endVertex();
+        worldrenderer.pos(p_181561_0_.minX, p_181561_0_.minY, p_181561_0_.minZ)
+                .color(c.getRed()/255f*0.9f, c.getGreen()/255f*0.9f, c.getBlue()/255f*0.9f, c.getAlpha()/255f*alpha).endVertex();
+        worldrenderer.pos(p_181561_0_.minX, p_181561_0_.minY, p_181561_0_.maxZ)
+                .color(c.getRed()/255f*0.9f, c.getGreen()/255f*0.9f, c.getBlue()/255f*0.9f, c.getAlpha()/255f*alpha).endVertex();
+        worldrenderer.pos(p_181561_0_.maxX, p_181561_0_.minY, p_181561_0_.maxZ)
+                .color(c.getRed()/255f*0.9f, c.getGreen()/255f*0.9f, c.getBlue()/255f*0.9f, c.getAlpha()/255f*alpha).endVertex();
+        worldrenderer.pos(p_181561_0_.maxX, p_181561_0_.maxY, p_181561_0_.maxZ)
+                .color(c.getRed()/255f*0.9f, c.getGreen()/255f*0.9f, c.getBlue()/255f*0.9f, c.getAlpha()/255f*alpha).endVertex();
+        worldrenderer.pos(p_181561_0_.minX, p_181561_0_.maxY, p_181561_0_.maxZ)
+                .color(c.getRed()/255f*0.9f, c.getGreen()/255f*0.9f, c.getBlue()/255f*0.9f, c.getAlpha()/255f*alpha).endVertex();
+    }
+
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/PetInfoOverlay.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/PetInfoOverlay.java
index 09ae3071..f759ba7d 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/PetInfoOverlay.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/PetInfoOverlay.java
@@ -48,7 +48,7 @@ public class PetInfoOverlay extends TextOverlay {
     private static final Pattern XP_BOOST_PATTERN = Pattern.compile("PET_ITEM_(COMBAT|FISHING|MINING|FORAGING|ALL|FARMING)_(SKILL|SKILLS)_BOOST_(COMMON|UNCOMMON|RARE|EPIC)");
     private static final Pattern PET_CONTAINER_PAGE = Pattern.compile("\\((\\d)/(\\d)\\) Pets");
     private static final Pattern PET_NAME_PATTERN = Pattern.compile("\u00a77\\[Lvl \\d+] \u00a7(.+)");
-    private static final Pattern XP_LINE_PATTERN = Pattern.compile("-------------------- (\\d+(?:,\\d+)*(?:\\.\\d+)?)/(\\d+(?:\\.\\d+)?[b|m|k]?)");
+    private static final Pattern XP_LINE_PATTERN = Pattern.compile("-------------------- (\\d+(?:,\\d+)*(?:\\.\\d+)?)/(\\d+(?:\\.\\d+)?[B|M|k]?)");
 
     public PetInfoOverlay(Position position, Supplier<List<String>> dummyStrings, Supplier<TextOverlayStyle> styleSupplier) {
         super(position, dummyStrings, styleSupplier);
@@ -95,6 +95,7 @@ public class PetInfoOverlay extends TextOverlay {
 
     private static HashMap<Integer, Pet> petMap = new HashMap<>();
     private static int selectedPet = -1;
+    private static int selectedPet2 = -1;
     private static long lastPetSelect = -1;
     //public static Pet currentPet = null;
     //public static HashMap<String, Set<Pet>> petList = new HashMap<>();
@@ -103,23 +104,40 @@ public class PetInfoOverlay extends TextOverlay {
     public static float beastMultiplier = 0;
     public static boolean setActivePet = false;
 
-    private long lastUpdate = 0;
-    private float levelXpLast = 0;
+    private static long lastUpdate = 0;
+    private static float levelXpLast = 0;
 
-    private LinkedList<Float> xpGainQueue = new LinkedList<>();
-    private float xpGainHourLast = -1;
-    private float xpGainHour = -1;
+    private static LinkedList<Float> xpGainQueue = new LinkedList<>();
+    private static float xpGainHourLast = -1;
+    private static float xpGainHour = -1;
+
+    private static float xpGainHourSecondPet = -1;
 
     private int xpAddTimer = 0;
 
     public static void clearPet() {
         selectedPet = -1;
+        selectedPet2 = -1;
+    }
+
+    public static void setCurrentPet(int index) {
+        selectedPet2 = selectedPet;
+        xpGainHourSecondPet = xpGainHour;
+        xpGainHourLast = xpGainHour;
+        xpGainQueue.clear();
+        selectedPet = index;
     }
 
     public static Pet getCurrentPet() {
         return petMap.get(selectedPet);
     }
 
+    public static Pet getCurrentPet2() {
+        if(!NotEnoughUpdates.INSTANCE.config.petOverlay.dualPets) return null;
+        if(selectedPet == selectedPet2) return null;
+        return petMap.get(selectedPet2);
+    }
+
     public float getLevelPercent() {
         DecimalFormat df = new DecimalFormat("#.#", DecimalFormatSymbols.getInstance(Locale.ENGLISH));
         Pet pet = getCurrentPet();
@@ -306,57 +324,59 @@ public class PetInfoOverlay extends TextOverlay {
         return interp;
     }
 
-    @Override
-    public void updateFrequent() {
-        Pet currentPet = getCurrentPet();
-        if(!NotEnoughUpdates.INSTANCE.config.petOverlay.enablePetInfo || currentPet == null) {
-            overlayStrings = null;
+    private List<String> createStringsForPet(Pet currentPet, boolean secondPet) {
+        float levelXp = currentPet.petLevel.levelXp;
+        if(!secondPet) levelXp = interp(currentPet.petLevel.levelXp, levelXpLast);
+        if(levelXp < 0) levelXp = 0;
+
+        String petName = EnumChatFormatting.GREEN + "[Lvl " + (int) currentPet.petLevel.level + "] " + currentPet.rarity.chatFormatting +
+                WordUtils.capitalizeFully(currentPet.petType.replace("_", " "));
+
+        String lvlStringShort = EnumChatFormatting.AQUA + "" + roundFloat(levelXp) + "/" +
+                roundFloat(currentPet.petLevel.currentLevelRequirement)
+                + EnumChatFormatting.YELLOW + " (" + getLevelPercent() + "%)";
+
+        String lvlString = EnumChatFormatting.AQUA + "" + Utils.shortNumberFormat(levelXp, 0) + "/" +
+                Utils.shortNumberFormat(currentPet.petLevel.currentLevelRequirement, 0)
+                + EnumChatFormatting.YELLOW + " (" + getLevelPercent() + "%)";
+
+        float xpGain;
+        if(!secondPet) {
+            xpGain = interp(xpGainHour, xpGainHourLast);
         } else {
-            float levelXp = interp(currentPet.petLevel.levelXp, levelXpLast);
-            if(levelXp < 0) levelXp = 0;
-
-            String petName = EnumChatFormatting.GREEN + "[Lvl " + (int) currentPet.petLevel.level + "] " + currentPet.rarity.chatFormatting +
-                    WordUtils.capitalizeFully(currentPet.petType.replace("_", " "));
-
-            String lvlStringShort = EnumChatFormatting.AQUA + "" + roundFloat(levelXp) + "/" +
-                    roundFloat(currentPet.petLevel.currentLevelRequirement)
-                    + EnumChatFormatting.YELLOW + " (" + getLevelPercent() + "%)";
-
-            String lvlString = EnumChatFormatting.AQUA + "" + Utils.shortNumberFormat(levelXp, 0) + "/" +
-                    Utils.shortNumberFormat(currentPet.petLevel.currentLevelRequirement, 0)
-                    + EnumChatFormatting.YELLOW + " (" + getLevelPercent() + "%)";
-
-            float xpGain = interp(xpGainHour, xpGainHourLast);
-            if(xpGain < 0) xpGain = 0;
-            String xpGainString = EnumChatFormatting.AQUA + "XP/h: " +
-                    EnumChatFormatting.YELLOW + roundFloat(xpGain);
-            if(xpGain > 0 && xpGainHour == xpGainHourLast) xpGainString += EnumChatFormatting.RED + " (PAUSED)";
-
-            String totalXpString = EnumChatFormatting.AQUA + "Total XP: " + EnumChatFormatting.YELLOW + roundFloat(currentPet.petLevel.totalXp);
-
-            String petItemStr = EnumChatFormatting.AQUA+ "Held Item: " + EnumChatFormatting.RED + "None";
-            if(currentPet.petItem != null) {
-                JsonObject json = NotEnoughUpdates.INSTANCE.manager.getItemInformation().get(currentPet.petItem);
-                if(json != null) {
-                    String name = NotEnoughUpdates.INSTANCE.manager.jsonToStack(json).getDisplayName();
-                    petItemStr = EnumChatFormatting.AQUA + "Held Item: " + name;
-                }
+            xpGain = xpGainHourSecondPet;
+        }
+        if(xpGain < 0) xpGain = 0;
+        String xpGainString = EnumChatFormatting.AQUA + "XP/h: " +
+                EnumChatFormatting.YELLOW + roundFloat(xpGain);
+        if(xpGain > 0 && xpGainHour == xpGainHourLast) xpGainString += EnumChatFormatting.RED + " (PAUSED)";
+
+        String totalXpString = EnumChatFormatting.AQUA + "Total XP: " + EnumChatFormatting.YELLOW + roundFloat(currentPet.petLevel.totalXp);
+
+        String petItemStr = EnumChatFormatting.AQUA+ "Held Item: " + EnumChatFormatting.RED + "None";
+        if(currentPet.petItem != null) {
+            JsonObject json = NotEnoughUpdates.INSTANCE.manager.getItemInformation().get(currentPet.petItem);
+            if(json != null) {
+                String name = NotEnoughUpdates.INSTANCE.manager.jsonToStack(json).getDisplayName();
+                petItemStr = EnumChatFormatting.AQUA + "Held Item: " + name;
             }
+        }
 
-            String etaStr = null;
-            String etaMaxStr = null;
-            if(currentPet.petLevel.level < 100) {
-                float remaining = currentPet.petLevel.currentLevelRequirement - currentPet.petLevel.levelXp;
-                if(remaining > 0) {
-                    if(xpGain < 1000) {
-                        etaStr = EnumChatFormatting.AQUA+"Until L"+(int)(currentPet.petLevel.level+1)+": " +
-                                EnumChatFormatting.YELLOW+"N/A";
-                    } else {
-                        etaStr = EnumChatFormatting.AQUA+"Until L"+(int)(currentPet.petLevel.level+1)+": " +
-                                EnumChatFormatting.YELLOW + Utils.prettyTime((long)(remaining)*1000*60*60/(long)xpGain);
-                    }
+        String etaStr = null;
+        String etaMaxStr = null;
+        if(currentPet.petLevel.level < 100) {
+            float remaining = currentPet.petLevel.currentLevelRequirement - currentPet.petLevel.levelXp;
+            if(remaining > 0) {
+                if(xpGain < 1000) {
+                    etaStr = EnumChatFormatting.AQUA+"Until L"+(int)(currentPet.petLevel.level+1)+": " +
+                            EnumChatFormatting.YELLOW+"N/A";
+                } else {
+                    etaStr = EnumChatFormatting.AQUA+"Until L"+(int)(currentPet.petLevel.level+1)+": " +
+                            EnumChatFormatting.YELLOW + Utils.prettyTime((long)(remaining)*1000*60*60/(long)xpGain);
                 }
+            }
 
+            if(currentPet.petLevel.level < 99 || !NotEnoughUpdates.INSTANCE.config.petOverlay.petOverlayText.contains(6)) {
                 float remainingMax = currentPet.petLevel.maxXP - currentPet.petLevel.totalXp;
                 if(remaining > 0) {
                     if(xpGain < 1000) {
@@ -368,28 +388,48 @@ public class PetInfoOverlay extends TextOverlay {
                     }
                 }
             }
+        }
+
+        List<String> strings = new ArrayList<>();
+
+        for(int index : NotEnoughUpdates.INSTANCE.config.petOverlay.petOverlayText) {
+            switch(index) {
+                case 0:
+                    strings.add(petName); break;
+                case 1:
+                    strings.add(lvlStringShort); break;
+                case 2:
+                    strings.add(lvlString); break;
+                case 3:
+                    strings.add(xpGainString); break;
+                case 4:
+                    strings.add(totalXpString); break;
+                case 5:
+                    strings.add(petItemStr); break;
+                case 6:
+                    if(etaStr != null) strings.add(etaStr); break;
+                case 7:
+                    if(etaMaxStr != null) strings.add(etaMaxStr); break;
+            }
+        }
 
+        return strings;
+    }
+
+    @Override
+    public void updateFrequent() {
+        Pet currentPet = getCurrentPet();
+        if(!NotEnoughUpdates.INSTANCE.config.petOverlay.enablePetInfo || currentPet == null) {
+            overlayStrings = null;
+        } else {
             overlayStrings = new ArrayList<>();
 
-            for(int index : NotEnoughUpdates.INSTANCE.config.petOverlay.petOverlayText) {
-                switch(index) {
-                    case 0:
-                        overlayStrings.add(petName); break;
-                    case 1:
-                        overlayStrings.add(lvlStringShort); break;
-                    case 2:
-                        overlayStrings.add(lvlString); break;
-                    case 3:
-                        overlayStrings.add(xpGainString); break;
-                    case 4:
-                        overlayStrings.add(totalXpString); break;
-                    case 5:
-                        overlayStrings.add(petItemStr); break;
-                    case 6:
-                        if(etaStr != null) overlayStrings.add(etaStr); break;
-                    case 7:
-                        if(etaMaxStr != null) overlayStrings.add(etaMaxStr); break;
-                }
+            overlayStrings.addAll(createStringsForPet(currentPet, false));
+
+            Pet currentPet2 = getCurrentPet2();
+            if(currentPet2 != null) {
+                overlayStrings.add("");
+                overlayStrings.addAll(createStringsForPet(currentPet2, true));
             }
 
         }
@@ -403,18 +443,8 @@ public class PetInfoOverlay extends TextOverlay {
 
         NEUConfig config = NotEnoughUpdates.INSTANCE.config;
         int updateTime = 60000;
-        if((config.itemOverlays.enableMonkeyCheck) && !config.petOverlay.enablePetInfo)
-            updateTime = 300000;
 
         if(NotEnoughUpdates.INSTANCE.hasSkyblockScoreboard()) {
-            /*if(petList.isEmpty()) {
-                ProfileViewer.Profile profile = NotEnoughUpdates.profileViewer.getProfileRaw(Minecraft.getMinecraft().thePlayer
-                        .getUniqueID().toString().replace("-", ""));
-                if(profile != null) {
-                    getAndSetPet(profile);
-                }
-            }*/
-
             ProfileApiSyncer.getInstance().requestResync("petinfo", updateTime, () -> {
             }, PetInfoOverlay::getAndSetPet);
         }
@@ -429,6 +459,32 @@ public class PetInfoOverlay extends TextOverlay {
         }
     }
 
+    private static GuiProfileViewer.PetLevel getMaxLevel(JsonArray levels, int offset) {
+        float xpTotal = 0;
+        float level = 1;
+        float currentLevelRequirement = 0;
+
+        for(int i=offset; i<offset+99; i++) {
+            currentLevelRequirement = levels.get(i).getAsFloat();
+            xpTotal += currentLevelRequirement;
+            level += 1;
+        }
+
+        if(level <= 0) {
+            level = 1;
+        } else if(level > 100) {
+            level = 100;
+        }
+        GuiProfileViewer.PetLevel levelObj = new GuiProfileViewer.PetLevel();
+        levelObj.level = level;
+        levelObj.currentLevelRequirement = currentLevelRequirement;
+        levelObj.maxXP = xpTotal;
+        levelObj.levelPercentage = 1;
+        levelObj.levelXp = currentLevelRequirement-5;
+        levelObj.totalXp = xpTotal-5;
+        return levelObj;
+    }
+
     private static GuiProfileViewer.PetLevel getLevel(JsonArray levels, int offset, float xpThisLevel, int xpMaxThisLevel) {
         float xpTotal = 0;
         float level = 1;
@@ -498,7 +554,6 @@ public class PetInfoOverlay extends TextOverlay {
                     .replace(" ", "_").toUpperCase();
         }
         if(petType == null || rarity == null) {
-            System.out.println("Failed pet : " + name);
             return null;
         }
 
@@ -541,7 +596,7 @@ public class PetInfoOverlay extends TextOverlay {
                 }
             } else if(xpLineMatcher.matches()) {
                 String xpThisLevelS = xpLineMatcher.group(1);
-                String xpMaxThisLevelS = xpLineMatcher.group(2);
+                String xpMaxThisLevelS = xpLineMatcher.group(2).toLowerCase();
 
                 try {
                     float xpThisLevel = Float.parseFloat(xpThisLevelS.replace(",", ""));
@@ -561,6 +616,8 @@ public class PetInfoOverlay extends TextOverlay {
 
                     level = getLevel(Constants.PETS.get("pet_levels").getAsJsonArray(), rarity.petOffset, xpThisLevel, xpMaxThisLevel);
                 } catch(NumberFormatException ignored) {}
+            } else if(line.equals("\u00a7b\u00a7lMAX LEVEL")) {
+                level = getMaxLevel(Constants.PETS.get("pet_levels").getAsJsonArray(), rarity.petOffset);
             }
         }
 
@@ -583,9 +640,6 @@ public class PetInfoOverlay extends TextOverlay {
 
     @SubscribeEvent
     public void onTick(TickEvent.ClientTickEvent event) {
-        if(Keyboard.isKeyDown(Keyboard.KEY_K)) {
-            System.out.println("Current:"+selectedPet+":"+getCurrentPet());
-        }
         if(Minecraft.getMinecraft().currentScreen instanceof GuiChest) {
             GuiChest chest = (GuiChest) Minecraft.getMinecraft().currentScreen;
             ContainerChest container = (ContainerChest) chest.inventorySlots;
@@ -667,7 +721,7 @@ public class PetInfoOverlay extends TextOverlay {
                                         }
                                     }
                                     if(!foundDespawn && selectedPet == petIndex) {
-                                        selectedPet = -1;
+                                        clearPet();
                                     }
                                 }
                             }
@@ -744,6 +798,24 @@ public class PetInfoOverlay extends TextOverlay {
             Utils.drawItemStack(stack, 0, 0);
             GlStateManager.popMatrix();
         }
+
+        Pet currentPet2 = getCurrentPet2();
+        if(currentPet2 != null) {
+            JsonObject petItem2 = NotEnoughUpdates.INSTANCE.manager.getItemInformation().get(currentPet2.petType + ";" + currentPet2.rarity.petId);
+            if(petItem2 != null) {
+                Vector2f position = getPosition(overlayWidth, overlayHeight);
+                int x = (int)position.x;
+                int y = (int)position.y + NotEnoughUpdates.INSTANCE.config.petOverlay.petOverlayText.size()*10+10;
+
+                ItemStack stack = NotEnoughUpdates.INSTANCE.manager.jsonToStack(petItem2);
+                GlStateManager.enableDepth();
+                GlStateManager.pushMatrix();
+                GlStateManager.translate(x-2, y-2, 0);
+                GlStateManager.scale(2, 2, 1);
+                Utils.drawItemStack(stack, 0, 0);
+                GlStateManager.popMatrix();
+            }
+        }
     }
 
     public static float getBoostMultiplier(String boostName) {
@@ -807,14 +879,13 @@ public class PetInfoOverlay extends TextOverlay {
                         System.out.println("removing");
                         System.out.println("new:"+newSelected+":OLD:"+selectedPet);
                         if(newSelected == selectedPet) {
-                            System.out.println("setting to -1");
-                            selectedPet = -1;
+                            clearPet();
                         } else if(selectedPet > newSelected) {
                             System.out.println("decrementing");
                             selectedPet--;
                         }
                     } else {
-                        selectedPet = newSelected;
+                        setCurrentPet(newSelected);
 
                         String[] lore = NotEnoughUpdates.INSTANCE.manager.getLoreFromNBT(stack.getTagCompound());
                         Pet pet = getPetFromStack(stack.getDisplayName(), lore);
@@ -831,6 +902,8 @@ public class PetInfoOverlay extends TextOverlay {
     }
 
     public static float getXpGain(Pet pet, float xp, String xpType) {
+        if(pet.petLevel.level >= 100) return 0;
+
         if(validXpTypes == null) validXpTypes = Lists.newArrayList("mining","foraging","enchanting","farming","combat","fishing","alchemy");
         if(!validXpTypes.contains(xpType.toLowerCase())) return 0;
 
@@ -1021,10 +1094,10 @@ public class PetInfoOverlay extends TextOverlay {
                             .replaceAll("[^\\w ]", "").trim()
                             .replace(" ", "_").toUpperCase();
 
-                    selectedPet = getClosestPetIndex(pet, rarity.petId, "", lastLevelHovered);
+                    setCurrentPet(getClosestPetIndex(pet, rarity.petId, "", lastLevelHovered));
                     if(selectedPet == -1) {
                         Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.RED+"[NEU] Can't find pet \u00a7" + petStringMatch +
-                                EnumChatFormatting.RED + " try visiting all pages of /pets."));
+                                EnumChatFormatting.RED + " try revisiting all pages of /pets."));
                     }
                 } else if(chatMessage.toLowerCase().startsWith("you despawned your")) {
                     clearPet();
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscgui/CalendarOverlay.java b/src/main/java/io/github/moulberry/notenoughupdates/miscgui/CalendarOverlay.java
index 4a343f9e..3305cd13 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/miscgui/CalendarOverlay.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscgui/CalendarOverlay.java
@@ -5,6 +5,7 @@ import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonPrimitive;
 import io.github.moulberry.notenoughupdates.NotEnoughUpdates;
+import io.github.moulberry.notenoughupdates.core.BackgroundBlur;
 import io.github.moulberry.notenoughupdates.util.Utils;
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.audio.PositionedSoundRecord;
@@ -787,7 +788,6 @@ public class CalendarOverlay {
 
     @SubscribeEvent
     public void onGuiScreenDrawTimer(GuiScreenEvent.BackgroundDrawnEvent event) {
-        blurBackground();
         if(!drawTimerForeground) {
             drawTimer();
         }
@@ -903,7 +903,7 @@ public class CalendarOverlay {
                     GlStateManager.disableLighting();
                     GlStateManager.disableColorMaterial();
 
-                    renderBlurredBackground(width, height, guiLeft+3, guiTop+3, xSize-6, ySize-6);
+                    renderBlurredBackground(10, width, height, guiLeft+3, guiTop+3, xSize-6, ySize-6);
 
                     Minecraft.getMinecraft().getTextureManager().bindTexture(DISPLAYBAR);
                     Utils.drawTexturedRect(guiLeft, guiTop, xSize, 20, GL11.GL_NEAREST);
@@ -985,6 +985,13 @@ public class CalendarOverlay {
         GlStateManager.popMatrix();
     }
 
+    private void renderBlurredBackground(float blurStrength, int screenWidth, int screenHeight,
+                                         int x, int y, int blurWidth, int blurHeight) {
+        BackgroundBlur.renderBlurredBackground(blurStrength, screenWidth, screenHeight, x, y, blurWidth, blurHeight);
+        Gui.drawRect(x, y, x+blurWidth, y+blurHeight, 0xc8101010);
+        GlStateManager.color(1, 1, 1, 1);
+    }
+
     @SubscribeEvent
     public void onGuiDraw(GuiScreenEvent.DrawScreenEvent.Pre event) {
         if(!(Minecraft.getMinecraft().currentScreen instanceof GuiChest)) {
@@ -1021,10 +1028,10 @@ public class CalendarOverlay {
 
         Utils.drawGradientRect(0, 0, width, height, -1072689136, -804253680);
 
-        renderBlurredBackground(width, height, guiLeft+3, guiTop+3, 162, 14);
-        renderBlurredBackground(width, height, guiLeft+3, guiTop+26, 14, 141);
-        renderBlurredBackground(width, height, guiLeft+151, guiTop+26, 14, 141);
-        renderBlurredBackground(width, height, guiLeft+26, guiTop+26, 116, 141);
+        renderBlurredBackground(10, width, height, guiLeft+3, guiTop+3, 162, 14);
+        renderBlurredBackground(10, width, height, guiLeft+3, guiTop+26, 14, 141);
+        renderBlurredBackground(10, width, height, guiLeft+151, guiTop+26, 14, 141);
+        renderBlurredBackground(10, width, height, guiLeft+26, guiTop+26, 116, 141);
 
         Minecraft.getMinecraft().getTextureManager().bindTexture(BACKGROUND);
         Utils.drawTexturedRect(guiLeft, guiTop, xSize, ySize, GL11.GL_NEAREST);
@@ -1381,82 +1388,4 @@ public class CalendarOverlay {
         return projMatrix;
     }
 
-    private double lastBgBlurFactor = -1;
-    private void blurBackground() {
-        if(!OpenGlHelper.isFramebufferEnabled()) return;
-
-        int width = Minecraft.getMinecraft().displayWidth;
-        int height = Minecraft.getMinecraft().displayHeight;
-
-        if(blurOutputHorz == null) {
-            blurOutputHorz = new Framebuffer(width, height, false);
-            blurOutputHorz.setFramebufferFilter(GL11.GL_NEAREST);
-        }
-        if(blurOutputVert == null) {
-            blurOutputVert = new Framebuffer(width, height, false);
-            blurOutputVert.setFramebufferFilter(GL11.GL_NEAREST);
-        }
-        if(blurOutputHorz.framebufferWidth != width || blurOutputHorz.framebufferHeight != height) {
-            blurOutputHorz.createBindFramebuffer(width, height);
-            blurShaderHorz.setProjectionMatrix(createProjectionMatrix(width, height));
-            Minecraft.getMinecraft().getFramebuffer().bindFramebuffer(false);
-        }
-        if(blurOutputVert.framebufferWidth != width || blurOutputVert.framebufferHeight != height) {
-            blurOutputVert.createBindFramebuffer(width, height);
-            blurShaderVert.setProjectionMatrix(createProjectionMatrix(width, height));
-            Minecraft.getMinecraft().getFramebuffer().bindFramebuffer(false);
-        }
-
-        if(blurShaderHorz == null) {
-            try {
-                blurShaderHorz = new Shader(Minecraft.getMinecraft().getResourceManager(), "blur",
-                        Minecraft.getMinecraft().getFramebuffer(), blurOutputHorz);
-                blurShaderHorz.getShaderManager().getShaderUniform("BlurDir").set(1, 0);
-                blurShaderHorz.setProjectionMatrix(createProjectionMatrix(width, height));
-            } catch(Exception e) { }
-        }
-        if(blurShaderVert == null) {
-            try {
-                blurShaderVert = new Shader(Minecraft.getMinecraft().getResourceManager(), "blur",
-                        blurOutputHorz, blurOutputVert);
-                blurShaderVert.getShaderManager().getShaderUniform("BlurDir").set(0, 1);
-                blurShaderVert.setProjectionMatrix(createProjectionMatrix(width, height));
-            } catch(Exception e) { }
-        }
-        if(blurShaderHorz != null && blurShaderVert != null) {
-            if(15 != lastBgBlurFactor) {
-                blurShaderHorz.getShaderManager().getShaderUniform("Radius").set((float)15);
-                blurShaderVert.getShaderManager().getShaderUniform("Radius").set((float)15);
-                lastBgBlurFactor = 15;
-            }
-            GL11.glPushMatrix();
-            blurShaderHorz.loadShader(0);
-            blurShaderVert.loadShader(0);
-            GlStateManager.enableDepth();
-            GL11.glPopMatrix();
-
-            Minecraft.getMinecraft().getFramebuffer().bindFramebuffer(false);
-        }
-    }
-
-    /**
-     * Renders a subsection of the blurred framebuffer on to the corresponding section of the screen.
-     * Essentially, this method will "blur" the background inside the bounds specified by [x->x+blurWidth, y->y+blurHeight]
-     */
-    public void renderBlurredBackground(int width, int height, int x, int y, int blurWidth, int blurHeight) {
-        if(!OpenGlHelper.isFramebufferEnabled()) return;
-
-        float uMin = x/(float)width;
-        float uMax = (x+blurWidth)/(float)width;
-        float vMin = (height-y)/(float)height;
-        float vMax = (height-y-blurHeight)/(float)height;
-
-        GlStateManager.depthMask(false);
-        blurOutputVert.bindFramebufferTexture();
-        GlStateManager.color(1f, 1f, 1f, 1f);
-        Utils.drawTexturedRect(x, y, blurWidth, blurHeight, uMin, uMax, vMin, vMax);
-        blurOutputVert.unbindFramebufferTexture();
-        GlStateManager.depthMask(true);
-    }
-
 }
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscgui/GuiInvButtonEditor.java b/src/main/java/io/github/moulberry/notenoughupdates/miscgui/GuiInvButtonEditor.java
new file mode 100644
index 00000000..1260dbd2
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/miscgui/GuiInvButtonEditor.java
@@ -0,0 +1,677 @@
+package io.github.moulberry.notenoughupdates.miscgui;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.annotations.Expose;
+import io.github.moulberry.notenoughupdates.NotEnoughUpdates;
+import io.github.moulberry.notenoughupdates.core.GlScissorStack;
+import io.github.moulberry.notenoughupdates.core.GuiElementTextField;
+import io.github.moulberry.notenoughupdates.core.util.lerp.LerpingInteger;
+import io.github.moulberry.notenoughupdates.options.NEUConfig;
+import io.github.moulberry.notenoughupdates.util.TexLoc;
+import io.github.moulberry.notenoughupdates.util.Utils;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.Gui;
+import net.minecraft.client.gui.GuiScreen;
+import net.minecraft.client.gui.ScaledResolution;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.init.Items;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.nbt.NBTTagList;
+import net.minecraft.util.ResourceLocation;
+import org.lwjgl.input.Keyboard;
+import org.lwjgl.input.Mouse;
+import org.lwjgl.opengl.GL11;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class GuiInvButtonEditor extends GuiScreen {
+
+    private static final ResourceLocation INVENTORY = new ResourceLocation("minecraft:textures/gui/container/inventory.png");
+    private static final ResourceLocation EDITOR = new ResourceLocation("notenoughupdates:invbuttons/editor.png");
+    private static final ResourceLocation EXTRA_ICONS_JSON = new ResourceLocation("notenoughupdates:invbuttons/extraicons.json");
+    private static final ResourceLocation PRESETS_JSON = new ResourceLocation("notenoughupdates:invbuttons/presets.json");
+
+    private int xSize = 176;
+    private int ySize = 166;
+
+    private int guiLeft;
+    private int guiTop;
+
+    private static final int BACKGROUND_TYPES = 5;
+    private static final int ICON_TYPES = 3;
+    private int iconTypeIndex = 0;
+
+    private int editorXSize = 150;
+    private int editorYSize = 204;
+    private int editorLeft;
+    private int editorTop;
+
+    private GuiElementTextField commandTextField = new GuiElementTextField("", editorXSize-14, 16, GuiElementTextField.SCALE_TEXT);
+    private GuiElementTextField iconTextField = new GuiElementTextField("", editorXSize-14, 16, GuiElementTextField.SCALE_TEXT);
+
+    private static final HashSet<String> prioritisedIcons = new HashSet<>();
+    static {
+        prioritisedIcons.add("WORKBENCH");
+        prioritisedIcons.add("LEATHER_CHESTPLATE");
+        prioritisedIcons.add("CHEST");
+        prioritisedIcons.add("BONE");
+        prioritisedIcons.add("ENDER_CHEST");
+        prioritisedIcons.add("GOLD_BARDING");
+        prioritisedIcons.add("COMPASS");
+        prioritisedIcons.add("GOLD_BLOCK");
+        prioritisedIcons.add("EMPTY_MAP");
+        prioritisedIcons.add("RAW_FISH");
+        prioritisedIcons.add("FISHING_ROD");
+        prioritisedIcons.add("EMERALD");
+        prioritisedIcons.add("IRON_SWORD");
+        prioritisedIcons.add("POTION");
+        prioritisedIcons.add("NETHER_STAR");
+        prioritisedIcons.add("PAINTING");
+        prioritisedIcons.add("COMMAND");
+        prioritisedIcons.add("BOOK");
+    }
+
+    private static HashMap<String, String> extraIcons = null;
+
+    private static final HashMap<String, String> skullIcons = new HashMap<>();
+    static {
+        skullIcons.put("personal bank", "skull:e36e94f6c34a35465fce4a90f2e25976389eb9709a12273574ff70fd4daa6852");
+        skullIcons.put("skyblock hub", "skull:d7cc6687423d0570d556ac53e0676cb563bbdd9717cd8269bdebed6f6d4e7bf8");
+        skullIcons.put("private island", "skull:c9c8881e42915a9d29bb61a16fb26d059913204d265df5b439b3d792acd56");
+        skullIcons.put("castle", "skull:f4559d75464b2e40a518e4de8e6cf3085f0a3ca0b1b7012614c4cd96fed60378");
+        skullIcons.put("sirius shack", "skull:7ab83858ebc8ee85c3e54ab13aabfcc1ef2ad446d6a900e471c3f33b78906a5b");
+        skullIcons.put("crypts", "skull:25d2f31ba162fe6272e831aed17f53213db6fa1c4cbe4fc827f3963cc98b9");
+        skullIcons.put("spiders den", "skull:c754318a3376f470e481dfcd6c83a59aa690ad4b4dd7577fdad1c2ef08d8aee6");
+        skullIcons.put("top of the nest", "skull:9d7e3b19ac4f3dee9c5677c135333b9d35a7f568b63d1ef4ada4b068b5a25");
+        skullIcons.put("blazing fortress", "skull:c3687e25c632bce8aa61e0d64c24e694c3eea629ea944f4cf30dcfb4fbce071");
+        skullIcons.put("blazing fortress magma boss", "skull:38957d5023c937c4c41aa2412d43410bda23cf79a9f6ab36b76fef2d7c429");
+        skullIcons.put("the end", "skull:7840b87d52271d2a755dedc82877e0ed3df67dcc42ea479ec146176b02779a5");
+        skullIcons.put("the end dragons nest", "skull:a1cd6d2d03f135e7c6b5d6cdae1b3a68743db4eb749faf7341e9fb347aa283b");
+        skullIcons.put("the park", "skull:a221f813dacee0fef8c59f76894dbb26415478d9ddfc44c2e708a6d3b7549b");
+        skullIcons.put("the park jungle", "skull:79ca3540621c1c79c32bf42438708ff1f5f7d0af9b14a074731107edfeb691c");
+        skullIcons.put("the park howling cave", "skull:1832d53997b451635c9cf9004b0f22bb3d99ab5a093942b5b5f6bb4e4de47065");
+        skullIcons.put("gold mines", "skull:73bc965d579c3c6039f0a17eb7c2e6faf538c7a5de8e60ec7a719360d0a857a9");
+        skullIcons.put("deep caverns", "skull:569a1f114151b4521373f34bc14c2963a5011cdc25a6554c48c708cd96ebfc");
+        skullIcons.put("the barn", "skull:4d3a6bd98ac1833c664c4909ff8d2dc62ce887bdcf3cc5b3848651ae5af6b");
+        skullIcons.put("mushroom desert", "skull:2116b9d8df346a25edd05f842e7a9345beaf16dca4118abf5a68c75bcaae10");
+        skullIcons.put("dungeon hub", "skull:9b56895b9659896ad647f58599238af532d46db9c1b0389b8bbeb70999dab33d");
+        skullIcons.put("dwarven mines", "skull:569a1f114151b4521373f34bc14c2963a5011cdc25a6554c48c708cd96ebfc");
+        skullIcons.put("hotm heart of the mountain", "skull:86f06eaa3004aeed09b3d5b45d976de584e691c0e9cade133635de93d23b9edb");
+        skullIcons.put("bazaar dude", "skull:c232e3820897429157619b0ee099fec0628f602fff12b695de54aef11d923ad7");
+    }
+
+    private static LinkedHashMap<String, List<NEUConfig.InventoryButton>> presets = null;
+
+    public GuiInvButtonEditor() {
+        super();
+        reloadExtraIcons();
+        reloadPresets();
+        Keyboard.enableRepeatEvents(true);
+    }
+
+    private static void reloadExtraIcons() {
+        extraIcons = new HashMap<>();
+
+        try(BufferedReader reader = new BufferedReader(new InputStreamReader(
+                Minecraft.getMinecraft().getResourceManager().getResource(EXTRA_ICONS_JSON).getInputStream(), StandardCharsets.UTF_8))) {
+            JsonObject json = NotEnoughUpdates.INSTANCE.manager.gson.fromJson(reader, JsonObject.class);
+
+            for(Map.Entry<String, JsonElement> entry : json.entrySet()) {
+                if(entry.getValue().isJsonPrimitive()) {
+                    extraIcons.put(entry.getKey(), "extra:"+entry.getValue().getAsString());
+                }
+            }
+        } catch(Exception e) {
+        }
+    }
+
+    private static void reloadPresets() {
+        presets = new LinkedHashMap<>();
+
+        try(BufferedReader reader = new BufferedReader(new InputStreamReader(
+                Minecraft.getMinecraft().getResourceManager().getResource(PRESETS_JSON).getInputStream(), StandardCharsets.UTF_8))) {
+            JsonObject json = NotEnoughUpdates.INSTANCE.manager.gson.fromJson(reader, JsonObject.class);
+
+            for(Map.Entry<String, JsonElement> entry : json.entrySet()) {
+                if(entry.getValue().isJsonArray()) {
+                    JsonArray arr = entry.getValue().getAsJsonArray();
+                    List<NEUConfig.InventoryButton> buttons = new ArrayList<>();
+                    for(int i=0; i<arr.size(); i++) {
+                        JsonObject o = arr.get(i).getAsJsonObject();
+                        NEUConfig.InventoryButton button = NotEnoughUpdates.INSTANCE.manager.gson.fromJson(o, NEUConfig.InventoryButton.class);
+                        buttons.add(button);
+                    }
+                    presets.put(entry.getKey(), buttons);
+                }
+            }
+        } catch(Exception e) {
+        }
+    }
+
+    private static final Comparator<String> prioritisingComparator = (o1, o2) -> {
+        boolean c1 = prioritisedIcons.contains(o1);
+        boolean c2 = prioritisedIcons.contains(o2);
+
+        if(c1 && !c2) return -1;
+        if(!c1 && c2) return 1;
+
+        return o1.compareTo(o2);
+    };
+
+    private final List<String> searchedIcons = new ArrayList<>();
+
+    private LerpingInteger itemScroll = new LerpingInteger(0, 100);
+
+    private NEUConfig.InventoryButton editingButton = null;
+
+    private static HashMap<String, ItemStack> skullMap = new HashMap<>();
+
+    public static void renderIcon(String icon, int x, int y) {
+        if(extraIcons == null) {
+            reloadExtraIcons();
+        }
+
+        if(icon.startsWith("extra:")) {
+            String name = icon.substring("extra:".length());
+            ResourceLocation resourceLocation = new ResourceLocation("notenoughupdates:invbuttons/extraicons/"+name+".png");
+            Minecraft.getMinecraft().getTextureManager().bindTexture(resourceLocation);
+            GlStateManager.color(1, 1, 1, 1);
+            Utils.drawTexturedRect(x, y, 16, 16, GL11.GL_NEAREST);
+        } else {
+            ItemStack stack = getStack(icon);
+
+            float scale = 1;
+            if(icon.startsWith("skull:")) {
+                scale = 1.2f;
+            }
+            GlStateManager.pushMatrix();
+            GlStateManager.translate(x+8, y+8, 0);
+            GlStateManager.scale(scale, scale, 1);
+            GlStateManager.translate(-8, -8, 0);
+            Utils.drawItemStack(stack, 0, 0);
+            GlStateManager.popMatrix();
+        }
+    }
+
+    public static ItemStack getStack(String icon) {
+        if(icon.startsWith("extra:")) {
+            return null;
+        } else if(icon.startsWith("skull:")) {
+            String link = icon.substring("skull:".length());
+            if(skullMap.containsKey(link)) return skullMap.get(link);
+
+            ItemStack render = new ItemStack(Items.skull, 1, 3);
+            NBTTagCompound nbt = new NBTTagCompound();
+            NBTTagCompound skullOwner = new NBTTagCompound();
+            NBTTagCompound properties = new NBTTagCompound();
+            NBTTagList textures = new NBTTagList();
+            NBTTagCompound textures_0 = new NBTTagCompound();
+
+            String uuid = UUID.nameUUIDFromBytes(link.getBytes()).toString();
+            skullOwner.setString("Id", uuid);
+            skullOwner.setString("Name", uuid);
+
+            String display = "{\"textures\":{\"SKIN\":{\"url\":\"http://textures.minecraft.net/texture/"+link+"\"}}}";
+            String displayB64 = Base64.getEncoder().encodeToString(display.getBytes());
+
+            textures_0.setString("Value", displayB64);
+            textures.appendTag(textures_0);
+
+            properties.setTag("textures", textures);
+            skullOwner.setTag("Properties", properties);
+            nbt.setTag("SkullOwner", skullOwner);
+            render.setTagCompound(nbt);
+
+            skullMap.put(link, render);
+            return render;
+        } else {
+            return NotEnoughUpdates.INSTANCE.manager.jsonToStack(NotEnoughUpdates.INSTANCE.manager.getItemInformation().get(icon));
+        }
+    }
+
+    @Override
+    public void drawScreen(int mouseX, int mouseY, float partialTicks) {
+        super.drawScreen(mouseX, mouseY, partialTicks);
+
+        super.drawDefaultBackground();
+
+        guiLeft = width/2 - xSize/2;
+        guiTop = height/2 - ySize/2;
+
+        GlStateManager.enableDepth();
+
+        Minecraft.getMinecraft().getTextureManager().bindTexture(INVENTORY);
+        GlStateManager.color(1, 1, 1, 1);
+        Utils.drawTexturedRect(guiLeft, guiTop, xSize, ySize, 0, xSize/256f, 0, ySize/256f, GL11.GL_NEAREST);
+
+        for(NEUConfig.InventoryButton button : NotEnoughUpdates.INSTANCE.config.hidden.inventoryButtons) {
+            int x = guiLeft+button.x;
+            int y = guiTop+button.y;
+            if(button.anchorRight) {
+                x += xSize;
+            }
+            if(button.anchorBottom) {
+                y += ySize;
+            }
+
+            if(button.isActive()) {
+                GlStateManager.color(1, 1, 1, 1f);
+            } else {
+                GlStateManager.color(1, 1, 1, 0.5f);
+            }
+
+            Minecraft.getMinecraft().getTextureManager().bindTexture(EDITOR);
+            Utils.drawTexturedRect(x, y, 18, 18,
+                    button.backgroundIndex*18/256f, (button.backgroundIndex*18+18)/256f, 18/256f, 36/256f, GL11.GL_NEAREST);
+
+            if(button.isActive()) {
+                if(button.icon != null && !button.icon.trim().isEmpty()) {
+                    GlStateManager.enableDepth();
+
+                    renderIcon(button.icon, x+1, y+1);
+                }
+            } else {
+                fontRendererObj.drawString("+", x+6, y+5, 0xffcccccc);
+            }
+        }
+
+        if(presets != null) {
+            Minecraft.getMinecraft().getTextureManager().bindTexture(EDITOR);
+            Utils.drawTexturedRect(guiLeft+xSize+22, guiTop, 80, ySize,
+                    editorXSize/256f, (editorXSize+80)/256f, 41/256f, (41+ySize)/256f, GL11.GL_NEAREST);
+            Utils.drawStringCenteredScaledMaxWidth("\u00a7nPresets", fontRendererObj, guiLeft+xSize+22+40, guiTop+10, false, 70, 0xffa0a0a0);
+
+            int index = 0;
+            for(String presetName : presets.keySet()) {
+                Utils.drawStringCenteredScaledMaxWidth(presetName, fontRendererObj, guiLeft+xSize+22+40, guiTop+25+10*(index++),
+                        false, 70, 0xff909090);
+            }
+        }
+
+        if(editingButton != null) {
+            int x = guiLeft+editingButton.x;
+            int y = guiTop+editingButton.y;
+            if(editingButton.anchorRight) {
+                x += xSize;
+            }
+            if(editingButton.anchorBottom) {
+                y += ySize;
+            }
+
+            GlStateManager.translate(0, 0, 300);
+            editorLeft = x + 8 - editorXSize/2;
+            editorTop = y + 18 + 2;
+
+            boolean showArrow = true;
+            if(editorTop+editorYSize+5 > height) {
+                editorTop = height-editorYSize-5;
+                showArrow = false;
+            }
+            if(editorLeft < 5) {
+                editorLeft = 5;
+                showArrow = false;
+            }
+            if(editorLeft+editorXSize+5 > width) {
+                editorLeft = width-editorXSize-5;
+                showArrow = false;
+            }
+
+            Minecraft.getMinecraft().getTextureManager().bindTexture(EDITOR);
+            GlStateManager.color(1, 1, 1, 1f);
+            Utils.drawTexturedRect(editorLeft, editorTop, editorXSize, editorYSize, 0, editorXSize/256f, 41/256f, (41+editorYSize)/256f, GL11.GL_NEAREST);
+
+            if(showArrow) Utils.drawTexturedRect(x+8-3, y+18, 10, 5, 0, 6/256f, 36/256f, 41/256f, GL11.GL_NEAREST);
+
+            fontRendererObj.drawString("Command", editorLeft+7, editorTop+7, 0xffa0a0a0, false);
+
+            commandTextField.setSize(editorXSize-14, 16);
+            commandTextField.setText(commandTextField.getText().replaceAll("^ +", ""));
+            if(commandTextField.getText().startsWith("/")) {
+                commandTextField.setPrependText("");
+            } else {
+                commandTextField.setPrependText("\u00a77/\u00a7r");
+            }
+            commandTextField.render(editorLeft+7, editorTop+19);
+
+            fontRendererObj.drawString("Background", editorLeft+7, editorTop+40, 0xffa0a0a0, false);
+
+            for(int i=0; i<BACKGROUND_TYPES; i++) {
+                if(i == editingButton.backgroundIndex) {
+                    Gui.drawRect(editorLeft+7+20*i-1, editorTop+50-1, editorLeft+7+20*i+19, editorTop+50+19, 0xff0000ff);
+                }
+                Minecraft.getMinecraft().getTextureManager().bindTexture(EDITOR);
+                GlStateManager.color(1, 1, 1, 1);
+                Utils.drawTexturedRect(editorLeft+7+20*i, editorTop+50, 18, 18,
+                        i*18/256f, (i*18+18)/256f, 0/256f, 18/256f, GL11.GL_NEAREST);
+            }
+
+            fontRendererObj.drawString("Icon Type", editorLeft+7, editorTop+50+24, 0xffa0a0a0, false);
+
+            Minecraft.getMinecraft().getTextureManager().bindTexture(EDITOR);
+            GlStateManager.color(1, 1, 1, 1);
+            float uMin = 18/256f;
+            float uMax = 36/256f;
+            float vMin = 0;
+            float vMax = 18/256f;
+
+            for(int i=0; i<ICON_TYPES; i++) {
+                boolean flip = iconTypeIndex == i;
+
+                Minecraft.getMinecraft().getTextureManager().bindTexture(EDITOR);
+                GlStateManager.color(1, 1, 1, 1);
+                Utils.drawTexturedRect(editorLeft+7+20*i, editorTop+50+34, 18, 18,
+                        flip ? uMax : uMin, flip ? uMin : uMax, flip ? vMax : vMin, flip ? vMin : vMax, GL11.GL_NEAREST);
+
+                ItemStack stack = null;
+                if(i == 0) {
+                    stack = new ItemStack(Items.diamond_sword);
+                } else if(i == 1) {
+                    stack = getStack("skull:c9c8881e42915a9d29bb61a16fb26d059913204d265df5b439b3d792acd56");
+                } else if(i == 2) {
+                    stack = new ItemStack(Items.lead);
+                }
+                if(stack != null) Utils.drawItemStack(stack, editorLeft+8+20*i, editorTop+50+35);
+            }
+
+            fontRendererObj.drawString("Icon Selector", editorLeft+7, editorTop+50+55, 0xffa0a0a0, false);
+
+            iconTextField.render(editorLeft+7, editorTop+50+65);
+
+            GlStateManager.enableDepth();
+
+            itemScroll.tick();
+
+            ScaledResolution scaledResolution = new ScaledResolution(Minecraft.getMinecraft());
+            GlScissorStack.push(0, editorTop+136, width, editorTop+196, scaledResolution);
+
+            synchronized(searchedIcons) {
+                if(iconTextField.getText().trim().isEmpty() && searchedIcons.isEmpty()) {
+                    searchedIcons.addAll(NotEnoughUpdates.INSTANCE.manager.getItemInformation().keySet());
+                    searchedIcons.sort(prioritisingComparator);
+                }
+
+                int max = (searchedIcons.size()-1)/6*20-40;
+                int scroll = itemScroll.getValue();
+                if(scroll > max) scroll = max;
+
+                int scrollBarHeight = (int)Math.ceil(3f*54f/(searchedIcons.size()-18));
+                if(scrollBarHeight < 0) scrollBarHeight = 54;
+                if(scrollBarHeight < 2) scrollBarHeight = 2;
+                int scrollY = (int)Math.floor(54f*((scroll/20f) / ((searchedIcons.size()-18)/6f)));
+                if(scrollY+scrollBarHeight > 54) scrollY = 54-scrollBarHeight;
+
+                Gui.drawRect(editorLeft+137, editorTop+139+scrollY, editorLeft+139, editorTop+139+scrollY+scrollBarHeight, 0xff202020);
+
+                int endIndex = searchedIcons.size();
+                int startIndex = scroll/20*6;
+                if(startIndex < 0) startIndex = 0;
+                if(endIndex > startIndex+24) endIndex = startIndex+24;
+
+                for(int i=startIndex; i<endIndex; i++) {
+                    String iconS = searchedIcons.get(i);
+
+                    int iconX = editorLeft+12+((i-startIndex)%6)*20;
+                    int iconY = editorTop+137+((i-startIndex)/6)*20 - (itemScroll.getValue()%20);
+
+                    Minecraft.getMinecraft().getTextureManager().bindTexture(EDITOR);
+                    GlStateManager.color(1, 1, 1, 1);
+                    Utils.drawTexturedRect(iconX, iconY, 18, 18,
+                            18/256f, 36/256f, 0/256f, 18/256f, GL11.GL_NEAREST);
+
+                    renderIcon(iconS, iconX+1, iconY+1);
+                }
+            }
+
+            GlScissorStack.pop(scaledResolution);
+
+            GlStateManager.translate(0, 0, -300);
+        }
+    }
+
+    @Override
+    public void handleMouseInput() throws IOException {
+        int scroll = Mouse.getEventDWheel();
+        if(scroll != 0) {
+            scroll = -scroll;
+            if(scroll > 1) scroll = 8;
+            if(scroll < -1) scroll = -8;
+
+            int delta = Math.abs(itemScroll.getTarget() - itemScroll.getValue());
+            float acc = delta/20+1;
+            scroll *= acc;
+
+            int max = (searchedIcons.size()-1)/6*20-40;
+            int newTarget = itemScroll.getTarget() + scroll;
+
+            if(newTarget > max) newTarget = max;
+            if(newTarget < 0) newTarget = 0;
+
+            itemScroll.setTarget(newTarget);
+            itemScroll.resetTimer();
+        }
+
+        super.handleMouseInput();
+    }
+
+    @Override
+    protected void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOException {
+        super.mouseClicked(mouseX, mouseY, mouseButton);
+
+        if(editingButton != null) {
+            if(mouseX >= editorLeft && mouseX <= editorLeft+editorXSize &&
+                    mouseY >= editorTop & mouseY <= editorTop + editorYSize) {
+                if(mouseX >= editorLeft+7 && mouseX <= editorLeft+7+commandTextField.getWidth() &&
+                        mouseY >= editorTop+12 && mouseY <= editorTop+12+commandTextField.getHeight()) {
+                    commandTextField.mouseClicked(mouseX, mouseY, mouseButton);
+                    iconTextField.unfocus();
+                    editingButton.command = commandTextField.getText();
+                    return;
+                }
+                if(mouseX >= editorLeft+7 && mouseX <= editorLeft+7+iconTextField.getWidth() &&
+                        mouseY >= editorTop+50+65 && mouseY <= editorTop+50+65+iconTextField.getHeight()) {
+                    iconTextField.mouseClicked(mouseX, mouseY, mouseButton);
+
+                    if(mouseButton == 1) {
+                        search();
+                    }
+
+                    commandTextField.unfocus();
+                    return;
+                }
+                if(mouseY >= editorTop+50 && mouseY <= editorTop+50+18) {
+                    for(int i=0; i<BACKGROUND_TYPES; i++) {
+                        if(mouseX >= editorLeft+7+20*i && mouseX <= editorLeft+7+20*i+18) {
+                            editingButton.backgroundIndex = i;
+                            return;
+                        }
+                    }
+                }
+                for(int i=0; i<ICON_TYPES; i++) {
+                    if(mouseX >= editorLeft+7+20*i && mouseX <= editorLeft+7+20*i+18 &&
+                            mouseY >= editorTop+50+34 && mouseY <= editorTop+50+34+18) {
+                        if(iconTypeIndex != i) {
+                            iconTypeIndex = i;
+                            search();
+                        }
+                        return;
+                    }
+                }
+                if(mouseX > editorLeft+8 && mouseX < editorLeft+editorXSize-16 && mouseY > editorTop+136 && mouseY < editorTop+196) {
+                    synchronized(searchedIcons) {
+                        int max = (searchedIcons.size()-1)/6*20-40;
+                        int scroll = itemScroll.getValue();
+                        if(scroll > max) scroll = max;
+
+                        int endIndex = searchedIcons.size();
+                        int startIndex = scroll/20*6;
+                        if(startIndex < 0) startIndex = 0;
+                        if(endIndex > startIndex+24) endIndex = startIndex+24;
+
+                        for(int i=startIndex; i<endIndex; i++) {
+                            String iconS = searchedIcons.get(i);
+
+                            int x = editorLeft+12+((i-startIndex)%6)*20;
+                            int y = editorTop+137+((i-startIndex)/6)*20 - (itemScroll.getValue()%20);
+
+                            if(mouseX >= x && mouseX <= x+18 &&
+                                    mouseY >= y && mouseY <= y+18) {
+                                editingButton.icon = iconS;
+                                return;
+                            }
+                        }
+                    }
+                }
+                return;
+            }
+        }
+
+        for(NEUConfig.InventoryButton button : NotEnoughUpdates.INSTANCE.config.hidden.inventoryButtons) {
+            int x = guiLeft+button.x;
+            int y = guiTop+button.y;
+            if(button.anchorRight) {
+                x += xSize;
+            }
+            if(button.anchorBottom) {
+                y += ySize;
+            }
+
+            if(mouseX >= x && mouseY >= y &&
+                    mouseX <= x+18 && mouseY <= y+18) {
+                if(editingButton == button) {
+                    editingButton = null;
+                } else {
+                    editingButton = button;
+                    commandTextField.setText(editingButton.command);
+                }
+                return;
+            }
+        }
+
+        if(editingButton == null) {
+            int index = 0;
+            for(List<NEUConfig.InventoryButton> buttons : presets.values()) {
+                if(mouseX >= guiLeft+xSize+22 && mouseX <= guiLeft+xSize+22+80 &&
+                        mouseY >= guiTop+21+10*index && mouseY <= guiTop+21+10*index+10) {
+                    NotEnoughUpdates.INSTANCE.config.hidden.inventoryButtons = buttons;
+                    return;
+                }
+                index++;
+            }
+        }
+
+        editingButton = null;
+    }
+
+    @Override
+    protected void keyTyped(char typedChar, int keyCode) throws IOException {
+        super.keyTyped(typedChar, keyCode);
+
+        if(editingButton != null && commandTextField.getFocus()) {
+            commandTextField.keyTyped(typedChar, keyCode);
+            editingButton.command = commandTextField.getText();
+        } else if(editingButton != null && iconTextField.getFocus()) {
+            String old = iconTextField.getText().trim();
+            iconTextField.keyTyped(typedChar, keyCode);
+            String newText = iconTextField.getText().trim();
+
+            if(!old.equalsIgnoreCase(newText)) {
+                search();
+            }
+        }
+    }
+
+    private ExecutorService searchES = Executors.newSingleThreadExecutor();
+    private AtomicInteger searchId = new AtomicInteger(0);
+
+    public void search() {
+        final int thisSearchId = searchId.incrementAndGet();
+        final String searchString = iconTextField.getText();
+
+        if(iconTypeIndex == 0) {
+            if(searchString.trim().isEmpty()) {
+                synchronized(searchedIcons) {
+                    searchedIcons.clear();
+
+                    List<String> unsorted = new ArrayList<>(NotEnoughUpdates.INSTANCE.manager.getItemInformation().keySet());
+                    unsorted.sort(prioritisingComparator);
+                    searchedIcons.addAll(unsorted);
+                }
+                return;
+            }
+
+            searchES.submit(() -> {
+                if(thisSearchId != searchId.get()) return;
+
+                List<String> title = new ArrayList<>(NotEnoughUpdates.INSTANCE.manager.search("title:" + searchString.trim()));
+
+                if(thisSearchId != searchId.get()) return;
+
+                if(!searchString.trim().contains(" ")) {
+                    StringBuilder sb = new StringBuilder();
+                    for(char c : searchString.toCharArray()) {
+                        sb.append(c).append(" ");
+                    }
+                    title.addAll(NotEnoughUpdates.INSTANCE.manager.search("title:" + sb.toString().trim()));
+                }
+
+                if(thisSearchId != searchId.get()) return;
+
+                List<String> desc = new ArrayList<>(NotEnoughUpdates.INSTANCE.manager.search("desc:" + searchString.trim()));
+                desc.removeAll(title);
+
+                if(thisSearchId != searchId.get()) return;
+
+                title.sort(prioritisingComparator);
+                desc.sort(prioritisingComparator);
+
+                if(thisSearchId != searchId.get()) return;
+
+                synchronized(searchedIcons) {
+                    searchedIcons.clear();
+                    searchedIcons.addAll(title);
+                    searchedIcons.addAll(desc);
+                }
+            });
+        } else if(iconTypeIndex == 1) {
+            if(searchString.trim().isEmpty()) {
+                searchedIcons.clear();
+                searchedIcons.addAll(skullIcons.values());
+                return;
+            }
+
+            synchronized(searchedIcons) {
+                searchedIcons.clear();
+                for(Map.Entry<String, String> entry : skullIcons.entrySet()) {
+                    if(NotEnoughUpdates.INSTANCE.manager.searchString(entry.getKey(), searchString)) {
+                        searchedIcons.add(entry.getValue());
+                    }
+                }
+            }
+        } else if(iconTypeIndex == 2) {
+            if(searchString.trim().isEmpty()) {
+                searchedIcons.clear();
+                searchedIcons.addAll(extraIcons.values());
+                return;
+            }
+
+            synchronized(searchedIcons) {
+                searchedIcons.clear();
+                for(Map.Entry<String, String> entry : extraIcons.entrySet()) {
+                    if(NotEnoughUpdates.INSTANCE.manager.searchString(entry.getKey(), searchString)) {
+                        searchedIcons.add(entry.getValue());
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/mixins/GuiContainerAccessor.java b/src/main/java/io/github/moulberry/notenoughupdates/mixins/GuiContainerAccessor.java
new file mode 100644
index 00000000..57d4de2a
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/mixins/GuiContainerAccessor.java
@@ -0,0 +1,22 @@
+package io.github.moulberry.notenoughupdates.mixins;
+
+import net.minecraft.client.gui.inventory.GuiContainer;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Accessor;
+
+@Mixin(GuiContainer.class)
+public interface GuiContainerAccessor {
+
+    @Accessor
+    int getXSize();
+
+    @Accessor
+    int getYSize();
+
+    @Accessor
+    int getGuiLeft();
+
+    @Accessor
+    int getGuiTop();
+
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinContainer.java b/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinContainer.java
index fb6fba4e..58aeb828 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinContainer.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinContainer.java
@@ -17,7 +17,7 @@ public class MixinContainer {
 
     @Inject(method = "putStacksInSlots", at=@At("RETURN"))
     public void putStacksInSlots(ItemStack[] stacks, CallbackInfo ci) {
-        EnchantingSolvers.processInventoryContents();
+        EnchantingSolvers.processInventoryContents(false);
     }
 
 }
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinGuiContainer.java b/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinGuiContainer.java
index 1281dec4..0c822827 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinGuiContainer.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinGuiContainer.java
@@ -1,13 +1,19 @@
 package io.github.moulberry.notenoughupdates.mixins;
 
+import io.github.moulberry.notenoughupdates.NEUEventListener;
+import io.github.moulberry.notenoughupdates.NEUOverlay;
 import io.github.moulberry.notenoughupdates.miscfeatures.BetterContainers;
 import io.github.moulberry.notenoughupdates.miscfeatures.EnchantingSolvers;
 import io.github.moulberry.notenoughupdates.NotEnoughUpdates;
 import io.github.moulberry.notenoughupdates.miscfeatures.PetInfoOverlay;
 import io.github.moulberry.notenoughupdates.util.Utils;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.Gui;
 import net.minecraft.client.gui.GuiScreen;
+import net.minecraft.client.gui.GuiTextField;
 import net.minecraft.client.gui.inventory.GuiChest;
 import net.minecraft.client.gui.inventory.GuiContainer;
+import net.minecraft.client.renderer.GlStateManager;
 import net.minecraft.client.renderer.RenderHelper;
 import net.minecraft.inventory.Container;
 import net.minecraft.inventory.ContainerChest;
@@ -28,6 +34,16 @@ public abstract class MixinGuiContainer extends GuiScreen {
     public void drawSlot(Slot slot, CallbackInfo ci) {
         if(slot == null) return;
 
+        if(slot.getStack() == null && NotEnoughUpdates.INSTANCE.overlay.searchMode && NEUEventListener.drawingGuiScreen) {
+            GlStateManager.pushMatrix();
+            GlStateManager.translate(0, 0, 100 + Minecraft.getMinecraft().getRenderItem().zLevel);
+            GlStateManager.depthMask(false);
+            Gui.drawRect(slot.xDisplayPosition, slot.yDisplayPosition,
+                    slot.xDisplayPosition+16, slot.yDisplayPosition+16, NEUOverlay.overlayColourDark);
+            GlStateManager.depthMask(true);
+            GlStateManager.popMatrix();
+        }
+
         GuiContainer $this = (GuiContainer)(Object)this;
         ItemStack stack = slot.getStack();
 
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinGuiInventory.java b/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinGuiInventory.java
new file mode 100644
index 00000000..9a5d739e
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinGuiInventory.java
@@ -0,0 +1,22 @@
+package io.github.moulberry.notenoughupdates.mixins;
+
+import io.github.moulberry.notenoughupdates.NEUEventListener;
+import io.github.moulberry.notenoughupdates.NotEnoughUpdates;
+import net.minecraft.client.gui.inventory.GuiInventory;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+@Mixin(GuiInventory.class)
+public class MixinGuiInventory {
+
+    @Inject(method="drawGuiContainerForegroundLayer", at=@At("HEAD"), cancellable = true)
+    protected void drawGuiContainerForegroundLayer(int mouseX, int mouseY, CallbackInfo ci) {
+        if(NotEnoughUpdates.INSTANCE.config.inventoryButtons.hideCrafting ||
+                NEUEventListener.disableCraftingText) {
+            ci.cancel();
+        }
+    }
+
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinItemStack.java b/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinItemStack.java
index c20ccebe..239dbe93 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinItemStack.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinItemStack.java
@@ -28,6 +28,10 @@ public class MixinItemStack {
     @Inject(method="getDisplayName",at=@At("HEAD"), cancellable=true)
     public void getDisplayName(CallbackInfoReturnable<String> returnable) {
         try {
+            if(stackTagCompound == null || !stackTagCompound.hasKey("ExtraAttributes", 10)) {
+                return;
+            }
+
             String customName = NotEnoughUpdates.INSTANCE.manager.itemRenameJson
                     .get(stackTagCompound.getCompoundTag("ExtraAttributes").getString("uuid")).getAsString();
             if(customName != null && !customName.equals("")) {
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinLayerCreeperCharge.java b/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinLayerCreeperCharge.java
deleted file mode 100644
index e6c9f048..00000000
--- a/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinLayerCreeperCharge.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package io.github.moulberry.notenoughupdates.mixins;
-
-import io.github.moulberry.notenoughupdates.miscfeatures.MiningStuff;
-import net.minecraft.client.renderer.GlStateManager;
-import net.minecraft.client.renderer.entity.layers.LayerCreeperCharge;
-import net.minecraft.entity.monster.EntityCreeper;
-import org.lwjgl.util.vector.Vector3f;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.injection.At;
-import org.spongepowered.asm.mixin.injection.Inject;
-import org.spongepowered.asm.mixin.injection.Redirect;
-
-@Mixin(LayerCreeperCharge.class)
-public class MixinLayerCreeperCharge {
-
-    /*@Redirect(method="doRenderLayer", at=@At(
-            value = "INVOKE",
-            target = "Lnet/minecraft/client/renderer/GlStateManager;color(FFFF)V"))
-    public void doRenderLayer_color(float red, float green, float blue, float alpha) {
-        Vector3f col = MiningStuff.getCreeperColour();
-        GlStateManager.color(col.getX(), col.getY(), col.getZ(), alpha);
-    }*/
-
-
-
-}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinNetHandlerPlayClient.java b/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinNetHandlerPlayClient.java
index f20a5557..7f88b369 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinNetHandlerPlayClient.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinNetHandlerPlayClient.java
@@ -31,7 +31,7 @@ public class MixinNetHandlerPlayClient {
 
     @Inject(method="handleSetSlot", at=@At("HEAD"))
     public void handleSetSlot(S2FPacketSetSlot packetIn, CallbackInfo ci) {
-        EnchantingSolvers.processInventoryContents();
+        EnchantingSolvers.processInventoryContents(false);
     }
 
     @Inject(method="handleBlockChange", at=@At("HEAD"))
@@ -45,13 +45,13 @@ public class MixinNetHandlerPlayClient {
         FlyFix.onReceiveAbilities(packetIn);
     }
 
-    @Inject(method="addToSendQueue", at=@At("HEAD"))
+    /*@Inject(method="addToSendQueue", at=@At("HEAD"))
     public void addToSendQueue(Packet packet, CallbackInfo ci) {
         if(packet instanceof C13PacketPlayerAbilities) {
             C13PacketPlayerAbilities abilities = (C13PacketPlayerAbilities) packet;
             FlyFix.onSendAbilities(abilities);
         }
-    }
+    }*/
 
     @Inject(method="handlePlayerListHeaderFooter", at=@At("HEAD"))
     public void handlePlayerListHeaderFooter(S47PacketPlayerListHeaderFooter packetIn, CallbackInfo ci) {
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinPlayerControllerMP.java b/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinPlayerControllerMP.java
index 2183e437..308dc779 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinPlayerControllerMP.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinPlayerControllerMP.java
@@ -20,10 +20,10 @@ public class MixinPlayerControllerMP {
     @Inject(method="clickBlock", at=@At("HEAD"), cancellable = true)
     public void clickBlock(BlockPos loc, EnumFacing face, CallbackInfoReturnable<Boolean> cir) {
         ItemCooldowns.blockClicked(loc);
-        if(MiningStuff.blockClicked(loc)) {
+        /*if(MiningStuff.blockClicked(loc)) {
             cir.setReturnValue(false);
             ((PlayerControllerMP)(Object)this).resetBlockRemoving();
-        }
+        }*/
     }
 
 }
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinRenderItem.java b/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinRenderItem.java
index afd44fd3..5fcb6e84 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinRenderItem.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinRenderItem.java
@@ -1,13 +1,20 @@
 package io.github.moulberry.notenoughupdates.mixins;
 
+import io.github.moulberry.notenoughupdates.NEUEventListener;
+import io.github.moulberry.notenoughupdates.NEUOverlay;
+import io.github.moulberry.notenoughupdates.NotEnoughUpdates;
 import io.github.moulberry.notenoughupdates.miscfeatures.ItemCooldowns;
 import io.github.moulberry.notenoughupdates.miscfeatures.ItemRarityHalo;
+import net.minecraft.client.Minecraft;
 import net.minecraft.client.gui.FontRenderer;
+import net.minecraft.client.gui.Gui;
+import net.minecraft.client.gui.GuiTextField;
 import net.minecraft.client.renderer.GlStateManager;
 import net.minecraft.client.renderer.Tessellator;
 import net.minecraft.client.renderer.WorldRenderer;
 import net.minecraft.client.renderer.entity.RenderItem;
 import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
+import net.minecraft.client.resources.model.IBakedModel;
 import net.minecraft.item.ItemStack;
 import org.lwjgl.input.Keyboard;
 import org.spongepowered.asm.mixin.Mixin;
@@ -17,6 +24,8 @@ import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
 
+import java.awt.*;
+
 @Mixin({RenderItem.class})
 public abstract class MixinRenderItem {
 
@@ -34,8 +43,81 @@ public abstract class MixinRenderItem {
         Tessellator.getInstance().draw();
     }
 
+    @Inject(method="renderItemIntoGUI", at=@At("HEAD"))
+    public void renderItemHead(ItemStack stack, int x, int y, CallbackInfo ci) {
+        if(NotEnoughUpdates.INSTANCE.overlay.searchMode && NEUEventListener.drawingGuiScreen) {
+            boolean matches = false;
+
+            GuiTextField textField = NotEnoughUpdates.INSTANCE.overlay.getTextField();
+
+            if(textField.getText().trim().isEmpty()) {
+                matches = true;
+            } else if(stack != null) {
+                for(String search : textField.getText().split("\\|")) {
+                    matches |= NotEnoughUpdates.INSTANCE.manager.doesStackMatchSearch(stack, search.trim());
+                }
+            }
+            if(matches) {
+                GlStateManager.pushMatrix();
+                GlStateManager.translate(0, 0, 100 + Minecraft.getMinecraft().getRenderItem().zLevel);
+                GlStateManager.depthMask(false);
+                Gui.drawRect(x, y, x+16, y+16, NEUOverlay.overlayColourLight);
+                GlStateManager.depthMask(true);
+                GlStateManager.popMatrix();
+            }
+        }
+    }
+
+    @Inject(method="renderItemIntoGUI", at=@At("RETURN"))
+    public void renderItemReturn(ItemStack stack, int x, int y, CallbackInfo ci) {
+        if(stack != null && stack.stackSize != 1) return;
+        if(NotEnoughUpdates.INSTANCE.overlay.searchMode && NEUEventListener.drawingGuiScreen) {
+            boolean matches = false;
+
+            GuiTextField textField = NotEnoughUpdates.INSTANCE.overlay.getTextField();
+
+            if(textField.getText().trim().isEmpty()) {
+                matches = true;
+            } else if(stack != null) {
+                for(String search : textField.getText().split("\\|")) {
+                    matches |= NotEnoughUpdates.INSTANCE.manager.doesStackMatchSearch(stack, search.trim());
+                }
+            }
+            if(!matches) {
+                GlStateManager.pushMatrix();
+                GlStateManager.translate(0, 0, 110 + Minecraft.getMinecraft().getRenderItem().zLevel);
+                Gui.drawRect(x, y, x+16, y+16, NEUOverlay.overlayColourDark);
+                GlStateManager.popMatrix();
+            }
+        }
+    }
+
     @Inject(method="renderItemOverlayIntoGUI", at=@At("RETURN"))
     public void renderItemOverlayIntoGUI(FontRenderer fr, ItemStack stack, int xPosition, int yPosition, String text, CallbackInfo ci) {
+        if(stack != null && stack.stackSize != 1) {
+            if(NotEnoughUpdates.INSTANCE.overlay.searchMode && NEUEventListener.drawingGuiScreen) {
+                boolean matches = false;
+
+                GuiTextField textField = NotEnoughUpdates.INSTANCE.overlay.getTextField();
+
+                if(textField.getText().trim().isEmpty()) {
+                    matches = true;
+                } else {
+                    for(String search : textField.getText().split("\\|")) {
+                        matches |= NotEnoughUpdates.INSTANCE.manager.doesStackMatchSearch(stack, search.trim());
+                    }
+                }
+                if(!matches) {
+                    GlStateManager.pushMatrix();
+                    GlStateManager.translate(0, 0, 110 + Minecraft.getMinecraft().getRenderItem().zLevel);
+                    GlStateManager.disableDepth();
+                    Gui.drawRect(xPosition, yPosition, xPosition+16, yPosition+16, NEUOverlay.overlayColourDark);
+                    GlStateManager.enableDepth();
+                    GlStateManager.popMatrix();
+                }
+            }
+        }
+
         if(stack == null) return;
 
         float damageOverride = ItemCooldowns.getDurabilityOverride(stack);
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/options/NEUConfig.java b/src/main/java/io/github/moulberry/notenoughupdates/options/NEUConfig.java
index 2ff43bd8..ae011f92 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/options/NEUConfig.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/options/NEUConfig.java
@@ -8,6 +8,8 @@ import io.github.moulberry.notenoughupdates.core.config.Config;
 import io.github.moulberry.notenoughupdates.core.config.Position;
 import io.github.moulberry.notenoughupdates.core.config.annotations.*;
 import io.github.moulberry.notenoughupdates.core.config.gui.GuiPositionEditor;
+import io.github.moulberry.notenoughupdates.miscgui.GuiInvButtonEditor;
+import io.github.moulberry.notenoughupdates.miscgui.NEUOverlayPlacements;
 import io.github.moulberry.notenoughupdates.overlays.*;
 import io.github.moulberry.notenoughupdates.util.SBInfo;
 import net.minecraft.client.Minecraft;
@@ -70,6 +72,12 @@ public class NEUConfig extends Config {
             case 5:
                 editOverlay(activeConfigCategory, OverlayManager.timersOverlay, miscOverlays.todoPosition);
                 return;
+            case 6:
+                NotEnoughUpdates.INSTANCE.openGui = new NEUOverlayPlacements();
+                return;
+            case 7:
+                NotEnoughUpdates.INSTANCE.openGui = new GuiInvButtonEditor();
+                return;
 
         }
     }
@@ -102,6 +110,13 @@ public class NEUConfig extends Config {
     )
     public Toolbar toolbar = new Toolbar();
 
+    @Expose
+    @Category(
+            name = "Inventory Buttons",
+            desc = "Inventory Buttons"
+    )
+    public InventoryButtons inventoryButtons = new InventoryButtons();
+
     @Expose
     @Category(
             name = "Tooltip Tweaks",
@@ -392,6 +407,14 @@ public class NEUConfig extends Config {
     }
 
     public static class Toolbar {
+        @Expose
+        @ConfigOption(
+                name = "Edit Toolbar Positions",
+                desc = "Edit the position of the QuickCommands / Search Bar"
+        )
+        @ConfigEditorButton(runnableId = 6, buttonText = "Edit")
+        public boolean positionButton = true;
+
         @Expose
         @ConfigOption(
                 name = "Show Quick Commands",
@@ -443,6 +466,34 @@ public class NEUConfig extends Config {
         public int quickCommandsClickType = 0;
     }
 
+    public static class InventoryButtons {
+        @Expose
+        @ConfigOption(
+                name = "Open Button Editor",
+                desc = "Open button editor GUI (/neubuttons)"
+        )
+        @ConfigEditorButton(runnableId = 7, buttonText = "Open")
+        public boolean openEditorButton = true;
+
+        @Expose
+        @ConfigOption(
+                name = "Always Hide \"Crafting\" Text",
+                desc = "Hide crafting text in inventory, even when no button is there"
+        )
+        @ConfigEditorBoolean
+        public boolean hideCrafting = false;
+
+        @Expose
+        @ConfigOption(
+                name = "Button Click Type",
+                desc = "Change the click type needed to trigger commands"
+        )
+        @ConfigEditorDropdown(
+                values = {"Mouse Down", "Mouse Up"}
+        )
+        public int clickType = 0;
+    }
+
     public static class TooltipTweaks {
         @ConfigOption(
                 name = "Tooltip Price Information",
@@ -1035,24 +1086,25 @@ public class NEUConfig extends Config {
         )
         @ConfigAccordionId(id = 0)
         public int todoStyle = 0;
-    }
 
-    public static class EnchSolvers {
         @Expose
         @ConfigOption(
-                name = "Enable Solvers",
-                desc = "Turn on solvers for the experimentation table"
+                name = "Todo Icons",
+                desc = "Add little item icons next to the lines in the todo overlay"
         )
         @ConfigEditorBoolean
-        public boolean enableEnchantingSolvers = true;
+        @ConfigAccordionId(id = 0)
+        public boolean todoIcons = true;
+    }
 
+    public static class EnchSolvers {
         @Expose
         @ConfigOption(
-                name = "Prevent Misclicks",
-                desc = "Prevent accidentally failing the Chronomatron and Ultrasequencer experiments"
+                name = "Enable Solvers",
+                desc = "Turn on solvers for the experimentation table"
         )
         @ConfigEditorBoolean
-        public boolean preventMisclicks = true;
+        public boolean enableEnchantingSolvers = true;
 
         @Expose
         @ConfigOption(
@@ -1282,21 +1334,6 @@ public class NEUConfig extends Config {
         @ConfigEditorBoolean
         public boolean titaniumAlert = true;
 
-        @Expose
-        @ConfigOption(
-                name = "Don't Mine Stone",
-                desc = "Prevent mining stone blocks in mining areas"
-        )
-        @ConfigEditorBoolean
-        public boolean dontMineStone = true;
-
-        @Expose
-        @ConfigOption(
-                name = "Reveal Mist Creepers",
-                desc = "Make the creepers in the Dwarven Mines mist visible"
-        )
-        @ConfigEditorBoolean
-        public boolean revealMistCreepers = true;
     }
 
     public static class NeuAuctionHouse {
@@ -1494,6 +1531,14 @@ public class NEUConfig extends Config {
                 values = {"Background", "No Shadow", "Shadow Only", "Full Shadow"}
         )
         public int petInfoOverlayStyle = 0;
+
+        @Expose
+        @ConfigOption(
+                name = "Show Last Pet",
+                desc = "Show 2 pets on the overlay\nUseful if training two pets at once with autopet"
+        )
+        @ConfigEditorBoolean
+        public boolean dualPets = false;
     }
 
     public static class AuctionHouseSearch {
@@ -1586,6 +1631,7 @@ public class NEUConfig extends Config {
 
     public static class Hidden {
         @Expose public HashMap<String, HiddenProfileSpecific> profileSpecific = new HashMap<>();
+        @Expose public List<InventoryButton> inventoryButtons = createDefaultInventoryButtons();
 
         @Expose public boolean enableItemEditing = false;
         @Expose public boolean cacheRenderedItempane = true;
@@ -1612,6 +1658,95 @@ public class NEUConfig extends Config {
                                 "Looting:\u003e:3:5:0");
     }
 
+    public static List<InventoryButton> createDefaultInventoryButtons() {
+        List<InventoryButton> buttons = new ArrayList<>();
+        //Below crafting
+        buttons.add(new InventoryButton(87, 63, null, true, false, false, 0, ""));
+        buttons.add(new InventoryButton(87+21, 63, null, true, false, false, 0, ""));
+        buttons.add(new InventoryButton(87+21*2, 63, null, true, false, false, 0, ""));
+        buttons.add(new InventoryButton(87+21*3, 63, null, true, false, false, 0, ""));
+
+        //Above crafting
+        buttons.add(new InventoryButton(87, 5, null, true, false, false, 0, ""));
+        buttons.add(new InventoryButton(87+21, 5, null, true, false, false, 0, ""));
+        buttons.add(new InventoryButton(87+21*2, 5, null, true, false, false, 0, ""));
+        buttons.add(new InventoryButton(87+21*3, 5, null, true, false, false, 0, ""));
+
+        //Crafting square
+        buttons.add(new InventoryButton(87, 25, null, true, false, false, 0, ""));
+        buttons.add(new InventoryButton(87+18, 25, null, true, false, false, 0, ""));
+        buttons.add(new InventoryButton(87, 25+18, null, true, false, false, 0, ""));
+        buttons.add(new InventoryButton(87+18, 25+18, null, true, false, false, 0, ""));
+
+        //Crafting result
+        buttons.add(new InventoryButton(143, 35, null, true, false, false, 0, ""));
+
+        //Player menu area
+        buttons.add(new InventoryButton(60, 8, null, true, false, false, 0, ""));
+        buttons.add(new InventoryButton(60, 60, null, true, false, false, 0, ""));
+        buttons.add(new InventoryButton(26, 8, null, true, false, false, 0, ""));
+        buttons.add(new InventoryButton(26, 60, null, true, false, false, 0, ""));
+
+        //Right side
+        for(int i=0; i<8; i++) {
+            int y = 2+20*i;
+            if(y < 80) {
+                buttons.add(new InventoryButton(2, 2+20*i, null, false, true, false, 0, ""));
+            } else {
+                buttons.add(new InventoryButton(2, 2+20*i-166, null, false, true, true, 0, ""));
+            }
+        }
+
+        //Top side
+        for(int i=0; i<8; i++) {
+            buttons.add(new InventoryButton(4+21*i, -19, null, false, false, false, 0, ""));
+        }
+
+        //Left side
+        for(int i=0; i<8; i++) {
+            int y = 2+20*i;
+            if(y < 80) {
+                buttons.add(new InventoryButton(-19, 2+20*i, null, false, false, false, 0, ""));
+            } else {
+                buttons.add(new InventoryButton(-19, 2+20*i-166, null, false, false, true, 0, ""));
+            }
+        }
+
+        //Bottom side
+        for(int i=0; i<8; i++) {
+            buttons.add(new InventoryButton(4+21*i, 2, null, false, false, true, 0, ""));
+        }
+        return buttons;
+    }
+
+    public static class InventoryButton {
+        @Expose public int x;
+        @Expose public int y;
+        @Expose public boolean playerInvOnly;
+
+        @Expose public boolean anchorRight;
+        @Expose public boolean anchorBottom;
+
+        @Expose public int backgroundIndex;
+        @Expose public String command;
+        @Expose public String icon;
+
+        public boolean isActive() {
+            return command.trim().length() > 0;
+        }
+
+        public InventoryButton(int x, int y, String icon, boolean playerInvOnly, boolean anchorRight, boolean anchorBottom, int backgroundIndex, String command) {
+            this.x = x;
+            this.y = y;
+            this.icon = icon;
+            this.playerInvOnly = playerInvOnly;
+            this.anchorRight = anchorRight;
+            this.anchorBottom = anchorBottom;
+            this.backgroundIndex = backgroundIndex;
+            this.command = command;
+        }
+    }
+
     public static class DungeonMap {
         @Expose
         @ConfigOption(
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/overlays/TimersOverlay.java b/src/main/java/io/github/moulberry/notenoughupdates/overlays/TimersOverlay.java
index da2b9bee..d4132f76 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/overlays/TimersOverlay.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/overlays/TimersOverlay.java
@@ -103,6 +103,11 @@ public class TimersOverlay extends TextOverlay {
 
     @Override
     protected void renderLine(String line, Vector2f position, boolean dummy) {
+        if(!NotEnoughUpdates.INSTANCE.config.miscOverlays.todoIcons) {
+            return;
+        }
+        GlStateManager.enableDepth();
+
         ItemStack icon = null;
 
         String clean = Utils.cleanColour(line);
@@ -115,7 +120,7 @@ public class TimersOverlay extends TextOverlay {
                 if(FETCHUR_ICONS == null) {
                     FETCHUR_ICONS = new ItemStack[] {
                             new ItemStack(Blocks.wool, 50, 14),
-                            new ItemStack(Blocks.glass, 20, 4),
+                            new ItemStack(Blocks.stained_glass, 20, 4),
                             new ItemStack(Items.compass, 1, 0),
                             new ItemStack(Items.prismarine_crystals, 20, 0),
                             new ItemStack(Items.fireworks, 1, 0),
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/GuiProfileViewer.java b/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/GuiProfileViewer.java
index 4a9a9257..13ada4bf 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/GuiProfileViewer.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/GuiProfileViewer.java
@@ -840,7 +840,7 @@ public class GuiProfileViewer extends GuiScreen {
             Utils.renderAlignedString(EnumChatFormatting.YELLOW+"Secrets (Total)  ",
                     EnumChatFormatting.WHITE.toString()+shortNumberFormat(secrets, 0), x, miscTopY+20, sectionWidth);
             Utils.renderAlignedString(EnumChatFormatting.YELLOW+"Secrets (/Run)  ",
-                    EnumChatFormatting.WHITE.toString()+(Math.round(secrets/Math.max(1, totalRunsF5)*100)/100f), x, miscTopY+30, sectionWidth);
+                    EnumChatFormatting.WHITE.toString()+(Math.round(secrets/Math.max(1, totalRuns)*100)/100f), x, miscTopY+30, sectionWidth);
             Utils.renderAlignedString(EnumChatFormatting.YELLOW+"Mob Kills (Total)  ",
                     EnumChatFormatting.WHITE.toString()+shortNumberFormat(mobKills, 0), x, miscTopY+40, sectionWidth);
 
@@ -2528,7 +2528,6 @@ public class GuiProfileViewer extends GuiScreen {
                     Arrays.fill(entityPlayer.inventory.armorInventory, null);
                 }
             }
-            System.out.println(entityPlayer.getUniqueID().toString());
             if(entityPlayer.getUniqueID().toString().equals("ae6193ab-494a-4719-b6e7-d50392c8f012")) {
                 entityPlayer.inventory.armorInventory[3] = NotEnoughUpdates.INSTANCE.manager.jsonToStack(
                         NotEnoughUpdates.INSTANCE.manager.getItemInformation().get("SMALL_BACKPACK"));
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/ProfileViewer.java b/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/ProfileViewer.java
index c61a0914..b2b19189 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/ProfileViewer.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/profileviewer/ProfileViewer.java
@@ -398,7 +398,7 @@ public class ProfileViewer {
                                                 JsonObject bzInfo2 = manager.auctionManager.getBazaarInfo(internalname2);
 
                                                 int auctionPrice2;
-                                                if(bzInfo2 != null && bzInfo.has("curr_sell")) {
+                                                if(bzInfo2 != null && bzInfo2.has("curr_sell")) {
                                                     auctionPrice2 = (int)bzInfo2.get("curr_sell").getAsFloat();
                                                 } else {
                                                     auctionPrice2 = (int)manager.auctionManager.getItemAvgBin(internalname2);
diff --git a/src/main/resources/assets/notenoughupdates/capes/alexxoffi.png b/src/main/resources/assets/notenoughupdates/capes/alexxoffi.png
new file mode 100644
index 00000000..dd81d641
Binary files /dev/null and b/src/main/resources/assets/notenoughupdates/capes/alexxoffi.png differ
diff --git a/src/main/resources/assets/notenoughupdates/capes/alexxoffi_preview.png b/src/main/resources/assets/notenoughupdates/capes/alexxoffi_preview.png
new file mode 100644
index 00000000..146e487f
Binary files /dev/null and b/src/main/resources/assets/notenoughupdates/capes/alexxoffi_preview.png differ
diff --git a/src/main/resources/assets/notenoughupdates/capes/dsm.png b/src/main/resources/assets/notenoughupdates/capes/dsm.png
index 0b691c9c..c452db0d 100644
Binary files a/src/main/resources/assets/notenoughupdates/capes/dsm.png and b/src/main/resources/assets/notenoughupdates/capes/dsm.png differ
diff --git a/src/main/resources/assets/notenoughupdates/capes/dsm_preview.png b/src/main/resources/assets/notenoughupdates/capes/dsm_preview.png
new file mode 100644
index 00000000..7589e2a9
Binary files /dev/null and b/src/main/resources/assets/notenoughupdates/capes/dsm_preview.png differ
diff --git a/src/main/resources/assets/notenoughupdates/capes/furf.png b/src/main/resources/assets/notenoughupdates/capes/furf.png
index ce9f4c19..61f1e45a 100644
Binary files a/src/main/resources/assets/notenoughupdates/capes/furf.png and b/src/main/resources/assets/notenoughupdates/capes/furf.png differ
diff --git a/src/main/resources/assets/notenoughupdates/capes/furf_preview.png b/src/main/resources/assets/notenoughupdates/capes/furf_preview.png
index a4ee6851..c2a2a106 100644
Binary files a/src/main/resources/assets/notenoughupdates/capes/furf_preview.png and b/src/main/resources/assets/notenoughupdates/capes/furf_preview.png differ
diff --git a/src/main/resources/assets/notenoughupdates/capes/jakethybro.png b/src/main/resources/assets/notenoughupdates/capes/jakethybro.png
new file mode 100644
index 00000000..3e957503
Binary files /dev/null and b/src/main/resources/assets/notenoughupdates/capes/jakethybro.png differ
diff --git a/src/main/resources/assets/notenoughupdates/capes/jakethybro_preview.png b/src/main/resources/assets/notenoughupdates/capes/jakethybro_preview.png
new file mode 100644
index 00000000..96483e98
Binary files /dev/null and b/src/main/resources/assets/notenoughupdates/capes/jakethybro_preview.png differ
diff --git a/src/main/resources/assets/notenoughupdates/capes/parallax.png b/src/main/resources/assets/notenoughupdates/capes/parallax.png
new file mode 100644
index 00000000..b7b4a027
Binary files /dev/null and b/src/main/resources/assets/notenoughupdates/capes/parallax.png differ
diff --git a/src/main/resources/assets/notenoughupdates/capes/zera.png b/src/main/resources/assets/notenoughupdates/capes/zera.png
new file mode 100644
index 00000000..74121976
Binary files /dev/null and b/src/main/resources/assets/notenoughupdates/capes/zera.png differ
diff --git a/src/main/resources/assets/notenoughupdates/capes/zera_preview.png b/src/main/resources/assets/notenoughupdates/capes/zera_preview.png
new file mode 100644
index 00000000..684cbd9e
Binary files /dev/null and b/src/main/resources/assets/notenoughupdates/capes/zera_preview.png differ
diff --git a/src/main/resources/assets/notenoughupdates/invbuttons/editor.png b/src/main/resources/assets/notenoughupdates/invbuttons/editor.png
new file mode 100644
index 00000000..0d58e0c9
Binary files /dev/null and b/src/main/resources/assets/notenoughupdates/invbuttons/editor.png differ
diff --git a/src/main/resources/assets/notenoughupdates/invbuttons/expanded_inventory.png b/src/main/resources/assets/notenoughupdates/invbuttons/expanded_inventory.png
new file mode 100644
index 00000000..d941fa6e
Binary files /dev/null and b/src/main/resources/assets/notenoughupdates/invbuttons/expanded_inventory.png differ
diff --git a/src/main/resources/assets/notenoughupdates/invbuttons/extraicons.json b/src/main/resources/assets/notenoughupdates/invbuttons/extraicons.json
new file mode 100644
index 00000000..5a99f3f0
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/invbuttons/extraicons.json
@@ -0,0 +1,18 @@
+{
+    "baubles ring": "baubles",
+	"baubles gold ring": "baubles_gold",
+	"cross x": "cross",
+	"green check mark": "green_check",
+	"white check mark": "white_check",
+	"question mark help": "question",
+	"settings cog config": "settings",
+	"accessory ring": "accessory",
+	"accessory ring gold": "accessory_gold",
+	"armor chestplate": "armor",
+	"armor gold chestplate": "armor_gold",
+	"pet cat": "pet",
+	"pet cat gold": "pet_gold",
+	"skyblock menu": "skyblock_menu",
+	"recipe book": "recipe",
+	"search glass": "search"
+}
\ No newline at end of file
diff --git a/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/accessory.png b/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/accessory.png
new file mode 100644
index 00000000..867b00fd
Binary files /dev/null and b/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/accessory.png differ
diff --git a/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/accessory_gold.png b/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/accessory_gold.png
new file mode 100644
index 00000000..70f8558a
Binary files /dev/null and b/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/accessory_gold.png differ
diff --git a/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/armor.png b/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/armor.png
new file mode 100644
index 00000000..69edc534
Binary files /dev/null and b/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/armor.png differ
diff --git a/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/armor_gold.png b/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/armor_gold.png
new file mode 100644
index 00000000..ee606367
Binary files /dev/null and b/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/armor_gold.png differ
diff --git a/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/baubles.png b/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/baubles.png
new file mode 100644
index 00000000..9c1f5035
Binary files /dev/null and b/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/baubles.png differ
diff --git a/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/baubles_gold.png b/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/baubles_gold.png
new file mode 100644
index 00000000..963ce85d
Binary files /dev/null and b/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/baubles_gold.png differ
diff --git a/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/cross.png b/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/cross.png
new file mode 100644
index 00000000..6ee727a6
Binary files /dev/null and b/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/cross.png differ
diff --git a/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/green_check.png b/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/green_check.png
new file mode 100644
index 00000000..5c5ffb45
Binary files /dev/null and b/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/green_check.png differ
diff --git a/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/pet.png b/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/pet.png
new file mode 100644
index 00000000..9aebf1fd
Binary files /dev/null and b/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/pet.png differ
diff --git a/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/pet_gold.png b/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/pet_gold.png
new file mode 100644
index 00000000..f86e367a
Binary files /dev/null and b/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/pet_gold.png differ
diff --git a/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/question.png b/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/question.png
new file mode 100644
index 00000000..3f9fe254
Binary files /dev/null and b/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/question.png differ
diff --git a/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/recipe.png b/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/recipe.png
new file mode 100644
index 00000000..4eaca5c8
Binary files /dev/null and b/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/recipe.png differ
diff --git a/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/search.png b/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/search.png
new file mode 100644
index 00000000..70a0476e
Binary files /dev/null and b/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/search.png differ
diff --git a/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/settings.png b/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/settings.png
new file mode 100644
index 00000000..481a0588
Binary files /dev/null and b/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/settings.png differ
diff --git a/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/skyblock_menu.png b/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/skyblock_menu.png
new file mode 100644
index 00000000..c0f62308
Binary files /dev/null and b/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/skyblock_menu.png differ
diff --git a/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/white_check.png b/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/white_check.png
new file mode 100644
index 00000000..d646e498
Binary files /dev/null and b/src/main/resources/assets/notenoughupdates/invbuttons/extraicons/white_check.png differ
diff --git a/src/main/resources/assets/notenoughupdates/invbuttons/presets.json b/src/main/resources/assets/notenoughupdates/invbuttons/presets.json
new file mode 100644
index 00000000..7692fc07
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/invbuttons/presets.json
@@ -0,0 +1,2729 @@
+{
+   "Empty":[
+      {
+         "x":87,
+         "y":63,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"",
+         "icon":""
+      },
+      {
+         "x":108,
+         "y":63,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"",
+         "icon":""
+      },
+      {
+         "x":129,
+         "y":63,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"",
+         "icon":""
+      },
+      {
+         "x":150,
+         "y":63,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"",
+         "icon":""
+      },
+      {
+         "x":87,
+         "y":5,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":108,
+         "y":5,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":129,
+         "y":5,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":150,
+         "y":5,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":87,
+         "y":25,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":105,
+         "y":25,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":87,
+         "y":43,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":105,
+         "y":43,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":143,
+         "y":35,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":60,
+         "y":8,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"",
+         "icon":""
+      },
+      {
+         "x":60,
+         "y":60,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":26,
+         "y":8,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":26,
+         "y":60,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":2,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":2,
+         "y":22,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":2,
+         "y":42,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":2,
+         "y":62,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":2,
+         "y":-84,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":2,
+         "y":-64,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":"",
+         "icon":""
+      },
+      {
+         "x":2,
+         "y":-44,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":2,
+         "y":-24,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":4,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":25,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":46,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":67,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":88,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":109,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":130,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":151,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":22,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":42,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":62,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":-84,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":-64,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":-44,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":-24,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":4,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":25,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":46,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":67,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":88,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":109,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":130,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":151,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      }
+   ],
+   "Empty (Dark)":[
+      {
+         "x":87,
+         "y":63,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":1,
+         "command":"",
+         "icon":""
+      },
+      {
+         "x":108,
+         "y":63,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":1,
+         "command":"",
+         "icon":""
+      },
+      {
+         "x":129,
+         "y":63,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":1,
+         "command":"",
+         "icon":""
+      },
+      {
+         "x":150,
+         "y":63,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":1,
+         "command":"",
+         "icon":""
+      },
+      {
+         "x":87,
+         "y":5,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":108,
+         "y":5,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":129,
+         "y":5,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":150,
+         "y":5,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":87,
+         "y":25,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":105,
+         "y":25,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":87,
+         "y":43,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":105,
+         "y":43,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":143,
+         "y":35,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":60,
+         "y":8,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":1,
+         "command":"",
+         "icon":""
+      },
+      {
+         "x":60,
+         "y":60,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":26,
+         "y":8,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":26,
+         "y":60,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":2,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":false,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":2,
+         "y":22,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":false,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":2,
+         "y":42,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":false,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":2,
+         "y":62,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":false,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":2,
+         "y":-84,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":true,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":2,
+         "y":-64,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":true,
+         "backgroundIndex":1,
+         "command":"",
+         "icon":""
+      },
+      {
+         "x":2,
+         "y":-44,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":true,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":2,
+         "y":-24,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":true,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":4,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":25,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":46,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":67,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":88,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":109,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":130,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":151,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":22,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":42,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":62,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":-84,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":-64,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":-44,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":-24,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":4,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":25,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":46,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":67,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":88,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":109,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":130,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":1,
+         "command":""
+      },
+      {
+         "x":151,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":1,
+         "command":""
+      }
+   ],
+   "Simple":[
+      {
+         "x":87,
+         "y":63,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"/warp home",
+         "icon":"skull:c9c8881e42915a9d29bb61a16fb26d059913204d265df5b439b3d792acd56"
+      },
+      {
+         "x":108,
+         "y":63,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"/warp hub",
+         "icon":"skull:d7cc6687423d0570d556ac53e0676cb563bbdd9717cd8269bdebed6f6d4e7bf8"
+      },
+      {
+         "x":129,
+         "y":63,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"/enderchest",
+         "icon":"ENDER_CHEST"
+      },
+      {
+         "x":150,
+         "y":63,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"/pets",
+         "icon":"extra:pet_gold"
+      },
+      {
+         "x":87,
+         "y":5,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":108,
+         "y":5,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":129,
+         "y":5,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":150,
+         "y":5,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":87,
+         "y":25,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":105,
+         "y":25,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":87,
+         "y":43,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":105,
+         "y":43,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":143,
+         "y":35,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":60,
+         "y":8,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":2,
+         "command":"/wardrobe",
+         "icon":"extra:baubles_gold"
+      },
+      {
+         "x":60,
+         "y":60,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":26,
+         "y":8,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":26,
+         "y":60,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":2,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":2,
+         "y":22,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":2,
+         "y":42,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":2,
+         "y":62,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":2,
+         "y":-84,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":2,
+         "y":-64,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":"",
+         "icon":"BONE"
+      },
+      {
+         "x":2,
+         "y":-44,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":2,
+         "y":-24,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":4,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":25,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":46,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":67,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":88,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":109,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":130,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":151,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":22,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":42,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":62,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":-84,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":-64,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":-44,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":-24,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":4,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":25,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":46,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":67,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":88,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":109,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":130,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":151,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      }
+   ],
+   "All Warps":[
+      {
+         "x":87,
+         "y":63,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"pets",
+         "icon":"extra:pet_gold"
+      },
+      {
+         "x":108,
+         "y":63,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"/wardrobe",
+         "icon":"extra:armor"
+      },
+      {
+         "x":129,
+         "y":63,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"/enderchest",
+         "icon":"ENDER_CHEST"
+      },
+      {
+         "x":150,
+         "y":63,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"hotm",
+         "icon":"skull:86f06eaa3004aeed09b3d5b45d976de584e691c0e9cade133635de93d23b9edb"
+      },
+      {
+         "x":87,
+         "y":5,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"",
+         "icon":"GOLD_BLOCK"
+      },
+      {
+         "x":108,
+         "y":5,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":129,
+         "y":5,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":150,
+         "y":5,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"/pv",
+         "icon":"PAINTING"
+      },
+      {
+         "x":87,
+         "y":25,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":105,
+         "y":25,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":87,
+         "y":43,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":105,
+         "y":43,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":143,
+         "y":35,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":60,
+         "y":8,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"",
+         "icon":"extra:baubles_gold"
+      },
+      {
+         "x":60,
+         "y":60,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":26,
+         "y":8,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":26,
+         "y":60,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":2,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"/ah",
+         "icon":"GOLD_BLOCK"
+      },
+      {
+         "x":2,
+         "y":22,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"/bz",
+         "icon":"GOLD_BARDING"
+      },
+      {
+         "x":2,
+         "y":42,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":2,
+         "y":62,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":2,
+         "y":-84,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":"/warp home",
+         "icon":"skull:c9c8881e42915a9d29bb61a16fb26d059913204d265df5b439b3d792acd56"
+      },
+      {
+         "x":2,
+         "y":-64,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":"/warp hub",
+         "icon":"skull:d7cc6687423d0570d556ac53e0676cb563bbdd9717cd8269bdebed6f6d4e7bf8"
+      },
+      {
+         "x":2,
+         "y":-44,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":"/warp dungeon_hub",
+         "icon":"skull:9b56895b9659896ad647f58599238af532d46db9c1b0389b8bbeb70999dab33d"
+      },
+      {
+         "x":2,
+         "y":-24,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":"/warp end",
+         "icon":"skull:7840b87d52271d2a755dedc82877e0ed3df67dcc42ea479ec146176b02779a5"
+      },
+      {
+         "x":4,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":25,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":46,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":67,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":88,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":109,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":130,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":151,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":22,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":42,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":62,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":-84,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":-64,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":-44,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":-24,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":4,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":"/warp barn",
+         "icon":"skull:4d3a6bd98ac1833c664c4909ff8d2dc62ce887bdcf3cc5b3848651ae5af6b"
+      },
+      {
+         "x":25,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":"/warp desert",
+         "icon":"skull:2116b9d8df346a25edd05f842e7a9345beaf16dca4118abf5a68c75bcaae10"
+      },
+      {
+         "x":46,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":"/warp gold",
+         "icon":"skull:73bc965d579c3c6039f0a17eb7c2e6faf538c7a5de8e60ec7a719360d0a857a9"
+      },
+      {
+         "x":67,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":"/warp deep",
+         "icon":"skull:569a1f114151b4521373f34bc14c2963a5011cdc25a6554c48c708cd96ebfc"
+      },
+      {
+         "x":88,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":"/warp mines",
+         "icon":"skull:c754318a3376f470e481dfcd6c83a59aa690ad4b4dd7577fdad1c2ef08d8aee6"
+      },
+      {
+         "x":109,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":"/warp park",
+         "icon":"skull:a221f813dacee0fef8c59f76894dbb26415478d9ddfc44c2e708a6d3b7549b"
+      },
+      {
+         "x":130,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":"/warp spider",
+         "icon":"skull:9d7e3b19ac4f3dee9c5677c135333b9d35a7f568b63d1ef4ada4b068b5a25"
+      },
+      {
+         "x":151,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":"/warp nether",
+         "icon":"skull:38957d5023c937c4c41aa2412d43410bda23cf79a9f6ab36b76fef2d7c429"
+      }
+   ],
+   "More Buttons":[
+      {
+         "x":87,
+         "y":63,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"/sbmenu",
+         "icon":"SKYBLOCK_MENU"
+      },
+      {
+         "x":108,
+         "y":63,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"enderchest",
+         "icon":"ENDER_CHEST"
+      },
+      {
+         "x":129,
+         "y":63,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"pets",
+         "icon":"BONE"
+      },
+      {
+         "x":150,
+         "y":63,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"effects",
+         "icon":"POTION"
+      },
+      {
+         "x":87,
+         "y":5,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"/recipes",
+         "icon":"extra:recipe"
+      },
+      {
+         "x":108,
+         "y":5,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"/collections",
+         "icon":"PAINTING"
+      },
+      {
+         "x":129,
+         "y":5,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"/scg",
+         "icon":"BOOK"
+      },
+      {
+         "x":150,
+         "y":5,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"/calendar",
+         "icon":"BOOK_AND_QUILL"
+      },
+      {
+         "x":87,
+         "y":25,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"",
+         "icon":"CHEST"
+      },
+      {
+         "x":105,
+         "y":25,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":87,
+         "y":43,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":105,
+         "y":43,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":143,
+         "y":35,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"craft",
+         "icon":"WORKBENCH"
+      },
+      {
+         "x":60,
+         "y":8,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":2,
+         "command":"/wardrobe",
+         "icon":"extra:baubles_gold"
+      },
+      {
+         "x":60,
+         "y":60,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":26,
+         "y":8,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":26,
+         "y":60,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":2,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"/warp hub",
+         "icon":"skull:d7cc6687423d0570d556ac53e0676cb563bbdd9717cd8269bdebed6f6d4e7bf8"
+      },
+      {
+         "x":2,
+         "y":22,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"/warp home",
+         "icon":"skull:c9c8881e42915a9d29bb61a16fb26d059913204d265df5b439b3d792acd56"
+      },
+      {
+         "x":2,
+         "y":42,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":2,
+         "y":62,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":2,
+         "y":-84,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":"",
+         "icon":""
+      },
+      {
+         "x":2,
+         "y":-64,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":"",
+         "icon":"BONE"
+      },
+      {
+         "x":2,
+         "y":-44,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":2,
+         "y":-24,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":4,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":25,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":46,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":67,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":88,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":109,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":130,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":151,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"/ah",
+         "icon":"GOLD_BLOCK"
+      },
+      {
+         "x":-19,
+         "y":22,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"/bz",
+         "icon":"GOLD_BARDING"
+      },
+      {
+         "x":-19,
+         "y":42,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":62,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":-84,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":-64,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":-44,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":-24,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":4,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":25,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":46,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":67,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":88,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":109,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":130,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":151,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      }
+   ],
+   "Dungeons":[
+      {
+         "x":87,
+         "y":63,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"warp dungeon_hub",
+         "icon":"skull:9b56895b9659896ad647f58599238af532d46db9c1b0389b8bbeb70999dab33d"
+      },
+      {
+         "x":108,
+         "y":63,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"reparty",
+         "icon":"COMMAND"
+      },
+      {
+         "x":129,
+         "y":63,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"/p FragRunBot azael_mew azael_mewh",
+         "icon":"GIANT_FRAGMENT_DIAMOND"
+      },
+      {
+         "x":150,
+         "y":63,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"pets",
+         "icon":"extra:pet_gold"
+      },
+      {
+         "x":87,
+         "y":5,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":108,
+         "y":5,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":129,
+         "y":5,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":150,
+         "y":5,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":87,
+         "y":25,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":105,
+         "y":25,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":87,
+         "y":43,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":105,
+         "y":43,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":143,
+         "y":35,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":60,
+         "y":8,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":2,
+         "command":"/wardrobe",
+         "icon":"extra:baubles_gold"
+      },
+      {
+         "x":60,
+         "y":60,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":26,
+         "y":8,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":26,
+         "y":60,
+         "playerInvOnly":true,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":2,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":2,
+         "y":22,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":2,
+         "y":42,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":2,
+         "y":62,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":2,
+         "y":-84,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":2,
+         "y":-64,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":"",
+         "icon":""
+      },
+      {
+         "x":2,
+         "y":-44,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":2,
+         "y":-24,
+         "playerInvOnly":false,
+         "anchorRight":true,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":4,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":25,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":46,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":67,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":88,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":109,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":130,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":151,
+         "y":-19,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":-19,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"joindungeon catacombs 1",
+         "icon":"GOLD_BONZO_HEAD"
+      },
+      {
+         "x":-19,
+         "y":22,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"joindungeon catacombs 2",
+         "icon":"GOLD_SCARF_HEAD"
+      },
+      {
+         "x":-19,
+         "y":42,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"joindungeon catacombs 3",
+         "icon":"GOLD_PROFESSOR_HEAD"
+      },
+      {
+         "x":-19,
+         "y":62,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":false,
+         "backgroundIndex":0,
+         "command":"joindungeon catacombs 4",
+         "icon":"GOLD_THORN_HEAD"
+      },
+      {
+         "x":-19,
+         "y":-84,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":"joindungeon catacombs 5",
+         "icon":"GOLD_LIVID_HEAD"
+      },
+      {
+         "x":-19,
+         "y":-64,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":"/joindungeon catacombs 6",
+         "icon":"GOLD_SADAN_HEAD"
+      },
+      {
+         "x":-19,
+         "y":-44,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":"joindungeons catacombs 7",
+         "icon":"GOLD_NECRON_HEAD"
+      },
+      {
+         "x":-19,
+         "y":-24,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":4,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":25,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":46,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":67,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":88,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":109,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":130,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      },
+      {
+         "x":151,
+         "y":2,
+         "playerInvOnly":false,
+         "anchorRight":false,
+         "anchorBottom":true,
+         "backgroundIndex":0,
+         "command":""
+      }
+   ]
+}
\ No newline at end of file
diff --git a/src/main/resources/assets/notenoughupdates/search_bar.png b/src/main/resources/assets/notenoughupdates/search_bar.png
new file mode 100644
index 00000000..c029ee85
Binary files /dev/null and b/src/main/resources/assets/notenoughupdates/search_bar.png differ
diff --git a/src/main/resources/assets/notenoughupdates/search_bar_gold.png b/src/main/resources/assets/notenoughupdates/search_bar_gold.png
new file mode 100644
index 00000000..481b4930
Binary files /dev/null and b/src/main/resources/assets/notenoughupdates/search_bar_gold.png differ
diff --git a/src/main/resources/assets/notenoughupdates/shaders/capes/planets/planets.frag b/src/main/resources/assets/notenoughupdates/shaders/capes/planets/planets.frag
new file mode 100644
index 00000000..2c34682a
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/shaders/capes/planets/planets.frag
@@ -0,0 +1,185 @@
+#version 120
+
+varying vec4 passColour;
+varying vec3 passNormal;
+varying vec3 passVertex;
+
+uniform sampler2D textureIn;
+uniform int millis;
+uniform int planetType;
+uniform vec3 sunVec;
+
+vec4 permute(vec4 x){return mod(((x*34.0)+1.0)*x, 289.0);}
+float permute(float x){return floor(mod(((x*34.0)+1.0)*x, 289.0));}
+vec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;}
+float taylorInvSqrt(float r){return 1.79284291400159 - 0.85373472095314 * r;}
+
+vec4 grad4(float j, vec4 ip){
+  const vec4 ones = vec4(1.0, 1.0, 1.0, -1.0);
+  vec4 p,s;
+
+  p.xyz = floor( fract (vec3(j) * ip.xyz) * 7.0) * ip.z - 1.0;
+  p.w = 1.5 - dot(abs(p.xyz), ones.xyz);
+  s = vec4(lessThan(p, vec4(0.0)));
+  p.xyz = p.xyz + (s.xyz*2.0 - 1.0) * s.www;
+
+  return p;
+}
+
+float snoise(vec4 v){
+  const vec2  C = vec2( 0.138196601125010504,  // (5 - sqrt(5))/20  G4
+                        0.309016994374947451); // (sqrt(5) - 1)/4   F4
+// First corner
+  vec4 i  = floor(v + dot(v, C.yyyy) );
+  vec4 x0 = v -   i + dot(i, C.xxxx);
+
+// Other corners
+
+// Rank sorting originally contributed by Bill Licea-Kane, AMD (formerly ATI)
+  vec4 i0;
+
+  vec3 isX = step( x0.yzw, x0.xxx );
+  vec3 isYZ = step( x0.zww, x0.yyz );
+//  i0.x = dot( isX, vec3( 1.0 ) );
+  i0.x = isX.x + isX.y + isX.z;
+  i0.yzw = 1.0 - isX;
+
+//  i0.y += dot( isYZ.xy, vec2( 1.0 ) );
+  i0.y += isYZ.x + isYZ.y;
+  i0.zw += 1.0 - isYZ.xy;
+
+  i0.z += isYZ.z;
+  i0.w += 1.0 - isYZ.z;
+
+  // i0 now contains the unique values 0,1,2,3 in each channel
+  vec4 i3 = clamp( i0, 0.0, 1.0 );
+  vec4 i2 = clamp( i0-1.0, 0.0, 1.0 );
+  vec4 i1 = clamp( i0-2.0, 0.0, 1.0 );
+
+  //  x0 = x0 - 0.0 + 0.0 * C
+  vec4 x1 = x0 - i1 + 1.0 * C.xxxx;
+  vec4 x2 = x0 - i2 + 2.0 * C.xxxx;
+  vec4 x3 = x0 - i3 + 3.0 * C.xxxx;
+  vec4 x4 = x0 - 1.0 + 4.0 * C.xxxx;
+
+// Permutations
+  i = mod(i, 289.0);
+  float j0 = permute( permute( permute( permute(i.w) + i.z) + i.y) + i.x);
+  vec4 j1 = permute( permute( permute( permute (
+             i.w + vec4(i1.w, i2.w, i3.w, 1.0 ))
+           + i.z + vec4(i1.z, i2.z, i3.z, 1.0 ))
+           + i.y + vec4(i1.y, i2.y, i3.y, 1.0 ))
+           + i.x + vec4(i1.x, i2.x, i3.x, 1.0 ));
+// Gradients
+// ( 7*7*6 points uniformly over a cube, mapped onto a 4-octahedron.)
+// 7*7*6 = 294, which is close to the ring size 17*17 = 289.
+
+  vec4 ip = vec4(1.0/294.0, 1.0/49.0, 1.0/7.0, 0.0) ;
+
+  vec4 p0 = grad4(j0,   ip);
+  vec4 p1 = grad4(j1.x, ip);
+  vec4 p2 = grad4(j1.y, ip);
+  vec4 p3 = grad4(j1.z, ip);
+  vec4 p4 = grad4(j1.w, ip);
+
+// Normalise gradients
+  vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));
+  p0 *= norm.x;
+  p1 *= norm.y;
+  p2 *= norm.z;
+  p3 *= norm.w;
+  p4 *= taylorInvSqrt(dot(p4,p4));
+
+// Mix contributions from the five corners
+  vec3 m0 = max(0.6 - vec3(dot(x0,x0), dot(x1,x1), dot(x2,x2)), 0.0);
+  vec2 m1 = max(0.6 - vec2(dot(x3,x3), dot(x4,x4)            ), 0.0);
+  m0 = m0 * m0;
+  m1 = m1 * m1;
+  return 49.0 * ( dot(m0*m0, vec3( dot( p0, x0 ), dot( p1, x1 ), dot( p2, x2 )))
+               + dot(m1*m1, vec2( dot( p3, x3 ), dot( p4, x4 ) ) ) ) ;
+
+}
+
+vec3 interp_hsv(vec3 one, vec3 two, float amount) {
+  return one + (two - one) * amount;
+}
+
+vec3 hsv2rgb(vec3 c) {
+  vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
+  vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
+  return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
+}
+
+vec3 ocean_deep = vec3(245.0/365.0, 82.0/100.0, 67.0/100.0);
+vec3 ocean_shallow = vec3(220.0/365.0, 75.0/100.0, 67.0/100.0);
+vec3 land_low = vec3(93.0/365.0, 87.0/100.0, 86.0/100.0);
+vec3 land_med = vec3(135.0/365.0, 84.0/100.0, 82.0/100.0);
+vec3 land_high = vec3(40.0/365.0, 52.0/100.0, 82.0/100.0);
+
+vec3 gray_light = vec3(31.0/365.0, 38.0/100.0, 80.0/100.0);
+vec3 gray_dark = vec3(15.0/365.0, 50.0/100.0, 24.0/100.0);
+
+vec3 jupiter_light = vec3(31.0/365.0, 50.0/100.0, 80.0/100.0);
+vec3 jupiter_dark = vec3(15.0/365.0, 70.0/100.0, 24.0/100.0);
+
+void main() {
+    if(gl_TexCoord[0].s > 0.5f || gl_TexCoord[0].t > 0.5f) {
+        gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
+        return;
+    }
+	vec2 uv = gl_TexCoord[0].st;
+
+    if(planetType == 0) {
+        float perlin = (snoise(vec4(passVertex.x*10.0, passVertex.y*10.0, passVertex.z*10.0, millis/1000.0))+1.0)/2.0;
+        float perlin2 = snoise(vec4(passVertex.x*2.0, passVertex.y*2.0, passVertex.z*2.0, millis/10000.0));
+
+        vec3 col1 = vec3(1, 0.1+perlin*0.6, 0);
+        vec3 col2 = vec3(perlin2*0.7);
+        vec3 colF = max(col1, col2);
+        gl_FragColor = vec4(colF,1.0);
+        return;
+    } else if(planetType == 1) {
+        float perlin = (snoise(vec4(passVertex.x*2.0, passVertex.y*2.0, passVertex.z*2.0, 0.0))+1.0)/2.0;
+        float perlin2 = snoise(vec4(passVertex.x*1.0, passVertex.y*1.0, passVertex.z*1.0, millis/10000.0));
+
+        vec3 col1 = vec3(0, 0, 1);
+        if(perlin < 0.55) {
+            col1 = hsv2rgb(interp_hsv(ocean_deep, ocean_shallow, perlin*perlin/0.55/0.55));
+        } else if(perlin < 0.9) {
+            col1 = hsv2rgb(interp_hsv(land_low, land_med, (perlin-0.55)/0.35));
+        } else {
+            col1 = hsv2rgb(interp_hsv(land_med, land_high, (perlin-0.9)/0.1));
+        }
+        vec3 col2 = vec3(perlin2*0.7);
+        vec3 colF = max(col1, col2);
+
+        gl_FragColor = vec4(colF,1.0);
+
+        gl_FragColor.rgb *= 1.2*max(0.35, dot(normalize(sunVec), normalize(passNormal)));
+
+        return;
+    } else if(planetType == 2) {
+       float perlin = (snoise(vec4(passVertex.x*10.0, passVertex.y*10.0, passVertex.z*10.0, 0.0))+1.0)/2.0;
+       gl_FragColor = vec4(hsv2rgb(interp_hsv(gray_light, gray_dark, perlin)), 1.0);
+
+       gl_FragColor.rgb *= 1.2*max(0.35, dot(normalize(sunVec), normalize(passNormal)));
+
+       return;
+    } else if(planetType == 3) {
+       float perlin = (snoise(vec4(passVertex.x*0.2, passVertex.y*10.0, passVertex.z*0.2, 0.0))+1.0)/2.0;
+       gl_FragColor = vec4(hsv2rgb(interp_hsv(jupiter_light, jupiter_dark, perlin)), 1.0);
+
+       gl_FragColor.rgb *= 1.2*max(0.35, dot(normalize(sunVec), normalize(passNormal)));
+
+       return;
+    } else if(planetType == 4) {
+        float perlin = (snoise(vec4(passVertex.x*0.2, passVertex.y*10.0, passVertex.z*0.2, 0.0))+1.0)/2.0;
+       gl_FragColor = vec4(hsv2rgb(interp_hsv(ocean_deep, ocean_shallow, perlin)), 1.0);
+
+       gl_FragColor.rgb *= 1.2*max(0.35, dot(normalize(sunVec), normalize(passNormal)));
+
+       return;
+    }
+
+    gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
+}
\ No newline at end of file
diff --git a/src/main/resources/assets/notenoughupdates/shaders/capes/planets/planets.vert b/src/main/resources/assets/notenoughupdates/shaders/capes/planets/planets.vert
new file mode 100644
index 00000000..632a4f94
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/shaders/capes/planets/planets.vert
@@ -0,0 +1,14 @@
+#version 120
+
+varying vec4 passColour;
+varying vec3 passNormal;
+varying vec3 passVertex;
+
+void main() {
+    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
+    gl_TexCoord[0] = gl_MultiTexCoord0;
+
+    passColour = gl_Color;
+    passNormal = normalize(gl_Normal);
+    passVertex = vec3(gl_Vertex.x, gl_Vertex.y, gl_Vertex.z);
+}
\ No newline at end of file
diff --git a/src/main/resources/assets/notenoughupdates/shaders/capes/tunnel/tunnel.frag b/src/main/resources/assets/notenoughupdates/shaders/capes/tunnel/tunnel.frag
new file mode 100644
index 00000000..fa3455ce
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/shaders/capes/tunnel/tunnel.frag
@@ -0,0 +1,50 @@
+#version 120
+
+varying vec4 passColour;
+varying vec3 passNormal;
+uniform sampler2D textureIn;
+
+uniform int millis;
+
+//Algorithm by hughsk
+vec3 hsv2rgb(vec3 c) {
+  vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
+  vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
+  return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
+}
+
+void main()
+{
+    float num = 5.0;
+
+    vec2 uv = vec2(gl_TexCoord[0].s*1024.0/300.0, gl_TexCoord[0].t*1024.0/420.0);
+
+    float timeMod = mod(millis/1000.0/num, 1.0/num);
+
+    if(uv.x > 1.5 || uv.y > 1) {
+        vec3 rgb = hsv2rgb(vec3((-1.0/num-millis/1000.0/num+timeMod)/2.0, 0.6, 0.8)) * 1.4;
+        gl_FragColor = vec4(rgb, 1.0);
+        return;
+    }
+
+    float xDist = 1.0 - min(uv.x, 1.0 - uv.x)*2.0;
+    float yDist = 1.0 - min(uv.y, 1.0 - uv.y)*2.0;
+    //float a = 20.0;
+    //float edgeDist = 1.0 - pow(pow(xDist, a) + pow(yDist, a), 1.0/a);
+    float edgeDist = 1.0 - max(xDist, yDist);
+
+    edgeDist += 0.2;
+    edgeDist *= edgeDist;
+    edgeDist -= timeMod;
+
+    float factor = 1.0 - mod(edgeDist*num, 1.0);
+    factor *= factor*factor;
+    factor = 1.4 - factor;
+    factor = max(0.8, factor);
+
+    edgeDist = floor(edgeDist*num)/num;
+
+    vec3 rgb = hsv2rgb(vec3((edgeDist-millis/1000.0/num+timeMod)/2.0, 0.6, 0.8));
+
+    gl_FragColor = vec4(rgb*factor, 1.0);
+}
diff --git a/src/main/resources/assets/notenoughupdates/shaders/capes/tunnel/tunnel.vert b/src/main/resources/assets/notenoughupdates/shaders/capes/tunnel/tunnel.vert
new file mode 100644
index 00000000..2b5c48f8
--- /dev/null
+++ b/src/main/resources/assets/notenoughupdates/shaders/capes/tunnel/tunnel.vert
@@ -0,0 +1,12 @@
+#version 120
+
+varying vec4 passColour;
+varying vec3 passNormal;
+
+void main() {
+    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
+    gl_TexCoord[0] = gl_MultiTexCoord0;
+
+    passColour = gl_Color;
+    passNormal = normalize(gl_Normal);
+}
\ No newline at end of file
diff --git a/src/main/resources/mixins.notenoughupdates.json b/src/main/resources/mixins.notenoughupdates.json
index 3fc98025..f3ef20a6 100644
--- a/src/main/resources/mixins.notenoughupdates.json
+++ b/src/main/resources/mixins.notenoughupdates.json
@@ -21,12 +21,13 @@
     "MixinContainer",
     "MixinWorld",
     "MixinPlayerControllerMP",
-    "MixinLayerCreeperCharge",
     "MixinEffectRenderer",
     "MixinRendererLivingEntity",
     "GuiEditSignAccessor",
     "MixinGuiScreen",
     "MixinTileEntitySkullRenderer",
-    "MixinItemCameraTransforms"
+    "MixinItemCameraTransforms",
+    "GuiContainerAccessor",
+    "MixinGuiInventory"
   ]
 }
\ No newline at end of file
-- 
cgit