aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md6
-rw-r--r--README.md2
-rw-r--r--src/main/java/de/cowtipper/cowlection/chesttracker/ChestOverviewGui.java228
-rw-r--r--src/main/java/de/cowtipper/cowlection/chesttracker/ChestTracker.java147
-rw-r--r--src/main/java/de/cowtipper/cowlection/chesttracker/data/HyBazaarData.java (renamed from src/main/java/de/cowtipper/cowlection/chesttracker/HyBazaarData.java)2
-rw-r--r--src/main/java/de/cowtipper/cowlection/chesttracker/data/HyItemsData.java31
-rw-r--r--src/main/java/de/cowtipper/cowlection/chesttracker/data/ItemData.java (renamed from src/main/java/de/cowtipper/cowlection/chesttracker/ItemData.java)43
-rw-r--r--src/main/java/de/cowtipper/cowlection/chesttracker/data/LowestBinsCache.java (renamed from src/main/java/de/cowtipper/cowlection/chesttracker/LowestBinsCache.java)2
-rw-r--r--src/main/java/de/cowtipper/cowlection/config/MooConfig.java15
-rw-r--r--src/main/java/de/cowtipper/cowlection/config/gui/MooConfigCategoryScrolling.java5
-rw-r--r--src/main/java/de/cowtipper/cowlection/util/ApiUtils.java21
-rw-r--r--src/main/java/de/cowtipper/cowlection/util/GsonUtils.java2
-rw-r--r--src/main/resources/assets/cowlection/lang/en_US.lang4
13 files changed, 418 insertions, 90 deletions
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 <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 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<GuiTooltip> 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();
}
@@ -79,10 +108,23 @@ public class ChestOverviewGui extends GuiScreen {
}
@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<ChestTracker.Updating> updating = this.main.getChestTracker().refreshPriceCache();
- if (updating.size() > 1) {
+ EnumSet<ItemData.PriceType> 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
@@ -199,6 +256,15 @@ public class ChestOverviewGui extends GuiScreen {
}
@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<ItemData.PriceType> 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<ItemData> 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
@@ -357,6 +450,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<BlockPos, List<ItemStack>> chestCache = new HashMap<>();
private final Map<BlockPos, EnumFacing> doubleChestCache = new HashMap<>();
private final Set<BlockPos> chestsWithWantedItem = new HashSet<>();
+ private final Set<String> hiddenItems = new HashSet<>();
private Map<String, ItemData> analysisResult = new HashMap<>();
private ChestInteractionListener chestInteractionListener;
private HyBazaarData bazaarCache;
private LowestBinsCache lowestBinsCache;
+ private Map<String, Double> npcSellCache;
private final Cowlection main;
public ChestTracker(Cowlection main) {
@@ -120,33 +129,61 @@ public class ChestTracker {
/**
* Returns ordered analysis result with prices
*/
- public List<ItemData> getAnalysisResult(ChestOverviewGui.Column orderBy, boolean orderDesc, boolean useInstantSellPrices) {
+ public List<ItemData> getAnalysisResult(String searchQuery, ChestOverviewGui.Column orderBy, boolean orderDesc, EnumSet<ItemData.PriceType> visiblePriceTypes, boolean useInstantSellPrices) {
List<ItemData> 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<String, ItemData> 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<ItemData> 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<Updating> refreshPriceCache() {
- EnumSet<Updating> updating = EnumSet.of(Updating.UNDEFINED);
+ public EnumSet<ItemData.PriceType> refreshPriceCache() {
+ EnumSet<ItemData.PriceType> 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<BlockPos, Integer> chestsWithWantedItemsAndCount = new TreeMap<>();
+
if (sbKey.endsWith("_ambiguous")) {
sbKey = sbKey.substring(0, sbKey.length() - 10);
}
- int relevantChests = 0;
+ int mostWantedItemsInOneChest = 0;
for (Map.Entry<BlockPos, List<ItemStack>> 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/data/HyBazaarData.java
index 38aba6f..17ffcf1 100644
--- a/src/main/java/de/cowtipper/cowlection/chesttracker/HyBazaarData.java
+++ b/src/main/java/de/cowtipper/cowlection/chesttracker/data/HyBazaarData.java
@@ -1,4 +1,4 @@
-package de.cowtipper.cowlection.chesttracker;
+package de.cowtipper.cowlection.chesttracker.data;
import com.google.gson.annotations.SerializedName;
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<Item> items;
+
+ public boolean isSuccess() {
+ return success;
+ }
+
+ public List<Item> 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/ItemData.java b/src/main/java/de/cowtipper/cowlection/chesttracker/data/ItemData.java
index e9bae4e..616032d 100644
--- a/src/main/java/de/cowtipper/cowlection/chesttracker/ItemData.java
+++ b/src/main/java/de/cowtipper/cowlection/chesttracker/data/ItemData.java
@@ -1,4 +1,4 @@
-package de.cowtipper.cowlection.chesttracker;
+package de.cowtipper.cowlection.chesttracker.data;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumChatFormatting;
@@ -11,7 +11,9 @@ public class ItemData {
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;
@@ -44,6 +46,8 @@ public class ItemData {
return useInstantSellPrices ? bazaarInstantSellPrice : bazaarSellOfferPrice;
case LOWEST_BIN:
return lowestBin;
+ case NPC_SELL:
+ return npcPrice;
default:
return 0;
}
@@ -55,6 +59,8 @@ public class ItemData {
return useInstantSellPrices ? getBazaarInstantSellValue() : getBazaarSellOfferValue();
case LOWEST_BIN:
return getLowestBinValue();
+ case NPC_SELL:
+ return getNpcSellValue();
default:
return 0;
}
@@ -75,6 +81,11 @@ public class ItemData {
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;
@@ -92,12 +103,28 @@ public class ItemData {
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());
+ 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) {
@@ -105,6 +132,16 @@ public class ItemData {
}
public enum PriceType {
- BAZAAR, LOWEST_BIN, NONE
+ 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/LowestBinsCache.java b/src/main/java/de/cowtipper/cowlection/chesttracker/data/LowestBinsCache.java
index adfeee6..344f6bb 100644
--- a/src/main/java/de/cowtipper/cowlection/chesttracker/LowestBinsCache.java
+++ b/src/main/java/de/cowtipper/cowlection/chesttracker/data/LowestBinsCache.java
@@ -1,4 +1,4 @@
-package de.cowtipper.cowlection.chesttracker;
+package de.cowtipper.cowlection.chesttracker.data;
import java.util.HashMap;
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<HyItemsData> 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<HyPlayerData> 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