diff options
| author | Cow <cow@volloeko.de> | 2021-04-23 14:43:24 +0200 |
|---|---|---|
| committer | Cow <cow@volloeko.de> | 2021-04-23 14:43:24 +0200 |
| commit | c4392eb697e507340454a8735e7b4d3bd297f5f1 (patch) | |
| tree | f5cc857d8b9e5c8f9032d590b456663d5ad3123b /src/main/java/de | |
| parent | 35f5d00656a968a003dd42b9150c9f19b1f3c9fd (diff) | |
| download | Cowlection-c4392eb697e507340454a8735e7b4d3bd297f5f1.tar.gz Cowlection-c4392eb697e507340454a8735e7b4d3bd297f5f1.tar.bz2 Cowlection-c4392eb697e507340454a8735e7b4d3bd297f5f1.zip | |
Added Chest Tracker & Analyzer
Diffstat (limited to 'src/main/java/de')
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 |
