aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md3
-rw-r--r--README.md2
-rw-r--r--src/main/java/de/cowtipper/cowlection/chesttracker/ChestOverviewGui.java236
-rw-r--r--src/main/java/de/cowtipper/cowlection/chesttracker/ChestTracker.java118
-rw-r--r--src/main/java/de/cowtipper/cowlection/chesttracker/ItemData.java62
-rw-r--r--src/main/java/de/cowtipper/cowlection/chesttracker/LowestBinsCache.java10
-rw-r--r--src/main/java/de/cowtipper/cowlection/config/MooConfig.java24
-rw-r--r--src/main/java/de/cowtipper/cowlection/config/gui/MooConfigCategoryScrolling.java7
-rw-r--r--src/main/java/de/cowtipper/cowlection/config/gui/MooConfigPreview.java4
-rw-r--r--src/main/java/de/cowtipper/cowlection/data/DataHelper.java6
-rw-r--r--src/main/java/de/cowtipper/cowlection/data/HySkyBlockStats.java18
-rw-r--r--src/main/java/de/cowtipper/cowlection/listener/skyblock/SkyBlockListener.java21
-rw-r--r--src/main/java/de/cowtipper/cowlection/util/ApiUtils.java19
-rw-r--r--src/main/java/de/cowtipper/cowlection/util/GsonUtils.java31
-rw-r--r--src/main/resources/assets/cowlection/lang/en_US.lang8
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
diff --git a/README.md b/README.md
index d23bc26..f8fad6d 100644
--- a/README.md
+++ b/README.md
@@ -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