diff options
15 files changed, 420 insertions, 149 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index a995110..88b22be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [1.8.9-0.14.0] - unreleased ### Added +- Chest Tracker & Analyzer: added support for 'lowest BIN' prices +- Bazaar: display items left on a buy order/sell order (toggleable) - (Dungeons) player lookups: - added ironman icon ♲ - added average secrets per completion @@ -21,7 +23,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Toggle: display dungeon performance summary at the end of a dungeon - Toggle: send warning when queued and entered dungeon floors are different - Toggle: shorten item quality info for non-randomized items -- Bazaar: display items left on a buy order/sell order (toggleable) ### Changed - Disabled `M` keybinding in MC Options > Controls > Cowlection by default to avoid conflicts @@ -26,7 +26,7 @@ It is a collection of different features mainly focused on Hypixel SkyBlock. | Feature | Command/Usage | |-------------------------------------------------------------------------|-----------------------------------------| | Stalk SkyBlock stats of a player | `/moo stalkskyblock` | -| Analyze chests and their Bazaar value on your private island | `/moo analyzeChests` | +| Analyze chests and their Bazaar & lowest BINs value on your private island | `/moo analyzeChests` | | Analyze minions on a private island | `/moo analyzeIsland` | | Improved Dungeon item tooltips (item quality + obtained floor; normalize dungeon item stats) | To normalize stats: Hold <kbd>shift</kbd> (configurable) while viewing a dungeon item tooltip | | Improved Dungeon Party Finder | configure with `/moo config party` | diff --git a/src/main/java/de/cowtipper/cowlection/chesttracker/ChestOverviewGui.java b/src/main/java/de/cowtipper/cowlection/chesttracker/ChestOverviewGui.java index 2c20727..e2be11b 100644 --- a/src/main/java/de/cowtipper/cowlection/chesttracker/ChestOverviewGui.java +++ b/src/main/java/de/cowtipper/cowlection/chesttracker/ChestOverviewGui.java @@ -25,11 +25,14 @@ public class ChestOverviewGui extends GuiScreen { private ItemOverview itemOverview; private List<GuiTooltip> guiTooltips; private GuiButton btnClose; - private GuiButton btnUpdateBazaar; + private GuiButton btnUpdatePrices; private GuiButton btnCopy; - private GuiCheckBox showNonBazaarItems; + private GuiCheckBox chkShowNoPriceItems; + private GuiCheckBox chkShowLowestBinItems; + private GuiCheckBox chkShowBazaarItems; private GuiButton btnBazaarInstantOrOffer; - private AbortableRunnable updateBazaar; + private boolean useBazaarInstantSellPrices; + private AbortableRunnable updatePrices; private final String screenTitle; private final Cowlection main; @@ -44,18 +47,26 @@ public class ChestOverviewGui extends GuiScreen { // close this.buttonList.add(this.btnClose = new GuiButtonExt(1, this.width - 25, 3, 22, 20, EnumChatFormatting.RED + "X")); addTooltip(btnClose, Arrays.asList(EnumChatFormatting.RED + "Close interface", "" + EnumChatFormatting.GRAY + EnumChatFormatting.ITALIC + "Hint:" + EnumChatFormatting.RESET + " alternatively press ESC")); - // update bazaar prices - this.buttonList.add(this.btnUpdateBazaar = new GuiButton(20, this.width - 165, 5, 130, 16, "⟳ Update Bazaar prices")); - addTooltip(btnUpdateBazaar, Arrays.asList(EnumChatFormatting.YELLOW + "Get latest Bazaar prices from Hypixel API", EnumChatFormatting.WHITE + "(only once per minute)")); + // update prices + this.buttonList.add(this.btnUpdatePrices = new GuiButton(20, this.width - 125, 5, 90, 16, "⟳ Update prices")); + addTooltip(btnUpdatePrices, Arrays.asList(EnumChatFormatting.YELLOW + "‣ Get latest Bazaar prices from Hypixel API", EnumChatFormatting.WHITE + " (only once per minute)", + EnumChatFormatting.YELLOW + "‣ Get latest lowest BINs from Moulberry's API", EnumChatFormatting.WHITE + " (only once every 5 minutes)")); // copy to clipboard - this.buttonList.add(this.btnCopy = new GuiButton(21, this.width - 280, 5, 110, 16, "⎘ Copy to clipboard")); + this.buttonList.add(this.btnCopy = new GuiButton(21, this.width - 240, 5, 110, 16, "⎘ Copy to clipboard")); addTooltip(btnCopy, Collections.singletonList(EnumChatFormatting.YELLOW + "Copied data can be pasted into e.g. Google Spreadsheets")); - // checkbox: show/hide non-bazaar items - this.buttonList.add(this.showNonBazaarItems = new GuiCheckBox(10, this.width - 162, this.height - 28, " Show non-Bazaar items", MooConfig.chestAnalyzerShowNonBazaarItems)); - addTooltip(showNonBazaarItems, Collections.singletonList(EnumChatFormatting.YELLOW + "Should items that are " + EnumChatFormatting.GOLD + "not " + EnumChatFormatting.YELLOW + "on the Bazaar be displayed?")); + // toggle: use insta-sell or sell offer prices - this.buttonList.add(this.btnBazaarInstantOrOffer = new GuiButton(15, this.width - 165, this.height - 16, 130, 14, MooConfig.useInstantSellBazaarPrices() ? "Use Instant-Sell prices" : "Use Sell Offer prices")); + this.buttonList.add(this.btnBazaarInstantOrOffer = new GuiButton(15, this.width - 165, this.height - 52, 130, 14, MooConfig.useInstantSellBazaarPrices() ? "Use Instant-Sell prices" : "Use Sell Offer prices")); addTooltip(btnBazaarInstantOrOffer, Collections.singletonList(EnumChatFormatting.YELLOW + "Use " + EnumChatFormatting.GOLD + "Instant-Sell " + EnumChatFormatting.YELLOW + "or " + EnumChatFormatting.GOLD + "Sell Offer" + EnumChatFormatting.YELLOW + " prices?")); + // checkbox: show/hide Bazaar items + this.buttonList.add(this.chkShowBazaarItems = new GuiCheckBox(10, this.width - 162, this.height - 36, " Show Bazaar items", MooConfig.chestAnalyzerShowBazaarItems)); + addTooltip(chkShowBazaarItems, Collections.singletonList(EnumChatFormatting.YELLOW + "Should items with a " + EnumChatFormatting.GOLD + "Bazaar " + EnumChatFormatting.YELLOW + "price be displayed?")); + // checkbox: show/hide lowest BIN items + this.buttonList.add(this.chkShowLowestBinItems = new GuiCheckBox(10, this.width - 162, this.height - 25, " Show lowest BIN items", MooConfig.chestAnalyzerShowLowestBinItems)); + addTooltip(chkShowLowestBinItems, Collections.singletonList(EnumChatFormatting.YELLOW + "Should items with a " + EnumChatFormatting.GOLD + "lowest BIN " + EnumChatFormatting.YELLOW + "price be displayed?")); + // checkbox: show/hide items without a price + this.buttonList.add(this.chkShowNoPriceItems = new GuiCheckBox(10, this.width - 162, this.height - 14, " Show items without price", MooConfig.chestAnalyzerShowNoPriceItems)); + addTooltip(chkShowNoPriceItems, Collections.singletonList(EnumChatFormatting.YELLOW + "Should items " + EnumChatFormatting.GOLD + "without " + EnumChatFormatting.YELLOW + "a Bazaar or BIN price be displayed?")); // main item gui this.itemOverview = new ItemOverview(); } @@ -67,7 +78,7 @@ public class ChestOverviewGui extends GuiScreen { @Override public void drawScreen(int mouseX, int mouseY, float partialTicks) { - btnUpdateBazaar.enabled = updateBazaar == null && main.getChestTracker().allowUpdateBazaar(); + btnUpdatePrices.enabled = updatePrices == null && (main.getChestTracker().allowUpdateBazaar() || main.getChestTracker().allowUpdateLowestBins()); itemOverview.drawScreen(mouseX, mouseY, partialTicks); this.drawString(this.fontRendererObj, this.screenTitle, itemOverview.getLeftX(), 10, 0xFFCC00); super.drawScreen(mouseX, mouseY, partialTicks); @@ -99,74 +110,76 @@ public class ChestOverviewGui extends GuiScreen { if (button.enabled) { if (button == btnClose) { this.mc.displayGuiScreen(null); - } else if (button == btnUpdateBazaar) { - btnUpdateBazaar.enabled = false; - this.main.getChestTracker().refreshBazaarCache(); - updateBazaar = new AbortableRunnable() { - private int retries = 20 * 20; // retry for up to 20 seconds - private final long previousBazaarUpdate = main.getChestTracker().getLastBazaarUpdate(); - - @SubscribeEvent - public void onTickCheckBazaarDataUpdated(TickEvent.ClientTickEvent e) { - if (!stopped && e.phase == TickEvent.Phase.END) { - if (Minecraft.getMinecraft().theWorld == null || retries <= 0) { - // already stopped; or world gone, probably disconnected; or no retries left (took too long [20 seconds not enough?] or is not on SkyBlock): stop! + } else if (button == btnUpdatePrices) { + btnUpdatePrices.enabled = false; + EnumSet<ChestTracker.Updating> updating = this.main.getChestTracker().refreshPriceCache(); + if (updating.size() > 1) { + updatePrices = new AbortableRunnable() { + private int retries = 20 * 20; // retry for up to 20 seconds + private final long previousBazaarUpdate = ChestTracker.lastBazaarUpdate; + private final long previousLowestBinsUpdate = ChestTracker.lastLowestBinsUpdate; + + @SubscribeEvent + public void onTickCheckPriceDataUpdated(TickEvent.ClientTickEvent e) { + if (!stopped && e.phase == TickEvent.Phase.END) { + if (Minecraft.getMinecraft().theWorld == null || retries <= 0) { + // already stopped; or world gone, probably disconnected; or no retries left (took too long [20 seconds not enough?] or is not on SkyBlock): stop! + stop(); + return; + } + retries--; + if (updating.contains(ChestTracker.Updating.BAZAAR) && previousBazaarUpdate == ChestTracker.lastBazaarUpdate + || updating.contains(ChestTracker.Updating.LOWEST_BINS) && previousLowestBinsUpdate == ChestTracker.lastLowestBinsUpdate) { + // cache(s) have not updated yet, retry next tick + return; + } + // refresh item overview + Minecraft.getMinecraft().addScheduledTask(() -> ChestOverviewGui.this.itemOverview.reloadItemData()); stop(); - return; - } - retries--; - if (previousBazaarUpdate == main.getChestTracker().getLastBazaarUpdate()) { - // bazaar data wasn't updated yet, retry next tick - return; } - // refresh item overview - Minecraft.getMinecraft().addScheduledTask(() -> ChestOverviewGui.this.itemOverview.reloadItemData()); - stop(); } - } - @Override - public void stop() { - if (!stopped) { - stopped = true; - retries = -1; - MinecraftForge.EVENT_BUS.unregister(this); - stopScoreboardChecker(); + @Override + public void stop() { + if (!stopped) { + stopped = true; + retries = -1; + MinecraftForge.EVENT_BUS.unregister(this); + stopPriceUpdateChecker(); + } } - } - @Override - public void run() { - MinecraftForge.EVENT_BUS.register(this); - } - }; - new TickDelay(updateBazaar, 20); // 2 second delay + retrying for 20 seconds, making sure bazaar data got updated - } else if (button == showNonBazaarItems) { + @Override + public void run() { + MinecraftForge.EVENT_BUS.register(this); + } + }; + new TickDelay(updatePrices, 20); // 1 second delay + retrying for 20 seconds, making sure price data got updated + } + } else if (button == chkShowNoPriceItems || button == chkShowLowestBinItems || button == chkShowBazaarItems) { this.itemOverview.reloadItemData(); } else if (button == btnCopy) { - StringBuilder allItemData = new StringBuilder("Item\tItem (formatted)\tAmount\tPrice (instant-sell)\tValue (instant-sell)\tPrice (sell offer)\tValue (sell offer)"); + StringBuilder allItemData = new StringBuilder("Item\tItem (formatted)\tAmount\tPrice (instant-sell)\tValue (instant-sell)\tPrice (sell offer)\tValue (sell offer)\tPrice (lowest BIN)\tValue (lowest BIN)"); for (ItemData itemData : itemOverview.itemDataHolder) { allItemData.append(itemData.toCopyableFormat()); } allItemData.append("\n\n").append("Bazaar value (instant-sell):\t").append(itemOverview.summedValueInstaSell) - .append("\n").append("Bazaar value (sell offer):\t").append(itemOverview.summedValueSellOffer); + .append("\n").append("Bazaar value (sell offer):\t").append(itemOverview.summedValueSellOffer) + .append("\n").append("Auction House value (lowest BINs):\t").append(itemOverview.summedValueLowestBins); GuiScreen.setClipboardString(allItemData.toString()); } else if (button == btnBazaarInstantOrOffer) { - if ("Use Instant-Sell prices".equals(btnBazaarInstantOrOffer.displayString)) { - btnBazaarInstantOrOffer.displayString = "Use Sell Offer prices"; - } else { - btnBazaarInstantOrOffer.displayString = "Use Instant-Sell prices"; - } + this.btnBazaarInstantOrOffer.displayString = this.useBazaarInstantSellPrices ? "Use Sell Offer prices" : "Use Instant-Sell prices"; + this.useBazaarInstantSellPrices = !this.useBazaarInstantSellPrices; this.itemOverview.reloadItemData(); } } } - private void stopScoreboardChecker() { - if (updateBazaar != null) { - // there is still a bazaar update-checker running, stop it - updateBazaar.stop(); - updateBazaar = null; + private void stopPriceUpdateChecker() { + if (updatePrices != null) { + // there is still a price update-checker running, stop it + updatePrices.stop(); + updatePrices = null; } } @@ -200,11 +213,15 @@ public class ChestOverviewGui extends GuiScreen { private boolean orderDesc = true; private long lastOrderChange; private List<ItemData> itemDataHolder; - private int summedValueInstaSell; - private int summedValueSellOffer; + private long summedValueInstaSell; + private long summedValueSellOffer; + private long summedValueLowestBins; + private long summedTotalValue; + private boolean showBazaarItems; + private boolean showLowestBinItems; ItemOverview() { - super(ChestOverviewGui.this.mc, ChestOverviewGui.this.width, ChestOverviewGui.this.height, 32, ChestOverviewGui.this.height - 32, 16); + super(ChestOverviewGui.this.mc, ChestOverviewGui.this.width, ChestOverviewGui.this.height, 32, ChestOverviewGui.this.height - 54, 16); this.setShowSelectionBox(false); // space above first entry for control buttons int headerPadding = 20; @@ -214,26 +231,46 @@ public class ChestOverviewGui extends GuiScreen { } private void reloadItemData() { - boolean useInstantSellPrices = "Use Instant-Sell prices".equals(btnBazaarInstantOrOffer.displayString); + boolean useInstantSellPrices = ChestOverviewGui.this.useBazaarInstantSellPrices; itemDataHolder = main.getChestTracker().getAnalysisResult(orderBy, orderDesc, useInstantSellPrices); summedValueInstaSell = 0; summedValueSellOffer = 0; - boolean showNonBazaarItems = ChestOverviewGui.this.showNonBazaarItems.isChecked(); + summedValueLowestBins = 0; + summedTotalValue = 0; + showBazaarItems = ChestOverviewGui.this.chkShowBazaarItems.isChecked(); + showLowestBinItems = ChestOverviewGui.this.chkShowLowestBinItems.isChecked(); + boolean showNoPriceItems = ChestOverviewGui.this.chkShowNoPriceItems.isChecked(); for (Iterator<ItemData> iterator = itemDataHolder.iterator(); iterator.hasNext(); ) { ItemData itemData = iterator.next(); - boolean hasBazaarPrice = false; - if (itemData.getBazaarInstantSellPrice() > 0) { - summedValueInstaSell += itemData.getBazaarInstantSellValue(); - hasBazaarPrice = true; - } - if (itemData.getBazaarSellOfferPrice() > 0) { - summedValueSellOffer += itemData.getBazaarSellOfferValue(); - hasBazaarPrice = true; - } - if (!showNonBazaarItems && !hasBazaarPrice) { - iterator.remove(); + switch (itemData.getPriceType()) { + case BAZAAR: + summedValueInstaSell += itemData.getBazaarInstantSellValue(); + summedValueSellOffer += itemData.getBazaarSellOfferValue(); + if (showBazaarItems) { + continue; + } + break; + case LOWEST_BIN: + summedValueLowestBins += itemData.getLowestBinValue(); + if (showLowestBinItems) { + continue; + } + break; + default: // case NONE: + if (showNoPriceItems) { + continue; + } + break; } + // otherwise: hide item + iterator.remove(); + } + if (showLowestBinItems) { + summedTotalValue = summedValueLowestBins; + } + if (showBazaarItems) { + summedTotalValue += (useInstantSellPrices ? summedValueInstaSell : summedValueSellOffer); } } @@ -247,7 +284,7 @@ public class ChestOverviewGui extends GuiScreen { // draw column titles for (Column column : Column.values()) { int columnX = x + column.getXOffset() - (column != Column.ITEM_NAME ? ChestOverviewGui.this.fontRendererObj.getStringWidth(column.getName()) : /* item name is aligned left, rest right */ 0); - ChestOverviewGui.this.drawString(ChestOverviewGui.this.fontRendererObj, column.getName(), columnX, y + 2, 0xFFFFFF); + ChestOverviewGui.this.fontRendererObj.drawStringWithShadow(column.getName(), columnX, y + 2, 0xFFFFFF); if (column == orderBy) { arrowX = columnX; } @@ -346,25 +383,40 @@ public class ChestOverviewGui extends GuiScreen { if (itemName.length() != itemData.getName().length()) { itemName += "…"; } - ChestOverviewGui.this.drawString(fontRenderer, itemName, itemNameXPos, y + 1, entryID % 2 == 0 ? 0xFFFFFF : 0x909090); - ChestOverviewGui.this.drawString(fontRenderer, itemAmount, amountXPos, y + 1, entryID % 2 == 0 ? 0xFFFFFF : 0x909090); + fontRenderer.drawStringWithShadow(itemName, itemNameXPos, y + 1, entryID % 2 == 0 ? 0xFFFFFF : 0x909090); + fontRenderer.drawStringWithShadow(itemAmount, amountXPos, y + 1, entryID % 2 == 0 ? 0xFFFFFF : 0x909090); - boolean useInstantSellPrices = "Use Instant-Sell prices".equals(btnBazaarInstantOrOffer.displayString); - double itemPrice = useInstantSellPrices ? itemData.getBazaarInstantSellPrice() : itemData.getBazaarSellOfferPrice(); - String bazaarPrice = itemPrice > 0 ? Utils.formatDecimal(itemPrice) : EnumChatFormatting.DARK_GRAY + "?"; - ChestOverviewGui.this.drawString(fontRenderer, bazaarPrice, x + Column.PRICE_EACH.getXOffset() - fontRenderer.getStringWidth(bazaarPrice), y + 1, entryID % 2 == 0 ? 0xFFFFFF : 0x909090); + boolean useInstantSellPrices = ChestOverviewGui.this.useBazaarInstantSellPrices; + double itemPrice = itemData.getPrice(useInstantSellPrices); + String bazaarOrBinPrice = itemPrice > 0 ? Utils.formatDecimal(itemPrice) : EnumChatFormatting.DARK_GRAY + "?"; + fontRenderer.drawStringWithShadow(bazaarOrBinPrice, x + Column.PRICE_EACH.getXOffset() - fontRenderer.getStringWidth(bazaarOrBinPrice), y + 1, entryID % 2 == 0 ? 0xFFFFFF : 0x909090); - double itemValue = useInstantSellPrices ? itemData.getBazaarInstantSellValue() : itemData.getBazaarSellOfferValue(); - String bazaarValue = itemPrice > 0 ? Utils.formatNumber(itemValue) : EnumChatFormatting.DARK_GRAY + "?"; - ChestOverviewGui.this.drawString(fontRenderer, bazaarValue, x + Column.PRICE_SUM.getXOffset() - fontRenderer.getStringWidth(bazaarValue), y + 1, entryID % 2 == 0 ? 0xFFFFFF : 0x909090); + double itemValue = itemData.getPriceSum(useInstantSellPrices); + String bazaarOrBinValue = itemPrice > 0 ? Utils.formatNumber(itemValue) : EnumChatFormatting.DARK_GRAY + "?"; + fontRenderer.drawStringWithShadow(bazaarOrBinValue, x + Column.PRICE_SUM.getXOffset() - fontRenderer.getStringWidth(bazaarOrBinValue), y + 1, entryID % 2 == 0 ? 0xFFFFFF : 0x909090); } public void drawScreenPost(int mouseX, int mouseY) { int xMin = getLeftX(); - String bazaarValueInstantSell = "∑ Bazaar value (instant-sell prices): " + (summedValueInstaSell > 0 ? EnumChatFormatting.WHITE + Utils.formatNumber(summedValueInstaSell) : EnumChatFormatting.DARK_GRAY + "?"); // sum - ChestOverviewGui.this.drawString(ChestOverviewGui.this.fontRendererObj, bazaarValueInstantSell, xMin + 175 - ChestOverviewGui.this.fontRendererObj.getStringWidth("∑ Bazaar value (instant-sell prices): "), ChestOverviewGui.this.height - 28, 0xdddddd); - String bazaarValueSellOffer = "∑ Bazaar value (sell offer prices): " + (summedValueSellOffer > 0 ? EnumChatFormatting.WHITE + Utils.formatNumber(summedValueSellOffer) : EnumChatFormatting.DARK_GRAY + "?"); // sum - ChestOverviewGui.this.drawString(ChestOverviewGui.this.fontRendererObj, bazaarValueSellOffer, xMin + 175 - ChestOverviewGui.this.fontRendererObj.getStringWidth("∑ Bazaar value (sell offer prices): "), ChestOverviewGui.this.height - 17, 0xdddddd); + FontRenderer fontRenderer = ChestOverviewGui.this.fontRendererObj; + + boolean useInstantSellPrices = ChestOverviewGui.this.useBazaarInstantSellPrices; + + String bazaarValueInstantSell = ((showBazaarItems && useInstantSellPrices) ? EnumChatFormatting.WHITE : EnumChatFormatting.DARK_GRAY) + "∑ Bazaar value (instant-sell prices): " + (summedValueInstaSell > 0 ? Utils.formatNumber(summedValueInstaSell) : EnumChatFormatting.DARK_GRAY + "―"); // sum + fontRenderer.drawStringWithShadow(bazaarValueInstantSell, xMin + 175 - fontRenderer.getStringWidth("∑ Bazaar value (instant-sell prices): "), ChestOverviewGui.this.height - 50, 0xdddddd); + String bazaarValueSellOffer = ((showBazaarItems && !useInstantSellPrices) ? EnumChatFormatting.WHITE : EnumChatFormatting.DARK_GRAY) + "∑ Bazaar value (sell offer prices): " + (summedValueSellOffer > 0 ? Utils.formatNumber(summedValueSellOffer) : EnumChatFormatting.DARK_GRAY + "―"); // sum + fontRenderer.drawStringWithShadow(bazaarValueSellOffer, xMin + 175 - fontRenderer.getStringWidth("∑ Bazaar value (sell offer prices): "), ChestOverviewGui.this.height - 39, 0xdddddd); + String lowestBinValue = ((showLowestBinItems) ? EnumChatFormatting.WHITE : EnumChatFormatting.DARK_GRAY) + "∑ Auction House value (lowest BINs): " + (summedValueLowestBins > 0 ? Utils.formatNumber(summedValueLowestBins) : EnumChatFormatting.DARK_GRAY + "―"); // sum + fontRenderer.drawStringWithShadow(lowestBinValue, xMin + 175 - fontRenderer.getStringWidth("∑ Auction House value (lowest BINs): "), ChestOverviewGui.this.height - 28, 0xdddddd); + String estimatedSellValue = ((showBazaarItems || showLowestBinItems) ? EnumChatFormatting.WHITE : EnumChatFormatting.DARK_GRAY) + + "∑ estimated sell value (" + + (showBazaarItems ? (useInstantSellPrices ? "insta" : "offer") : "") + + (showLowestBinItems ? (showBazaarItems ? "+" : "") + "BIN" : "") + + "): "; + String totalValue = estimatedSellValue + (summedTotalValue > 0 ? EnumChatFormatting.WHITE + Utils.formatNumber(summedTotalValue) : EnumChatFormatting.DARK_GRAY + "―"); // sum + int estimatedTotalEndX = fontRenderer.drawStringWithShadow(totalValue, xMin + 175 - fontRenderer.getStringWidth(estimatedSellValue), ChestOverviewGui.this.height - 13, 0xdddddd); + + drawRect(3, ChestOverviewGui.this.height - 16, estimatedTotalEndX + 2, ChestOverviewGui.this.height - 15, 0xff555555); if (isMouseYWithinSlotBounds(mouseY)) { int slotIndex = this.getSlotIndexFromScreenCoords(mouseX, mouseY); @@ -379,7 +431,7 @@ public class ChestOverviewGui extends GuiScreen { ItemData itemData = itemDataHolder.get(slotIndex); FontRenderer font = itemData.getItemStack().getItem().getFontRenderer(itemData.getItemStack()); GlStateManager.pushMatrix(); - ChestOverviewGui.this.drawHoveringText(itemData.getItemStack().getTooltip(mc.thePlayer, false), mouseX, mouseY, (font == null ? fontRendererObj : font)); + ChestOverviewGui.this.drawHoveringText(itemData.getItemStack().getTooltip(mc.thePlayer, false), mouseX, mouseY, (font == null ? ChestOverviewGui.this.fontRendererObj : font)); GlStateManager.popMatrix(); } } diff --git a/src/main/java/de/cowtipper/cowlection/chesttracker/ChestTracker.java b/src/main/java/de/cowtipper/cowlection/chesttracker/ChestTracker.java index 64e4067..4ff6fe2 100644 --- a/src/main/java/de/cowtipper/cowlection/chesttracker/ChestTracker.java +++ b/src/main/java/de/cowtipper/cowlection/chesttracker/ChestTracker.java @@ -1,29 +1,39 @@ package de.cowtipper.cowlection.chesttracker; import de.cowtipper.cowlection.Cowlection; +import de.cowtipper.cowlection.data.DataHelper; +import de.cowtipper.cowlection.data.HySkyBlockStats; import de.cowtipper.cowlection.util.ApiUtils; +import de.cowtipper.cowlection.util.GsonUtils; import de.cowtipper.cowlection.util.MooChatComponent; import net.minecraft.init.Blocks; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.nbt.NBTTagString; import net.minecraft.util.BlockPos; +import net.minecraft.util.EnumChatFormatting; import net.minecraft.util.EnumFacing; import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.common.util.Constants; import java.util.*; public class ChestTracker { + public static long lastBazaarUpdate; + public static long lastLowestBinsUpdate; private final Map<BlockPos, List<ItemStack>> chestCache = new HashMap<>(); private final Map<BlockPos, EnumFacing> doubleChestCache = new HashMap<>(); private Map<String, ItemData> analysisResult = new HashMap<>(); private ChestInteractionListener chestInteractionListener; private HyBazaarData bazaarCache; - private long lastBazaarUpdate; + private LowestBinsCache lowestBinsCache; private final Cowlection main; public ChestTracker(Cowlection main) { this.main = main; - refreshBazaarCache(); + refreshPriceCache(); chestInteractionListener = new ChestInteractionListener(main); MinecraftForge.EVENT_BUS.register(chestInteractionListener); } @@ -34,12 +44,30 @@ public class ChestTracker { for (ItemStack item : chestContents) { String key = item.hasDisplayName() ? item.getDisplayName() : item.getUnlocalizedName(); + boolean isAmbiguousItem = false; if (item.hasTagCompound()) { key = item.getTagCompound().getCompoundTag("ExtraAttributes").getString("id"); } + if ("PET".equals(key)) { + HySkyBlockStats.Profile.Pet petInfo = GsonUtils.fromJson(item.getTagCompound().getCompoundTag("ExtraAttributes").getString("petInfo"), HySkyBlockStats.Profile.Pet.class); + key = petInfo.getType() + ";" + petInfo.getRarity().ordinal(); + // remove pet lvl from name, as lowest BINs also disregard it + String petName = item.getDisplayName(); + int endOfPetLevel = petName.indexOf(']'); + if (petName.startsWith(EnumChatFormatting.GRAY + "[Lvl ") && endOfPetLevel > 0) { + item.setStackDisplayName(EnumChatFormatting.GRAY + "[Lvl " + EnumChatFormatting.DARK_GRAY + "?" + EnumChatFormatting.GRAY + petName.substring(endOfPetLevel)); + } + } else if (DataHelper.AMBIGUOUS_ITEM_IDS.contains(key)) { + isAmbiguousItem = true; + key += "_ambiguous"; + } ItemData itemData = itemCounts.get(key); if (itemData == null) { + // item hasn't been cached yet + if (isAmbiguousItem) { + convertToDummyItem(item, key); + } itemData = new ItemData(key, item.copy()); } itemCounts.put(key, itemData.addAmount(item.stackSize)); @@ -48,6 +76,46 @@ public class ChestTracker { this.analysisResult = itemCounts; } + private void convertToDummyItem(ItemStack item, String key) { + NBTTagCompound itemNbtDisplay = item.getSubCompound("display", true); + NBTTagList loreList = new NBTTagList(); + loreList.appendTag(new NBTTagString("" + EnumChatFormatting.RED + EnumChatFormatting.ITALIC + "This ambiguous item type")); + loreList.appendTag(new NBTTagString("" + EnumChatFormatting.RED + EnumChatFormatting.ITALIC + "is not listed separately.")); + itemNbtDisplay.setTag("Lore", loreList); + String itemName = null; + switch (key) { + case "ENCHANTED_BOOK_ambiguous": + itemName = "Enchanted Book"; + break; + case "POTION_ambiguous": + itemName = "Potion"; + break; + case "RUNE_ambiguous": + itemName = "Rune"; + NBTTagCompound skullNbtTextureData = item.getSubCompound("SkullOwner", false); + if (skullNbtTextureData != null) { + skullNbtTextureData.setString("Id", UUID.randomUUID().toString()); + NBTTagCompound nbtSkin = new NBTTagCompound(); + // set texture to Empty Rune + nbtSkin.setString("Value", "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvODJiODIwN2E1ZmUxOTJjZDQ3N2U5MjE0NjYxOTdjOGFmNzQ5YWYxOGRkMWVmMzg5ZTI3MzNhMmY3NGQwOTI4YiJ9fX0="); + skullNbtTextureData.getCompoundTag("Properties").getTagList("textures", Constants.NBT.TAG_COMPOUND).set(0, nbtSkin); + } + break; + case "NEW_YEAR_CAKE_ambiguous": + itemName = "New Year Cake"; + break; + case "SPOOKY_PIE_ambiguous": + itemName = "Spooky Pie"; + break; + case "CAKE_SOUL_ambiguous": + itemName = "Cake Soul"; + break; + } + if (itemName != null) { + item.setStackDisplayName(EnumChatFormatting.GRAY + itemName); + } + } + /** * Returns ordered analysis result with prices */ @@ -55,6 +123,7 @@ public class ChestTracker { List<ItemData> orderedAnalysisResult = new ArrayList<>(); // sort by bazaar value (most value first) for (Map.Entry<String, ItemData> itemEntry : analysisResult.entrySet()) { + boolean foundPriceForItem = false; if (bazaarCache != null && bazaarCache.isSuccess()) { String productKey = itemEntry.getKey(); HyBazaarData.Product product = bazaarCache.getProduct(productKey); @@ -62,6 +131,15 @@ public class ChestTracker { // item is sold on bazaar! itemEntry.getValue().setBazaarInstantSellPrice(product.getInstantSellPrice()); itemEntry.getValue().setBazaarSellOfferPrice(product.getSellOfferPrice()); + foundPriceForItem = true; + } + } + if (!foundPriceForItem && lowestBinsCache != null && lowestBinsCache.size() > 0) { + String productKey = itemEntry.getKey().replace(':', '-'); + Integer lowestBin = lowestBinsCache.get(productKey); + if (lowestBin != null) { + // item is sold via BIN + itemEntry.getValue().setLowestBin(lowestBin); } } orderedAnalysisResult.add(itemEntry.getValue()); @@ -75,10 +153,10 @@ public class ChestTracker { comparator = Comparator.comparing(ItemData::getAmount); break; case PRICE_EACH: - comparator = useInstantSellPrices ? Comparator.comparing(ItemData::getBazaarInstantSellPrice) : Comparator.comparing(ItemData::getBazaarSellOfferPrice); + comparator = Comparator.comparing(itemData -> itemData.getPrice(useInstantSellPrices)); break; default: // case PRICE_SUM: - comparator = useInstantSellPrices ? Comparator.comparing(ItemData::getBazaarInstantSellValue) : Comparator.comparing(ItemData::getBazaarSellOfferValue); + comparator = Comparator.comparing(itemData -> itemData.getPriceSum(useInstantSellPrices)); break; } orderedAnalysisResult.sort((orderDesc ? comparator.reversed() : comparator).thenComparing(ItemData::getName)); @@ -93,6 +171,7 @@ public class ChestTracker { MinecraftForge.EVENT_BUS.unregister(chestInteractionListener); chestInteractionListener = null; bazaarCache = null; + lowestBinsCache = null; chestCache.clear(); doubleChestCache.clear(); analysisResult.clear(); @@ -148,23 +227,46 @@ public class ChestTracker { return doubleChestCache.getOrDefault(pos, EnumFacing.UP); } - public void refreshBazaarCache() { + public EnumSet<Updating> refreshPriceCache() { + EnumSet<Updating> updating = EnumSet.of(Updating.UNDEFINED); if (allowUpdateBazaar()) { + updating.add(Updating.BAZAAR); ApiUtils.fetchBazaarData(bazaarData -> { if (bazaarData == null || !bazaarData.isSuccess()) { main.getChatHelper().sendMessage(new MooChatComponent("Error: Couldn't get Bazaar data from Hypixel API! API might be down: check status.hypixel.net").red().setUrl("https://status.hypixel.net/")); } this.bazaarCache = bazaarData; - this.lastBazaarUpdate = System.currentTimeMillis(); + lastBazaarUpdate = System.currentTimeMillis(); + }); + } + if (allowUpdateLowestBins()) { + updating.add(Updating.LOWEST_BINS); + ApiUtils.fetchLowestBins(lowestBins -> { + if (!lowestBins.hasData()) { + main.getChatHelper().sendMessage(new MooChatComponent("Error: Couldn't get lowest BINs from Moulberry's API! API might be down: check if " + ApiUtils.LOWEST_BINS + " is reachable.").red().setUrl(ApiUtils.LOWEST_BINS)); + } + this.lowestBinsCache = lowestBins; + lastLowestBinsUpdate = System.currentTimeMillis(); }); } + return updating; } + /** + * Allow bazaar update once per minute + */ public boolean allowUpdateBazaar() { return bazaarCache == null || bazaarCache.allowRefreshData(); } - public long getLastBazaarUpdate() { - return this.lastBazaarUpdate; + /** + * Allow lowest bins update once every 5 minutes + */ + public boolean allowUpdateLowestBins() { + return lowestBinsCache == null || (System.currentTimeMillis() - lastLowestBinsUpdate) > 300000; + } + + public enum Updating { + UNDEFINED, BAZAAR, LOWEST_BINS } } diff --git a/src/main/java/de/cowtipper/cowlection/chesttracker/ItemData.java b/src/main/java/de/cowtipper/cowlection/chesttracker/ItemData.java index 05f52bf..e9bae4e 100644 --- a/src/main/java/de/cowtipper/cowlection/chesttracker/ItemData.java +++ b/src/main/java/de/cowtipper/cowlection/chesttracker/ItemData.java @@ -8,8 +8,10 @@ public class ItemData { private final ItemStack itemStack; private final String name; private int amount; - private double bazaarInstantSellPrice = -1; - private double bazaarSellOfferPrice = -1; + private double bazaarInstantSellPrice = 0; + private double bazaarSellOfferPrice = 0; + private int lowestBin = 0; + private PriceType priceType; public ItemData(String key, ItemStack itemStack) { this.key = key; @@ -17,6 +19,7 @@ public class ItemData { this.itemStack.stackSize = 1; this.name = itemStack.getDisplayName(); this.amount = 0; + this.priceType = PriceType.NONE; } public String getKey() { @@ -35,20 +38,41 @@ public class ItemData { return amount; } - public double getBazaarInstantSellPrice() { - return bazaarInstantSellPrice; + public double getPrice(boolean useInstantSellPrices) { + switch (priceType) { + case BAZAAR: + return useInstantSellPrices ? bazaarInstantSellPrice : bazaarSellOfferPrice; + case LOWEST_BIN: + return lowestBin; + default: + return 0; + } } - public void setBazaarInstantSellPrice(double bazaarInstantSellPrice) { - this.bazaarInstantSellPrice = bazaarInstantSellPrice; + public double getPriceSum(boolean useInstantSellPrices) { + switch (priceType) { + case BAZAAR: + return useInstantSellPrices ? getBazaarInstantSellValue() : getBazaarSellOfferValue(); + case LOWEST_BIN: + return getLowestBinValue(); + default: + return 0; + } } - public double getBazaarSellOfferPrice() { - return bazaarSellOfferPrice; + public void setBazaarInstantSellPrice(double bazaarInstantSellPrice) { + this.bazaarInstantSellPrice = bazaarInstantSellPrice; + this.priceType = PriceType.BAZAAR; } public void setBazaarSellOfferPrice(double bazaarSellOfferPrice) { this.bazaarSellOfferPrice = bazaarSellOfferPrice; + this.priceType = PriceType.BAZAAR; + } + + public void setLowestBin(int lowestBin) { + this.lowestBin = lowestBin; + this.priceType = PriceType.LOWEST_BIN; } public ItemData addAmount(int stackSize) { @@ -57,14 +81,30 @@ public class ItemData { } public double getBazaarInstantSellValue() { - return bazaarInstantSellPrice >= 0 ? amount * bazaarInstantSellPrice : -1; + return amount * bazaarInstantSellPrice; } public double getBazaarSellOfferValue() { - return bazaarSellOfferPrice >= 0 ? amount * bazaarSellOfferPrice : -1; + return amount * bazaarSellOfferPrice; + } + + public long getLowestBinValue() { + return (long) amount * lowestBin; + } + + public PriceType getPriceType() { + return priceType; } public String toCopyableFormat() { - return "\n" + EnumChatFormatting.getTextWithoutFormattingCodes(name) + "\t" + name + "\t" + amount + "\t" + Math.round(getBazaarInstantSellPrice()) + "\t" + Math.round(getBazaarInstantSellValue()) + "\t" + Math.round(getBazaarSellOfferPrice()) + "\t" + Math.round(getBazaarSellOfferValue()); + return "\n" + EnumChatFormatting.getTextWithoutFormattingCodes(name) + "\t" + name + "\t" + amount + "\t" + toCopyableFormat(bazaarInstantSellPrice) + "\t" + toCopyableFormat(getBazaarInstantSellValue()) + "\t" + toCopyableFormat(bazaarSellOfferPrice) + "\t" + toCopyableFormat(getBazaarSellOfferValue()) + "\t" + toCopyableFormat(lowestBin) + "\t" + toCopyableFormat(getLowestBinValue()); + } + + private String toCopyableFormat(double value) { + return value > 0 ? Long.toString(Math.round(value)) : ""; + } + + public enum PriceType { + BAZAAR, LOWEST_BIN, NONE } } diff --git a/src/main/java/de/cowtipper/cowlection/chesttracker/LowestBinsCache.java b/src/main/java/de/cowtipper/cowlection/chesttracker/LowestBinsCache.java new file mode 100644 index 0000000..4fb7b26 --- /dev/null +++ b/src/main/java/de/cowtipper/cowlection/chesttracker/LowestBinsCache.java @@ -0,0 +1,10 @@ +package de.cowtipper.cowlection.chesttracker; + +import java.util.HashMap; + +public class LowestBinsCache extends HashMap<String, Integer> { + + public boolean hasData() { + return size() > 0; + } +} diff --git a/src/main/java/de/cowtipper/cowlection/config/MooConfig.java b/src/main/java/de/cowtipper/cowlection/config/MooConfig.java index d6e93cd..ba1c2dc 100644 --- a/src/main/java/de/cowtipper/cowlection/config/MooConfig.java +++ b/src/main/java/de/cowtipper/cowlection/config/MooConfig.java @@ -74,8 +74,10 @@ public class MooConfig { public static int notifyFreshServer; public static int notifyOldServer; public static boolean notifyServerAge; - public static boolean chestAnalyzerShowNonBazaarItems; + public static boolean chestAnalyzerShowBazaarItems; private static String chestAnalyzerUseBazaarPrices; + public static boolean chestAnalyzerShowLowestBinItems; + public static boolean chestAnalyzerShowNoPriceItems; public static boolean chestAnalyzerShowCommandUsage; public static int tooltipToggleKeyBinding; private static String tooltipItemAge; @@ -393,17 +395,21 @@ public class MooConfig { Property propNotifyServerAge = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), "notifyServerAge", true, "Show server age notifications?")); - // Sub-Category: Chest Analyzer (Bazaar prices) - subCat = configCat.addSubCategory("Chest Tracker & Analyzer (Bazaar prices)"); + // Sub-Category: Chest Analyzer (Bazaar & lowest BIN prices) + subCat = configCat.addSubCategory("Chest Tracker & Analyzer (Bazaar & lowest BIN prices)"); String analyzeCommand = "/moo analyzeChests"; subCat.addExplanations("Use " + EnumChatFormatting.YELLOW + analyzeCommand + EnumChatFormatting.RESET + " to start tracking chests on your island! " + EnumChatFormatting.GREEN + "Then you can...", EnumChatFormatting.GREEN + " ❶ " + EnumChatFormatting.RESET + "add chests by opening them; deselect chests by Sneaking + Right Click.", EnumChatFormatting.GREEN + " ❷ " + EnumChatFormatting.RESET + "use " + EnumChatFormatting.YELLOW + analyzeCommand + EnumChatFormatting.RESET + " again to run the chest analysis.", EnumChatFormatting.GREEN + " ❸ " + EnumChatFormatting.RESET + "use " + EnumChatFormatting.YELLOW + analyzeCommand + " stop" + EnumChatFormatting.RESET + " to stop the chest tracker and clear current results."); - Property propChestAnalyzerShowNonBazaarItems = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), - "chestAnalyzerShowNonBazaarItems", false, "Show non-Bazaar items in Chest Tracker?")); + Property propChestAnalyzerShowBazaarItems = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), + "chestAnalyzerShowBazaarItems", true, "Show Bazaar items in Chest Tracker?")); Property propChestAnalyzerUseBazaarPrices = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), "chestAnalyzerUseBazaarPrices", "Instant-Sell", "Use Bazaar prices?", new String[]{"Instant-Sell", "Sell Offer"})); + Property propChestAnalyzerShowLowestBinItems = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), + "chestAnalyzerShowLowestBinItems", true, "Show lowest BIN items in Chest Tracker?")); + Property propChestAnalyzerShowNoPriceItems = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), + "chestAnalyzerShowNoPriceItems", false, "Show items without price in Chest Tracker?")); Property propChestAnalyzerShowCommandUsage = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), "chestAnalyzerShowCommandUsage", true, "Show command usage?")); @@ -684,8 +690,10 @@ public class MooConfig { notifyFreshServer = propNotifyFreshServer.getInt(); notifyOldServer = propNotifyOldServer.getInt(); notifyServerAge = propNotifyServerAge.getBoolean(); - chestAnalyzerShowNonBazaarItems = propChestAnalyzerShowNonBazaarItems.getBoolean(); + chestAnalyzerShowBazaarItems = propChestAnalyzerShowBazaarItems.getBoolean(); chestAnalyzerUseBazaarPrices = propChestAnalyzerUseBazaarPrices.getString(); + chestAnalyzerShowLowestBinItems = propChestAnalyzerShowLowestBinItems.getBoolean(); + chestAnalyzerShowNoPriceItems = propChestAnalyzerShowNoPriceItems.getBoolean(); chestAnalyzerShowCommandUsage = propChestAnalyzerShowCommandUsage.getBoolean(); tooltipToggleKeyBinding = propTooltipToggleKeyBinding.getInt(); tooltipItemAge = propTooltipItemAge.getString(); @@ -774,8 +782,10 @@ public class MooConfig { propNotifyFreshServer.set(notifyFreshServer); propNotifyOldServer.set(notifyOldServer); propNotifyServerAge.set(notifyServerAge); - propChestAnalyzerShowNonBazaarItems.set(chestAnalyzerShowNonBazaarItems); + propChestAnalyzerShowBazaarItems.set(chestAnalyzerShowBazaarItems); propChestAnalyzerUseBazaarPrices.set(chestAnalyzerUseBazaarPrices); + propChestAnalyzerShowLowestBinItems.set(chestAnalyzerShowLowestBinItems); + propChestAnalyzerShowNoPriceItems.set(chestAnalyzerShowNoPriceItems); propChestAnalyzerShowCommandUsage.set(chestAnalyzerShowCommandUsage); propTooltipToggleKeyBinding.set(tooltipToggleKeyBinding); propTooltipItemAge.set(tooltipItemAge); diff --git a/src/main/java/de/cowtipper/cowlection/config/gui/MooConfigCategoryScrolling.java b/src/main/java/de/cowtipper/cowlection/config/gui/MooConfigCategoryScrolling.java index a3b28e6..731e9f9 100644 --- a/src/main/java/de/cowtipper/cowlection/config/gui/MooConfigCategoryScrolling.java +++ b/src/main/java/de/cowtipper/cowlection/config/gui/MooConfigCategoryScrolling.java @@ -27,6 +27,7 @@ import net.minecraftforge.fml.relauncher.SideOnly; import org.apache.commons.lang3.NotImplementedException; import org.apache.commons.lang3.StringUtils; import org.lwjgl.input.Keyboard; +import org.lwjgl.opengl.GL11; import java.util.*; @@ -333,9 +334,9 @@ public class MooConfigCategoryScrolling extends GuiListExtended { this.overlayBackground(0, this.top, 255, 255); this.overlayBackground(this.bottom, this.height, 255, 255); GlStateManager.enableBlend(); - GlStateManager.tryBlendFuncSeparate(770, 771, 0, 1); + GlStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, 0, 1); GlStateManager.disableAlpha(); - GlStateManager.shadeModel(7425); + GlStateManager.shadeModel(GL11.GL_SMOOTH); GlStateManager.disableTexture2D(); int maxScroll = this.func_148135_f(); @@ -374,7 +375,7 @@ public class MooConfigCategoryScrolling extends GuiListExtended { this.func_148142_b(mouseX, mouseY); // GuiSlot#renderDecorations GlStateManager.enableTexture2D(); - GlStateManager.shadeModel(7424); + GlStateManager.shadeModel(GL11.GL_FLAT); GlStateManager.enableAlpha(); GlStateManager.disableBlend(); diff --git a/src/main/java/de/cowtipper/cowlection/config/gui/MooConfigPreview.java b/src/main/java/de/cowtipper/cowlection/config/gui/MooConfigPreview.java index 675c392..5afb052 100644 --- a/src/main/java/de/cowtipper/cowlection/config/gui/MooConfigPreview.java +++ b/src/main/java/de/cowtipper/cowlection/config/gui/MooConfigPreview.java @@ -18,6 +18,7 @@ import net.minecraft.util.IChatComponent; import net.minecraft.util.ResourceLocation; import net.minecraftforge.fml.client.config.GuiUtils; import org.apache.commons.lang3.text.WordUtils; +import org.lwjgl.opengl.GL11; import java.util.HashMap; import java.util.Map; @@ -118,7 +119,8 @@ public class MooConfigPreview { GlStateManager.enableRescaleNormal(); GlStateManager.enableBlend(); - GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0); + + GlStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, 1, 0); RenderHelper.enableGUIStandardItemLighting(); int xItem = xFakeHotbar + 1; diff --git a/src/main/java/de/cowtipper/cowlection/data/DataHelper.java b/src/main/java/de/cowtipper/cowlection/data/DataHelper.java index ee2abe3..714a87d 100644 --- a/src/main/java/de/cowtipper/cowlection/data/DataHelper.java +++ b/src/main/java/de/cowtipper/cowlection/data/DataHelper.java @@ -4,11 +4,11 @@ import com.google.gson.annotations.SerializedName; import de.cowtipper.cowlection.util.Utils; import net.minecraft.util.EnumChatFormatting; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; +import java.util.*; public final class DataHelper { + public static final Set<String> AMBIGUOUS_ITEM_IDS = new HashSet<>(Arrays.asList("ENCHANTED_BOOK", "RUNE", "PET", "POTION", "NEW_YEAR_CAKE", "SPOOKY_PIE", "CAKE_SOUL")); + private DataHelper() { } diff --git a/src/main/java/de/cowtipper/cowlection/data/HySkyBlockStats.java b/src/main/java/de/cowtipper/cowlection/data/HySkyBlockStats.java index 2fa802b..4bf5093 100644 --- a/src/main/java/de/cowtipper/cowlection/data/HySkyBlockStats.java +++ b/src/main/java/de/cowtipper/cowlection/data/HySkyBlockStats.java @@ -220,12 +220,16 @@ public class HySkyBlockStats { } public List<Pet> getPets() { - pets.sort((p1, p2) -> ComparisonChain.start().compare(p2.active, p1.active).compare(p2.getRarity(), p1.getRarity()).compare(p2.exp, p1.exp).result()); + if (pets == null) { + pets = Collections.emptyList(); + } else { + pets.sort((p1, p2) -> ComparisonChain.start().compare(p2.active, p1.active).compare(p2.getRarity(), p1.getRarity()).compare(p2.exp, p1.exp).result()); + } return pets; } public Pet getActivePet() { - for (Pet pet : pets) { + for (Pet pet : getPets()) { if (pet.isActive()) { return pet; } @@ -234,7 +238,7 @@ public class HySkyBlockStats { } public Pet getPet(String type) { - for (Pet pet : pets) { + for (Pet pet : getPets()) { if (type.equals(pet.type)) { return pet; } @@ -298,6 +302,14 @@ public class HySkyBlockStats { return active; } + public String getType() { + return type; + } + + public double getExp() { + return exp; + } + public DataHelper.SkyBlockRarity getRarity() { return DataHelper.SkyBlockRarity.valueOf(tier); } diff --git a/src/main/java/de/cowtipper/cowlection/listener/skyblock/SkyBlockListener.java b/src/main/java/de/cowtipper/cowlection/listener/skyblock/SkyBlockListener.java index 80fe155..f619752 100644 --- a/src/main/java/de/cowtipper/cowlection/listener/skyblock/SkyBlockListener.java +++ b/src/main/java/de/cowtipper/cowlection/listener/skyblock/SkyBlockListener.java @@ -8,7 +8,9 @@ import de.cowtipper.cowlection.config.MooConfig; import de.cowtipper.cowlection.config.gui.MooConfigGui; import de.cowtipper.cowlection.data.BestiaryEntry; import de.cowtipper.cowlection.data.DataHelper; +import de.cowtipper.cowlection.data.HySkyBlockStats; import de.cowtipper.cowlection.data.XpTables; +import de.cowtipper.cowlection.util.GsonUtils; import de.cowtipper.cowlection.util.GuiHelper; import de.cowtipper.cowlection.util.MooChatComponent; import de.cowtipper.cowlection.util.Utils; @@ -65,7 +67,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; public class SkyBlockListener { - private static final Set<String> blackList = new HashSet<>(Arrays.asList("ENCHANTED_BOOK", "RUNE", "PET", "POTION")); // + minions (_GENERATOR_) private static final Pattern ITEM_COUNT_PREFIXED_PATTERN = Pattern.compile("^(?:§[0-9a-fl-or])*[\\d]+x "); private static final Pattern ITEM_COUNT_SUFFIXED_PATTERN = Pattern.compile(" (?:§[0-9a-fl-or])*x[\\d]+$"); private static final Pattern PET_NAME_PATTERN = Pattern.compile("^§7\\[Lvl (\\d+)] (§[0-9a-f])"); @@ -107,7 +108,7 @@ public class SkyBlockListener { if (extraAttributes != null && extraAttributes.hasKey("id")) { // seems to be a SkyBlock item String sbId = extraAttributes.getString("id"); - if (itemLookupType == ItemLookupType.WIKI || (/* itemLookupType == ItemLookupType.PRICE && */ !blackList.contains(sbId) && !sbId.contains("_GENERATOR_"))) { + if (itemLookupType == ItemLookupType.WIKI || (/* itemLookupType == ItemLookupType.PRICE && */ !DataHelper.AMBIGUOUS_ITEM_IDS.contains(sbId) && !sbId.contains("_GENERATOR_"))) { // open item price info or open wiki entry Pair<String, String> sbItemBaseName = Utils.extractSbItemBaseName(itemStack.getDisplayName(), extraAttributes, false); itemBaseName = sbItemBaseName.first(); @@ -205,19 +206,9 @@ public class SkyBlockListener { && e.itemStack.getItem() == Items.skull) { if (extraAttributes != null && extraAttributes.hasKey("petInfo")) { // pet in inventory, auction house or similar - String petInfo = extraAttributes.getString("petInfo"); - String expSubstr = "\"exp\":"; - int beginPetExp = petInfo.indexOf(expSubstr); - int endPetExp = petInfo.indexOf(',', beginPetExp); - if (beginPetExp > 0 && endPetExp > 0) { - try { - long petExp = (long) Double.parseDouble(petInfo.substring(beginPetExp + expSubstr.length(), endPetExp)); - int index = Math.max(0, e.toolTip.size() - (e.showAdvancedItemTooltips ? /* item name & nbt info */ 2 : 0)); - e.toolTip.add(index, EnumChatFormatting.GRAY + "Pet exp: " + EnumChatFormatting.GOLD + numberFormatter.format(petExp)); - } catch (NumberFormatException ignored) { - // do nothing - } - } + HySkyBlockStats.Profile.Pet petInfo = GsonUtils.fromJson(extraAttributes.getString("petInfo"), HySkyBlockStats.Profile.Pet.class); + int index = Math.max(0, e.toolTip.size() - (e.showAdvancedItemTooltips ? /* item name & nbt info */ 2 : 0)); + e.toolTip.add(index, EnumChatFormatting.GRAY + "Pet exp: " + EnumChatFormatting.GOLD + numberFormatter.format(petInfo.getExp())); } else if (e.itemStack.getDisplayName().contains("[Lvl ")) { // pet in pets menu for (int i = e.toolTip.size() - 1; i >= 0; i--) { diff --git a/src/main/java/de/cowtipper/cowlection/util/ApiUtils.java b/src/main/java/de/cowtipper/cowlection/util/ApiUtils.java index 35d9542..293c6ad 100644 --- a/src/main/java/de/cowtipper/cowlection/util/ApiUtils.java +++ b/src/main/java/de/cowtipper/cowlection/util/ApiUtils.java @@ -6,6 +6,7 @@ import com.google.gson.JsonSyntaxException; import com.mojang.util.UUIDTypeAdapter; import de.cowtipper.cowlection.Cowlection; import de.cowtipper.cowlection.chesttracker.HyBazaarData; +import de.cowtipper.cowlection.chesttracker.LowestBinsCache; import de.cowtipper.cowlection.command.exception.ThrowingConsumer; import de.cowtipper.cowlection.config.CredentialStorage; import de.cowtipper.cowlection.data.*; @@ -29,6 +30,7 @@ public class ApiUtils { private static final String ONLINE_STATUS_URL = "https://api.hypixel.net/status?key=%s&uuid=%s"; private static final String SKYBLOCK_STATS_URL = "https://api.hypixel.net/skyblock/profiles?key=%s&uuid=%s"; private static final String BAZAAR_URL = "https://api.hypixel.net/skyblock/bazaar"; + public static final String LOWEST_BINS = "https://moulberry.codes/lowestbin.json"; private static final String PLAYER_URL = "https://api.hypixel.net/player?key=%s&uuid=%s"; private static final String API_KEY_URL = "https://api.hypixel.net/key?key=%s"; private static final ExecutorService pool = Executors.newCachedThreadPool(); @@ -118,6 +120,21 @@ public class ApiUtils { return null; } + public static void fetchLowestBins(ThrowingConsumer<LowestBinsCache> action) { + pool.execute(() -> action.accept(getLowestBins())); + } + + private static LowestBinsCache getLowestBins() { + try (BufferedReader reader = makeApiCall(LOWEST_BINS)) { + if (reader != null) { + return GsonUtils.fromJson(reader, LowestBinsCache.class); + } + } catch (IOException | JsonSyntaxException e) { + e.printStackTrace(); + } + return new LowestBinsCache(); + } + public static void fetchHyPlayerDetails(Friend stalkedPlayer, ThrowingConsumer<HyPlayerData> action) { pool.execute(() -> action.accept(stalkHyPlayer(stalkedPlayer))); } @@ -161,6 +178,8 @@ public class ApiUtils { return null; } else if (connection.getResponseCode() == HttpStatus.SC_BAD_GATEWAY && url.startsWith("https://api.hypixel.net/")) { // http status 502 (cloudflare) throw new IOException("Couldn't contact Hypixel API (502 Bad Gateway). API might be down, check https://status.hypixel.net for info."); + } else if (connection.getResponseCode() == HttpStatus.SC_BAD_GATEWAY && url.startsWith("https://moulberry.codes/")) { // http status 502 (cloudflare) + throw new IOException("Couldn't contact Moulberry's API (502 Bad Gateway). API might be down, check if " + LOWEST_BINS + " is reachable."); } else { BufferedReader reader; InputStream errorStream = connection.getErrorStream(); diff --git a/src/main/java/de/cowtipper/cowlection/util/GsonUtils.java b/src/main/java/de/cowtipper/cowlection/util/GsonUtils.java index e97a88d..d2d3e28 100644 --- a/src/main/java/de/cowtipper/cowlection/util/GsonUtils.java +++ b/src/main/java/de/cowtipper/cowlection/util/GsonUtils.java @@ -2,6 +2,7 @@ package de.cowtipper.cowlection.util; import com.google.gson.*; import com.mojang.util.UUIDTypeAdapter; +import de.cowtipper.cowlection.chesttracker.LowestBinsCache; import de.cowtipper.cowlection.data.HyPlayerData; import net.minecraft.nbt.*; import net.minecraftforge.common.util.Constants; @@ -19,8 +20,14 @@ import java.util.TreeMap; import java.util.UUID; public final class GsonUtils { - private static final Gson gson = new GsonBuilder().registerTypeAdapter(UUID.class, new UUIDTypeAdapter()).registerTypeAdapter(HyPlayerData.class, new HyPlayerDataDeserializer()).create(); - private static final Gson gsonPrettyPrinter = new GsonBuilder().registerTypeAdapter(UUID.class, new UUIDTypeAdapter()).setPrettyPrinting().create(); + private static final Gson gson = new GsonBuilder() + .registerTypeAdapter(UUID.class, new UUIDTypeAdapter()) + .registerTypeAdapter(HyPlayerData.class, new HyPlayerDataDeserializer()) + .registerTypeAdapter(LowestBinsCache.class, new LowestBinsDeserializer()) + .create(); + private static final Gson gsonPrettyPrinter = new GsonBuilder() + .registerTypeAdapter(UUID.class, new UUIDTypeAdapter()) + .setPrettyPrinting().create(); private GsonUtils() { } @@ -168,4 +175,24 @@ public final class GsonUtils { return hyPlayerData; } } + + public static class LowestBinsDeserializer implements JsonDeserializer<LowestBinsCache> { + @Override + public LowestBinsCache deserialize(JsonElement json, Type type, JsonDeserializationContext jdc) throws JsonParseException { + LowestBinsCache lowestBinsCache = new LowestBinsCache(); + if (!json.isJsonObject()) { + // invalid JSON + return lowestBinsCache; + } + JsonObject lowestBins = json.getAsJsonObject(); + for (Map.Entry<String, JsonElement> entry : lowestBins.entrySet()) { + try { + lowestBinsCache.put(entry.getKey(), entry.getValue().getAsInt()); + } catch (ClassCastException | NumberFormatException ignored) { + // somehow not an integer + } + } + return lowestBinsCache; + } + } } diff --git a/src/main/resources/assets/cowlection/lang/en_US.lang b/src/main/resources/assets/cowlection/lang/en_US.lang index 0c7d1da..7ec8977 100644 --- a/src/main/resources/assets/cowlection/lang/en_US.lang +++ b/src/main/resources/assets/cowlection/lang/en_US.lang @@ -46,10 +46,14 @@ cowlection.config.notifyOldServer=Notify when server restarted ≥X days ago cowlection.config.notifyOldServer.tooltip=Notify when joining a server that hasn't restarted for X ingame days.\n§eSet to 0 to disable notifications! cowlection.config.notifyServerAge=Show server age notifications? cowlection.config.notifyServerAge.tooltip=Overrides the two settings above.\n§7This setting can also be changed with §e/moo worldage <on|off> -cowlection.config.chestAnalyzerShowNonBazaarItems=Show non-Bazaar items? -cowlection.config.chestAnalyzerShowNonBazaarItems.tooltip=Should items that are §enot §ron the Bazaar be displayed by default? +cowlection.config.chestAnalyzerShowBazaarItems=Show Bazaar items? +cowlection.config.chestAnalyzerShowBazaarItems.tooltip=Should items with a §eBazaar §rprice be displayed by default? cowlection.config.chestAnalyzerUseBazaarPrices=Use Bazaar prices cowlection.config.chestAnalyzerUseBazaarPrices.tooltip=Should §eInstant-Sell §ror §eSell Offer §rprices be used by default? +cowlection.config.chestAnalyzerShowLowestBinItems=Show lowest BIN items? +cowlection.config.chestAnalyzerShowLowestBinItems.tooltip=Should items with a §elowest BIN §rprice be displayed by default? +cowlection.config.chestAnalyzerShowNoPriceItems=Show items without price? +cowlection.config.chestAnalyzerShowNoPriceItems.tooltip=Should items §ewithout §ra Bazaar or BIN price be displayed by default? cowlection.config.chestAnalyzerShowCommandUsage=Show command usage? cowlection.config.chestAnalyzerShowCommandUsage.tooltip=Should the command usage of §e/moo analyzeChests §rbe displayed again each time? cowlection.config.tooltipToggleKeyBinding=Key binding: toggle tooltip |