From 307610afe9a46e1e5ad57b6c1bcbbe2fa5e6747f Mon Sep 17 00:00:00 2001 From: Cow Date: Mon, 10 Oct 2022 23:24:04 +0200 Subject: Chest Analyzer rework - Added search - Added NPC sell prices - Added 'deselect/hide item' - Added list of coords for highlighted chests - Added info button (`[?]`) - Added 'price type' column --- CHANGELOG.md | 6 + README.md | 2 +- .../cowlection/chesttracker/ChestOverviewGui.java | 228 ++++++++++++++++----- .../cowlection/chesttracker/ChestTracker.java | 147 ++++++++++--- .../cowlection/chesttracker/HyBazaarData.java | 61 ------ .../cowlection/chesttracker/ItemData.java | 110 ---------- .../cowlection/chesttracker/LowestBinsCache.java | 9 - .../cowlection/chesttracker/data/HyBazaarData.java | 61 ++++++ .../cowlection/chesttracker/data/HyItemsData.java | 31 +++ .../cowlection/chesttracker/data/ItemData.java | 147 +++++++++++++ .../chesttracker/data/LowestBinsCache.java | 9 + .../de/cowtipper/cowlection/config/MooConfig.java | 15 +- .../config/gui/MooConfigCategoryScrolling.java | 5 +- .../de/cowtipper/cowlection/util/ApiUtils.java | 21 +- .../de/cowtipper/cowlection/util/GsonUtils.java | 2 +- .../resources/assets/cowlection/lang/en_US.lang | 4 +- 16 files changed, 593 insertions(+), 265 deletions(-) delete mode 100644 src/main/java/de/cowtipper/cowlection/chesttracker/HyBazaarData.java delete mode 100644 src/main/java/de/cowtipper/cowlection/chesttracker/ItemData.java delete mode 100644 src/main/java/de/cowtipper/cowlection/chesttracker/LowestBinsCache.java create mode 100644 src/main/java/de/cowtipper/cowlection/chesttracker/data/HyBazaarData.java create mode 100644 src/main/java/de/cowtipper/cowlection/chesttracker/data/HyItemsData.java create mode 100644 src/main/java/de/cowtipper/cowlection/chesttracker/data/ItemData.java create mode 100644 src/main/java/de/cowtipper/cowlection/chesttracker/data/LowestBinsCache.java diff --git a/CHANGELOG.md b/CHANGELOG.md index b85a8fb..f398963 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - added new slayers (enderman + blaze) - `/moo analyzeIsland`: added new minions (mainly Crimson Isle related) - `/moo analyzeIsland`: Added chests and hopper counters +- `/moo analyzeChests`: Chest Analyzer rework: + - Added search + - Added NPC sell prices (only used if an item has neither a Bazaar nor lowest BIN price, or if one of them is hidden) + - Added 'deselect/hide item' = not calculated in total price sum (right click inside GUI) + - Added list of coords for highlighted chests when searching for chests with a certain item (double click inside the GUI to search, hover the chat message to see coords) + - Added info button (`[?]`) ### Changed diff --git a/README.md b/README.md index 5de5ad9..ae4e7eb 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,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 & lowest BINs value on your private island | `/moo analyzeChests` | +| Analyze chests and their Bazaar, lowest BINs, and NPC 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 shift (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 6851779..8907858 100644 --- a/src/main/java/de/cowtipper/cowlection/chesttracker/ChestOverviewGui.java +++ b/src/main/java/de/cowtipper/cowlection/chesttracker/ChestOverviewGui.java @@ -1,6 +1,8 @@ package de.cowtipper.cowlection.chesttracker; +import com.google.common.collect.Lists; import de.cowtipper.cowlection.Cowlection; +import de.cowtipper.cowlection.chesttracker.data.ItemData; import de.cowtipper.cowlection.config.MooConfig; import de.cowtipper.cowlection.search.GuiTooltip; import de.cowtipper.cowlection.util.*; @@ -17,14 +19,22 @@ import net.minecraftforge.fml.client.config.GuiButtonExt; import net.minecraftforge.fml.client.config.GuiCheckBox; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.gameevent.TickEvent; +import org.apache.commons.lang3.StringUtils; +import org.lwjgl.input.Mouse; import java.io.IOException; import java.util.*; public class ChestOverviewGui extends GuiScreen { + private static final String SEARCH_QUERY_PLACE_HOLDER = "Search for..."; + private ItemOverview itemOverview; private List guiTooltips; + private GuiTextField fieldSearchQuery; + private String searchQuery; + private boolean isPlaceholderSearchQuery; private GuiButton btnClose; + private GuiButton btnHelp; private GuiButton btnUpdatePrices; private GuiButton btnCopy; private GuiCheckBox chkShowNoPriceItems; @@ -32,6 +42,7 @@ public class ChestOverviewGui extends GuiScreen { private GuiCheckBox chkShowBazaarItems; private GuiButton btnBazaarInstantOrOffer; private boolean useBazaarInstantSellPrices; + private GuiCheckBox chkShowNpcItems; private AbortableRunnable updatePrices; private final String screenTitle; private final Cowlection main; @@ -47,28 +58,46 @@ public class ChestOverviewGui extends GuiScreen { public void initGui() { this.guiTooltips = new ArrayList<>(); // close - this.buttonList.add(this.btnClose = new GuiButtonExt(1, this.width - 25, 3, 22, 20, EnumChatFormatting.RED + "X")); + this.buttonList.add(this.btnClose = new GuiButtonExt(1, this.width - 25, 3, 20, 20, EnumChatFormatting.RED + "X")); addTooltip(btnClose, Arrays.asList(EnumChatFormatting.RED + "Close interface", "" + EnumChatFormatting.GRAY + EnumChatFormatting.ITALIC + "Hint:" + EnumChatFormatting.RESET + " alternatively press ESC")); + // help + this.buttonList.add(this.btnHelp = new GuiButtonExt(2, this.width - 47, 3, 20, 20, "?")); + addTooltip(btnHelp, Arrays.asList("" + EnumChatFormatting.GOLD + EnumChatFormatting.BOLD + screenTitle, + EnumChatFormatting.GRAY + "You can set the default settings via " + EnumChatFormatting.YELLOW + "/moo config chest", + "", + EnumChatFormatting.WHITE + " ‣ " + EnumChatFormatting.GOLD + "double click" + EnumChatFormatting.WHITE + ": highlight chests with selected item; hover over the chat message to see chest coords", + EnumChatFormatting.WHITE + " ‣ " + EnumChatFormatting.GOLD + "right click" + EnumChatFormatting.WHITE + ": exclude item from sell value calculation")); // 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)")); + this.buttonList.add(this.btnUpdatePrices = new GuiButton(20, this.width - 145, 5, 90, 16, "⟳ Update prices")); + addTooltip(btnUpdatePrices, Arrays.asList(EnumChatFormatting.YELLOW + "‣ Get latest " + EnumChatFormatting.GOLD + "Bazaar prices " + EnumChatFormatting.YELLOW + "from Hypixel API", EnumChatFormatting.WHITE + " (only once per minute)", + EnumChatFormatting.YELLOW + "‣ Get latest " + EnumChatFormatting.GOLD + "lowest BINs " + EnumChatFormatting.YELLOW + "from Moulberry's API", EnumChatFormatting.WHITE + " (only once every 5 minutes)", + EnumChatFormatting.YELLOW + "‣ Get latest " + EnumChatFormatting.GOLD + "NPC sell prices " + EnumChatFormatting.YELLOW + "from Hypixel API", EnumChatFormatting.WHITE + " (only once every 15 minutes)")); // copy to clipboard - this.buttonList.add(this.btnCopy = new GuiButton(21, this.width - 240, 5, 110, 16, "⎘ Copy to clipboard")); + this.buttonList.add(this.btnCopy = new GuiButton(21, this.width - 222, 5, 74, 16, "⎘ Copy data")); addTooltip(btnCopy, Collections.singletonList(EnumChatFormatting.YELLOW + "Copied data can be pasted into e.g. Google Spreadsheets")); + // input: search + this.fieldSearchQuery = new GuiTextField(23, this.fontRendererObj, this.width - 330, 6, 100, 15); + addTooltip(fieldSearchQuery, Collections.singletonList(EnumChatFormatting.YELLOW + "Search by item name and item id")); + this.fieldSearchQuery.setMaxStringLength(42); + this.isPlaceholderSearchQuery = StringUtils.isEmpty(searchQuery) || SEARCH_QUERY_PLACE_HOLDER.equals(searchQuery); + this.fieldSearchQuery.setText(isPlaceholderSearchQuery ? SEARCH_QUERY_PLACE_HOLDER : searchQuery); // toggle: use insta-sell or 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?")); + this.buttonList.add(this.btnBazaarInstantOrOffer = new GuiButton(15, this.width - 78, this.height - 51, 75, 12, MooConfig.useInstantSellBazaarPrices() ? "via Insta-Sell" : "via Sell Offer")); + addTooltip(btnBazaarInstantOrOffer, Collections.singletonList(EnumChatFormatting.WHITE + "Bazaar items: " + 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)); + this.buttonList.add(this.chkShowBazaarItems = new GuiCheckBox(10, this.width - 162, this.height - 50, " 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)); + this.buttonList.add(this.chkShowLowestBinItems = new GuiCheckBox(11, this.width - 162, this.height - 39, " 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 NPC items + this.buttonList.add(this.chkShowNpcItems = new GuiCheckBox(12, this.width - 162, this.height - 28, " NPC items", MooConfig.chestAnalyzerShowNpcItems)); + addTooltip(chkShowNpcItems, Lists.newArrayList(EnumChatFormatting.YELLOW + "Should items with an " + EnumChatFormatting.GOLD + "NPC sell " + EnumChatFormatting.YELLOW + "price be displayed?", + EnumChatFormatting.RED + "(NPC sell price is only used if an item has neither a Bazaar nor lowest BIN price, or if one of them is hidden)")); // 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?")); + this.buttonList.add(this.chkShowNoPriceItems = new GuiCheckBox(13, this.width - 162, this.height - 15, " items without price", MooConfig.chestAnalyzerShowNoPriceItems)); + addTooltip(chkShowNoPriceItems, Collections.singletonList(EnumChatFormatting.YELLOW + "Should items " + EnumChatFormatting.GOLD + "without " + EnumChatFormatting.YELLOW + "a Bazaar, BIN, or NPC price be displayed?")); // main item gui this.itemOverview = new ItemOverview(); } @@ -78,11 +107,24 @@ public class ChestOverviewGui extends GuiScreen { this.guiTooltips.add(guiTooltip); } + @Override + public void updateScreen() { + super.updateScreen(); + fieldSearchQuery.updateCursorCounter(); + } + @Override public void drawScreen(int mouseX, int mouseY, float partialTicks) { - btnUpdatePrices.enabled = updatePrices == null && (main.getChestTracker().allowUpdateBazaar() || main.getChestTracker().allowUpdateLowestBins()); + btnUpdatePrices.enabled = updatePrices == null && main.getChestTracker().allowAnyPriceUpdate(); itemOverview.drawScreen(mouseX, mouseY, partialTicks); this.drawString(this.fontRendererObj, this.screenTitle, itemOverview.getLeftX(), 10, 0xFFCC00); + this.fieldSearchQuery.drawTextBox(); + // left of checkboxes: price type indicators + this.drawString(this.fontRendererObj, ItemData.PriceType.BAZAAR.getIndicator(), chkShowBazaarItems.xPosition - 5 - this.fontRendererObj.getStringWidth(ItemData.PriceType.BAZAAR.getIndicator()), chkShowBazaarItems.yPosition + 2, 0x666666); + this.drawString(this.fontRendererObj, ItemData.PriceType.LOWEST_BIN.getIndicator(), chkShowLowestBinItems.xPosition - 5 - this.fontRendererObj.getStringWidth(ItemData.PriceType.LOWEST_BIN.getIndicator()), chkShowLowestBinItems.yPosition + 2, 0x666666); + this.drawString(this.fontRendererObj, ItemData.PriceType.NPC_SELL.getIndicator(), chkShowNpcItems.xPosition - 5 - this.fontRendererObj.getStringWidth(ItemData.PriceType.NPC_SELL.getIndicator()), chkShowNpcItems.yPosition + 2, 0x666666); + this.drawString(this.fontRendererObj, ItemData.PriceType.NONE.getIndicator(), chkShowNoPriceItems.xPosition - 5 - this.fontRendererObj.getStringWidth(ItemData.PriceType.NONE.getIndicator()), chkShowNoPriceItems.yPosition + 2, 0x666666); + super.drawScreen(mouseX, mouseY, partialTicks); itemOverview.drawScreenPost(mouseX, mouseY); for (GuiTooltip guiTooltip : guiTooltips) { @@ -112,14 +154,17 @@ public class ChestOverviewGui extends GuiScreen { if (button.enabled) { if (button == btnClose) { this.mc.displayGuiScreen(null); + } else if (button == btnHelp) { + mc.displayGuiScreen(new GuiChat("/moo config chest")); } else if (button == btnUpdatePrices) { btnUpdatePrices.enabled = false; - EnumSet updating = this.main.getChestTracker().refreshPriceCache(); - if (updating.size() > 1) { + EnumSet updating = this.main.getChestTracker().refreshPriceCache(); + if (updating.size() > 0) { 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; + private final long previousNpcSellUpdate = ChestTracker.lastNpcSellUpdate; @SubscribeEvent public void onTickCheckPriceDataUpdated(TickEvent.ClientTickEvent e) { @@ -130,8 +175,9 @@ public class ChestOverviewGui extends GuiScreen { return; } retries--; - if (updating.contains(ChestTracker.Updating.BAZAAR) && previousBazaarUpdate == ChestTracker.lastBazaarUpdate - || updating.contains(ChestTracker.Updating.LOWEST_BINS) && previousLowestBinsUpdate == ChestTracker.lastLowestBinsUpdate) { + if (updating.contains(ItemData.PriceType.BAZAAR) && previousBazaarUpdate == ChestTracker.lastBazaarUpdate + || updating.contains(ItemData.PriceType.LOWEST_BIN) && previousLowestBinsUpdate == ChestTracker.lastLowestBinsUpdate + || updating.contains(ItemData.PriceType.NPC_SELL) && previousNpcSellUpdate == ChestTracker.lastNpcSellUpdate) { // cache(s) have not updated yet, retry next tick return; } @@ -158,25 +204,36 @@ public class ChestOverviewGui extends GuiScreen { }; 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) { + } else if (button == chkShowNoPriceItems || button == chkShowNpcItems || 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)\tPrice (lowest BIN)\tValue (lowest BIN)"); + 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)\tPrice (NPC sell)\tValue(NPC sell)"); 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("Auction House value (lowest BINs):\t").append(itemOverview.summedValueLowestBins); + .append("\n").append("Auction House value (lowest BINs):\t").append(itemOverview.summedValueLowestBins) + .append("\n").append("NPC sell value (NPC sell):\t").append(itemOverview.summedValueNpcSell); GuiScreen.setClipboardString(allItemData.toString()); } else if (button == btnBazaarInstantOrOffer) { - this.btnBazaarInstantOrOffer.displayString = this.useBazaarInstantSellPrices ? "Use Sell Offer prices" : "Use Instant-Sell prices"; + this.btnBazaarInstantOrOffer.displayString = this.useBazaarInstantSellPrices ? "via Sell Offer" : "via Insta-Sell"; this.useBazaarInstantSellPrices = !this.useBazaarInstantSellPrices; this.itemOverview.reloadItemData(); } } } + @Override + protected void keyTyped(char typedChar, int keyCode) throws IOException { + super.keyTyped(typedChar, keyCode); + if (this.fieldSearchQuery.textboxKeyTyped(typedChar, keyCode)) { + searchQuery = this.fieldSearchQuery.getText(); + isPlaceholderSearchQuery = SEARCH_QUERY_PLACE_HOLDER.equals(searchQuery); + itemOverview.reloadItemData(); + } + } + private void stopPriceUpdateChecker() { if (updatePrices != null) { // there is still a price update-checker running, stop it @@ -198,6 +255,15 @@ public class ChestOverviewGui extends GuiScreen { return true; } + @Override + protected void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOException { + super.mouseClicked(mouseX, mouseY, mouseButton); + this.fieldSearchQuery.mouseClicked(mouseX, mouseY, mouseButton); + if (this.fieldSearchQuery.isFocused() && this.isPlaceholderSearchQuery) { + this.fieldSearchQuery.setText(""); + } + } + @Override public void handleMouseInput() throws IOException { super.handleMouseInput(); @@ -218,12 +284,14 @@ public class ChestOverviewGui extends GuiScreen { private long summedValueInstaSell; private long summedValueSellOffer; private long summedValueLowestBins; + private long summedValueNpcSell; private long summedTotalValue; private boolean showBazaarItems; private boolean showLowestBinItems; + private boolean showNpcItems; ItemOverview() { - super(ChestOverviewGui.this.mc, ChestOverviewGui.this.width, ChestOverviewGui.this.height, 32, ChestOverviewGui.this.height - 54, 16); + super(ChestOverviewGui.this.mc, ChestOverviewGui.this.width, ChestOverviewGui.this.height, 26, ChestOverviewGui.this.height - 54, 16); this.setShowSelectionBox(false); // space above first entry for control buttons int headerPadding = 20; @@ -233,32 +301,54 @@ public class ChestOverviewGui extends GuiScreen { } private void reloadItemData() { + showBazaarItems = ChestOverviewGui.this.chkShowBazaarItems.isChecked(); + showLowestBinItems = ChestOverviewGui.this.chkShowLowestBinItems.isChecked(); + showNpcItems = ChestOverviewGui.this.chkShowNpcItems.isChecked(); + + EnumSet visiblePriceTypes = EnumSet.of( + showBazaarItems ? ItemData.PriceType.BAZAAR : ItemData.PriceType.NONE, + showLowestBinItems ? ItemData.PriceType.LOWEST_BIN : ItemData.PriceType.NONE, + showNpcItems ? ItemData.PriceType.NPC_SELL : ItemData.PriceType.NONE + ); + boolean useInstantSellPrices = ChestOverviewGui.this.useBazaarInstantSellPrices; - itemDataHolder = main.getChestTracker().getAnalysisResult(orderBy, orderDesc, useInstantSellPrices); + itemDataHolder = main.getChestTracker().getAnalysisResult((isPlaceholderSearchQuery ? null : ChestOverviewGui.this.searchQuery), orderBy, orderDesc, visiblePriceTypes, useInstantSellPrices); summedValueInstaSell = 0; summedValueSellOffer = 0; summedValueLowestBins = 0; + summedValueNpcSell = 0; summedTotalValue = 0; - showBazaarItems = ChestOverviewGui.this.chkShowBazaarItems.isChecked(); - showLowestBinItems = ChestOverviewGui.this.chkShowLowestBinItems.isChecked(); boolean showNoPriceItems = ChestOverviewGui.this.chkShowNoPriceItems.isChecked(); for (Iterator iterator = itemDataHolder.iterator(); iterator.hasNext(); ) { ItemData itemData = iterator.next(); + boolean isVisibleItem = !itemData.isHidden(); switch (itemData.getPriceType()) { case BAZAAR: - summedValueInstaSell += itemData.getBazaarInstantSellValue(); - summedValueSellOffer += itemData.getBazaarSellOfferValue(); + if (isVisibleItem) { + summedValueInstaSell += itemData.getBazaarInstantSellValue(); + summedValueSellOffer += itemData.getBazaarSellOfferValue(); + } if (showBazaarItems) { continue; } break; case LOWEST_BIN: - summedValueLowestBins += itemData.getLowestBinValue(); + if (isVisibleItem) { + summedValueLowestBins += itemData.getLowestBinValue(); + } if (showLowestBinItems) { continue; } break; + case NPC_SELL: + if (isVisibleItem) { + summedValueNpcSell += itemData.getNpcSellValue(); + } + if (showNpcItems) { + continue; + } + break; default: // case NONE: if (showNoPriceItems) { continue; @@ -274,6 +364,9 @@ public class ChestOverviewGui extends GuiScreen { if (showBazaarItems) { summedTotalValue += (useInstantSellPrices ? summedValueInstaSell : summedValueSellOffer); } + if (showNpcItems) { + summedTotalValue += summedValueNpcSell; + } } @Override @@ -356,6 +449,26 @@ public class ChestOverviewGui extends GuiScreen { } } + @Override + public void handleMouseInput() { + super.handleMouseInput(); + if (Mouse.getEventButton() == /* right click */ 1 && Mouse.getEventButtonState() && this.isMouseYWithinSlotBounds(this.mouseY)) { + int slotLeft = (this.width - this.getListWidth()) / 2; + int slotRight = (this.width + this.getListWidth()) / 2; + int k = this.mouseY - this.top - this.headerPadding + (int) this.amountScrolled - 4; + int clickedSlot = k / this.slotHeight; + + if (clickedSlot < this.getSize() && this.mouseX >= slotLeft && this.mouseX <= slotRight && clickedSlot >= 0 && k >= 0) { + // right-clicked a slot + ItemData itemData = itemDataHolder.get(clickedSlot); + if (itemData != null) { + main.getChestTracker().toggleHiddenStateForItem(itemData.getKey()); + this.reloadItemData(); + } + } + } + } + @Override protected boolean isSelected(int slotIndex) { return false; @@ -371,12 +484,12 @@ public class ChestOverviewGui extends GuiScreen { } @Override - protected void drawSlot(int entryID, int x, int y, int z, int mouseXIn, int mouseYIn) { + protected void drawSlot(int entryId, int x, int y, int z, int mouseXIn, int mouseYIn) { if (!isMouseYWithinSlotBounds(y + 5)) { // slot isn't visible anyways... return; } - ItemData itemData = itemDataHolder.get(entryID); + ItemData itemData = itemDataHolder.get(entryId); // render item icon without shadows GlStateManager.enableRescaleNormal(); @@ -393,46 +506,53 @@ public class ChestOverviewGui extends GuiScreen { if (itemName.length() != itemData.getName().length()) { itemName += "…"; } - fontRenderer.drawStringWithShadow(itemName, itemNameXPos, y + 1, entryID % 2 == 0 ? 0xFFFFFF : 0x909090); - fontRenderer.drawStringWithShadow(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 = 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); + fontRenderer.drawStringWithShadow(bazaarOrBinPrice, x + Column.PRICE_EACH.getXOffset() - fontRenderer.getStringWidth(bazaarOrBinPrice), 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); + fontRenderer.drawStringWithShadow(bazaarOrBinValue, x + Column.PRICE_SUM.getXOffset() - fontRenderer.getStringWidth(bazaarOrBinValue), y + 1, entryId % 2 == 0 ? 0xFFFFFF : 0x909090); + + String itemPriceType = itemData.getPriceType().getIndicator(); + fontRenderer.drawStringWithShadow(itemPriceType, x + Column.PRICE_TYPE.getXOffset() - fontRenderer.getStringWidth(itemPriceType), y + 1, entryId % 2 == 0 ? 0xFFFFFF : 0x909090); + + if (itemData.isHidden()) { + Gui.drawRect(x, y - 3, x + getListWidth(), y - 3 + slotHeight, 0xdd444444); + } } public void drawScreenPost(int mouseX, int mouseY) { - int xMin = getLeftX(); - FontRenderer fontRenderer = ChestOverviewGui.this.fontRendererObj; + GlStateManager.pushMatrix(); + float scaleFactor = 0.9f; + GlStateManager.scale(scaleFactor, scaleFactor, 0); 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 (" + drawSummedValue(scaleFactor, showBazaarItems && useInstantSellPrices, summedValueInstaSell, "Bazaar value (instant-sell prices)", 52); + drawSummedValue(scaleFactor, showBazaarItems && !useInstantSellPrices, summedValueSellOffer, "Bazaar value (sell offer prices)", 43); + drawSummedValue(scaleFactor, showLowestBinItems, summedValueLowestBins, "Auction House value (lowest BINs)", 34); + drawSummedValue(scaleFactor, showNpcItems, summedValueNpcSell, "NPC value (NPC sell prices)", 25); + + String estimatedTotalSellValue = "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); + + (showNpcItems ? (showBazaarItems || showLowestBinItems ? "+" : "") + "NPC" : "") + ")"; + int estimatedTotalEndX = drawSummedValue(scaleFactor, (showBazaarItems || showLowestBinItems || showNpcItems), summedTotalValue, estimatedTotalSellValue, 11); - drawRect(3, ChestOverviewGui.this.height - 16, estimatedTotalEndX + 2, ChestOverviewGui.this.height - 15, 0xff555555); + drawRect((int) (3 / scaleFactor), (int) ((ChestOverviewGui.this.height - 15) / scaleFactor), (int) ((estimatedTotalEndX + 2) / scaleFactor), (int) ((ChestOverviewGui.this.height - 14) / scaleFactor), 0xff555555); + GlStateManager.popMatrix(); if (isMouseYWithinSlotBounds(mouseY)) { int slotIndex = this.getSlotIndexFromScreenCoords(mouseX, mouseY); if (slotIndex >= 0) { // mouse is over a slot: maybe draw item tooltip + int xMin = getLeftX(); int xMax = xMin + 16; // 16 = item icon width if (mouseX < xMin || mouseX > xMax) { // mouseX outside of valid item x values @@ -447,6 +567,15 @@ public class ChestOverviewGui extends GuiScreen { } } + private int drawSummedValue(float scaleFactor, boolean isPriceTypeEnabled, long summedValue, String valueType, int yOffset) { + String valueText = (isPriceTypeEnabled ? EnumChatFormatting.WHITE : EnumChatFormatting.DARK_GRAY) + + "∑ " + valueType + ": " + + (summedValue > 0 ? Utils.formatNumber(summedValue) : EnumChatFormatting.DARK_GRAY + "-"); + return ChestOverviewGui.this.fontRendererObj.drawStringWithShadow(valueText, + Math.max(2, (getLeftX() + 175 - ChestOverviewGui.this.fontRendererObj.getStringWidth("∑ " + valueType + ": ")) / scaleFactor), + (ChestOverviewGui.this.height - yOffset) / scaleFactor, 0xdddddd); + } + /** * GuiSlot#drawScreen: x of slot */ @@ -458,8 +587,9 @@ public class ChestOverviewGui extends GuiScreen { public enum Column { ITEM_NAME("Item", 22), ITEM_AMOUNT("Amount", 200), - PRICE_EACH("Price", 260), - PRICE_SUM("Value", 330); + PRICE_EACH("Price", 275), + PRICE_SUM("Value", 360), + PRICE_TYPE("Type", 410); private final int xOffset; private final String name; diff --git a/src/main/java/de/cowtipper/cowlection/chesttracker/ChestTracker.java b/src/main/java/de/cowtipper/cowlection/chesttracker/ChestTracker.java index 34d4a44..a823401 100644 --- a/src/main/java/de/cowtipper/cowlection/chesttracker/ChestTracker.java +++ b/src/main/java/de/cowtipper/cowlection/chesttracker/ChestTracker.java @@ -1,11 +1,16 @@ package de.cowtipper.cowlection.chesttracker; import de.cowtipper.cowlection.Cowlection; +import de.cowtipper.cowlection.chesttracker.data.HyBazaarData; +import de.cowtipper.cowlection.chesttracker.data.HyItemsData; +import de.cowtipper.cowlection.chesttracker.data.ItemData; +import de.cowtipper.cowlection.chesttracker.data.LowestBinsCache; 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 de.cowtipper.cowlection.util.Utils; import net.minecraft.init.Blocks; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; @@ -17,19 +22,23 @@ import net.minecraft.util.EnumChatFormatting; import net.minecraft.util.EnumFacing; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.util.Constants; +import org.apache.commons.lang3.StringUtils; import java.util.*; public class ChestTracker { public static long lastBazaarUpdate; public static long lastLowestBinsUpdate; + public static long lastNpcSellUpdate; private final Map> chestCache = new HashMap<>(); private final Map doubleChestCache = new HashMap<>(); private final Set chestsWithWantedItem = new HashSet<>(); + private final Set hiddenItems = new HashSet<>(); private Map analysisResult = new HashMap<>(); private ChestInteractionListener chestInteractionListener; private HyBazaarData bazaarCache; private LowestBinsCache lowestBinsCache; + private Map npcSellCache; private final Cowlection main; public ChestTracker(Cowlection main) { @@ -120,33 +129,61 @@ public class ChestTracker { /** * Returns ordered analysis result with prices */ - public List getAnalysisResult(ChestOverviewGui.Column orderBy, boolean orderDesc, boolean useInstantSellPrices) { + public List getAnalysisResult(String searchQuery, ChestOverviewGui.Column orderBy, boolean orderDesc, EnumSet visiblePriceTypes, boolean useInstantSellPrices) { List orderedAnalysisResult = new ArrayList<>(); - // sort by bazaar value (most value first) + + boolean checkBazaarPrices = bazaarCache != null && bazaarCache.isSuccess() && visiblePriceTypes.contains(ItemData.PriceType.BAZAAR); + boolean checkLowestBinPrices = lowestBinsCache != null && lowestBinsCache.size() > 0 && visiblePriceTypes.contains(ItemData.PriceType.LOWEST_BIN); + boolean checkNpcSellPrices = npcSellCache != null && npcSellCache.size() > 0 && visiblePriceTypes.contains(ItemData.PriceType.NPC_SELL); + + boolean hasSearchQuery = StringUtils.isNotEmpty(searchQuery); for (Map.Entry itemEntry : analysisResult.entrySet()) { + ItemData itemData = itemEntry.getValue(); + + if (hasSearchQuery + && !StringUtils.containsIgnoreCase(itemData.getKey(), searchQuery) + && !StringUtils.containsIgnoreCase(EnumChatFormatting.getTextWithoutFormattingCodes(itemData.getName()), searchQuery)) { + // item doesn't match search query + continue; + } boolean foundPriceForItem = false; - if (bazaarCache != null && bazaarCache.isSuccess()) { + + if (checkBazaarPrices) { String productKey = itemEntry.getKey(); HyBazaarData.Product product = bazaarCache.getProduct(productKey); if (product != null) { // item is sold on bazaar! - itemEntry.getValue().setBazaarInstantSellPrice(product.getInstantSellPrice()); - itemEntry.getValue().setBazaarSellOfferPrice(product.getSellOfferPrice()); + itemData.setBazaarInstantSellPrice(product.getInstantSellPrice()); + itemData.setBazaarSellOfferPrice(product.getSellOfferPrice()); foundPriceForItem = true; } } - if (!foundPriceForItem && lowestBinsCache != null && lowestBinsCache.size() > 0) { + if (!foundPriceForItem && checkLowestBinPrices) { String productKey = itemEntry.getKey().replace(':', '-'); Integer lowestBin = lowestBinsCache.get(productKey); if (lowestBin != null) { // item is sold via BIN - itemEntry.getValue().setLowestBin(lowestBin); + itemData.setLowestBin(lowestBin); + foundPriceForItem = true; } } - orderedAnalysisResult.add(itemEntry.getValue()); + if (!foundPriceForItem && checkNpcSellPrices) { + String productKey = itemEntry.getKey(); + Double npcSellPrice = npcSellCache.get(productKey); + if (npcSellPrice != null) { + // item can be sold to NPC + itemData.setNpcPrice(npcSellPrice); + // foundPriceForItem = true; + } + } + itemData.setHidden(hiddenItems.contains(itemData.getKey())); + orderedAnalysisResult.add(itemData); } Comparator comparator; switch (orderBy) { + case PRICE_TYPE: + comparator = Comparator.comparing(ItemData::getPriceType).reversed(); + break; case ITEM_NAME: comparator = Comparator.comparing(ItemData::getName); break; @@ -180,6 +217,7 @@ public class ChestTracker { chestCache.clear(); doubleChestCache.clear(); chestsWithWantedItem.clear(); + hiddenItems.clear(); analysisResult.clear(); } @@ -234,10 +272,10 @@ public class ChestTracker { return doubleChestCache.getOrDefault(pos, EnumFacing.UP); } - public EnumSet refreshPriceCache() { - EnumSet updating = EnumSet.of(Updating.UNDEFINED); + public EnumSet refreshPriceCache() { + EnumSet updating = EnumSet.noneOf(ItemData.PriceType.class); if (allowUpdateBazaar()) { - updating.add(Updating.BAZAAR); + updating.add(ItemData.PriceType.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/")); @@ -247,7 +285,7 @@ public class ChestTracker { }); } if (allowUpdateLowestBins()) { - updating.add(Updating.LOWEST_BINS); + updating.add(ItemData.PriceType.LOWEST_BIN); 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)); @@ -256,6 +294,23 @@ public class ChestTracker { lastLowestBinsUpdate = System.currentTimeMillis(); }); } + if (allowUpdateNpcSell()) { + updating.add(ItemData.PriceType.NPC_SELL); + ApiUtils.fetchItemsData(itemsData -> { + this.npcSellCache = new HashMap<>(); + if (itemsData == null || !itemsData.isSuccess()) { + main.getChatHelper().sendMessage(new MooChatComponent("Error: Couldn't get Items data from Hypixel API! API might be down: check status.hypixel.net").red().setUrl("https://status.hypixel.net/")); + } else { + for (HyItemsData.Item item : itemsData.getItems()) { + if (item.getNpcSellPrice() > 0) { + // item has a NPC sell price + this.npcSellCache.put(item.getId(), item.getNpcSellPrice()); + } + } + } + lastNpcSellUpdate = System.currentTimeMillis(); + }); + } return updating; } @@ -273,40 +328,84 @@ public class ChestTracker { return lowestBinsCache == null || (System.currentTimeMillis() - lastLowestBinsUpdate) > 300000; } + /** + * Allow NPC sell prices update once every 15 minutes + */ + public boolean allowUpdateNpcSell() { + return npcSellCache == null || (System.currentTimeMillis() - lastNpcSellUpdate) > 900000; + } + + public boolean allowAnyPriceUpdate() { + return allowUpdateBazaar() || allowUpdateLowestBins() || allowUpdateNpcSell(); + } + public void markChestsWithWantedItem(String sbKey, int amount, String itemName) { // clear old search results chestsWithWantedItem.clear(); + Map chestsWithWantedItemsAndCount = new TreeMap<>(); + if (sbKey.endsWith("_ambiguous")) { sbKey = sbKey.substring(0, sbKey.length() - 10); } - int relevantChests = 0; + int mostWantedItemsInOneChest = 0; for (Map.Entry> chestCache : chestCache.entrySet()) { - boolean hasItemBeenFoundInChest = false; + int foundWantedItemsInChest = 0; for (ItemStack item : chestCache.getValue()) { String key = item.hasDisplayName() ? item.getDisplayName() : item.getUnlocalizedName(); if (item.hasTagCompound()) { key = item.getTagCompound().getCompoundTag("ExtraAttributes").getString("id"); } if (sbKey.equals(key)) { - if (!hasItemBeenFoundInChest) { - chestsWithWantedItem.add(chestCache.getKey()); - hasItemBeenFoundInChest = true; - ++relevantChests; - } - amount -= item.stackSize; + foundWantedItemsInChest += item.stackSize; } } + if (foundWantedItemsInChest > 0) { + // chest was a match! + chestsWithWantedItemsAndCount.put(chestCache.getKey(), foundWantedItemsInChest); + if (foundWantedItemsInChest > mostWantedItemsInOneChest) { + mostWantedItemsInOneChest = foundWantedItemsInChest; + } + } + amount -= foundWantedItemsInChest; if (amount <= 0) { // already found all relevant chests break; } } - main.getChatHelper().sendMessage(EnumChatFormatting.GREEN, "Chest Tracker & Analyzer is now highlighting " + EnumChatFormatting.LIGHT_PURPLE + relevantChests + EnumChatFormatting.GREEN + " chest" + (relevantChests > 1 ? "s" : "") + " with " + itemName - + EnumChatFormatting.GREEN + ". Re-opening the chest analysis results with " + EnumChatFormatting.GRAY + "/moo analyzeChests " + EnumChatFormatting.GREEN + "clears the current search."); + + int relevantChestCount = chestsWithWantedItemsAndCount.size(); + int relevantChestsCoordsLimit = 30; + int maxItemCountLength = Utils.formatNumber(mostWantedItemsInOneChest).length(); + final MooChatComponent relevantChestCoordsHover = new MooChatComponent("Chests with ").gold().bold().appendSibling(new MooChatComponent(itemName).reset()) + .appendFreshSibling(new MooChatComponent(StringUtils.repeat(' ', maxItemCountLength) + " (x | y | z)").gray()); + + chestsWithWantedItemsAndCount.entrySet().stream() + .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())) + .limit(relevantChestsCoordsLimit) + .forEach(wantedChest -> { + BlockPos chestPos = wantedChest.getKey(); + String itemCountInChest = StringUtils.leftPad(Utils.formatNumber(wantedChest.getValue()), maxItemCountLength, ' '); + + relevantChestCoordsHover.appendFreshSibling(new MooChatComponent(" " + EnumChatFormatting.YELLOW + itemCountInChest + EnumChatFormatting.DARK_GRAY + "x ➜ " + + EnumChatFormatting.WHITE + chestPos.getX() + EnumChatFormatting.DARK_GRAY + + " | " + EnumChatFormatting.WHITE + chestPos.getY() + EnumChatFormatting.DARK_GRAY + + " | " + EnumChatFormatting.WHITE + chestPos.getZ()).white()); + }); + if (relevantChestCount > relevantChestsCoordsLimit) { + relevantChestCoordsHover.appendFreshSibling(new MooChatComponent(" + " + (relevantChestCount - relevantChestsCoordsLimit) + " more chests").gray()); + } + main.getChatHelper().sendMessage(new MooChatComponent("Chest Tracker & Analyzer is now highlighting " + EnumChatFormatting.LIGHT_PURPLE + relevantChestCount + EnumChatFormatting.GREEN + " chest" + (relevantChestCount > 1 ? "s" : "") + " with " + itemName + + EnumChatFormatting.DARK_GREEN + " [hover for coords, click to re-open GUI]").green() + .setHover(relevantChestCoordsHover) + .setSuggestCommand("/moo analyzeChests", false)); + chestsWithWantedItem.addAll(chestsWithWantedItemsAndCount.keySet()); } - public enum Updating { - UNDEFINED, BAZAAR, LOWEST_BINS + public void toggleHiddenStateForItem(String key) { + boolean removed = hiddenItems.remove(key); + if (!removed) { + hiddenItems.add(key); + } } } diff --git a/src/main/java/de/cowtipper/cowlection/chesttracker/HyBazaarData.java b/src/main/java/de/cowtipper/cowlection/chesttracker/HyBazaarData.java deleted file mode 100644 index 38aba6f..0000000 --- a/src/main/java/de/cowtipper/cowlection/chesttracker/HyBazaarData.java +++ /dev/null @@ -1,61 +0,0 @@ -package de.cowtipper.cowlection.chesttracker; - -import com.google.gson.annotations.SerializedName; - -import java.util.Map; - -@SuppressWarnings("unused") -public class HyBazaarData { - private boolean success; - private long lastUpdated; - @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") - private Map products; - - public boolean isSuccess() { - return success; - } - - /** - * Returns {@link Product} from bazaar reply. - * Returns null if product does not exist - * - * @param productId product in bazaar - * @return instance of Product - */ - public Product getProduct(String productId) { - return products.get(productId); - } - - /** - * Refresh only allowed once per minute - */ - public boolean allowRefreshData() { - return (System.currentTimeMillis() - lastUpdated) > 60000; - } - - public static class Product { - @SerializedName("quick_status") - private Status quickStatus; - - public double getInstantSellPrice() { - return quickStatus.getSellPrice(); - } - - public double getSellOfferPrice() { - return quickStatus.getBuyPrice(); - } - - public static class Status { - private double sellPrice; - private double buyPrice; - - public double getSellPrice() { - return sellPrice; - } - - public double getBuyPrice() { - return buyPrice; - } - } - } -} diff --git a/src/main/java/de/cowtipper/cowlection/chesttracker/ItemData.java b/src/main/java/de/cowtipper/cowlection/chesttracker/ItemData.java deleted file mode 100644 index e9bae4e..0000000 --- a/src/main/java/de/cowtipper/cowlection/chesttracker/ItemData.java +++ /dev/null @@ -1,110 +0,0 @@ -package de.cowtipper.cowlection.chesttracker; - -import net.minecraft.item.ItemStack; -import net.minecraft.util.EnumChatFormatting; - -public class ItemData { - private final String key; - private final ItemStack itemStack; - private final String name; - private int amount; - private double bazaarInstantSellPrice = 0; - private double bazaarSellOfferPrice = 0; - private int lowestBin = 0; - private PriceType priceType; - - public ItemData(String key, ItemStack itemStack) { - this.key = key; - this.itemStack = itemStack; - this.itemStack.stackSize = 1; - this.name = itemStack.getDisplayName(); - this.amount = 0; - this.priceType = PriceType.NONE; - } - - public String getKey() { - return key; - } - - public ItemStack getItemStack() { - return itemStack; - } - - public String getName() { - return name; - } - - public int getAmount() { - return amount; - } - - public double getPrice(boolean useInstantSellPrices) { - switch (priceType) { - case BAZAAR: - return useInstantSellPrices ? bazaarInstantSellPrice : bazaarSellOfferPrice; - case LOWEST_BIN: - return lowestBin; - default: - return 0; - } - } - - public double getPriceSum(boolean useInstantSellPrices) { - switch (priceType) { - case BAZAAR: - return useInstantSellPrices ? getBazaarInstantSellValue() : getBazaarSellOfferValue(); - case LOWEST_BIN: - return getLowestBinValue(); - default: - return 0; - } - } - - 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) { - this.amount += stackSize; - return this; - } - - public double getBazaarInstantSellValue() { - return amount * bazaarInstantSellPrice; - } - - public double getBazaarSellOfferValue() { - 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" + 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 deleted file mode 100644 index adfeee6..0000000 --- a/src/main/java/de/cowtipper/cowlection/chesttracker/LowestBinsCache.java +++ /dev/null @@ -1,9 +0,0 @@ -package de.cowtipper.cowlection.chesttracker; - -import java.util.HashMap; - -public class LowestBinsCache extends HashMap { - public boolean hasData() { - return size() > 0; - } -} diff --git a/src/main/java/de/cowtipper/cowlection/chesttracker/data/HyBazaarData.java b/src/main/java/de/cowtipper/cowlection/chesttracker/data/HyBazaarData.java new file mode 100644 index 0000000..17ffcf1 --- /dev/null +++ b/src/main/java/de/cowtipper/cowlection/chesttracker/data/HyBazaarData.java @@ -0,0 +1,61 @@ +package de.cowtipper.cowlection.chesttracker.data; + +import com.google.gson.annotations.SerializedName; + +import java.util.Map; + +@SuppressWarnings("unused") +public class HyBazaarData { + private boolean success; + private long lastUpdated; + @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") + private Map products; + + public boolean isSuccess() { + return success; + } + + /** + * Returns {@link Product} from bazaar reply. + * Returns null if product does not exist + * + * @param productId product in bazaar + * @return instance of Product + */ + public Product getProduct(String productId) { + return products.get(productId); + } + + /** + * Refresh only allowed once per minute + */ + public boolean allowRefreshData() { + return (System.currentTimeMillis() - lastUpdated) > 60000; + } + + public static class Product { + @SerializedName("quick_status") + private Status quickStatus; + + public double getInstantSellPrice() { + return quickStatus.getSellPrice(); + } + + public double getSellOfferPrice() { + return quickStatus.getBuyPrice(); + } + + public static class Status { + private double sellPrice; + private double buyPrice; + + public double getSellPrice() { + return sellPrice; + } + + public double getBuyPrice() { + return buyPrice; + } + } + } +} diff --git a/src/main/java/de/cowtipper/cowlection/chesttracker/data/HyItemsData.java b/src/main/java/de/cowtipper/cowlection/chesttracker/data/HyItemsData.java new file mode 100644 index 0000000..be9a2c2 --- /dev/null +++ b/src/main/java/de/cowtipper/cowlection/chesttracker/data/HyItemsData.java @@ -0,0 +1,31 @@ +package de.cowtipper.cowlection.chesttracker.data; + +import java.util.List; + +@SuppressWarnings("unused") +public class HyItemsData { + private boolean success; + @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") + private List items; + + public boolean isSuccess() { + return success; + } + + public List getItems() { + return items; + } + + public static class Item { + private String id; + private double npc_sell_price; + + public String getId() { + return id; + } + + public double getNpcSellPrice() { + return npc_sell_price; + } + } +} diff --git a/src/main/java/de/cowtipper/cowlection/chesttracker/data/ItemData.java b/src/main/java/de/cowtipper/cowlection/chesttracker/data/ItemData.java new file mode 100644 index 0000000..616032d --- /dev/null +++ b/src/main/java/de/cowtipper/cowlection/chesttracker/data/ItemData.java @@ -0,0 +1,147 @@ +package de.cowtipper.cowlection.chesttracker.data; + +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumChatFormatting; + +public class ItemData { + private final String key; + private final ItemStack itemStack; + private final String name; + private int amount; + private double bazaarInstantSellPrice = 0; + private double bazaarSellOfferPrice = 0; + private int lowestBin = 0; + private double npcPrice = 0; + private PriceType priceType; + private boolean isHidden = false; + + public ItemData(String key, ItemStack itemStack) { + this.key = key; + this.itemStack = itemStack; + this.itemStack.stackSize = 1; + this.name = itemStack.getDisplayName(); + this.amount = 0; + this.priceType = PriceType.NONE; + } + + public String getKey() { + return key; + } + + public ItemStack getItemStack() { + return itemStack; + } + + public String getName() { + return name; + } + + public int getAmount() { + return amount; + } + + public double getPrice(boolean useInstantSellPrices) { + switch (priceType) { + case BAZAAR: + return useInstantSellPrices ? bazaarInstantSellPrice : bazaarSellOfferPrice; + case LOWEST_BIN: + return lowestBin; + case NPC_SELL: + return npcPrice; + default: + return 0; + } + } + + public double getPriceSum(boolean useInstantSellPrices) { + switch (priceType) { + case BAZAAR: + return useInstantSellPrices ? getBazaarInstantSellValue() : getBazaarSellOfferValue(); + case LOWEST_BIN: + return getLowestBinValue(); + case NPC_SELL: + return getNpcSellValue(); + default: + return 0; + } + } + + 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 void setNpcPrice(double npcPrice) { + this.npcPrice = npcPrice; + this.priceType = PriceType.NPC_SELL; + } + + public ItemData addAmount(int stackSize) { + this.amount += stackSize; + return this; + } + + public double getBazaarInstantSellValue() { + return amount * bazaarInstantSellPrice; + } + + public double getBazaarSellOfferValue() { + return amount * bazaarSellOfferPrice; + } + + public long getLowestBinValue() { + return (long) amount * lowestBin; + } + + public long getNpcSellValue() { + return (long) Math.floor((long) amount * npcPrice); + } + + public PriceType getPriceType() { + return priceType; + } + + public boolean isHidden() { + return isHidden; + } + + public void setHidden(boolean hidden) { + isHidden = hidden; + } + + public String toCopyableFormat() { + 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()) + "\t" + + toCopyableFormat(npcPrice) + "\t" + toCopyableFormat(getNpcSellValue()); + } + + private String toCopyableFormat(double value) { + return value > 0 ? Long.toString(Math.round(value)) : ""; + } + + public enum PriceType { + LOWEST_BIN("BIN"), BAZAAR("BZ"), NPC_SELL("NPC"), NONE("-"); + + private final String indicator; + + PriceType(String indicator) { + this.indicator = indicator; + } + + public String getIndicator() { + return indicator; + } + } +} diff --git a/src/main/java/de/cowtipper/cowlection/chesttracker/data/LowestBinsCache.java b/src/main/java/de/cowtipper/cowlection/chesttracker/data/LowestBinsCache.java new file mode 100644 index 0000000..344f6bb --- /dev/null +++ b/src/main/java/de/cowtipper/cowlection/chesttracker/data/LowestBinsCache.java @@ -0,0 +1,9 @@ +package de.cowtipper.cowlection.chesttracker.data; + +import java.util.HashMap; + +public class LowestBinsCache extends HashMap { + 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 fa1ec29..0183a76 100644 --- a/src/main/java/de/cowtipper/cowlection/config/MooConfig.java +++ b/src/main/java/de/cowtipper/cowlection/config/MooConfig.java @@ -78,6 +78,7 @@ public class MooConfig { public static boolean chestAnalyzerShowBazaarItems; private static String chestAnalyzerUseBazaarPrices; public static boolean chestAnalyzerShowLowestBinItems; + public static boolean chestAnalyzerShowNpcItems; public static boolean chestAnalyzerShowNoPriceItems; public static boolean chestAnalyzerShowCommandUsage; public static int tooltipToggleKeyBinding; @@ -449,19 +450,25 @@ public class MooConfig { Property propNotifyServerAge = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), "notifyServerAge", true, "Show server age notifications?")); - // Sub-Category: Chest Analyzer (Bazaar & lowest BIN prices) - subCat = configCat.addSubCategory("Chest Tracker & Analyzer (Bazaar & lowest BIN prices)"); + // Sub-Category: Chest Analyzer (Bazaar, lowest BIN, & NPC prices) + subCat = configCat.addSubCategory("Chest Tracker & Analyzer (Bazaar, lowest BIN, & NPC 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."); + EnumChatFormatting.GREEN + " ❸ " + EnumChatFormatting.RESET + "use " + EnumChatFormatting.YELLOW + analyzeCommand + " stop" + EnumChatFormatting.RESET + " to stop the chest tracker and clear current results.", + "", + EnumChatFormatting.GREEN + "Inside the GUI you can also:", + EnumChatFormatting.WHITE + " ‣ " + EnumChatFormatting.GOLD + "double click" + EnumChatFormatting.WHITE + ": highlight chests with selected item; hover over the chat message to see chest coords", + EnumChatFormatting.WHITE + " ‣ " + EnumChatFormatting.GOLD + "right click" + EnumChatFormatting.WHITE + ": exclude item from sell value calculation"); 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 propChestAnalyzerShowNpcItems = subCat.addConfigEntry(cfg.get(configCat.getConfigName(), + "chestAnalyzerShowNpcItems", true, "Show NPC 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(), @@ -762,6 +769,7 @@ public class MooConfig { chestAnalyzerShowBazaarItems = propChestAnalyzerShowBazaarItems.getBoolean(); chestAnalyzerUseBazaarPrices = propChestAnalyzerUseBazaarPrices.getString(); chestAnalyzerShowLowestBinItems = propChestAnalyzerShowLowestBinItems.getBoolean(); + chestAnalyzerShowNpcItems = propChestAnalyzerShowNpcItems.getBoolean(); chestAnalyzerShowNoPriceItems = propChestAnalyzerShowNoPriceItems.getBoolean(); chestAnalyzerShowCommandUsage = propChestAnalyzerShowCommandUsage.getBoolean(); tooltipToggleKeyBinding = propTooltipToggleKeyBinding.getInt(); @@ -854,6 +862,7 @@ public class MooConfig { propChestAnalyzerShowBazaarItems.set(chestAnalyzerShowBazaarItems); propChestAnalyzerUseBazaarPrices.set(chestAnalyzerUseBazaarPrices); propChestAnalyzerShowLowestBinItems.set(chestAnalyzerShowLowestBinItems); + propChestAnalyzerShowNpcItems.set(chestAnalyzerShowNpcItems); propChestAnalyzerShowNoPriceItems.set(chestAnalyzerShowNoPriceItems); propChestAnalyzerShowCommandUsage.set(chestAnalyzerShowCommandUsage); propTooltipToggleKeyBinding.set(tooltipToggleKeyBinding); 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 3e72057..7c516c6 100644 --- a/src/main/java/de/cowtipper/cowlection/config/gui/MooConfigCategoryScrolling.java +++ b/src/main/java/de/cowtipper/cowlection/config/gui/MooConfigCategoryScrolling.java @@ -59,10 +59,7 @@ public class MooConfigCategoryScrolling extends GuiListExtended { this.mc = mc; listEntriesPreviews = new TreeMap<>(); - newConfigOptions = Sets.newHashSet("enableCopyInventory", "copyOrSaveWailaAndInventoryData", "maxLogFileSize", "maxLatestLogFileSize", - "chestAnalyzerShowLowestBinItems", "chestAnalyzerShowNoPriceItems", "bazaarSellAllOrderAscDesc", "bazaarShowItemsLeft", - "dungItemQualityShortenNonRandomized", "dungSendPerformanceOnEndScreen", "dungMarkPartiesWithArcher", "dungMarkPartiesWithBerserk", "dungMarkPartiesWithHealer", "dungMarkPartiesWithMage", "dungMarkPartiesWithTank", - "dungPartyFinderRuleEditorSimplified", "dungPartyFinderRuleEditorShowOpenButton", "dungSendWrongFloorWarning", "gotoKeyBindings"); + newConfigOptions = Sets.newHashSet("chestAnalyzerShowNpcItems"); explanations = new HashMap<>(); listEntries = new ArrayList<>(); } diff --git a/src/main/java/de/cowtipper/cowlection/util/ApiUtils.java b/src/main/java/de/cowtipper/cowlection/util/ApiUtils.java index 4e81004..663936f 100644 --- a/src/main/java/de/cowtipper/cowlection/util/ApiUtils.java +++ b/src/main/java/de/cowtipper/cowlection/util/ApiUtils.java @@ -5,8 +5,9 @@ import com.google.gson.JsonParser; 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.chesttracker.data.HyBazaarData; +import de.cowtipper.cowlection.chesttracker.data.HyItemsData; +import de.cowtipper.cowlection.chesttracker.data.LowestBinsCache; import de.cowtipper.cowlection.command.exception.ThrowingConsumer; import de.cowtipper.cowlection.config.CredentialStorage; import de.cowtipper.cowlection.data.*; @@ -33,6 +34,7 @@ public class ApiUtils { 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 ITEMS_URL = "https://api.hypixel.net/resources/skyblock/items"; 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(); @@ -137,6 +139,21 @@ public class ApiUtils { return new LowestBinsCache(); } + public static void fetchItemsData(ThrowingConsumer action) { + pool.execute(() -> action.accept(getItemsData())); + } + + private static HyItemsData getItemsData() { + try (BufferedReader reader = makeApiCall(ITEMS_URL)) { + if (reader != null) { + return GsonUtils.fromJson(reader, HyItemsData.class); + } + } catch (IOException | JsonSyntaxException e) { + handleApiException(e); + } + return null; + } + public static void fetchHyPlayerDetails(Friend stalkedPlayer, ThrowingConsumer action) { pool.execute(() -> action.accept(stalkHyPlayer(stalkedPlayer))); } diff --git a/src/main/java/de/cowtipper/cowlection/util/GsonUtils.java b/src/main/java/de/cowtipper/cowlection/util/GsonUtils.java index d2d3e28..c21a1fd 100644 --- a/src/main/java/de/cowtipper/cowlection/util/GsonUtils.java +++ b/src/main/java/de/cowtipper/cowlection/util/GsonUtils.java @@ -2,7 +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.chesttracker.data.LowestBinsCache; import de.cowtipper.cowlection.data.HyPlayerData; import net.minecraft.nbt.*; import net.minecraftforge.common.util.Constants; diff --git a/src/main/resources/assets/cowlection/lang/en_US.lang b/src/main/resources/assets/cowlection/lang/en_US.lang index bf38336..2691fe9 100644 --- a/src/main/resources/assets/cowlection/lang/en_US.lang +++ b/src/main/resources/assets/cowlection/lang/en_US.lang @@ -52,8 +52,10 @@ 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.chestAnalyzerShowNpcItems=Show NPC sell price items? +cowlection.config.chestAnalyzerShowNpcItems.tooltip=Should items with an §eNPC sell §rprice be displayed by default?\n§c(NPC sell price is only used if an item has neither a Bazaar nor lowest BIN price, or if one of them is hidden) 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.chestAnalyzerShowNoPriceItems.tooltip=Should items §ewithout §ra Bazaar, BIN, or NPC 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 -- cgit