From de97f55968d183cc7d76aad87e3b27d382bfdbc9 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 31 May 2020 01:59:47 +1000 Subject: 1.5 --- .../notenoughupdates/AllowEmptyHTMLTag.java | 74 + .../notenoughupdates/CustomRenderItem.java | 167 ++ .../moulberry/notenoughupdates/GuiItemRecipe.java | 80 + .../moulberry/notenoughupdates/GuiItemUsages.java | 165 ++ .../moulberry/notenoughupdates/GuiTextures.java | 47 + .../github/moulberry/notenoughupdates/NEUIO.java | 100 ++ .../moulberry/notenoughupdates/NEUManager.java | 1119 +++++++++++++ .../moulberry/notenoughupdates/NEUOverlay.java | 1704 ++++++++++++++++++++ .../notenoughupdates/NotEnoughUpdates.java | 584 +++++++ .../notenoughupdates/RequestFocusListener.java | 63 + .../github/moulberry/notenoughupdates/Utils.java | 431 +++++ .../notenoughupdates/infopanes/DevInfoPane.java | 106 ++ .../infopanes/FlipperInfoPane.java | 111 ++ .../notenoughupdates/infopanes/HTMLInfoPane.java | 316 ++++ .../notenoughupdates/infopanes/InfoPane.java | 44 + .../notenoughupdates/infopanes/QOLInfoPane.java | 344 ++++ .../infopanes/ScrollableInfoPane.java | 34 + .../infopanes/SettingsInfoPane.java | 259 +++ .../notenoughupdates/infopanes/TextInfoPane.java | 61 + .../notenoughupdates/itemeditor/GuiElement.java | 15 + .../itemeditor/GuiElementButton.java | 35 + .../itemeditor/GuiElementText.java | 42 + .../itemeditor/GuiElementTextField.java | 453 ++++++ .../notenoughupdates/itemeditor/NEUItemEditor.java | 429 +++++ .../notenoughupdates/mixins/MixinItemStack.java | 20 + .../notenoughupdates/options/Options.java | 203 +++ .../notenoughupdates/util/HTMLParagraphView.java | 30 + .../notenoughupdates/util/HtmlImageGenerator.java | 125 ++ .../notenoughupdates/util/HypixelApi.java | 68 + .../notenoughupdates/util/LargeHTMLEditorKit.java | 132 ++ .../notenoughupdates/util/LerpingFloat.java | 68 + .../notenoughupdates/util/LerpingInteger.java | 68 + .../util/SynchronousHTMLEditorKit.java | 36 + .../moulberry/notenoughupdates/util/TexLoc.java | 50 + 34 files changed, 7583 insertions(+) create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/AllowEmptyHTMLTag.java create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/CustomRenderItem.java create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/GuiItemRecipe.java create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/GuiItemUsages.java create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/GuiTextures.java create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/NEUIO.java create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/NEUManager.java create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/NEUOverlay.java create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/RequestFocusListener.java create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/Utils.java create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/infopanes/DevInfoPane.java create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/infopanes/FlipperInfoPane.java create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/infopanes/HTMLInfoPane.java create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/infopanes/InfoPane.java create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/infopanes/QOLInfoPane.java create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/infopanes/ScrollableInfoPane.java create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/infopanes/SettingsInfoPane.java create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/infopanes/TextInfoPane.java create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/itemeditor/GuiElement.java create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/itemeditor/GuiElementButton.java create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/itemeditor/GuiElementText.java create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/itemeditor/GuiElementTextField.java create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/itemeditor/NEUItemEditor.java create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinItemStack.java create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/options/Options.java create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/util/HTMLParagraphView.java create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/util/HtmlImageGenerator.java create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/util/HypixelApi.java create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/util/LargeHTMLEditorKit.java create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/util/LerpingFloat.java create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/util/LerpingInteger.java create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/util/SynchronousHTMLEditorKit.java create mode 100644 src/main/java/io/github/moulberry/notenoughupdates/util/TexLoc.java (limited to 'src/main/java/io') diff --git a/src/main/java/io/github/moulberry/notenoughupdates/AllowEmptyHTMLTag.java b/src/main/java/io/github/moulberry/notenoughupdates/AllowEmptyHTMLTag.java new file mode 100644 index 00000000..3cf5ef31 --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/AllowEmptyHTMLTag.java @@ -0,0 +1,74 @@ +package io.github.moulberry.notenoughupdates; + +import info.bliki.htmlcleaner.TagNode; +import info.bliki.wiki.filter.ITextConverter; +import info.bliki.wiki.model.IWikiModel; +import info.bliki.wiki.tags.HTMLTag; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +public class AllowEmptyHTMLTag extends HTMLTag { + public AllowEmptyHTMLTag(String name) { + super(name); + } + + public void renderHTML(ITextConverter converter, Appendable buf, IWikiModel model) throws IOException { + boolean newLinesAfterTag = false; + boolean newLinesAfterChildren = false; + TagNode node = this; + String name = node.getName(); + List children = node.getChildren(); + + if (NEW_LINES) { + switch (name) { + case "div": + case "p": + case "li": + case "td": + buf.append('\n'); + break; + case "table": + case "ul": + case "ol": + case "th": + case "tr": + buf.append('\n'); + newLinesAfterTag = true; + newLinesAfterChildren = true; + break; + case "pre": + buf.append('\n'); + newLinesAfterTag = false; + newLinesAfterChildren = true; + break; + case "blockquote": + newLinesAfterChildren = true; + break; + } + } + buf.append('<'); + buf.append(name); + + Map tagAtttributes = node.getAttributes(); + + appendAttributes(buf, tagAtttributes); + + if (children.size() == 0) { + buf.append(" />"); + } else { + buf.append('>'); + if (newLinesAfterTag) { + buf.append('\n'); + } + converter.nodesToText(children, buf, model); + if (newLinesAfterChildren) { + buf.append('\n'); + } + buf.append("'); + } + } +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/CustomRenderItem.java b/src/main/java/io/github/moulberry/notenoughupdates/CustomRenderItem.java new file mode 100644 index 00000000..bf194e33 --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/CustomRenderItem.java @@ -0,0 +1,167 @@ +package io.github.moulberry.notenoughupdates; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.*; +import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.client.renderer.block.model.ItemCameraTransforms; +import net.minecraft.client.renderer.entity.RenderItem; +import net.minecraft.client.renderer.texture.TextureMap; +import net.minecraft.client.renderer.texture.TextureUtil; +import net.minecraft.client.renderer.tileentity.TileEntityItemStackRenderer; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.client.resources.model.IBakedModel; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.Vec3i; + +import java.lang.reflect.Method; +import java.util.List; + +public class CustomRenderItem { + + public static void renderItemIntoGUI(RenderItem renderItem, ItemStack stack, int x, int y, boolean effect) { + IBakedModel ibakedmodel = renderItem.getItemModelMesher().getItemModel(stack); + GlStateManager.pushMatrix(); + Minecraft.getMinecraft().getTextureManager().bindTexture(TextureMap.locationBlocksTexture); + Minecraft.getMinecraft().getTextureManager().getTexture(TextureMap.locationBlocksTexture).setBlurMipmap(false, false); + GlStateManager.enableRescaleNormal(); + GlStateManager.enableAlpha(); + GlStateManager.alphaFunc(516, 0.1F); + GlStateManager.enableBlend(); + GlStateManager.blendFunc(770, 771); + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + setupGuiTransform(x, y, ibakedmodel.isGui3d()); + ibakedmodel = net.minecraftforge.client.ForgeHooksClient.handleCameraTransforms(ibakedmodel, ItemCameraTransforms.TransformType.GUI); + renderItem(renderItem, stack, ibakedmodel, effect); + GlStateManager.disableAlpha(); + GlStateManager.disableRescaleNormal(); + GlStateManager.disableLighting(); + GlStateManager.popMatrix(); + Minecraft.getMinecraft().getTextureManager().bindTexture(TextureMap.locationBlocksTexture); + Minecraft.getMinecraft().getTextureManager().getTexture(TextureMap.locationBlocksTexture).restoreLastBlurMipmap(); + } + + private static void setupGuiTransform(int xPosition, int yPosition, boolean isGui3d) { + GlStateManager.translate((float)xPosition, (float)yPosition, 0); + GlStateManager.translate(8.0F, 8.0F, 0.0F); + GlStateManager.scale(1.0F, 1.0F, -1.0F); + GlStateManager.scale(0.5F, 0.5F, 0.5F); + + if (isGui3d) { + GlStateManager.scale(40.0F, 40.0F, 40.0F); + GlStateManager.rotate(210.0F, 1.0F, 0.0F, 0.0F); + GlStateManager.rotate(-135.0F, 0.0F, 1.0F, 0.0F); + GlStateManager.enableLighting(); + } else { + GlStateManager.scale(64.0F, 64.0F, 64.0F); + GlStateManager.rotate(180.0F, 1.0F, 0.0F, 0.0F); + GlStateManager.disableLighting(); + } + } + + public static void renderItem(RenderItem renderItem, ItemStack stack, IBakedModel model, boolean effect) { + if (stack != null) { + GlStateManager.pushMatrix(); + GlStateManager.scale(0.5F, 0.5F, 0.5F); + + if (model.isBuiltInRenderer()) { + GlStateManager.rotate(180.0F, 0.0F, 1.0F, 0.0F); + GlStateManager.translate(-0.5F, -0.5F, -0.5F); + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + GlStateManager.enableRescaleNormal(); + TileEntityItemStackRenderer.instance.renderByItem(stack); + } else { + GlStateManager.translate(-0.5F, -0.5F, -0.5F); + Class[] paramsRM = new Class[]{IBakedModel.class, ItemStack.class}; + Method renderModelMethod = Utils.getMethod(RenderItem.class, paramsRM, + "renderModel", "func_175036_a"); + if(renderModelMethod != null) { + renderModelMethod.setAccessible(true); + try { + renderModelMethod.invoke(renderItem, model, stack); + } catch(Exception e) {} + } + + if (stack.hasEffect() && effect) { + Class[] paramsRE = new Class[]{IBakedModel.class}; + Method renderEffectMethod = Utils.getMethod(RenderItem.class, paramsRE, + "renderEffect", "func_180451_a"); + if(renderEffectMethod != null) { + renderEffectMethod.setAccessible(true); + try { + renderEffectMethod.invoke(renderItem, model); + } catch(Exception e) {} + } + } + } + + GlStateManager.popMatrix(); + } + } + + /*private static void renderModel(IBakedModel model, ItemStack stack) + { + renderModel(model, -1, stack); + } + + private static void renderModel(IBakedModel model, int color) + { + renderModel(model, color, (ItemStack)null); + } + + private static void renderModel(IBakedModel model, int color, ItemStack stack) + { + Tessellator tessellator = Tessellator.getInstance(); + WorldRenderer worldrenderer = tessellator.getWorldRenderer(); + worldrenderer.begin(7, DefaultVertexFormats.ITEM); + + for (EnumFacing enumfacing : EnumFacing.values()) + { + renderQuads(worldrenderer, model.getFaceQuads(enumfacing), color, stack); + } + + renderQuads(worldrenderer, model.getGeneralQuads(), color, stack); + tessellator.draw(); + } + + private static void putQuadNormal(WorldRenderer renderer, BakedQuad quad) + { + Vec3i vec3i = quad.getFace().getDirectionVec(); + renderer.putNormal((float)vec3i.getX(), (float)vec3i.getY(), (float)vec3i.getZ()); + } + + private static void renderQuad(WorldRenderer renderer, BakedQuad quad, int color) + { + renderer.addVertexData(quad.getVertexData()); + renderer.putColor4(color); + putQuadNormal(renderer, quad); + } + + private static void renderQuads(WorldRenderer renderer, List quads, int color, ItemStack stack) + { + boolean flag = color == -1 && stack != null; + int i = 0; + + for (int j = quads.size(); i < j; ++i) + { + BakedQuad bakedquad = (BakedQuad)quads.get(i); + int k = color; + + if (flag && bakedquad.hasTintIndex()) + { + k = stack.getItem().getColorFromItemStack(stack, bakedquad.getTintIndex()); + + if (EntityRenderer.anaglyphEnable) + { + k = TextureUtil.anaglyphColor(k); + } + + k = k | -16777216; + } + + net.minecraftforge.client.model.pipeline.LightUtil.renderQuadColor(renderer, bakedquad, k); + } + }*/ + +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/GuiItemRecipe.java b/src/main/java/io/github/moulberry/notenoughupdates/GuiItemRecipe.java new file mode 100644 index 00000000..6916372b --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/GuiItemRecipe.java @@ -0,0 +1,80 @@ +package io.github.moulberry.notenoughupdates; + +import com.google.gson.JsonObject; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.inventory.GuiCrafting; +import net.minecraft.client.resources.I18n; +import net.minecraft.inventory.ContainerWorkbench; +import net.minecraft.inventory.IInventory; +import net.minecraft.inventory.Slot; +import net.minecraft.item.ItemStack; + +public class GuiItemRecipe extends GuiCrafting { + + private ItemStack[] craftMatrix; + private String text; + private String craftText = ""; + private NEUManager manager; + + public GuiItemRecipe(ItemStack[] craftMatrix, JsonObject result, String text, NEUManager manager) { + super(Minecraft.getMinecraft().thePlayer.inventory, Minecraft.getMinecraft().theWorld); + this.craftMatrix = craftMatrix; + this.text = text; + this.manager = manager; + + ContainerWorkbench cw = (ContainerWorkbench) this.inventorySlots; + for(int i=0; i= 1 && slotId <= 9) { + click = craftMatrix[slotId-1]; + } else if(slotId == 0) { + ContainerWorkbench cw = (ContainerWorkbench) this.inventorySlots; + click = cw.craftResult.getStackInSlot(0); + } + if(click != null) { + if(clickedButton == 0) { + manager.displayGuiItemRecipe(manager.getInternalNameForItem(click), ""); + } else if(clickedButton == 1) { + manager.displayGuiItemUsages(manager.getInternalNameForItem(click), ""); + } + } + } + + /*public void handleMouseInput() throws IOException { + ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft()); + int height = scaledresolution.getScaledHeight(); + + int mouseX = Mouse.getX() / scaledresolution.getScaleFactor(); + int mouseY = height - Mouse.getY() / scaledresolution.getScaleFactor(); + if(mouseY > this.guiTop + this.ySize - 94 || mouseY < this.guiTop || + mouseX < this.guiLeft || mouseX > this.guiLeft+this.xSize) { + //Potentially allow mouse input in the future. For now this is still broken. + //super.handleMouseInput(); + } + }*/ + + public void onCraftMatrixChanged(IInventory inventoryIn){} +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/GuiItemUsages.java b/src/main/java/io/github/moulberry/notenoughupdates/GuiItemUsages.java new file mode 100644 index 00000000..ceb4e5d4 --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/GuiItemUsages.java @@ -0,0 +1,165 @@ +package io.github.moulberry.notenoughupdates; + +import com.google.gson.JsonObject; +import io.github.moulberry.notenoughupdates.util.TexLoc; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.inventory.GuiCrafting; +import net.minecraft.client.resources.I18n; +import net.minecraft.inventory.ContainerWorkbench; +import net.minecraft.inventory.IInventory; +import net.minecraft.inventory.Slot; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ResourceLocation; +import org.lwjgl.input.Keyboard; +import org.lwjgl.opengl.GL11; + +import java.awt.*; +import java.io.IOException; +import java.util.List; + +public class GuiItemUsages extends GuiCrafting { + private static final ResourceLocation resourcePacksTexture = new ResourceLocation("textures/gui/resource_packs.png"); + + private List craftMatrices; + private List results; + private int currentIndex = 0; + + private String text; + private String craftText = ""; + private String collectionText = ""; + private NEUManager manager; + + private TexLoc left = new TexLoc(0, 0, Keyboard.KEY_N); + private TexLoc right = new TexLoc(0, 0, Keyboard.KEY_M); + + public GuiItemUsages(List craftMatrices, List results, String text, NEUManager manager) { + super(Minecraft.getMinecraft().thePlayer.inventory, Minecraft.getMinecraft().theWorld); + + this.craftMatrices = craftMatrices; + this.results = results; + this.text = text; + this.manager = manager; + + setIndex(0); + } + + private void setIndex(int index) { + if(index < 0 || index >= craftMatrices.size()) { + return; + } else { + currentIndex = index; + + ContainerWorkbench cw = (ContainerWorkbench) this.inventorySlots; + for(int i=0; i + 63 && guiY < + 63 + buttonHeight) { + if(guiX > + 110 && guiX < 110 + buttonWidth) { + leftSelected = true; + } else if(guiX > 147 && guiX < 147 + buttonWidth) { + rightSelected = true; + } + } + + Minecraft.getMinecraft().getTextureManager().bindTexture(resourcePacksTexture); + //Left arrow + Utils.drawTexturedRect(110, 63, 7, 11, 34/256f, 48/256f, + 5/256f + (leftSelected ? 32/256f : 0), 27/256f + (leftSelected ? 32/256f : 0)); + //Right arrow + Utils.drawTexturedRect(147, 63, 7, 11, 10/256f, 24/256f, + 5/256f + (rightSelected ? 32/256f : 0), 27/256f + (rightSelected ? 32/256f : 0)); + GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0); + + String str = (currentIndex+1)+"/"+craftMatrices.size(); + Utils.drawStringCenteredScaledMaxWidth(str, fontRendererObj, 132, 69, + false, 24, Color.BLACK.getRGB()); + + + Utils.drawStringCenteredScaledMaxWidth(craftText, fontRendererObj, 132, 25, + false, 75, 4210752); + + + Utils.drawStringScaledMaxWidth(t, fontRendererObj, 28, 6, t.contains("\u00a7"), xSize-38, 4210752); + this.fontRendererObj.drawString(I18n.format("container.inventory", new Object[0]), 8, this.ySize - 96 + 2, 4210752); + } + + protected void mouseClickMove(int mouseX, int mouseY, int clickedMouseButton, long timeSinceLastClick) { + } + + @Override + public void handleKeyboardInput() throws IOException { + super.handleKeyboardInput(); //TODO: r and u + left.handleKeyboardInput(); + right.handleKeyboardInput(); + } + + @Override + protected void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOException { + super.mouseClicked(mouseX, mouseY, mouseButton); + + int guiX = mouseX - guiLeft; + int guiY = mouseY - guiTop; + + int buttonWidth = 7; + int buttonHeight = 11; + + if(guiY > + 63 && guiY < + 63 + buttonHeight) { + if(guiX > + 110 && guiX < 110 + buttonWidth) { + setIndex(currentIndex-1); + } else if(guiX > 147 && guiX < 147 + buttonWidth) { + setIndex(currentIndex+1); + } + } + } + + protected void handleMouseClick(Slot slotIn, int slotId, int clickedButton, int clickType) { + ItemStack click = null; + if(slotId >= 1 && slotId <= 9) { + click = craftMatrices.get(currentIndex)[slotId-1]; + } else if(slotId == 0) { + ContainerWorkbench cw = (ContainerWorkbench) this.inventorySlots; + click = cw.craftResult.getStackInSlot(0); + } + if(click != null) { + if(clickedButton == 0) { + manager.displayGuiItemRecipe(manager.getInternalNameForItem(click), ""); + } else if(clickedButton == 1) { + manager.displayGuiItemUsages(manager.getInternalNameForItem(click), ""); + } + } + } + + /*public void handleMouseInput() throws IOException { + ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft()); + int height = scaledresolution.getScaledHeight(); + + int mouseX = Mouse.getX() / scaledresolution.getScaleFactor(); + int mouseY = height - Mouse.getY() / scaledresolution.getScaleFactor(); + if(mouseY > this.guiTop + this.ySize - 94 || mouseY < this.guiTop || + mouseX < this.guiLeft || mouseX > this.guiLeft+this.xSize) { + //Potentially allow mouse input in the future. For now this is still broken. + //super.handleMouseInput(); + } + }*/ + + public void onCraftMatrixChanged(IInventory inventoryIn){} +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/GuiTextures.java b/src/main/java/io/github/moulberry/notenoughupdates/GuiTextures.java new file mode 100644 index 00000000..48db19a5 --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/GuiTextures.java @@ -0,0 +1,47 @@ +package io.github.moulberry.notenoughupdates; + +import net.minecraft.util.ResourceLocation; + +public class GuiTextures { + + private GuiTextures() {} //Not instantiable. Use import static to access class members. + + public static final ResourceLocation itemPaneTabArrow = new ResourceLocation("notenoughupdates:item_pane_tab_arrow.png"); + //public static final ResourceLocation prev = new ResourceLocation("notenoughupdates:prev.png"); + //public static final ResourceLocation next = new ResourceLocation("notenoughupdates:next.png"); + public static final ResourceLocation rightarrow_overlay = new ResourceLocation("notenoughupdates:rightarrow_overlay.png"); + public static final ResourceLocation rightarrow = new ResourceLocation("notenoughupdates:rightarrow.png"); + public static final ResourceLocation item_edit = new ResourceLocation("notenoughupdates:item_edit.png"); + public static final ResourceLocation close = new ResourceLocation("notenoughupdates:close.png"); + public static final ResourceLocation settings = new ResourceLocation("notenoughupdates:settings.png"); + public static final ResourceLocation off = new ResourceLocation("notenoughupdates:off.png"); + public static final ResourceLocation on = new ResourceLocation("notenoughupdates:on.png"); + public static final ResourceLocation help = new ResourceLocation("notenoughupdates:help.png"); + + public static final ResourceLocation item_mask = new ResourceLocation("notenoughupdates:item_mask.png"); + + public static final ResourceLocation logo = new ResourceLocation("notenoughupdates:logo.png"); + public static final ResourceLocation logo_fg = new ResourceLocation("notenoughupdates:logo_fg.png"); + public static final ResourceLocation logo_bg = new ResourceLocation("notenoughupdates:logo_bg.png"); + + public static final ResourceLocation sort_all = new ResourceLocation("notenoughupdates:sort_all.png"); + public static final ResourceLocation sort_mob = new ResourceLocation("notenoughupdates:sort_mob.png"); + public static final ResourceLocation sort_pet = new ResourceLocation("notenoughupdates:sort_pet.png"); + public static final ResourceLocation sort_tool = new ResourceLocation("notenoughupdates:sort_weapon.png"); + public static final ResourceLocation sort_armor = new ResourceLocation("notenoughupdates:sort_armor.png"); + public static final ResourceLocation sort_accessory = new ResourceLocation("notenoughupdates:sort_accessory.png"); + public static final ResourceLocation sort_all_active = new ResourceLocation("notenoughupdates:sort_all_active.png"); + public static final ResourceLocation sort_mob_active = new ResourceLocation("notenoughupdates:sort_mob_active.png"); + public static final ResourceLocation sort_pet_active = new ResourceLocation("notenoughupdates:sort_pet_active.png"); + public static final ResourceLocation sort_tool_active = new ResourceLocation("notenoughupdates:sort_weapon_active.png"); + public static final ResourceLocation sort_armor_active = new ResourceLocation("notenoughupdates:sort_armor_active.png"); + public static final ResourceLocation sort_accessory_active = new ResourceLocation("notenoughupdates:sort_accessory_active.png"); + + public static final ResourceLocation order_alphabetical = new ResourceLocation("notenoughupdates:order_alphabetical.png"); + public static final ResourceLocation order_rarity = new ResourceLocation("notenoughupdates:order_rarity.png"); + public static final ResourceLocation order_alphabetical_active = new ResourceLocation("notenoughupdates:order_alphabetical_active.png"); + public static final ResourceLocation order_rarity_active = new ResourceLocation("notenoughupdates:order_rarity_active.png"); + public static final ResourceLocation ascending_overlay = new ResourceLocation("notenoughupdates:ascending_overlay.png"); + public static final ResourceLocation descending_overlay = new ResourceLocation("notenoughupdates:descending_overlay.png"); + +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/NEUIO.java b/src/main/java/io/github/moulberry/notenoughupdates/NEUIO.java new file mode 100644 index 00000000..a89bd1dc --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/NEUIO.java @@ -0,0 +1,100 @@ +package io.github.moulberry.notenoughupdates; + +import org.kohsuke.github.*; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class NEUIO { + + private final String accessToken; + + public NEUIO(String accessToken) { + this.accessToken = accessToken; + } + + /** + * Creates a new branch, commits to it with a single file change and submits a pull request from the new branch + * back to the master branch. + */ + public boolean createNewRequest(String newBranchName, String prTitle, String prBody, String filename, String content) { + try { + GitHub github = new GitHubBuilder().withOAuthToken(accessToken).build(); + System.out.println("Getting repo"); + + //https://github.com/Moulberry/NotEnoughUpdates-REPO + GHRepository repo = github.getRepositoryById("247692460"); + + System.out.println("Getting last commit"); + String lastCommitSha = repo.getRef("heads/master").getObject().getSha(); + System.out.println("Last master commit sha: " + lastCommitSha); + + String lastTreeSha = repo.getCommit(lastCommitSha).getTree().getSha(); + + GHTreeBuilder tb = repo.createTree(); + tb.baseTree(lastTreeSha); + tb.add(filename, content, false); + GHTree tree = tb.create(); + System.out.println("Created new tree: " + tree.getSha()); + + GHCommitBuilder cb = repo.createCommit(); + cb.message(prTitle); + cb.tree(tree.getSha()); + cb.parent(lastCommitSha); + GHCommit commit = cb.create(); + System.out.println("Created commit: " + commit.getSHA1()); + + repo.createRef("refs/heads/"+newBranchName, commit.getSHA1()); + System.out.println("Set new branch head to commit."); + + repo.createPullRequest(prTitle, newBranchName, "master", prBody); + return true; + } catch(IOException e) { + e.printStackTrace(); + return false; + } + } + + /** + * @param oldShas Map from filename (eg. BOW.json) to the sha in the local repository + * @return Map from filename to the new shas + */ + public Map getChangedItems(Map oldShas) { + HashMap changedFiles = new HashMap<>(); + try { + GitHub github = new GitHubBuilder().withOAuthToken(accessToken).build(); + GHRepository repo = github.getRepositoryById("247692460"); + + for(GHContent content : repo.getDirectoryContent("items")) { + String oldSha = oldShas.get(content.getName()); + if(!content.getSha().equals(oldSha)) { + changedFiles.put(content.getName(), content.getSha()); + } + } + } catch(IOException e) { + return null; + } + return changedFiles; + } + + /** + * Takes set of filename (eg. BOW.json) and returns map from that filename to the individual download link. + */ + public Map getItemsDownload(Set filename) { + HashMap downloadUrls = new HashMap<>(); + try { + GitHub github = new GitHubBuilder().withOAuthToken(accessToken).build(); + GHRepository repo = github.getRepositoryById("247692460"); + + for(GHContent content : repo.getDirectoryContent("items")) { + if(filename.contains(content.getName())) { + downloadUrls.put(content.getName(), content.getDownloadUrl()); + } + } + } catch(IOException e) { } + return downloadUrls; + } +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/NEUManager.java b/src/main/java/io/github/moulberry/notenoughupdates/NEUManager.java new file mode 100644 index 00000000..08939cca --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/NEUManager.java @@ -0,0 +1,1119 @@ +package io.github.moulberry.notenoughupdates; + +import com.google.common.io.CharSource; +import com.google.gson.*; +import io.github.moulberry.notenoughupdates.options.Options; +import io.github.moulberry.notenoughupdates.util.HypixelApi; +import javafx.scene.control.Alert; +import net.minecraft.client.Minecraft; +import net.minecraft.init.Items; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.*; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.ResourceLocation; +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.input.ReaderInputStream; +import org.apache.commons.lang3.tuple.Pair; +import org.lwjgl.opengl.Display; + +import javax.swing.*; +import java.io.*; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.text.NumberFormat; +import java.util.*; +import java.util.zip.GZIPInputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +public class NEUManager { + + private final NotEnoughUpdates neu; + public final NEUIO neuio; + public final Gson gson; + + private TreeMap itemMap = new TreeMap<>(); + + private TreeMap> tagWordMap = new TreeMap<>(); + private TreeMap>> titleWordMap = new TreeMap<>(); + private TreeMap>> loreWordMap = new TreeMap<>(); + + public String viewItemAttemptID = null; + public long viewItemAttemptTime = 0; + + public String currentProfile = ""; + public final HypixelApi hypixelApi = new HypixelApi(); + + private ResourceLocation wkZip = new ResourceLocation("notenoughupdates:wkhtmltox.zip"); + private Map itemstackCache = new HashMap<>(); + + private static final String AUCTIONS_PRICE_URL = "https://moulberry.github.io/files/auc_avg_jsons/average_3day.json.gz"; + private JsonObject auctionPricesJson = null; + private long auctionLastUpdate = 0; + + private HashMap craftCost = new HashMap<>(); + + private HashMap> usagesMap = new HashMap<>(); + + public File configLocation; + private File itemsLocation; + private File itemShaLocation; + private JsonObject itemShaConfig; + private File configFile; + public Options config; + + public NEUManager(NotEnoughUpdates neu, NEUIO neuio, File configLocation) { + this.neu = neu; + this.configLocation = configLocation; + this.neuio = neuio; + + GsonBuilder gsonBuilder = new GsonBuilder().setPrettyPrinting(); + gsonBuilder.registerTypeAdapter(Options.Option.class, Options.createSerializer()); + gsonBuilder.registerTypeAdapter(Options.Option.class, Options.createDeserializer()); + gson = gsonBuilder.create(); + + this.configFile = new File(configLocation, "config.json"); + try { + configFile.createNewFile(); + config = Options.loadFromFile(gson, configFile); + } catch(Exception e) { + config = new Options(); + } + + this.itemsLocation = new File(configLocation, "items"); + itemsLocation.mkdir(); + + this.itemShaLocation = new File(configLocation, "itemSha.json"); + try { + itemShaLocation.createNewFile(); + itemShaConfig = getJsonFromFile(itemShaLocation); + if(itemShaConfig == null) itemShaConfig = new JsonObject(); + } catch(IOException e) { } + + 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) { + } + } + + //Unused code, used to automatically grab items from auctions. Leaving here in case I need it. + /*try { + for(int j=0; j<=89; j++) { + JsonObject auctions0 = getJsonFromFile(new File(configLocation, "auctions/auctions"+j+".json")); + + JsonArray arr = auctions0.getAsJsonArray("auctions"); + for(int i=0; i tag.toString().length()) { + writeItemJson(internalname, itemid, displayname, lore, info, clickcommand, damage, tag); + } + } + + } catch(Exception e) { + } + } + } + } catch(Exception e) { + e.printStackTrace(); + } + + throw new RuntimeException();*/ + } + + public class CraftInfo { + public boolean fromRecipe = false; + public float craftCost = -1; + } + + public CraftInfo getCraftCost(String internalname) { + if(craftCost.containsKey(internalname)) { + return craftCost.get(internalname); + } else { + CraftInfo ci = new CraftInfo(); + + JsonObject auctionInfo = getItemAuctionInfo(internalname); + JsonObject bazaarInfo = getBazaarInfo(internalname); + + if(bazaarInfo != null) { + float bazaarInstantBuyPrice = bazaarInfo.get("curr_buy").getAsFloat(); + ci.craftCost = bazaarInstantBuyPrice; + } + if(auctionInfo != null) { + float auctionPrice = auctionInfo.get("price").getAsFloat() / auctionInfo.get("count").getAsFloat(); + if(ci.craftCost < 0 || auctionPrice < ci.craftCost) { + ci.craftCost = auctionPrice; + } + } + JsonObject item = getItemInformation().get(internalname); + if(item != null && item.has("recipe")) { + float craftPrice = 0; + JsonObject recipe = item.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.length() == 0) continue; + + int count = 1; + if(itemS != null && itemS.split(":").length == 2) { + count = Integer.valueOf(itemS.split(":")[1]); + itemS = itemS.split(":")[0]; + } + float compCost = getCraftCost(itemS).craftCost * count; + if(compCost < 0) { + craftCost.put(internalname, ci); + return ci; + } else { + craftPrice += compCost; + } + } + + if(ci.craftCost < 0 || craftPrice < ci.craftCost) { + ci.craftCost = craftPrice; + ci.fromRecipe = true; + } + } + craftCost.put(internalname, ci); + return ci; + } + } + + public void saveConfig() throws IOException { + config.saveToFile(gson, configFile); + } + + public void updatePrices() { + if(System.currentTimeMillis() - auctionLastUpdate > 1000*60*30) { //30 minutes + craftCost.clear(); + System.out.println("UPDATING PRICE INFORMATION"); + auctionLastUpdate = System.currentTimeMillis(); + try(Reader inReader = new InputStreamReader(new GZIPInputStream(new URL(AUCTIONS_PRICE_URL).openStream()))) { + auctionPricesJson = gson.fromJson(inReader, JsonObject.class); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + public JsonObject getAuctionPricesJson() { + return auctionPricesJson; + } + + public JsonObject getItemAuctionInfo(String internalname) { + JsonElement e = auctionPricesJson.get("prices").getAsJsonObject().get(internalname); + if(e == null) { + return null; + } + return e.getAsJsonObject(); + } + + public JsonObject getBazaarInfo(String internalname) { + JsonElement e = auctionPricesJson.get("bazaar").getAsJsonObject().get(internalname); + if(e == null) { + return null; + } + return e.getAsJsonObject(); + } + + public float getCostOfEnchants(String internalname, NBTTagCompound tag) { + float costOfEnchants = 0; + JsonObject info = getItemAuctionInfo(internalname); + if(info == null || !info.has("price")) { + return 0; + } + float price = getItemAuctionInfo(internalname).get("price").getAsFloat(); + if(tag.hasKey("ExtraAttributes")) { + NBTTagCompound ea = tag.getCompoundTag("ExtraAttributes"); + if(ea.hasKey("enchantments")) { + JsonObject ench_prices = auctionPricesJson.get("ench_prices").getAsJsonObject(); + + NBTTagCompound enchs = ea.getCompoundTag("enchantments"); + for(String ench : enchs.getKeySet()) { + int level = enchs.getInteger(ench); + + for(Map.Entry entry : ench_prices.entrySet()) { + if(matchEnch(ench, level, entry.getKey())) { + costOfEnchants += entry.getValue().getAsJsonObject().get("A").getAsFloat()*price + + entry.getValue().getAsJsonObject().get("B").getAsFloat(); + break; + } + } + } + } + } + return costOfEnchants; + } + + private boolean matchEnch(String ench, int level, String id) { + String idEnch = id.split(":")[0]; + String idLevel = id.split(":")[1]; + + if(!ench.equals(idEnch)) { + return false; + } + + if(String.valueOf(level).equals(idLevel)) { + return true; + } + + if(idLevel.startsWith("LE")) { + int idLevelI = Integer.valueOf(idLevel.substring(2)); + return level <= idLevelI; + } else if(idLevel.startsWith("GE")) { + int idLevelI = Integer.valueOf(idLevel.substring(2)); + return level >= idLevelI; + } + + return false; + } + + /** + * Parses a file in to a JsonObject. + */ + public JsonObject getJsonFromFile(File file) throws IOException { + InputStream in = new FileInputStream(file); + BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); + JsonObject json = gson.fromJson(reader, JsonObject.class); + return json; + } + + /** + * Called when the game is first loaded. Compares the local repository to the github repository and handles + * the downloading of new/updated files. This then calls the "loadItem" method for every item in the local + * repository. + */ + public void loadItemInformation() { + if(config.autoupdate.value) { + JOptionPane pane = new JOptionPane("Getting items to download from remote repository."); + JDialog dialog = pane.createDialog("NotEnoughUpdates Remote Sync"); + dialog.setModal(false); + //dialog.setVisible(true); + + if (Display.isActive()) dialog.toFront(); + + HashMap oldShas = new HashMap<>(); + for (Map.Entry entry : itemShaConfig.entrySet()) { + if (new File(itemsLocation, entry.getKey() + ".json").exists()) { + oldShas.put(entry.getKey() + ".json", entry.getValue().getAsString()); + } + } + Map changedFiles = neuio.getChangedItems(oldShas); + + if (changedFiles != null) { + for (Map.Entry changedFile : changedFiles.entrySet()) { + itemShaConfig.addProperty(changedFile.getKey().substring(0, changedFile.getKey().length() - 5), + changedFile.getValue()); + } + try { + writeJson(itemShaConfig, itemShaLocation); + } catch (IOException e) { + } + } + + if (Display.isActive()) dialog.toFront(); + + if (changedFiles != null && changedFiles.size() <= 20) { + Map downloads = neuio.getItemsDownload(changedFiles.keySet()); + + String startMessage = "NotEnoughUpdates: Syncing with remote repository ("; + int downloaded = 0; + + for (Map.Entry entry : downloads.entrySet()) { + pane.setMessage(startMessage + (++downloaded) + "/" + downloads.size() + ")\nCurrent: " + entry.getKey()); + dialog.pack(); + dialog.setVisible(true); + if (Display.isActive()) dialog.toFront(); + + File item = new File(itemsLocation, entry.getKey()); + try { + item.createNewFile(); + } catch (IOException e) { + } + try (BufferedInputStream inStream = new BufferedInputStream(new URL(entry.getValue()).openStream()); + FileOutputStream fileOutputStream = new FileOutputStream(item)) { + byte dataBuffer[] = new byte[1024]; + int bytesRead; + while ((bytesRead = inStream.read(dataBuffer, 0, 1024)) != -1) { + fileOutputStream.write(dataBuffer, 0, bytesRead); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } else { + //TODO: Store hard-coded value somewhere else + String dlUrl = "https://github.com/Moulberry/NotEnoughUpdates-REPO/archive/master.zip"; + + pane.setMessage("Downloading NEU Master Archive. (DL# >20)"); + dialog.pack(); + dialog.setVisible(true); + if (Display.isActive()) dialog.toFront(); + + File itemsZip = new File(configLocation, "neu-items-master.zip"); + try { + itemsZip.createNewFile(); + } catch (IOException e) { + } + try (BufferedInputStream inStream = new BufferedInputStream(new URL(dlUrl).openStream()); + FileOutputStream fileOutputStream = new FileOutputStream(itemsZip)) { + byte dataBuffer[] = new byte[1024]; + int bytesRead; + while ((bytesRead = inStream.read(dataBuffer, 0, 1024)) != -1) { + fileOutputStream.write(dataBuffer, 0, bytesRead); + } + } catch (IOException e) { + e.printStackTrace(); + } + + pane.setMessage("Unzipping NEU Master Archive."); + dialog.pack(); + dialog.setVisible(true); + if (Display.isActive()) dialog.toFront(); + + unzipIgnoreFirstFolder(itemsZip.getAbsolutePath(), configLocation.getAbsolutePath()); + } + + dialog.dispose(); + } + + for(File f : itemsLocation.listFiles()) { + loadItem(f.getName().substring(0, f.getName().length()-5)); + } + } + + /** + * Loads the item in to the itemMap and also stores various words associated with this item + * in to titleWordMap and loreWordMap. These maps are used in the searching algorithm. + * @param internalName + */ + public void loadItem(String internalName) { + itemstackCache.remove(internalName); + try { + JsonObject json = getJsonFromFile(new File(itemsLocation, internalName + ".json")); + if(json == null) { + return; + } + 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]; + } + + if(!usagesMap.containsKey(itemS)) { + usagesMap.put(itemS, new HashSet<>()); + } + 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<>()); + } + 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<>()); + } + loreWordMap.get(str).get(internalName).add(wordIndex); + wordIndex++; + } + } + } + } catch(IOException e) { + e.printStackTrace(); + } + } + + /** + * Searches a string for a query. This method is used to mimic the behaviour of the + * more complex map-based search function. This method is used for the chest-item-search feature. + */ + public boolean searchString(String toSearch, String query) { + int lastMatch = -1; + + toSearch = clean(toSearch).toLowerCase(); + query = clean(query).toLowerCase(); + String[] splitToSeach = toSearch.split(" "); + out: + for(String s : query.split(" ")) { + for(int i=0; i search(String query) { + LinkedHashSet results = new LinkedHashSet<>(); + if(query.startsWith("title:")) { + query = query.substring(6); + results.addAll(new TreeSet<>(search(query, titleWordMap))); + } else if(query.startsWith("desc:")) { + query = query.substring(5); + results.addAll(new TreeSet<>(search(query, loreWordMap))); + } else if(query.startsWith("id:")) { + query = query.substring(3); + results.addAll(new TreeSet<>(subMapWithKeysThatAreSuffixes(query.toUpperCase(), itemMap).keySet())); + } else { + if(!query.trim().contains(" ")) { + StringBuilder sb = new StringBuilder(); + for(char c : query.toCharArray()) { + sb.append(c).append(" "); + } + results.addAll(new TreeSet<>(search(sb.toString(), titleWordMap))); + } + results.addAll(new TreeSet<>(search(query, titleWordMap))); + results.addAll(new TreeSet<>(search(query, loreWordMap))); + } + return results; + } + + /** + * Splits a search query into an array of strings delimited by a space character. Then, matches the query to + * the start of words in the various maps (title & lore). The small query does not need to match the whole entry + * of the map, only the beginning. eg. "ench" and "encha" will both match "enchanted". All sub queries must + * follow a word matching the previous sub query. eg. "ench po" will match "enchanted pork" but will not match + * "pork enchanted". + */ + private Set search(String query, TreeMap>> wordMap) { + HashMap> matches = null; + + query = clean(query).toLowerCase(); + for(String queryWord : query.split(" ")) { + HashMap> matchesToKeep = new HashMap<>(); + for(HashMap> wordMatches : subMapWithKeysThatAreSuffixes(queryWord, wordMap).values()) { + if(wordMatches != null && !wordMatches.isEmpty()) { + if(matches == null) { + //Copy all wordMatches to titleMatches + for(String internalname : wordMatches.keySet()) { + if(!matchesToKeep.containsKey(internalname)) { + matchesToKeep.put(internalname, new ArrayList<>()); + } + matchesToKeep.get(internalname).addAll(wordMatches.get(internalname)); + } + } else { + for(String internalname : matches.keySet()) { + if(wordMatches.containsKey(internalname)) { + for(Integer newIndex : wordMatches.get(internalname)) { + if(matches.get(internalname).contains(newIndex-1)) { + if(!matchesToKeep.containsKey(internalname)) { + matchesToKeep.put(internalname, new ArrayList<>()); + } + matchesToKeep.get(internalname).add(newIndex); + } + } + } + } + } + } + } + if(matchesToKeep.isEmpty()) return new HashSet<>(); + matches = matchesToKeep; + } + + return matches.keySet(); + } + + /** + * From https://stackoverflow.com/questions/10711494/get-values-in-treemap-whose-string-keys-start-with-a-pattern + */ + public Map subMapWithKeysThatAreSuffixes(String prefix, NavigableMap map) { + if ("".equals(prefix)) return map; + String lastKey = createLexicographicallyNextStringOfTheSameLenght(prefix); + return map.subMap(prefix, true, lastKey, false); + } + + String createLexicographicallyNextStringOfTheSameLenght(String input) { + final int lastCharPosition = input.length()-1; + String inputWithoutLastChar = input.substring(0, lastCharPosition); + char lastChar = input.charAt(lastCharPosition) ; + char incrementedLastChar = (char) (lastChar + 1); + return inputWithoutLastChar+incrementedLastChar; + } + + private String clean(String str) { + return str.replaceAll("(\u00a7.)|[^0-9a-zA-Z ]", "").toLowerCase().trim(); + } + + public void showRecipe(JsonObject item) { + if(item.has("useneucraft") && item.get("useneucraft").getAsBoolean()) { + displayGuiItemRecipe(item.get("internalname").getAsString(), ""); + } else if(item.has("clickcommand")) { + String clickcommand = item.get("clickcommand").getAsString(); + + if(clickcommand.equals("viewrecipe")) { + neu.sendChatMessage( + "/" + clickcommand + " " + + item.get("internalname").getAsString().split(";")[0]); + viewItemAttemptID = item.get("internalname").getAsString(); + viewItemAttemptTime = System.currentTimeMillis(); + } else if(clickcommand.equals("viewpotion")) { + neu.sendChatMessage( + "/" + clickcommand + " " + + item.get("internalname").getAsString().split(";")[0].toLowerCase()); + viewItemAttemptID = item.get("internalname").getAsString(); + viewItemAttemptTime = System.currentTimeMillis(); + } + } + } + + /** + * Takes an