diff options
author | DeDiamondPro <67508414+DeDiamondPro@users.noreply.github.com> | 2021-12-29 15:26:46 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-12-29 09:26:46 -0500 |
commit | 98436ad0cf75c9a4bacbfb35a63948cc61c543f9 (patch) | |
tree | a06e98a175174b9e5aecc587499c46c8d1db394a | |
parent | afee9dc68041cfc01e02508a86ba24bb899c29fc (diff) | |
download | NotEnoughUpdates-98436ad0cf75c9a4bacbfb35a63948cc61c543f9.tar.gz NotEnoughUpdates-98436ad0cf75c9a4bacbfb35a63948cc61c543f9.tar.bz2 NotEnoughUpdates-98436ad0cf75c9a4bacbfb35a63948cc61c543f9.zip |
Add price graph (#43)
* graph thingy
* styles
* cool stuff
* move enabled to top
* no pretty printing so smaller files
* general improvements to how data is visualised
* small oopsie
* bazaar support (delete old price data or crash)
* Update AHGraph.java
* remove 1 line so iron doesn't have to
* Update GuiPriceGraph.java
* happy now jani?
* there ironmoon
Co-authored-by: nopothegamer <40329022+nopothegamer@users.noreply.github.com>
11 files changed, 673 insertions, 1 deletions
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/NEUOverlay.java b/src/main/java/io/github/moulberry/notenoughupdates/NEUOverlay.java index 18e0fc3b..acc15e8d 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/NEUOverlay.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/NEUOverlay.java @@ -17,6 +17,7 @@ import io.github.moulberry.notenoughupdates.mbgui.MBGuiElement; import io.github.moulberry.notenoughupdates.mbgui.MBGuiGroupAligned; import io.github.moulberry.notenoughupdates.mbgui.MBGuiGroupFloating; import io.github.moulberry.notenoughupdates.miscfeatures.SunTzu; +import io.github.moulberry.notenoughupdates.miscgui.GuiPriceGraph; import io.github.moulberry.notenoughupdates.options.NEUConfigEditor; import io.github.moulberry.notenoughupdates.util.Constants; import io.github.moulberry.notenoughupdates.util.LerpingFloat; @@ -1037,6 +1038,9 @@ public class NEUOverlay extends Gui { textField.setText("id:" + internalname.get()); itemPaneOpen = true; updateSearch(); + } else if (keyPressed == NotEnoughUpdates.INSTANCE.config.ahGraph.graphKey && NotEnoughUpdates.INSTANCE.config.ahGraph.graphEnabled) { + NotEnoughUpdates.INSTANCE.openGui = new GuiPriceGraph(internalname.get()); + return true; } } } diff --git a/src/main/java/io/github/moulberry/notenoughupdates/auction/APIManager.java b/src/main/java/io/github/moulberry/notenoughupdates/auction/APIManager.java index b3e145a8..254ab5cf 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/auction/APIManager.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/auction/APIManager.java @@ -6,6 +6,7 @@ import com.google.gson.JsonObject; import io.github.moulberry.notenoughupdates.ItemPriceInformation; import io.github.moulberry.notenoughupdates.NEUManager; import io.github.moulberry.notenoughupdates.NotEnoughUpdates; +import io.github.moulberry.notenoughupdates.miscgui.GuiPriceGraph; import io.github.moulberry.notenoughupdates.util.Constants; import io.github.moulberry.notenoughupdates.util.Utils; import net.minecraft.client.Minecraft; @@ -261,6 +262,7 @@ public class APIManager { ItemPriceInformation.updateAuctionableItemsList(); didFirstUpdate = true; } + GuiPriceGraph.addToCache(lowestBins, false); }, () -> {}); } @@ -675,6 +677,7 @@ public class APIManager { bazaarJson.add(entry.getKey().replace(":", "-"), productInfo); } } + GuiPriceGraph.addToCache(bazaarJson, true); }); } diff --git a/src/main/java/io/github/moulberry/notenoughupdates/commands/Commands.java b/src/main/java/io/github/moulberry/notenoughupdates/commands/Commands.java index 97004752..910870d5 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/commands/Commands.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/commands/Commands.java @@ -715,7 +715,7 @@ public class Commands { "Ok, this is actually the last message, use the command again and you'll crash I promise"}; private int devFailIndex = 0; - private static final List<String> devTestUsers = new ArrayList<>(Arrays.asList("moulberry", "lucycoconut", "ironm00n", "ariyio", "throwpo")); + private static final List<String> devTestUsers = new ArrayList<>(Arrays.asList("moulberry", "lucycoconut", "ironm00n", "ariyio", "throwpo", "dediamondpro")); SimpleCommand devTestCommand = new SimpleCommand("neudevtest", new SimpleCommand.ProcessCommandRunnable() { @Override public void processCommand(ICommandSender sender, String[] args) { @@ -748,6 +748,14 @@ public class Commands { DupePOC.doDupe(args[0]); return; }*/ + if (args.length >= 1 && args[0].equalsIgnoreCase("pricetest")) { + if (args.length == 1) { + NotEnoughUpdates.INSTANCE.manager.auctionManager.updateBazaar(); + } else { + NotEnoughUpdates.INSTANCE.openGui = new GuiPriceGraph(args[1]); + } + } + if (args.length == 1 && args[0].equalsIgnoreCase("positiontest")) { NotEnoughUpdates.INSTANCE.openGui = new GuiPositionEditor(); return; diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscgui/GuiPriceGraph.java b/src/main/java/io/github/moulberry/notenoughupdates/miscgui/GuiPriceGraph.java new file mode 100644 index 00000000..a063bafe --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/miscgui/GuiPriceGraph.java @@ -0,0 +1,492 @@ +package io.github.moulberry.notenoughupdates.miscgui; + +import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import io.github.moulberry.notenoughupdates.NotEnoughUpdates; +import io.github.moulberry.notenoughupdates.util.SpecialColour; +import io.github.moulberry.notenoughupdates.util.Utils; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Gui; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.ResourceLocation; +import org.lwjgl.opengl.GL11; + +import java.io.*; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.text.SimpleDateFormat; +import java.time.Instant; +import java.util.*; +import java.util.stream.Collectors; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +public class GuiPriceGraph extends GuiScreen { + + private static final Gson GSON = new GsonBuilder().create(); + private static final SimpleDateFormat format = new SimpleDateFormat("dd-MM-yyyy"); + private final ResourceLocation TEXTURE; + private static final int X_SIZE = 364; + private static final int Y_SIZE = 215; + private Data dataPoints; + private float highestValue; + private long firstTime; + private long lastTime; + private Float lowestValue = null; + private String itemName; + private final String itemId; + private int guiLeft; + private int guiTop; + private ItemStack itemStack = null; + private boolean loaded = false; + /** + * 0 = hour + * 1 = day + * 2 = week + * 3 = all + * 4 = custom + **/ + private int mode = NotEnoughUpdates.INSTANCE.config.ahGraph.defaultMode; + private long customStart = 0; + private long customEnd = 0; + private boolean customSelecting = false; + + public GuiPriceGraph(String itemId) { + switch (NotEnoughUpdates.INSTANCE.config.ahGraph.graphStyle) { + case 1: + TEXTURE = new ResourceLocation("notenoughupdates:price_graph_gui/price_information_gui_dark.png"); + break; + case 2: + TEXTURE = new ResourceLocation("notenoughupdates:price_graph_gui/price_information_gui_phqdark.png"); + break; + case 3: + TEXTURE = new ResourceLocation("notenoughupdates:price_graph_gui/price_information_gui_fsr.png"); + break; + default: + TEXTURE = new ResourceLocation("notenoughupdates:price_graph_gui/price_information_gui.png"); + break; + } + this.itemId = itemId; + if (NotEnoughUpdates.INSTANCE.manager.getItemInformation().containsKey(itemId)) { + JsonObject itemInfo = NotEnoughUpdates.INSTANCE.manager.getItemInformation().get(itemId); + itemName = itemInfo.get("displayname").getAsString(); + itemStack = NotEnoughUpdates.INSTANCE.manager.jsonToStack(itemInfo); + } + loadData(); + } + + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks) { + guiLeft = (width - X_SIZE) / 2; + guiTop = (height - Y_SIZE) / 2; + + Minecraft.getMinecraft().getTextureManager().bindTexture(TEXTURE); + GlStateManager.color(1, 1, 1, 1); + Utils.drawTexturedRect(guiLeft, guiTop, X_SIZE, Y_SIZE, + 0, X_SIZE / 512f, 0, Y_SIZE / 512f, GL11.GL_NEAREST); + Utils.drawTexturedRect(guiLeft + 245, guiTop + 17, 16, 16, + 0, 16 / 512f, (mode == 0 ? 215 : 231) / 512f, (mode == 0 ? 231 : 247) / 512f, GL11.GL_NEAREST); + Utils.drawTexturedRect(guiLeft + 263, guiTop + 17, 16, 16, + 16 / 512f, 32 / 512f, (mode == 1 ? 215 : 231) / 512f, (mode == 1 ? 231 : 247) / 512f, GL11.GL_NEAREST); + Utils.drawTexturedRect(guiLeft + 281, guiTop + 17, 16, 16, + 32 / 512f, 48 / 512f, (mode == 2 ? 215 : 231) / 512f, (mode == 2 ? 231 : 247) / 512f, GL11.GL_NEAREST); + Utils.drawTexturedRect(guiLeft + 299, guiTop + 17, 16, 16, + 48 / 512f, 64 / 512f, (mode == 3 ? 215 : 231) / 512f, (mode == 3 ? 231 : 247) / 512f, GL11.GL_NEAREST); + + if (itemName != null && itemStack != null) { + Utils.drawItemStack(itemStack, guiLeft + 16, guiTop + 11); + Utils.drawStringScaledMax(itemName, Minecraft.getMinecraft().fontRendererObj, guiLeft + 35, guiTop + 13, false, + 0xffffff, 1.77f, 208); + } + + if (!loaded) + Utils.drawStringCentered("Loading...", Minecraft.getMinecraft().fontRendererObj, + guiLeft + 166, guiTop + 116, false, 0xffffff00); + else if (dataPoints == null || dataPoints.get() == null || dataPoints.get().size() <= 1) + Utils.drawStringCentered("No data found.", Minecraft.getMinecraft().fontRendererObj, + guiLeft + 166, guiTop + 116, false, 0xffff0000); + else { + + int graphColor = SpecialColour.specialToChromaRGB(NotEnoughUpdates.INSTANCE.config.ahGraph.graphColor); + int graphColor2 = SpecialColour.specialToChromaRGB(NotEnoughUpdates.INSTANCE.config.ahGraph.graphColor2); + Integer lowestDist = null; + Long lowestDistTime = null; + HashMap<Integer, Integer> secondLineData = new HashMap<>(); + for (int i = (dataPoints.isBz() ? 1 : 0); i >= 0; i--) { + Utils.drawGradientRect(0, guiLeft + 17, guiTop + 35, guiLeft + 315, guiTop + 198, + changeAlpha(i == 0 ? graphColor : graphColor2, 120), changeAlpha(i == 0 ? graphColor : graphColor2, 10)); + Integer prevX = null; + Integer prevY = null; + for (Long time : dataPoints.get().keySet()) { + float price = dataPoints.isBz() ? i == 0 ? dataPoints.bz.get(time).b : dataPoints.bz.get(time).s : dataPoints.ah.get(time); + int xPos = (int) map(time, firstTime, lastTime, guiLeft + 17, guiLeft + 315); + int yPos = (int) map(price, highestValue + 10, lowestValue - 10, guiTop + 35, guiTop + 198); + if (prevX != null) { + Minecraft.getMinecraft().getTextureManager().bindTexture(TEXTURE); + GlStateManager.color(1, 1, 1, 1); + Utils.drawTexturedQuad(prevX, prevY, xPos, yPos, xPos, guiTop + 35, prevX, guiTop + 35, 18 / 512f, 19 / 512f, + 36 / 512f, 37 / 512f, GL11.GL_NEAREST); + if (i == 0) { + Utils.drawLine(prevX, prevY + 0.5f, xPos, yPos + 0.5f, 2, graphColor); + if (dataPoints.isBz()) + Utils.drawLine(prevX, secondLineData.get(prevX) + 0.5f, xPos, secondLineData.get(xPos) + 0.5f, 2, graphColor2); + } + } + if (i == 1) + secondLineData.put(xPos, yPos); + if (mouseX >= guiLeft + 17 && mouseX <= guiLeft + 315 && mouseY >= guiTop + 35 && mouseY <= guiTop + 198) { + int dist = Math.abs(mouseX - xPos); + if (lowestDist == null || dist < lowestDist) { + lowestDist = dist; + lowestDistTime = time; + } + } + prevX = xPos; + prevY = yPos; + } + } + boolean showDays = lastTime - firstTime > 86400; + int prevNum = showDays ? Date.from(Instant.ofEpochSecond(firstTime)).getDate() : Date.from(Instant.ofEpochSecond(firstTime)).getHours(); + long prevXPos = -100; + for (long time = firstTime; time <= lastTime; time += showDays ? 3600 : 60) { + int num = showDays ? Date.from(Instant.ofEpochSecond(time)).getDate() : Date.from(Instant.ofEpochSecond(time)).getHours(); + if (num != prevNum) { + int xPos = (int) map(time, firstTime, lastTime, guiLeft + 17, guiLeft + 315); + if (Math.abs(prevXPos - xPos) > 30) { + Utils.drawStringCentered(String.valueOf(num), Minecraft.getMinecraft().fontRendererObj, + xPos, guiTop + 206, false, 0x8b8b8b); + prevXPos = xPos; + } + prevNum = num; + } + } + for (int i = 0; i <= 6; i++) { + long price = (long) map(i, 0, 6, highestValue, lowestValue); + String formattedPrice = formatPrice(price); + Utils.drawStringF(formattedPrice, Minecraft.getMinecraft().fontRendererObj, guiLeft + 320, + (float) map(i, 0, 6, guiTop + 35, guiTop + 198) + - Minecraft.getMinecraft().fontRendererObj.FONT_HEIGHT / 2f, + false, 0x8b8b8b); + } + if (customSelecting) { + Utils.drawDottedLine(customStart, guiTop + 36, customStart, guiTop + 197, 2, 10, 0xFFc6c6c6); + Utils.drawDottedLine(customEnd, guiTop + 36, customEnd, guiTop + 197, 2, 10, 0xFFc6c6c6); + Utils.drawDottedLine(customStart, guiTop + 36, customEnd, guiTop + 36, 2, 10, 0xFFc6c6c6); + Utils.drawDottedLine(customStart, guiTop + 197, customEnd, guiTop + 197, 2, 10, 0xFFc6c6c6); + } + if (lowestDist != null && !customSelecting) { + float price = dataPoints.isBz() ? dataPoints.bz.get(lowestDistTime).b : dataPoints.ah.get(lowestDistTime); + Float price2 = dataPoints.isBz() ? dataPoints.bz.get(lowestDistTime).s : null; + int xPos = (int) map(lowestDistTime, firstTime, lastTime, guiLeft + 17, guiLeft + 315); + int yPos = (int) map(price, highestValue + 10, lowestValue - 10, guiTop + 35, guiTop + 198); + int yPos2 = price2 != null ? (int) map(price2, highestValue + 10, lowestValue - 10, guiTop + 35, guiTop + 198) : 0; + + Utils.drawLine(xPos, guiTop + 35, xPos, guiTop + 198, 2, 0x4D8b8b8b); + Minecraft.getMinecraft().getTextureManager().bindTexture(TEXTURE); + GlStateManager.color(1, 1, 1, 1); + Utils.drawTexturedRect(xPos - 2.5f, yPos - 2.5f, 5, 5, + 0, 5 / 512f, 247 / 512f, 252 / 512f, GL11.GL_NEAREST); + if (price2 != null) { + Utils.drawTexturedRect(xPos - 2.5f, yPos2 - 2.5f, 5, 5, + 0, 5 / 512f, 247 / 512f, 252 / 512f, GL11.GL_NEAREST); + } + + Date date = Date.from(Instant.ofEpochSecond(lowestDistTime)); + SimpleDateFormat displayFormat = new SimpleDateFormat("'§b'd MMMMM yyyy '§eat§b' HH:mm"); + NumberFormat nf = NumberFormat.getInstance(); + ArrayList<String> text = new ArrayList<>(); + text.add(displayFormat.format(date)); + if (dataPoints.isBz()) { + text.add(EnumChatFormatting.YELLOW + "" + EnumChatFormatting.BOLD + "Bazaar Insta-Buy: " + + EnumChatFormatting.GOLD + EnumChatFormatting.BOLD + nf.format(price)); + text.add(EnumChatFormatting.YELLOW + "" + EnumChatFormatting.BOLD + "Bazaar Insta-Sell: " + + EnumChatFormatting.GOLD + EnumChatFormatting.BOLD + nf.format(price2)); + } else { + text.add(EnumChatFormatting.YELLOW + "" + EnumChatFormatting.BOLD + "Lowest BIN: " + + EnumChatFormatting.GOLD + EnumChatFormatting.BOLD + nf.format(price)); + } + drawHoveringText(text, xPos, yPos); + } + } + + if (mouseY >= guiTop + 17 && mouseY <= guiTop + 35 && mouseX >= guiLeft + 244 && mouseX <= guiLeft + 316) { + int index = (mouseX - guiLeft - 245) / 18; + switch (index) { + case 0: + Gui.drawRect(guiLeft + 245, guiTop + 17, guiLeft + 261, guiTop + 33, 0x80ffffff); + drawHoveringText(Collections.singletonList("Show 1 Hour"), mouseX, mouseY); + break; + case 1: + Gui.drawRect(guiLeft + 263, guiTop + 17, guiLeft + 279, guiTop + 33, 0x80ffffff); + drawHoveringText(Collections.singletonList("Show 1 Day"), mouseX, mouseY); + break; + case 2: + Gui.drawRect(guiLeft + 281, guiTop + 17, guiLeft + 297, guiTop + 33, 0x80ffffff); + drawHoveringText(Collections.singletonList("Show 1 Week"), mouseX, mouseY); + break; + case 3: + Gui.drawRect(guiLeft + 299, guiTop + 17, guiLeft + 315, guiTop + 33, 0x80ffffff); + drawHoveringText(Collections.singletonList("Show All"), mouseX, mouseY); + break; + } + } + } + + @Override + protected void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOException { + super.mouseClicked(mouseX, mouseY, mouseButton); + if (mouseY >= guiTop + 17 && mouseY <= guiTop + 35 && mouseX >= guiLeft + 244 && mouseX <= guiLeft + 316) { + mode = (mouseX - guiLeft - 245) / 18; + loadData(); + } else if (mouseY >= guiTop + 35 && mouseY <= guiTop + 198 && mouseX >= guiLeft + 17 && mouseX <= guiLeft + 315) { + customSelecting = true; + customStart = mouseX; + customEnd = mouseX; + } + } + + @Override + protected void mouseReleased(int mouseX, int mouseY, int state) { + super.mouseReleased(mouseX, mouseY, state); + if (customSelecting) { + customSelecting = false; + customStart = (int) map(customStart, guiLeft + 17, guiLeft + 315, firstTime, lastTime); + customEnd = (int) map(mouseX, guiLeft + 17, guiLeft + 315, firstTime, lastTime); + if (customStart > customEnd) { + long temp = customStart; + customStart = customEnd; + customEnd = temp; + } + if (customEnd - customStart != 0) { + mode = 4; + loadData(); + } + } + } + + @Override + protected void mouseClickMove(int mouseX, int mouseY, int clickedMouseButton, long timeSinceLastClick) { + super.mouseClickMove(mouseX, mouseY, clickedMouseButton, timeSinceLastClick); + if (customSelecting) { + customEnd = mouseX < guiLeft + 18 ? guiLeft + 18 : Math.min(mouseX, guiLeft + 314); + } + } + + private void loadData() { + dataPoints = null; + loaded = false; + new Thread(() -> { + File dir = new File("config/notenoughupdates/prices"); + if (!dir.exists()) { + loaded = true; + return; + } + File[] files = dir.listFiles(); + Data data = new Data(); + if (files == null) return; + for (File file : files) { + if (!file.getName().endsWith(".gz")) continue; + HashMap<String, Data> data2 = load(file); + if (data2 == null || !data2.containsKey(itemId)) continue; + if (data2.get(itemId).isBz()) { + if (data.bz == null) data.bz = data2.get(itemId).bz; + else data.bz.putAll(data2.get(itemId).bz); + } else if (data.ah == null) data.ah = data2.get(itemId).ah; + else data.ah.putAll(data2.get(itemId).ah); + } + if (data.get() != null && !data.get().isEmpty()) { + if (mode < 3) + data = new Data(new TreeMap<>(data.get().entrySet().stream() + .filter(e -> e.getKey() > System.currentTimeMillis() / 1000 - (mode == 0 ? 3600 : mode == 1 ? 86400 : 604800)) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))), data.isBz()); + else if (mode == 4) + data = new Data(new TreeMap<>(data.get().entrySet().stream() + .filter(e -> e.getKey() >= customStart && e.getKey() <= customEnd) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))), data.isBz()); + if (data.get() == null || data.get().isEmpty()) { + loaded = true; + return; + } + dataPoints = trimData(data); + firstTime = dataPoints.get().firstKey(); + lastTime = dataPoints.get().lastKey(); + highestValue = 0; + lowestValue = null; + for (long key : dataPoints.get().keySet()) { + float value1 = dataPoints.isBz() ? dataPoints.bz.get(key).b : dataPoints.ah.get(key); + Float value2 = dataPoints.isBz() ? dataPoints.bz.get(key).s : null; + if (value1 > highestValue) { + highestValue = value1; + } + if (value2 != null && value2 > highestValue) { + highestValue = value2; + } + if (lowestValue == null || value1 < lowestValue) { + lowestValue = value1; + } + if (value2 != null && value2 < lowestValue) { + lowestValue = value2; + } + } + } + loaded = true; + }).start(); + } + + public static void addToCache(JsonObject items, boolean bazaar) { + if (!NotEnoughUpdates.INSTANCE.config.ahGraph.graphEnabled) return; + try { + File dir = new File("config/notenoughupdates/prices"); + if (!dir.exists() && !dir.mkdir()) return; + File[] files = dir.listFiles(); + if (files != null) + for (File file : files) { + if (!file.getName().endsWith(".gz")) continue; + if (file.lastModified() < System.currentTimeMillis() - NotEnoughUpdates.INSTANCE.config.ahGraph.dataRetention * 86400000L) + file.delete(); + } + Date date = new Date(); + Long epochSecond = date.toInstant().getEpochSecond(); + File file = new File(dir, "prices_" + format.format(date) + ".gz"); + HashMap<String, Data> prices = new HashMap<>(); + if (file.exists()) + prices = load(file); + if (prices == null) return; + for (Map.Entry<String, JsonElement> item : items.entrySet()) { + if (prices.containsKey(item.getKey())) { + if (bazaar && item.getValue().getAsJsonObject().has("curr_buy") && item.getValue().getAsJsonObject().has("curr_sell")) + prices.get(item.getKey()).bz.put(epochSecond, new BzData(item.getValue().getAsJsonObject().get("curr_buy").getAsFloat(), + item.getValue().getAsJsonObject().get("curr_sell").getAsFloat())); + else if (!bazaar) + prices.get(item.getKey()).ah.put(epochSecond, item.getValue().getAsInt()); + } else { + TreeMap<Long, Object> mapData = new TreeMap<>(); + if (bazaar && item.getValue().getAsJsonObject().has("curr_buy") && item.getValue().getAsJsonObject().has("curr_sell")) + mapData.put(epochSecond, new BzData(item.getValue().getAsJsonObject().get("curr_buy").getAsFloat(), + item.getValue().getAsJsonObject().get("curr_sell").getAsFloat())); + else if (!bazaar) + mapData.put(epochSecond, item.getValue().getAsLong()); + prices.put(item.getKey(), new Data(mapData, bazaar)); + } + } + //noinspection ResultOfMethodCallIgnored + file.createNewFile(); + try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new GZIPOutputStream(new FileOutputStream(file)), StandardCharsets.UTF_8))) { + writer.write(GSON.toJson(prices)); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + private Data trimData(Data data) { + long first = data.get().firstKey(); + long last = data.get().lastKey(); + Data trimmed = new Data(); + if (data.isBz()) + trimmed.bz = new TreeMap<>(); + else + trimmed.ah = new TreeMap<>(); + int zones = NotEnoughUpdates.INSTANCE.config.ahGraph.graphZones; + Long[] dataArray = !data.isBz() ? data.ah.keySet().toArray(new Long[0]) : data.bz.keySet().toArray(new Long[0]); + int prev = 0; + for (int i = 0; i < zones; i++) { + long lowest = (long) map(i, 0, zones, first, last); + long highest = (long) map(i + 1, 0, zones, first, last); + int amount = 0; + double sumBuy = 0; + double sumSell = 0; + for (int l = prev; l < dataArray.length; l++) { + if (dataArray[l] >= lowest && dataArray[l] <= highest) { + amount++; + sumBuy += data.isBz() ? data.bz.get(dataArray[l]).b : data.ah.get(dataArray[l]); + if (data.isBz()) sumSell += data.bz.get(dataArray[l]).s; + prev = l + 1; + } else if (dataArray[l] > highest) + break; + } + if (amount > 0) { + if (data.isBz()) + trimmed.bz.put(lowest, new BzData((float) (sumBuy / amount), (float) (sumSell / amount))); + else + trimmed.ah.put(lowest, (int) (sumBuy / amount)); + } + } + return trimmed; + } + + + private static HashMap<String, Data> load(File file) { + Type type = new TypeToken<HashMap<String, Data>>() { + }.getType(); + if (file.exists()) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(new GZIPInputStream(new FileInputStream(file)), StandardCharsets.UTF_8))) { + return GSON.fromJson(reader, type); + } catch (Exception ignored) { + } + } + return null; + } + + private static double map(double x, double in_min, double in_max, double out_min, double out_max) { + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; + } + + private static String formatPrice(long price) { + DecimalFormat df = new DecimalFormat("#.00"); + if (price >= 1000000000) { + return df.format(price / 1000000000f) + "B"; + } else if (price >= 1000000) { + return df.format(price / 1000000f) + "M"; + } else if (price >= 1000) { + return df.format(price / 1000f) + "K"; + } + return String.valueOf(price); + } + + private int changeAlpha(int origColor, int alpha) { + origColor = origColor & 0x00ffffff; //drop the previous alpha value + return (alpha << 24) | origColor; //add the one the user inputted + } +} + +class Data { + public TreeMap<Long, Integer> ah = null; + public TreeMap<Long, BzData> bz = null; + + public Data() { + } + + public Data(TreeMap<Long, ?> map, boolean bz) { + if (bz) + this.bz = (TreeMap<Long, BzData>) map; + else + this.ah = (TreeMap<Long, Integer>) map; + } + + public TreeMap<Long, ?> get() { + return !isBz() ? ah : bz; + } + + public boolean isBz() { + return bz != null && !bz.isEmpty(); + } +} + +class BzData { + float b; + float s; + + public BzData(float b, float s) { + this.b = b; + this.s = s; + } +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/options/NEUConfig.java b/src/main/java/io/github/moulberry/notenoughupdates/options/NEUConfig.java index 31930e77..1cb06c52 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/options/NEUConfig.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/options/NEUConfig.java @@ -273,6 +273,13 @@ public class NEUConfig extends Config { @Expose @Category( + name = "AH/BZ Graph", + desc = "Graph of auction and bazaar prices" + ) + public AHGraph ahGraph = new AHGraph(); + + @Expose + @Category( name = "Accessory Bag Overlay", desc = "Accessory Bag Overlay" ) diff --git a/src/main/java/io/github/moulberry/notenoughupdates/options/seperateSections/AHGraph.java b/src/main/java/io/github/moulberry/notenoughupdates/options/seperateSections/AHGraph.java new file mode 100644 index 00000000..ce4c8f09 --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/options/seperateSections/AHGraph.java @@ -0,0 +1,83 @@ +package io.github.moulberry.notenoughupdates.options.seperateSections; + +import com.google.gson.annotations.Expose; +import io.github.moulberry.notenoughupdates.core.config.annotations.*; +import org.lwjgl.input.Keyboard; + +public class AHGraph { + @Expose + @ConfigOption( + name = "Enabled", + desc = "Enable or disable the graph. Disabling this will also make it so that no price data is stored." + ) + @ConfigEditorBoolean + public boolean graphEnabled = true; + + @Expose + @ConfigOption( + name = "Keybind", + desc = "Key to press to open the graph." + ) + @ConfigEditorKeybind(defaultKey = Keyboard.KEY_P) + public int graphKey = Keyboard.KEY_P; + + @Expose + @ConfigOption( + name = "GUI Style", + desc = "Change the style of the graph GUI" + ) + @ConfigEditorDropdown( + values = {"Minecraft", "Dark", "PacksHQ Dark", "FSR"} + ) + public int graphStyle = 0; + + @Expose + @ConfigOption( + name = "Graph Colour", + desc = "Set a custom colour for the graph." + ) + @ConfigEditorColour + public String graphColor = "0:255:0:255:0"; + + @Expose + @ConfigOption( + name = "Secondary Graph Colour", + desc = "Set a custom colour for the second graph line." + ) + @ConfigEditorColour + public String graphColor2 = "0:255:255:255:0"; + + @Expose + @ConfigOption( + name = "Default Time", + desc = "Change the default time period for the graph." + ) + @ConfigEditorDropdown( + values = {"1 Hour", "1 Day", "1 Week", "All Time"} + ) + public int defaultMode = 1; + + @Expose + @ConfigOption( + name = "Data Retention", + desc = "Change the time (in days) that data is kept for.\nLonger retention require more storage." + ) + @ConfigEditorSlider( + minValue = 1, + maxValue = 30, + minStep = 1 + ) + public int dataRetention = 7; + + @Expose + @ConfigOption( + name = "Number of Graph Zones", + desc = "Change the number of graph zones.\nHigher numbers will have more detail, but will look way more cramped." + ) + @ConfigEditorSlider( + minValue = 50, + maxValue = 300, + minStep = 1 + ) + public int graphZones = 175; +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/util/Utils.java b/src/main/java/io/github/moulberry/notenoughupdates/util/Utils.java index f34ba230..dc301db2 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/util/Utils.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/util/Utils.java @@ -472,6 +472,7 @@ public class Utils { //EnumChatFormatting.AQUA+EnumChatFormatting.BOLD.toString()+"DIVINE", }; + public static final HashMap<String, String> rarityArrMap = new HashMap<String, String>() {{ put("COMMON", rarityArrC[0]); put("UNCOMMON", rarityArrC[1]); @@ -828,6 +829,16 @@ public class Utils { GlStateManager.scale(1 / factor, 1 / factor, 1); } + public static void drawStringScaledMax(String str, FontRenderer fr, float x, float y, boolean shadow, int colour, float factor, int len) { + int strLen = fr.getStringWidth(str); + float f = len / (float) strLen; + factor = Math.min(factor, f); + + GlStateManager.scale(factor, factor, 1); + fr.drawString(str, x / factor, y / factor, colour, shadow); + GlStateManager.scale(1 / factor, 1 / factor, 1); + } + public static void drawStringCenteredScaledMaxWidth(String str, FontRenderer fr, float x, float y, boolean shadow, int len, int colour) { int strLen = fr.getStringWidth(str); float factor = len / (float) strLen; @@ -1351,4 +1362,68 @@ public class Utils { return endsIn; } + + public static void drawLine(float sx, float sy, float ex, float ey, int width, int color) { + float f = (float) (color >> 24 & 255) / 255.0F; + float f1 = (float) (color >> 16 & 255) / 255.0F; + float f2 = (float) (color >> 8 & 255) / 255.0F; + float f3 = (float) (color & 255) / 255.0F; + GlStateManager.pushMatrix(); + GlStateManager.disableTexture2D(); + GlStateManager.enableBlend(); + GlStateManager.disableAlpha(); + GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0); + GlStateManager.color(f1, f2, f3, f); + GL11.glLineWidth(width); + GL11.glBegin(GL11.GL_LINES); + GL11.glVertex2d(sx, sy); + GL11.glVertex2d(ex, ey); + GL11.glEnd(); + GlStateManager.disableBlend(); + GlStateManager.enableAlpha(); + GlStateManager.enableTexture2D(); + GlStateManager.popMatrix(); + } + + public static void drawDottedLine(float sx, float sy, float ex, float ey, int width, int factor , int color) { + GlStateManager.pushMatrix(); + GL11.glLineStipple(factor, (short) 0xAAAA); + GL11.glEnable(GL11.GL_LINE_STIPPLE); + drawLine(sx, sy, ex, ey, width, color); + GL11.glDisable(GL11.GL_LINE_STIPPLE); + GlStateManager.popMatrix(); + } + + public static void drawTexturedQuad(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, + float uMin, float uMax, float vMin, float vMax, int filter) { + GlStateManager.enableTexture2D(); + GlStateManager.enableBlend(); + GlStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA); + GL14.glBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA); + + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, filter); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, filter); + + Tessellator tessellator = Tessellator.getInstance(); + WorldRenderer worldrenderer = tessellator.getWorldRenderer(); + worldrenderer.begin(7, DefaultVertexFormats.POSITION_TEX); + worldrenderer + .pos(x1, y1, 0.0D) + .tex(uMin, vMax).endVertex(); + worldrenderer + .pos(x2, y2, 0.0D) + .tex(uMax, vMax).endVertex(); + worldrenderer + .pos(x3, y3, 0.0D) + .tex(uMax, vMin).endVertex(); + worldrenderer + .pos(x4, y4, 0.0D) + .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(); + } } diff --git a/src/main/resources/assets/notenoughupdates/price_graph_gui/price_information_gui.png b/src/main/resources/assets/notenoughupdates/price_graph_gui/price_information_gui.png Binary files differnew file mode 100644 index 00000000..633be870 --- /dev/null +++ b/src/main/resources/assets/notenoughupdates/price_graph_gui/price_information_gui.png diff --git a/src/main/resources/assets/notenoughupdates/price_graph_gui/price_information_gui_dark.png b/src/main/resources/assets/notenoughupdates/price_graph_gui/price_information_gui_dark.png Binary files differnew file mode 100644 index 00000000..0f7743e6 --- /dev/null +++ b/src/main/resources/assets/notenoughupdates/price_graph_gui/price_information_gui_dark.png diff --git a/src/main/resources/assets/notenoughupdates/price_graph_gui/price_information_gui_fsr.png b/src/main/resources/assets/notenoughupdates/price_graph_gui/price_information_gui_fsr.png Binary files differnew file mode 100644 index 00000000..e98bdb7d --- /dev/null +++ b/src/main/resources/assets/notenoughupdates/price_graph_gui/price_information_gui_fsr.png diff --git a/src/main/resources/assets/notenoughupdates/price_graph_gui/price_information_gui_phqdark.png b/src/main/resources/assets/notenoughupdates/price_graph_gui/price_information_gui_phqdark.png Binary files differnew file mode 100644 index 00000000..f794319f --- /dev/null +++ b/src/main/resources/assets/notenoughupdates/price_graph_gui/price_information_gui_phqdark.png |