diff options
Diffstat (limited to 'src/main/java/io')
34 files changed, 7583 insertions, 0 deletions
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<Object> 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<String, String> 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("</"); + buf.append(node.getName()); + 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<BakedQuad> 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<Math.min(craftMatrix.length, 9); i++) { + if(craftMatrix[i] == null) continue; + cw.craftMatrix.setInventorySlotContents(i, craftMatrix[i]); + } + if(result.has("crafttext")) { + craftText = result.get("crafttext").getAsString(); + } + cw.craftResult.setInventorySlotContents(0, manager.jsonToStack(result)); + } + + protected void drawGuiContainerForegroundLayer(int mouseX, int mouseY) { + String t = text.equals("") ? I18n.format("container.crafting", new Object[0]) : text; + + 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); + + Utils.drawStringCenteredScaledMaxWidth(craftText, fontRendererObj, 132, 25, + false, 75, 4210752); + } + + protected void mouseClickMove(int mouseX, int mouseY, int clickedMouseButton, long timeSinceLastClick) { + } + + protected void handleMouseClick(Slot slotIn, int slotId, int clickedButton, int clickType) { + ItemStack click = null; + if(slotId >= 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<ItemStack[]> craftMatrices; + private List<JsonObject> 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<ItemStack[]> craftMatrices, List<JsonObject> 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<Math.min(craftMatrices.get(currentIndex).length, 9); i++) { + cw.craftMatrix.setInventorySlotContents(i, craftMatrices.get(currentIndex)[i]); + } + if(results.get(currentIndex).has("crafttext")) { + craftText = results.get(currentIndex).get("crafttext").getAsString(); + } + cw.craftResult.setInventorySlotContents(0, manager.jsonToStack(results.get(currentIndex))); + } + } + + protected void drawGuiContainerForegroundLayer(int mouseX, int mouseY) { + String t = "Crafting Usages"; + + int guiX = mouseX - guiLeft; + int guiY = mouseY - guiTop; + + int buttonWidth = 7; + int buttonHeight = 11; + + boolean leftSelected = false; + boolean rightSelected = false; + + if(guiY > + 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<String, String> getChangedItems(Map<String, String> oldShas) { + HashMap<String, String> 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<String, String> getItemsDownload(Set<String> filename) { + HashMap<String, String> 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<String, JsonObject> itemMap = new TreeMap<>(); + + private TreeMap<String, Set<String>> tagWordMap = new TreeMap<>(); + private TreeMap<String, HashMap<String, List<Integer>>> titleWordMap = new TreeMap<>(); + private TreeMap<String, HashMap<String, List<Integer>>> 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<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 JsonObject auctionPricesJson = null; + private long auctionLastUpdate = 0; + + private HashMap<String, CraftInfo> craftCost = new HashMap<>(); + + private HashMap<String, Set<String>> 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<arr.size(); i++) { + JsonObject item0 = arr.get(i).getAsJsonObject(); + try { + String item_bytes = item0.get("item_bytes").getAsString(); + + NBTTagCompound tag = CompressedStreamTools.readCompressed(new ByteArrayInputStream(Base64.getDecoder().decode(item_bytes))); + tag = tag.getTagList("i", 10).getCompoundTagAt(0); + int id = tag.getShort("id"); + int damage = tag.getShort("Damage"); + tag = tag.getCompoundTag("tag"); + + String internalname = ""; + if(tag != null && tag.hasKey("ExtraAttributes", 10)) { + NBTTagCompound ea = tag.getCompoundTag("ExtraAttributes"); + + if(ea.hasKey("id", 8)) { + internalname = ea.getString("id"); + } + } + + String[] lore = new String[0]; + NBTTagCompound display = tag.getCompoundTag("display"); + + if(display.hasKey("Lore", 9)) { + NBTTagList list = display.getTagList("Lore", 8); + lore = new String[list.tagCount()]; + for(int k=0; k<list.tagCount(); k++) { + lore[k] = list.getStringTagAt(k); + } + } + + if(Item.itemRegistry.getObject(new ResourceLocation(internalname.toLowerCase())) != null) { + continue; + } + + String itemid = Item.getItemById(id).getRegistryName(); + String displayname = display.getString("Name"); + String[] info = new String[0]; + String clickcommand = ""; + + File file = new File(itemsLocation, internalname+".json"); + if(!file.exists()) { + writeItemJson(internalname, itemid, displayname, lore, info, clickcommand, damage, tag); + } else { + JsonObject existing = getJsonFromFile(file); + String nbttag = existing.get("nbttag").toString(); + if(nbttag.length() > 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<String, JsonElement> 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<String, String> oldShas = new HashMap<>(); + for (Map.Entry<String, JsonElement> entry : itemShaConfig.entrySet()) { + if (new File(itemsLocation, entry.getKey() + ".json").exists()) { + oldShas.put(entry.getKey() + ".json", entry.getValue().getAsString()); + } + } + Map<String, String> changedFiles = neuio.getChangedItems(oldShas); + + if (changedFiles != null) { + for (Map.Entry<String, String> 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<String, String> downloads = neuio.getItemsDownload(changedFiles.keySet()); + + String startMessage = "NotEnoughUpdates: Syncing with remote repository ("; + int downloaded = 0; + + for (Map.Entry<String, String> 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<splitToSeach.length; i++) { + if(lastMatch == -1 || lastMatch == i-1) { + if (splitToSeach[i].startsWith(s)) { + lastMatch = i; + continue out; + } + } + } + return false; + } + + return true; + } + + /** + * Checks whether an itemstack matches a certain query, following the same rules implemented by the + * more complex map-based search function. + */ + public boolean doesStackMatchSearch(ItemStack stack, String query) { + if(query.startsWith("title:")) { + query = query.substring(6); + return searchString(stack.getDisplayName(), query); + } else if(query.startsWith("desc:")) { + query = query.substring(5); + String lore = ""; + NBTTagCompound tag = stack.getTagCompound(); + if(tag != null) { + NBTTagCompound display = tag.getCompoundTag("display"); + if (display.hasKey("Lore", 9)) { + NBTTagList list = display.getTagList("Lore", 8); + for (int i = 0; i < list.tagCount(); i++) { + lore += list.getStringTagAt(i) + " "; + } + } + } + return searchString(lore, query); + } else if(query.startsWith("id:")) { + query = query.substring(3); + String internalName = getInternalNameForItem(stack); + return query.equalsIgnoreCase(internalName); + } else { + boolean result = false; + if(!query.trim().contains(" ")) { + StringBuilder sb = new StringBuilder(); + for(char c : query.toCharArray()) { + sb.append(c).append(" "); + } + result = result || searchString(stack.getDisplayName(), sb.toString()); + } + result = result || searchString(stack.getDisplayName(), query); + + String lore = ""; + NBTTagCompound tag = stack.getTagCompound(); + if(tag != null) { + NBTTagCompound display = tag.getCompoundTag("display"); + if (display.hasKey("Lore", 9)) { + NBTTagList list = display.getTagList("Lore", 8); + for (int i = 0; i < list.tagCount(); i++) { + lore += list.getStringTagAt(i) + " "; + } + } + } + + result = result || searchString(lore, query); + + return result; + } + } + + /** + * Returns the name of items which match a certain search query. + */ + public Set<String> search(String query) { + LinkedHashSet<String> 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<String> search(String query, TreeMap<String, HashMap<String, List<Integer>>> wordMap) { + HashMap<String, List<Integer>> matches = null; + + query = clean(query).toLowerCase(); + for(String queryWord : query.split(" ")) { + HashMap<String, List<Integer>> matchesToKeep = new HashMap<>(); + for(HashMap<String, List<Integer>> 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 <T> Map<String, T> subMapWithKeysThatAreSuffixes(String prefix, NavigableMap<String, T> 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 item stack and produces a JsonObject. This is used in the item editor. + */ + public JsonObject getJsonForItem(ItemStack stack) { + NBTTagCompound tag = stack.getTagCompound() == null ? new NBTTagCompound() : stack.getTagCompound(); + + //Item lore + String[] lore = new String[0]; + if(tag.hasKey("display", 10)) { + NBTTagCompound display = tag.getCompoundTag("display"); + + if(display.hasKey("Lore", 9)) { + NBTTagList list = display.getTagList("Lore", 8); + lore = new String[list.tagCount()]; + for(int i=0; i<list.tagCount(); i++) { + lore[i] = list.getStringTagAt(i); + } + } + } + + if(stack.getDisplayName().endsWith(" Recipes")) { + stack.setStackDisplayName(stack.getDisplayName().substring(0, stack.getDisplayName().length()-8)); + } + + if(lore.length > 0 && (lore[lore.length-1].contains("Click to view recipes!") || + lore[lore.length-1].contains("Click to view recipe!"))) { + String[] lore2 = new String[lore.length-2]; + System.arraycopy(lore, 0, lore2, 0, lore.length-2); + lore = lore2; + } + + JsonObject json = new JsonObject(); + json.addProperty("itemid", stack.getItem().getRegistryName()); + json.addProperty("displayname", stack.getDisplayName()); + json.addProperty("nbttag", tag.toString()); + json.addProperty("damage", stack.getItemDamage()); + + JsonArray jsonlore = new JsonArray(); + for(String line : lore) { + jsonlore.add(new JsonPrimitive(line)); + } + json.add("lore", jsonlore); + + return json; + } + + public String getInternalNameForItem(ItemStack stack) { + NBTTagCompound tag = stack.getTagCompound(); + //Internal id + if(tag != null && tag.hasKey("ExtraAttributes", 10)) { + NBTTagCompound ea = tag.getCompoundTag("ExtraAttributes"); + + if(ea.hasKey("id", 8)) { + return ea.getString("id").replaceAll(":", "-"); + } + } + return null; + } + + //Currently unused in production. + public void writeItemToFile(ItemStack stack) { + String internalname = getInternalNameForItem(stack); + + if(internalname == null) { + return; + } + + JsonObject json = getJsonForItem(stack); + json.addProperty("internalname", internalname); + json.addProperty("clickcommand", ""); + json.addProperty("modver", NotEnoughUpdates.VERSION); + + try { + writeJson(json, new File(itemsLocation, internalname+".json")); + } catch (IOException e) {} + + loadItem(internalname); //Collection: Cocoa Beans VII + } + + public JsonObject createItemJson(String internalname, String itemid, String displayname, String[] lore, + String crafttext, String infoType, String[] info, + String clickcommand, int damage, NBTTagCompound nbttag) { + return createItemJson(new JsonObject(), internalname, itemid, displayname, lore, crafttext, infoType, info, clickcommand, damage, nbttag); + } + + public JsonObject createItemJson(JsonObject base, String internalname, String itemid, String displayname, String[] lore, + String crafttext, String infoType, String[] info, + String clickcommand, int damage, NBTTagCompound nbttag) { + if(internalname == null || internalname.isEmpty()) { + return null; + } + + JsonObject json = gson.fromJson(gson.toJson(base, JsonObject.class), JsonObject.class); + json.addProperty("internalname", internalname); + json.addProperty("itemid", itemid); + json.addProperty("displayname", displayname); + json.addProperty("crafttext", crafttext); + json.addProperty("clickcommand", clickcommand); + json.addProperty("damage", damage); + json.addProperty("nbttag", nbttag.toString()); + json.addProperty("modver", NotEnoughUpdates.VERSION); + json.addProperty("infoType", infoType.toString()); + + if(info != null && info.length > 0) { + JsonArray jsoninfo = new JsonArray(); + for (String line : info) { + jsoninfo.add(new JsonPrimitive(line)); + } + json.add("info", jsoninfo); + } + + JsonArray jsonlore = new JsonArray(); + for(String line : lore) { + jsonlore.add(new JsonPrimitive(line)); + } + json.add("lore", jsonlore); + + return json; + } + + public boolean writeItemJson(String internalname, String itemid, String displayname, String[] lore, String crafttext, + String infoType, String[] info, String clickcommand, int damage, NBTTagCompound nbttag) { + return writeItemJson(new JsonObject(), internalname, itemid, displayname, lore, crafttext, infoType, info, clickcommand, damage, nbttag); + } + + public boolean writeItemJson(JsonObject base, String internalname, String itemid, String displayname, String[] lore, + String crafttext, String infoType, String[] info, String clickcommand, int damage, NBTTagCompound nbttag) { + JsonObject json = createItemJson(base, internalname, itemid, displayname, lore, crafttext, infoType, info, clickcommand, damage, nbttag); + if(json == null) { + return false; + } + + try { + writeJsonDefaultDir(json, internalname+".json"); + } catch(IOException e) { + return false; + } + + loadItem(internalname); + return true; + } + + public boolean uploadItemJson(String internalname, String itemid, String displayname, String[] lore, String crafttext, String infoType, String[] info, + String clickcommand, int damage, NBTTagCompound nbttag) { + JsonObject json = createItemJson(internalname, itemid, displayname, lore, crafttext, infoType, info, clickcommand, damage, nbttag); + if(json == null) { + return false; + } + + String username = Minecraft.getMinecraft().thePlayer.getName(); + String newBranchName = UUID.randomUUID().toString().substring(0, 8) + "-" + internalname + "-" + username; + String prTitle = internalname + "-" + username; + String prBody = "Internal name: " + internalname + "\nSubmitted by: " + username; + String file = "items/"+internalname+".json"; + if(!neuio.createNewRequest(newBranchName, prTitle, prBody, file, gson.toJson(json))) { + return false; + } + + try { + writeJsonDefaultDir(json, internalname+".json"); + } catch(IOException e) { + return false; + } + + loadItem(internalname); + return true; + } + + public void writeJson(JsonObject json, File file) throws IOException { + file.createNewFile(); + + try(BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))) { + writer.write(gson.toJson(json)); + } + } + + public void writeJsonDefaultDir(JsonObject json, String filename) throws IOException { + File file = new File(itemsLocation, filename); + writeJson(json, file); + } + + public TreeMap<String, JsonObject> getItemInformation() { + return itemMap; + } + + /** + * Stolen from https://www.journaldev.com/960/java-unzip-file-example + */ + private static void unzipIgnoreFirstFolder(String zipFilePath, String destDir) { + File dir = new File(destDir); + // create output directory if it doesn't exist + if(!dir.exists()) dir.mkdirs(); + FileInputStream fis; + //buffer for read and write data to file + byte[] buffer = new byte[1024]; + try { + fis = new FileInputStream(zipFilePath); + ZipInputStream zis = new ZipInputStream(fis); + ZipEntry ze = zis.getNextEntry(); + while(ze != null){ + if(!ze.isDirectory()) { + String fileName = ze.getName(); + fileName = fileName.substring(fileName.split("/")[0].length()+1); + File newFile = new File(destDir + File.separator + fileName); + //create directories for sub directories in zip + new File(newFile.getParent()).mkdirs(); + FileOutputStream fos = new FileOutputStream(newFile); + int len; + while ((len = zis.read(buffer)) > 0) { + fos.write(buffer, 0, len); + } + fos.close(); + } + //close this ZipEntry + zis.closeEntry(); + ze = zis.getNextEntry(); + } + //close last ZipEntry + zis.closeEntry(); + zis.close(); + fis.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Stolen from https://www.journaldev.com/960/java-unzip-file-example + */ + private static void unzip(InputStream src, File dest) { + //buffer for read and write data to file + byte[] buffer = new byte[1024]; + try { + ZipInputStream zis = new ZipInputStream(src); + ZipEntry ze = zis.getNextEntry(); + while(ze != null){ + if(!ze.isDirectory()) { + String fileName = ze.getName(); + File newFile = new File(dest, fileName); + //create directories for sub directories in zip + new File(newFile.getParent()).mkdirs(); + FileOutputStream fos = new FileOutputStream(newFile); + int len; + while ((len = zis.read(buffer)) > 0) { + fos.write(buffer, 0, len); + } + fos.close(); + } + //close this ZipEntry + zis.closeEntry(); + ze = zis.getNextEntry(); + } + //close last ZipEntry + zis.closeEntry(); + zis.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public ItemStack jsonToStack(JsonObject json) { + if(itemstackCache.containsKey(json.get("internalname").getAsString())) { + return itemstackCache.get(json.get("internalname").getAsString()).copy(); + } + ItemStack stack = new ItemStack(Item.itemRegistry.getObject( + new ResourceLocation(json.get("itemid").getAsString()))); + + if(stack.getItem() == null) { + stack = new ItemStack(Items.diamond, 1, 10); //Purple broken texture item + } else { + if(json.has("damage")) { + stack.setItemDamage(json.get("damage").getAsInt()); + } + + if(json.has("nbttag")) { + try { + NBTTagCompound tag = JsonToNBT.getTagFromJson(json.get("nbttag").getAsString()); + stack.setTagCompound(tag); + } catch(NBTException e) { + } + } + + if(json.has("lore")) { + NBTTagCompound display = stack.getTagCompound().getCompoundTag("display"); + NBTTagList lore = new NBTTagList(); + for(JsonElement line : json.get("lore").getAsJsonArray()) { + String lineStr = line.getAsString(); + if(!lineStr.contains("Click to view recipes!") && + !lineStr.contains("Click to view recipe!")) { + lore.appendTag(new NBTTagString(lineStr)); + } + } + display.setTag("Lore", lore); + NBTTagCompound tag = stack.getTagCompound(); + tag.setTag("display", display); + stack.setTagCompound(tag); + } + } + + itemstackCache.put(json.get("internalname").getAsString(), stack); + return stack; + } + + public boolean displayGuiItemUsages(String internalName, String text) { + List<ItemStack[]> craftMatrices = new ArrayList<>(); + List<JsonObject> results = new ArrayList<>(); + + if(!usagesMap.containsKey(internalName)) { + return false; + } + + for(String internalNameResult : usagesMap.get(internalName)) { + JsonObject item = getItemInformation().get(internalNameResult); + results.add(item); + + if(item != null && item.has("recipe")) { + JsonObject recipe = item.get("recipe").getAsJsonObject(); + + ItemStack[] craftMatrix = new ItemStack[9]; + + 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(); + int count = 1; + if(itemS != null && itemS.split(":").length == 2) { + count = Integer.valueOf(itemS.split(":")[1]); + itemS = itemS.split(":")[0]; + } + JsonObject craft = getItemInformation().get(itemS); + if(craft != null) { + ItemStack stack = jsonToStack(craft); + stack.stackSize = count; + craftMatrix[i] = stack; + } + } + + craftMatrices.add(craftMatrix); + } + } + + if(craftMatrices.size() > 0) { + Minecraft.getMinecraft().displayGuiScreen(new GuiItemUsages(craftMatrices, results, text, this)); + return true; + } + return false; + } + + public boolean displayGuiItemRecipe(String internalName, String text) { + JsonObject item = getItemInformation().get(internalName); + if(item != null && item.has("recipe")) { + JsonObject recipe = item.get("recipe").getAsJsonObject(); + + ItemStack[] craftMatrix = new ItemStack[9]; + + 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(); + int count = 1; + if(itemS != null && itemS.split(":").length == 2) { + count = Integer.valueOf(itemS.split(":")[1]); + itemS = itemS.split(":")[0]; + } + JsonObject craft = getItemInformation().get(itemS); + if(craft != null) { + ItemStack stack = jsonToStack(craft); + stack.stackSize = count; + craftMatrix[i] = stack; + } + } + + Minecraft.getMinecraft().displayGuiScreen(new GuiItemRecipe(craftMatrix, item, text, this)); + return true; + } + return false; + } + + public boolean failViewItem(String text) { + if(viewItemAttemptID != null && !viewItemAttemptID.isEmpty()) { + if(System.currentTimeMillis() - viewItemAttemptTime < 500) { + return displayGuiItemRecipe(viewItemAttemptID, text); + } + } + return false; + } + + public File getWebFile(String url) { + File f = new File(configLocation, "tmp/"+Base64.getEncoder().encodeToString(url.getBytes())+".html"); + if(f.exists()) { + return f; + } + + try { + f.getParentFile().mkdirs(); + f.createNewFile(); + f.deleteOnExit(); + } catch (IOException e) { + return null; + } + try (BufferedInputStream inStream = new BufferedInputStream(new URL(url+"?action=raw&templates=expand").openStream()); + FileOutputStream fileOutputStream = new FileOutputStream(f)) { + 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(); + return null; + } + + return f; + } + +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/NEUOverlay.java b/src/main/java/io/github/moulberry/notenoughupdates/NEUOverlay.java new file mode 100644 index 00000000..e60ca14f --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/NEUOverlay.java @@ -0,0 +1,1704 @@ +package io.github.moulberry.notenoughupdates; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import io.github.moulberry.notenoughupdates.infopanes.*; +import io.github.moulberry.notenoughupdates.itemeditor.NEUItemEditor; +import io.github.moulberry.notenoughupdates.util.LerpingFloat; +import io.github.moulberry.notenoughupdates.util.LerpingInteger; +import io.github.moulberry.notenoughupdates.util.TexLoc; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.*; +import net.minecraft.client.gui.inventory.GuiContainer; +import net.minecraft.client.renderer.*; +import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.client.renderer.entity.RenderManager; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.client.resources.model.IBakedModel; +import net.minecraft.client.shader.Framebuffer; +import net.minecraft.client.shader.Shader; +import net.minecraft.client.shader.ShaderGroup; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityList; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.init.Items; +import net.minecraft.inventory.Slot; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.Matrix4f; +import net.minecraft.util.ResourceLocation; +import net.minecraft.world.World; +import org.apache.commons.lang3.StringUtils; +import org.lwjgl.input.Keyboard; +import org.lwjgl.input.Mouse; +import org.lwjgl.opengl.GL11; + +import java.awt.*; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.text.NumberFormat; +import java.util.List; +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import static io.github.moulberry.notenoughupdates.GuiTextures.*; + +public class NEUOverlay extends Gui { + + private NEUManager manager; + + private String mobRegex = ".*?((_MONSTER)|(_ANIMAL)|(_MINIBOSS)|(_BOSS)|(_SC))$"; + private String petRegex = ".*?;[0-4]$"; + + private ResourceLocation[] sortIcons = new ResourceLocation[] { + sort_all, sort_mob, sort_pet, sort_tool, sort_armor, sort_accessory + }; + private ResourceLocation[] sortIconsActive = new ResourceLocation[] { + sort_all_active, sort_mob_active, sort_pet_active, sort_tool_active, sort_armor_active, sort_accessory_active + }; + + private ResourceLocation[] orderIcons = new ResourceLocation[] { + order_alphabetical, order_rarity + }; + private ResourceLocation[] orderIconsActive = new ResourceLocation[] { + order_alphabetical_active, order_rarity_active + }; + + private int searchBarXSize = 200; + private final int searchBarYOffset = 10; + private final int searchBarYSize = 40; + private final int searchBarPadding = 2; + + public static final int BOX_PADDING = 15; + public static final int ITEM_PADDING = 4; + public static final int ITEM_SIZE = 16; + + private Color bg = new Color(90, 90, 140, 50); + private Color fg = new Color(100,100,100, 255); + + //private String informationPaneTitle; + //private ResourceLocation informationPaneImage = null; + //private String[] informationPane; + //private AtomicInteger webpageAwaitID = new AtomicInteger(-1); + //private boolean configOpen = false; + private InfoPane activeInfoPane = null; + + private TreeSet<JsonObject> searchedItems = null; + private JsonObject[] searchedItemsArr = null; + + private boolean itemPaneOpen = false; + private boolean hoveringItemPaneToggle = false; + + private int page = 0; + + private LerpingFloat itemPaneOffsetFactor = new LerpingFloat(1); + private LerpingInteger itemPaneTabOffset = new LerpingInteger(20, 50); + private LerpingFloat infoPaneOffsetFactor = new LerpingFloat(0); + + private boolean searchMode = false; + private long millisLastLeftClick = 0; + + boolean mouseDown = false; + + private boolean redrawItems = false; + + private boolean searchBarHasFocus = false; + GuiTextField textField = new GuiTextField(0, null, 0, 0, 0, 0); + + private static final int COMPARE_MODE_ALPHABETICAL = 0; + private static final int COMPARE_MODE_RARITY = 1; + + private static final int SORT_MODE_ALL = 0; + private static final int SORT_MODE_MOB = 1; + private static final int SORT_MODE_PET = 2; + private static final int SORT_MODE_TOOL = 3; + private static final int SORT_MODE_ARMOR = 4; + private static final int SORT_MODE_ACCESSORY = 5; + + private ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft()); + + public NEUOverlay(NEUManager manager) { + this.manager = manager; + textField.setFocused(true); + textField.setCanLoseFocus(false); + } + + public void reset() { + searchBarHasFocus = false; + if(!(searchMode || (manager.config.keepopen.value && itemPaneOpen))) { + itemPaneOpen = false; + itemPaneOffsetFactor.setValue(1); + itemPaneTabOffset.setValue(20); + } + } + + public void showInfo(JsonObject item) { + if(item.has("info") && item.has("infoType")) { + JsonArray lore = item.get("info").getAsJsonArray(); + String[] loreA = new String[lore.size()]; + for (int i = 0; i < lore.size(); i++) loreA[i] = lore.get(i).getAsString(); + String loreS = StringUtils.join(loreA, "\n"); + + String name = item.get("displayname").getAsString(); + switch(item.get("infoType").getAsString()) { + case "WIKI_URL": + displayInformationPane(HTMLInfoPane.createFromWikiUrl(this, manager, name, loreS)); + return; + case "WIKI": + displayInformationPane(HTMLInfoPane.createFromWiki(this, manager, name, loreS)); + return; + case "HTML": + displayInformationPane(new HTMLInfoPane(this, manager, name, loreS)); + return; + } + displayInformationPane(new TextInfoPane(this, manager, name, loreS)); + } + } + + /** + * Handles the mouse input, cancelling the forge event if a NEU gui element is clicked. + */ + public boolean mouseInput() { + int width = scaledresolution.getScaledWidth(); + int height = scaledresolution.getScaledHeight(); + + int mouseX = Mouse.getX() / scaledresolution.getScaleFactor(); + int mouseY = height - Mouse.getY() / scaledresolution.getScaleFactor(); + + if(Mouse.getEventButtonState()) { + mouseDown = true; + } else if(Mouse.getEventButton() != -1) { + mouseDown = false; + } + + //Unfocuses the search bar by default. Search bar is focused if the click is on the bar itself. + if(Mouse.getEventButtonState()) setSearchBarFocus(false); + + //Item selection (right) gui + if(mouseX > width*getItemPaneOffsetFactor()) { + if(!Mouse.getEventButtonState()) return true; //End early if the mouse isn't pressed, but still cancel event. + + AtomicBoolean clickedItem = new AtomicBoolean(false); + iterateItemSlots(new ItemSlotConsumer() { + public void consume(int x, int y, int id) { + if(mouseX >= x-1 && mouseX <= x+ITEM_SIZE+1) { + if(mouseY >= y-1 && mouseY <= y+ITEM_SIZE+1) { + clickedItem.set(true); + + JsonObject item = getSearchedItemPage(id); + if (item != null) { + if(Mouse.getEventButton() == 0) { + manager.showRecipe(item); + } else if(Mouse.getEventButton() == 1) { + showInfo(item); + } else if(Mouse.getEventButton() == 2) { + textField.setText("id:"+item.get("internalname").getAsString()); + updateSearch(); + searchMode = true; + } + } + } + } + } + }); + if(!clickedItem.get()) { + int paneWidth = (int)(width/3*getWidthMult()); + int leftSide = (int)(width*getItemPaneOffsetFactor()); + int rightSide = leftSide+paneWidth-BOX_PADDING-getItemBoxXPadding(); + leftSide = leftSide+BOX_PADDING+getItemBoxXPadding(); + + FontRenderer fr = Minecraft.getMinecraft().fontRendererObj; + int maxPages = getMaxPages(); + String name = scaledresolution.getScaleFactor()<4?"Page: ":""; + float maxStrLen = fr.getStringWidth(EnumChatFormatting.BOLD+name + maxPages + "/" + maxPages); + float maxButtonXSize = (rightSide-leftSide+2 - maxStrLen*0.5f - 10)/2f; + int buttonXSize = (int)Math.min(maxButtonXSize, getSearchBarYSize()*480/160f); + int ySize = (int)(buttonXSize/480f*160); + int yOffset = (int)((getSearchBarYSize()-ySize)/2f); + int top = BOX_PADDING+yOffset; + + if(mouseY >= top && mouseY <= top+ySize) { + int leftPrev = leftSide-1; + if(mouseX > leftPrev && mouseX < leftPrev+buttonXSize) { //"Previous" button + setPage(page-1); + } + int leftNext = rightSide+1-buttonXSize; + if(mouseX > leftNext && mouseX < leftNext+buttonXSize) { //"Next" button + setPage(page+1); + } + } + + float sortIconsMinX = (sortIcons.length+orderIcons.length)*(ITEM_SIZE+ITEM_PADDING)+ITEM_SIZE; + float availableX = rightSide-(leftSide+BOX_PADDING+getItemBoxXPadding()); + float sortOrderScaleFactor = Math.min(1, availableX / sortIconsMinX); + + int scaledITEM_SIZE = (int)(ITEM_SIZE*sortOrderScaleFactor); + int scaledItemPaddedSize = (int)((ITEM_SIZE+ITEM_PADDING)*sortOrderScaleFactor); + int iconTop = height-BOX_PADDING-(ITEM_SIZE+scaledITEM_SIZE)/2-1; + + if(mouseY >= iconTop && mouseY <= iconTop+scaledITEM_SIZE) { + for(int i=0; i<orderIcons.length; i++) { + int orderIconX = leftSide+BOX_PADDING+getItemBoxXPadding()+i*scaledItemPaddedSize; + if(mouseX >= orderIconX && mouseX <= orderIconX+scaledITEM_SIZE) { + if(Mouse.getEventButton() == 0) { + manager.config.compareMode.value = new Double(i); + updateSearch(); + } else if(Mouse.getEventButton() == 1) { + manager.config.compareAscending.value.set(i, !manager.config.compareAscending.value.get(i)); + updateSearch(); + } + } + } + + for(int i=0; i<sortIcons.length; i++) { + int sortIconX = rightSide-scaledITEM_SIZE-i*scaledItemPaddedSize; + if(mouseX >= sortIconX && mouseX <= sortIconX+scaledITEM_SIZE) { + manager.config.sortMode.value = new Double(i); + updateSearch(); + } + } + } + } + return true; + } + + if(Mouse.getEventButton() == 2) { + Slot slot = Utils.getSlotUnderMouse((GuiContainer)Minecraft.getMinecraft().currentScreen); + if(slot != null) { + ItemStack hover = slot.getStack(); + if(hover != null) { + textField.setText("id:"+manager.getInternalNameForItem(hover)); + updateSearch(); + searchMode = true; + return true; + } + } + } + + //Clicking on "close info pane" button + if(mouseX > width*getInfoPaneOffsetFactor()-22 && mouseX < width*getInfoPaneOffsetFactor()-6) { + if(mouseY > 7 && mouseY < 23) { + if(Mouse.getEventButtonState() && Mouse.getEventButton() < 2) { //Left or right click up + displayInformationPane(null); + return true; + } + } + } + + //Search bar + if(mouseX >= width/2 - getSearchBarXSize()/2 && mouseX <= width/2 + getSearchBarXSize()/2) { + if(mouseY >= height - searchBarYOffset - getSearchBarYSize() && + mouseY <= height - searchBarYOffset) { + if(Mouse.getEventButtonState()) { + setSearchBarFocus(true); + if(Mouse.getEventButton() == 1) { //Right mouse button down + textField.setText(""); + updateSearch(); + } else { + if(System.currentTimeMillis() - millisLastLeftClick < 300) { + searchMode = !searchMode; + } + textField.setCursorPosition(getClickedIndex(mouseX, mouseY)); + millisLastLeftClick = System.currentTimeMillis(); + } + } + return true; + } + } + + int paddingUnscaled = searchBarPadding/scaledresolution.getScaleFactor(); + int topTextBox = height - searchBarYOffset - getSearchBarYSize(); + int iconSize = getSearchBarYSize()+paddingUnscaled*2; + if(paddingUnscaled < 1) paddingUnscaled = 1; + + if(mouseY > topTextBox - paddingUnscaled && mouseY < topTextBox - paddingUnscaled + iconSize) { + if(mouseX > width/2 + getSearchBarXSize()/2 + paddingUnscaled*6 && + mouseX < width/2 + getSearchBarXSize()/2 + paddingUnscaled*6 + iconSize) { + if(Mouse.getEventButtonState()) { + displayInformationPane(HTMLInfoPane.createFromWikiUrl(this, manager, "Help", + "https://moulberry.github.io/files/neu_help.html")); + } + } else if(mouseX > width/2 - getSearchBarXSize()/2 - paddingUnscaled*6 - iconSize && + mouseX < width/2 - getSearchBarXSize()/2 - paddingUnscaled*6) { + if(Mouse.getEventButtonState()) { + if(activeInfoPane instanceof SettingsInfoPane) { + displayInformationPane(null); + } else { + displayInformationPane(new SettingsInfoPane(this, manager)); + } + } + } + } + + if(activeInfoPane != null) { + if(mouseX < width*getInfoPaneOffsetFactor()) { + activeInfoPane.mouseInput(width, height, mouseX, mouseY, mouseDown); + return true; + } + } + + return false; + } + + public int getSearchBarXSize() { + if(scaledresolution.getScaleFactor()==4) return (int)(searchBarXSize*0.8); + return searchBarXSize; + } + + public void displayInformationPane(InfoPane pane) { + if(pane == null) { + infoPaneOffsetFactor.setTarget(0); + } else { + infoPaneOffsetFactor.setTarget(1/3f); + } + infoPaneOffsetFactor.resetTimer(); + this.activeInfoPane = pane; + } + + public InfoPane getActiveInfoPane() { + return activeInfoPane; + } + + /*public void displayInformationPane(String title, String infoType, String[] info) { + scrollHeight.setValue(0); + informationPaneTitle = title; + informationPaneImage = null; + informationPane = null; + + configOpen = false; + + infoPaneOffsetFactor.setTarget(1/3f); + infoPaneOffsetFactor.resetTimer(); + + webpageAwaitID.incrementAndGet(); + + if(info == null || info.length == 0) { + informationPane = new String[]{"\u00A77No additional information."}; + } else { + String joined = StringUtils.join(info, "\n"); + String wiki = null; + String html = null; + if(infoType.equals("TEXT")) { + informationPane = info; + return; + } else if(infoType.equals("WIKI_URL")) { + File f = manager.getWebFile(joined); + if(f == null) { + informationPane = new String[] { EnumChatFormatting.RED+"Failed to load wiki url: "+joined }; + return; + }; + + StringBuilder sb = new StringBuilder(); + try(BufferedReader br = new BufferedReader(new InputStreamReader( + new FileInputStream(f), StandardCharsets.UTF_8))) { + String l; + while((l = br.readLine()) != null){ + sb.append(l).append("\n"); + } + } catch(IOException e) { + informationPane = new String[] { EnumChatFormatting.RED+"Failed to load wiki url: "+joined }; + return; + } + wiki = sb.toString(); + } + + if(infoType.equals("WIKI") || wiki != null) { + if(wiki == null) wiki = joined; + try { + String[] split = wiki.split("</infobox>"); + wiki = split[split.length - 1]; //Remove everything before infobox + wiki = wiki.split("<span class=\"navbox-vde\">")[0]; //Remove navbox + wiki = wiki.split("<table class=\"navbox mw-collapsible\"")[0]; + wiki = "__NOTOC__\n" + wiki; //Remove TOC + try (PrintWriter out = new PrintWriter(new File(manager.configLocation, "debug/parsed.txt"))) { + out.println(wiki); + } catch (IOException e) { + } + html = wikiModel.render(wiki); + try (PrintWriter out = new PrintWriter(new File(manager.configLocation, "debug/html.txt"))) { + out.println(html); + } catch (IOException e) { + } + } catch (Exception e) { + informationPane = new String[]{EnumChatFormatting.RED + "Failed to parse wiki: " + joined}; + return; + } + } + + if(infoType.equals("HTML") || html != null) { + if(html == null) html = joined; + processAndSetWebpageImage(html, title); + } + } + }*/ + + public int getClickedIndex(int mouseX, int mouseY) { + int width = scaledresolution.getScaledWidth(); + int height = scaledresolution.getScaledHeight(); + + int xComp = mouseX - (width/2 - getSearchBarXSize()/2 + 5); + + String trimmed = Minecraft.getMinecraft().fontRendererObj.trimStringToWidth(textField.getText(), xComp); + int linePos = trimmed.length(); + if(linePos != textField.getText().length()) { + char after = textField.getText().charAt(linePos); + int trimmedWidth = Minecraft.getMinecraft().fontRendererObj.getStringWidth(trimmed); + int charWidth = Minecraft.getMinecraft().fontRendererObj.getCharWidth(after); + if(trimmedWidth + charWidth/2 < xComp-5) { + linePos++; + } + } + return linePos; + } + + public void setSearchBarFocus(boolean focus) { + if(focus) { + itemPaneOpen = true; + } + searchBarHasFocus = focus; + } + + /** + * Handles the keyboard input, cancelling the forge event if the search bar has focus. + */ + public boolean keyboardInput(boolean hoverInv) { + if(Minecraft.getMinecraft().currentScreen == null) return false; + Keyboard.enableRepeatEvents(true); + + if(Keyboard.isKeyDown(Keyboard.KEY_Y)) { + displayInformationPane(new DevInfoPane(this, manager)); + //displayInformationPane(new QOLInfoPane(this, manager)); + } + + if(Keyboard.getEventKeyState()) { + if(searchBarHasFocus) { + if(Keyboard.getEventKey() == 1) { + searchBarHasFocus = false; + } else { + if(textField.textboxKeyTyped(Keyboard.getEventCharacter(), Keyboard.getEventKey())) { + updateSearch(); + } + } + } else { + if(activeInfoPane != null) { + activeInfoPane.keyboardInput(); + } + + AtomicReference<String> internalname = new AtomicReference<>(null); + AtomicReference<ItemStack> itemstack = new AtomicReference<>(null); + Slot slot = Utils.getSlotUnderMouse((GuiContainer)Minecraft.getMinecraft().currentScreen); + if(slot != null) { + ItemStack hover = slot.getStack(); + if(hover != null) { + internalname.set(manager.getInternalNameForItem(hover)); + itemstack.set(hover); + } + } else if(!hoverInv) { + int height = scaledresolution.getScaledHeight(); + + int mouseX = Mouse.getX() / scaledresolution.getScaleFactor(); + int mouseY = height - Mouse.getY() / scaledresolution.getScaleFactor(); + + iterateItemSlots(new ItemSlotConsumer() { + public void consume(int x, int y, int id) { + if (mouseX >= x - 1 && mouseX <= x + ITEM_SIZE + 1) { + if (mouseY >= y - 1 && mouseY <= y + ITEM_SIZE + 1) { + JsonObject json = getSearchedItemPage(id); + if (json != null) internalname.set(json.get("internalname").getAsString()); + } + } + } + }); + } + if(internalname.get() != null) { + if(itemstack.get() != null) { + if(manager.config.enableItemEditing.value && Keyboard.getEventCharacter() == 'k') { + Minecraft.getMinecraft().displayGuiScreen(new NEUItemEditor(manager, + internalname.get(), manager.getJsonForItem(itemstack.get()))); + return true; + } + } + JsonObject item = manager.getItemInformation().get(internalname.get()); + if(item != null) { + if(Keyboard.getEventCharacter() == 'u') { + manager.displayGuiItemUsages(internalname.get(), ""); + return true; + } else if(Keyboard.getEventCharacter() == 'f') { + toggleRarity(item.get("internalname").getAsString()); + return true; + } else if(Keyboard.getEventCharacter() == 'r') { + manager.showRecipe(item); + return true; + } else if(Keyboard.getEventCharacter() == 'l') { + if(Minecraft.getMinecraft().thePlayer.capabilities.isCreativeMode) { + Minecraft.getMinecraft().thePlayer.inventory.addItemStackToInventory( + manager.jsonToStack(item)); + } + } else if(manager.config.enableItemEditing.value && Keyboard.getEventCharacter() == 'k') { + Minecraft.getMinecraft().displayGuiScreen(new NEUItemEditor(manager, + internalname.get(), item)); + return true; + } + } + } + } + } + + return searchBarHasFocus; //Cancels keyboard events if the search bar has focus + } + + public void toggleRarity(String internalname) { + if(getFavourites().contains(internalname)) { + getFavourites().remove(internalname); + } else { + getFavourites().add(internalname); + } + updateSearch(); + } + + String[] rarityArr = new String[] { + EnumChatFormatting.WHITE+EnumChatFormatting.BOLD.toString()+"COMMON", + EnumChatFormatting.GREEN+EnumChatFormatting.BOLD.toString()+"UNCOMMON", + EnumChatFormatting.BLUE+EnumChatFormatting.BOLD.toString()+"RARE", + EnumChatFormatting.DARK_PURPLE+EnumChatFormatting.BOLD.toString()+"EPIC", + EnumChatFormatting.GOLD+EnumChatFormatting.BOLD.toString()+"LEGENDARY", + EnumChatFormatting.LIGHT_PURPLE+EnumChatFormatting.BOLD.toString()+"SPECIAL", + }; + public int getRarity(JsonArray lore) { + for(int i=lore.size()-1; i>=0; i--) { + String line = lore.get(i).getAsString(); + + for(int j=0; j<rarityArr.length; j++) { + if(line.startsWith(rarityArr[j])) { + return j; + } + } + } + return -1; + } + + private int getCompareMode() { + return manager.config.compareMode.value.intValue(); + } + private int getSortMode() { + return manager.config.sortMode.value.intValue(); + } + private List<Boolean> getCompareAscending() { + return manager.config.compareAscending.value; + } + private List<String> getFavourites() { + return manager.config.favourites.value; + } + + private Comparator<JsonObject> getItemComparator() { + return (o1, o2) -> { + //1 (mult) if o1 should appear after o2 + //-1 (-mult) if o2 should appear after o1 + if(getFavourites().contains(o1.get("internalname").getAsString()) && !getFavourites().contains(o2.get("internalname").getAsString())) { + return -1; + } + if(!getFavourites().contains(o1.get("internalname").getAsString()) && getFavourites().contains(o2.get("internalname").getAsString())) { + return 1; + } + + int mult = getCompareAscending().get(getCompareMode()) ? 1 : -1; + if(getCompareMode() == COMPARE_MODE_RARITY) { + int rarity1 = getRarity(o1.get("lore").getAsJsonArray()); + int rarity2 = getRarity(o2.get("lore").getAsJsonArray()); + + if(rarity1 < rarity2) return mult; + if(rarity1 > rarity2) return -mult; + } + + String i1 = o1.get("internalname").getAsString(); + String[] split1 = i1.split("_"); + String last1 = split1[split1.length-1]; + String start1 = i1.substring(0, i1.length()-last1.length()); + + String i2 = o2.get("internalname").getAsString(); + String[] split2 = i2.split("_"); + String last2 = split2[split2.length-1]; + String start2 = i2.substring(0, i2.length()-last2.length()); + + mult = getCompareAscending().get(COMPARE_MODE_ALPHABETICAL) ? 1 : -1; + if(start1.equals(start2)) { + String[] order = new String[]{"HELMET","CHESTPLATE","LEGGINGS","BOOTS"}; + int type1 = checkItemType(o1.get("lore").getAsJsonArray(), order); + int type2 = checkItemType(o2.get("lore").getAsJsonArray(), order); + + + if(type1 < type2) return -mult; + if(type1 > type2) return mult; + } + + int nameComp = mult*o1.get("displayname").getAsString().replaceAll("(?i)\\u00A7.", "") + .compareTo(o2.get("displayname").getAsString().replaceAll("(?i)\\u00A7.", "")); + if(nameComp != 0) { + return nameComp; + } + return mult*o1.get("internalname").getAsString().compareTo(o2.get("internalname").getAsString()); + }; + } + + public int checkItemType(JsonArray lore, String... typeMatches) { + for(int i=lore.size()-1; i>=0; i--) { + String line = lore.get(i).getAsString(); + + for(String rarity : rarityArr) { + for(int j=0; j<typeMatches.length; j++) { + if(line.trim().equals(rarity + " " + typeMatches[j])) { + return j; + } + } + } + } + return -1; + } + + public boolean checkMatchesSort(String internalname, JsonObject item) { + if(getSortMode() == SORT_MODE_ALL) { + return !internalname.matches(mobRegex); + } else if(getSortMode() == SORT_MODE_MOB) { + return internalname.matches(mobRegex); + } else if(getSortMode() == SORT_MODE_PET) { + return internalname.matches(petRegex); + } else if(getSortMode() == SORT_MODE_TOOL) { + return checkItemType(item.get("lore").getAsJsonArray(), + "SWORD", "BOW", "AXE", "PICKAXE", "FISHING ROD", "WAND", "SHOVEL", "HOE") >= 0; + } else if(getSortMode() == SORT_MODE_ARMOR) { + return checkItemType(item.get("lore").getAsJsonArray(), "HELMET", "CHESTPLATE", "LEGGINGS", "BOOTS") >= 0; + } else if(getSortMode() == SORT_MODE_ACCESSORY) { + return checkItemType(item.get("lore").getAsJsonArray(), "ACCESSORY") >= 0; + } + return true; + } + + public void updateSearch() { + if(searchedItems==null) searchedItems = new TreeSet<>(getItemComparator()); + searchedItems.clear(); + searchedItemsArr = null; + redrawItems = true; + Set<String> itemsMatch = manager.search(textField.getText()); + for(String itemname : itemsMatch) { + JsonObject item = manager.getItemInformation().get(itemname); + if(checkMatchesSort(itemname, item)) { + searchedItems.add(item); + } + } + } + + public JsonObject[] getSearchedItems() { + if(searchedItems==null) { + updateSearch(); + } + if(searchedItemsArr==null) { + searchedItemsArr = new JsonObject[searchedItems.size()]; + int i=0; + for(JsonObject item : searchedItems) { + searchedItemsArr[i] = item; + i++; + } + } + return searchedItemsArr; + } + + public JsonObject getSearchedItemPage(int index) { + if(index < getSlotsXSize()*getSlotsYSize()) { + int actualIndex = index + getSlotsXSize()*getSlotsYSize()*page; + if(actualIndex < getSearchedItems().length) { + return getSearchedItems()[actualIndex]; + } else { + return null; + } + } else { + return null; + } + } + + public int getItemBoxXPadding() { + int width = scaledresolution.getScaledWidth(); + return (((int)(width/3*getWidthMult())-2*BOX_PADDING)%(ITEM_SIZE+ITEM_PADDING)+ITEM_PADDING)/2; + } + + private abstract class ItemSlotConsumer { + public abstract void consume(int x, int y, int id); + } + + public void iterateItemSlots(ItemSlotConsumer itemSlotConsumer) { + int width = scaledresolution.getScaledWidth(); + int itemBoxXPadding = getItemBoxXPadding(); + iterateItemSlots(itemSlotConsumer, (int)(width*getItemPaneOffsetFactor())+BOX_PADDING+itemBoxXPadding); + } + + /** + * Iterates through all the item slots in the right panel and calls a ItemSlotConsumer for each slot with + * arguments equal to the slot's x and y position respectively. This is used in order to prevent + * code duplication issues. + */ + public void iterateItemSlots(ItemSlotConsumer itemSlotConsumer, int xStart) { + int width = scaledresolution.getScaledWidth(); + int height = scaledresolution.getScaledHeight(); + + int paneWidth = (int)(width/3*getWidthMult()); + int itemBoxYPadding = ((height-getSearchBarYSize()-2*BOX_PADDING-ITEM_SIZE-2)%(ITEM_SIZE+ITEM_PADDING)+ITEM_PADDING)/2; + + int yStart = BOX_PADDING+getSearchBarYSize()+itemBoxYPadding; + int itemBoxXPadding = getItemBoxXPadding(); + int xEnd = xStart+paneWidth-BOX_PADDING*2-ITEM_SIZE-itemBoxXPadding; + int yEnd = height-BOX_PADDING-ITEM_SIZE-2-itemBoxYPadding; + + //Render the items, displaying the tooltip if the cursor is over the item + int id = 0; + for(int y = yStart; y < yEnd; y+=ITEM_SIZE+ITEM_PADDING) { + for(int x = xStart; x < xEnd; x+=ITEM_SIZE+ITEM_PADDING) { + itemSlotConsumer.consume(x, y, id++); + } + } + } + + public float getWidthMult() { + float scaleFMult = 1; + if(scaledresolution.getScaleFactor()==4) scaleFMult = 0.9f; + return (float)Math.max(0.5, Math.min(1.5, manager.config.paneWidthMult.value.floatValue()))*scaleFMult; + } + + /** + * Calculates the number of horizontal item slots. + */ + public int getSlotsXSize() { + int width = scaledresolution.getScaledWidth(); + + int paneWidth = (int)(width/3*getWidthMult()); + int itemBoxXPadding = (((int)(width-width*getItemPaneOffsetFactor())-2*BOX_PADDING)%(ITEM_SIZE+ITEM_PADDING)+ITEM_PADDING)/2; + int xStart = (int)(width*getItemPaneOffsetFactor())+BOX_PADDING+itemBoxXPadding; + int xEnd = (int)(width*getItemPaneOffsetFactor())+paneWidth-BOX_PADDING-ITEM_SIZE; + + return (int)Math.ceil((xEnd - xStart)/((float)(ITEM_SIZE+ITEM_PADDING))); + } + + /** + * Calculates the number of vertical item slots. + */ + public int getSlotsYSize() { + int height = scaledresolution.getScaledHeight(); + + int itemBoxYPadding = ((height-getSearchBarYSize()-2*BOX_PADDING-ITEM_SIZE-2)%(ITEM_SIZE+ITEM_PADDING)+ITEM_PADDING)/2; + int yStart = BOX_PADDING+getSearchBarYSize()+itemBoxYPadding; + int yEnd = height-BOX_PADDING-ITEM_SIZE-2-itemBoxYPadding; + + return (int)Math.ceil((yEnd - yStart)/((float)(ITEM_SIZE+ITEM_PADDING))); + } + + public int getMaxPages() { + if(getSearchedItems().length == 0) return 1; + return (int)Math.ceil(getSearchedItems().length/(float)getSlotsYSize()/getSlotsXSize()); + } + + /** + * Takes in the x and y coordinates of a slot and returns the id of that slot. + */ + /*public int getSlotId(int x, int y) { + int width = scaledresolution.getScaledWidth(); + int height = scaledresolution.getScaledHeight(); + + int itemBoxXPadding = (((int)(width-width*getItemPaneOffsetFactor())-2*BOX_PADDING)%(ITEM_SIZE+ITEM_PADDING)+ITEM_PADDING)/2; + int itemBoxYPadding = ((height-getSearchBarYSize()-2*BOX_PADDING-ITEM_SIZE-2)%(ITEM_SIZE+ITEM_PADDING)+ITEM_PADDING)/2; + + int xStart = (int)(width*getItemPaneOffsetFactor())+BOX_PADDING+itemBoxXPadding; + int yStart = BOX_PADDING+getSearchBarYSize()+itemBoxYPadding; + + int xIndex = (x-xStart)/(ITEM_SIZE+ITEM_PADDING); + int yIndex = (y-yStart)/(ITEM_SIZE+ITEM_PADDING); + return xIndex + yIndex*getSlotsXSize(); + }*/ + + public int getSearchBarYSize() { + return Math.max(searchBarYSize/scaledresolution.getScaleFactor(), ITEM_SIZE); + } + + public void renderNavElement(int leftSide, int rightSide, int maxPages, int page, String name) { + FontRenderer fr = Minecraft.getMinecraft().fontRendererObj; + + String pageText = EnumChatFormatting.BOLD+name + page + "/" + maxPages; + + float maxStrLen = fr.getStringWidth(EnumChatFormatting.BOLD+name + maxPages + "/" + maxPages); + float maxButtonXSize = (rightSide-leftSide+2 - maxStrLen*0.5f - 10)/2f; + int buttonXSize = (int)Math.min(maxButtonXSize, getSearchBarYSize()*480/160f); + int ySize = (int)(buttonXSize/480f*160); + int yOffset = (int)((getSearchBarYSize()-ySize)/2f); + int top = BOX_PADDING+yOffset; + + /*drawRect(leftSide-1, top, + rightSide+1, + top+ySize, fg.getRGB());*/ + + int leftPressed = 0; + int rightPressed = 0; + + if(Mouse.isButtonDown(0) || Mouse.isButtonDown(1)) { + int height = scaledresolution.getScaledHeight(); + + int mouseX = Mouse.getX() / scaledresolution.getScaleFactor(); + int mouseY = height - Mouse.getY() / scaledresolution.getScaleFactor(); + + if(mouseY >= top && mouseY <= top+ySize) { + int leftPrev = leftSide-1; + if(mouseX > leftPrev && mouseX < leftPrev+buttonXSize) { //"Previous" button + leftPressed = 1; + } + int leftNext = rightSide+1-buttonXSize; + if(mouseX > leftNext && mouseX < leftNext+buttonXSize) { //"Next" button + rightPressed = 1; + } + } + } + + drawRect(leftSide-1, top, leftSide-1+buttonXSize, top+ySize, fg.getRGB()); + GlStateManager.color(1f, 1f, 1f, 1f); + Minecraft.getMinecraft().getTextureManager().bindTexture(rightarrow); + Utils.drawTexturedRect(leftSide-1+leftPressed, + top+leftPressed, + buttonXSize, ySize, 1, 0, 0, 1); + Minecraft.getMinecraft().getTextureManager().bindTexture(rightarrow_overlay); + Utils.drawTexturedRect(leftSide-1, + top, + buttonXSize, ySize, 1-leftPressed, leftPressed, 1-leftPressed, leftPressed); + GlStateManager.bindTexture(0); + Utils.drawStringCenteredScaled(EnumChatFormatting.BOLD+"Prev", fr, + leftSide-1+buttonXSize*300/480f+leftPressed, + top+ySize/2f+leftPressed, false, + (int)(buttonXSize*240/480f), Color.BLACK.getRGB()); + + drawRect(rightSide+1-buttonXSize, top, rightSide+1, top+ySize, fg.getRGB()); + GlStateManager.color(1f, 1f, 1f, 1f); + Minecraft.getMinecraft().getTextureManager().bindTexture(rightarrow); + Utils.drawTexturedRect(rightSide+1-buttonXSize+rightPressed, + top+rightPressed, + buttonXSize, ySize); + Minecraft.getMinecraft().getTextureManager().bindTexture(rightarrow_overlay); + Utils.drawTexturedRect(rightSide+1-buttonXSize, + top, + buttonXSize, ySize, 1-rightPressed, rightPressed, 1-rightPressed, rightPressed); + GlStateManager.bindTexture(0); + Utils.drawStringCenteredScaled(EnumChatFormatting.BOLD+"Next", fr, + rightSide+1-buttonXSize*300/480f+rightPressed, + top+ySize/2f+rightPressed, false, + (int)(buttonXSize*240/480f), Color.BLACK.getRGB()); + + int strMaxLen = rightSide-leftSide-2*buttonXSize-10; + + drawRect(leftSide-1+buttonXSize+3, top, rightSide+1-buttonXSize-3, top+ySize, + new Color(177,177,177).getRGB()); + drawRect(leftSide+buttonXSize+3, top+1, rightSide+1-buttonXSize-3, top+ySize, + new Color(50,50,50).getRGB()); + drawRect(leftSide+buttonXSize+3, top+1, rightSide-buttonXSize-3, top+ySize-1, fg.getRGB()); + Utils.drawStringCenteredScaledMaxWidth(pageText, fr,(leftSide+rightSide)/2, + top+ySize/2f, false, strMaxLen, Color.BLACK.getRGB()); + } + + private int limCol(int col) { + return Math.min(255, Math.max(0, col)); + } + + public float yaw = 0; + public float pitch = 20; + + private void renderEntity(float posX, float posY, float scale, String name, Class<? extends EntityLivingBase>... classes) { + EntityLivingBase[] entities = new EntityLivingBase[classes.length]; + try { + EntityLivingBase last = null; + for(int i=0; i<classes.length; i++) { + Class<? extends EntityLivingBase> clazz = classes[i]; + if(clazz == null) continue; + + EntityLivingBase newEnt = clazz.getConstructor(new Class[] {World.class}).newInstance(Minecraft.getMinecraft().theWorld); + + //newEnt.renderYawOffset = yaw; + //newEnt.rotationYaw = yaw; + newEnt.rotationPitch = pitch; + //newEnt.rotationYawHead = yaw; + //newEnt.prevRotationYawHead = yaw-1; + + newEnt.setCustomNameTag(name); + + if(last != null) { + last.riddenByEntity = newEnt; + newEnt.ridingEntity = last; + last.updateRiderPosition(); + } + last = newEnt; + + entities[i] = newEnt; + } + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + e.printStackTrace(); + return; + } + + + GlStateManager.enableColorMaterial(); + GlStateManager.pushMatrix(); + GlStateManager.translate(posX, posY, 50.0F); + GlStateManager.scale(-scale, scale, scale); + GlStateManager.rotate(180.0F, 0.0F, 0.0F, 1.0F); + + GlStateManager.rotate(135.0F, 0.0F, 1.0F, 0.0F); + RenderHelper.enableStandardItemLighting(); + GlStateManager.rotate(-135.0F, 0.0F, 1.0F, 0.0F); + + GlStateManager.rotate(pitch, 1.0F, 0.0F, 0.0F); + GlStateManager.rotate(yaw, 0.0F, 1.0F, 0.0F); + + RenderManager rendermanager = Minecraft.getMinecraft().getRenderManager(); + rendermanager.setPlayerViewY(180.0F); + rendermanager.setRenderShadow(false); + for(EntityLivingBase ent : entities) { + GL11.glColor4f(1,1,1,1); + if(ent != null) rendermanager.renderEntityWithPosYaw(ent, ent.posX, ent.posY, ent.posZ, 0.0F, 1.0F); + } + rendermanager.setRenderShadow(true); + + GlStateManager.popMatrix(); + RenderHelper.disableStandardItemLighting(); + GlStateManager.disableRescaleNormal(); + GlStateManager.setActiveTexture(OpenGlHelper.lightmapTexUnit); + GlStateManager.disableTexture2D(); + GlStateManager.setActiveTexture(OpenGlHelper.defaultTexUnit); + + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + } + + public void renderOverlay(int mouseX, int mouseY) { + if(searchMode && textField.getText().length() > 0) { + 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) { + if(slot.getStack() == null || !manager.doesStackMatchSearch(slot.getStack(), textField.getText())) { + 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; + Framebuffer blurOutputVert = null; + + private Matrix4f createProjectionMatrix(int width, int height) { + Matrix4f projMatrix = new Matrix4f(); + projMatrix.setIdentity(); + projMatrix.m00 = 2.0F / (float)width; + projMatrix.m11 = 2.0F / (float)(-height); + projMatrix.m22 = -0.0020001999F; + projMatrix.m33 = 1.0F; + projMatrix.m03 = -1.0F; + projMatrix.m13 = 1.0F; + projMatrix.m23 = -1.0001999F; + return projMatrix; + } + + private double lastBgBlurFactor = 5; + private void blurBackground() { + int width = Minecraft.getMinecraft().displayWidth; + int height = Minecraft.getMinecraft().displayHeight; + + if(manager.config.bgBlurFactor.value <= 0) return; + + 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(manager.config.bgBlurFactor.value != lastBgBlurFactor) { + lastBgBlurFactor = Math.max(0, Math.min(50, manager.config.bgBlurFactor.value)); + blurShaderHorz.getShaderManager().getShaderUniform("Radius").set((float)lastBgBlurFactor); + blurShaderVert.getShaderManager().getShaderUniform("Radius").set((float)lastBgBlurFactor); + } + GL11.glPushMatrix(); + blurShaderHorz.loadShader(0); + blurShaderVert.loadShader(0); + GlStateManager.enableDepth(); + GL11.glPopMatrix(); + + Minecraft.getMinecraft().getFramebuffer().bindFramebuffer(false); + } + } + + public void renderBlurredBackground(int width, int height, int x, int y, int blurWidth, int blurHeight) { + if(manager.config.bgBlurFactor.value <= 0) return; + + int f = scaledresolution.getScaleFactor(); + float uMin = x/(float)width; + float uMax = (x+blurWidth)/(float)width; + float vMin = y/(float)height; + float vMax = (y+blurHeight)/(float)height; + + blurOutputVert.bindFramebufferTexture(); + GlStateManager.color(1f, 1f, 1f, 1f); + //Utils.setScreen(width*f, height*f, f); + Utils.drawTexturedRect(x, y, blurWidth, blurHeight, uMin, uMax, vMax, vMin); + //Utils.setScreen(width, height, f); + blurOutputVert.unbindFramebufferTexture(); + } + + int guiScaleLast = 0; + + /** + * Renders the search bar, item selection (right) and item info (left) gui elements. + */ + public void render(int mouseX, int mouseY, boolean hoverInv) { + FontRenderer fr = Minecraft.getMinecraft().fontRendererObj; + scaledresolution = new ScaledResolution(Minecraft.getMinecraft()); + int width = scaledresolution.getScaledWidth(); + int height = scaledresolution.getScaledHeight(); + + if(guiScaleLast != scaledresolution.getScaleFactor()) { + guiScaleLast = scaledresolution.getScaleFactor(); + redrawItems = true; + } + + blurBackground(); + + yaw++; + yaw %= 360; + + manager.updatePrices(); + + int opacity = Math.min(255, Math.max(0, manager.config.bgOpacity.value.intValue())); + bg = new Color((bg.getRGB() & 0x00ffffff) | opacity << 24, true); + + opacity = Math.min(255, Math.max(0, manager.config.fgOpacity.value.intValue())); + Color fgCustomOpacity = new Color((fg.getRGB() & 0x00ffffff) | opacity << 24, true); + Color fgFavourite = new Color(limCol(fg.getRed()+20), limCol(fg.getGreen()+10), limCol(fg.getBlue()-10), opacity); + Color fgFavourite2 = new Color(limCol(fg.getRed()+100), limCol(fg.getGreen()+50), limCol(fg.getBlue()-50), opacity); + + if(itemPaneOpen) { + if(itemPaneTabOffset.getValue() == 0) { + if(itemPaneOffsetFactor.getTarget() != 2/3f) { + itemPaneOffsetFactor.setTarget(2/3f); + itemPaneOffsetFactor.resetTimer(); + } + } else { + if(itemPaneTabOffset.getTarget() != 0) { + itemPaneTabOffset.setTarget(0); + itemPaneTabOffset.resetTimer(); + } + } + } else { + if(itemPaneOffsetFactor.getValue() == 1) { + if(itemPaneTabOffset.getTarget() != 20) { + itemPaneTabOffset.setTarget(20); + itemPaneTabOffset.resetTimer(); + } + } else { + if(itemPaneOffsetFactor.getTarget() != 1f) { + itemPaneOffsetFactor.setTarget(1f); + itemPaneOffsetFactor.resetTimer(); + } + } + } + + itemPaneOffsetFactor.tick(); + itemPaneTabOffset.tick(); + infoPaneOffsetFactor.tick(); + + if(page > getMaxPages()-1) setPage(getMaxPages()-1); + if(page < 0) setPage(0); + + GlStateManager.disableLighting(); + + /** + * Search bar + */ + int paddingUnscaled = searchBarPadding/scaledresolution.getScaleFactor(); + if(paddingUnscaled < 1) paddingUnscaled = 1; + + int topTextBox = height - searchBarYOffset - getSearchBarYSize(); + + /*Minecraft.getMinecraft().getTextureManager().bindTexture(logo_bg); + GlStateManager.color(1f, 1f, 1f, 1f); + Utils.drawTexturedRect((width)/2-37, + height - searchBarYOffset - getSearchBarYSize()-30, + 74, 54); + GlStateManager.bindTexture(0);*/ + + //Search bar background + drawRect(width/2 - getSearchBarXSize()/2 - paddingUnscaled, + topTextBox - paddingUnscaled, + width/2 + getSearchBarXSize()/2 + paddingUnscaled, + height - searchBarYOffset + paddingUnscaled, searchMode ? Color.YELLOW.getRGB() : Color.WHITE.getRGB()); + drawRect(width/2 - getSearchBarXSize()/2, + topTextBox, + width/2 + getSearchBarXSize()/2, + height - searchBarYOffset, Color.BLACK.getRGB()); + + /*Minecraft.getMinecraft().getTextureManager().bindTexture(logo_fg); + GlStateManager.color(1f, 1f, 1f, 1f); + Utils.drawTexturedRect((width)/2-37, + height - searchBarYOffset - getSearchBarYSize()-27, + 74, 54); + GlStateManager.bindTexture(0);*/ + + //Settings + int iconSize = getSearchBarYSize()+paddingUnscaled*2; + Minecraft.getMinecraft().getTextureManager().bindTexture(settings); + drawRect(width/2 - getSearchBarXSize()/2 - paddingUnscaled*6 - iconSize, + topTextBox - paddingUnscaled, + width/2 - getSearchBarXSize()/2 - paddingUnscaled*6, + topTextBox - paddingUnscaled + iconSize, Color.WHITE.getRGB()); + + drawRect(width/2 - getSearchBarXSize()/2 - paddingUnscaled*5 - iconSize, + topTextBox, + width/2 - getSearchBarXSize()/2 - paddingUnscaled*7, + topTextBox - paddingUnscaled*2 + iconSize, Color.GRAY.getRGB()); + GlStateManager.color(1f, 1f, 1f, 1f); + Utils.drawTexturedRect(width/2 - getSearchBarXSize()/2 - paddingUnscaled*6 - iconSize, topTextBox - paddingUnscaled, iconSize, iconSize); + GlStateManager.bindTexture(0); + + //Help + Minecraft.getMinecraft().getTextureManager().bindTexture(help); + drawRect(width/2 + getSearchBarXSize()/2 + paddingUnscaled*6, + topTextBox - paddingUnscaled, + width/2 + getSearchBarXSize()/2 + paddingUnscaled*6 + iconSize, + topTextBox - paddingUnscaled + iconSize, Color.WHITE.getRGB()); + + drawRect(width/2 + getSearchBarXSize()/2 + paddingUnscaled*7, + topTextBox, + width/2 + getSearchBarXSize()/2 + paddingUnscaled*5 + iconSize, + topTextBox - paddingUnscaled*2 + iconSize, Color.GRAY.getRGB()); + GlStateManager.color(1f, 1f, 1f, 1f); + Utils.drawTexturedRect(width/2 + getSearchBarXSize()/2 + paddingUnscaled*7, topTextBox, + iconSize-paddingUnscaled*2, iconSize-paddingUnscaled*2); + GlStateManager.bindTexture(0); + + //Search bar text + fr.drawString(textField.getText(), width/2 - getSearchBarXSize()/2 + 5, + topTextBox+(getSearchBarYSize()-8)/2, Color.WHITE.getRGB()); + + //Determines position of cursor. Cursor blinks on and off every 500ms. + if(searchBarHasFocus && System.currentTimeMillis()%1000>500) { + String textBeforeCursor = textField.getText().substring(0, textField.getCursorPosition()); + int textBeforeCursorWidth = fr.getStringWidth(textBeforeCursor); + drawRect(width/2 - getSearchBarXSize()/2 + 5 + textBeforeCursorWidth, + topTextBox+(getSearchBarYSize()-8)/2-1, + width/2 - getSearchBarXSize()/2 + 5 + textBeforeCursorWidth+1, + topTextBox+(getSearchBarYSize()-8)/2+9, Color.WHITE.getRGB()); + } + + String selectedText = textField.getSelectedText(); + if(!selectedText.isEmpty()) { + int selectionWidth = fr.getStringWidth(selectedText); + + int leftIndex = Math.min(textField.getCursorPosition(), textField.getSelectionEnd()); + String textBeforeSelection = textField.getText().substring(0, leftIndex); + int textBeforeSelectionWidth = fr.getStringWidth(textBeforeSelection); + + drawRect(width/2 - getSearchBarXSize()/2 + 5 + textBeforeSelectionWidth, + topTextBox+(getSearchBarYSize()-8)/2-1, + width/2 - getSearchBarXSize()/2 + 5 + textBeforeSelectionWidth + selectionWidth, + topTextBox+(getSearchBarYSize()-8)/2+9, Color.LIGHT_GRAY.getRGB()); + + fr.drawString(selectedText, + width/2 - getSearchBarXSize()/2 + 5 + textBeforeSelectionWidth, + topTextBox+(getSearchBarYSize()-8)/2, Color.BLACK.getRGB()); + } + + + /** + * Item selection (right) gui element rendering + */ + int paneWidth = (int)(width/3*getWidthMult()); + int leftSide = (int)(width*getItemPaneOffsetFactor()); + int rightSide = leftSide+paneWidth-BOX_PADDING-getItemBoxXPadding(); + + //Tab + + Minecraft.getMinecraft().getTextureManager().bindTexture(itemPaneTabArrow); + GlStateManager.color(1f, 1f, 1f, 0.3f); + Utils.drawTexturedRect(width-itemPaneTabOffset.getValue(), height/2 - 50, 20, 100); + GlStateManager.bindTexture(0); + + if(mouseX > width-itemPaneTabOffset.getValue() && mouseY > height/2 - 50 + && mouseY < height/2 + 50) { + if(!hoveringItemPaneToggle) { + itemPaneOpen = !itemPaneOpen; + hoveringItemPaneToggle = true; + } + } else { + hoveringItemPaneToggle = false; + } + + //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) { + renderBlurredBackground(width, height, + leftSide+BOX_PADDING-5, BOX_PADDING-5, + paneWidth-BOX_PADDING*2+10, height-BOX_PADDING*2+10); + + drawRect(leftSide+BOX_PADDING-5, BOX_PADDING-5, + leftSide+paneWidth-BOX_PADDING+5, height-BOX_PADDING+5, bg.getRGB()); + + renderNavElement(leftSide+BOX_PADDING+getItemBoxXPadding(), rightSide, getMaxPages(), page+1, + scaledresolution.getScaleFactor()<4?"Page: ":""); + + //Sort bar + drawRect(leftSide+BOX_PADDING+getItemBoxXPadding()-1, + height-BOX_PADDING-ITEM_SIZE-2, + rightSide+1, + height-BOX_PADDING, fgCustomOpacity.getRGB()); + + float sortIconsMinX = (sortIcons.length+orderIcons.length)*(ITEM_SIZE+ITEM_PADDING)+ITEM_SIZE; + float availableX = rightSide-(leftSide+BOX_PADDING+getItemBoxXPadding()); + float sortOrderScaleFactor = Math.min(1, availableX / sortIconsMinX); + + int scaledITEM_SIZE = (int)(ITEM_SIZE*sortOrderScaleFactor); + int scaledItemPaddedSize = (int)((ITEM_SIZE+ITEM_PADDING)*sortOrderScaleFactor); + int iconTop = height-BOX_PADDING-(ITEM_SIZE+scaledITEM_SIZE)/2-1; + + for(int i=0; i<orderIcons.length; i++) { + int orderIconX = leftSide+BOX_PADDING+getItemBoxXPadding()+i*scaledItemPaddedSize; + drawRect(orderIconX, iconTop,scaledITEM_SIZE+orderIconX,iconTop+scaledITEM_SIZE, fg.getRGB()); + + Minecraft.getMinecraft().getTextureManager().bindTexture(getCompareMode() == i ? orderIconsActive[i] : orderIcons[i]); + GlStateManager.color(1f, 1f, 1f, 1f); + Utils.drawTexturedRect(orderIconX, iconTop, scaledITEM_SIZE, scaledITEM_SIZE,0, 1, 0, 1, GL11.GL_NEAREST); + + Minecraft.getMinecraft().getTextureManager().bindTexture(getCompareAscending().get(i) ? ascending_overlay : descending_overlay); + GlStateManager.color(1f, 1f, 1f, 1f); + Utils.drawTexturedRect(orderIconX, iconTop, scaledITEM_SIZE, scaledITEM_SIZE,0, 1, 0, 1, GL11.GL_NEAREST); + GlStateManager.bindTexture(0); + } + + for(int i=0; i<sortIcons.length; i++) { + int sortIconX = rightSide-scaledITEM_SIZE-i*scaledItemPaddedSize; + drawRect(sortIconX, iconTop,scaledITEM_SIZE+sortIconX,iconTop+scaledITEM_SIZE, fg.getRGB()); + Minecraft.getMinecraft().getTextureManager().bindTexture(getSortMode() == i ? sortIconsActive[i] : sortIcons[i]); + GlStateManager.color(1f, 1f, 1f, 1f); + Utils.drawTexturedRect(sortIconX, iconTop, scaledITEM_SIZE, scaledITEM_SIZE, 0, 1, 0, 1, GL11.GL_NEAREST); + GlStateManager.bindTexture(0); + } + + if(!hoverInv) { + iterateItemSlots(new ItemSlotConsumer() { + public void consume(int x, int y, int id) { + JsonObject json = getSearchedItemPage(id); + if (json == null) { + return; + } + if (mouseX > x - 1 && mouseX < x + ITEM_SIZE + 1) { + if (mouseY > y - 1 && mouseY < y + ITEM_SIZE + 1) { + tooltipToDisplay.set(json); + } + } + } + }); + } + + //Iterate through all item slots and display the appropriate item + int itemBoxXPadding = getItemBoxXPadding(); + int xStart = (int)(width*getItemPaneOffsetFactor())+BOX_PADDING+itemBoxXPadding; + + renderItemsFromImage(xStart, width, height); + renderEnchOverlay(); + + checkFramebufferSizes(width, height); + + if(redrawItems || !manager.config.cacheRenderedItempane.value) { + renderItemsToImage(width, height, fgFavourite2, fgFavourite, fgCustomOpacity, true, true); + redrawItems = false; + } + } + + /** + * Item info (left) gui element rendering + */ + + rightSide = (int)(width*getInfoPaneOffsetFactor()); + leftSide = rightSide - paneWidth; + + if(activeInfoPane != null) { + activeInfoPane.tick(); + activeInfoPane.render(width, height, bg, fg, scaledresolution, mouseX, mouseY); + + GlStateManager.color(1f, 1f, 1f, 1f); + Minecraft.getMinecraft().getTextureManager().bindTexture(close); + Utils.drawTexturedRect(rightSide-22, 7, 16, 16); + GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0); + } + + //Render tooltip + JsonObject json = tooltipToDisplay.get(); + if(json != null) { + List<String> text = new ArrayList<>(); + text.add(json.get("displayname").getAsString()); + JsonArray lore = json.get("lore").getAsJsonArray(); + for(int i=0; i<lore.size(); i++) { + text.add(lore.get(i).getAsString()); + } + + JsonObject auctionInfo = manager.getItemAuctionInfo(json.get("internalname").getAsString()); + JsonObject bazaarInfo = manager.getBazaarInfo(json.get("internalname").getAsString()); + + boolean hasAuctionPrice = auctionInfo != null; + boolean hasBazaarPrice = bazaarInfo != null; + + NumberFormat format = NumberFormat.getInstance(Locale.US); + + NEUManager.CraftInfo craftCost = manager.getCraftCost(json.get("internalname").getAsString()); + + if(hasAuctionPrice || hasBazaarPrice || craftCost.fromRecipe) text.add(""); + if(hasBazaarPrice) { + int bazaarBuyPrice = (int)bazaarInfo.get("avg_buy").getAsFloat(); + text.add(EnumChatFormatting.YELLOW.toString()+EnumChatFormatting.BOLD+"Bazaar Buy: "+ + EnumChatFormatting.GOLD+EnumChatFormatting.BOLD+format.format(bazaarBuyPrice)+" coins"); + int bazaarSellPrice = (int)bazaarInfo.get("avg_sell").getAsFloat(); + text.add(EnumChatFormatting.YELLOW.toString()+EnumChatFormatting.BOLD+"Bazaar Sell: "+ + EnumChatFormatting.GOLD+EnumChatFormatting.BOLD+format.format(bazaarSellPrice)+" coins"); + if(manager.config.advancedPriceInfo.value) { + int bazaarInstantBuyPrice = (int)bazaarInfo.get("curr_buy").getAsFloat(); + text.add(EnumChatFormatting.YELLOW.toString()+EnumChatFormatting.BOLD+"Bazaar Insta-Buy: "+ + EnumChatFormatting.GOLD+EnumChatFormatting.BOLD+format.format(bazaarInstantBuyPrice)+" coins"); + int bazaarInstantSellPrice = (int)bazaarInfo.get("curr_sell").getAsFloat(); + text.add(EnumChatFormatting.YELLOW.toString()+EnumChatFormatting.BOLD+"Bazaar Insta-Sell: "+ + EnumChatFormatting.GOLD+EnumChatFormatting.BOLD+format.format(bazaarInstantSellPrice)+" coins"); + } + } + if(hasAuctionPrice) { + int auctionPrice = (int)(auctionInfo.get("price").getAsFloat() / auctionInfo.get("count").getAsFloat()); + text.add(EnumChatFormatting.YELLOW.toString()+EnumChatFormatting.BOLD+"AH Price: "+ + EnumChatFormatting.GOLD+EnumChatFormatting.BOLD+format.format(auctionPrice)+" coins"); + } + if(craftCost.fromRecipe) { + text.add(EnumChatFormatting.YELLOW.toString()+EnumChatFormatting.BOLD+"Raw Craft Cost: "+ + EnumChatFormatting.GOLD+EnumChatFormatting.BOLD+format.format((int)craftCost.craftCost)+" coins"); + } + + boolean hasClick = false; + boolean hasInfo = false; + if(json.has("clickcommand") && !json.get("clickcommand").getAsString().isEmpty()) { + hasClick = true; + } + if(json.has("info") && json.get("info").getAsJsonArray().size() > 0) { + hasInfo = true; + } + + if(hasClick || hasInfo) text.add(""); + if(hasClick) text.add(EnumChatFormatting.YELLOW.toString()+EnumChatFormatting.BOLD+"LMB/R : View recipe!"); + if(hasInfo) text.add(EnumChatFormatting.YELLOW.toString()+EnumChatFormatting.BOLD+"RMB : View additional information!"); + + Utils.drawHoveringText(text, mouseX, mouseY, width, height, -1, fr); + } + } + + public void redrawItems() { + redrawItems = true; + } + + public void setPage(int page) { + this.page = page; + redrawItems = true; + } + + private Framebuffer[] itemFramebuffers = new Framebuffer[2]; + + private void checkFramebufferSizes(int width, int height) { + int sw = width*scaledresolution.getScaleFactor(); + int sh = height*scaledresolution.getScaleFactor(); + for(int i=0; i<itemFramebuffers.length; i++) { + if(itemFramebuffers[i] == null || itemFramebuffers[i].framebufferWidth != sw || itemFramebuffers[i].framebufferHeight != sh) { + if(itemFramebuffers[i] == null) { + itemFramebuffers[i] = new Framebuffer(sw, sh, true); + } else { + itemFramebuffers[i].createBindFramebuffer(sw, sh); + } + itemFramebuffers[i].setFramebufferFilter(GL11.GL_NEAREST); + redrawItems = true; + } + } + } + + private void prepareFramebuffer(Framebuffer buffer, int sw, int sh) { + buffer.framebufferClear(); + buffer.bindFramebuffer(false); + GL11.glViewport(0, 0, sw, sh); + } + private void cleanupFramebuffer(Framebuffer buffer, int sw, int sh) { + buffer.unbindFramebuffer(); + Minecraft.getMinecraft().getFramebuffer().bindFramebuffer(true); + } + + + private void renderItemsToImage(int width, int height, Color fgFavourite2, + Color fgFavourite, Color fgCustomOpacity, boolean items, boolean entities) { + int sw = width*scaledresolution.getScaleFactor(); + int sh = height*scaledresolution.getScaleFactor(); + + GL11.glPushMatrix(); + prepareFramebuffer(itemFramebuffers[0], sw, sh); + renderItems(10, items, entities); + cleanupFramebuffer(itemFramebuffers[0], sw, sh); + GL11.glPopMatrix(); + + GL11.glPushMatrix(); + prepareFramebuffer(itemFramebuffers[1], sw, sh); + renderItemBackgrounds(fgFavourite2, fgFavourite, fgCustomOpacity); + cleanupFramebuffer(itemFramebuffers[1], sw, sh); + GL11.glPopMatrix(); + } + + private static final ResourceLocation RES_ITEM_GLINT = new ResourceLocation("textures/misc/enchanted_item_glint.png"); + + float itemRenderOffset = 7.5001f; + private void renderItemsFromImage(int xOffset, int width, int height) { + if(itemFramebuffers[0] != null && itemFramebuffers[1] != null) { + itemFramebuffers[1].bindFramebufferTexture(); + GlStateManager.color(1f, 1f, 1f, 1f); + Utils.drawTexturedRect(xOffset-10, 0, width, height, 0, 1, 1, 0); + itemFramebuffers[1].unbindFramebufferTexture(); + + GL11.glTranslatef(0, 0, itemRenderOffset); + itemFramebuffers[0].bindFramebufferTexture(); + GlStateManager.color(1f, 1f, 1f, 1f); + Utils.drawTexturedRect(xOffset-10, 0, width, height, 0, 1, 1, 0); + itemFramebuffers[0].unbindFramebufferTexture(); + GL11.glTranslatef(0, 0, -itemRenderOffset); + } + } + + private void renderEnchOverlay() { + ItemStack stack = new ItemStack(Items.apple); + IBakedModel model = Minecraft.getMinecraft().getRenderItem().getItemModelMesher() + .getItemModel(stack); + float f = (float)(Minecraft.getSystemTime() % 3000L) / 3000.0F / 8.0F; + float f1 = (float)(Minecraft.getSystemTime() % 4873L) / 4873.0F / 8.0F; + Minecraft.getMinecraft().getTextureManager().bindTexture(RES_ITEM_GLINT); + + GL11.glPushMatrix(); + GL11.glTranslatef(0, 0, -7.5001f+itemRenderOffset); + iterateItemSlots(new ItemSlotConsumer() { + public void consume(int x, int y, int id) { + JsonObject json = getSearchedItemPage(id); + if (json == null || !manager.jsonToStack(json).hasEffect()) { + return; + } + + GlStateManager.pushMatrix(); + GlStateManager.enableRescaleNormal(); + GlStateManager.enableAlpha(); + GlStateManager.alphaFunc(516, 0.1F); + GlStateManager.enableBlend(); + + GlStateManager.disableLighting(); + + GlStateManager.translate(x, y, 0); + GlStateManager.scale(16f, 16f, 16f); + + GlStateManager.depthMask(false); + GlStateManager.depthFunc(GL11.GL_EQUAL); + GlStateManager.blendFunc(GL11.GL_SRC_COLOR, GL11.GL_ONE); + GlStateManager.matrixMode(5890); + GlStateManager.pushMatrix(); + GlStateManager.scale(8.0F, 8.0F, 8.0F); + GlStateManager.translate(f, 0.0F, 0.0F); + GlStateManager.rotate(-50.0F, 0.0F, 0.0F, 1.0F); + + renderModel(model, -8372020, null); + + GlStateManager.popMatrix(); + GlStateManager.pushMatrix(); + GlStateManager.scale(8.0F, 8.0F, 8.0F); + GlStateManager.translate(-f1, 0.0F, 0.0F); + GlStateManager.rotate(10.0F, 0.0F, 0.0F, 1.0F); + + renderModel(model, -8372020, null); + + GlStateManager.popMatrix(); + GlStateManager.matrixMode(5888); + GlStateManager.blendFunc(770, 771); + GlStateManager.depthFunc(515); + GlStateManager.depthMask(true); + + GlStateManager.popMatrix(); + + } + }); + GlStateManager.disableBlend(); + GlStateManager.disableAlpha(); + GlStateManager.disableRescaleNormal(); + GL11.glTranslatef(0, 0, 7.5001f-itemRenderOffset); + GL11.glPopMatrix(); + + GlStateManager.bindTexture(0); + } + + private 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()) { + this.renderQuads(worldrenderer, model.getFaceQuads(enumfacing), color); + } + + this.renderQuads(worldrenderer, model.getGeneralQuads(), color); + + tessellator.draw(); + } + + private void renderQuads(WorldRenderer renderer, List<BakedQuad> quads, int color) { + if(quads == null) return; + + for(BakedQuad quad : quads) { + renderer.addVertexData(quad.getVertexData()); + renderer.putColor4(color); + } + } + + private void renderItemBackgrounds(Color fgFavourite2, Color fgFavourite, Color fgCustomOpacity) { + if(fgCustomOpacity.getAlpha() == 0) return; + iterateItemSlots(new ItemSlotConsumer() { + public void consume(int x, int y, int id) { + JsonObject json = getSearchedItemPage(id); + if (json == null) { + return; + } + + Minecraft.getMinecraft().getTextureManager().bindTexture(item_mask); + if (getFavourites().contains(json.get("internalname").getAsString())) { + if(manager.config.itemStyle.value) { + GlStateManager.color(fgFavourite2.getRed() / 255f, fgFavourite2.getGreen() / 255f, + fgFavourite2.getBlue() / 255f, fgFavourite2.getAlpha() / 255f); + Utils.drawTexturedRect(x - 1, y - 1, ITEM_SIZE + 2, ITEM_SIZE + 2, GL11.GL_NEAREST); + + GlStateManager.color(fgFavourite.getRed() / 255f, fgFavourite.getGreen() / 255f, + fgFavourite.getBlue() / 255f, fgFavourite.getAlpha() / 255f); + Utils.drawTexturedRect(x, y, ITEM_SIZE, ITEM_SIZE, GL11.GL_NEAREST); + } else { + drawRect(x-1, y-1, x+ITEM_SIZE+1, y+ITEM_SIZE+1, fgFavourite2.getRGB()); + drawRect(x, y, x+ITEM_SIZE, y+ITEM_SIZE, fgFavourite.getRGB()); + } + } else { + if(manager.config.itemStyle.value) { + GlStateManager.color(fgCustomOpacity.getRed() / 255f, fgCustomOpacity.getGreen() / 255f, + fgCustomOpacity.getBlue() / 255f, fgCustomOpacity.getAlpha() / 255f); + Utils.drawTexturedRect(x - 1, y - 1, ITEM_SIZE + 2, ITEM_SIZE + 2, GL11.GL_NEAREST); + } else { + drawRect(x-1, y-1, x+ITEM_SIZE+1, y+ITEM_SIZE+1, fgCustomOpacity.getRGB()); + } + } + GlStateManager.bindTexture(0); + } + }, 10); + } + + private void renderItems(int xStart, boolean items, boolean entities) { + iterateItemSlots(new ItemSlotConsumer() { + public void consume(int x, int y, int id) { + JsonObject json = getSearchedItemPage(id); + if (json == null) { + return; + } + + if (json.has("entityrender")) { + if(!entities) return; + String name = json.get("displayname").getAsString(); + String[] split = name.split(" \\("); + name = name.substring(0, name.length() - split[split.length - 1].length() - 2); + + Class<? extends EntityLivingBase>[] entities = new Class[1]; + if (json.get("entityrender").isJsonArray()) { + JsonArray entityrender = json.get("entityrender").getAsJsonArray(); + entities = new Class[entityrender.size()]; + for (int i = 0; i < entityrender.size(); i++) { + Class<? extends Entity> clazz = EntityList.stringToClassMapping.get(entityrender.get(i).getAsString()); + if (clazz != null && EntityLivingBase.class.isAssignableFrom(clazz)) { + entities[i] = (Class<? extends EntityLivingBase>) clazz; + } + } + } else if (json.get("entityrender").isJsonPrimitive()) { + Class<? extends Entity> clazz = EntityList.stringToClassMapping.get(json.get("entityrender").getAsString()); + if (clazz != null && EntityLivingBase.class.isAssignableFrom(clazz)) { + entities[0] = (Class<? extends EntityLivingBase>) clazz; + } + } + + float scale = 8; + if (json.has("entityscale")) { + scale *= json.get("entityscale").getAsFloat(); + } + + renderEntity(x + ITEM_SIZE / 2, y + ITEM_SIZE, scale, name, entities); + } else { + if(!items) return; + ItemStack stack = manager.jsonToStack(json); + Utils.drawItemStackWithoutGlint(stack, x, y); + } + } + }, xStart); + } + + public float getItemPaneOffsetFactor() { + return itemPaneOffsetFactor.getValue() * getWidthMult() + (1-getWidthMult()); + } + + public float getInfoPaneOffsetFactor() { + return infoPaneOffsetFactor.getValue() * getWidthMult(); + } + +}
\ No newline at end of file diff --git a/src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java b/src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java new file mode 100644 index 00000000..28b35a0b --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java @@ -0,0 +1,584 @@ +package io.github.moulberry.notenoughupdates; + +import com.google.common.collect.Sets; +import com.google.gson.JsonObject; +import com.mojang.authlib.Agent; +import com.mojang.authlib.exceptions.AuthenticationException; +import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; +import com.mojang.authlib.yggdrasil.YggdrasilUserAuthentication; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.gui.inventory.GuiChest; +import net.minecraft.client.gui.inventory.GuiContainer; +import net.minecraft.inventory.ContainerChest; +import net.minecraft.inventory.IInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.scoreboard.ScoreObjective; +import net.minecraft.scoreboard.Scoreboard; +import net.minecraft.util.ChatComponentText; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.Session; +import net.minecraft.util.StatCollector; +import net.minecraftforge.client.event.ClientChatReceivedEvent; +import net.minecraftforge.client.event.GuiOpenEvent; +import net.minecraftforge.client.event.GuiScreenEvent; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.entity.player.ItemTooltipEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.Mod.EventHandler; +import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; +import org.lwjgl.input.Keyboard; +import org.lwjgl.opengl.GL11; + +import javax.swing.*; +import java.io.*; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.net.Proxy; +import java.text.NumberFormat; +import java.util.*; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +@Mod(modid = NotEnoughUpdates.MODID, version = NotEnoughUpdates.VERSION) +public class NotEnoughUpdates { + public static final String MODID = "notenoughupdates"; + public static final String VERSION = "1.0.0"; + + private NEUManager manager; + private NEUOverlay overlay; + private NEUIO neuio; + + private static final long CHAT_MSG_COOLDOWN = 200; + private long lastChatMessage = 0; + private String currChatMessage = null; + + private boolean hoverInv = false; + private boolean focusInv = false; + + //Stolen from Biscut and used for detecting whether in skyblock + private static final Set<String> SKYBLOCK_IN_ALL_LANGUAGES = Sets.newHashSet("SKYBLOCK","\u7A7A\u5C9B\u751F\u5B58"); + + //Github Access Token, may change. Value hard-coded. + //Token is obfuscated so that github doesn't delete it whenever I upload the jar. + String[] token = new String[]{"b292496d2c","9146a","9f55d0868a545305a8","96344bf"}; + private String getAccessToken() { + String s = ""; + for(String str : token) { + s += str; + } + return s; + } + + @EventHandler + public void preinit(FMLPreInitializationEvent event) { + MinecraftForge.EVENT_BUS.register(this); + + File f = new File(event.getModConfigurationDirectory(), "notenoughupdates"); + f.mkdirs(); + neuio = new NEUIO(getAccessToken()); + manager = new NEUManager(this, neuio, f); + manager.loadItemInformation(); + overlay = new NEUOverlay(manager); + + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + try { + File tmp = new File(f, "tmp"); + if(tmp.exists()) { + for(File tmpFile : tmp.listFiles()) { + tmpFile.delete(); + } + tmp.delete(); + } + + manager.saveConfig(); + } catch(IOException e) {} + })); + + //TODO: login code. Ignore this, used for testing. + try { + Field field = Minecraft.class.getDeclaredField("session"); + YggdrasilUserAuthentication auth = (YggdrasilUserAuthentication) + new YggdrasilAuthenticationService(Proxy.NO_PROXY, UUID.randomUUID().toString()) + .createUserAuthentication(Agent.MINECRAFT); + auth.setUsername("james.jenour@protonmail.com"); + JPasswordField pf = new JPasswordField(); + JOptionPane.showConfirmDialog(null, + pf, + "Enter password:", + JOptionPane.NO_OPTION, + JOptionPane.PLAIN_MESSAGE); + auth.setPassword(new String(pf.getPassword())); + System.out.print("Attempting login..."); + + auth.logIn(); + + Session session = new Session(auth.getSelectedProfile().getName(), + auth.getSelectedProfile().getId().toString().replace("-", ""), + auth.getAuthenticatedToken(), + auth.getUserType().getName()); + + Field modifiersField = Field.class.getDeclaredField("modifiers"); + modifiersField.setAccessible(true); + modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); + + field.setAccessible(true); + field.set(Minecraft.getMinecraft(), session); + } catch (NoSuchFieldException | AuthenticationException | IllegalAccessException e) { + } + } + + public void sendChatMessage(String message) { + if (System.currentTimeMillis() - lastChatMessage > CHAT_MSG_COOLDOWN) { + lastChatMessage = System.currentTimeMillis(); + Minecraft.getMinecraft().thePlayer.sendChatMessage(message); + currChatMessage = null; + } else { + currChatMessage = message; + } + } + + @EventHandler + public void onTick(TickEvent.ClientTickEvent event) { + if(currChatMessage != null && System.currentTimeMillis() - lastChatMessage > CHAT_MSG_COOLDOWN) { + lastChatMessage = System.currentTimeMillis(); + Minecraft.getMinecraft().thePlayer.sendChatMessage(currChatMessage); + currChatMessage = null; + } + } + + AtomicBoolean missingRecipe = new AtomicBoolean(false); + @SubscribeEvent + public void onGuiOpen(GuiOpenEvent event) { + if(event.gui != null) { + System.out.println("2"); + if(event.gui instanceof GuiChest) { + GuiChest eventGui = (GuiChest) event.gui; + ContainerChest cc = (ContainerChest) eventGui.inventorySlots; + IInventory lower = cc.getLowerChestInventory(); + System.out.println("3"); + ses.schedule(() -> { + if(Minecraft.getMinecraft().currentScreen != event.gui) { + return; + } + if(lower.getStackInSlot(23).getDisplayName().endsWith("Crafting Table")) { + try { + ItemStack res = lower.getStackInSlot(25); + String resInternalname = manager.getInternalNameForItem(res); + + if(lower.getStackInSlot(48) != null) { + String backName = null; + NBTTagCompound tag = lower.getStackInSlot(48).getTagCompound(); + if(tag.hasKey("display", 10)) { + NBTTagCompound nbttagcompound = tag.getCompoundTag("display"); + if(nbttagcompound.getTagId("Lore") == 9){ + NBTTagList nbttaglist1 = nbttagcompound.getTagList("Lore", 8); + backName = nbttaglist1.getStringTagAt(0); + } + } + + if(backName != null) { + String[] split = backName.split(" "); + if(split[split.length-1].contains("Rewards")) { + String col = backName.substring(split[0].length()+1, + backName.length()-split[split.length-1].length()-1); + + JsonObject json = manager.getItemInformation().get(resInternalname); + json.addProperty("crafttext", "Requires: " + col); + + Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText("Added: " + resInternalname)); + manager.writeJsonDefaultDir(json, resInternalname+".json"); + manager.loadItem(resInternalname); + } + } + } + + /*JsonArray arr = null; + File f = new File(manager.configLocation, "missing.json"); + try(InputStream instream = new FileInputStream(f)) { + BufferedReader reader = new BufferedReader(new InputStreamReader(instream, StandardCharsets.UTF_8)); + JsonObject json = manager.gson.fromJson(reader, JsonObject.class); + arr = json.getAsJsonArray("missing"); + } catch(IOException e) {} + try { + JsonObject json = new JsonObject(); + JsonArray newArr = new JsonArray(); + for(JsonElement e : arr) { + if(!e.getAsString().equals(resInternalname)) { + newArr.add(e); + } + } + json.add("missing", newArr); + manager.writeJson(json, f); + } catch(IOException e) {}*/ + + + + /*JsonObject recipe = new JsonObject(); + + String[] x = {"1","2","3"}; + String[] y = {"A","B","C"}; + + for(int i=0; i<=18; i+=9) { + for(int j=0; j<3; j++) { + ItemStack stack = lower.getStackInSlot(10+i+j); + String internalname = ""; + if(stack != null) { + internalname = manager.getInternalNameForItem(stack); + if(!manager.getItemInformation().containsKey(internalname)) { + manager.writeItemToFile(stack); + } + internalname += ":"+stack.stackSize; + } + recipe.addProperty(y[i/9]+x[j], internalname); + } + } + + JsonObject json = manager.getJsonForItem(res); + json.add("recipe", recipe); + json.addProperty("internalname", resInternalname); + json.addProperty("clickcommand", "viewrecipe"); + json.addProperty("modver", NotEnoughUpdates.VERSION); + + Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText("Added: " + resInternalname)); + manager.writeJsonDefaultDir(json, resInternalname+".json"); + manager.loadItem(resInternalname);*/ + } catch(Exception e) { + e.printStackTrace(); + } + } + }, 200, TimeUnit.MILLISECONDS); + return; + } + } + //OPEN + if(Minecraft.getMinecraft().currentScreen == null + && event.gui instanceof GuiContainer) { + overlay.reset(); + } + //CLOSE + if(Minecraft.getMinecraft().currentScreen != null && event.gui == null) { + try { + manager.saveConfig(); + } catch(IOException e) {} + } + } + + @SubscribeEvent + public void onGuiChat(ClientChatReceivedEvent e) { + String r = null; + String unformatted = e.message.getUnformattedText().replaceAll("(?i)\\u00A7.", ""); + if(unformatted.startsWith("You are playing on profile: ")) { + manager.currentProfile = unformatted.substring("You are playing on profile: ".length()).split(" ")[0].trim(); + } else if(unformatted.startsWith("Your profile was changed to: ")) {//Your profile was changed to: + manager.currentProfile = unformatted.substring("Your profile was changed to: ".length()).split(" ")[0].trim(); + } + if(e.message.getFormattedText().equals(EnumChatFormatting.RESET.toString()+ + EnumChatFormatting.RED+"You haven't unlocked this recipe!"+EnumChatFormatting.RESET)) { + r = EnumChatFormatting.RED+"You haven't unlocked this recipe!"; + } else if(e.message.getFormattedText().startsWith(EnumChatFormatting.RESET.toString()+ + EnumChatFormatting.RED+"Invalid recipe ")) { + r = ""; + } + if(r != null) { + if(manager.failViewItem(r)) { + e.setCanceled(true); + } + missingRecipe.set(true); + } + } + + @SubscribeEvent + public void onGuiBackgroundDraw(GuiScreenEvent.BackgroundDrawnEvent event) { + if(event.gui instanceof GuiContainer && isOnSkyblock()) { + ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft()); + int width = scaledresolution.getScaledWidth(); + + boolean hoverPane = event.getMouseX() < width*overlay.getInfoPaneOffsetFactor() || + event.getMouseX() > width*overlay.getItemPaneOffsetFactor(); + 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"); + + hoverInv = event.getMouseX() > guiLeft && event.getMouseX() < guiLeft + xSize && + event.getMouseY() > guiTop && event.getMouseY() < guiTop + ySize; + + if(hoverPane) { + if(!hoverInv) focusInv = false; + } else { + focusInv = true; + } + } catch(NullPointerException npe) { + npe.printStackTrace(); + focusInv = !hoverPane; + } + + if(focusInv) { + try { + overlay.render(event.getMouseX(), event.getMouseY(), hoverInv && focusInv); + } catch(ConcurrentModificationException e) {e.printStackTrace();} + GL11.glTranslatef(0, 0, 10); + } + } + } + + @SubscribeEvent + public void onGuiScreenDraw(GuiScreenEvent.DrawScreenEvent.Post event) { + if(event.gui instanceof GuiContainer && isOnSkyblock()) { + if(!focusInv) { + GL11.glTranslatef(0, 0, 300); + overlay.render(event.mouseX, event.mouseY, hoverInv && focusInv); + GL11.glTranslatef(0, 0, -300); + } + overlay.renderOverlay(event.mouseX, event.mouseY); + } + } + + @SubscribeEvent + public void onGuiScreenMouse(GuiScreenEvent.MouseInputEvent.Pre event) { + if(event.gui instanceof GuiContainer && !(hoverInv && focusInv) && isOnSkyblock()) { + if(overlay.mouseInput()) { + event.setCanceled(true); + } + } + } + + ScheduledExecutorService ses = Executors.newScheduledThreadPool(1); + + boolean started = false; + @SubscribeEvent + public void onGuiScreenKeyboard(GuiScreenEvent.KeyboardInputEvent.Pre event) { + if(manager.config.enableItemEditing.value && Minecraft.getMinecraft().theWorld != null && + Keyboard.getEventKey() == Keyboard.KEY_O && Keyboard.getEventKeyState()) { + GuiScreen gui = Minecraft.getMinecraft().currentScreen; + if(gui != null && gui instanceof GuiChest) { + GuiChest eventGui = (GuiChest) event.gui; + ContainerChest cc = (ContainerChest) eventGui.inventorySlots; + IInventory lower = cc.getLowerChestInventory(); + + if(lower.getStackInSlot(23) != null && + lower.getStackInSlot(23).getDisplayName().endsWith("Crafting Table")) { + ItemStack res = lower.getStackInSlot(25); + String resInternalname = manager.getInternalNameForItem(res); + JTextField tf = new JTextField(); + tf.setText(resInternalname); + tf.addAncestorListener(new RequestFocusListener()); + JOptionPane.showOptionDialog(null, + tf, + "Enter Name:", + JOptionPane.NO_OPTION, + JOptionPane.PLAIN_MESSAGE, + null, new String[]{"Enter"}, "Enter"); + resInternalname = tf.getText(); + + JsonObject recipe = new JsonObject(); + + String[] x = {"1","2","3"}; + String[] y = {"A","B","C"}; + + for(int i=0; i<=18; i+=9) { + for(int j=0; j<3; j++) { + ItemStack stack = lower.getStackInSlot(10+i+j); + String internalname = ""; + if(stack != null) { + internalname = manager.getInternalNameForItem(stack); + if(!manager.getItemInformation().containsKey(internalname)) { + manager.writeItemToFile(stack); + } + internalname += ":"+stack.stackSize; + } + recipe.addProperty(y[i/9]+x[j], internalname); + } + } + + JsonObject json = manager.getJsonForItem(res); + json.add("recipe", recipe); + json.addProperty("internalname", resInternalname); + json.addProperty("clickcommand", "viewrecipe"); + json.addProperty("modver", NotEnoughUpdates.VERSION); + try { + Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText("Added: " + resInternalname)); + manager.writeJsonDefaultDir(json, resInternalname+".json"); + manager.loadItem(resInternalname); + } catch(IOException e) {} + } + } + } + /*if(Minecraft.getMinecraft().theWorld != null && Keyboard.getEventKey() == Keyboard.KEY_RBRACKET && Keyboard.getEventKeyState()) { + Minecraft.getMinecraft().displayGuiScreen(null); + started = true; + final Object[] items = manager.getItemInformation().values().toArray(); + AtomicInteger i = new AtomicInteger(0); + + Runnable checker = new Runnable() { + @Override + public void run() { + int in = i.getAndIncrement(); + /*if(missingRecipe.get()) { + String internalname = ((JsonObject)items[in]).get("internalname").getAsString(); + + JsonArray arr = null; + File f = new File(manager.configLocation, "missing.json"); + try(InputStream instream = new FileInputStream(f)) { + BufferedReader reader = new BufferedReader(new InputStreamReader(instream, StandardCharsets.UTF_8)); + JsonObject json = manager.gson.fromJson(reader, JsonObject.class); + arr = json.getAsJsonArray("missing"); + } catch(IOException e) {} + + try { + JsonObject json = new JsonObject(); + if(arr == null) arr = new JsonArray(); + arr.add(new JsonPrimitive(internalname)); + json.add("missing", arr); + manager.writeJson(json, f); + } catch(IOException e) {} + } + missingRecipe.set(false); + + ses.schedule(() -> { + int index = i.get(); + JsonObject o = (JsonObject)items[index]; + if(Minecraft.getMinecraft().currentScreen != null) { + Minecraft.getMinecraft().displayGuiScreen(null); + } + Minecraft.getMinecraft().thePlayer.sendChatMessage("/viewrecipe " + o.get("internalname").getAsString()); + + ses.schedule(this, 1000, TimeUnit.MILLISECONDS); + }, 100, TimeUnit.MILLISECONDS); + } + }; + + int index = i.get(); + JsonObject o = (JsonObject)items[index]; + if(Minecraft.getMinecraft().currentScreen != null) { + Minecraft.getMinecraft().displayGuiScreen(null); + } + Minecraft.getMinecraft().thePlayer.sendChatMessage("/viewrecipe " + o.get("internalname").getAsString()); + + ses.schedule(checker, 1000, TimeUnit.MILLISECONDS); + }*/ + if(event.gui instanceof GuiContainer && isOnSkyblock()) { + if(overlay.keyboardInput(focusInv)) { + event.setCanceled(true); + } + } + } + + /** + * This was code leftover from testing but it ended up in the final mod so I guess its staying here. + * This makes it so that holding LCONTROL while hovering over an item with NBT will show the NBT of the item. + * @param event + */ + @SubscribeEvent + public void onItemTooltip(ItemTooltipEvent event) { + if(Minecraft.getMinecraft().currentScreen != null) { + if(Minecraft.getMinecraft().currentScreen instanceof GuiChest) { + GuiChest chest = (GuiChest) Minecraft.getMinecraft().currentScreen; + ContainerChest container = (ContainerChest) chest.inventorySlots; + String containerName = container.getLowerChestInventory().getDisplayName().getUnformattedText(); + if(containerName.trim().equals("Auctions Browser")) { + String internalname = manager.getInternalNameForItem(event.itemStack); + if(internalname != null) { + for(int i=0; i<event.toolTip.size(); i++) { + String line = event.toolTip.get(i); + if(line.contains(EnumChatFormatting.GRAY + "Bidder: ") || + line.contains(EnumChatFormatting.GRAY + "Starting bid: ") || + line.contains(EnumChatFormatting.GRAY + "Buy it now: ")) { + manager.updatePrices(); + JsonObject auctionInfo = manager.getItemAuctionInfo(internalname); + + if(auctionInfo != null) { + NumberFormat format = NumberFormat.getInstance(Locale.US); + int auctionPrice = (int)(auctionInfo.get("price").getAsFloat() / auctionInfo.get("count").getAsFloat()); + float costOfEnchants = manager.getCostOfEnchants(internalname, + event.itemStack.getTagCompound()); + int priceWithEnchants = auctionPrice+(int)costOfEnchants; + + event.toolTip.add(++i, EnumChatFormatting.GRAY + "Average price: " + + EnumChatFormatting.GOLD + format.format(auctionPrice) + " coins"); + if(costOfEnchants > 0) { + event.toolTip.add(++i, EnumChatFormatting.GRAY + "Average price (w/ enchants): " + + EnumChatFormatting.GOLD + + format.format(priceWithEnchants) + " coins"); + } + + if(manager.config.advancedPriceInfo.value) { + int salesVolume = (int) auctionInfo.get("sales").getAsFloat(); + int flipPrice = (int)(0.93*priceWithEnchants); + + event.toolTip.add(++i, EnumChatFormatting.GRAY + "Flip Price (93%): " + + EnumChatFormatting.GOLD + format.format(flipPrice) + " coins"); + event.toolTip.add(++i, EnumChatFormatting.GRAY + "Volume: " + + EnumChatFormatting.GOLD + format.format(salesVolume) + " sales/day"); + } + break; + } + } + } + } + } + } + } + if(!Keyboard.isKeyDown(Keyboard.KEY_LCONTROL) || !manager.config.dev.value) return; + if(event.toolTip.get(event.toolTip.size()-1).startsWith(EnumChatFormatting.DARK_GRAY + "NBT: ")) { + event.toolTip.remove(event.toolTip.size()-1); + + StringBuilder sb = new StringBuilder(); + String nbt = event.itemStack.getTagCompound().toString(); + int indent = 0; + for(char c : nbt.toCharArray()) { + boolean newline = false; + if(c == '{' || c == '[') { + indent++; + newline = true; + } else if(c == '}' || c == ']') { + indent--; + sb.append("\n"); + for(int i=0; i<indent; i++) sb.append(" "); + } else if(c == ',') { + newline = true; + } else if(c == '\"') { + sb.append(EnumChatFormatting.RESET.toString() + EnumChatFormatting.GRAY); + } + + sb.append(c); + if(newline) { + sb.append("\n"); + for(int i=0; i<indent; i++) sb.append(" "); + } + } + event.toolTip.add(sb.toString()); + } + } + + //Stolen from Biscut's SkyblockAddons + public boolean isOnSkyblock() { + if(!manager.config.onlyShowOnSkyblock.value) return true; + + Minecraft mc = Minecraft.getMinecraft(); + + if (mc != null && mc.theWorld != null) { + Scoreboard scoreboard = mc.theWorld.getScoreboard(); + ScoreObjective sidebarObjective = scoreboard.getObjectiveInDisplaySlot(1); + if (sidebarObjective != null) { + String objectiveName = sidebarObjective.getDisplayName().replaceAll("(?i)\\u00A7.", ""); + for (String skyblock : SKYBLOCK_IN_ALL_LANGUAGES) { + if (objectiveName.startsWith(skyblock)) { + return true; + } + } + } + } + + return false; + } +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/RequestFocusListener.java b/src/main/java/io/github/moulberry/notenoughupdates/RequestFocusListener.java new file mode 100644 index 00000000..ecc27c9a --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/RequestFocusListener.java @@ -0,0 +1,63 @@ +package io.github.moulberry.notenoughupdates; + +import javax.swing.*; +import javax.swing.event.*; + +/** + * Convenience class to request focus on a component. + * + * When the component is added to a realized Window then component will + * request focus immediately, since the ancestorAdded event is fired + * immediately. + * + * When the component is added to a non realized Window, then the focus + * request will be made once the window is realized, since the + * ancestorAdded event will not be fired until then. + * + * Using the default constructor will cause the listener to be removed + * from the component once the AncestorEvent is generated. A second constructor + * allows you to specify a boolean value of false to prevent the + * AncestorListener from being removed when the event is generated. This will + * allow you to reuse the listener each time the event is generated. + */ +public class RequestFocusListener implements AncestorListener +{ + private boolean removeListener; + + /* + * Convenience constructor. The listener is only used once and then it is + * removed from the component. + */ + public RequestFocusListener() + { + this(true); + } + + /* + * Constructor that controls whether this listen can be used once or + * multiple times. + * + * @param removeListener when true this listener is only invoked once + * otherwise it can be invoked multiple times. + */ + public RequestFocusListener(boolean removeListener) + { + this.removeListener = removeListener; + } + + @Override + public void ancestorAdded(AncestorEvent e) + { + JComponent component = e.getComponent(); + component.requestFocusInWindow(); + + if (removeListener) + component.removeAncestorListener( this ); + } + + @Override + public void ancestorMoved(AncestorEvent e) {} + + @Override + public void ancestorRemoved(AncestorEvent e) {} +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/Utils.java b/src/main/java/io/github/moulberry/notenoughupdates/Utils.java new file mode 100644 index 00000000..c08adc94 --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/Utils.java @@ -0,0 +1,431 @@ +package io.github.moulberry.notenoughupdates; + +import com.mojang.authlib.Agent; +import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; +import com.mojang.authlib.yggdrasil.YggdrasilUserAuthentication; +import io.github.moulberry.notenoughupdates.util.TexLoc; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.inventory.GuiContainer; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.RenderHelper; +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.inventory.Slot; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Session; +import org.lwjgl.input.Keyboard; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL14; + +import javax.swing.*; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.Proxy; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Utils { + + private static boolean hasEffectOverride = false; + + public static <T> ArrayList<T> createList(T... values) { + ArrayList<T> list = new ArrayList<>(); + for(T value : values)list.add(value); + return list; + } + + public static boolean getHasEffectOverride() { + return hasEffectOverride; + } + + public static void drawItemStackWithoutGlint(ItemStack stack, int x, int y) { + RenderItem itemRender = Minecraft.getMinecraft().getRenderItem(); + + RenderHelper.enableGUIStandardItemLighting(); + itemRender.zLevel = -145; //Negates the z-offset of the below method. + hasEffectOverride = true; + try { + itemRender.renderItemAndEffectIntoGUI(stack, x, y); + } catch(Exception e) {e.printStackTrace();} //Catch exceptions to ensure that hasEffectOverride is set back to false. + hasEffectOverride = false; + itemRender.zLevel = 0; + RenderHelper.disableStandardItemLighting(); + } + + public static void drawItemStack(ItemStack stack, int x, int y) { + RenderItem itemRender = Minecraft.getMinecraft().getRenderItem(); + + RenderHelper.enableGUIStandardItemLighting(); + itemRender.zLevel = -145; //Negates the z-offset of the below method. + itemRender.renderItemAndEffectIntoGUI(stack, x, y); + itemRender.zLevel = 0; + RenderHelper.disableStandardItemLighting(); + } + + public static Method getMethod(Class<?> clazz, Class<?>[] params, String... methodNames) { + for(String methodName : methodNames) { + try { + return clazz.getDeclaredMethod(methodName, params); + } catch(Exception e) {} + } + return null; + } + + public static Object getField(Class<?> clazz, Object o, String... fieldNames) { + Field field = null; + for(String fieldName : fieldNames) { + try { + field = clazz.getDeclaredField(fieldName); + break; + } catch(Exception e) {} + } + if(field != null) { + field.setAccessible(true); + try { + return field.get(o); + } catch(IllegalAccessException e) { + } + } + return null; + } + + public static Slot getSlotUnderMouse(GuiContainer container) { + return (Slot) getField(GuiContainer.class, container, "theSlot", "field_147006_u"); + } + + public static void drawTexturedRect(float x, float y, float width, float height) { + drawTexturedRect(x, y, width, height, 0, 1, 0 , 1); + } + + public static void drawTexturedRect(float x, float y, float width, float height, int filter) { + drawTexturedRect(x, y, width, height, 0, 1, 0 , 1, filter); + } + + public static void drawTexturedRect(float x, float y, float width, float height, float uMin, float uMax, float vMin, float vMax) { + drawTexturedRect(x, y, width, height, uMin, uMax, vMin , vMax, GL11.GL_LINEAR); + } + + public static void drawTexturedRect(float x, float y, float width, float height, float uMin, float uMax, float vMin, float vMax, int filter) { + GlStateManager.enableTexture2D(); + GlStateManager.enableBlend(); + GL14.glBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA); + + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, filter); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, filter); + + Tessellator tessellator = Tessellator.getInstance(); + WorldRenderer worldrenderer = tessellator.getWorldRenderer(); + worldrenderer.begin(7, DefaultVertexFormats.POSITION_TEX); + worldrenderer + .pos(x, y+height, 0.0D) + .tex(uMin, vMax).endVertex(); + worldrenderer + .pos(x+width, y+height, 0.0D) + .tex(uMax, vMax).endVertex(); + worldrenderer + .pos(x+width, y, 0.0D) + .tex(uMax, vMin).endVertex(); + worldrenderer + .pos(x, y, 0.0D) + .tex(uMin, vMin).endVertex(); + tessellator.draw(); + + /*Tessellator tessellator = Tessellator.getInstance(); + WorldRenderer worldrenderer = tessellator.getWorldRenderer(); + worldrenderer.begin(7, DefaultVertexFormats.POSITION_TEX); + worldrenderer + .pos(x, y + height, 0) + .tex(uMin, vMax).endVertex(); + worldrenderer + .pos(x + width, y + height, 0) + .tex(uMax, vMax).endVertex(); + worldrenderer + .pos(x + width, y, 0) + .tex(uMax, vMin).endVertex(); + worldrenderer + .pos(x, y, 0) + .tex(uMin, vMin).endVertex(); + tessellator.draw();*/ + + /*GL11.glBegin(GL11.GL_TRIANGLE_STRIP); + GL11.glTexCoord2f(uMin, vMin); + GL11.glVertex3f(x, y, 0.0F); + GL11.glTexCoord2f(uMin, vMax); + GL11.glVertex3f(x, y+height, 0.0F); + GL11.glTexCoord2f(uMax, vMin); + GL11.glVertex3f(x+width, y, 0.0F); + GL11.glTexCoord2f(uMax, vMax); + GL11.glVertex3f(x+width, y+height, 0.0F); + GL11.glEnd();*/ + + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST); + + GlStateManager.disableBlend(); + } + + public static void drawStringScaledMaxWidth(String str, FontRenderer fr, float x, float y, boolean shadow, int len, int colour) { + int strLen = fr.getStringWidth(str); + float factor = len/(float)strLen; + factor = Math.min(1, factor); + + drawStringScaled(str, fr, x, y, shadow, colour, factor); + } + + public static void drawStringScaled(String str, FontRenderer fr, float x, float y, boolean shadow, int colour, float factor) { + GlStateManager.scale(factor, factor, 1); + fr.drawString(str, x/factor, y/factor, colour, shadow); + GlStateManager.scale(1/factor, 1/factor, 1); + } + + public static void drawStringCenteredScaledMaxWidth(String str, FontRenderer fr, float x, float y, boolean shadow, int len, int colour) { + int strLen = fr.getStringWidth(str); + float factor = len/(float)strLen; + factor = Math.min(1, factor); + int newLen = Math.min(strLen, len); + + float fontHeight = 8*factor; + + drawStringScaled(str, fr, x-newLen/2, y-fontHeight/2, shadow, colour, factor); + } + + public static void drawStringCenteredScaled(String str, FontRenderer fr, float x, float y, boolean shadow, int len, int colour) { + int strLen = fr.getStringWidth(str); + float factor = len/(float)strLen; + float fontHeight = 8*factor; + + drawStringScaled(str, fr, x-len/2, y-fontHeight/2, shadow, colour, factor); + } + + public static int renderStringTrimWidth(String str, FontRenderer fr, boolean shadow, int x, int y, int len, int colour, int maxLines) { + int yOff = 0; + String excess; + String trimmed = trimToWidth(str, len); + + String colourCodes = ""; + Pattern pattern = Pattern.compile("\\u00A7."); + Matcher matcher = pattern.matcher(trimmed); + while(matcher.find()) { + colourCodes += matcher.group(); + } + + boolean firstLine = true; + int trimmedCharacters = trimmed.length(); + int lines = 0; + while((lines++<maxLines) || maxLines<0) { + if(trimmed.length() == str.length()) { + fr.drawString(trimmed, x, y + yOff, colour, shadow); + break; + } else if(trimmed.isEmpty()) { + yOff -= 12; + break; + } else { + if(firstLine) { + fr.drawString(trimmed, x, y + yOff, colour, shadow); + firstLine = false; + } else { + if(trimmed.startsWith(" ")) { + trimmed = trimmed.substring(1); + } + fr.drawString(colourCodes + trimmed, x, y + yOff, colour, shadow); + } + + excess = str.substring(trimmedCharacters); + trimmed = trimToWidth(excess, len); + trimmedCharacters += trimmed.length(); + yOff += 12; + } + } + return yOff; + } + + public static String trimToWidth(String str, int len) { + FontRenderer fr = Minecraft.getMinecraft().fontRendererObj; + String trim = fr.trimStringToWidth(str, len); + + if(str.length() != trim.length() && !trim.endsWith(" ")) { + char next = str.charAt(trim.length()); + if(next != ' ') { + String[] split = trim.split(" "); + String last = split[split.length-1]; + if(last.length() < 8) { + trim = trim.substring(0, trim.length()-last.length()); + } + } + } + + return trim; + } + + public static void drawHoveringText(List<String> textLines, final int mouseX, final int mouseY, final int screenWidth, final int screenHeight, final int maxTextWidth, FontRenderer font) { + if (!textLines.isEmpty()) + { + GlStateManager.disableRescaleNormal(); + RenderHelper.disableStandardItemLighting(); + GlStateManager.disableLighting(); + GlStateManager.disableDepth(); + int tooltipTextWidth = 0; + + for (String textLine : textLines) + { + int textLineWidth = font.getStringWidth(textLine); + + if (textLineWidth > tooltipTextWidth) + { + tooltipTextWidth = textLineWidth; + } + } + + boolean needsWrap = false; + + int titleLinesCount = 1; + int tooltipX = mouseX + 12; + if (tooltipX + tooltipTextWidth + 4 > screenWidth) + { + tooltipX = mouseX - 16 - tooltipTextWidth; + if (tooltipX < 4) // if the tooltip doesn't fit on the screen + { + if (mouseX > screenWidth / 2) + { + tooltipTextWidth = mouseX - 12 - 8; + } + else + { + tooltipTextWidth = screenWidth - 16 - mouseX; + } + needsWrap = true; + } + } + + if (maxTextWidth > 0 && tooltipTextWidth > maxTextWidth) + { + tooltipTextWidth = maxTextWidth; + needsWrap = true; + } + + if (needsWrap) + { + int wrappedTooltipWidth = 0; + List<String> wrappedTextLines = new ArrayList<String>(); + for (int i = 0; i < textLines.size(); i++) + { + String textLine = textLines.get(i); + List<String> wrappedLine = font.listFormattedStringToWidth(textLine, tooltipTextWidth); + if (i == 0) + { + titleLinesCount = wrappedLine.size(); + } + + for (String line : wrappedLine) + { + int lineWidth = font.getStringWidth(line); + if (lineWidth > wrappedTooltipWidth) + { + wrappedTooltipWidth = lineWidth; + } + wrappedTextLines.add(line); + } + } + tooltipTextWidth = wrappedTooltipWidth; + textLines = wrappedTextLines; + + if (mouseX > screenWidth / 2) + { + tooltipX = mouseX - 16 - tooltipTextWidth; + } + else + { + tooltipX = mouseX + 12; + } + } + + int tooltipY = mouseY - 12; + int tooltipHeight = 8; + + if (textLines.size() > 1) + { + tooltipHeight += (textLines.size() - 1) * 10; + if (textLines.size() > titleLinesCount) { + tooltipHeight += 2; // gap between title lines and next lines + } + } + + if (tooltipY + tooltipHeight + 6 > screenHeight) + { + tooltipY = screenHeight - tooltipHeight - 6; + } + + final int zLevel = 300; + final int backgroundColor = 0xF0100010; + drawGradientRect(zLevel, tooltipX - 3, tooltipY - 4, tooltipX + tooltipTextWidth + 3, tooltipY - 3, backgroundColor, backgroundColor); + drawGradientRect(zLevel, tooltipX - 3, tooltipY + tooltipHeight + 3, tooltipX + tooltipTextWidth + 3, tooltipY + tooltipHeight + 4, backgroundColor, backgroundColor); + drawGradientRect(zLevel, tooltipX - 3, tooltipY - 3, tooltipX + tooltipTextWidth + 3, tooltipY + tooltipHeight + 3, backgroundColor, backgroundColor); + drawGradientRect(zLevel, tooltipX - 4, tooltipY - 3, tooltipX - 3, tooltipY + tooltipHeight + 3, backgroundColor, backgroundColor); + drawGradientRect(zLevel, tooltipX + tooltipTextWidth + 3, tooltipY - 3, tooltipX + tooltipTextWidth + 4, tooltipY + tooltipHeight + 3, backgroundColor, backgroundColor); + final int borderColorStart = 0x505000FF; + final int borderColorEnd = (borderColorStart & 0xFEFEFE) >> 1 | borderColorStart & 0xFF000000; + drawGradientRect(zLevel, tooltipX - 3, tooltipY - 3 + 1, tooltipX - 3 + 1, tooltipY + tooltipHeight + 3 - 1, borderColorStart, borderColorEnd); + drawGradientRect(zLevel, tooltipX + tooltipTextWidth + 2, tooltipY - 3 + 1, tooltipX + tooltipTextWidth + 3, tooltipY + tooltipHeight + 3 - 1, borderColorStart, borderColorEnd); + drawGradientRect(zLevel, tooltipX - 3, tooltipY - 3, tooltipX + tooltipTextWidth + 3, tooltipY - 3 + 1, borderColorStart, borderColorStart); + drawGradientRect(zLevel, tooltipX - 3, tooltipY + tooltipHeight + 2, tooltipX + tooltipTextWidth + 3, tooltipY + tooltipHeight + 3, borderColorEnd, borderColorEnd); + + for (int lineNumber = 0; lineNumber < textLines.size(); ++lineNumber) + { + String line = textLines.get(lineNumber); + font.drawStringWithShadow(line, (float)tooltipX, (float)tooltipY, -1); + + if (lineNumber + 1 == titleLinesCount) + { + tooltipY += 2; + } + + tooltipY += 10; + } + + GlStateManager.enableLighting(); + GlStateManager.enableDepth(); + RenderHelper.enableStandardItemLighting(); + GlStateManager.enableRescaleNormal(); + } + GlStateManager.disableLighting(); + } + + public static void drawGradientRect(int zLevel, int left, int top, int right, int bottom, int startColor, int endColor) { + float startAlpha = (float)(startColor >> 24 & 255) / 255.0F; + float startRed = (float)(startColor >> 16 & 255) / 255.0F; + float startGreen = (float)(startColor >> 8 & 255) / 255.0F; + float startBlue = (float)(startColor & 255) / 255.0F; + float endAlpha = (float)(endColor >> 24 & 255) / 255.0F; + float endRed = (float)(endColor >> 16 & 255) / 255.0F; + float endGreen = (float)(endColor >> 8 & 255) / 255.0F; + float endBlue = (float)(endColor & 255) / 255.0F; + + GlStateManager.disableTexture2D(); + GlStateManager.enableBlend(); + GlStateManager.disableAlpha(); + GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0); + GlStateManager.shadeModel(7425); + + Tessellator tessellator = Tessellator.getInstance(); + WorldRenderer worldrenderer = tessellator.getWorldRenderer(); + worldrenderer.begin(7, DefaultVertexFormats.POSITION_COLOR); + worldrenderer.pos(right, top, zLevel).color(startRed, startGreen, startBlue, startAlpha).endVertex(); + worldrenderer.pos(left, top, zLevel).color(startRed, startGreen, startBlue, startAlpha).endVertex(); + worldrenderer.pos(left, bottom, zLevel).color(endRed, endGreen, endBlue, endAlpha).endVertex(); + worldrenderer.pos(right, bottom, zLevel).color(endRed, endGreen, endBlue, endAlpha).endVertex(); + tessellator.draw(); + + GlStateManager.shadeModel(7424); + GlStateManager.disableBlend(); + GlStateManager.enableAlpha(); + GlStateManager.enableTexture2D(); + } + +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/infopanes/DevInfoPane.java b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/DevInfoPane.java new file mode 100644 index 00000000..0abca240 --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/DevInfoPane.java @@ -0,0 +1,106 @@ +package io.github.moulberry.notenoughupdates.infopanes; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import io.github.moulberry.notenoughupdates.NEUManager; +import io.github.moulberry.notenoughupdates.NEUOverlay; +import io.github.moulberry.notenoughupdates.Utils; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.item.Item; +import net.minecraft.util.ResourceLocation; +import org.lwjgl.input.Keyboard; + +import java.awt.*; +import java.util.*; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +public class DevInfoPane extends TextInfoPane { + + public DevInfoPane(NEUOverlay overlay, NEUManager manager) { + super(overlay, manager, "Dev", ""); + text = getText(); + } + + private String getText() { + String text = ""; + for(Map.Entry<String, JsonElement> entry : manager.getAuctionPricesJson().get("prices").getAsJsonObject().entrySet()) { + if(!manager.getItemInformation().keySet().contains(entry.getKey())) { + if(entry.getKey().contains("-")) { + continue; + } + if(entry.getKey().startsWith("PERFECT")) continue; + if(Item.itemRegistry.getObject(new ResourceLocation(entry.getKey().toLowerCase())) != null) { + continue; + } + text += entry.getKey() + "\n"; + } + } + return text; + } + + AtomicBoolean running = new AtomicBoolean(false); + ScheduledExecutorService ses = Executors.newScheduledThreadPool(1); + + @Override + public void keyboardInput() { + if(Keyboard.isKeyDown(Keyboard.KEY_J)) { + running.set(!running.get()); + + if(running.get()) { + List<String> add = new ArrayList<>(); + for(Map.Entry<String, JsonObject> item : manager.getItemInformation().entrySet()) { + if(item.getValue().has("recipe")) { + if(!item.getKey().contains("-") && !item.getKey().contains(";")) { + add.add(item.getKey()); + } + } + } + AtomicInteger index = new AtomicInteger(0); + + ses.schedule(new Runnable() { + public void run() { + if(!running.get()) return; + + int i = index.getAndIncrement(); + String item = add.get(i).split("-")[0].split(";")[0]; + Minecraft.getMinecraft().thePlayer.sendChatMessage("/viewrecipe " + item); + ses.schedule(this, 1000L, TimeUnit.MILLISECONDS); + } + }, 1000L, TimeUnit.MILLISECONDS); + } + } + /*if(Keyboard.isKeyDown(Keyboard.KEY_J) && !running) { + running = true; + List<String> add = new ArrayList<>(); + for(Map.Entry<String, JsonElement> entry : manager.getAuctionPricesJson().get("prices").getAsJsonObject().entrySet()) { + if(!manager.getItemInformation().keySet().contains(entry.getKey())) { + if(entry.getKey().contains("-")) { + continue; + } + if(entry.getKey().startsWith("PERFECT")) continue; + if(Item.itemRegistry.getObject(new ResourceLocation(entry.getKey().toLowerCase())) != null) { + continue; + } + add.add(entry.getKey()); + } + } + AtomicInteger index = new AtomicInteger(0); + + ses.schedule(new Runnable() { + public void run() { + int i = index.getAndIncrement(); + String item = add.get(i).split("-")[0].split(";")[0]; + Minecraft.getMinecraft().thePlayer.sendChatMessage("/viewrecipe " + item); + ses.schedule(this, 1000L, TimeUnit.MILLISECONDS); + } + }, 1000L, TimeUnit.MILLISECONDS); + }*/ + } +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/infopanes/FlipperInfoPane.java b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/FlipperInfoPane.java new file mode 100644 index 00000000..49cc5389 --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/FlipperInfoPane.java @@ -0,0 +1,111 @@ +package io.github.moulberry.notenoughupdates.infopanes; + +import io.github.moulberry.notenoughupdates.NEUManager; +import io.github.moulberry.notenoughupdates.NEUOverlay; +import io.github.moulberry.notenoughupdates.Utils; +import io.github.moulberry.notenoughupdates.itemeditor.GuiElementButton; +import io.github.moulberry.notenoughupdates.itemeditor.GuiElementTextField; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.util.EnumChatFormatting; +import org.lwjgl.opengl.GL11; + +import static io.github.moulberry.notenoughupdates.GuiTextures.*; +import static io.github.moulberry.notenoughupdates.itemeditor.GuiElementTextField.*; + +import java.awt.*; +import java.util.ArrayList; +import java.util.List; + +public class FlipperInfoPane extends InfoPane { + + protected String title; + protected String text; + + GuiElementTextField minPrice = new GuiElementTextField("0", NUM_ONLY | NO_SPACE); + GuiElementTextField maxPrice = new GuiElementTextField("100000000", NUM_ONLY | NO_SPACE); + GuiElementTextField priceDiff = new GuiElementTextField("1000000", NUM_ONLY | NO_SPACE); + + public FlipperInfoPane(NEUOverlay overlay, NEUManager manager, String title, String text) { + super(overlay, manager); + this.title = title; + this.text = text; + + minPrice.setSize(60, 16); + maxPrice.setSize(60, 16); + priceDiff.setSize(60, 16); + } + + public void render(int width, int height, Color bg, Color fg, ScaledResolution scaledresolution, int mouseX, int mouseY) { + FontRenderer fr = Minecraft.getMinecraft().fontRendererObj; + + int paneWidth = (int)(width/3*overlay.getWidthMult()); + int rightSide = (int)(width*overlay.getInfoPaneOffsetFactor()); + int leftSide = rightSide - paneWidth; + + int titleLen = fr.getStringWidth(title); + fr.drawString(title, (leftSide+rightSide-titleLen)/2, overlay.BOX_PADDING + 5, + Color.WHITE.getRGB()); + + int y = 0; + y += renderParagraph(width, height, y, "Bazaar Flips", bg); + //draw controls + y += 20; + y += renderParagraph(width, height, y, "-- Strong Dragon Fragments", bg); + y += renderParagraph(width, height, y, "-- Strong Dragon Fragments", bg); + y += renderParagraph(width, height, y, "-- Strong Dragon Fragments", bg); + y += renderParagraph(width, height, y, "-- Strong Dragon Fragments", bg); + + y += renderParagraph(width, height, y, "AH Flips", bg); + //min price, max price, price diff, blacklist stackables + //GuiElementButton stackables = new GuiElementButton("1000000", NUM_ONLY | NO_SPACE); + + y += 10; + int x = 10; + fr.drawString("Min Price: ", x, y, Color.WHITE.getRGB()); + minPrice.render(x, y+10); x += 70; + fr.drawString("Max Price: ", x, y, Color.WHITE.getRGB()); + maxPrice.render(x, y+10); x += 70; + fr.drawString("Price Diff: ", x, y, Color.WHITE.getRGB()); + priceDiff.render(x, y+10); x += 70; + fr.drawString("Incl. Stackables: ", x, y, Color.WHITE.getRGB()); + drawButton(x, y, false); + + drawRect(leftSide+overlay.BOX_PADDING-5, overlay.BOX_PADDING-5, + rightSide-overlay.BOX_PADDING+5, height-overlay.BOX_PADDING+5, bg.getRGB()); + } + + private void drawButton(int x, int y, boolean enabled) { + GlStateManager.color(1f, 1f, 1f, 1f); + Minecraft.getMinecraft().getTextureManager().bindTexture((enabled) ? on : off); + Utils.drawTexturedRect(x, y, 48, 16); + } + + public void mouseInput(int width, int height, int mouseX, int mouseY, boolean mouseDown) { + + } + + public void keyboardInput() { + + } + + private int renderParagraph(int width, int height, int startY, String text, Color bg) { + FontRenderer fr = Minecraft.getMinecraft().fontRendererObj; + + int paneWidth = (int)(width/3*overlay.getWidthMult()); + int rightSide = (int)(width*overlay.getInfoPaneOffsetFactor()); + int leftSide = rightSide - paneWidth; + + int yOff = 0; + for(String line : text.split("\n")) { + yOff += Utils.renderStringTrimWidth(line, fr, false,leftSide+overlay.BOX_PADDING + 5, + startY+overlay.BOX_PADDING + 10 + yOff, + width*1/3-overlay.BOX_PADDING*2-10, Color.WHITE.getRGB(), -1); + yOff += 16; + } + + return yOff; + } +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/infopanes/HTMLInfoPane.java b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/HTMLInfoPane.java new file mode 100644 index 00000000..6469ff1c --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/HTMLInfoPane.java @@ -0,0 +1,316 @@ +package io.github.moulberry.notenoughupdates.infopanes; + +import info.bliki.htmlcleaner.TagNode; +import info.bliki.wiki.filter.Encoder; +import info.bliki.wiki.model.Configuration; +import info.bliki.wiki.model.ImageFormat; +import info.bliki.wiki.model.WikiModel; +import info.bliki.wiki.tags.HTMLBlockTag; +import info.bliki.wiki.tags.HTMLTag; +import info.bliki.wiki.tags.IgnoreTag; +import io.github.moulberry.notenoughupdates.AllowEmptyHTMLTag; +import io.github.moulberry.notenoughupdates.NEUManager; +import io.github.moulberry.notenoughupdates.NEUOverlay; +import io.github.moulberry.notenoughupdates.Utils; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.texture.DynamicTexture; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.ResourceLocation; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.*; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +public class HTMLInfoPane extends TextInfoPane { + + private static WikiModel wikiModel; + + private final int ZOOM_FACTOR = 2; + private final int IMAGE_WIDTH = 400; + private final int EXT_WIDTH = 100; + + private ResourceLocation imageTexture = null; + private BufferedImage imageTemp = null; + private int imageHeight = 0; + private int imageWidth = 0; + + static { + Configuration conf = new Configuration(); + conf.addTokenTag("img", new HTMLTag("img")); + conf.addTokenTag("code", new HTMLTag("code")); + conf.addTokenTag("span", new AllowEmptyHTMLTag("span")); + conf.addTokenTag("table", new HTMLBlockTag("table", Configuration.SPECIAL_BLOCK_TAGS+"span|")); + conf.addTokenTag("infobox", new IgnoreTag("infobox")); + conf.addTokenTag("tabber", new IgnoreTag("tabber")); + conf.addTokenTag("kbd", new HTMLTag("kbd")); + wikiModel = new WikiModel(conf,"https://hypixel-skyblock.fandom.com/wiki/Special:Filepath/${image}", + "https://hypixel-skyblock.fandom.com/wiki/${title}") { + { + TagNode.addAllowedAttribute("style"); + TagNode.addAllowedAttribute("src"); + } + + protected String createImageName(ImageFormat imageFormat) { + String imageName = imageFormat.getFilename(); + if (imageName.endsWith(".svg")) { + imageName += ".png"; + } + imageName = Encoder.encodeUrl(imageName); + if (replaceColon()) { + imageName = imageName.replace(':', '/'); + } + return imageName; + } + + public void parseInternalImageLink(String imageNamespace, String rawImageLink) { + rawImageLink = rawImageLink.replaceFirst("\\|x([0-9]+)px", "\\|$1x$1px"); + if(!rawImageLink.split("\\|")[0].toLowerCase().endsWith(".jpg")) { + super.parseInternalImageLink(imageNamespace, rawImageLink); + } + } + }; + } + + public static HTMLInfoPane createFromWikiUrl(NEUOverlay overlay, NEUManager manager, String name, String wikiUrl) { + File f = manager.getWebFile(wikiUrl); + if(f == null) { + return new HTMLInfoPane(overlay, manager, "error", "Failed to load wiki url: "+ wikiUrl); + }; + + StringBuilder sb = new StringBuilder(); + try(BufferedReader br = new BufferedReader(new InputStreamReader( + new FileInputStream(f), StandardCharsets.UTF_8))) { + String l; + while((l = br.readLine()) != null){ + sb.append(l).append("\n"); + } + } catch(IOException e) { + return new HTMLInfoPane(overlay, manager, "error", "Failed to load wiki url: "+ wikiUrl); + } + return createFromWiki(overlay, manager, name, sb.toString()); + } + + public static HTMLInfoPane createFromWiki(NEUOverlay overlay, NEUManager manager, String name, String wiki) { + String[] split = wiki.split("</infobox>"); + wiki = split[split.length - 1]; //Remove everything before infobox + wiki = wiki.split("<span class=\"navbox-vde\">")[0]; //Remove navbox + wiki = wiki.split("<table class=\"navbox mw-collapsible\"")[0]; + wiki = "__NOTOC__\n" + wiki; //Remove TOC + try (PrintWriter out = new PrintWriter(new File(manager.configLocation, "debug/parsed.txt"))) { + out.println(wiki); + } catch (IOException e) { + } + String html; + try { + html = wikiModel.render(wiki); + } catch(IOException e) { + return new HTMLInfoPane(overlay, manager, "error", "Could not render wiki."); + } + try (PrintWriter out = new PrintWriter(new File(manager.configLocation, "debug/html.txt"))) { + out.println(html); + } catch (IOException e) { + } + return new HTMLInfoPane(overlay, manager, name, html); + } + + public HTMLInfoPane(NEUOverlay overlay, NEUManager manager, String name, String html) { + super(overlay, manager, name, ""); + this.title = name; + + File cssFile = new File(manager.configLocation, "wikia.css"); + File wkHtmlToImage = new File(manager.configLocation, "wkhtmltox/bin/wkhtmltoimage"); + File input = new File(manager.configLocation, "tmp/input.html"); + String outputFileName = name.replaceAll("(?i)\\u00A7.", "") + .replaceAll("[^a-zA-Z0-9_\\-]", "_"); + File output = new File(manager.configLocation, "tmp/"+ + outputFileName+".png"); + File outputExt = new File(manager.configLocation, "tmp/"+ + outputFileName+"_ext.png"); + + input.deleteOnExit(); + output.deleteOnExit(); + + File tmp = new File(manager.configLocation, "tmp"); + if(!tmp.exists()) { + tmp.mkdir(); + } + + if(output.exists()) { + try { + imageTemp = ImageIO.read(output); + text = EnumChatFormatting.RED+"Creating dynamic texture."; + } catch(IOException e) { + e.printStackTrace(); + text = EnumChatFormatting.RED+"Failed to read image."; + return; + } + } 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; + + try(PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter( + new FileOutputStream(input), StandardCharsets.UTF_8)), false)) { + + 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(); + Process p = runtime.exec("\""+wkHtmlToImage.getAbsolutePath() + "\" --width "+ + IMAGE_WIDTH*ZOOM_FACTOR+" --transparent --zoom "+ZOOM_FACTOR+" \"" + input.getAbsolutePath() + + "\" \"" + output.getAbsolutePath() + "\""); + /*Process p2 = runtime.exec("\""+wkHtmlToImage.getAbsolutePath() + "\" --width "+ + (IMAGE_WIDTH+EXT_WIDTH)*ZOOM_FACTOR+" --transparent --zoom "+ZOOM_FACTOR+" \"" + input.getAbsolutePath() + + "\" \"" + outputExt.getAbsolutePath() + "\"");*/ + ste.submit(() -> { + try { + if(p.waitFor(15, TimeUnit.SECONDS)) { + //if(p2.waitFor(5, TimeUnit.SECONDS)) { + if(overlay.getActiveInfoPane() != this) return; + + try { + imageTemp = ImageIO.read(output); + /*BufferedImage imageReg = ImageIO.read(output); + BufferedImage imageExt = ImageIO.read(outputExt); + ArrayList<Integer[]> pixels = new ArrayList<>(); + + int skip = IMAGE_WIDTH/EXT_WIDTH+1; + + for(int y=0; y<imageReg.getHeight(); y++) { + pixels.add(new Integer[IMAGE_WIDTH*ZOOM_FACTOR]); + if(new Color(imageReg.getRGB(IMAGE_WIDTH*ZOOM_FACTOR-1, y), true).getAlpha() == 0) { + for(int x=0; x<IMAGE_WIDTH*ZOOM_FACTOR; x++) { + pixels.get(y)[x] = imageReg.getRGB(x, y); + } + } else { + for(int x=0; x<(IMAGE_WIDTH+EXT_WIDTH)*ZOOM_FACTOR; x++) { + int x2 = x*IMAGE_WIDTH/(IMAGE_WIDTH+EXT_WIDTH); + int y2 = y*(IMAGE_WIDTH+EXT_WIDTH)/IMAGE_WIDTH; + pixels.get(y)[x2] = imageExt.getRGB(x, y2); + } + } + } + imageTemp = new BufferedImage(IMAGE_WIDTH*ZOOM_FACTOR, pixels.size(), imageReg.getType()); + for(int y=0; y<pixels.size(); y++) { + for(int x=0; x<IMAGE_WIDTH*ZOOM_FACTOR; x++) { + int col = pixels.get(y)[x]; + imageTemp.setRGB(x, y, col); + } + }*/ + text = EnumChatFormatting.RED+"Creating dynamic texture."; + } catch(IOException e) { + e.printStackTrace(); + text = EnumChatFormatting.RED+"Failed to read image."; + return; + } + } else { + if(overlay.getActiveInfoPane() != this) return; + + text = EnumChatFormatting.RED+"Webpage render timed out (>15sec). Maybe it's too large?"; + } + } catch(Exception e) { + e.printStackTrace(); + } + }); + } catch(IOException e) { + e.printStackTrace(); + text = EnumChatFormatting.RED+"Failed to exec webpage renderer."; + } finally { + ste.shutdown(); + } + } + } + + @Override + public void render(int width, int height, Color bg, Color fg, ScaledResolution scaledresolution, int mouseX, int mouseY) { + if(imageTemp != null && imageTexture == null) { + DynamicTexture tex = new DynamicTexture(imageTemp); + imageTexture = Minecraft.getMinecraft().getTextureManager().getDynamicTextureLocation( + "notenoughupdates/informationPaneImage", tex); + imageHeight = imageTemp.getHeight(); + imageWidth = imageTemp.getWidth(); + } + if(imageTexture == null) { + super.render(width, height, bg, fg, scaledresolution, mouseX, mouseY); + return; + } + + FontRenderer fr = Minecraft.getMinecraft().fontRendererObj; + + int paneWidth = (int)(width/3*overlay.getWidthMult()); + int rightSide = (int)(width*overlay.getInfoPaneOffsetFactor()); + int leftSide = rightSide - paneWidth; + + int titleLen = fr.getStringWidth(title); + fr.drawString(title, (leftSide+rightSide-titleLen)/2, overlay.BOX_PADDING + 5, Color.WHITE.getRGB()); + + drawRect(leftSide+overlay.BOX_PADDING-5, overlay.BOX_PADDING-5, rightSide-overlay.BOX_PADDING+5, + height-overlay.BOX_PADDING+5, bg.getRGB()); + + int imageW = paneWidth - overlay.BOX_PADDING*2; + float scaleF = IMAGE_WIDTH*ZOOM_FACTOR/(float)imageW; + + Minecraft.getMinecraft().getTextureManager().bindTexture(imageTexture); + GlStateManager.color(1f, 1f, 1f, 1f); + if(height-overlay.BOX_PADDING*3 < imageHeight/scaleF) { + if(scrollHeight.getValue() > imageHeight/scaleF-height+overlay.BOX_PADDING*3) { + scrollHeight.setValue((int)(imageHeight/scaleF-height+overlay.BOX_PADDING*3)); + } + int yScroll = scrollHeight.getValue(); + + float vMin = yScroll/(imageHeight/scaleF); + float vMax = (yScroll+height-overlay.BOX_PADDING*3)/(imageHeight/scaleF); + Utils.drawTexturedRect(leftSide+overlay.BOX_PADDING, overlay.BOX_PADDING*2, imageW, + height-overlay.BOX_PADDING*3, + 0, 1, vMin, vMax); + } else { + scrollHeight.setValue(0); + + Utils.drawTexturedRect(leftSide+overlay.BOX_PADDING, overlay.BOX_PADDING*2, imageW, + (int)(imageHeight/scaleF)); + } + GlStateManager.bindTexture(0); + } + + @Override + public void keyboardInput() { + } + + @Override + public void mouseInput(int width, int height, int mouseX, int mouseY, boolean mouseDown) { + super.mouseInput(width, height, mouseX, mouseY, mouseDown); + } + + //From https://stackoverflow.com/questions/1760766/how-to-convert-non-supported-character-to-html-entity-in-java + public String encodeNonAscii(String c) { + StringBuilder buf = new StringBuilder(c.length()); + CharsetEncoder enc = StandardCharsets.US_ASCII.newEncoder(); + for (int idx = 0; idx < c.length(); ++idx) { + char ch = c.charAt(idx); + if (enc.canEncode(ch)) + buf.append(ch); + else { + buf.append("&#"); + buf.append((int) ch); + buf.append(';'); + } + } + return buf.toString(); + } +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/infopanes/InfoPane.java b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/InfoPane.java new file mode 100644 index 00000000..74321081 --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/InfoPane.java @@ -0,0 +1,44 @@ +package io.github.moulberry.notenoughupdates.infopanes; + +import io.github.moulberry.notenoughupdates.NEUManager; +import io.github.moulberry.notenoughupdates.NEUOverlay; +import net.minecraft.client.gui.Gui; +import net.minecraft.client.gui.ScaledResolution; + +import java.awt.*; + +public abstract class InfoPane extends Gui { + + final NEUOverlay overlay; + final NEUManager manager; + + public InfoPane(NEUOverlay overlay, NEUManager manager) { + this.overlay = overlay; + this.manager = manager; + } + + public void tick() {} + + public abstract void render(int width, int height, Color bg, Color fg, ScaledResolution scaledresolution, int mouseX, + int mouseY); + + public abstract void mouseInput(int width, int height, int mouseX, int mouseY, boolean mouseDown); + + public abstract void keyboardInput(); + + public void renderDefaultBackground(int width, int height, Color bg) { + int paneWidth = (int)(width/3*overlay.getWidthMult()); + int rightSide = (int)(width*overlay.getInfoPaneOffsetFactor()); + int leftSide = rightSide - paneWidth; + + int boxLeft = leftSide + overlay.BOX_PADDING - 5; + int boxRight = rightSide - overlay.BOX_PADDING + 5; + + overlay.renderBlurredBackground(width, height, + boxLeft, overlay.BOX_PADDING-5, + boxRight-boxLeft, height-overlay.BOX_PADDING*2+10); + drawRect(boxLeft, overlay.BOX_PADDING - 5, boxRight, + height - overlay.BOX_PADDING + 5, bg.getRGB()); + } + +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/infopanes/QOLInfoPane.java b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/QOLInfoPane.java new file mode 100644 index 00000000..bf1a987e --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/QOLInfoPane.java @@ -0,0 +1,344 @@ +package io.github.moulberry.notenoughupdates.infopanes; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import io.github.moulberry.notenoughupdates.NEUManager; +import io.github.moulberry.notenoughupdates.NEUOverlay; +import io.github.moulberry.notenoughupdates.Utils; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompressedStreamTools; +import net.minecraft.nbt.JsonToNBT; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.ResourceLocation; +import org.lwjgl.input.Keyboard; + +import java.awt.*; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.*; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +public class QOLInfoPane extends ScrollableInfoPane { + private LinkedHashMap<String, ItemStack> accessoryMap = new LinkedHashMap<>(); + private LinkedHashMap<String, JsonObject> allTalismans = new LinkedHashMap<>(); + private List<JsonObject> recommended = new ArrayList<>(); + private int maxAccessories = 39; //TODO: Get from API + + private Integer[] talismanRarityValue = new Integer[]{1, 2, 4, 7, 10}; + + public QOLInfoPane(NEUOverlay overlay, NEUManager manager) { + super(overlay, manager); + + Comparator<JsonObject> rarityComparator = new Comparator<JsonObject>() { + @Override + public int compare(JsonObject o1, JsonObject o2) { + int rarity1 = overlay.getRarity(o1.get("lore").getAsJsonArray()); + int rarity2 = overlay.getRarity(o2.get("lore").getAsJsonArray()); + + int rarityDiff = rarity2 - rarity1; + if(rarityDiff != 0) { + return rarityDiff; + } + + return o2.get("internalname").getAsString().compareTo(o1.get("internalname").getAsString()); + } + }; + + TreeSet<JsonObject> all = new TreeSet<>(rarityComparator); + LinkedHashMap<String, JsonObject> highestRarity = new LinkedHashMap<>(); + LinkedHashMap<String, JsonObject> lowerRarity = new LinkedHashMap<>(); + + for(Map.Entry<String, JsonObject> entry : manager.getItemInformation().entrySet()) { + if(overlay.checkItemType(entry.getValue().get("lore").getAsJsonArray(), "ACCESSORY") >= 0) { + all.add(entry.getValue()); + } + } + outer: + for(JsonObject o : all) { + String internalname = o.get("internalname").getAsString(); + String name = getTalismanName(internalname); + int power = getTalismanPower(internalname); + for(JsonObject o2 : all) { + if(o != o2) { + String internalname2 = o2.get("internalname").getAsString(); + String name2 = getTalismanName(internalname2); + if(name2.equals(name)) { + int power2 = getTalismanPower(internalname2); + if(power2 > power) { + lowerRarity.put(internalname, o); + continue outer; + } + } + } + } + highestRarity.put(internalname, o); + } + for(Map.Entry<String, JsonObject> entry : highestRarity.entrySet()) { + allTalismans.put(entry.getKey(), entry.getValue()); + } + for(Map.Entry<String, JsonObject> entry : lowerRarity.entrySet()) { + allTalismans.put(entry.getKey(), entry.getValue()); + } + + HashMap<String, String> args = new HashMap<>(); + String uuid = Minecraft.getMinecraft().thePlayer.getGameProfile().getId().toString(); + args.put("uuid", uuid); + manager.hypixelApi.getHypixelApiAsync(manager.config.apiKey.value, "skyblock/profiles", + args, jsonObject -> { + if(jsonObject.get("success").getAsBoolean()) { + JsonObject currProfile = null; + for(JsonElement e : jsonObject.get("profiles").getAsJsonArray()) { + JsonObject profile = e.getAsJsonObject(); + String profileId = profile.get("profile_id").getAsString(); + String cuteName = profile.get("cute_name").getAsString(); + + if(manager.currentProfile.equals(cuteName)) { + JsonObject members = profile.get("members").getAsJsonObject(); + JsonObject profile_member = members.get(uuid.replaceAll("-","")).getAsJsonObject(); + currProfile = profile_member; + } + } + if(currProfile.has("talisman_bag")) { + String b64 = currProfile.get("talisman_bag").getAsJsonObject().get("data").getAsString(); + try { + NBTTagCompound tag = CompressedStreamTools.readCompressed(new ByteArrayInputStream(Base64.getDecoder().decode(b64))); + NBTTagList list = tag.getTagList("i", 10); + for(int i=0; i<list.tagCount(); i++) { + NBTTagCompound accessory = list.getCompoundTagAt(i); + if(accessory.hasKey("tag")) { + String accessoryID = accessory.getCompoundTag("tag") + .getCompoundTag("ExtraAttributes").getString("id"); + ItemStack accessoryStack = ItemStack.loadItemStackFromNBT(accessory); + accessoryMap.put(accessoryID, accessoryStack); + } + + } + } catch(IOException e) { + } + + int lowestRarity = -1; + if(accessoryMap.size() >= maxAccessories) { + lowestRarity = 999; + for(Map.Entry<String, ItemStack> entry : accessoryMap.entrySet()) { + JsonObject json = manager.getJsonForItem(entry.getValue()); + int rarity = overlay.getRarity(json.get("lore").getAsJsonArray()); + if(rarity < lowestRarity) { + lowestRarity = rarity; + } + } + } + System.out.println("lowestrarity:"+lowestRarity); + + TreeMap<Float, JsonObject> valueMap = new TreeMap<>(); + outer: + for(Map.Entry<String, JsonObject> entry : allTalismans.entrySet()) { + int rarity = overlay.getRarity(entry.getValue().get("lore").getAsJsonArray()); + System.out.println(entry.getKey() + ":" + rarity); + if(rarity > lowestRarity) { + System.out.println("found greater:"+entry.getKey()); + float rarityVal = (float)talismanRarityValue[rarity]; + System.out.println("rarity val:"+rarityVal); + float price = manager.getCraftCost(entry.getKey()).craftCost; + + System.out.println("cc:"+price); + if(price < 0) { + System.out.println("invalid price:"+entry.getKey()); + continue; + } + + String internalname = entry.getValue().get("internalname").getAsString(); + String name = getTalismanName(internalname); + int power = getTalismanPower(internalname); + for(Map.Entry<String, ItemStack> entry2 : accessoryMap.entrySet()) { + try { + JsonObject json = manager.getJsonForItem(entry2.getValue()); + String internalname2 = json.get("internalname").getAsString(); + + if(internalname.equals(internalname2)) { + //continue outer; + } + + String name2 = getTalismanName(internalname2); + } catch(Exception e) { + e.printStackTrace(); + System.out.println(":( -> " + entry2.getKey()); + } + + + /*if(name2.equals(name)) { + int power2 = getTalismanPower(internalname2); + if(power2 > power) { + continue outer; + } + }*/ + } + + valueMap.put(-rarityVal/price, entry.getValue()); + } + } + System.out.println("valuemap size:"+valueMap.size()); + int i=0; + for(Map.Entry<Float, JsonObject> entry : valueMap.entrySet()) { + recommended.add(entry.getValue()); + if(++i >= 500) { + break; + } + } + System.out.println("recommended size:"+recommended.size()); + } + //jsonObject.get("profiles") + } + }); + } + + + String[] talismanPowers = new String[]{"RING","ARTIFACT"}; + public int getTalismanPower(String internalname) { + for(int i=0; i<talismanPowers.length; i++) { + if(internalname.endsWith("_"+talismanPowers[i])) { + return i+1; + } + } + return 0; + } + + public String getTalismanName(String internalname) { + String[] split = internalname.split("_"); + StringBuilder name = new StringBuilder(); + for(int i=0; i<split.length; i++) { + name.append(split[i]); + if(i < split.length-1) { + name.append("_"); + } + } + return name.toString(); + } + + public void render(int width, int height, Color bg, Color fg, ScaledResolution scaledresolution, int mouseX, int mouseY) { + renderDefaultBackground(width, height, bg); + + int paneWidth = (int)(width/3*overlay.getWidthMult()); + int rightSide = (int)(width*overlay.getInfoPaneOffsetFactor()); + int leftSide = rightSide - paneWidth; + + int y=overlay.BOX_PADDING+10; + y += renderParagraph(width, height, y, "Current Accessories"); + + ItemStack display = null; + int x=leftSide+overlay.BOX_PADDING+5; + for(Map.Entry<String, ItemStack> entry : accessoryMap.entrySet()) { + if(mouseX > x && mouseX < x+16) { + if(mouseY > y && mouseY < y+16) { + display = entry.getValue(); + } + } + + drawRect(x, y, x+16, y+16, fg.getRGB()); + Utils.drawItemStack(entry.getValue(), x, y); + x += 20; + if(x + 20 + (leftSide+overlay.BOX_PADDING+5) > paneWidth) { + x=leftSide+overlay.BOX_PADDING+5; + y+=20; + } + } + + y+=20; + + y += renderParagraph(width, height, y, "Missing Accessories"); + + y+=10; + + x=leftSide+overlay.BOX_PADDING+5; + for(Map.Entry<String, JsonObject> entry : allTalismans.entrySet()) { + if(accessoryMap.containsKey(entry.getKey())) { + continue; + } + if(mouseX > x && mouseX < x+16) { + if(mouseY > y && mouseY < y+16) { + display = manager.jsonToStack(entry.getValue()); + } + } + + drawRect(x, y, x+16, y+16, fg.getRGB()); + Utils.drawItemStack(manager.jsonToStack(entry.getValue()), x, y); + x += 20; + if(x + 20 + (leftSide+overlay.BOX_PADDING+5) > paneWidth) { + x=leftSide+overlay.BOX_PADDING+5; + y+=20; + } + } + + y+=20; + y += renderParagraph(width, height, y, "Recommended Accessory Upgrades"); + + x=leftSide+overlay.BOX_PADDING+5; + for(JsonObject json : recommended) { + if(mouseX > x && mouseX < x+16) { + if(mouseY > y && mouseY < y+16) { + display = manager.jsonToStack(json); + } + } + + drawRect(x, y, x+16, y+16, fg.getRGB()); + Utils.drawItemStack(manager.jsonToStack(json), x, y); + x += 20; + if(x + 20 + (leftSide+overlay.BOX_PADDING+5) > paneWidth) { + x=leftSide+overlay.BOX_PADDING+5; + y+=20; + } + } + + //L:9/cost, E=6/cost, R=3/cost, C=1/cost + + + if(display != null) { + List<String> list = display.getTooltip(Minecraft.getMinecraft().thePlayer, + Minecraft.getMinecraft().gameSettings.advancedItemTooltips); + + for (int i = 0; i < list.size(); ++i){ + if (i == 0){ + list.set(i, display.getRarity().rarityColor + list.get(i)); + } else { + list.set(i, EnumChatFormatting.GRAY + list.get(i)); + } + } + + Utils.drawHoveringText(list, mouseX, mouseY, width, height, -1, Minecraft.getMinecraft().fontRendererObj); + } + } + + public void keyboardInput() { + + } + + + private int renderParagraph(int width, int height, int startY, String text) { + FontRenderer fr = Minecraft.getMinecraft().fontRendererObj; + + int paneWidth = (int)(width/3*overlay.getWidthMult()); + int rightSide = (int)(width*overlay.getInfoPaneOffsetFactor()); + int leftSide = rightSide - paneWidth; + + int yOff = 0; + for(String line : text.split("\n")) { + yOff += Utils.renderStringTrimWidth(line, fr, false,leftSide+overlay.BOX_PADDING + 5, + startY + yOff, + width*1/3-overlay.BOX_PADDING*2-10, Color.WHITE.getRGB(), -1); + yOff += 16; + } + + return yOff; + } + + +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/infopanes/ScrollableInfoPane.java b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/ScrollableInfoPane.java new file mode 100644 index 00000000..056aeaf1 --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/ScrollableInfoPane.java @@ -0,0 +1,34 @@ +package io.github.moulberry.notenoughupdates.infopanes; + +import io.github.moulberry.notenoughupdates.util.LerpingInteger; +import io.github.moulberry.notenoughupdates.NEUManager; +import io.github.moulberry.notenoughupdates.NEUOverlay; +import org.lwjgl.input.Mouse; + +public abstract class ScrollableInfoPane extends InfoPane { + + private static final int SCROLL_AMOUNT = 50; + protected LerpingInteger scrollHeight = new LerpingInteger(0); + + public ScrollableInfoPane(NEUOverlay overlay, NEUManager manager) { + super(overlay, manager); + } + + public void tick() { + scrollHeight.tick(); + if(scrollHeight.getValue() < 0) scrollHeight.setValue(0); + } + + @Override + public void mouseInput(int width, int height, int mouseX, int mouseY, boolean mouseDown) { + int dWheel = Mouse.getEventDWheel(); + + if(dWheel < 0) { + scrollHeight.setTarget(scrollHeight.getTarget()+SCROLL_AMOUNT); + scrollHeight.resetTimer(); + } else if(dWheel > 0) { + scrollHeight.setTarget(scrollHeight.getTarget()-SCROLL_AMOUNT); + scrollHeight.resetTimer(); + } + } +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/infopanes/SettingsInfoPane.java b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/SettingsInfoPane.java new file mode 100644 index 00000000..31f6dca9 --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/SettingsInfoPane.java @@ -0,0 +1,259 @@ +package io.github.moulberry.notenoughupdates.infopanes; + +import io.github.moulberry.notenoughupdates.NEUManager; +import io.github.moulberry.notenoughupdates.NEUOverlay; +import io.github.moulberry.notenoughupdates.Utils; +import io.github.moulberry.notenoughupdates.itemeditor.GuiElementTextField; +import io.github.moulberry.notenoughupdates.options.Options; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.util.EnumChatFormatting; +import org.lwjgl.input.Keyboard; +import org.lwjgl.input.Mouse; +import org.lwjgl.opengl.GL11; + +import java.awt.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import static io.github.moulberry.notenoughupdates.GuiTextures.*; + +public class SettingsInfoPane extends InfoPane { + + private final Map<Options.Option<?>, GuiElementTextField> textConfigMap = new HashMap<>(); + private int page = 0; + private int maxPages = 1; + + public SettingsInfoPane(NEUOverlay overlay, NEUManager manager) { + super(overlay, manager); + } + + public void render(int width, int height, Color bg, Color fg, ScaledResolution scaledresolution, int mouseX, + int mouseY) { + FontRenderer fr = Minecraft.getMinecraft().fontRendererObj; + + int paneWidth = (int)(width/3*overlay.getWidthMult()); + int rightSide = (int)(width*overlay.getInfoPaneOffsetFactor()); + int leftSide = rightSide - paneWidth; + + this.renderDefaultBackground(width, height, bg); + + if(page > maxPages-1) page = maxPages-1; + if(page < 0) page = 0; + + overlay.renderNavElement(leftSide+overlay.BOX_PADDING, rightSide-overlay.BOX_PADDING, + maxPages,page+1,"Settings: "); + + AtomicReference<List<String>> textToDisplay = new AtomicReference<>(null); + AtomicReference<GuiElementTextField> tfTop = new AtomicReference<>(); + AtomicInteger tfTopX = new AtomicInteger(); + AtomicInteger tfTopY = new AtomicInteger(); + iterateSettingTile(new SettingsTileConsumer() { + public void consume(int x, int y, int tileWidth, int tileHeight, Options.Option<?> option) { + float mult = tileWidth/90f; + + drawRect(x, y, x+tileWidth, y+tileHeight, fg.getRGB()); + if(scaledresolution.getScaleFactor()==4) { + GL11.glScalef(0.5f,0.5f,1); + Utils.renderStringTrimWidth(option.displayName, fr, true, (x+(int)(8*mult))*2, (y+(int)(8*mult))*2, + (tileWidth-(int)(16*mult))*2, new Color(100,255,150).getRGB(), 3); + GL11.glScalef(2,2,1); + } else { + Utils.renderStringTrimWidth(option.displayName, fr, true, x+(int)(8*mult), y+(int)(8*mult), + tileWidth-(int)(16*mult), new Color(100,255,150).getRGB(), 3); + } + + if(Keyboard.isKeyDown(Keyboard.KEY_H)) return; + + if(option.value instanceof Boolean) { + GlStateManager.color(1f, 1f, 1f, 1f); + Minecraft.getMinecraft().getTextureManager().bindTexture(((Boolean)option.value) ? on : off); + Utils.drawTexturedRect(x+tileWidth/2-(int)(32*mult), y+tileHeight-(int)(20*mult), (int)(48*mult), (int)(16*mult)); + + Minecraft.getMinecraft().getTextureManager().bindTexture(help); + Utils.drawTexturedRect(x+tileWidth/2+(int)(19*mult), y+tileHeight-(int)(19*mult), (int)(14*mult), (int)(14*mult)); + GlStateManager.bindTexture(0); + + if(mouseX > x+tileWidth/2+(int)(19*mult) && mouseX < x+tileWidth/2+(int)(19*mult)+(int)(14*mult)) { + if(mouseY > y+tileHeight-(int)(19*mult) && mouseY < y+tileHeight-(int)(19*mult)+(int)(14*mult)) { + List<String> textLines = new ArrayList<>(); + textLines.add(option.displayName); + textLines.add(EnumChatFormatting.GRAY+option.desc); + textToDisplay.set(textLines); + } + } + } else { + if(!textConfigMap.containsKey(option)) { + textConfigMap.put(option, new GuiElementTextField(String.valueOf(option.value), 0)); + } + GuiElementTextField tf = textConfigMap.get(option); + if(tf.getFocus()) { + tf.setSize(Math.max(tileWidth-(int)(20*mult), fr.getStringWidth(tf.getText())+10), (int)(16*mult)); + tfTop.set(tf); + tfTopX.set(x+(int)(10*mult)); + tfTopY.set(y+tileHeight-(int)(20*mult)); + } else { + tf.setSize(tileWidth-(int)(20*mult), (int)(16*mult)); + tf.render(x+(int)(10*mult), y+tileHeight-(int)(20*mult)); + } + } + } + }); + if(tfTop.get() != null) { + tfTop.get().render(tfTopX.get(), tfTopY.get()); + } + if(textToDisplay.get() != null) { + Utils.drawHoveringText(textToDisplay.get(), mouseX, mouseY, width, height, 200, fr); + } + } + + private void onTextfieldChange(GuiElementTextField tf, Options.Option<?> option) { + try { + tf.setCustomBorderColour(-1); + option.setValue(tf.getText()); + overlay.redrawItems(); + } catch(Exception e) { + tf.setCustomBorderColour(Color.RED.getRGB()); + } + } + + public void mouseInput(int width, int height, int mouseX, int mouseY, boolean mouseDown) { + iterateSettingTile(new SettingsTileConsumer() { + @Override + public void consume(int x, int y, int tileWidth, int tileHeight, Options.Option<?> option) { + float mult = tileWidth/90f; + if(option.value instanceof Boolean) { + if(Mouse.getEventButtonState()) { + if(mouseX > x+tileWidth/2-(int)(32*mult) && mouseX < x+tileWidth/2-(int)(32*mult)+(int)(48*mult)) { + if(mouseY > y+tileHeight-(int)(20*mult) && mouseY < y+tileHeight-(int)(20*mult)+(int)(16*mult)) { + ((Options.Option<Boolean>)option).value = !((Boolean)option.value); + overlay.redrawItems(); + return; + } + } + } + } else { + if(!textConfigMap.containsKey(option)) { + textConfigMap.put(option, new GuiElementTextField(String.valueOf(option.value), 0)); + } + GuiElementTextField tf = textConfigMap.get(option); + if(mouseX > x+(int)(10*mult) && mouseX < x+(int)(10*mult)+tileWidth-(int)(20*mult)) { + if(mouseY > y+tileHeight-(int)(20*mult) && mouseY < y+tileHeight-(int)(20*mult)+(int)(16*mult)) { + if(Mouse.getEventButtonState()) { + tf.mouseClicked(mouseX, mouseY, Mouse.getEventButton()); + onTextfieldChange(tf, option); + return; + } else if(Mouse.getEventButton() == -1 && mouseDown) { + tf.mouseClickMove(mouseX, mouseY, 0, 0); //last 2 values are unused + return; + } + } + } + if(Mouse.getEventButtonState()) tf.otherComponentClick(); + } + } + }); + + if(Mouse.getEventButtonState()) { + int paneWidth = (int)(width/3*overlay.getWidthMult()); + int rightSide = (int)(width*overlay.getInfoPaneOffsetFactor()); + int leftSide = rightSide - paneWidth; + rightSide -= overlay.BOX_PADDING; + leftSide += overlay.BOX_PADDING; + + FontRenderer fr = Minecraft.getMinecraft().fontRendererObj; + float maxStrLen = fr.getStringWidth(EnumChatFormatting.BOLD+"Settings: " + maxPages + "/" + maxPages); + float maxButtonXSize = (rightSide-leftSide+2 - maxStrLen*0.5f - 10)/2f; + int buttonXSize = (int)Math.min(maxButtonXSize, overlay.getSearchBarYSize()*480/160f); + int ySize = (int)(buttonXSize/480f*160); + int yOffset = (int)((overlay.getSearchBarYSize()-ySize)/2f); + int top = overlay.BOX_PADDING+yOffset; + + if(mouseY >= top && mouseY <= top+ySize) { + int leftPrev = leftSide-1; + if(mouseX > leftPrev && mouseX < leftPrev+buttonXSize) { //"Previous" button + page--; + } + int leftNext = rightSide+1-buttonXSize; + if(mouseX > leftNext && mouseX < leftNext+buttonXSize) { //"Next" button + page++; + } + } + } + } + + public void keyboardInput() { + iterateSettingTile(new SettingsTileConsumer() { + @Override + public void consume(int x, int y, int tileWidth, int tileHeight, Options.Option<?> option) { + if(!textConfigMap.containsKey(option)) { + textConfigMap.put(option, new GuiElementTextField(String.valueOf(option.value), 0)); + } + GuiElementTextField tf = textConfigMap.get(option); + + if(!(option.value instanceof Boolean)) { + if(tf.getFocus()) { + tf.keyTyped(Keyboard.getEventCharacter(), Keyboard.getEventKey()); + onTextfieldChange(tf, option); + } + } + } + }); + } + + private abstract static class SettingsTileConsumer { + public abstract void consume(int x, int y, int tileWidth, int tileHeight, Options.Option<?> option); + } + + public void iterateSettingTile(SettingsTileConsumer settingsTileConsumer) { + ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft()); + int width = scaledresolution.getScaledWidth(); + int height = scaledresolution.getScaledHeight(); + + int numHorz = scaledresolution.getScaleFactor() >= 3 ? 2 : 3; + + int paneWidth = (int)(width/3*overlay.getWidthMult()); + int rightSide = (int)(width*overlay.getInfoPaneOffsetFactor()); + int leftSide = rightSide - paneWidth; + + int boxLeft = leftSide+overlay.BOX_PADDING-5; + int boxRight = rightSide-overlay.BOX_PADDING+5; + + int boxBottom = height - overlay.BOX_PADDING + 5; + + int boxWidth = boxRight-boxLeft; + int tilePadding = 7; + int tileWidth = (boxWidth-tilePadding*4)/numHorz; + int tileHeight = tileWidth*3/4; + + maxPages=1; + int currPage=0; + int x=0; + int y=tilePadding+overlay.BOX_PADDING+overlay.getSearchBarYSize(); + for(int i=0; i<manager.config.getOptions().size(); i++) { + if(i!=0 && i%numHorz==0) { + x = 0; + y += tileHeight+tilePadding; + } + if(y + tileHeight > boxBottom) { + x=0; + y=tilePadding+overlay.BOX_PADDING+overlay.getSearchBarYSize(); + currPage++; + maxPages = currPage+1; + } + x+=tilePadding; + + if(currPage == page) { + settingsTileConsumer.consume(boxLeft+x, y, tileWidth, tileHeight, manager.config.getOptions().get(i)); + } + + x+=tileWidth; + } + } +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/infopanes/TextInfoPane.java b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/TextInfoPane.java new file mode 100644 index 00000000..388719d0 --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/infopanes/TextInfoPane.java @@ -0,0 +1,61 @@ +package io.github.moulberry.notenoughupdates.infopanes; + +import io.github.moulberry.notenoughupdates.NEUManager; +import io.github.moulberry.notenoughupdates.NEUOverlay; +import io.github.moulberry.notenoughupdates.Utils; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.ScaledResolution; + +import java.awt.*; + +public class TextInfoPane extends ScrollableInfoPane { + + protected String title; + protected String text; + + public TextInfoPane(NEUOverlay overlay, NEUManager manager, String title, String text) { + super(overlay, manager); + this.title = title; + this.text = text; + } + + public void render(int width, int height, Color bg, Color fg, ScaledResolution scaledresolution, int mouseX, int mouseY) { + FontRenderer fr = Minecraft.getMinecraft().fontRendererObj; + + int paneWidth = (int)(width/3*overlay.getWidthMult()); + int rightSide = (int)(width*overlay.getInfoPaneOffsetFactor()); + int leftSide = rightSide - paneWidth; + + int titleLen = fr.getStringWidth(title); + int yScroll = -scrollHeight.getValue(); + fr.drawString(title, (leftSide+rightSide-titleLen)/2, yScroll+overlay.BOX_PADDING + 5, + Color.WHITE.getRGB()); + + int yOff = 20; + for(String line : text.split("\n")) { + yOff += Utils.renderStringTrimWidth(line, fr, false,leftSide+overlay.BOX_PADDING + 5, + yScroll+overlay.BOX_PADDING + 10 + yOff, + width*1/3-overlay.BOX_PADDING*2-10, Color.WHITE.getRGB(), -1); + yOff += 16; + } + + int top = overlay.BOX_PADDING - 5; + int totalBoxHeight = yOff+14; + int bottom = Math.max(top+totalBoxHeight, height-overlay.BOX_PADDING+5); + + if(scrollHeight.getValue() > top+totalBoxHeight-(height-overlay.BOX_PADDING+5)) { + scrollHeight.setValue(top+totalBoxHeight-(height-overlay.BOX_PADDING+5)); + } + drawRect(leftSide+overlay.BOX_PADDING-5, yScroll+overlay.BOX_PADDING-5, + rightSide-overlay.BOX_PADDING+5, yScroll+bottom, bg.getRGB()); + } + + public void keyboardInput() { + + } + + public void mouseInput(int width, int height, int mouseX, int mouseY, boolean mouseDown) { + super.mouseInput(width, height, mouseX, mouseY, mouseDown); + } +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/itemeditor/GuiElement.java b/src/main/java/io/github/moulberry/notenoughupdates/itemeditor/GuiElement.java new file mode 100644 index 00000000..06a8810d --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/itemeditor/GuiElement.java @@ -0,0 +1,15 @@ +package io.github.moulberry.notenoughupdates.itemeditor; + +import net.minecraft.client.gui.Gui; + +public abstract class GuiElement extends Gui { + + public abstract void render(int x, int y); + public abstract int getWidth(); + public abstract int getHeight(); + public void mouseClicked(int mouseX, int mouseY, int mouseButton) {} + public void mouseClickMove(int mouseX, int mouseY, int clickedMouseButton, long timeSinceLastClick) {} + public void otherComponentClick() {} + public void keyTyped(char typedChar, int keyCode) {} + +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/itemeditor/GuiElementButton.java b/src/main/java/io/github/moulberry/notenoughupdates/itemeditor/GuiElementButton.java new file mode 100644 index 00000000..0ed03c36 --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/itemeditor/GuiElementButton.java @@ -0,0 +1,35 @@ +package io.github.moulberry.notenoughupdates.itemeditor; + +import java.awt.*; + +public class GuiElementButton extends GuiElementText { + + private Runnable callback; + + public GuiElementButton(String text, int colour, Runnable callback) { + super(text, colour); + this.callback = callback; + } + + @Override + public int getHeight() { + return super.getHeight() + 5; + } + + @Override + public int getWidth() { + return super.getWidth() + 10; + } + + @Override + public void mouseClicked(int mouseX, int mouseY, int mouseButton) { + callback.run(); + } + + @Override + public void render(int x, int y) { + drawRect(x, y, x+getWidth(), y+super.getHeight(), Color.WHITE.getRGB()); + drawRect(x+1, y+1, x+getWidth()-1, y+super.getHeight()-1, Color.BLACK.getRGB()); + super.render(x+5, y-1); + } +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/itemeditor/GuiElementText.java b/src/main/java/io/github/moulberry/notenoughupdates/itemeditor/GuiElementText.java new file mode 100644 index 00000000..28bc9b71 --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/itemeditor/GuiElementText.java @@ -0,0 +1,42 @@ +package io.github.moulberry.notenoughupdates.itemeditor; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; + +public class GuiElementText extends GuiElement { + + protected String text; + private int colour; + + public GuiElementText(String text, int colour) { + this.text = text; + this.colour = colour; + } + + @Override + public int getHeight() { + return 18; + } + + @Override + public int getWidth() { + FontRenderer fr = Minecraft.getMinecraft().fontRendererObj; + return fr.getStringWidth(text); + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + @Override + public void render(int x, int y) { + FontRenderer fr = Minecraft.getMinecraft().fontRendererObj; + + fr.drawString(text, x, y+6, colour); + } + +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/itemeditor/GuiElementTextField.java b/src/main/java/io/github/moulberry/notenoughupdates/itemeditor/GuiElementTextField.java new file mode 100644 index 00000000..1112a882 --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/itemeditor/GuiElementTextField.java @@ -0,0 +1,453 @@ +package io.github.moulberry.notenoughupdates.itemeditor; + +import com.google.common.base.Predicate; +import io.github.moulberry.notenoughupdates.Utils; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.gui.GuiTextField; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.util.EnumChatFormatting; +import org.apache.commons.lang3.StringUtils; + +import javax.annotation.Nullable; +import java.awt.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class GuiElementTextField extends GuiElement { + + public static final int NUM_ONLY = 0b10000; + public static final int NO_SPACE = 0b01000; + public static final int FORCE_CAPS = 0b00100; + public static final int COLOUR = 0b00010; + public static final int MULTILINE = 0b00001; + + private int searchBarYSize = 20; + private int searchBarXSize = 350; + private static final int searchBarPadding = 2; + + private int options = 0; + + private boolean focus = false; + + private int x; + private int y; + + private GuiTextField textField = new GuiTextField(0, Minecraft.getMinecraft().fontRendererObj, + 0 , 0, 0, 0); + + private int customBorderColour = -1; + + public GuiElementTextField(String initialText, int options) { + textField.setFocused(true); + textField.setCanLoseFocus(false); + textField.setMaxStringLength(999); + textField.setText(initialText); + this.options = options; + } + + public void setCustomBorderColour(int colour) { + this.customBorderColour = colour; + } + + public String getText() { + return textField.getText(); + } + + public void setSize(int searchBarXSize, int searchBarYSize) { + this.searchBarXSize = searchBarXSize; + this.searchBarYSize = searchBarYSize; + } + + @Override + public String toString() { + return textField.getText(); + } + + public boolean getFocus() { + return focus; + } + + @Override + public int getHeight() { + ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft()); + int paddingUnscaled = searchBarPadding/scaledresolution.getScaleFactor(); + + int numLines = StringUtils.countMatches(textField.getText(), "\n")+1; + int extraSize = (searchBarYSize-8)/2+8; + int bottomTextBox = searchBarYSize + extraSize*(numLines-1); + + return bottomTextBox + paddingUnscaled*2; + } + + @Override + public int getWidth() { + ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft()); + int paddingUnscaled = searchBarPadding/scaledresolution.getScaleFactor(); + + return searchBarXSize + paddingUnscaled*2; + } + + public int getCursorPos(int mouseX, int mouseY) { + int xComp = mouseX - x; + int yComp = mouseY - y; + + int extraSize = (searchBarYSize-8)/2+8; + + int lineNum = Math.round(((yComp - (searchBarYSize-8)/2))/extraSize); + + Pattern patternControlCode = Pattern.compile("(?i)\\u00A7([^\\u00B6])(?!\\u00B6)"); + String text = textField.getText(); + String textNoColour = textField.getText(); + while(true) { + Matcher matcher = patternControlCode.matcher(text); + if(!matcher.find() || matcher.groupCount() < 1) break; + String code = matcher.group(1); + text = matcher.replaceFirst("\u00A7"+code+"\u00B6"+code); + } + while(true) { + Matcher matcher = patternControlCode.matcher(textNoColour); + if(!matcher.find() || matcher.groupCount() < 1) break; + String code = matcher.group(1); + textNoColour = matcher.replaceFirst("\u00B6"+code); + } + + int currentLine = 0; + int cursorIndex = 0; + for(; cursorIndex<textNoColour.length(); cursorIndex++) { + if(currentLine == lineNum) break; + if(textNoColour.charAt(cursorIndex) == '\n') { + currentLine++; + } + } + String textNC = textNoColour.substring(0, cursorIndex); + int colorCodes = StringUtils.countMatches(textNC, "\u00B6"); + String line = text.substring(cursorIndex+colorCodes*2).split("\n")[0]; + String trimmed = Minecraft.getMinecraft().fontRendererObj.trimStringToWidth(line, xComp-5); + int linePos = strLenNoColor(trimmed); + if(linePos != strLenNoColor(line)) { + char after = line.charAt(linePos); + int trimmedWidth = Minecraft.getMinecraft().fontRendererObj.getStringWidth(trimmed); + int charWidth = Minecraft.getMinecraft().fontRendererObj.getCharWidth(after); + if(trimmedWidth + charWidth/2 < xComp-5) { + linePos++; + } + } + cursorIndex += linePos; + return cursorIndex; + } + + @Override + public void mouseClicked(int mouseX, int mouseY, int mouseButton) { + if(mouseButton == 1) { + textField.setText(""); + } else { + textField.setCursorPosition(getCursorPos(mouseX, mouseY)); + } + focus = true; + } + + public void otherComponentClick() { + focus = false; + textField.setSelectionPos(textField.getCursorPosition()); + } + + public int strLenNoColor(String str) { + return str.replaceAll("(?i)\\u00A7.", "").length(); + } + + public void mouseClickMove(int mouseX, int mouseY, int clickedMouseButton, long timeSinceLastClick) { + if(focus) { + textField.setSelectionPos(getCursorPos(mouseX, mouseY)); + } + } + + @Override + public void keyTyped(char typedChar, int keyCode) { + if(focus) { + if((options & MULTILINE) != 0) { //Carriage return + Pattern patternControlCode = Pattern.compile("(?i)\\u00A7([^\\u00B6\n])(?!\\u00B6)"); + + String text = textField.getText(); + String textNoColour = textField.getText(); + while(true) { + Matcher matcher = patternControlCode.matcher(text); + if(!matcher.find() || matcher.groupCount() < 1) break; + String code = matcher.group(1); + text = matcher.replaceFirst("\u00A7"+code+"\u00B6"+code); + } + while(true) { + Matcher matcher = patternControlCode.matcher(textNoColour); + if(!matcher.find() || matcher.groupCount() < 1) break; + String code = matcher.group(1); + textNoColour = matcher.replaceFirst("\u00B6"+code); + } + + if(keyCode == 28) { + String before = textField.getText().substring(0, textField.getCursorPosition()); + String after = textField.getText().substring(textField.getCursorPosition()); + int pos = textField.getCursorPosition(); + textField.setText(before + "\n" + after); + textField.setCursorPosition(pos+1); + return; + } else if(keyCode == 200) { //Up + String textNCBeforeCursor = textNoColour.substring(0, textField.getSelectionEnd()); + int colorCodes = StringUtils.countMatches(textNCBeforeCursor, "\u00B6"); + String textBeforeCursor = text.substring(0, textField.getSelectionEnd()+colorCodes*2); + + int numLinesBeforeCursor = StringUtils.countMatches(textBeforeCursor, "\n"); + + String[] split = textBeforeCursor.split("\n"); + int textBeforeCursorWidth; + String lineBefore; + String thisLineBeforeCursor; + if(split.length == numLinesBeforeCursor && split.length > 0) { + textBeforeCursorWidth = 0; + lineBefore = split[split.length-1]; + thisLineBeforeCursor = ""; + } else if(split.length > 1) { + thisLineBeforeCursor = split[split.length-1]; + lineBefore = split[split.length-2]; + textBeforeCursorWidth = Minecraft.getMinecraft().fontRendererObj.getStringWidth(thisLineBeforeCursor); + } else { + return; + } + String trimmed = Minecraft.getMinecraft().fontRendererObj + .trimStringToWidth(lineBefore, textBeforeCursorWidth); + int linePos = strLenNoColor(trimmed); + if(linePos != strLenNoColor(lineBefore)) { + char after = lineBefore.charAt(linePos); + int trimmedWidth = Minecraft.getMinecraft().fontRendererObj.getStringWidth(trimmed); + int charWidth = Minecraft.getMinecraft().fontRendererObj.getCharWidth(after); + if(trimmedWidth + charWidth/2 < textBeforeCursorWidth) { + linePos++; + } + } + int newPos = textField.getSelectionEnd()-strLenNoColor(thisLineBeforeCursor) + -strLenNoColor(lineBefore)-1+linePos; + + if(GuiScreen.isShiftKeyDown()) { + textField.setSelectionPos(newPos); + } else { + textField.setCursorPosition(newPos); + } + } else if(keyCode == 208) { //Down + String textNCBeforeCursor = textNoColour.substring(0, textField.getSelectionEnd()); + int colorCodes = StringUtils.countMatches(textNCBeforeCursor, "\u00B6"); + String textBeforeCursor = text.substring(0, textField.getSelectionEnd()+colorCodes*2); + + int numLinesBeforeCursor = StringUtils.countMatches(textBeforeCursor, "\n"); + + String[] split = textBeforeCursor.split("\n"); + String thisLineBeforeCursor; + int textBeforeCursorWidth; + if(split.length == numLinesBeforeCursor) { + thisLineBeforeCursor = ""; + textBeforeCursorWidth = 0; + } else if(split.length > 0) { + thisLineBeforeCursor = split[split.length-1]; + textBeforeCursorWidth = Minecraft.getMinecraft().fontRendererObj.getStringWidth(thisLineBeforeCursor); + } else { + return; + } + + String[] split2 = textNoColour.split("\n"); + if(split2.length > numLinesBeforeCursor+1) { + String lineAfter = split2[numLinesBeforeCursor+1]; + String trimmed = Minecraft.getMinecraft().fontRendererObj + .trimStringToWidth(lineAfter, textBeforeCursorWidth); + int linePos = strLenNoColor(trimmed); + if(linePos != strLenNoColor(lineAfter)) { + char after = lineAfter.charAt(linePos); + int trimmedWidth = Minecraft.getMinecraft().fontRendererObj.getStringWidth(trimmed); + int charWidth = Minecraft.getMinecraft().fontRendererObj.getCharWidth(after); + if(trimmedWidth + charWidth/2 < textBeforeCursorWidth) { + linePos++; + } + } + int newPos = textField.getSelectionEnd()-strLenNoColor(thisLineBeforeCursor) + +strLenNoColor(split2[numLinesBeforeCursor])+1+linePos; + + if(GuiScreen.isShiftKeyDown()) { + textField.setSelectionPos(newPos); + } else { + textField.setCursorPosition(newPos); + } + } + } + } + + String old = textField.getText(); + if((options & FORCE_CAPS) != 0) typedChar = Character.toUpperCase(typedChar); + if((options & NO_SPACE) != 0 && typedChar == ' ') return; + + textField.textboxKeyTyped(typedChar, keyCode); + + if((options & COLOUR) != 0) { + if(typedChar == '&') { + int pos = textField.getCursorPosition()-2; + if(pos >= 0 && pos < textField.getText().length()) { + if(textField.getText().charAt(pos) == '&') { + String before = textField.getText().substring(0, pos); + String after = ""; + if(pos+2 < textField.getText().length()) { + after = textField.getText().substring(pos+2); + } + textField.setText(before + "\u00A7" + after); + textField.setCursorPosition(pos+1); + } + } + + } + } + + if((options & NUM_ONLY) != 0 && textField.getText().matches("[^0-9]")) textField.setText(old); + } + } + + public void render(int x, int y) { + this.x = x; + this.y = y; + drawTextbox(x, y, searchBarXSize, searchBarYSize, searchBarPadding, textField, focus); + } + + private void drawTextbox(int x, int y, int searchBarXSize, int searchBarYSize, int searchBarPadding, + GuiTextField textField, boolean focus) { + ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft()); + + GlStateManager.disableLighting(); + + /** + * Search bar + */ + int paddingUnscaled = searchBarPadding/scaledresolution.getScaleFactor(); + if(paddingUnscaled < 1) paddingUnscaled = 1; + + int numLines = StringUtils.countMatches(textField.getText(), "\n")+1; + int extraSize = (searchBarYSize-8)/2+8; + int bottomTextBox = y + searchBarYSize + extraSize*(numLines-1); + + int borderColour = focus ? Color.GREEN.getRGB() : Color.WHITE.getRGB(); + if(customBorderColour != -1) { + borderColour = customBorderColour; + } + //bar background + drawRect(x - paddingUnscaled, + y - paddingUnscaled, + x + searchBarXSize + paddingUnscaled, + bottomTextBox + paddingUnscaled, borderColour); + drawRect(x, + y, + x + searchBarXSize, + bottomTextBox, Color.BLACK.getRGB()); + + //bar text + Pattern patternControlCode = Pattern.compile("(?i)\\u00A7([^\\u00B6\n])(?!\\u00B6)"); + + String text = textField.getText(); + String textNoColor = textField.getText(); + while(true) { + Matcher matcher = patternControlCode.matcher(text); + if(!matcher.find() || matcher.groupCount() < 1) break; + String code = matcher.group(1); + text = matcher.replaceFirst("\u00A7"+code+"\u00B6"+code); + } + while(true) { + Matcher matcher = patternControlCode.matcher(textNoColor); + if(!matcher.find() || matcher.groupCount() < 1) break; + String code = matcher.group(1); + textNoColor = matcher.replaceFirst("\u00B6"+code); + } + + String[] texts = text.split("\n"); + for(int yOffI = 0; yOffI < texts.length; yOffI++) { + int yOff = yOffI*extraSize; + + Minecraft.getMinecraft().fontRendererObj.drawString(Utils.trimToWidth(texts[yOffI], searchBarXSize-10), x + 5, + y+(searchBarYSize-8)/2+yOff, Color.WHITE.getRGB()); + } + + if(focus && System.currentTimeMillis()%1000>500) { + String textNCBeforeCursor = textNoColor.substring(0, textField.getCursorPosition()); + int colorCodes = StringUtils.countMatches(textNCBeforeCursor, "\u00B6"); + String textBeforeCursor = text.substring(0, textField.getCursorPosition()+colorCodes*2); + + int numLinesBeforeCursor = StringUtils.countMatches(textBeforeCursor, "\n"); + int yOff = numLinesBeforeCursor*extraSize; + + String[] split = textBeforeCursor.split("\n"); + int textBeforeCursorWidth; + if(split.length <= numLinesBeforeCursor || split.length == 0) { + textBeforeCursorWidth = 0; + } else { + textBeforeCursorWidth = Minecraft.getMinecraft().fontRendererObj.getStringWidth(split[split.length-1]); + } + drawRect(x + 5 + textBeforeCursorWidth, + y+(searchBarYSize-8)/2-1 + yOff, + x + 5 + textBeforeCursorWidth+1, + y+(searchBarYSize-8)/2+9 + yOff, Color.WHITE.getRGB()); + } + + String selectedText = textField.getSelectedText(); + if(!selectedText.isEmpty()) { + int leftIndex = textField.getCursorPosition() < textField.getSelectionEnd() ? + textField.getCursorPosition() : textField.getSelectionEnd(); + int rightIndex = textField.getCursorPosition() > textField.getSelectionEnd() ? + textField.getCursorPosition() : textField.getSelectionEnd(); + + int texX = 0; + int texY = 0; + boolean sectionSignPrev = false; + boolean bold = false; + for(int i=0; i<textNoColor.length(); i++) { + char c = textNoColor.charAt(i); + if(sectionSignPrev) { + if(c != 'k' && c != 'K' + && c != 'm' && c != 'M' + && c != 'n' && c != 'N' + && c != 'o' && c != 'O') { + bold = c == 'l' || c == 'L'; + } + } + sectionSignPrev = false; + if(c == '\u00B6') sectionSignPrev = true; + + if(c == '\n') { + if(i >= leftIndex && i < rightIndex) { + drawRect(x + 5 + texX, + y+(searchBarYSize-8)/2-1 + texY, + x + 5 + texX + 3, + y+(searchBarYSize-8)/2+9 + texY, Color.LIGHT_GRAY.getRGB()); + } + + texX = 0; + texY += extraSize; + continue; + } + + //String c2 = bold ? EnumChatFormatting.BOLD.toString() : "" + c; + + int len = Minecraft.getMinecraft().fontRendererObj.getStringWidth(String.valueOf(c)); + if(bold) len++; + if(i >= leftIndex && i < rightIndex) { + drawRect(x + 5 + texX, + y+(searchBarYSize-8)/2-1 + texY, + x + 5 + texX + len, + y+(searchBarYSize-8)/2+9 + texY, Color.LIGHT_GRAY.getRGB()); + + Minecraft.getMinecraft().fontRendererObj.drawString(String.valueOf(c), + x + 5 + texX, + y+(searchBarYSize-8)/2 + texY, Color.BLACK.getRGB()); + if(bold) { + Minecraft.getMinecraft().fontRendererObj.drawString(String.valueOf(c), + x + 5 + texX +1, + y+(searchBarYSize-8)/2 + texY, Color.BLACK.getRGB()); + } + } + + texX += len; + } + } + } +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/itemeditor/NEUItemEditor.java b/src/main/java/io/github/moulberry/notenoughupdates/itemeditor/NEUItemEditor.java new file mode 100644 index 00000000..5591fcb9 --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/itemeditor/NEUItemEditor.java @@ -0,0 +1,429 @@ +package io.github.moulberry.notenoughupdates.itemeditor; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import io.github.moulberry.notenoughupdates.util.LerpingInteger; +import io.github.moulberry.notenoughupdates.NEUManager; +import io.github.moulberry.notenoughupdates.Utils; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.*; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.RenderHelper; +import net.minecraft.client.renderer.entity.RenderItem; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.*; +import net.minecraft.util.ResourceLocation; +import org.lwjgl.input.Keyboard; +import org.lwjgl.input.Mouse; +import org.lwjgl.opengl.GL11; + +import java.awt.*; +import java.io.IOException; +import java.util.*; +import java.util.List; +import java.util.function.Supplier; +import static io.github.moulberry.notenoughupdates.itemeditor.GuiElementTextField.*; + +public class NEUItemEditor extends GuiScreen { + + private NEUManager manager; + + private List<GuiElement> options = new ArrayList<>(); + private List<GuiElement> rightOptions = new ArrayList<>(); + + private JsonObject item; + + private static final int PADDING = 10; + private static final int SCROLL_AMOUNT = 20; + + private LerpingInteger scrollHeight = new LerpingInteger(0); + + private Supplier<String> internalname; + private Supplier<String> itemid; + private Supplier<String> displayname; + private Supplier<String> lore; + private Supplier<String> crafttext; + private Supplier<String> infoType; + private Supplier<String> info; + private Supplier<String> clickcommand; + private Supplier<String> damage; + private NBTTagCompound nbttag; + + public NEUItemEditor(NEUManager manager, String internalname, JsonObject item) { + this.manager = manager; + this.item = item; + + if(item.has("nbttag")) { + try { + nbttag = JsonToNBT.getTagFromJson(item.get("nbttag").getAsString()); + } catch(NBTException e) {} + } + + internalname = internalname == null ? "" : internalname; + options.add(new GuiElementText("Internal Name: ", Color.WHITE.getRGB())); + this.internalname = addTextFieldWithSupplier(internalname, NO_SPACE | FORCE_CAPS); + + options.add(new GuiElementText("Item ID: ", Color.WHITE.getRGB())); + String itemid = item.has("itemid") ? item.get("itemid").getAsString() : ""; + this.itemid = addTextFieldWithSupplier(itemid, NO_SPACE); + + options.add(new GuiElementText("Display name: ", Color.WHITE.getRGB())); + String displayname = item.has("displayname") ? item.get("displayname").getAsString() : ""; + this.displayname = addTextFieldWithSupplier(displayname, COLOUR); + + options.add(new GuiElementText("Lore: ", Color.WHITE.getRGB())); + JsonArray lore = item.has("lore") ? item.get("lore").getAsJsonArray() : new JsonArray(); + String[] loreA = new String[lore.size()]; + for(int i=0; i<lore.size(); i++) loreA[i] = lore.get(i).getAsString(); + this.lore = addTextFieldWithSupplier(String.join("\n", loreA), COLOUR | MULTILINE); + + options.add(new GuiElementText("Craft text: ", Color.WHITE.getRGB())); + String crafttext = item.has("crafttext") ? item.get("crafttext").getAsString() : ""; + this.crafttext = addTextFieldWithSupplier(crafttext, COLOUR); + + options.add(new GuiElementText("Info type: ", Color.WHITE.getRGB())); + String infoType = item.has("infoType") ? item.get("infoType").getAsString() : ""; + this.infoType = addTextFieldWithSupplier(infoType, NO_SPACE | FORCE_CAPS); + + options.add(new GuiElementText("Additional information: ", Color.WHITE.getRGB())); + JsonArray info = item.has("info") ? item.get("info").getAsJsonArray() : new JsonArray(); + String[] infoA = new String[info.size()]; + for(int i=0; i<info.size(); i++) infoA[i] = info.get(i).getAsString(); + this.info = addTextFieldWithSupplier(String.join("\n", infoA), COLOUR | MULTILINE); + + options.add(new GuiElementText("Click-command (viewrecipe or viewpotion): ", Color.WHITE.getRGB())); + String clickcommand = item.has("clickcommand") ? item.get("clickcommand").getAsString() : ""; + this.clickcommand = addTextFieldWithSupplier(clickcommand, NO_SPACE); + + options.add(new GuiElementText("Damage: ", Color.WHITE.getRGB())); + String damage = item.has("damage") ? item.get("damage").getAsString() : ""; + this.damage = addTextFieldWithSupplier(damage, NO_SPACE | NUM_ONLY); + + rightOptions.add(new GuiElementButton("Close (discards changes)", Color.LIGHT_GRAY.getRGB(), () -> { + Minecraft.getMinecraft().displayGuiScreen(null); + })); + GuiElementButton button = new Object() { //Used to make the compiler shut the fuck up + GuiElementButton b = new GuiElementButton("Save to local disk", Color.GREEN.getRGB(), new Runnable() { + public void run() { + if(save()) { + b.setText("Save to local disk (SUCCESS)"); + } else { + b.setText("Save to local disk (FAILED)"); + } + } + }); + }.b; + rightOptions.add(button); + + button = new Object() { //Used to make the compiler shut the fuck up + GuiElementButton b = new GuiElementButton("Upload", Color.YELLOW.getRGB(), new Runnable() { + public void run() { + if(b.getText().equals("Upload")) { + b.setText("Confirm upload?"); + } else { + if(upload()) { + b.setText("Uploaded"); + } else { + b.setText("Upload failed."); + } + } + } + }); + }.b; + rightOptions.add(button); + + rightOptions.add(new GuiElementText("", Color.WHITE.getRGB())); + + rightOptions.add(new GuiElementButton("Remove enchants", Color.RED.getRGB(), () -> { + nbttag.removeTag("ench"); + nbttag.getCompoundTag("ExtraAttributes").removeTag("enchantments"); + })); + rightOptions.add(new GuiElementButton("Add enchant glint", Color.ORANGE.getRGB(), () -> { + nbttag.setTag("ench", new NBTTagList()); + })); + + resetScrollToTop(); + } + + public boolean save() { + int damageI = 0; + try { + damageI = Integer.valueOf(damage.get()); + } catch(NumberFormatException e) {} + resyncNbttag(); + String[] infoA = info.get().trim().split("\n"); + if(infoA.length == 0 || infoA[0].isEmpty()) { + infoA = new String[0]; + } + return manager.writeItemJson(item, internalname.get(), itemid.get(), displayname.get(), lore.get().split("\n"), + crafttext.get(), infoType.get(), infoA, clickcommand.get(), damageI, nbttag); + } + + public boolean upload() { + int damageI = 0; + try { + damageI = Integer.valueOf(damage.get()); + } catch(NumberFormatException e) {} + resyncNbttag(); + String[] infoA = info.get().trim().split("\n"); + if(infoA.length == 0 || infoA[0].isEmpty()) { + infoA = new String[0]; + } + return manager.uploadItemJson(internalname.get(), itemid.get(), displayname.get(), lore.get().split("\n"), + crafttext.get(), infoType.get(), infoA, clickcommand.get(), damageI, nbttag); + } + + public void onGuiClosed() { + Keyboard.enableRepeatEvents(false); + } + + public Supplier<String> addTextFieldWithSupplier(String initialText, int options) { + GuiElementTextField textField = new GuiElementTextField(initialText, options); + this.options.add(textField); + return () -> textField.toString(); + } + + public void resyncNbttag() { + if(nbttag == null) nbttag = new NBTTagCompound(); + + //Item lore + NBTTagList list = new NBTTagList(); + for(String lore : this.lore.get().split("\n")) { + list.appendTag(new NBTTagString(lore)); + } + + NBTTagCompound display = nbttag.getCompoundTag("display"); + display.setTag("Lore", list); + + //Name + display.setString("Name", displayname.get()); + nbttag.setTag("display", display); + + //Internal ID + NBTTagCompound ea = nbttag.getCompoundTag("ExtraAttributes"); + ea.setString("id", internalname.get()); + nbttag.setTag("ExtraAttributes", ea); + } + + public void resetScrollToTop() { + int totalHeight = PADDING; + for(GuiElement gui : options) { + totalHeight += gui.getHeight(); + } + + ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft()); + int height = scaledresolution.getScaledHeight(); + + scrollHeight.setValue(totalHeight-height+PADDING); + } + + public int calculateYScroll() { + ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft()); + int height = scaledresolution.getScaledHeight(); + + int totalHeight = PADDING; + for(GuiElement gui : options) { + totalHeight += gui.getHeight(); + } + + if(scrollHeight.getValue() < 0) scrollHeight.setValue(0); + + int yScroll = 0; + if(totalHeight > height-PADDING) { + yScroll = totalHeight-height+PADDING-scrollHeight.getValue(); + } else { + scrollHeight.setValue(0); + } + if(yScroll < 0) { + yScroll = 0; + scrollHeight.setValue(totalHeight-height+PADDING); + } + + return yScroll; + } + + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks) { + scrollHeight.tick(); + + ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft()); + int width = scaledresolution.getScaledWidth(); + int height = scaledresolution.getScaledHeight(); + + GlStateManager.disableLighting(); + + Color backgroundColour = new Color(10, 10, 10, 240); + drawRect(0, 0, width, height, backgroundColour.getRGB()); + + int yScroll = calculateYScroll(); + if(yScroll > 0){ + //Render scroll bar + } + + int currentY = PADDING-yScroll; + for(GuiElement gui : options) { + gui.render(PADDING, currentY); + currentY += gui.getHeight(); + } + + currentY = PADDING; + for(GuiElement gui : rightOptions) { + gui.render(width-PADDING-gui.getWidth(), currentY); + currentY += gui.getHeight(); + } + + int itemX = 424; + int itemY = 32; + int itemSize = 128; + Color itemBorder = new Color(100, 50, 150, 255); + Color itemBackground = new Color(120, 120, 120, 255); + drawRect(itemX-10, itemY-10, itemX+itemSize+10, itemY+itemSize+10, Color.DARK_GRAY.getRGB()); + drawRect(itemX-9, itemY-9, itemX+itemSize+9, itemY+itemSize+9, itemBorder.getRGB()); + drawRect(itemX-6, itemY-6, itemX+itemSize+6, itemY+itemSize+6, Color.DARK_GRAY.getRGB()); + drawRect(itemX-5, itemY-5, itemX+itemSize+5, itemY+itemSize+5, itemBackground.getRGB()); + ItemStack stack = new ItemStack(Item.itemRegistry.getObject(new ResourceLocation(itemid.get()))); + + if(stack.getItem() != null) { + try { + stack.setItemDamage(Integer.valueOf(damage.get())); + } catch(NumberFormatException e) {} + + resyncNbttag(); + stack.setTagCompound(nbttag); + + int scaleFactor = itemSize/16; + GL11.glPushMatrix(); + GlStateManager.scale(scaleFactor, scaleFactor, 1); + drawItemStack(stack, itemX/scaleFactor, itemY/scaleFactor, null); + GL11.glPopMatrix(); + } + + //Tooltip + List<String> text = new ArrayList<>(); + text.add(displayname.get()); + text.addAll(Arrays.asList(lore.get().split("\n"))); + + Utils.drawHoveringText(text, itemX-20, itemY+itemSize+28, width, height, -1, + Minecraft.getMinecraft().fontRendererObj); + + GlStateManager.disableLighting(); + } + + @Override + protected void keyTyped(char typedChar, int keyCode) { + for(GuiElement gui : options) { + gui.keyTyped(typedChar, keyCode); + } + } + + @Override + protected void mouseClickMove(int mouseX, int mouseY, int clickedMouseButton, long timeSinceLastClick) { + ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft()); + int width = scaledresolution.getScaledWidth(); + + int yScroll = calculateYScroll(); + int currentY = PADDING-yScroll; + for(GuiElement gui : options) { + if(mouseY > currentY && mouseY < currentY+gui.getHeight() + && mouseX < gui.getWidth()) { + gui.mouseClickMove(mouseX, mouseY, clickedMouseButton, timeSinceLastClick); + return; + } + currentY += gui.getHeight(); + } + + currentY = PADDING; + for(GuiElement gui : rightOptions) { + if(mouseY > currentY && mouseY < currentY+gui.getHeight() + && mouseX > width-PADDING-gui.getWidth()) { + gui.mouseClickMove(mouseX, mouseY, clickedMouseButton, timeSinceLastClick); + return; + } + currentY += gui.getHeight(); + } + } + + @Override + public void handleMouseInput() throws IOException { + ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft()); + + int maxWidth = 0; + for(GuiElement gui : options) { + if(gui.getWidth() > maxWidth) maxWidth = gui.getWidth(); + } + + if(Mouse.getX() < maxWidth*scaledresolution.getScaleFactor()) { + int dWheel = Mouse.getEventDWheel(); + + if(dWheel < 0) { + scrollHeight.setTarget(scrollHeight.getTarget()-SCROLL_AMOUNT); + scrollHeight.resetTimer(); + } else if(dWheel > 0) { + scrollHeight.setTarget(scrollHeight.getTarget()+SCROLL_AMOUNT); + scrollHeight.resetTimer(); + } + } + + super.handleMouseInput(); + } + + @Override + protected void mouseClicked(int mouseX, int mouseY, int mouseButton) { + ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft()); + int width = scaledresolution.getScaledWidth(); + + int yScroll = calculateYScroll(); + int currentY = PADDING-yScroll; + for(GuiElement gui : options) { + if(mouseY > currentY && mouseY < currentY+gui.getHeight() + && mouseX < gui.getWidth()) { + gui.mouseClicked(mouseX, mouseY, mouseButton); + for(GuiElement gui2 : options) { + if(gui2 != gui) { + gui2.otherComponentClick(); + } + } + for(GuiElement gui2 : rightOptions) { + if(gui2 != gui) { + gui2.otherComponentClick(); + } + } + return; + } + currentY += gui.getHeight(); + } + + currentY = PADDING; + for(GuiElement gui : rightOptions) { + if(mouseY > currentY && mouseY < currentY+gui.getHeight() + && mouseX > width-PADDING-gui.getWidth()) { + gui.mouseClicked(mouseX, mouseY, mouseButton); + for(GuiElement gui2 : options) { + if(gui2 != gui) { + gui2.otherComponentClick(); + } + } + for(GuiElement gui2 : rightOptions) { + if(gui2 != gui) { + gui2.otherComponentClick(); + } + } + return; + } + currentY += gui.getHeight(); + } + } + + private void drawItemStack(ItemStack stack, int x, int y, String altText) { + RenderItem itemRender = Minecraft.getMinecraft().getRenderItem(); + FontRenderer font = Minecraft.getMinecraft().fontRendererObj; + + RenderHelper.enableGUIStandardItemLighting(); + itemRender.renderItemAndEffectIntoGUI(stack, x, y); + RenderHelper.disableStandardItemLighting(); + + itemRender.renderItemOverlayIntoGUI(font, stack, x, y, altText); + } + +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinItemStack.java b/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinItemStack.java new file mode 100644 index 00000000..4a713218 --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinItemStack.java @@ -0,0 +1,20 @@ +package io.github.moulberry.notenoughupdates.mixins; + +import io.github.moulberry.notenoughupdates.Utils; +import net.minecraft.item.ItemStack; +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.CallbackInfoReturnable; + +@Mixin({ItemStack.class}) +public class MixinItemStack { + + @Inject(method="hasEffect", at=@At("HEAD"), cancellable = true) + public void hasEffect(CallbackInfoReturnable cir) { + if(Utils.getHasEffectOverride()) { + cir.setReturnValue(false); + } + } + +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/options/Options.java b/src/main/java/io/github/moulberry/notenoughupdates/options/Options.java new file mode 100644 index 00000000..6c7a70a0 --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/options/Options.java @@ -0,0 +1,203 @@ +package io.github.moulberry.notenoughupdates.options; + +import com.google.common.collect.Lists; +import com.google.gson.*; +import com.google.gson.annotations.Expose; +import io.github.moulberry.notenoughupdates.Utils; + +import java.io.*; +import java.lang.reflect.Field; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +public class Options { + + public Option<Boolean> enableItemEditing = new Option( + false, + "Enable Item Editing", + true, + "Dev Feature. Please don't use."); + public Option<Boolean> onlyShowOnSkyblock = new Option( + true, + "Only Show On Skyblock", + false, + "GUI Overlay only appears when you are playing Skyblock."); + public Option<Boolean> advancedPriceInfo = new Option( + false, + "Advanced Price Information", + false, + "Shows some extra information about item sales."); + public Option<Boolean> cacheRenderedItempane = new Option( + true, + "Cache Rendered Itempane", + false, + "Caches the drawn itempane, drastically improving performance. However, animated textures will not work."); + public Option<Double> bgBlurFactor = new Option( + 5.0, + "Background Blur Factor", + false, + "Changes the strength of pane background blur. 0-50."); + public Option<String> apiKey = new Option( + "", + "Api key used for certain features.", + false, + "Type /api new to receive key and put it here."); + public Option<Boolean> autoupdate = new Option( + true, + "Automatically Update Items", + false, + "If true, updated items will automatically download from the remote repository when you start the game. \nHIGHLY RECOMMENDED."); + public Option<Boolean> keepopen = new Option( + false, + "Keep Itempane Open", + false, + "If true, the itempane will stay open after the gui is closed."); + public Option<Boolean> itemStyle = new Option( + true, + "Circular Item BG Style", + false, + "If true, uses the circular item background style instead of the square style."); + public Option<Double> paneWidthMult = new Option( + 1.0, + "Pane Width Multiplier", + false, + "Changes how wide the item and info panes are. Value between 0.5-1.5."); + public Option<Double> bgOpacity = new Option( + 50.0, + "Pane Background Opacity", + false, + "Changes the background colour opacity of item and info panes. Value between 0-255."); + public Option<Double> fgOpacity = new Option( + 255.0, + "Item Background Opacity", + false, + "Changes the opacity of item background. Value between 0-255."); + + /** + * OPTIONS THAT DON'T SHOW IN GUI + */ + public Option<Boolean> dev = new Option( + false, + "Show Dev Options", + true, + "Dev Feature. Please don't use."); + public Option<Double> compareMode = new Option( + 0.0, + "Compare Mode", + false, + "Compare Mode"); + public Option<Double> sortMode = new Option( + 0.0, + "Sort Mode", + false, + "Sort Mode"); + public Option<ArrayList<Boolean>> compareAscending = new Option( + Utils.createList(true, true), + "Compare Ascending", + false, + "Compare Ascending"); + public Option<ArrayList<String>> favourites = new Option( + new ArrayList<String>(), + "Favourites", + false, + "Favourites"); + + public List<Option> getOptions() { + List<Option> options = new ArrayList<>(); + + tryAddOption(enableItemEditing, options); + tryAddOption(onlyShowOnSkyblock, options); + tryAddOption(advancedPriceInfo, options); + tryAddOption(cacheRenderedItempane, options); + tryAddOption(autoupdate, options); + tryAddOption(keepopen, options); + tryAddOption(itemStyle, options); + tryAddOption(bgBlurFactor, options); + tryAddOption(apiKey, options); + tryAddOption(paneWidthMult, options); + tryAddOption(bgOpacity, options); + tryAddOption(fgOpacity, options); + + return options; + } + + private void tryAddOption(Option<?> option, List<Option> list) { + if(!option.secret || dev.value) { + list.add(option); + } + } + + public static class Option<T> implements Serializable { + public T value; + public final transient T defaultValue; + public final transient String displayName; + public final transient boolean secret; + public final transient String desc; + + public Option(T defaultValue, String displayName, boolean secret, String desc) { + this.value = defaultValue; + this.defaultValue = defaultValue; + this.displayName = displayName; + this.secret = secret; + this.desc = desc; + } + + public void setValue(String value) { + if(this.value instanceof Boolean) { + ((Option<Boolean>) this).value = Boolean.valueOf(value); + } else if(this.value instanceof Double) { + ((Option<Double>)this).value = Double.valueOf(value); + } else if(this.value instanceof String) { + ((Option<String>)this).value = value; + } + } + } + + public static JsonSerializer<Option<?>> createSerializer() { + return (src, typeOfSrc, context) -> { + if(src.secret && src.defaultValue.equals(src.value)) { + return null; + } + return context.serialize(src.value); + }; + } + + public static JsonDeserializer<Option<?>> createDeserializer() { + return (json, typeOfT, context) -> { + try { + return new Option(context.deserialize(json, Object.class), "unknown", false, "unknown"); + } catch(Exception e) { + return null; + } + }; + } + + public static Options loadFromFile(Gson gson, File file) throws IOException { + InputStream in = new FileInputStream(file); + BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); + + Options oLoad = gson.fromJson(reader, Options.class); + Options oDefault = new Options(); + if(oLoad == null) return oDefault; + + for(Field f : Options.class.getDeclaredFields()) { + try { + ((Option)f.get(oDefault)).value = ((Option)f.get(oLoad)).value; + } catch (Exception e) { } + } + return oDefault; + } + + public void saveToFile(Gson gson, File file) throws IOException { + file.createNewFile(); + + try(BufferedWriter writer = new BufferedWriter( + new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))) { + writer.write(gson.toJson(this)); + } + } + + +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/util/HTMLParagraphView.java b/src/main/java/io/github/moulberry/notenoughupdates/util/HTMLParagraphView.java new file mode 100644 index 00000000..ab4be4a7 --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/util/HTMLParagraphView.java @@ -0,0 +1,30 @@ +package io.github.moulberry.notenoughupdates.util; + +import javax.swing.text.Element; +import javax.swing.text.FlowView; +import javax.swing.text.View; +import javax.swing.text.html.ParagraphView; + +class HTMLParagraphView extends ParagraphView { + + public static int MAX_VIEW_SIZE=100; + + public HTMLParagraphView(Element elem) { + super(elem); + strategy = new HTMLParagraphView.HTMLFlowStrategy(); + } + + public static class HTMLFlowStrategy extends FlowStrategy { + protected View createView(FlowView fv, int startOffset, int spanLeft, int rowIndex) { + View res=super.createView(fv, startOffset, spanLeft, rowIndex); + if (res.getEndOffset()-res.getStartOffset()> MAX_VIEW_SIZE) { + res = res.createFragment(startOffset, startOffset+ MAX_VIEW_SIZE); + } + return res; + } + + } + public int getResizeWeight(int axis) { + return 0; + } +}
\ No newline at end of file diff --git a/src/main/java/io/github/moulberry/notenoughupdates/util/HtmlImageGenerator.java b/src/main/java/io/github/moulberry/notenoughupdates/util/HtmlImageGenerator.java new file mode 100644 index 00000000..40866a00 --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/util/HtmlImageGenerator.java @@ -0,0 +1,125 @@ +// +// Source code recreated from a .class file by IntelliJ IDEA +// (powered by Fernflower decompiler) +// +package io.github.moulberry.notenoughupdates.util; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.net.URL; +import java.util.Iterator; +import java.util.List; +import java.util.Map.Entry; +import javax.imageio.ImageIO; +import javax.swing.JEditorPane; +import javax.swing.text.html.HTMLEditorKit; +import javax.swing.text.html.ImageView; +import javax.swing.text.html.StyleSheet; + +public class HtmlImageGenerator { + public JEditorPane editorPane = this.createJEditorPane(); + static final Dimension DEFAULT_SIZE = new Dimension(800, 800); + + public HtmlImageGenerator() { + } + + public ComponentOrientation getOrientation() { + return this.editorPane.getComponentOrientation(); + } + + public void setOrientation(ComponentOrientation orientation) { + this.editorPane.setComponentOrientation(orientation); + } + + public Dimension getSize() { + return this.editorPane.getSize(); + } + + public void setSize(Dimension dimension) { + this.editorPane.setSize(dimension); + } + + public void loadUrl(URL url) { + try { + this.editorPane.setPage(url); + } catch (IOException var3) { + throw new RuntimeException(String.format("Exception while loading %s", url), var3); + } + } + + public void loadUrl(String url) { + try { + this.editorPane.setPage(url); + } catch (IOException var3) { + throw new RuntimeException(String.format("Exception while loading %s", url), var3); + } + } + + public void loadHtml(String html) { + this.editorPane.setText(html); + this.onDocumentLoad(); + } + + public void saveAsImage(String file) { + this.saveAsImage(new File(file)); + } + + public void saveAsImage(File file) { + BufferedImage img = this.getBufferedImage(); + + try { + ImageIO.write(img, "png", file); + } catch (IOException var4) { + throw new RuntimeException(String.format("Exception while saving '%s' image", file), var4); + } + } + + protected void onDocumentLoad() { + } + + public Dimension getDefaultSize() { + return DEFAULT_SIZE; + } + + public BufferedImage getBufferedImage() { + Dimension prefSize = this.editorPane.getPreferredSize(); + BufferedImage img = new BufferedImage(prefSize.width, this.editorPane.getPreferredSize().height, 2); + Graphics graphics = img.getGraphics(); + this.editorPane.setSize(prefSize); + this.editorPane.paint(graphics); + return img; + } + + public void addCss(String css) { + HTMLEditorKit kit = (HTMLEditorKit) editorPane.getEditorKitForContentType("text/html"); + kit.getStyleSheet().addRule(css); + } + + public void setScale(float factor) { + editorPane.getDocument().putProperty("ZOOM_FACTOR", new Double(factor)); + } + + protected JEditorPane createJEditorPane() { + JEditorPane editorPane = new JEditorPane(); + editorPane.setSize(this.getDefaultSize()); + editorPane.setEditable(false); + HTMLEditorKit kit = new LargeHTMLEditorKit(); + editorPane.setEditorKitForContentType("text/html", kit); + editorPane.setBackground(new Color(0, true)); + editorPane.setContentType("text/html"); + editorPane.addPropertyChangeListener(new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getPropertyName().equals("page")) { + HtmlImageGenerator.this.onDocumentLoad(); + } + + } + }); + return editorPane; + } +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/util/HypixelApi.java b/src/main/java/io/github/moulberry/notenoughupdates/util/HypixelApi.java new file mode 100644 index 00000000..a39da281 --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/util/HypixelApi.java @@ -0,0 +1,68 @@ +package io.github.moulberry.notenoughupdates.util; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.net.URLConnection; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.function.Consumer; + +public class HypixelApi { + + private Gson gson = new Gson(); + private ExecutorService es = Executors.newSingleThreadExecutor(); + + //https://api.hypixel.net/skyblock/profiles?key=06b20aa2-ff04-4851-9687-669c8111526f&uuid=d0e05de7-6067-454d-beae-c6d19d886191 + public void getHypixelApiAsync(String apiKey, String method, HashMap<String, String> args, Consumer<JsonObject> consumer) { + getHypixelApiAsync(generateApiUrl(apiKey, method, args), consumer); + } + + public void getHypixelApiAsync(String urlS, Consumer<JsonObject> consumer) { + es.submit(() -> { + consumer.accept(getHypixelApiSync(urlS)); + }); + } + + public JsonObject getHypixelApiSync(String urlS) { + URLConnection connection; + try { + URL url = new URL(urlS); + connection = url.openConnection(); + connection.setConnectTimeout(3000); + } catch(IOException e) { + return null; + } + + try(BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { + StringBuilder builder = new StringBuilder(); + int codePoint; + while((codePoint = reader.read()) != -1) { + builder.append(((char)codePoint)); + } + String response = builder.toString(); + + JsonObject json = gson.fromJson(response, JsonObject.class); + return json; + } catch(IOException e) { + return null; + } + } + + public String generateApiUrl(String apiKey, String method, HashMap<String, String> args) { + String url = "https://api.hypixel.net/" + method + "?key=" + apiKey; + for(Map.Entry<String, String> entry : args.entrySet()) { + url += "&" + entry.getKey() + "=" + entry.getValue(); + } + return url; + } + +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/util/LargeHTMLEditorKit.java b/src/main/java/io/github/moulberry/notenoughupdates/util/LargeHTMLEditorKit.java new file mode 100644 index 00000000..7b69a541 --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/util/LargeHTMLEditorKit.java @@ -0,0 +1,132 @@ +package io.github.moulberry.notenoughupdates.util; + +import javax.swing.text.*; +import javax.swing.text.html.*; +import java.awt.*; +import java.awt.geom.AffineTransform; + +public class LargeHTMLEditorKit extends HTMLEditorKit { + + ViewFactory factory = new MyViewFactory(); + + @Override + public ViewFactory getViewFactory() { + return factory; + } + + public Document createDefaultDocument() { + HTMLDocument doc = (HTMLDocument)super.createDefaultDocument(); + doc.setAsynchronousLoadPriority(-1); + return doc; + } + + class MyViewFactory extends HTMLFactory { + @Override + public View create(Element elem) { + AttributeSet attrs = elem.getAttributes(); + Object elementName = attrs.getAttribute(AbstractDocument.ElementNameAttribute); + Object o = (elementName != null) ? null : attrs.getAttribute(StyleConstants.NameAttribute); + if (o instanceof HTML.Tag) { + HTML.Tag kind = (HTML.Tag) o; + if (kind == HTML.Tag.HTML) { + return new HTMLBlockView(elem); + } + } + View view = super.create(elem); + if(view instanceof ImageView) { + //((ImageView)view).setLoadsSynchronously(true); + } + return view; + } + + } + + + private class HTMLBlockView extends BlockView { + + public HTMLBlockView(Element elem) { + super(elem, View.Y_AXIS); + } + + @Override + protected void layout(int width, int height) { + if (width<Integer.MAX_VALUE) { + super.layout(new Double(width / getZoomFactor()).intValue(), + new Double(height * + getZoomFactor()).intValue()); + } + } + + public double getZoomFactor() { + Double scale = (Double) getDocument().getProperty("ZOOM_FACTOR"); + if (scale != null) { + return scale.doubleValue(); + } + + return 1; + } + + @Override + public void paint(Graphics g, Shape allocation) { + Graphics2D g2d = (Graphics2D) g; + double zoomFactor = getZoomFactor(); + AffineTransform old = g2d.getTransform(); + g2d.scale(zoomFactor, zoomFactor); + super.paint(g2d, allocation); + g2d.setTransform(old); + } + + @Override + public float getMinimumSpan(int axis) { + float f = super.getMinimumSpan(axis); + f *= getZoomFactor(); + return f; + } + + @Override + public float getMaximumSpan(int axis) { + float f = super.getMaximumSpan(axis); + f *= getZoomFactor(); + return f; + } + + @Override + public float getPreferredSpan(int axis) { + float f = super.getPreferredSpan(axis); + f *= getZoomFactor(); + return f; + } + + @Override + public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException { + double zoomFactor = getZoomFactor(); + Rectangle alloc; + alloc = a.getBounds(); + Shape s = super.modelToView(pos, alloc, b); + alloc = s.getBounds(); + alloc.x *= zoomFactor; + alloc.y *= zoomFactor; + alloc.width *= zoomFactor; + alloc.height *= zoomFactor; + + return alloc; + } + + @Override + public int viewToModel(float x, float y, Shape a, + Position.Bias[] bias) { + double zoomFactor = getZoomFactor(); + Rectangle alloc = a.getBounds(); + x /= zoomFactor; + y /= zoomFactor; + alloc.x /= zoomFactor; + alloc.y /= zoomFactor; + alloc.width /= zoomFactor; + alloc.height /= zoomFactor; + + return super.viewToModel(x, y, alloc, bias); + } + + } + +}
\ No newline at end of file diff --git a/src/main/java/io/github/moulberry/notenoughupdates/util/LerpingFloat.java b/src/main/java/io/github/moulberry/notenoughupdates/util/LerpingFloat.java new file mode 100644 index 00000000..83fa1a6d --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/util/LerpingFloat.java @@ -0,0 +1,68 @@ +package io.github.moulberry.notenoughupdates.util; + +public class LerpingFloat { + + private int timeSpent; + private long lastMillis; + private int timeToReachTarget; + + private float targetValue; + private float lerpValue; + + public LerpingFloat(float initialValue, int timeToReachTarget) { + this.targetValue = this.lerpValue = initialValue; + this.timeToReachTarget = timeToReachTarget; + } + + public LerpingFloat(int initialValue) { + this(initialValue, 200); + } + + public void tick() { + int lastTimeSpent = timeSpent; + this.timeSpent += System.currentTimeMillis() - lastMillis; + + float lastDistPercentToTarget = lastTimeSpent/(float)timeToReachTarget; + float distPercentToTarget = timeSpent/(float)timeToReachTarget; + float fac = (1-lastDistPercentToTarget)/lastDistPercentToTarget; + + float startValue = lerpValue - (targetValue - lerpValue)/fac; + + float dist = targetValue - startValue; + if(dist == 0) return; + + float oldLerpValue = lerpValue; + if(distPercentToTarget >= 1) { + lerpValue = targetValue; + } else { + lerpValue = startValue + dist*distPercentToTarget; + } + + if(lerpValue == oldLerpValue) { + timeSpent = lastTimeSpent; + } else { + this.lastMillis = System.currentTimeMillis(); + } + } + + public void resetTimer() { + this.timeSpent = 0; + this.lastMillis = System.currentTimeMillis(); + } + + public void setTarget(float targetValue) { + this.targetValue = targetValue; + } + + public void setValue(float value) { + this.targetValue = this.lerpValue = value; + } + + public float getValue() { + return lerpValue; + } + + public float getTarget() { + return targetValue; + } +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/util/LerpingInteger.java b/src/main/java/io/github/moulberry/notenoughupdates/util/LerpingInteger.java new file mode 100644 index 00000000..93c011e1 --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/util/LerpingInteger.java @@ -0,0 +1,68 @@ +package io.github.moulberry.notenoughupdates.util; + +public class LerpingInteger { + + private int timeSpent; + private long lastMillis; + private int timeToReachTarget; + + private int targetValue; + private int lerpValue; + + public LerpingInteger(int initialValue, int timeToReachTarget) { + this.targetValue = this.lerpValue = initialValue; + this.timeToReachTarget = timeToReachTarget; + } + + public LerpingInteger(int initialValue) { + this(initialValue, 200); + } + + public void tick() { + int lastTimeSpent = timeSpent; + this.timeSpent += System.currentTimeMillis() - lastMillis; + + float lastDistPercentToTarget = lastTimeSpent/(float)timeToReachTarget; + float distPercentToTarget = timeSpent/(float)timeToReachTarget; + float fac = (1-lastDistPercentToTarget)/lastDistPercentToTarget; + + int startValue = lerpValue - (int)((targetValue - lerpValue)/fac); + + int dist = targetValue - startValue; + if(dist == 0) return; + + int oldLerpValue = lerpValue; + if(distPercentToTarget >= 1) { + lerpValue = targetValue; + } else { + lerpValue = startValue + (int)(dist*distPercentToTarget); + } + + if(lerpValue == oldLerpValue) { + timeSpent = lastTimeSpent; + } else { + this.lastMillis = System.currentTimeMillis(); + } + } + + public void resetTimer() { + this.timeSpent = 0; + this.lastMillis = System.currentTimeMillis(); + } + + public void setTarget(int targetValue) { + this.targetValue = targetValue; + } + + public void setValue(int value) { + this.targetValue = this.lerpValue = value; + } + + public int getValue() { + return lerpValue; + } + + public int getTarget() { + return targetValue; + } +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/util/SynchronousHTMLEditorKit.java b/src/main/java/io/github/moulberry/notenoughupdates/util/SynchronousHTMLEditorKit.java new file mode 100644 index 00000000..7f02baa7 --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/util/SynchronousHTMLEditorKit.java @@ -0,0 +1,36 @@ +package io.github.moulberry.notenoughupdates.util; + +import javax.swing.text.Document; +import javax.swing.text.Element; +import javax.swing.text.View; +import javax.swing.text.ViewFactory; +import javax.swing.text.html.HTMLDocument; +import javax.swing.text.html.HTMLEditorKit; +import javax.swing.text.html.ImageView; +import javax.swing.text.html.HTMLEditorKit.HTMLFactory; + +public class SynchronousHTMLEditorKit extends HTMLEditorKit { + public SynchronousHTMLEditorKit() { + } + + public Document createDefaultDocument() { + HTMLDocument doc = (HTMLDocument)super.createDefaultDocument(); + doc.setAsynchronousLoadPriority(-1); + return doc; + } + + public ViewFactory getViewFactory() { + return new HTMLFactory() { + public View create(Element elem) { + View view = super.create(elem); + if (view instanceof ImageView) { + //((ImageView)view).setLoadsSynchronously(true); + } + + return view; + } + }; + } + + +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/util/TexLoc.java b/src/main/java/io/github/moulberry/notenoughupdates/util/TexLoc.java new file mode 100644 index 00000000..47a9c86b --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/util/TexLoc.java @@ -0,0 +1,50 @@ +package io.github.moulberry.notenoughupdates.util; + +import org.lwjgl.input.Keyboard; + +public class TexLoc { + + public int x; + public int y; + private int toggleKey; + private boolean toggled; + private boolean pressedLastTick; + private boolean dirPressed; + + public TexLoc(int x, int y, int toggleKey) { + this.x = x; + this.y = y; + this.toggleKey = toggleKey; + } + + public void handleKeyboardInput() { + if(Keyboard.isKeyDown(toggleKey)) { + if(!pressedLastTick) { + toggled = !toggled; + } + pressedLastTick = true; + } else { + pressedLastTick = false; + } + if(toggled) { + if(Keyboard.isKeyDown(Keyboard.KEY_LEFT)) { + if(!dirPressed) x--; + dirPressed = true; + } else if(Keyboard.isKeyDown(Keyboard.KEY_RIGHT)) { + if(!dirPressed) x++; + dirPressed = true; + } else if(Keyboard.isKeyDown(Keyboard.KEY_UP)) { + if(!dirPressed) y--; + dirPressed = true; + } else if(Keyboard.isKeyDown(Keyboard.KEY_DOWN)) { + if(!dirPressed) y++; + dirPressed = true; + } else { + dirPressed = false; + return; + } + System.out.println("X: " + x + " ; Y: " + y); + } + } + +} |