aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/de
diff options
context:
space:
mode:
authorCow <cow@volloeko.de>2021-04-23 14:43:24 +0200
committerCow <cow@volloeko.de>2021-04-23 14:43:24 +0200
commitc4392eb697e507340454a8735e7b4d3bd297f5f1 (patch)
treef5cc857d8b9e5c8f9032d590b456663d5ad3123b /src/main/java/de
parent35f5d00656a968a003dd42b9150c9f19b1f3c9fd (diff)
downloadCowlection-c4392eb697e507340454a8735e7b4d3bd297f5f1.tar.gz
Cowlection-c4392eb697e507340454a8735e7b4d3bd297f5f1.tar.bz2
Cowlection-c4392eb697e507340454a8735e7b4d3bd297f5f1.zip
Added Chest Tracker & Analyzer
Diffstat (limited to 'src/main/java/de')
-rw-r--r--src/main/java/de/cowtipper/cowlection/Cowlection.java25
-rw-r--r--src/main/java/de/cowtipper/cowlection/chestTracker/ChestInteractionListener.java277
-rw-r--r--src/main/java/de/cowtipper/cowlection/chestTracker/ChestOverviewGui.java418
-rw-r--r--src/main/java/de/cowtipper/cowlection/chestTracker/ChestTracker.java170
-rw-r--r--src/main/java/de/cowtipper/cowlection/chestTracker/HyBazaarData.java61
-rw-r--r--src/main/java/de/cowtipper/cowlection/chestTracker/ItemData.java70
-rw-r--r--src/main/java/de/cowtipper/cowlection/command/MooCommand.java52
-rw-r--r--src/main/java/de/cowtipper/cowlection/config/MooConfig.java27
-rw-r--r--src/main/java/de/cowtipper/cowlection/search/GuiSearch.java1
-rw-r--r--src/main/java/de/cowtipper/cowlection/search/LogEntry.java (renamed from src/main/java/de/cowtipper/cowlection/data/LogEntry.java)2
-rw-r--r--src/main/java/de/cowtipper/cowlection/search/LogFilesSearcher.java1
-rw-r--r--src/main/java/de/cowtipper/cowlection/util/ApiUtils.java17
-rw-r--r--src/main/java/de/cowtipper/cowlection/util/Utils.java17
13 files changed, 1126 insertions, 12 deletions
diff --git a/src/main/java/de/cowtipper/cowlection/Cowlection.java b/src/main/java/de/cowtipper/cowlection/Cowlection.java
index 5e0e7bb..855e7c4 100644
--- a/src/main/java/de/cowtipper/cowlection/Cowlection.java
+++ b/src/main/java/de/cowtipper/cowlection/Cowlection.java
@@ -1,5 +1,6 @@
package de.cowtipper.cowlection;
+import de.cowtipper.cowlection.chestTracker.ChestTracker;
import de.cowtipper.cowlection.command.MooCommand;
import de.cowtipper.cowlection.command.ReplyCommand;
import de.cowtipper.cowlection.command.ShrugCommand;
@@ -47,9 +48,10 @@ public class Cowlection {
private ChatHelper chatHelper;
private PlayerCache playerCache;
private DungeonCache dungeonCache;
+ private ChestTracker chestTracker;
private Logger logger;
- @Mod.EventHandler
+ @EventHandler
public void preInit(FMLPreInitializationEvent e) {
instance = this;
logger = e.getModLog();
@@ -123,6 +125,27 @@ public class Cowlection {
return dungeonCache;
}
+ public boolean enableChestTracker() {
+ if (chestTracker == null) {
+ chestTracker = new ChestTracker(this);
+ return true;
+ }
+ return false;
+ }
+
+ public boolean disableChestTracker() {
+ if (chestTracker != null) {
+ chestTracker.clear();
+ chestTracker = null;
+ return true;
+ }
+ return false;
+ }
+
+ public ChestTracker getChestTracker() {
+ return chestTracker;
+ }
+
public File getConfigDirectory() {
return configDir;
}
diff --git a/src/main/java/de/cowtipper/cowlection/chestTracker/ChestInteractionListener.java b/src/main/java/de/cowtipper/cowlection/chestTracker/ChestInteractionListener.java
new file mode 100644
index 0000000..f4278fb
--- /dev/null
+++ b/src/main/java/de/cowtipper/cowlection/chestTracker/ChestInteractionListener.java
@@ -0,0 +1,277 @@
+package de.cowtipper.cowlection.chestTracker;
+
+import de.cowtipper.cowlection.Cowlection;
+import de.cowtipper.cowlection.util.AbortableRunnable;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.inventory.GuiChest;
+import net.minecraft.client.player.inventory.ContainerLocalMenu;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.client.renderer.Tessellator;
+import net.minecraft.client.renderer.WorldRenderer;
+import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
+import net.minecraft.inventory.ContainerChest;
+import net.minecraft.item.ItemStack;
+import net.minecraft.scoreboard.Score;
+import net.minecraft.scoreboard.ScoreObjective;
+import net.minecraft.scoreboard.ScorePlayerTeam;
+import net.minecraft.scoreboard.Scoreboard;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.tileentity.TileEntityChest;
+import net.minecraft.util.BlockPos;
+import net.minecraft.util.EnumChatFormatting;
+import net.minecraft.util.EnumFacing;
+import net.minecraft.util.Vec3;
+import net.minecraftforge.client.event.DrawBlockHighlightEvent;
+import net.minecraftforge.client.event.GuiScreenEvent;
+import net.minecraftforge.common.MinecraftForge;
+import net.minecraftforge.event.entity.player.PlayerInteractEvent;
+import net.minecraftforge.event.entity.player.PlayerSetSpawnEvent;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+import net.minecraftforge.fml.common.gameevent.TickEvent;
+import org.lwjgl.input.Keyboard;
+import org.lwjgl.opengl.GL11;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+public class ChestInteractionListener {
+ private TileEntityChest lastInteractedChest = null;
+ private boolean interactedWhileSneaking;
+ private boolean isOnOwnIsland;
+ private AbortableRunnable checkScoreboard;
+ private final Cowlection main;
+
+ public ChestInteractionListener(Cowlection main) {
+ this.main = main;
+ checkIfOnPrivateIsland();
+ }
+
+ @SubscribeEvent
+ public void onWorldEnter(PlayerSetSpawnEvent e) {
+ checkIfOnPrivateIsland();
+ }
+
+ private void checkIfOnPrivateIsland() {
+ stopScoreboardChecker();
+ isOnOwnIsland = false;
+
+ // check if player has entered or left their private island
+ checkScoreboard = new AbortableRunnable() {
+ private int retries = 20 * 20; // retry for up to 20 seconds
+
+ @SubscribeEvent
+ public void onTickCheckScoreboard(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--;
+ Scoreboard scoreboard = Minecraft.getMinecraft().theWorld.getScoreboard();
+ ScoreObjective scoreboardSidebar = scoreboard.getObjectiveInDisplaySlot(1);
+ if (scoreboardSidebar == null && retries >= 0) {
+ // scoreboard hasn't loaded yet, retry next tick
+ return;
+ } else if (scoreboardSidebar != null) {
+ // scoreboard loaded!
+ Collection<Score> scoreboardLines = scoreboard.getSortedScores(scoreboardSidebar);
+ for (Score line : scoreboardLines) {
+ ScorePlayerTeam scorePlayerTeam = scoreboard.getPlayersTeam(line.getPlayerName());
+ if (scorePlayerTeam != null) {
+ String lineWithoutFormatting = EnumChatFormatting.getTextWithoutFormattingCodes(scorePlayerTeam.getColorPrefix() + scorePlayerTeam.getColorSuffix());
+ if (lineWithoutFormatting.startsWith(" ⏣")) {
+ // current location: own private island or somewhere else?
+ isOnOwnIsland = lineWithoutFormatting.startsWith(" ⏣ Your Island");
+ break;
+ }
+ }
+ }
+ }
+ stop();
+ }
+ }
+
+ @Override
+ public void stop() {
+ if (!stopped) {
+ stopped = true;
+ retries = -1;
+ MinecraftForge.EVENT_BUS.unregister(this);
+ stopScoreboardChecker();
+ }
+ }
+
+ @Override
+ public void run() {
+ MinecraftForge.EVENT_BUS.register(this);
+ }
+ };
+ checkScoreboard.run();
+ }
+
+ private void stopScoreboardChecker() {
+ if (checkScoreboard != null) {
+ // there is still a scoreboard-checker running, stop it
+ checkScoreboard.stop();
+ checkScoreboard = null;
+ }
+ }
+
+ @SubscribeEvent
+ public void onRightClickChest(PlayerInteractEvent e) {
+ if (isOnOwnIsland && e.action == PlayerInteractEvent.Action.RIGHT_CLICK_BLOCK) {
+ TileEntity tileEntity = Minecraft.getMinecraft().theWorld.getTileEntity(e.pos);
+ if (tileEntity instanceof TileEntityChest) {
+ // Interacted with a chest at position e.pos
+ lastInteractedChest = ((TileEntityChest) tileEntity);
+ interactedWhileSneaking = Minecraft.getMinecraft().thePlayer.isSneaking();
+ }
+ }
+ }
+
+ @SubscribeEvent
+ public void onGuiClose(GuiScreenEvent.KeyboardInputEvent.Pre e) {
+ if (isOnOwnIsland && e.gui instanceof GuiChest && Keyboard.getEventKeyState() &&
+ // closing chest via ESC or key bind to open (and close) inventory (Default: E)
+ (Keyboard.getEventKey() == Keyboard.KEY_ESCAPE || Keyboard.getEventKey() == Minecraft.getMinecraft().gameSettings.keyBindInventory.getKeyCode())) {
+ if (lastInteractedChest == null || lastInteractedChest.isInvalid()) {
+ // gui wasn't a chest gui or chest got removed
+ return;
+ }
+ BlockPos chestPos = lastInteractedChest.getPos();
+ EnumFacing otherChestFacing = getOtherChestFacing(lastInteractedChest);
+
+ if (interactedWhileSneaking) {
+ // remove chest from cache
+ main.getChestTracker().removeChest(chestPos, otherChestFacing);
+ } else {
+ // add chest to cache
+ ContainerChest chestContainer = (ContainerChest) ((GuiChest) e.gui).inventorySlots;
+ ContainerLocalMenu chestInventory = (ContainerLocalMenu) chestContainer.getLowerChestInventory();
+
+ List<ItemStack> chestContents = new ArrayList<>();
+ for (int slot = 0; slot < chestInventory.getSizeInventory(); slot++) {
+ ItemStack item = chestInventory.getStackInSlot(slot);
+ if (item != null) {
+ chestContents.add(item);
+ }
+ }
+ main.getChestTracker().addChest(chestPos, chestContents, otherChestFacing);
+ }
+ lastInteractedChest = null;
+ }
+ }
+
+ /**
+ * Get the facing of the other chest of a double chest.
+ *
+ * @param chest clicked chest
+ * @return facing of the other chest of a double chest, EnumFacing.UP means no double chest
+ */
+ private EnumFacing getOtherChestFacing(TileEntityChest chest) {
+ EnumFacing otherChestFacing = EnumFacing.UP;
+ if (chest.adjacentChestXNeg != null) {
+ otherChestFacing = EnumFacing.WEST;
+ } else if (chest.adjacentChestXPos != null) {
+ otherChestFacing = EnumFacing.EAST;
+ } else if (chest.adjacentChestZNeg != null) {
+ otherChestFacing = EnumFacing.NORTH;
+ } else if (chest.adjacentChestZPos != null) {
+ otherChestFacing = EnumFacing.SOUTH;
+ }
+ return otherChestFacing;
+ }
+
+ /**
+ * Renders a bounding box around all cached chests.
+ * Partially taken from RenderManager#renderDebugBoundingBox
+ */
+ @SubscribeEvent
+ public void highlightChests(DrawBlockHighlightEvent e) {
+ if (isOnOwnIsland && Minecraft.getMinecraft().theWorld != null && Minecraft.getMinecraft().thePlayer != null) {
+ Set<BlockPos> cachedChestPositions = main.getChestTracker().getCachedPositions();
+ if (cachedChestPositions.isEmpty()) {
+ return;
+ }
+ // highlight chests whose contents have already been cached
+ Vec3 playerPos = e.player.getPositionEyes(e.partialTicks);
+ double xMinOffset = playerPos.xCoord - 0.06;
+ double xMaxOffset = playerPos.xCoord + 0.06;
+ double yMinOffset = playerPos.yCoord - Minecraft.getMinecraft().thePlayer.getEyeHeight() + /* to avoid z-fighting: */ 0.009999999776482582d;
+ double yMaxOffset = playerPos.yCoord + 0.12 - Minecraft.getMinecraft().thePlayer.getEyeHeight();
+ double zMinOffset = playerPos.zCoord - 0.06;
+ double zMaxOffset = playerPos.zCoord + 0.06;
+
+ GlStateManager.pushMatrix();
+ GlStateManager.depthMask(false);
+ GlStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, 1, 0);
+ GlStateManager.disableTexture2D();
+ Tessellator tessellator = Tessellator.getInstance();
+ WorldRenderer worldRenderer = tessellator.getWorldRenderer();
+
+ GlStateManager.color(55 / 255f, 155 / 255f, 55 / 255f, 100 / 255f);
+
+ for (BlockPos chestPos : cachedChestPositions) {
+ EnumFacing otherChestFacing = main.getChestTracker().getOtherChestFacing(chestPos);
+ double chestPosXMin = chestPos.getX() - xMinOffset - (otherChestFacing == EnumFacing.WEST ? 1 : 0);
+ double chestPosXMax = chestPos.getX() - xMaxOffset + 1 + (otherChestFacing == EnumFacing.EAST ? 1 : 0);
+ double chestPosYMin = chestPos.getY() - yMinOffset;
+ double chestPosYMax = chestPos.getY() - yMaxOffset + 1;
+ double chestPosZMin = chestPos.getZ() - zMinOffset - (otherChestFacing == EnumFacing.NORTH ? 1 : 0);
+ double chestPosZMax = chestPos.getZ() - zMaxOffset + 1 + (otherChestFacing == EnumFacing.SOUTH ? 1 : 0);
+
+ // one coordinate is always either min or max; the other two coords are: min,min > min,max > max,max > max,min
+
+ // down
+ worldRenderer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION);
+ worldRenderer.pos(chestPosXMin, chestPosYMin, chestPosZMin).endVertex();
+ worldRenderer.pos(chestPosXMin, chestPosYMin, chestPosZMax).endVertex();
+ worldRenderer.pos(chestPosXMax, chestPosYMin, chestPosZMax).endVertex();
+ worldRenderer.pos(chestPosXMax, chestPosYMin, chestPosZMin).endVertex();
+ tessellator.draw();
+ // up
+ worldRenderer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION);
+ worldRenderer.pos(chestPosXMin, chestPosYMax, chestPosZMin).endVertex();
+ worldRenderer.pos(chestPosXMin, chestPosYMax, chestPosZMax).endVertex();
+ worldRenderer.pos(chestPosXMax, chestPosYMax, chestPosZMax).endVertex();
+ worldRenderer.pos(chestPosXMax, chestPosYMax, chestPosZMin).endVertex();
+ tessellator.draw();
+ // north
+ worldRenderer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION);
+ worldRenderer.pos(chestPosXMin, chestPosYMin, chestPosZMin).endVertex();
+ worldRenderer.pos(chestPosXMin, chestPosYMax, chestPosZMin).endVertex();
+ worldRenderer.pos(chestPosXMax, chestPosYMax, chestPosZMin).endVertex();
+ worldRenderer.pos(chestPosXMax, chestPosYMin, chestPosZMin).endVertex();
+ tessellator.draw();
+ // south
+ worldRenderer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION);
+ worldRenderer.pos(chestPosXMin, chestPosYMin, chestPosZMax).endVertex();
+ worldRenderer.pos(chestPosXMin, chestPosYMax, chestPosZMax).endVertex();
+ worldRenderer.pos(chestPosXMax, chestPosYMax, chestPosZMax).endVertex();
+ worldRenderer.pos(chestPosXMax, chestPosYMin, chestPosZMax).endVertex();
+ tessellator.draw();
+ // west
+ worldRenderer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION);
+ worldRenderer.pos(chestPosXMin, chestPosYMin, chestPosZMin).endVertex();
+ worldRenderer.pos(chestPosXMin, chestPosYMin, chestPosZMax).endVertex();
+ worldRenderer.pos(chestPosXMin, chestPosYMax, chestPosZMax).endVertex();
+ worldRenderer.pos(chestPosXMin, chestPosYMax, chestPosZMin).endVertex();
+ tessellator.draw();
+ // east
+ worldRenderer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION);
+ worldRenderer.pos(chestPosXMax, chestPosYMin, chestPosZMin).endVertex();
+ worldRenderer.pos(chestPosXMax, chestPosYMin, chestPosZMax).endVertex();
+ worldRenderer.pos(chestPosXMax, chestPosYMax, chestPosZMax).endVertex();
+ worldRenderer.pos(chestPosXMax, chestPosYMax, chestPosZMin).endVertex();
+ tessellator.draw();
+ }
+ GlStateManager.enableTexture2D();
+ GlStateManager.depthMask(true);
+ GlStateManager.disableBlend();
+ GlStateManager.popMatrix();
+ }
+ }
+}
diff --git a/src/main/java/de/cowtipper/cowlection/chestTracker/ChestOverviewGui.java b/src/main/java/de/cowtipper/cowlection/chestTracker/ChestOverviewGui.java
new file mode 100644
index 0000000..970eb93
--- /dev/null
+++ b/src/main/java/de/cowtipper/cowlection/chestTracker/ChestOverviewGui.java
@@ -0,0 +1,418 @@
+package de.cowtipper.cowlection.chestTracker;
+
+import de.cowtipper.cowlection.Cowlection;
+import de.cowtipper.cowlection.config.MooConfig;
+import de.cowtipper.cowlection.search.GuiTooltip;
+import de.cowtipper.cowlection.util.*;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.audio.PositionedSoundRecord;
+import net.minecraft.client.gui.*;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.client.renderer.RenderHelper;
+import net.minecraft.client.renderer.Tessellator;
+import net.minecraft.util.EnumChatFormatting;
+import net.minecraft.util.ResourceLocation;
+import net.minecraftforge.common.MinecraftForge;
+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 java.io.IOException;
+import java.util.*;
+
+public class ChestOverviewGui extends GuiScreen {
+ private ItemOverview itemOverview;
+ private List<GuiTooltip> guiTooltips;
+ private GuiButton btnClose;
+ private GuiButton btnUpdateBazaar;
+ private GuiButton btnCopy;
+ private GuiCheckBox showNonBazaarItems;
+ private GuiButton btnBazaarInstantOrOffer;
+ private AbortableRunnable updateBazaar;
+ private final String screenTitle;
+ private final Cowlection main;
+
+ public ChestOverviewGui(Cowlection main) {
+ this.screenTitle = Cowlection.MODNAME + " Chest Analyzer";
+ this.main = main;
+ }
+
+ @Override
+ 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"));
+ 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)"));
+ // copy to clipboard
+ this.buttonList.add(this.btnCopy = new GuiButton(21, this.width - 280, 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"));
+ addTooltip(btnBazaarInstantOrOffer, Collections.singletonList(EnumChatFormatting.YELLOW + "Use " + EnumChatFormatting.GOLD + "Instant-Sell " + EnumChatFormatting.YELLOW + "or " + EnumChatFormatting.GOLD + "Sell Offer" + EnumChatFormatting.YELLOW + " prices?"));
+ // main item gui
+ this.itemOverview = new ItemOverview();
+ }
+
+ private <T extends Gui> void addTooltip(T field, List<String> tooltip) {
+ GuiTooltip guiTooltip = new GuiTooltip(field, tooltip);
+ this.guiTooltips.add(guiTooltip);
+ }
+
+ @Override
+ public void drawScreen(int mouseX, int mouseY, float partialTicks) {
+ btnUpdateBazaar.enabled = updateBazaar == null && main.getChestTracker().allowUpdateBazaar();
+ itemOverview.drawScreen(mouseX, mouseY, partialTicks);
+ this.drawString(this.fontRendererObj, this.screenTitle, itemOverview.getLeftX(), 10, 0xFFCC00);
+ super.drawScreen(mouseX, mouseY, partialTicks);
+ itemOverview.drawScreenPost(mouseX, mouseY);
+ for (GuiTooltip guiTooltip : guiTooltips) {
+ if (guiTooltip.checkHover(mouseX, mouseY)) {
+ GuiHelper.drawHoveringText(guiTooltip.getText(), mouseX, mouseY, width, height, 300);
+ // only one tooltip can be displayed at a time: break!
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void drawDefaultBackground() {
+ }
+
+ @Override
+ public void drawWorldBackground(int tint) {
+ }
+
+ @Override
+ public void drawBackground(int tint) {
+ // = dirt background
+ }
+
+ @Override
+ protected void actionPerformed(GuiButton button) {
+ 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!
+ 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 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) {
+ 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)");
+ 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);
+ 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.itemOverview.reloadItemData();
+ }
+ }
+ }
+
+ private void stopScoreboardChecker() {
+ if (updateBazaar != null) {
+ // there is still a bazaar update-checker running, stop it
+ updateBazaar.stop();
+ updateBazaar = null;
+ }
+ }
+
+ @Override
+ public void onGuiClosed() {
+ if (MooConfig.chestAnalyzerShowCommandUsage) {
+ main.getChatHelper().sendMessage(new MooChatComponent(EnumChatFormatting.GRAY + "Use " + EnumChatFormatting.WHITE + "/moo analyzeChests stop " + EnumChatFormatting.GRAY + "to stop the Chest Analyzer.").setSuggestCommand("/moo analyzeChests stop")
+ .appendFreshSibling(new MooChatComponent(EnumChatFormatting.GRAY + "Or continue adding more chests and run " + EnumChatFormatting.WHITE + "/moo analyzeChests " + EnumChatFormatting.GRAY + " again to re-run the analysis.").setSuggestCommand("/moo analyzeChests")));
+ }
+ }
+
+ @Override
+ public boolean doesGuiPauseGame() {
+ return true;
+ }
+
+ @Override
+ public void handleMouseInput() throws IOException {
+ super.handleMouseInput();
+
+ if (this.itemOverview != null) {
+ this.itemOverview.handleMouseInput();
+ }
+ }
+
+ /**
+ * Inspired by {@link net.minecraft.client.gui.achievement.GuiStats}
+ */
+ class ItemOverview extends GuiSlot {
+ private Column orderBy = Column.PRICE_SUM;
+ private boolean orderDesc = true;
+ private long lastOrderChange;
+ private List<ItemData> itemDataHolder;
+ private int summedValueInstaSell;
+ private int summedValueSellOffer;
+
+ ItemOverview() {
+ super(ChestOverviewGui.this.mc, ChestOverviewGui.this.width, ChestOverviewGui.this.height, 32, ChestOverviewGui.this.height - 32, 16);
+ this.setShowSelectionBox(false);
+ // space above first entry for control buttons
+ int headerPadding = 20;
+ this.setHasListHeader(true, headerPadding);
+
+ reloadItemData();
+ }
+
+ private void reloadItemData() {
+ boolean useInstantSellPrices = "Use Instant-Sell prices".equals(btnBazaarInstantOrOffer.displayString);
+ itemDataHolder = main.getChestTracker().getAnalysisResult(orderBy, orderDesc, useInstantSellPrices);
+ summedValueInstaSell = 0;
+ summedValueSellOffer = 0;
+ boolean showNonBazaarItems = ChestOverviewGui.this.showNonBazaarItems.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();
+ }
+ }
+ }
+
+ @Override
+ protected void drawListHeader(int x, int y, Tessellator tessellator) {
+ if (y < 0) {
+ // header not on screen
+ return;
+ }
+ int arrowX = -50;
+ // 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);
+ if (column == orderBy) {
+ arrowX = columnX;
+ }
+ }
+ // draw arrow down/up
+ GuiHelper.drawSprite(arrowX - 18, y - 3, 18 + (orderDesc ? 0 : 18), 0, 500);
+ }
+
+ @Override
+ public void actionPerformed(GuiButton button) {
+ super.actionPerformed(button);
+ }
+
+ @Override
+ protected int getSize() {
+ return this.itemDataHolder.size();
+ }
+
+ /**
+ * GuiSlot#clickedHeader
+ */
+ @Override
+ protected void func_148132_a(int x, int y) {
+ long now = System.currentTimeMillis();
+ if (now - lastOrderChange < 50) {
+ // detected two clicks
+ return;
+ }
+ lastOrderChange = now;
+
+ int allowedMargin = 1; // tolerance to the left and right of a word to still be considered a click
+ for (Column column : Column.values()) {
+ int xOffset = getLeftX() + column.getXOffset();
+ int columnTitleWidth = ChestOverviewGui.this.fontRendererObj.getStringWidth(column.getName());
+ int columnXMin;
+ int columnXMax;
+ if (column == Column.ITEM_NAME) {
+ // aligned left
+ columnXMin = xOffset - /* up/down arrow */ 16;
+ columnXMax = xOffset + columnTitleWidth;
+ } else {
+ // aligned right
+ columnXMin = xOffset - columnTitleWidth - /* up/down arrow */ 16;
+ columnXMax = xOffset;
+ }
+
+ if (mouseX + allowedMargin >= columnXMin && mouseX - allowedMargin <= columnXMax) {
+ // clicked on column title!
+ orderDesc = orderBy != column || !orderDesc;
+ orderBy = column;
+ mc.getSoundHandler().playSound(PositionedSoundRecord.create(new ResourceLocation("gui.button.press"), 1.0F));
+ break;
+ }
+ }
+ reloadItemData();
+ }
+
+ @Override
+ protected void elementClicked(int slotIndex, boolean isDoubleClick, int mouseX, int mouseY) {
+ }
+
+ @Override
+ protected boolean isSelected(int slotIndex) {
+ return false;
+ }
+
+ @Override
+ protected void drawBackground() {
+ }
+
+ @Override
+ public int getListWidth() {
+ return this.width - 30;
+ }
+
+ @Override
+ 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);
+
+ // render item icon without shadows
+ GlStateManager.enableRescaleNormal();
+ RenderHelper.enableGUIStandardItemLighting();
+ ChestOverviewGui.this.itemRender.renderItemIntoGUI(itemData.getItemStack(), x, y - 3);
+ RenderHelper.disableStandardItemLighting();
+ GlStateManager.disableRescaleNormal();
+
+ FontRenderer fontRenderer = ChestOverviewGui.this.fontRendererObj;
+ String itemAmount = Utils.formatNumber(itemData.getAmount());
+ int amountXPos = x + Column.ITEM_AMOUNT.getXOffset() - fontRenderer.getStringWidth(itemAmount);
+ int itemNameXPos = x + Column.ITEM_NAME.getXOffset();
+ String itemName = fontRenderer.trimStringToWidth(itemData.getName(), amountXPos - itemNameXPos - 5);
+ 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);
+
+ 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);
+
+ 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);
+ }
+
+ 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);
+
+ if (isMouseYWithinSlotBounds(mouseY)) {
+ int slotIndex = this.getSlotIndexFromScreenCoords(mouseX, mouseY);
+
+ if (slotIndex >= 0) {
+ // mouse is over a slot: maybe draw item tooltip
+ int xMax = xMin + 16; // 16 = item icon width
+ if (mouseX < xMin || mouseX > xMax) {
+ // mouseX outside of valid item x