diff options
48 files changed, 3412 insertions, 436 deletions
diff --git a/build.gradle b/build.gradle index 3f59af89..4c5c5386 100644 --- a/build.gradle +++ b/build.gradle @@ -40,6 +40,7 @@ dependencies { //compile('org.spongepowered:mixin:0.7.11-SNAPSHOT') compile('org.kohsuke:github-api:1.108') compile('com.fasterxml.jackson.core:jackson-core:2.10.2') + compile('info.bliki.wiki:bliki-core:3.1.0') } /*mixin { @@ -64,10 +65,14 @@ shadowJar { include(dependency('com.fasterxml.jackson.core:jackson-databind:2.10.2')) include(dependency('com.fasterxml.jackson.core:jackson-annotations:2.10.2')) include(dependency('com.fasterxml.jackson.core:jackson-core:2.10.2')) - //exclude(dependency('com.fasterxml.jackson.core:jackson-annotations')) + + include(dependency('info.bliki.wiki:bliki-core:3.1.0')) + include(dependency('org.slf4j:slf4j-api:1.7.18')) + include(dependency('org.luaj:luaj-jse:3.0.1')) } relocate 'com.fasterxml.jackson', 'neu.com.fasterxml.jackson' + relocate 'org.slf4j', 'neu.org.slf4j' exclude 'module-info.class' exclude 'dummyThing' 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/GuiItemRecipe.java b/src/main/java/io/github/moulberry/notenoughupdates/GuiItemRecipe.java new file mode 100644 index 00000000..1e31a0f2 --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/GuiItemRecipe.java @@ -0,0 +1,78 @@ +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; + int xPos = 28; + if(this.fontRendererObj.getStringWidth(t) > this.xSize - 40) { + xPos = 4; + } + if(t.contains("\u00a7")) { + this.fontRendererObj.drawStringWithShadow(t, xPos, 6, 4210752); + } else { + this.fontRendererObj.drawString(t, xPos, 6, 4210752); + } + this.fontRendererObj.drawString(I18n.format("container.inventory", new Object[0]), 8, this.ySize - 96 + 2, 4210752); + + this.fontRendererObj.drawString(craftText, 88, 19, 4210752); + } + + protected void mouseClickMove(int mouseX, int mouseY, int clickedMouseButton, long timeSinceLastClick) { + } + + protected void handleMouseClick(Slot slotIn, int slotId, int clickedButton, int clickType) { + if(slotId >= 1 && slotId <= 9) { + ItemStack click = craftMatrix[slotId-1]; + if(click != null) { + manager.displayGuiItemRecipe(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/NEUIO.java b/src/main/java/io/github/moulberry/notenoughupdates/NEUIO.java index 7e7091cf..a89bd1dc 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/NEUIO.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/NEUIO.java @@ -74,7 +74,9 @@ public class NEUIO { changedFiles.put(content.getName(), content.getSha()); } } - } catch(IOException e) { } + } catch(IOException e) { + return null; + } return changedFiles; } diff --git a/src/main/java/io/github/moulberry/notenoughupdates/NEUManager.java b/src/main/java/io/github/moulberry/notenoughupdates/NEUManager.java index c6fab0f1..bcf4635e 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/NEUManager.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/NEUManager.java @@ -1,14 +1,18 @@ package io.github.moulberry.notenoughupdates; +import com.google.common.io.CharSource; import com.google.gson.*; +import io.github.moulberry.notenoughupdates.options.Options; 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.JsonToNBT; -import net.minecraft.nbt.NBTTagCompound; -import net.minecraft.nbt.NBTTagList; -import net.minecraft.nbt.NBTUtil; +import net.minecraft.nbt.*; 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.*; @@ -22,8 +26,9 @@ import java.util.zip.ZipInputStream; public class NEUManager { + private final NotEnoughUpdates neu; public final NEUIO neuio; - public static final Gson gson = new GsonBuilder().setPrettyPrinting().create(); + public final Gson gson; private TreeMap<String, JsonObject> itemMap = new TreeMap<>(); @@ -31,23 +36,41 @@ public class NEUManager { private TreeMap<String, HashMap<String, List<Integer>>> titleWordMap = new TreeMap<>(); private TreeMap<String, HashMap<String, List<Integer>>> loreWordMap = new TreeMap<>(); - private File configLocation; + public String viewItemAttemptID = null; + public long viewItemAttemptTime = 0; + + private ResourceLocation wkZip = new ResourceLocation("notenoughupdates:wkhtmltox.zip"); + private Map<String, ItemStack> itemstackCache = new HashMap<>(); + + private static final String AUCTIONS_PRICE_URL = "https://sll5cr6l5a.execute-api.us-east-1.amazonaws.com/Prod/get_prices"; + private TreeMap<String, Float> auctionPrices = new TreeMap<>(); + private TreeMap<String, Pair<Float, Float>> bazaarBuySellPrices = new TreeMap<>(); + private long auctionLastUpdate = 0; + + public File configLocation; private File itemsLocation; private File itemShaLocation; private JsonObject itemShaConfig; - private File config; - private JsonObject configJson; + private File configFile; + public Options config; - public NEUManager(NEUIO neuio, File configLocation) { + public NEUManager(NotEnoughUpdates neu, NEUIO neuio, File configLocation) { + this.neu = neu; this.configLocation = configLocation; this.neuio = neuio; - this.config = new File(configLocation, "config.json"); + 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 { - config.createNewFile(); - configJson = getJsonFromFile(config); - if(configJson == null) configJson = new JsonObject(); - } catch(IOException e) { } + configFile.createNewFile(); + config = Options.loadFromFile(gson, configFile); + } catch(IOException e) { + config = new Options(); + } this.itemsLocation = new File(configLocation, "items"); itemsLocation.mkdir(); @@ -58,6 +81,115 @@ public class NEUManager { 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 void saveConfig() throws IOException { + config.saveToFile(gson, configFile); + } + + public void updatePrices() { + if(System.currentTimeMillis() - auctionLastUpdate > 1000*60*30) { //30 minutes + System.out.println("UPDATING PRICE INFORMATION"); + auctionLastUpdate = System.currentTimeMillis(); + try (Reader inReader = new BufferedReader(new InputStreamReader(new URL(AUCTIONS_PRICE_URL).openStream()))) { + JsonObject json = gson.fromJson(inReader, JsonObject.class); + for(Map.Entry<String, JsonElement> entry : json.getAsJsonObject("AuctionPricesItem").entrySet()) { + if(entry.getKey().equals("id")) continue; + auctionPrices.put(entry.getKey(), entry.getValue().getAsFloat()); + } + for(Map.Entry<String, JsonElement> entry : json.getAsJsonObject("BazaarPricesItem").entrySet()) { + if(entry.getKey().equals("id")) continue; + JsonObject val = entry.getValue().getAsJsonObject(); + bazaarBuySellPrices.put(entry.getKey(), Pair.of(val.get("buyPrice").getAsFloat(), val.get("sellPrice").getAsFloat())); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + public NavigableMap<String, Float> getAuctionPrices() { + return Collections.unmodifiableNavigableMap(auctionPrices); + } + + public NavigableMap<String, Pair<Float, Float>> getBazaarBuySellPrices() { + return Collections.unmodifiableNavigableMap(bazaarBuySellPrices); } /** @@ -71,69 +203,84 @@ public class NEUManager { } /** - * Some convenience methods for working with the config. Should probably switch to using a more sophisticated - * config system, but until we only use it for more than one value, this should be fine. - */ - public void setAllowEditing(boolean allowEditing) { - try { - configJson.addProperty("allowEditing", allowEditing); - writeJson(configJson, config); - } catch(IOException e) {} - } - - public boolean getAllowEditing() { - try { - return configJson.get("allowEditing").getAsBoolean(); - } catch(Exception e) {} - return false; - } - - /** * 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() { - 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(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(); + 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()); + 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); - 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) {} + Map<String, String> changedFiles = neuio.getChangedItems(oldShas); - if(Display.isActive()) dialog.toFront(); + 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.size() <= 20) { - Map<String, String> downloads = neuio.getItemsDownload(changedFiles.keySet()); + if (changedFiles != null && changedFiles.size() <= 20) { + Map<String, String> downloads = neuio.getItemsDownload(changedFiles.keySet()); - String startMessage = "NotEnoughUpdates: Syncing with remote repository ("; - int downloaded = 0; + 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()); + 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(); + 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)) { + 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) { @@ -142,40 +289,18 @@ public class NEUManager { } 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(); - pane.setMessage("Unzipping NEU Master Archive."); - dialog.pack(); - dialog.setVisible(true); - if(Display.isActive()) dialog.toFront(); + unzipIgnoreFirstFolder(itemsZip.getAbsolutePath(), configLocation.getAbsolutePath()); + } - unzipIgnoreFirstFolder(itemsZip.getAbsolutePath(), configLocation.getAbsolutePath()); + dialog.dispose(); } - - dialog.dispose(); - for(File f : itemsLocation.listFiles()) { loadItem(f.getName().substring(0, f.getName().length()-5)); } @@ -186,7 +311,8 @@ public class NEUManager { * in to titleWordMap and loreWordMap. These maps are used in the searching algorithm. * @param internalName */ - private void loadItem(String internalName) { + public void loadItem(String internalName) { + itemstackCache.remove(internalName); try { JsonObject json = getJsonFromFile(new File(itemsLocation, internalName + ".json")); if(json == null) { @@ -406,6 +532,28 @@ public class NEUManager { 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. */ @@ -430,8 +578,8 @@ public class NEUManager { stack.setStackDisplayName(stack.getDisplayName().substring(0, stack.getDisplayName().length()-8)); } - if(lore.length > 0 && (lore[lore.length-1].endsWith("Click to view recipes!") || - lore[lore.length-1].endsWith("Click to view recipe!"))) { + 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; @@ -459,7 +607,7 @@ public class NEUManager { NBTTagCompound ea = tag.getCompoundTag("ExtraAttributes"); if(ea.hasKey("id", 8)) { - return ea.getString("id"); + return ea.getString("id").replaceAll(":", "-"); } } return null; @@ -475,13 +623,9 @@ public class NEUManager { JsonObject json = getJsonForItem(stack); json.addProperty("internalname", internalname); - json.addProperty("clickcommand", "viewrecipe"); + json.addProperty("clickcommand", ""); json.addProperty("modver", NotEnoughUpdates.VERSION); - if(!json.has("internalname")) { - return; - } - try { writeJson(json, new File(itemsLocation, internalname+".json")); } catch (IOException e) {} @@ -489,13 +633,18 @@ public class NEUManager { loadItem(internalname); } - public JsonObject createItemJson(String internalname, String itemid, String displayname, String[] lore, String[] info, + public JsonObject createItemJson(String internalname, String itemid, String displayname, String[] lore, String infoType, String[] info, + String clickcommand, int damage, NBTTagCompound nbttag) { + return createItemJson(new JsonObject(), internalname, itemid, displayname, lore, infoType, info, clickcommand, damage, nbttag); + } + + public JsonObject createItemJson(JsonObject base, String internalname, String itemid, String displayname, String[] lore, String infoType, String[] info, String clickcommand, int damage, NBTTagCompound nbttag) { if(internalname == null || internalname.isEmpty()) { return null; } - JsonObject json = new JsonObject(); + JsonObject json = gson.fromJson(gson.toJson(base, JsonObject.class), JsonObject.class); json.addProperty("internalname", internalname); json.addProperty("itemid", itemid); json.addProperty("displayname", displayname); @@ -503,6 +652,7 @@ public class NEUManager { 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(); @@ -514,7 +664,6 @@ public class NEUManager { JsonArray jsonlore = new JsonArray(); for(String line : lore) { - System.out.println("Lore:"+line); jsonlore.add(new JsonPrimitive(line)); } json.add("lore", jsonlore); @@ -522,9 +671,14 @@ public class NEUManager { return json; } - public boolean writeItemJson(String internalname, String itemid, String displayname, String[] lore, String[] info, + public boolean writeItemJson(String internalname, String itemid, String displayname, String[] lore, String infoType, String[] info, + String clickcommand, int damage, NBTTagCompound nbttag) { + return writeItemJson(new JsonObject(), internalname, itemid, displayname, lore, infoType, info, clickcommand, damage, nbttag); + } + + public boolean writeItemJson(JsonObject base, String internalname, String itemid, String displayname, String[] lore, String infoType, String[] info, String clickcommand, int damage, NBTTagCompound nbttag) { - JsonObject json = createItemJson(internalname, itemid, displayname, lore, info, clickcommand, damage, nbttag); + JsonObject json = createItemJson(base, internalname, itemid, displayname, lore, infoType, info, clickcommand, damage, nbttag); if(json == null) { return false; } @@ -539,9 +693,9 @@ public class NEUManager { return true; } - public boolean uploadItemJson(String internalname, String itemid, String displayname, String[] lore, String[] info, + public boolean uploadItemJson(String internalname, String itemid, String displayname, String[] lore, String infoType, String[] info, String clickcommand, int damage, NBTTagCompound nbttag) { - JsonObject json = createItemJson(internalname, itemid, displayname, lore, info, clickcommand, damage, nbttag); + JsonObject json = createItemJson(internalname, itemid, displayname, lore, infoType, info, clickcommand, damage, nbttag); if(json == null) { return false; } @@ -565,7 +719,7 @@ public class NEUManager { return true; } - private void writeJson(JsonObject json, File file) throws IOException { + public void writeJson(JsonObject json, File file) throws IOException { file.createNewFile(); try(BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))) { @@ -573,7 +727,7 @@ public class NEUManager { } } - private void writeJsonDefaultDir(JsonObject json, String filename) throws IOException { + public void writeJsonDefaultDir(JsonObject json, String filename) throws IOException { File file = new File(itemsLocation, filename); writeJson(json, file); } @@ -621,7 +775,151 @@ public class NEUManager { } 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()); + } + 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 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 index b86ad932..3eadcf25 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/NEUOverlay.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/NEUOverlay.java @@ -3,31 +3,65 @@ package io.github.moulberry.notenoughupdates; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.mojang.realmsclient.gui.ChatFormatting; +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 info.bliki.wiki.tags.MathTag; +import io.github.moulberry.notenoughupdates.itemeditor.GuiElementTextField; import io.github.moulberry.notenoughupdates.itemeditor.NEUItemEditor; +import io.github.moulberry.notenoughupdates.options.Options; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.*; import net.minecraft.client.gui.inventory.GuiContainer; +import net.minecraft.client.gui.inventory.GuiInventory; import net.minecraft.client.renderer.*; +import net.minecraft.client.renderer.block.model.ItemCameraTransforms; +import net.minecraft.client.renderer.entity.RenderEnderman; import net.minecraft.client.renderer.entity.RenderItem; -import net.minecraft.client.renderer.vertex.DefaultVertexFormats; -import net.minecraft.init.Blocks; -import net.minecraft.init.Items; +import net.minecraft.client.renderer.entity.RenderManager; +import net.minecraft.client.renderer.texture.DynamicTexture; +import net.minecraft.client.renderer.texture.TextureMap; +import net.minecraft.client.resources.model.IBakedModel; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityList; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.boss.EntityDragon; +import net.minecraft.entity.monster.EntityCaveSpider; +import net.minecraft.entity.monster.EntityEnderman; +import net.minecraft.entity.monster.EntitySpider; +import net.minecraft.entity.monster.EntityZombie; import net.minecraft.inventory.Slot; -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 net.minecraftforge.fml.client.config.GuiUtils; +import net.minecraft.world.World; +import org.apache.commons.lang3.StringEscapeUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; import org.lwjgl.input.Keyboard; import org.lwjgl.input.Mouse; import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL14; +import javax.imageio.ImageIO; import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.*; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.StandardCharsets; import java.util.*; import java.util.List; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiConsumer; import java.util.regex.Matcher; @@ -38,25 +72,85 @@ public class NEUOverlay extends Gui { private NEUManager manager; private ResourceLocation itemPaneTabArrow = new ResourceLocation("notenoughupdates:item_pane_tab_arrow.png"); - private ResourceLocation prev = new ResourceLocation("notenoughupdates:prev_pow2.png"); - private ResourceLocation next = new ResourceLocation("notenoughupdates:next_pow2.png"); + private ResourceLocation prev = new ResourceLocation("notenoughupdates:prev.png"); + private ResourceLocation next = new ResourceLocation("notenoughupdates:next.png"); private ResourceLocation item_edit = new ResourceLocation("notenoughupdates:item_edit.png"); + private ResourceLocation close = new ResourceLocation("notenoughupdates:close.png"); + private ResourceLocation settings = new ResourceLocation("notenoughupdates:settings.png"); + private ResourceLocation off = new ResourceLocation("notenoughupdates:off.png"); + private ResourceLocation on = new ResourceLocation("notenoughupdates:on.png"); + private ResourceLocation help = new ResourceLocation("notenoughupdates:help.png"); + + private ResourceLocation sort_all = new ResourceLocation("notenoughupdates:sort_all.png"); + private ResourceLocation sort_mob = new ResourceLocation("notenoughupdates:sort_mob.png"); + private ResourceLocation sort_pet = new ResourceLocation("notenoughupdates:sort_pet.png"); + private ResourceLocation sort_tool = new ResourceLocation("notenoughupdates:sort_weapon.png"); + private ResourceLocation sort_armor = new ResourceLocation("notenoughupdates:sort_armor.png"); + private ResourceLocation sort_accessory = new ResourceLocation("notenoughupdates:sort_accessory.png"); + private ResourceLocation sort_all_active = new ResourceLocation("notenoughupdates:sort_all_active.png"); + private ResourceLocation sort_mob_active = new ResourceLocation("notenoughupdates:sort_mob_active.png"); + private ResourceLocation sort_pet_active = new ResourceLocation("notenoughupdates:sort_pet_active.png"); + private ResourceLocation sort_tool_active = new ResourceLocation("notenoughupdates:sort_weapon_active.png"); + private ResourceLocation sort_armor_active = new ResourceLocation("notenoughupdates:sort_armor_active.png"); + private ResourceLocation sort_accessory_active = new ResourceLocation("notenoughupdates:sort_accessory_active.png"); + + + private ResourceLocation order_alphabetical = new ResourceLocation("notenoughupdates:order_alphabetical.png"); + private ResourceLocation order_rarity = new ResourceLocation("notenoughupdates:order_rarity.png"); + private ResourceLocation order_alphabetical_active = new ResourceLocation("notenoughupdates:order_alphabetical_active.png"); + private ResourceLocation order_rarity_active = new ResourceLocation("notenoughupdates:order_rarity_active.png"); + private ResourceLocation ascending_overlay = new ResourceLocation("notenoughupdates:ascending_overlay.png"); + private ResourceLocation descending_overlay = new ResourceLocation("notenoughupdates:descending_overlay.png"); + + + + 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 final WikiModel wikiModel; private final int searchBarXSize = 200; private final int searchBarYOffset = 10; private final int searchBarYSize = 40; private final int searchBarPadding = 2; + private final int ZOOM_FACTOR = 2; + private final int IMAGE_WIDTH = 400; + private final int boxPadding = 15; private final int itemPadding = 4; private final int itemSize = 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 BufferedImage webpageLoadTemp = null; + private int informationPaneImageHeight = 0; private String[] informationPane; + private AtomicInteger webpageAwaitID = new AtomicInteger(-1); + private boolean configOpen = false; - private boolean allowItemEditing; + private static final int SCROLL_AMOUNT = 50; + private LerpingInteger scrollHeight = new LerpingInteger(0); - private LinkedHashMap<String, JsonObject> searchedItems = null; + private TreeSet<JsonObject> searchedItems = null; + private JsonObject[] searchedItemsArr = null; private boolean itemPaneOpen = false; @@ -69,40 +163,110 @@ public class NEUOverlay extends Gui { private boolean searchMode = false; private long millisLastLeftClick = 0; + boolean mouseDown = false; + private boolean searchBarHasFocus = false; GuiTextField textField = new GuiTextField(0, null, 0, 0, 0, 0); + Map<Options.Option<?>, GuiElementTextField> textConfigMap = new HashMap<>(); + + 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); - allowItemEditing = manager.getAllowEditing(); + 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(); + /*String sizeStr = imageFormat.getWidthStr(); + if (sizeStr != null) { + imageName = sizeStr + '-' + imageName; + }*/ + 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 void reset() { searchBarHasFocus = false; - itemPaneOpen = searchMode; - itemPaneOffsetFactor.setValue(1); - itemPaneTabOffset.setValue(20); + 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(); + displayInformationPane(item.get("displayname").getAsString(), item.get("infoType").getAsString(), loreA); + } } /** * Handles the mouse input, cancelling the forge event if a NEU gui element is clicked. */ public boolean mouseInput() { - ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft()); 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*itemPaneOffsetFactor.getValue()) { + 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); @@ -114,49 +278,112 @@ public class NEUOverlay extends Gui { int id = getSlotId(x, y); JsonObject item = getSearchedItemPage(id); if (item != null) { - if(item.has("clickcommand") && Mouse.getEventButton() == 0) { - String clickcommand = item.get("clickcommand").getAsString(); - - if(clickcommand.equals("viewrecipe")) { - Minecraft.getMinecraft().thePlayer.sendChatMessage( - "/" + clickcommand + " " + item.get("internalname").getAsString().toUpperCase()); - } else if(clickcommand.equals("viewpotion")) { - - Minecraft.getMinecraft().thePlayer.sendChatMessage( - "/" + clickcommand + " " + item.get("internalname").getAsString().toLowerCase()); - } - } else if(item.has("info") && Mouse.getEventButton() == 1) { - 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(); - displayInformationPane(item.get("displayname").getAsString(), loreA); - } else if(Mouse.getEventButton() == 3) { - textField.setText("itemid:"+item.get("internalname").getAsString()); + 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 leftSide = (int)(width*itemPaneOffsetFactor.getValue()); + int paneWidth = (int)(width/3*getWidthMult()); + int leftSide = (int)(width*getItemPaneOffsetFactor()); + int rightSide = leftSide+paneWidth-boxPadding-getItemBoxXPadding(); + + int scaledYSize = searchBarYSize/scaledresolution.getScaleFactor(); - if(mouseY > boxPadding && mouseY < boxPadding+searchBarYSize/scaledresolution.getScaleFactor()) { + if(mouseY >= boxPadding && mouseY <= boxPadding+getSearchBarYSize()) { int leftPrev = leftSide+boxPadding+getItemBoxXPadding(); - if(mouseX > leftPrev && mouseX < leftPrev+120/scaledresolution.getScaleFactor()) { //"Previous" button + if(mouseX > leftPrev && mouseX < leftPrev+scaledYSize*400/160) { //"Previous" button page--; } - int rightNext = leftSide+width/3-boxPadding-getItemBoxXPadding(); - if(mouseX > rightNext-120/scaledresolution.getScaleFactor() && mouseX < rightNext) { + int rightNext = leftSide+paneWidth-boxPadding-getItemBoxXPadding(); + if(mouseX > rightNext-scaledYSize*400/160 && mouseX < rightNext) { //"Next" button page++; } } + + float sortIconsMinX = (sortIcons.length+orderIcons.length)*(itemSize+itemPadding)+itemSize; + float availableX = rightSide-(leftSide+boxPadding+getItemBoxXPadding()); + float sortOrderScaleFactor = Math.min(1, availableX / sortIconsMinX); + + int scaledItemSize = (int)(itemSize*sortOrderScaleFactor); + int scaledItemPaddedSize = (int)((itemSize+itemPadding)*sortOrderScaleFactor); + int iconTop = height-boxPadding-(itemSize+scaledItemSize)/2-1; + + if(mouseY >= iconTop && mouseY <= iconTop+scaledItemSize) { + for(int i=0; i<orderIcons.length; i++) { + int orderIconX = leftSide+boxPadding+getItemBoxXPadding()+i*scaledItemPaddedSize; + if(mouseX >= orderIconX && mouseX <= orderIconX+scaledItemSize) { + 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-scaledItemSize-i*scaledItemPaddedSize; + if(mouseX >= sortIconX && mouseX <= sortIconX+scaledItemSize) { + manager.config.sortMode.value = new Double(i); + updateSearch(); + } + } + } } return true; } + if(Mouse.getEventButton() == 2) { + Slot slot = ((GuiContainer)Minecraft.getMinecraft().currentScreen).getSlotUnderMouse(); + if(slot != null) { + ItemStack hover = slot.getStack(); + if(hover != null) { + textField.setText("id:"+manager.getInternalNameForItem(hover)); + updateSearch(); + searchMode = true; + } + } + } + + //Left gui + if(mouseX < width*infoPaneOffsetFactor.getValue()) { + int dWheel = Mouse.getEventDWheel(); + + if(dWheel < 0) { + scrollHeight.setTarget(scrollHeight.getTarget()+SCROLL_AMOUNT); + scrollHeight.resetTimer(); + return true; + } else if(dWheel > 0) { + scrollHeight.setTarget(scrollHeight.getTarget()-SCROLL_AMOUNT); + scrollHeight.resetTimer(); + return true; + } + } + + 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 + informationPaneTitle = null; + informationPaneImage = null; + informationPane = new String[0]; + configOpen = false; + } + } + } + //Search bar if(mouseX >= width/2 - searchBarXSize/2 && mouseX <= width/2 + searchBarXSize/2) { - if(mouseY >= height - searchBarYOffset - searchBarYSize/scaledresolution.getScaleFactor() && + if(mouseY >= height - searchBarYOffset - getSearchBarYSize() && mouseY <= height - searchBarYOffset) { if(Mouse.getEventButtonState()) { setSearchBarFocus(true); @@ -176,36 +403,225 @@ public class NEUOverlay extends Gui { } int paddingUnscaled = searchBarPadding/scaledresolution.getScaleFactor(); - int topTextBox = height - searchBarYOffset - searchBarYSize/scaledresolution.getScaleFactor(); - int iconSize = searchBarYSize/scaledresolution.getScaleFactor()+paddingUnscaled*2; + int topTextBox = height - searchBarYOffset - getSearchBarYSize(); + int iconSize = getSearchBarYSize()+paddingUnscaled*2; if(paddingUnscaled < 1) paddingUnscaled = 1; - if(mouseX > width/2 + searchBarXSize/2 + paddingUnscaled*6 && - mouseX < width/2 + searchBarXSize/2 + paddingUnscaled*6 + iconSize) { - if(mouseY > topTextBox - paddingUnscaled && mouseY < topTextBox - paddingUnscaled + iconSize) { + + if(mouseY > topTextBox - paddingUnscaled && mouseY < topTextBox - paddingUnscaled + iconSize) { + if(mouseX > width/2 + searchBarXSize/2 + paddingUnscaled*6 && + mouseX < width/2 + searchBarXSize/2 + paddingUnscaled*6 + iconSize) { + if(Mouse.getEventButtonState()) { + manager.config.enableItemEditing.value = !manager.config.enableItemEditing.value; + } + } else if(mouseX > width/2 - searchBarXSize/2 - paddingUnscaled*6 - iconSize && + mouseX < width/2 - searchBarXSize/2 - paddingUnscaled*6) { if(Mouse.getEventButtonState()) { - allowItemEditing = !allowItemEditing; - manager.setAllowEditing(allowItemEditing); + configOpen = !configOpen; + infoPaneOffsetFactor.setTarget(1/3f); + infoPaneOffsetFactor.resetTimer(); + } } } + 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); + } + } + } + } 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()); + } else if(Mouse.getEventButton() == -1 && mouseDown) { + tf.mouseClickMove(mouseX, mouseY, 0, 0); //last 2 values are unused + } + return; + } + } + if(Mouse.getEventButtonState()) tf.otherComponentClick(); + } + } + }); + return false; } - public void displayInformationPane(String title, String[] info) { + //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(); + } + + ExecutorService ste = Executors.newSingleThreadExecutor(); + + private void processAndSetWebpageImage(String html, String 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"); + File output = new File(manager.configLocation, "tmp/"+ + name.replaceAll("(?i)\\u00A7.", "") + .replaceAll("[^a-zA-Z0-9_\\-]", "_")+".png"); + input.deleteOnExit(); + output.deleteOnExit(); + + if(output.exists()) { + try { + webpageLoadTemp = ImageIO.read(output); + informationPane = new String[] { EnumChatFormatting.RED+"Failed to set informationPaneImage." }; + } catch(IOException e) { + e.printStackTrace(); + informationPaneImage = null; + informationPane = new String[] { EnumChatFormatting.RED+"ERROR" }; + 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) {} + + try { + final int awaitID = webpageAwaitID.get(); + + informationPane = new String[] { EnumChatFormatting.GRAY+"Rendering webpage, please wait... (AwaitID: "+awaitID+")" }; + + Runtime runtime = Runtime.getRuntime(); + Process p = runtime.exec("\""+wkHtmlToImage.getAbsolutePath() + "\" --width "+ IMAGE_WIDTH*ZOOM_FACTOR+" --transparent --zoom "+ZOOM_FACTOR+" \"" + input.getAbsolutePath() + "\" \"" + output.getAbsolutePath() + "\""); + ste.submit(() -> { + try { + if(p.waitFor(15, TimeUnit.SECONDS)) { + if(awaitID != webpageAwaitID.get()) return; + + try { + webpageLoadTemp = ImageIO.read(output); + informationPane = new String[] { EnumChatFormatting.RED+"Failed to set informationPaneImage." }; + } catch(IOException e) { + e.printStackTrace(); + informationPaneImage = null; + informationPane = new String[] { EnumChatFormatting.RED+"ERROR" }; + return; + } + } else { + if(awaitID != webpageAwaitID.get()) return; + + informationPane = new String[] { EnumChatFormatting.RED+"Webpage render timed out (>15sec). Maybe it's too large?" }; + } + } catch(Exception e) { + e.printStackTrace(); + } + }); + } catch(Exception e) { + e.printStackTrace(); + informationPaneImage = null; + informationPane = new String[] { EnumChatFormatting.RED+"ERROR" }; + return; + } + } + } + + 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 { - informationPane = info; + 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); + } } - infoPaneOffsetFactor.setTarget(1/3f); - infoPaneOffsetFactor.resetTimer(); } public int getClickedIndex(int mouseX, int mouseY) { - ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft()); int width = scaledresolution.getScaledWidth(); int height = scaledresolution.getScaledHeight(); @@ -235,82 +651,244 @@ public class NEUOverlay extends Gui { * Handles the keyboard input, cancelling the forge event if the search bar has focus. */ public boolean keyboardInput() { - if(searchBarHasFocus && Keyboard.getEventKey() == 1 && Keyboard.getEventKeyState()) { - searchBarHasFocus = false; - if(!textField.getText().isEmpty()) { - return true; - } - } + if(Minecraft.getMinecraft().currentScreen == null) return false; - if(searchBarHasFocus && Keyboard.getEventKeyState()) { - if(textField.textboxKeyTyped(Keyboard.getEventCharacter(), Keyboard.getEventKey())) { - if(textField.getText().isEmpty()) { - searchedItems = null; + if(Keyboard.getEventKeyState()) { + if(searchBarHasFocus) { + if(Keyboard.getEventKey() == 1) { + searchBarHasFocus = false; } else { - updateSearch(); + if(textField.textboxKeyTyped(Keyboard.getEventCharacter(), Keyboard.getEventKey())) { + updateSearch(); + } } + } else { + if(Keyboard.getEventKey() == Keyboard.KEY_LEFT) yaw--; + if(Keyboard.getEventKey() == Keyboard.KEY_RIGHT) yaw++; + if(Keyboard.getEventKey() == Keyboard.KEY_UP) pitch--; + if(Keyboard.getEventKey() == Keyboard.KEY_DOWN) pitch++; + + 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)) { + tf.keyTyped(Keyboard.getEventCharacter(), Keyboard.getEventKey()); + } + } + }); } } - if(allowItemEditing) { - if(Keyboard.getEventCharacter() == 'k' && Keyboard.getEventKeyState()) { - Slot slot = ((GuiContainer)Minecraft.getMinecraft().currentScreen).getSlotUnderMouse(); - if(slot != null) { - ItemStack hover = slot.getStack(); - if(hover != null) { + if(Keyboard.getEventKeyState()) { + AtomicReference<String> internalname = new AtomicReference<>(null); + Slot slot = ((GuiContainer)Minecraft.getMinecraft().currentScreen).getSlotUnderMouse(); + if(slot != null) { + ItemStack hover = slot.getStack(); + if(hover != null) { + internalname.set(manager.getInternalNameForItem(hover)); + } + } else { + int height = scaledresolution.getScaledHeight(); + + int mouseX = Mouse.getX() / scaledresolution.getScaleFactor(); + int mouseY = height - Mouse.getY() / scaledresolution.getScaleFactor(); + + iterateItemSlots((x, y) -> { + if (mouseX >= x - 1 && mouseX <= x + itemSize + 1) { + if (mouseY >= y - 1 && mouseY <= y + itemSize + 1) { + int id = getSlotId(x, y); + JsonObject json = getSearchedItemPage(id); + if(json != null) internalname.set(json.get("internalname").getAsString()); + return; + } + } + }); + } + if(internalname.get() != null) { + JsonObject item = manager.getItemInformation().get(internalname.get()); + if(item != null) { + if(manager.config.enableItemEditing.value && Keyboard.getEventCharacter() == 'k') { Minecraft.getMinecraft().displayGuiScreen(new NEUItemEditor(manager, - manager.getInternalNameForItem(hover), manager.getJsonForItem(hover))); - //manager.writeItemToFile(hover); + item.get("internalname").getAsString(), item)); + 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 { - ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft()); - int height = scaledresolution.getScaledHeight(); + } + } + } - int mouseX = Mouse.getX() / scaledresolution.getScaleFactor(); - int mouseY = height - Mouse.getY() / scaledresolution.getScaleFactor(); + return searchBarHasFocus; //Cancels keyboard events if the search bar has focus + } - iterateItemSlots((x, y) -> { - if (mouseX >= x - 1 && mouseX <= x + itemSize + 1) { - if (mouseY >= y - 1 && mouseY <= y + itemSize + 1) { - int id = getSlotId(x, y); - JsonObject item = getSearchedItemPage(id); + public void toggleRarity(String internalname) { + if(getFavourites().contains(internalname)) { + getFavourites().remove(internalname); + } else { + getFavourites().add(internalname); + } + updateSearch(); + } - if(item != null) { - Minecraft.getMinecraft().displayGuiScreen(new NEUItemEditor(manager, - item.get("internalname").getAsString(), item)); - } - } - } - }); + 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; + } - return searchBarHasFocus; //Cancels keyboard events if the search bar has focus + 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 LinkedHashMap<>(); + if(searchedItems==null) searchedItems = new TreeSet<>(getItemComparator()); searchedItems.clear(); + searchedItemsArr = null; Set<String> itemsMatch = manager.search(textField.getText()); - for(String item : itemsMatch) { - searchedItems.put(item, manager.getItemInformation().get(item)); + for(String itemname : itemsMatch) { + JsonObject item = manager.getItemInformation().get(itemname); + if(checkMatchesSort(itemname, item)) { + searchedItems.add(item); + } } } - public Collection<JsonObject> getSearchedItems() { + public JsonObject[] getSearchedItems() { if(searchedItems==null) { - return manager.getItemInformation().values(); - } else { - return searchedItems.values(); + 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().size()) { - return (JsonObject) (getSearchedItems().toArray()[actualIndex]); + if(actualIndex < getSearchedItems().length) { + return getSearchedItems()[actualIndex]; } else { return null; } @@ -320,9 +898,8 @@ public class NEUOverlay extends Gui { } public int getItemBoxXPadding() { - ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft()); int width = scaledresolution.getScaledWidth(); - return (((int)(width-width*itemPaneOffsetFactor.getValue())-2*boxPadding)%(itemSize+itemPadding)+itemPadding)/2; + return (((int)(width-width*getItemPaneOffsetFactor())-2*boxPadding)%(itemSize+itemPadding)+itemPadding)/2; } /** @@ -331,17 +908,17 @@ public class NEUOverlay extends Gui { * code duplication issues. */ public void iterateItemSlots(BiConsumer<Integer, Integer> itemSlotConsumer) { - ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft()); int width = scaledresolution.getScaledWidth(); int height = scaledresolution.getScaledHeight(); + int paneWidth = (int)(width/3*getWidthMult()); int itemBoxXPadding = getItemBoxXPadding(); - int itemBoxYPadding = ((height-2*boxPadding)%(itemSize+itemPadding)+itemPadding)/2; + int itemBoxYPadding = ((height-getSearchBarYSize()-2*boxPadding-itemSize-2)%(itemSize+itemPadding)+itemPadding)/2; - int xStart = (int)(width*itemPaneOffsetFactor.getValue())+boxPadding+itemBoxXPadding; - int yStart = boxPadding+searchBarYSize/scaledresolution.getScaleFactor()+itemBoxYPadding; - int xEnd = (int)(width*itemPaneOffsetFactor.getValue())+width/3-boxPadding-itemSize; - int yEnd = height-boxPadding-itemSize; + int xStart = (int)(width*getItemPaneOffsetFactor())+boxPadding+itemBoxXPadding; + int yStart = boxPadding+getSearchBarYSize()+itemBoxYPadding; + int xEnd = (int)(width*getItemPaneOffsetFactor())+paneWidth-boxPadding-itemSize; + int yEnd = height-boxPadding-itemSize-2-itemBoxYPadding; //Render the items, displaying the tooltip if the cursor is over the item for(int y = yStart; y < yEnd; y+=itemSize+itemPadding) { @@ -351,16 +928,22 @@ public class NEUOverlay extends Gui { } } + public float getWidthMult() { + float scaleFMult = 1; + if(scaledresolution.getScaleFactor()==4) scaleFMult = 0.9f; + return (float)Math.min(1.5, Math.max(0.5, manager.config.paneWidthMult.value.floatValue()))*scaleFMult; + } + /** * Calculates the number of horizontal item slots. */ public int getSlotsXSize() { - ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft()); int width = scaledresolution.getScaledWidth(); - int itemBoxXPadding = (((int)(width-width*itemPaneOffsetFactor.getValue())-2*boxPadding)%(itemSize+itemPadding)+itemPadding)/2; - int xStart = (int)(width*itemPaneOffsetFactor.getValue())+boxPadding+itemBoxXPadding; - int xEnd = (int)(width*itemPaneOffsetFactor.getValue())+width/3-boxPadding-itemSize; + int paneWidth = (int)(width/3*getWidthMult()); + int itemBoxXPadding = (((int)(width-width*getItemPaneOffsetFactor())-2*boxPadding)%(itemSize+itemPadding)+itemPadding)/2; + int xStart = (int)(width*getItemPaneOffsetFactor())+boxPadding+itemBoxXPadding; + int xEnd = (int)(width*getItemPaneOffsetFactor())+paneWidth-boxPadding-itemSize; return (int)Math.ceil((xEnd - xStart)/((float)(itemSize+itemPadding))); } @@ -369,62 +952,155 @@ public class NEUOverlay extends Gui { * Calculates the number of vertical item slots. */ public int getSlotsYSize() { - ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft()); int height = scaledresolution.getScaledHeight(); - int itemBoxYPadding = ((height-2*boxPadding)%(itemSize+itemPadding)+itemPadding)/2; - int yStart = boxPadding+searchBarYSize/scaledresolution.getScaleFactor()+itemBoxYPadding; - int yEnd = height-boxPadding-itemSize; + int itemBoxYPadding = ((height-getSearchBarYSize()-2*boxPadding-itemSize-2)%(itemSize+itemPadding)+itemPadding)/2; + int yStart = boxPadding+getSearchBarYSize()+itemBoxYPadding; + int yEnd = height-boxPadding-itemSize-2-itemBoxYPadding; return (int)Math.ceil((yEnd - yStart)/((float)(itemSize+itemPadding))); } public int getMaxPages() { - if(getSearchedItems().size() == 0) return 1; - return (int)Math.ceil(getSearchedItems().size()/(float)getSlotsYSize()/getSlotsXSize()); + 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) { - ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft()); int width = scaledresolution.getScaledWidth(); int height = scaledresolution.getScaledHeight(); - int itemBoxXPadding = (((int)(width-width*itemPaneOffsetFactor.getValue())-2*boxPadding)%(itemSize+itemPadding)+itemPadding)/2; - int itemBoxYPadding = ((height-2*boxPadding)%(itemSize+itemPadding)+itemPadding)/2; + int itemBoxXPadding = (((int)(width-width*getItemPaneOffsetFactor())-2*boxPadding)%(itemSize+itemPadding)+itemPadding)/2; + int itemBoxYPadding = ((height-getSearchBarYSize()-2*boxPadding-itemSize-2)%(itemSize+itemPadding)+itemPadding)/2; - int xStart = (int)(width*itemPaneOffsetFactor.getValue())+boxPadding+itemBoxXPadding; - int yStart = boxPadding+searchBarYSize/scaledresolution.getScaleFactor()+itemBoxYPadding; + int xStart = (int)(width*getItemPaneOffsetFactor())+boxPadding+itemBoxXPadding; + int yStart = boxPadding+getSearchBarYSize()+itemBoxYPadding; int xIndex = (x-xStart)/(itemSize+itemPadding); int yIndex = (y-yStart)/(itemSize+itemPadding); return xIndex + yIndex*getSlotsXSize(); } - /** - * Renders the search bar, item selection (right) and item info (left) gui elements. - */ - public void render(float partialTicks, int mouseX, int mouseY) { + private int getSearchBarYSize() { + return Math.max(searchBarYSize/scaledresolution.getScaleFactor(), itemSize); + } + + public void renderNavElement(int leftSide, int rightSide, int maxPages, String name) { FontRenderer fr = Minecraft.getMinecraft().fontRendererObj; - ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft()); - int width = scaledresolution.getScaledWidth(); - int height = scaledresolution.getScaledHeight(); + drawRect(leftSide-1, boxPadding, + rightSide+1, + boxPadding+getSearchBarYSize(), fg.getRGB()); + + float scaledYSize = searchBarYSize/scaledresolution.getScaleFactor(); + + Minecraft.getMinecraft().getTextureManager().bindTexture(prev); + GlStateManager.color(1f, 1f, 1f, 1f); + drawTexturedRect(leftSide, boxPadding+(getSearchBarYSize()-scaledYSize)/2f, + scaledYSize*400/160, scaledYSize); + GlStateManager.bindTexture(0); + + Minecraft.getMinecraft().getTextureManager().bindTexture(next); + GlStateManager.color(1f, 1f, 1f, 1f); + drawTexturedRect(rightSide-scaledYSize*400/160, boxPadding+(getSearchBarYSize()-scaledYSize)/2f, + scaledYSize*400/160, scaledYSize); + GlStateManager.bindTexture(0); + + String pageText = EnumChatFormatting.BOLD+name + (page+1) + "/" + maxPages; + fr.drawString(pageText, (leftSide+rightSide)/2-fr.getStringWidth(pageText)/2, + boxPadding+(getSearchBarYSize()-8)/2+1, 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) { + int width = scaledresolution.getScaledWidth(); + int height = scaledresolution.getScaledHeight(); try { GuiContainer inv = (GuiContainer) Minecraft.getMinecraft().currentScreen; - Field guiLeft = GuiContainer.class.getDeclaredField("field_147003_i"); - Field guiTop = GuiContainer.class.getDeclaredField("field_147009_r"); + Field guiLeft = GuiContainer.class.getDeclaredField("field_147003_i");//guiLeft + Field guiTop = GuiContainer.class.getDeclaredField("field_147009_r");//guiTop guiLeft.setAccessible(true); guiTop.setAccessible(true); int guiLeftI = (int) guiLeft.get(inv); int guiTopI = (int) guiTop.get(inv); - GL11.glPushMatrix(); GL11.glTranslatef(0, 0, 300); - int overlay = new Color(50, 50, 50, 150).getRGB(); + 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, @@ -440,20 +1116,53 @@ public class NEUOverlay extends Gui { for (int i = 0; i < list.size(); ++i){ if (i == 0){ - list.set(i, stack.getRarity().rarityColor + (String)list.get(i)); + list.set(i, stack.getRarity().rarityColor + list.get(i)); } else { - list.set(i, EnumChatFormatting.GRAY + (String)list.get(i)); + list.set(i, EnumChatFormatting.GRAY + list.get(i)); } } - GuiUtils.drawHoveringText(list, mouseX, mouseY, width, height, -1, Minecraft.getMinecraft().fontRendererObj); + Utils.drawHoveringText(list, mouseX, mouseY, width, height, -1, Minecraft.getMinecraft().fontRendererObj); } } - GL11.glPopMatrix(); + GL11.glTranslatef(0, 0, -300); } catch (NoSuchFieldException | IllegalAccessException e) { e.printStackTrace(); } } + } + + /** + * Renders the search bar, item selection (right) and item info (left) gui elements. + */ + public void render(int mouseX, int mouseY) { + FontRenderer fr = Minecraft.getMinecraft().fontRendererObj; + scaledresolution = new ScaledResolution(Minecraft.getMinecraft()); + int width = scaledresolution.getScaledWidth(); + int height = scaledresolution.getScaledHeight(); + + yaw++; + yaw %= 360; + + scrollHeight.tick(); + 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(webpageLoadTemp != null && informationPaneImage == null) { + DynamicTexture tex = new DynamicTexture(webpageLoadTemp); + informationPaneImage = Minecraft.getMinecraft().getTextureManager().getDynamicTextureLocation( + "notenoughupdates/informationPaneImage", tex); + informationPaneImageHeight = webpageLoadTemp.getHeight(); + + webpageLoadTemp = null; + } if(itemPaneOpen) { if(itemPaneTabOffset.getValue() == 0) { @@ -496,7 +1205,7 @@ public class NEUOverlay extends Gui { int paddingUnscaled = searchBarPadding/scaledresolution.getScaleFactor(); if(paddingUnscaled < 1) paddingUnscaled = 1; - int topTextBox = height - searchBarYOffset - searchBarYSize/scaledresolution.getScaleFactor(); + int topTextBox = height - searchBarYOffset - getSearchBarYSize(); //Search bar background drawRect(width/2 - searchBarXSize/2 - paddingUnscaled, @@ -508,102 +1217,61 @@ public class NEUOverlay extends Gui { width/2 + searchBarXSize/2, height - searchBarYOffset, Color.BLACK.getRGB()); - //Item editor enable button - int iconSize = searchBarYSize/scaledresolution.getScaleFactor()+paddingUnscaled*2; - Minecraft.getMinecraft().getTextureManager().bindTexture(item_edit); - drawRect(width/2 + searchBarXSize/2 + paddingUnscaled*6, + //Settings + int iconSize = getSearchBarYSize()+paddingUnscaled*2; + Minecraft.getMinecraft().getTextureManager().bindTexture(settings); + drawRect(width/2 - searchBarXSize/2 - paddingUnscaled*6 - iconSize, topTextBox - paddingUnscaled, - width/2 + searchBarXSize/2 + paddingUnscaled*6 + iconSize, + width/2 - searchBarXSize/2 - paddingUnscaled*6, topTextBox - paddingUnscaled + iconSize, Color.WHITE.getRGB()); - drawRect(width/2 + searchBarXSize/2 + paddingUnscaled*7, + drawRect(width/2 - searchBarXSize/2 - paddingUnscaled*5 - iconSize, topTextBox, - width/2 + searchBarXSize/2 + paddingUnscaled*5 + iconSize, - topTextBox - paddingUnscaled*2 + iconSize, allowItemEditing ? Color.GREEN.getRGB() : Color.RED.getRGB()); + width/2 - searchBarXSize/2 - paddingUnscaled*7, + topTextBox - paddingUnscaled*2 + iconSize, Color.GRAY.getRGB()); GlStateManager.color(1f, 1f, 1f, 1f); - drawTexturedRect(width/2 + searchBarXSize/2 + paddingUnscaled*6, topTextBox - paddingUnscaled, iconSize, iconSize); + drawTexturedRect(width/2 - searchBarXSize/2 - paddingUnscaled*6 - iconSize, topTextBox - paddingUnscaled, iconSize, iconSize); GlStateManager.bindTexture(0); - if(mouseX > width/2 + searchBarXSize/2 + paddingUnscaled*6 && - mouseX < width/2 + searchBarXSize/2 + paddingUnscaled*6 + iconSize) { - if(mouseY > topTextBox - paddingUnscaled && mouseY < topTextBox - paddingUnscaled + iconSize) { - GuiUtils.drawHoveringText(Arrays.asList( - EnumChatFormatting.GOLD.toString()+EnumChatFormatting.BOLD+"Enable item editing", - EnumChatFormatting.RED+"Warning: "+EnumChatFormatting.YELLOW+"Uploading fake, " + - "misinformative or misleading information using the item editor may result in your " + - "account being banned from using this mod.", - EnumChatFormatting.GREEN.toString()+EnumChatFormatting.BOLD+"Press k on an any item to use the item editor."), - mouseX, mouseY, width, height, 250, Minecraft.getMinecraft().fontRendererObj); - GlStateManager.disableLighting(); - } - } - //Search bar text fr.drawString(textField.getText(), width/2 - searchBarXSize/2 + 5, - topTextBox+(searchBarYSize/scaledresolution.getScaleFactor()-8)/2, Color.WHITE.getRGB()); + 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 - searchBarXSize/2 + 5 + textBeforeCursorWidth, - topTextBox+(searchBarYSize/scaledresolution.getScaleFactor()-8)/2-1, + topTextBox+(getSearchBarYSize()-8)/2-1, width/2 - searchBarXSize/2 + 5 + textBeforeCursorWidth+1, - topTextBox+(searchBarYSize/scaledresolution.getScaleFactor()-8)/2+9, Color.WHITE.getRGB()); + topTextBox+(getSearchBarYSize()-8)/2+9, Color.WHITE.getRGB()); } String selectedText = textField.getSelectedText(); if(!selectedText.isEmpty()) { int selectionWidth = fr.getStringWidth(selectedText); - int leftIndex = textField.getCursorPosition() < textField.getSelectionEnd() ? - textField.getCursorPosition() : textField.getSelectionEnd(); + int leftIndex = Math.min(textField.getCursorPosition(), textField.getSelectionEnd()); String textBeforeSelection = textField.getText().substring(0, leftIndex); int textBeforeSelectionWidth = fr.getStringWidth(textBeforeSelection); drawRect(width/2 - searchBarXSize/2 + 5 + textBeforeSelectionWidth, - topTextBox+(searchBarYSize/scaledresolution.getScaleFactor()-8)/2-1, + topTextBox+(getSearchBarYSize()-8)/2-1, width/2 - searchBarXSize/2 + 5 + textBeforeSelectionWidth + selectionWidth, - topTextBox+(searchBarYSize/scaledresolution.getScaleFactor()-8)/2+9, Color.LIGHT_GRAY.getRGB()); + topTextBox+(getSearchBarYSize()-8)/2+9, Color.LIGHT_GRAY.getRGB()); fr.drawString(selectedText, width/2 - searchBarXSize/2 + 5 + textBeforeSelectionWidth, - topTextBox+(searchBarYSize/scaledresolution.getScaleFactor()-8)/2, Color.BLACK.getRGB()); + topTextBox+(getSearchBarYSize()-8)/2, Color.BLACK.getRGB()); } /** * Item selection (right) gui element rendering */ - Color bg = new Color(128, 128, 128, 50); - int leftSide = (int)(width*itemPaneOffsetFactor.getValue()); - int rightSide = leftSide+width/3-boxPadding-getItemBoxXPadding(); - drawRect(leftSide+boxPadding-5, boxPadding-5, - leftSide+width/3-boxPadding+5, height-boxPadding+5, bg.getRGB()); - - drawRect(leftSide+boxPadding+getItemBoxXPadding()-1, boxPadding, - rightSide+1, - boxPadding+searchBarYSize/scaledresolution.getScaleFactor(), Color.GRAY.getRGB()); - - Minecraft.getMinecraft().getTextureManager().bindTexture(prev); - GlStateManager.color(1f, 1f, 1f, 1f); - drawTexturedRect(leftSide+boxPadding+getItemBoxXPadding(), boxPadding, - 120/scaledresolution.getScaleFactor(), searchBarYSize/scaledresolution.getScaleFactor()); - GlStateManager.bindTexture(0); - - - Minecraft.getMinecraft().getTextureManager().bindTexture(next); - GlStateManager.color(1f, 1f, 1f, 1f); - drawTexturedRect(rightSide-120/scaledresolution.getScaleFactor(), boxPadding, - 120/scaledresolution.getScaleFactor(), searchBarYSize/scaledresolution.getScaleFactor()); - GlStateManager.bindTexture(0); - - int offset = 1; - if(scaledresolution.getScaleFactor()==4) offset = 0; - - String pageText = EnumChatFormatting.BOLD+"Page: " + (page+1) + "/" + getMaxPages(); - fr.drawString(pageText, leftSide+width/6-fr.getStringWidth(pageText)/2-1, - boxPadding+(searchBarYSize/scaledresolution.getScaleFactor())/2-4+offset, Color.BLACK.getRGB()); + int paneWidth = (int)(width/3*getWidthMult()); + int leftSide = (int)(width*getItemPaneOffsetFactor()); + int rightSide = leftSide+paneWidth-boxPadding-getItemBoxXPadding(); //Tab Minecraft.getMinecraft().getTextureManager().bindTexture(itemPaneTabArrow); @@ -612,50 +1280,242 @@ public class NEUOverlay extends Gui { GlStateManager.bindTexture(0); if(mouseX > width-itemPaneTabOffset.getValue() && mouseY > height/2 - 50 - && mouseY < height/2 + 50) { + && mouseY < height/2 + 50) { itemPaneOpen = true; } - //Atomic integer used so that below lambda doesn't complain about non-effectively-final variable + //Atomic reference used so that below lambda doesn't complain about non-effectively-final variable AtomicReference<JsonObject> tooltipToDisplay = new AtomicReference<>(null); - //Iterate through all item slots and display the appropriate item - iterateItemSlots((x, y) -> { - int id = getSlotId(x, y); - JsonObject json = getSearchedItemPage(id); - if(json == null) { - return; + if(itemPaneOffsetFactor.getValue() < 1) { + drawRect(leftSide+boxPadding-5, boxPadding-5, + leftSide+paneWidth-boxPadding+5, height-boxPadding+5, bg.getRGB()); + + renderNavElement(leftSide+boxPadding+getItemBoxXPadding(), rightSide, getMaxPages(), + scaledresolution.getScaleFactor()<4?"Page: ":""); + + //Sort bar + drawRect(leftSide+boxPadding+getItemBoxXPadding()-1, + height-boxPadding-itemSize-2, + rightSide+1, + height-boxPadding, fgCustomOpacity.getRGB()); + + float sortIconsMinX = (sortIcons.length+orderIcons.length)*(itemSize+itemPadding)+itemSize; + float availableX = rightSide-(leftSide+boxPadding+getItemBoxXPadding()); + float sortOrderScaleFactor = Math.min(1, availableX / sortIconsMinX); + + int scaledItemSize = (int)(itemSize*sortOrderScaleFactor); + int scaledItemPaddedSize = (int)((itemSize+itemPadding)*sortOrderScaleFactor); + int iconTop = height-boxPadding-(itemSize+scaledItemSize)/2-1; + + for(int i=0; i<orderIcons.length; i++) { + int orderIconX = leftSide+boxPadding+getItemBoxXPadding()+i*scaledItemPaddedSize; + drawRect(orderIconX, iconTop,scaledItemSize+orderIconX,iconTop+scaledItemSize, fg.getRGB()); + + Minecraft.getMinecraft().getTextureManager().bindTexture(getCompareMode() == i ? orderIconsActive[i] : orderIcons[i]); + GlStateManager.color(1f, 1f, 1f, 1f); + drawTexturedRect(orderIconX, iconTop, scaledItemSize, scaledItemSize,0, 1, 0, 1, GL11.GL_NEAREST); + + Minecraft.getMinecraft().getTextureManager().bindTexture(getCompareAscending().get(i) ? ascending_overlay : descending_overlay); + GlStateManager.color(1f, 1f, 1f, 1f); + drawTexturedRect(orderIconX, iconTop, scaledItemSize, scaledItemSize,0, 1, 0, 1, GL11.GL_NEAREST); + GlStateManager.bindTexture(0); + } + + for(int i=0; i<sortIcons.length; i++) { + int sortIconX = rightSide-scaledItemSize-i*scaledItemPaddedSize; + drawRect(sortIconX, iconTop,scaledItemSize+sortIconX,iconTop+scaledItemSize, fg.getRGB()); + Minecraft.getMinecraft().getTextureManager().bindTexture(getSortMode() == i ? sortIconsActive[i] : sortIcons[i]); + GlStateManager.color(1f, 1f, 1f, 1f); + drawTexturedRect(sortIconX, iconTop, scaledItemSize, scaledItemSize, 0, 1, 0, 1, GL11.GL_NEAREST); + GlStateManager.bindTexture(0); } - drawRect(x-1, y-1, x+itemSize+1, y+itemSize+1, Color.GRAY.getRGB()); - ItemStack stack = new ItemStack(Item.itemRegistry.getObject( - new ResourceLocation(json.get("itemid").getAsString()))); + //Iterate through all item slots and display the appropriate item + iterateItemSlots((x, y) -> { + int id = getSlotId(x, y); + JsonObject json = getSearchedItemPage(id); + if(json == null) { + return; + } - 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(getFavourites().contains(json.get("internalname").getAsString())) { + drawRect(x-1, y-1, x+itemSize+1, y+itemSize+1, fgFavourite2.getRGB()); + drawRect(x, y, x+itemSize, y+itemSize, fgFavourite.getRGB()); + } else { + drawRect(x-1, y-1, x+itemSize+1, y+itemSize+1, fgCustomOpacity.getRGB()); } - if(json.has("nbttag")) { - try { - NBTTagCompound tag = JsonToNBT.getTagFromJson(json.get("nbttag").getAsString()); - stack.setTagCompound(tag); - } catch(NBTException e) { + if(json.has("entityrender")) { + 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+itemSize/2, y+itemSize, scale, name, entities); + } else { + drawItemStack(manager.jsonToStack(json), x, y, null); } - } - //Integer.toString(getSlotId(x, y)) - drawItemStack(stack, x, y, null); - if(mouseX > x-1 && mouseX < x+itemSize+1) { - if(mouseY > y-1 && mouseY < y+itemSize+1) { - tooltipToDisplay.set(json); + if(mouseX > x-1 && mouseX < x+itemSize+1) { + if(mouseY > y-1 && mouseY < y+itemSize+1) { + tooltipToDisplay.set(json); + } + } + }); + } + + /** + * Item info (left) gui element rendering + */ + + rightSide = (int)(width*getInfoPaneOffsetFactor()); + leftSide = rightSide - paneWidth; + + if(scrollHeight.getValue() < 0) scrollHeight.setValue(0); + + if(informationPaneTitle != null || configOpen) { + if(configOpen) { + int boxLeft = leftSide+boxPadding-5; + int boxRight = rightSide-boxPadding+5; + drawRect(boxLeft, boxPadding-5, boxRight, + height-boxPadding+5, bg.getRGB()); + + renderNavElement(leftSide+boxPadding, rightSide-boxPadding, 1,"Settings: "); + + AtomicReference<List<String>> textToDisplay = new AtomicReference<>(null); + 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); + 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 { + 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(option.value instanceof Boolean) { + GlStateManager.color(1f, 1f, 1f, 1f); + Minecraft.getMinecraft().getTextureManager().bindTexture(((Boolean)option.value) ? on : off); + drawTexturedRect(x+tileWidth/2-(int)(32*mult), y+tileHeight-(int)(20*mult), (int)(48*mult), (int)(16*mult)); + + Minecraft.getMinecraft().getTextureManager().bindTexture(help); + drawTexturedRect(x+tileWidth/2+(int)(19*mult), y+tileHeight-(int)(19*mult), (int)(14*mult), (int)(14*mult)); + GL11.glBindTexture(GL11.GL_TEXTURE_2D, 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); + tf.setSize(tileWidth-(int)(20*mult), (int)(16*mult)); + tf.render(x+(int)(10*mult), y+tileHeight-(int)(20*mult)); + + try { + tf.setCustomBorderColour(-1); + option.setValue(tf.getText()); + } catch(Exception e) { + tf.setCustomBorderColour(Color.RED.getRGB()); + } + } + } + }); + if(textToDisplay.get() != null) { + Utils.drawHoveringText(textToDisplay.get(), mouseX, mouseY, width, height, 200, fr); } + } else if(informationPaneImage != null) { + int titleLen = fr.getStringWidth(informationPaneTitle); + fr.drawString(informationPaneTitle, (leftSide+rightSide-titleLen)/2, +boxPadding + 5, Color.WHITE.getRGB()); + + drawRect(leftSide+boxPadding-5, boxPadding-5, rightSide-boxPadding+5, + height-boxPadding+5, bg.getRGB()); + + int imageW = paneWidth - boxPadding*2; + float scaleF = IMAGE_WIDTH*ZOOM_FACTOR/(float)imageW; + + Minecraft.getMinecraft().getTextureManager().bindTexture(informationPaneImage); + GlStateManager.color(1f, 1f, 1f, 1f); + if(height-boxPadding*3 < informationPaneImageHeight/scaleF) { + if(scrollHeight.getValue() > informationPaneImageHeight/scaleF-height+boxPadding*3) { + scrollHeight.setValue((int)(informationPaneImageHeight/scaleF-height+boxPadding*3)); + } + int yScroll = scrollHeight.getValue(); + + float vMin = yScroll/(informationPaneImageHeight/scaleF); + float vMax = (yScroll+height-boxPadding*3)/(informationPaneImageHeight/scaleF); + drawTexturedRect(leftSide+boxPadding, boxPadding*2, imageW, height-boxPadding*3, + 0, 1, vMin, vMax); + } else { + scrollHeight.setValue(0); + + drawTexturedRect(leftSide+boxPadding, boxPadding*2, imageW, (int)(informationPaneImageHeight/scaleF)); + } + GlStateManager.bindTexture(0); + } else if(informationPane != null) { + int titleLen = fr.getStringWidth(informationPaneTitle); + int yScroll = -scrollHeight.getValue(); + fr.drawString(informationPaneTitle, (leftSide+rightSide-titleLen)/2, yScroll+boxPadding + 5, Color.WHITE.getRGB()); + + int yOff = 20; + for(int i=0; i<informationPane.length; i++) { + String line = informationPane[i]; + + yOff += renderStringTrimWidth(line, fr, false,leftSide+boxPadding + 5, yScroll+boxPadding + 10 + yOff, + width*1/3-boxPadding*2-10, Color.WHITE.getRGB(), -1); + yOff += 16; + } + + int top = boxPadding - 5; + int totalBoxHeight = yOff+14; + int bottom = Math.max(top+totalBoxHeight, height-boxPadding+5); + + if(scrollHeight.getValue() > top+totalBoxHeight-(height-boxPadding+5)) { + scrollHeight.setValue(top+totalBoxHeight-(height-boxPadding+5)); + } + drawRect(leftSide+boxPadding-5, yScroll+boxPadding-5, rightSide-boxPadding+5, yScroll+bottom, bg.getRGB()); } - }); + if(rightSide > 0) { + GlStateManager.color(1f, 1f, 1f, 1f); + Minecraft.getMinecraft().getTextureManager().bindTexture(close); + drawTexturedRect(rightSide-22, 7, 16, 16); + GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0); + } + } //Render tooltip JsonObject json = tooltipToDisplay.get(); @@ -667,6 +1527,20 @@ public class NEUOverlay extends Gui { text.add(lore.get(i).getAsString()); } + Float auctionPrice = manager.getAuctionPrices().get(json.get("internalname").getAsString()); + Pair<Float, Float> bazaarBuySellPrice = manager.getBazaarBuySellPrices().get(json.get("internalname").getAsString()); + + boolean hasAuctionPrice = auctionPrice != null; + boolean hasBazaarPrice = bazaarBuySellPrice != null; + + if(hasAuctionPrice || hasBazaarPrice) text.add(""); + if(hasBazaarPrice) text.add(EnumChatFormatting.YELLOW.toString()+EnumChatFormatting.BOLD+"Bazaar Buy: "+ + EnumChatFormatting.GOLD+EnumChatFormatting.BOLD+bazaarBuySellPrice.getLeft()+" coins"); + if(hasBazaarPrice) text.add(EnumChatFormatting.YELLOW.toString()+EnumChatFormatting.BOLD+"Bazaar Sell: "+ + EnumChatFormatting.GOLD+EnumChatFormatting.BOLD+bazaarBuySellPrice.getRight()+" coins"); + if(hasAuctionPrice) text.add(EnumChatFormatting.YELLOW.toString()+EnumChatFormatting.BOLD+"Auction House Buy/Sell: "+ + EnumChatFormatting.GOLD+EnumChatFormatting.BOLD+auctionPrice+" coins"); + boolean hasClick = false; boolean hasInfo = false; if(json.has("clickcommand") && !json.get("clickcommand").getAsString().isEmpty()) { @@ -677,77 +1551,66 @@ public class NEUOverlay extends Gui { } if(hasClick || hasInfo) text.add(""); - if(hasClick) text.add("\u00A7e\u00A7lLeft click to view recipe!"); - if(hasInfo) text.add("\u00A7e\u00A7lRight click to view additional information!"); + 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!"); - GuiUtils.drawHoveringText(text, mouseX, mouseY, width, height, -1, fr); - GlStateManager.disableLighting(); + Utils.drawHoveringText(text, mouseX, mouseY, width, height, -1, fr); } + } - /** - * Item info (left) gui element rendering - */ + public float getItemPaneOffsetFactor() { + return itemPaneOffsetFactor.getValue() * getWidthMult() + (1-getWidthMult()); + } - rightSide = (int)(width*getInfoPaneOffsetFactor()); - leftSide = rightSide - width/3; - - drawRect(leftSide+boxPadding-5, boxPadding-5, rightSide-boxPadding+5, height-boxPadding+5, bg.getRGB()); - if(informationPane != null && informationPaneTitle != null) { - int titleLen = fr.getStringWidth(informationPaneTitle); - fr.drawString(informationPaneTitle, (leftSide+rightSide-titleLen)/2, boxPadding + 5, Color.WHITE.getRGB()); - - int yOff = 20; - for(int i=0; i<informationPane.length; i++) { - String line = informationPane[i]; - - String excess; - String trimmed = trimToWidth(line, width*1/3-boxPadding*2-10); - - 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(); - while(true) { - if(trimmed.length() == line.length()) { - fr.drawString(trimmed, leftSide+boxPadding + 5, boxPadding + 10 + yOff, Color.WHITE.getRGB()); - break; - } else if(trimmed.isEmpty()) { - yOff -= 12; - break; - } else { - if(firstLine) { - fr.drawString(trimmed, leftSide+boxPadding + 5, boxPadding + 10 + yOff, Color.WHITE.getRGB()); - firstLine = false; - } else { - if(trimmed.startsWith(" ")) { - trimmed = trimmed.substring(1); - } - fr.drawString(colourCodes + trimmed, leftSide+boxPadding + 5, boxPadding + 10 + yOff, Color.WHITE.getRGB()); - } + public float getInfoPaneOffsetFactor() { + if(configOpen) return infoPaneOffsetFactor.getValue() * getWidthMult(); + if(itemPaneOffsetFactor.getValue() < 1 && itemPaneOffsetFactor.getValue() == itemPaneOffsetFactor.getTarget()) { + return infoPaneOffsetFactor.getValue() * getWidthMult(); + } else { + return Math.min(infoPaneOffsetFactor.getValue(), 1-getItemPaneOffsetFactor()); + } + } + + public 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); - excess = line.substring(trimmedCharacters); - trimmed = trimToWidth(excess, width * 1 / 3 - boxPadding * 2 - 10); - trimmedCharacters += trimmed.length(); - yOff += 12; + 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); } - yOff += 16; + excess = str.substring(trimmedCharacters); + trimmed = trimToWidth(excess, len); + trimmedCharacters += trimmed.length(); + yOff += 12; } } - } - - public float getInfoPaneOffsetFactor() { - if(itemPaneOffsetFactor.getValue() == 2/3f) { - return infoPaneOffsetFactor.getValue(); - } else { - return Math.min(infoPaneOffsetFactor.getValue(), 1-itemPaneOffsetFactor.getValue()); - } + return yOff; } public String trimToWidth(String str, int len) { @@ -768,37 +1631,94 @@ public class NEUOverlay extends Gui { return trim; } + private abstract class SettingsTileConsumer { + public abstract void consume(int x, int y, int tileWidth, int tileHeight, Options.Option<?> option); + } + + public int getTotalOptions() { + return manager.config.getOptions().size(); + } + + public void iterateSettingTile(SettingsTileConsumer settingsTileConsumer) { + FontRenderer fr = Minecraft.getMinecraft().fontRendererObj; + int width = scaledresolution.getScaledWidth(); + int height = scaledresolution.getScaledHeight(); + + int numHorz = scaledresolution.getScaleFactor() >= 3 ? 2 : 3; + + int paneWidth = (int)(width/3*getWidthMult()); + int rightSide = (int)(width*getInfoPaneOffsetFactor()); + int leftSide = rightSide - paneWidth; + + int boxLeft = leftSide+boxPadding-5; + int boxRight = rightSide-boxPadding+5; + + int boxWidth = boxRight-boxLeft; + int tilePadding = 7; + int tileWidth = (boxWidth-tilePadding*4)/numHorz; + int tileHeight = tileWidth*3/4; + + int x=0; + int y=tilePadding+boxPadding+getSearchBarYSize(); + for(int i=0; i<getTotalOptions(); i++) { + if(i!=0 && i%numHorz==0) { + x = 0; + y += tileHeight+tilePadding; + } + x+=tilePadding; + + settingsTileConsumer.consume(boxLeft+x, y, tileWidth, tileHeight, manager.config.getOptions().get(i)); + + x+=tileWidth; + } + } + 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); } - private void drawTexturedRect(int x, int y, int width, int height) { + private void drawTexturedRect(float x, float y, float width, float height) { drawTexturedRect(x, y, width, height, 0, 1, 0 , 1); } - private void drawTexturedRect(int x, int y, int width, int height, float uMin, float uMax, float vMin, float vMax) { - Tessellator tessellator = Tessellator.getInstance(); - WorldRenderer worldrenderer = tessellator.getWorldRenderer(); + private 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); + } - GlStateManager.enableAlpha(); + private void drawTexturedRect(float x, float y, float width, float height, float uMin, float uMax, float vMin, float vMax, int filter) { + GlStateManager.enableTexture2D(); GlStateManager.enableBlend(); - GlStateManager.blendFunc(770, 771); - GlStateManager.alphaFunc(516, 0.003921569F); + 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); + + 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(); - worldrenderer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); - worldrenderer.pos(x + 0, y + height, this.zLevel).tex(uMin, vMax).endVertex(); - worldrenderer.pos(x + width, y + height, this.zLevel).tex(uMax, vMax).endVertex(); - worldrenderer.pos(x + width, y + 0, this.zLevel).tex(uMax, vMin).endVertex(); - worldrenderer.pos(x + 0, y + 0, this.zLevel).tex(uMin, vMin).endVertex(); - tessellator.draw(); + 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(); + + /*worldrenderer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); + worldrenderer.pos(x, y + height + offset, this.zLevel).tex(uMin, vMax).endVertex(); + worldrenderer.pos(x + width + offset, y + height + offset, this.zLevel).tex(uMax, vMax).endVertex(); + worldrenderer.pos(x + width + offset, y, this.zLevel).tex(uMax, vMin).endVertex(); + worldrenderer.pos(x, y, this.zLevel).tex(uMin, vMin).endVertex(); + tessellator.draw();*/ + } }
\ 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 index 17295aa8..0e310cf5 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java @@ -1,14 +1,30 @@ package io.github.moulberry.notenoughupdates; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; import com.mojang.authlib.Agent; import com.mojang.authlib.GameProfile; 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.scoreboard.ScoreObjective; +import net.minecraft.scoreboard.Scoreboard; +import net.minecraft.util.ChatComponentText; import net.minecraft.util.EnumChatFormatting; import net.minecraft.util.Session; +import net.minecraftforge.client.event.ClientChatReceivedEvent; import net.minecraftforge.client.event.GuiOpenEvent; import net.minecraftforge.client.event.GuiScreenEvent; import net.minecraftforge.common.MinecraftForge; @@ -18,15 +34,25 @@ import net.minecraftforge.fml.common.Mod.EventHandler; import net.minecraftforge.fml.common.event.FMLInitializationEvent; 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.input.Mouse; import javax.swing.*; -import java.io.File; +import java.io.*; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.net.Proxy; +import java.nio.charset.StandardCharsets; +import java.util.ConcurrentModificationException; import java.util.Scanner; +import java.util.Set; import java.util.UUID; +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; @Mod(modid = NotEnoughUpdates.MODID, version = NotEnoughUpdates.VERSION) public class NotEnoughUpdates { @@ -36,26 +62,57 @@ public class NotEnoughUpdates { 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; + + //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(); - //Github Access Token, may change. Value hard-coded. - neuio = new NEUIO("c49fcad378ae46e5c08bf6b0c5502f7e4830bfef"); - manager = new NEUManager(neuio, f); + 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("..."); + auth.setUsername("james.jenour@protonmail.com"); JPasswordField pf = new JPasswordField(); JOptionPane.showConfirmDialog(null, pf, @@ -81,36 +138,282 @@ public class NotEnoughUpdates { } catch (NoSuchFieldException | AuthenticationException | IllegalAccessException e) { e.printStackTrace(); }*/ + } + + 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) { + if(event.gui instanceof GuiChest) { + GuiChest eventGui = (GuiChest) event.gui; + ContainerChest cc = (ContainerChest) eventGui.inventorySlots; + IInventory lower = cc.getLowerChestInventory(); + 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); + + /*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; + 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(); + if(event.getMouseX() <= width*2/3 && event.getMouseX() >= width*1/3) { + try { + overlay.render(event.getMouseX(), event.getMouseY()); + } catch(ConcurrentModificationException e) {e.printStackTrace(); } + } + } } @SubscribeEvent public void onGuiScreenDraw(GuiScreenEvent.DrawScreenEvent.Post event) { - if(event.gui instanceof GuiContainer) { - overlay.render(event.renderPartialTicks, event.mouseX, event.mouseY); + if(event.gui instanceof GuiContainer && isOnSkyblock()) { + ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft()); + int width = scaledresolution.getScaledWidth(); + if(event.mouseX > width*2/3 || event.mouseX < width*1/3) { + try { + overlay.render(event.mouseX, event.mouseY); + } catch(ConcurrentModificationException e) {e.printStackTrace(); } + } + overlay.renderOverlay(event.mouseX, event.mouseY); } } @SubscribeEvent public void onGuiScreenMouse(GuiScreenEvent.MouseInputEvent.Pre event) { - if(event.gui instanceof GuiContainer) { + if(event.gui instanceof GuiContainer && isOnSkyblock()) { if(overlay.mouseInput()) { event.setCanceled(true); } } } + ScheduledExecutorService ses = Executors.newScheduledThreadPool(1); + + boolean started = false; @SubscribeEvent public void onGuiScreenKeyboard(GuiScreenEvent.KeyboardInputEvent.Pre event) { - if(event.gui instanceof GuiContainer) { + 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()) { event.setCanceled(true); } @@ -125,7 +428,7 @@ public class NotEnoughUpdates { */ @SubscribeEvent public void onItemTooltip(ItemTooltipEvent event) { - if(!Keyboard.isKeyDown(Keyboard.KEY_LCONTROL)) return; + 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); @@ -156,4 +459,26 @@ public class NotEnoughUpdates { 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..bcab59c4 --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/Utils.java @@ -0,0 +1,185 @@ +package io.github.moulberry.notenoughupdates; + +import net.minecraft.client.gui.FontRenderer; +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.vertex.DefaultVertexFormats; + +import java.util.ArrayList; +import java.util.List; + +public class Utils { + + public static <T> ArrayList<T> createList(T... values) { + ArrayList<T> list = new ArrayList<>(); + for(T value : values)list.add(value); + return list; + } + + 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/WikiaParser.java b/src/main/java/io/github/moulberry/notenoughupdates/WikiaParser.java new file mode 100644 index 00000000..fc94f60c --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/WikiaParser.java @@ -0,0 +1,212 @@ +package io.github.moulberry.notenoughupdates; + +import info.bliki.wiki.filter.PlainTextConverter; +import info.bliki.wiki.model.WikiModel; +import net.minecraft.util.EnumChatFormatting; + +import java.io.*; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Stack; + +public class WikiaParser { + + private static Stack<Formatting> formattingStack = new Stack<>(); + private static Stack<String> textStack = new Stack<>(); + + public static String parse(String raw) { + if(textStack.isEmpty()) textStack.push(get()); + + raw = raw.replaceAll("\u00A0", " "); + + out: + for(int i=0; i<raw.length(); i++) { + if(i >= raw.length()) break; + String lineS = raw.substring(i); + for(Formatting f : Formatting.values()) { + if(lineS.startsWith(f.start)) { + if(tryPush(f, true)) { + writeToStack(get()); + i += f.start.length()-1; + continue out; + } + } else if(lineS.startsWith(f.end)) { + if(tryPush(f, false)) { + writeToStack(get()); + i += f.end.length()-1; + continue out; + } + } + } + writeToStack(String.valueOf(lineS.charAt(0))); + if(lineS.charAt(0) == '\n') { + writeToStack(get()); + } + } + + String f = trimIgnoreColour(clear()); + return f.replaceAll("(\\n(\\u00A7.)*){2,}", "$1") + .replaceAll("\r","\n"); + } + private enum Formatting { + LINK("[[", "]]", "") { + public String apply(String ctx) { + String[] split = ctx.split("\\|"); + String base = split[split.length-1]; + return "[["+ctx+"]]"; + } + }, + INFOBOX("<infobox", "</infobox>", "") { + public String apply(String ctx) { + return ""; + } + }, + MATH("<math>", "</math>", "") { + public String apply(String ctx) { + //ctx = trimIgnoreColour(ctx).replaceAll("\\times", "") + return ctx; + } + }, + TEMPLATE("{{", "}}", "") { + @Override + public String apply(String ctx) { + //String[] colours = new String[]{"red","green","yellow"}; + + String[] split = ctx.split("\\|"); + String after = split[split.length-1].trim(); + /*if(ctx.trim().startsWith("ItemAbility")) { + return "<br><br><span style=\"color:orange\">Item Ability: " + after + "</span> "; + } else { + for(String col : colours) { + if(ctx.trim().toLowerCase().startsWith(col)) { + return "<p style=\"color:"+col+"\">"+after+"</p>"; + } + } + }*/ + return "{{"+ctx+"}}"; + } + }; + + private boolean ambiguous; + private String start; + private String end; + private String colourCode; + private boolean autoend; + + Formatting(String start, String end, String colourCode) { + this.start = start; + this.end = end; + this.ambiguous = start.equals(end); + this.colourCode = colourCode; + } + + Formatting(String start, String end, String colourCode, boolean autoend) { + this.start = start; + this.end = end; + this.ambiguous = start.equals(end); + this.colourCode = colourCode; + this.autoend = autoend; + } + + public String apply(String ctx) { + return ctx; + } + public boolean consume(boolean start) { + return true; + } + } + + private static String trimIgnoreColour(String str) { + StringBuilder sb = new StringBuilder(); + + while(!str.isEmpty()) { + str = str.trim(); + if(str.startsWith("\u00a7")) { + sb.append(str, 0, 2); + str = str.substring(2); + } else { + sb.append(str); + break; + } + } + + return sb.toString(); + } + + private static int strLenNoColor(String str) { + return noColour(str).length(); + } + + private static String noColour(String str) { + return str.replaceAll("(?i)\\u00A7.", ""); + } + + private static boolean tryPush(Formatting formatting, boolean start) { + if(formatting.ambiguous) { + if(!formattingStack.isEmpty() && formattingStack.peek() == formatting) { + pop(); + } else { + push(formatting); + } + } else { + if(start) { + push(formatting); + } else { + if(!formattingStack.isEmpty()) { + for(int i=1; i<=formattingStack.size(); i++) { + Formatting f = formattingStack.get(formattingStack.size()-i); + if(f == formatting) { + pop(); + return formatting.consume(start); + } else if(f.autoend) { + pop(); + } else { + return false; + } + } + } + return false; + } + } + + return formatting.consume(start); + } + + private static String clear() { + while(!formattingStack.empty()) { + pop(); + } + String ret = textStack.peek(); + + formattingStack.clear(); + textStack.clear(); + textStack.push(get()); + + return ret; + } + + private static void pop() { + Formatting f = formattingStack.pop(); + String applied = f.apply(textStack.pop()); + writeToStack(applied); + } + + private static void push(Formatting formatting) { + formattingStack.push(formatting); + textStack.push(""); + } + + private static void writeToStack(String s) { + textStack.push(textStack.pop() + s); + } + + private static String get() { + StringBuilder colour = new StringBuilder(""); + for(Formatting f : formattingStack) { + colour.append(f.colourCode); + } + return colour.toString(); + } + +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/WikiaParserOLD.java b/src/main/java/io/github/moulberry/notenoughupdates/WikiaParserOLD.java new file mode 100644 index 00000000..82f7505e --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/WikiaParserOLD.java @@ -0,0 +1,270 @@ +package io.github.moulberry.notenoughupdates; + +import info.bliki.wiki.filter.PlainTextConverter; +import info.bliki.wiki.model.WikiModel; +import net.minecraft.util.EnumChatFormatting; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Queue; +import java.util.Stack; + +public class WikiaParserOLD { + + private static WikiModel wikiModel = new WikiModel("https://hypixel-skyblock.fandom.com/wiki/${image}", + "https://hypixel-skyblock.fandom.com/wiki/${title}"); + private static PlainTextConverter ptc = new PlainTextConverter(); + private static Stack<Formatting> formattingStack = new Stack<>(); + private static Stack<String> textStack = new Stack<>(); + + public static String parse(String raw) { + if(textStack.isEmpty()) textStack.push(get()); + + raw = raw.replaceAll("\u00A0", " "); + String[] split = raw.split("</infobox>"); + String afterInfobox = split[split.length-1]; + + out: + for(int i=0; i<afterInfobox.length(); i++) { + if(i >= afterInfobox.length()) break; + String lineS = afterInfobox.substring(i); + for(Formatting f : Formatting.values()) { + if(lineS.startsWith(f.start)) { + if(tryPush(f, true)) { + writeToStack(get()); + i += f.start.length()-1; + continue out; + } + } else if(lineS.startsWith(f.end)) { + if(tryPush(f, false)) { + writeToStack(get()); + i += f.end.length()-1; + continue out; + } + } + } + writeToStack(String.valueOf(lineS.charAt(0))); + if(lineS.charAt(0) == '\n') { + writeToStack(get()); + } + } + + String f = trimIgnoreColour(clear()); + return f.replaceAll("(\\n(\\u00A7.)*){2,}", "$1") + .replaceAll("\r","\n"); + } + + private enum Formatting { + BOLD("'''", "'''", EnumChatFormatting.WHITE+EnumChatFormatting.BOLD.toString()), + //GREEN("{{Green|","}}",EnumChatFormatting.GREEN.toString()), + LIST("\n*", "\n", "", true) { + public String apply(String ctx) { + return "\n \u2022 " + trimIgnoreColour(ctx) + "\n"; + } + public boolean consume(boolean start) { + return start; + } + }, + LIST2("\n#", "\n", "", true) { + public String apply(String ctx) { + return "\n \u2022 " + trimIgnoreColour(ctx) + "\n"; + } + public boolean consume(boolean start) { + return start; + } + }, + SMALL_HEADER("\n;", "\n", EnumChatFormatting.WHITE.toString(), true) { + public String apply(String ctx) { + return "\n" + trimIgnoreColour(ctx) + "\n"; + } + public boolean consume(boolean start) { + return start; + } + }, + TAB("\n:", "\n", "", true) { + public String apply(String ctx) { + return "\n " + trimIgnoreColour(ctx) + "\n"; + } + public boolean consume(boolean start) { + return start; + } + }, + TINY("====", "====", EnumChatFormatting.WHITE+EnumChatFormatting.BOLD.toString()) { + public String apply(String ctx) { + return trimIgnoreColour(ctx); + } + }, + SMALL("===", "===", EnumChatFormatting.GOLD.toString()) { + public String apply(String ctx) { + return " " + trimIgnoreColour(ctx); + } + }, + MEDIUM("==", "==", EnumChatFormatting.GOLD+EnumChatFormatting.BOLD.toString()) { + public String apply(String ctx) { + return "\r " + trimIgnoreColour(ctx); + } + }, + CUSTOM("{{","}}","") { + //❤,❁,☠,✦ + public String apply(String ctx) { + if(noColour(ctx).trim().startsWith("Green|")) { + return EnumChatFormatting.GREEN+ctx.split("Green\\|")[1]; + } else if(noColour(ctx).trim().startsWith("Grey|")) { + return EnumChatFormatting.DARK_GRAY+ctx.split("Grey\\|")[1]; + } else if(noColour(ctx).trim().equalsIgnoreCase("Statname|Crit Damage")) { + return EnumChatFormatting.BLUE+"☠ Crit Damage"; + } else { + try { + return wikiModel.render(ptc, "{{"+ctx+"}}"); + } catch(Exception e) {return "";} + } + } + }, + LINK("[[", "]]", "") { + public String apply(String ctx) { + if(noColour(ctx).toLowerCase().startsWith("file:")) return ""; + + String[] split = ctx.split("#"); + ctx = split[split.length-1]; + + split = ctx.split("\\|"); + ctx = split[split.length-1]; + return ctx; + } + }, + MATH("<math>", "</math>", EnumChatFormatting.BOLD.toString()+EnumChatFormatting.WHITE) { + public String apply(String ctx) { + //ctx = trimIgnoreColour(ctx).replaceAll("\\times", "") + return ctx; + } + },CLASS("{|", "|}", "") { + public String apply(String ctx) { + try { + return wikiModel.render(ptc, "{|"+ctx+"|}"); + } catch(Exception e) {return "";} + } + }; + + private boolean ambiguous; + private String start; + private String end; + private String colourCode; + private boolean autoend; + + Formatting(String start, String end, String colourCode) { + this.start = start; + this.end = end; + this.ambiguous = start.equals(end); + this.colourCode = colourCode; + } + + Formatting(String start, String end, String colourCode, boolean autoend) { + this.start = start; + this.end = end; + this.ambiguous = start.equals(end); + this.colourCode = colourCode; + this.autoend = autoend; + } + + public String apply(String ctx) { + return ctx; + } + public boolean consume(boolean start) { + return true; + } + } + + private static String trimIgnoreColour(String str) { + StringBuilder sb = new StringBuilder(); + + while(!str.isEmpty()) { + str = str.trim(); + if(str.startsWith("\u00a7")) { + sb.append(str, 0, 2); + str = str.substring(2); + } else { + sb.append(str); + break; + } + } + + return sb.toString(); + } + + private static int strLenNoColor(String str) { + return noColour(str).length(); + } + + private static String noColour(String str) { + return str.replaceAll("(?i)\\u00A7.", ""); + } + + private static boolean tryPush(Formatting formatting, boolean start) { + if(formatting.ambiguous) { + if(!formattingStack.isEmpty() && formattingStack.peek() == formatting) { + pop(); + } else { + push(formatting); + } + } else { + if(start) { + push(formatting); + } else { + if(!formattingStack.isEmpty()) { + for(int i=1; i<=formattingStack.size(); i++) { + Formatting f = formattingStack.get(formattingStack.size()-i); + if(f == formatting) { + pop(); + return formatting.consume(start); + } else if(f.autoend) { + pop(); + } else { + return false; + } + } + } + return false; + } + } + + return formatting.consume(start); + } + + private static String clear() { + while(!formattingStack.empty()) { + pop(); + } + String ret = textStack.peek(); + + formattingStack.clear(); + textStack.clear(); + textStack.push(get()); + + return ret; + } + + private static void pop() { + Formatting f = formattingStack.pop(); + String applied = f.apply(textStack.pop()); + writeToStack(applied); + } + + private static void push(Formatting formatting) { + formattingStack.push(formatting); + textStack.push(""); + } + + private static void writeToStack(String s) { + textStack.push(textStack.pop() + s); + } + + private static String get() { + StringBuilder colour = new StringBuilder(EnumChatFormatting.RESET.toString() + EnumChatFormatting.GRAY); + for(Formatting f : formattingStack) { + colour.append(f.colourCode); + } + return colour.toString(); + } + +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/itemeditor/GuiElementTextField.java b/src/main/java/io/github/moulberry/notenoughupdates/itemeditor/GuiElementTextField.java index 5ad5c66c..99fbfd78 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/itemeditor/GuiElementTextField.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/itemeditor/GuiElementTextField.java @@ -6,6 +6,7 @@ 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; @@ -21,8 +22,8 @@ public class GuiElementTextField extends GuiElement { public static final int COLOUR = 0b00010; public static final int MULTILINE = 0b00001; - private static final int searchBarYSize = 20; - private static final int searchBarXSize = 350; + private int searchBarYSize = 20; + private int searchBarXSize = 350; private static final int searchBarPadding = 2; private int options = 0; @@ -35,6 +36,8 @@ public class GuiElementTextField extends GuiElement { 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); @@ -43,6 +46,19 @@ public class GuiElementTextField extends GuiElement { 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(); @@ -307,17 +323,21 @@ public class GuiElementTextField extends GuiElement { int extraSize = (searchBarYSize-8)/2+8; int bottomTextBox = y + searchBarYSize + extraSize*(numLines-1); - //Search bar background + 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, focus ? Color.GREEN.getRGB() : Color.WHITE.getRGB()); + bottomTextBox + paddingUnscaled, borderColour); drawRect(x, y, x + searchBarXSize, bottomTextBox, Color.BLACK.getRGB()); - //Search bar text + //bar text Pattern patternControlCode = Pattern.compile("(?i)\\u00A7([^\\u00B6\n])(?!\\u00B6)"); String text = textField.getText(); @@ -373,8 +393,21 @@ public class GuiElementTextField extends GuiElement { 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, @@ -388,7 +421,10 @@ public class GuiElementTextField extends GuiElement { 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, @@ -398,6 +434,11 @@ public class GuiElementTextField extends GuiElement { 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 index 6cc8aacd..23849ceb 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/itemeditor/NEUItemEditor.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/itemeditor/NEUItemEditor.java @@ -4,6 +4,7 @@ import com.google.gson.JsonArray; import com.google.gson.JsonObject; import io.github.moulberry.notenoughupdates.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; @@ -13,8 +14,6 @@ import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.nbt.*; import net.minecraft.util.ResourceLocation; -import net.minecraftforge.fml.client.config.GuiUtils; -import org.apache.commons.lang3.StringUtils; import org.lwjgl.input.Keyboard; import org.lwjgl.input.Mouse; import org.lwjgl.opengl.GL11; @@ -44,6 +43,7 @@ public class NEUItemEditor extends GuiScreen { private Supplier<String> itemid; private Supplier<String> displayname; private Supplier<String> lore; + private Supplier<String> infoType; private Supplier<String> info; private Supplier<String> clickcommand; private Supplier<String> damage; @@ -77,8 +77,15 @@ public class NEUItemEditor extends GuiScreen { 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("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())); - this.info = addTextFieldWithSupplier("", COLOUR | MULTILINE); + 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() : ""; @@ -144,8 +151,8 @@ public class NEUItemEditor extends GuiScreen { if(infoA.length == 0 || infoA[0].isEmpty()) { infoA = new String[0]; } - return manager.writeItemJson(internalname.get(), itemid.get(), displayname.get(), lore.get().split("\n"), - infoA, clickcommand.get(), damageI, nbttag); + return manager.writeItemJson(item, internalname.get(), itemid.get(), displayname.get(), lore.get().split("\n"), + infoType.get(), infoA, clickcommand.get(), damageI, nbttag); } public boolean upload() { @@ -159,7 +166,7 @@ public class NEUItemEditor extends GuiScreen { infoA = new String[0]; } return manager.uploadItemJson(internalname.get(), itemid.get(), displayname.get(), lore.get().split("\n"), - infoA, clickcommand.get(), damageI, nbttag); + infoType.get(), infoA, clickcommand.get(), damageI, nbttag); } public void onGuiClosed() { @@ -292,7 +299,7 @@ public class NEUItemEditor extends GuiScreen { text.add(displayname.get()); text.addAll(Arrays.asList(lore.get().split("\n"))); - GuiUtils.drawHoveringText(text, itemX-20, itemY+itemSize+28, width, height, -1, + Utils.drawHoveringText(text, itemX-20, itemY+itemSize+28, width, height, -1, Minecraft.getMinecraft().fontRendererObj); GlStateManager.disableLighting(); 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..61807553 --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/options/Options.java @@ -0,0 +1,173 @@ +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> 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<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(autoupdate, options); + tryAddOption(keepopen, 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/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/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/resources/assets/notenoughupdates/ascending_overlay.png b/src/main/resources/assets/notenoughupdates/ascending_overlay.png Binary files differnew file mode 100644 index 00000000..5b703a93 --- /dev/null +++ b/src/main/resources/assets/notenoughupdates/ascending_overlay.png diff --git a/src/main/resources/assets/notenoughupdates/close.png b/src/main/resources/assets/notenoughupdates/close.png Binary files differnew file mode 100644 index 00000000..8ba630fb --- /dev/null +++ b/src/main/resources/assets/notenoughupdates/close.png diff --git a/src/main/resources/assets/notenoughupdates/descending_overlay.png b/src/main/resources/assets/notenoughupdates/descending_overlay.png Binary files differnew file mode 100644 index 00000000..ad9d0bd6 --- /dev/null +++ b/src/main/resources/assets/notenoughupdates/descending_overlay.png diff --git a/src/main/resources/assets/notenoughupdates/help.png b/src/main/resources/assets/notenoughupdates/help.png Binary files differnew file mode 100644 index 00000000..1a7ebb60 --- /dev/null +++ b/src/main/resources/assets/notenoughupdates/help.png diff --git a/src/main/resources/assets/notenoughupdates/next.png b/src/main/resources/assets/notenoughupdates/next.png Binary files differindex 8e4a96cc..d215a7e8 100644 --- a/src/main/resources/assets/notenoughupdates/next.png +++ b/src/main/resources/assets/notenoughupdates/next.png diff --git a/src/main/resources/assets/notenoughupdates/next.xcf b/src/main/resources/assets/notenoughupdates/next.xcf Binary files differnew file mode 100644 index 00000000..f93cc5b9 --- /dev/null +++ b/src/main/resources/assets/notenoughupdates/next.xcf diff --git a/src/main/resources/assets/notenoughupdates/next_pow2.png b/src/main/resources/assets/notenoughupdates/next_pow2.png Binary files differdeleted file mode 100644 index 03365e1c..00000000 --- a/src/main/resources/assets/notenoughupdates/next_pow2.png +++ /dev/null diff --git a/src/main/resources/assets/notenoughupdates/off.png b/src/main/resources/assets/notenoughupdates/off.png Binary files differnew file mode 100644 index 00000000..5d0b6ad2 --- /dev/null +++ b/src/main/resources/assets/notenoughupdates/off.png diff --git a/src/main/resources/assets/notenoughupdates/on.png b/src/main/resources/assets/notenoughupdates/on.png Binary files differnew file mode 100644 index 00000000..4117c519 --- /dev/null +++ b/src/main/resources/assets/notenoughupdates/on.png diff --git a/src/main/resources/assets/notenoughupdates/order_alphabetical.png b/src/main/resources/assets/notenoughupdates/order_alphabetical.png Binary files differnew file mode 100644 index 00000000..4e204691 --- /dev/null +++ b/src/main/resources/assets/notenoughupdates/order_alphabetical.png diff --git a/src/main/resources/assets/notenoughupdates/order_alphabetical_active.png b/src/main/resources/assets/notenoughupdates/order_alphabetical_active.png Binary files differnew file mode 100644 index 00000000..ab596112 --- /dev/null +++ b/src/main/resources/assets/notenoughupdates/order_alphabetical_active.png diff --git a/src/main/resources/assets/notenoughupdates/order_rarity.png b/src/main/resources/assets/notenoughupdates/order_rarity.png Binary files differnew file mode 100644 index 00000000..df23e5da --- /dev/null +++ b/src/main/resources/assets/notenoughupdates/order_rarity.png diff --git a/src/main/resources/assets/notenoughupdates/order_rarity_active.png b/src/main/resources/assets/notenoughupdates/order_rarity_active.png Binary files differnew file mode 100644 index 00000000..84bd3318 --- /dev/null +++ b/src/main/resources/assets/notenoughupdates/order_rarity_active.png diff --git a/src/main/resources/assets/notenoughupdates/prev.png b/src/main/resources/assets/notenoughupdates/prev.png Binary files differindex a4cef66d..a4ec7743 100644 --- a/src/main/resources/assets/notenoughupdates/prev.png +++ b/src/main/resources/assets/notenoughupdates/prev.png diff --git a/src/main/resources/assets/notenoughupdates/prev.xcf b/src/main/resources/assets/notenoughupdates/prev.xcf Binary files differnew file mode 100644 index 00000000..e9fecc61 --- /dev/null +++ b/src/main/resources/assets/notenoughupdates/prev.xcf diff --git a/src/main/resources/assets/notenoughupdates/prev_pow2.png b/src/main/resources/assets/notenoughupdates/prev_pow2.png Binary files differdeleted file mode 100644 index 3b756716..00000000 --- a/src/main/resources/assets/notenoughupdates/prev_pow2.png +++ /dev/null diff --git a/src/main/resources/assets/notenoughupdates/settings.png b/src/main/resources/assets/notenoughupdates/settings.png Binary files differnew file mode 100644 index 00000000..481a0588 --- /dev/null +++ b/src/main/resources/assets/notenoughupdates/settings.png diff --git a/src/main/resources/assets/notenoughupdates/sort_accessory.png b/src/main/resources/assets/notenoughupdates/sort_accessory.png Binary files differnew file mode 100644 index 00000000..e901e5ba --- /dev/null +++ b/src/main/resources/assets/notenoughupdates/sort_accessory.png diff --git a/src/main/resources/assets/notenoughupdates/sort_accessory_active.png b/src/main/resources/assets/notenoughupdates/sort_accessory_active.png Binary files differnew file mode 100644 index 00000000..dcc4d985 --- /dev/null +++ b/src/main/resources/assets/notenoughupdates/sort_accessory_active.png diff --git a/src/main/resources/assets/notenoughupdates/sort_all.png b/src/main/resources/assets/notenoughupdates/sort_all.png Binary files differnew file mode 100644 index 00000000..d7f2a1e3 --- /dev/null +++ b/src/main/resources/assets/notenoughupdates/sort_all.png diff --git a/src/main/resources/assets/notenoughupdates/sort_all_active.png b/src/main/resources/assets/notenoughupdates/sort_all_active.png Binary files differnew file mode 100644 index 00000000..fe2cc803 --- /dev/null +++ b/src/main/resources/assets/notenoughupdates/sort_all_active.png diff --git a/src/main/resources/assets/notenoughupdates/sort_armor.png b/src/main/resources/assets/notenoughupdates/sort_armor.png Binary files differnew file mode 100644 index 00000000..fab73503 --- /dev/null +++ b/src/main/resources/assets/notenoughupdates/sort_armor.png diff --git a/src/main/resources/assets/notenoughupdates/sort_armor_active.png b/src/main/resources/assets/notenoughupdates/sort_armor_active.png Binary files differnew file mode 100644 index 00000000..37135164 --- /dev/null +++ b/src/main/resources/assets/notenoughupdates/sort_armor_active.png diff --git a/src/main/resources/assets/notenoughupdates/sort_mob.png b/src/main/resources/assets/notenoughupdates/sort_mob.png Binary files differnew file mode 100644 index 00000000..1049e056 --- /dev/null +++ b/src/main/resources/assets/notenoughupdates/sort_mob.png diff --git a/src/main/resources/assets/notenoughupdates/sort_mob_active.png b/src/main/resources/assets/notenoughupdates/sort_mob_active.png Binary files differnew file mode 100644 index 00000000..90ef509e --- /dev/null +++ b/src/main/resources/assets/notenoughupdates/sort_mob_active.png diff --git a/src/main/resources/assets/notenoughupdates/sort_pet.png b/src/main/resources/assets/notenoughupdates/sort_pet.png Binary files differnew file mode 100644 index 00000000..72bcb674 --- /dev/null +++ b/src/main/resources/assets/notenoughupdates/sort_pet.png diff --git a/src/main/resources/assets/notenoughupdates/sort_pet_active.png b/src/main/resources/assets/notenoughupdates/sort_pet_active.png Binary files differnew file mode 100644 index 00000000..ea0c9852 --- /dev/null +++ b/src/main/resources/assets/notenoughupdates/sort_pet_active.png diff --git a/src/main/resources/assets/notenoughupdates/sort_weapon.png b/src/main/resources/assets/notenoughupdates/sort_weapon.png Binary files differnew file mode 100644 index 00000000..f3292f66 --- /dev/null +++ b/src/main/resources/assets/notenoughupdates/sort_weapon.png diff --git a/src/main/resources/assets/notenoughupdates/sort_weapon_active.png b/src/main/resources/assets/notenoughupdates/sort_weapon_active.png Binary files differnew file mode 100644 index 00000000..f6854ce5 --- /dev/null +++ b/src/main/resources/assets/notenoughupdates/sort_weapon_active.png diff --git a/src/main/resources/assets/notenoughupdates/wkhtmltox.zip b/src/main/resources/assets/notenoughupdates/wkhtmltox.zip Binary files differnew file mode 100644 index 00000000..427617a9 --- /dev/null +++ b/src/main/resources/assets/notenoughupdates/wkhtmltox.zip |