diff options
author | Lulonaut <lulonaut@tutanota.de> | 2023-03-12 00:54:04 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-03-12 10:54:04 +1100 |
commit | 193ba468e43bd4db5b5534d17472078708783349 (patch) | |
tree | 43532db60d853628242842763c1bf39ccb9d11d7 | |
parent | 5f147d6adbe9898239a0cb86e4daaa74c9e4c08a (diff) | |
download | NotEnoughUpdates-193ba468e43bd4db5b5534d17472078708783349.tar.gz NotEnoughUpdates-193ba468e43bd4db5b5534d17472078708783349.tar.bz2 NotEnoughUpdates-193ba468e43bd4db5b5534d17472078708783349.zip |
cheapest museum item to donate (#522)
Co-authored-by: nea <romangraef@gmail.com>
Co-authored-by: nea <nea@nea.moe>
Co-authored-by: Roman / Linnea Gräf <roman.graef@gmail.com>
Co-authored-by: nopo <nopotheemail@gmail.com>
Co-authored-by: hannibal2 <24389977+hannibal00212@users.noreply.github.com>
17 files changed, 1026 insertions, 362 deletions
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java b/src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java index 7f0136ee..8b6c2255 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java @@ -43,6 +43,8 @@ import io.github.moulberry.notenoughupdates.miscfeatures.PetInfoOverlay; import io.github.moulberry.notenoughupdates.miscfeatures.SlotLocking; import io.github.moulberry.notenoughupdates.miscfeatures.StorageManager; import io.github.moulberry.notenoughupdates.miscfeatures.customblockzones.CustomBlockSounds; +import io.github.moulberry.notenoughupdates.miscfeatures.inventory.MuseumCheapestItemOverlay; +import io.github.moulberry.notenoughupdates.miscfeatures.inventory.MuseumItemHighlighter; import io.github.moulberry.notenoughupdates.miscfeatures.updater.AutoUpdater; import io.github.moulberry.notenoughupdates.mixins.AccessorMinecraft; import io.github.moulberry.notenoughupdates.oneconfig.IOneConfigCompat; @@ -168,6 +170,13 @@ public class NotEnoughUpdates { private File neuDir; private boolean hasSkyblockScoreboard; + public NotEnoughUpdates() { + // Budget Construction Event + ((AccessorMinecraft) FMLClientHandler.instance().getClient()) + .onGetDefaultResourcePacks() + .add(new NEURepoResourcePack(null, "neurepo")); + } + public File getConfigFile() { return this.configFile; } @@ -180,13 +189,6 @@ public class NotEnoughUpdates { return this.neuDir; } - public NotEnoughUpdates() { - // Budget Construction Event - ((AccessorMinecraft) FMLClientHandler.instance().getClient()) - .onGetDefaultResourcePacks() - .add(new NEURepoResourcePack(null, "neurepo")); - } - /** * Instantiates NEUIo, NEUManager and NEUOverlay instances. Registers keybinds and adds a shutdown hook to clear tmp folder. */ @@ -270,6 +272,8 @@ public class NotEnoughUpdates { MinecraftForge.EVENT_BUS.register(navigation); MinecraftForge.EVENT_BUS.register(new WorldListener(this)); AutoLoad.INSTANCE.provide(supplier -> MinecraftForge.EVENT_BUS.register(supplier.get())); + MinecraftForge.EVENT_BUS.register(MuseumItemHighlighter.INSTANCE); + MinecraftForge.EVENT_BUS.register(MuseumCheapestItemOverlay.INSTANCE); if (Minecraft.getMinecraft().getResourceManager() instanceof IReloadableResourceManager) { IReloadableResourceManager manager = (IReloadableResourceManager) Minecraft.getMinecraft().getResourceManager(); diff --git a/src/main/java/io/github/moulberry/notenoughupdates/listener/RenderListener.java b/src/main/java/io/github/moulberry/notenoughupdates/listener/RenderListener.java index 03f3a449..5e39aa45 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/listener/RenderListener.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/listener/RenderListener.java @@ -28,12 +28,11 @@ import io.github.moulberry.notenoughupdates.NotEnoughUpdates; import io.github.moulberry.notenoughupdates.auction.CustomAHGui; import io.github.moulberry.notenoughupdates.core.GuiScreenElementWrapper; import io.github.moulberry.notenoughupdates.dungeons.DungeonWin; +import io.github.moulberry.notenoughupdates.events.ButtonExclusionZoneEvent; import io.github.moulberry.notenoughupdates.miscfeatures.AbiphoneWarning; import io.github.moulberry.notenoughupdates.miscfeatures.AuctionBINWarning; -import io.github.moulberry.notenoughupdates.miscfeatures.AuctionProfit; import io.github.moulberry.notenoughupdates.miscfeatures.BetterContainers; import io.github.moulberry.notenoughupdates.miscfeatures.CrystalMetalDetectorSolver; -import io.github.moulberry.notenoughupdates.miscfeatures.DungeonNpcProfitOverlay; import io.github.moulberry.notenoughupdates.miscfeatures.EnchantingSolvers; import io.github.moulberry.notenoughupdates.miscfeatures.PresetWarning; import io.github.moulberry.notenoughupdates.miscfeatures.StorageManager; @@ -45,20 +44,18 @@ import io.github.moulberry.notenoughupdates.miscgui.GuiInvButtonEditor; import io.github.moulberry.notenoughupdates.miscgui.GuiItemRecipe; import io.github.moulberry.notenoughupdates.miscgui.StorageOverlay; import io.github.moulberry.notenoughupdates.miscgui.TradeWindow; -import io.github.moulberry.notenoughupdates.miscgui.TrophyRewardOverlay; import io.github.moulberry.notenoughupdates.miscgui.hex.GuiCustomHex; -import io.github.moulberry.notenoughupdates.miscgui.minionhelper.MinionHelperManager; import io.github.moulberry.notenoughupdates.mixins.AccessorGuiContainer; import io.github.moulberry.notenoughupdates.options.NEUConfig; import io.github.moulberry.notenoughupdates.overlays.AuctionSearchOverlay; import io.github.moulberry.notenoughupdates.overlays.BazaarSearchOverlay; -import io.github.moulberry.notenoughupdates.overlays.EquipmentOverlay; import io.github.moulberry.notenoughupdates.overlays.OverlayManager; import io.github.moulberry.notenoughupdates.overlays.RancherBootOverlay; import io.github.moulberry.notenoughupdates.overlays.TextOverlay; import io.github.moulberry.notenoughupdates.profileviewer.GuiProfileViewer; import io.github.moulberry.notenoughupdates.util.ItemUtils; import io.github.moulberry.notenoughupdates.util.NotificationHandler; +import io.github.moulberry.notenoughupdates.util.Rectangle; import io.github.moulberry.notenoughupdates.util.SBInfo; import io.github.moulberry.notenoughupdates.util.Utils; import net.minecraft.client.Minecraft; @@ -105,6 +102,7 @@ import java.util.Map; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -484,96 +482,68 @@ public class RenderListener { if (CalendarOverlay.isEnabled() || event.isCanceled()) return; if (NotEnoughUpdates.INSTANCE.hasSkyblockScoreboard() && NotificationHandler.shouldRenderOverlay(event.gui) && event.gui instanceof GuiContainer) { - doInventoryButtons = true; - - int zOffset = 50; - - GlStateManager.translate(0, 0, zOffset); - - int xSize = ((AccessorGuiContainer) event.gui).getXSize(); - int ySize = ((AccessorGuiContainer) event.gui).getYSize(); - int guiLeft = ((AccessorGuiContainer) event.gui).getGuiLeft(); - int guiTop = ((AccessorGuiContainer) event.gui).getGuiTop(); + renderButtons((GuiContainer) event.gui); + } + } - if (!NEUApi.disableInventoryButtons) { - if (!EnchantingSolvers.disableButtons()) { - for (NEUConfig.InventoryButton button : NotEnoughUpdates.INSTANCE.config.hidden.inventoryButtons) { - if (!button.isActive()) continue; - if (button.playerInvOnly && !(event.gui instanceof GuiInventory)) continue; + public void iterateButtons(GuiContainer gui, BiConsumer<NEUConfig.InventoryButton, Rectangle> acceptButton) { + if (NEUApi.disableInventoryButtons || EnchantingSolvers.disableButtons()) { + return; + } - int x = guiLeft + button.x; - int y = guiTop + button.y; - if (button.anchorRight) { - x += xSize; - } - if (button.anchorBottom) { - y += ySize; - } - if (AccessoryBagOverlay.isInAccessoryBag()) { - if (x > guiLeft + xSize && x < guiLeft + xSize + 80 + 28 + 5 && y > guiTop - 18 && y < guiTop + 150) { - x += 80 + 28; - } - } - if (TrophyRewardOverlay.inTrophyFishingInventory()) { - int diffX = 162; - if (x > guiLeft + xSize && x < guiLeft + xSize + diffX + 5 && y > guiTop - 18 && y < guiTop + 120) { - x += diffX; - } - } - if (MinionHelperManager.getInstance().inCraftedMinionsInventory()) { - int diffX = 172; - if (x > guiLeft + xSize && x < guiLeft + xSize + diffX + 5 && y > guiTop - 18 && y < guiTop + 128) { - x += diffX; - } - } - if (AuctionProfit.inAuctionPage()) { - if (x + 18 > guiLeft + xSize && x + 18 < guiLeft + xSize + 4 + 28 + 20 && y > guiTop - 180 && - y < guiTop + 56) { - x -= 68 - 200; - } - } - if (EquipmentOverlay.isRenderingArmorHud()) { - if (x < guiLeft + xSize - 150 && x > guiLeft + xSize - 200 && y > guiTop && y < guiTop + 84) { - x -= 25; - } - } - if (EquipmentOverlay.isRenderingPetHud()) { - if (x < guiLeft + xSize - 150 && x > guiLeft + xSize - 200 && y > guiTop + 60 && y < guiTop + 120) { - x -= 25; - } - } - if (inDungeonPage || DungeonNpcProfitOverlay.isRendering()) { - if (x + 10 > guiLeft + xSize && x + 18 < guiLeft + xSize + 4 + 28 + 20 && y > guiTop - 180 && - y < guiTop + 100) { - x += 185; - } - } + AccessorGuiContainer accessor = (AccessorGuiContainer) gui; + Rectangle guiRectangle = new Rectangle( + accessor.getGuiLeft(), + accessor.getGuiTop(), + accessor.getXSize(), + accessor.getYSize() + ); + + ButtonExclusionZoneEvent buttonExclusionZoneEvent = new ButtonExclusionZoneEvent(gui, guiRectangle); + buttonExclusionZoneEvent.post(); + for (NEUConfig.InventoryButton button : NotEnoughUpdates.INSTANCE.config.hidden.inventoryButtons) { + if (!button.isActive()) continue; + if (button.playerInvOnly && !(gui instanceof GuiInventory)) continue; + + Rectangle buttonPosition = buttonExclusionZoneEvent.findButtonPosition(new Rectangle( + accessor.getGuiLeft() + button.x + (button.anchorRight ? accessor.getXSize() : 0), + accessor.getGuiTop() + button.y + (button.anchorBottom ? accessor.getYSize() : 0), + 18, 18 + ) + ); + acceptButton.accept(button, buttonPosition); + } + } - GlStateManager.color(1, 1, 1, 1f); - - GlStateManager.enableDepth(); - GlStateManager.enableAlpha(); - Minecraft.getMinecraft().getTextureManager().bindTexture(EDITOR); - Utils.drawTexturedRect( - x, - y, - 18, - 18, - button.backgroundIndex * 18 / 256f, - (button.backgroundIndex * 18 + 18) / 256f, - 18 / 256f, - 36 / 256f, - GL11.GL_NEAREST - ); + public void renderButtons(GuiContainer gui) { + doInventoryButtons = true; - if (button.icon != null && !button.icon.trim().isEmpty()) { - GuiInvButtonEditor.renderIcon(button.icon, x + 1, y + 1); - } - } - } + int zOffset = 50; + GlStateManager.pushMatrix(); + GlStateManager.translate(0, 0, zOffset); + iterateButtons(gui, (button, buttonPosition) -> { + GlStateManager.color(1, 1, 1, 1f); + GlStateManager.enableDepth(); + GlStateManager.enableAlpha(); + + Minecraft.getMinecraft().getTextureManager().bindTexture(EDITOR); + Utils.drawTexturedRect( + buttonPosition.getX(), + buttonPosition.getY(), + 18, + 18, + button.backgroundIndex * 18 / 256f, + (button.backgroundIndex * 18 + 18) / 256f, + 18 / 256f, + 36 / 256f, + GL11.GL_NEAREST + ); + + if (button.icon != null && !button.icon.trim().isEmpty()) { + GuiInvButtonEditor.renderIcon(button.icon, buttonPosition.getX() + 1, buttonPosition.getY() + 1); } - GlStateManager.translate(0, 0, -zOffset); - } + }); + GlStateManager.popMatrix(); } /** @@ -620,104 +590,50 @@ public class RenderListener { } } - boolean hoveringButton = false; + final boolean[] hoveringButton = {false}; if (!doInventoryButtons) return; if (NotEnoughUpdates.INSTANCE.hasSkyblockScoreboard() && NotificationHandler.shouldRenderOverlay(event.gui) && event.gui instanceof GuiContainer) { - int xSize = ((AccessorGuiContainer) event.gui).getXSize(); - int ySize = ((AccessorGuiContainer) event.gui).getYSize(); - int guiLeft = ((AccessorGuiContainer) event.gui).getGuiLeft(); - int guiTop = ((AccessorGuiContainer) event.gui).getGuiTop(); - - if (!NEUApi.disableInventoryButtons) { - if (!EnchantingSolvers.disableButtons()) { - for (NEUConfig.InventoryButton button : NotEnoughUpdates.INSTANCE.config.hidden.inventoryButtons) { - if (!button.isActive()) continue; - if (button.playerInvOnly && !(event.gui instanceof GuiInventory)) continue; - - int x = guiLeft + button.x; - int y = guiTop + button.y; - if (button.anchorRight) { - x += xSize; - } - if (button.anchorBottom) { - y += ySize; - } - if (AccessoryBagOverlay.isInAccessoryBag()) { - if (x > guiLeft + xSize && x < guiLeft + xSize + 80 + 28 + 5 && y > guiTop - 18 && y < guiTop + 150) { - x += 80 + 28; - } - } - if (TrophyRewardOverlay.inTrophyFishingInventory()) { - int diffX = 162; - if (x > guiLeft + xSize && x < guiLeft + xSize + diffX + 5 && y > guiTop - 18 && y < guiTop + 120) { - x += diffX; - } - } - if (MinionHelperManager.getInstance().inCraftedMinionsInventory()) { - int diffX = 172; - if (x > guiLeft + xSize && x < guiLeft + xSize + diffX + 5 && y > guiTop - 18 && y < guiTop + 128) { - x += diffX; - } - } - if (AuctionProfit.inAuctionPage()) { - if (x + 18 > guiLeft + xSize && x + 18 < guiLeft + xSize + 4 + 28 + 20 && y > guiTop - 180 && - y < guiTop + 56) { - x -= 68 - 200; - } - } - if (EquipmentOverlay.isRenderingArmorHud()) { - if (x < guiLeft + xSize - 150 && x > guiLeft + xSize - 200 && y > guiTop && y < guiTop + 84) { - x -= 25; - } - } - if (EquipmentOverlay.isRenderingPetHud()) { - if (x < guiLeft + xSize - 150 && x > guiLeft + xSize - 200 && y > guiTop + 60 && y < guiTop + 120) { - x -= 25; - } - } + AccessorGuiContainer acc = (AccessorGuiContainer) event.gui; + Rectangle mousePosition = new Rectangle(event.mouseX, event.mouseY, 0, 0); + Rectangle craftingTextRectangle = new Rectangle(acc.getGuiLeft() + 85, acc.getGuiTop() + 4, 30, 21); + iterateButtons((GuiContainer) guiScreen, (button, buttonPosition) -> { - if (inDungeonPage || DungeonNpcProfitOverlay.isRendering()) { - if (x + 10 > guiLeft + xSize && x + 18 < guiLeft + xSize + 4 + 28 + 20 && y > guiTop - 180 && - y < guiTop + 100) { - x += 185; - } - } + if (buttonPosition.intersects(craftingTextRectangle)) { + disableCraftingText = true; + } - if (x - guiLeft >= 85 && x - guiLeft <= 115 && y - guiTop >= 4 && y - guiTop <= 25) { - disableCraftingText = true; - } + if (!buttonPosition.intersects(mousePosition)) { + return; + } + hoveringButton[0] = true; + long currentTime = System.currentTimeMillis(); - if (event.mouseX >= x && event.mouseX <= x + 18 && event.mouseY >= y && event.mouseY <= y + 18) { - hoveringButton = true; - long currentTime = System.currentTimeMillis(); + if (buttonHovered != button) { + buttonHoveredMillis = currentTime; + buttonHovered = button; + } - if (buttonHovered != button) { - buttonHoveredMillis = currentTime; - buttonHovered = button; - } + if (currentTime - buttonHoveredMillis <= NotEnoughUpdates.INSTANCE.config.inventoryButtons.tooltipDelay) { + return; + } + String command = button.command.trim(); + if (!command.startsWith("/")) { + command = "/" + command; + } - if (currentTime - buttonHoveredMillis > NotEnoughUpdates.INSTANCE.config.inventoryButtons.tooltipDelay) { - String command = button.command.trim(); - if (!command.startsWith("/")) { - command = "/" + command; - } + Utils.drawHoveringText( + Lists.newArrayList("\u00a77" + command), + event.mouseX, + event.mouseY, + event.gui.width, + event.gui.height, + -1 + ); - Utils.drawHoveringText( - Lists.newArrayList("\u00a77" + command), - event.mouseX, - event.mouseY, - event.gui.width, - event.gui.height, - -1 - ); - } - } - } - } - } + }); } - if (!hoveringButton) buttonHovered = null; + if (!hoveringButton[0]) buttonHovered = null; if (AuctionBINWarning.getInstance().shouldShow()) { AuctionBINWarning.getInstance().render(); @@ -1121,85 +1037,28 @@ public class RenderListener { if (!doInventoryButtons) return; if (NotEnoughUpdates.INSTANCE.hasSkyblockScoreboard() && NotificationHandler.shouldRenderOverlay(event.gui) && Mouse.getEventButton() >= 0 && event.gui instanceof GuiContainer) { - int xSize = ((AccessorGuiContainer) event.gui).getXSize(); - int ySize = ((AccessorGuiContainer) event.gui).getYSize(); - int guiLeft = ((AccessorGuiContainer) event.gui).getGuiLeft(); - int guiTop = ((AccessorGuiContainer) event.gui).getGuiTop(); - if (!NEUApi.disableInventoryButtons) { - if (!EnchantingSolvers.disableButtons()) { - for (NEUConfig.InventoryButton button : NotEnoughUpdates.INSTANCE.config.hidden.inventoryButtons) { - if (!button.isActive()) continue; - if (button.playerInvOnly && !(event.gui instanceof GuiInventory)) continue; - - int x = guiLeft + button.x; - int y = guiTop + button.y; - if (button.anchorRight) { - x += xSize; - } - if (button.anchorBottom) { - y += ySize; - } - if (AccessoryBagOverlay.isInAccessoryBag()) { - if (x > guiLeft + xSize && x < guiLeft + xSize + 80 + 28 + 5 && y > guiTop - 18 && y < guiTop + 150) { - x += 80 + 28; - } - } - if (TrophyRewardOverlay.inTrophyFishingInventory()) { - int diffX = 162; - if (x > guiLeft + xSize && x < guiLeft + xSize + diffX + 5 && y > guiTop - 18 && y < guiTop + 120) { - x += diffX; - } - } - if (MinionHelperManager.getInstance().inCraftedMinionsInventory()) { - int diffX = 172; - if (x > guiLeft + xSize && x < guiLeft + xSize + diffX + 5 && y > guiTop - 18 && y < guiTop + 128) { - x += diffX; - } - } - if (AuctionProfit.inAuctionPage()) { - if (x + 18 > guiLeft + xSize && x + 18 < guiLeft + xSize + 4 + 28 + 20 && y > guiTop - 180 && - y < guiTop + 56) { - x -= 68 - 200; - } - } - if (EquipmentOverlay.isRenderingArmorHud()) { - if (x < guiLeft + xSize - 150 && x > guiLeft + xSize - 200 && y > guiTop && y < guiTop + 84) { - x -= 25; - } + Rectangle mouseRect = new Rectangle(mouseX, mouseY, 0, 0); + iterateButtons((GuiContainer) event.gui, (button, buttonPositon) -> { + if (!buttonPositon.intersects(mouseRect)) { + return; + } + if (Minecraft.getMinecraft().thePlayer.inventory.getItemStack() == null) { + int clickType = NotEnoughUpdates.INSTANCE.config.inventoryButtons.clickType; + if ((clickType == 0 && Mouse.getEventButtonState()) || + (clickType == 1 && !Mouse.getEventButtonState())) { + String command = button.command.trim(); + if (!command.startsWith("/")) { + command = "/" + command; } - if (EquipmentOverlay.isRenderingPetHud()) { - if (x < guiLeft + xSize - 150 && x > guiLeft + xSize - 200 && y > guiTop + 60 && y < guiTop + 120) { - x -= 25; - } - } - if (inDungeonPage || DungeonNpcProfitOverlay.isRendering()) { - if (x + 10 > guiLeft + xSize && x + 18 < guiLeft + xSize + 4 + 28 + 20 && y > guiTop - 180 && - y < guiTop + 100) { - x += 185; - } - } - - if (mouseX >= x && mouseX <= x + 18 && mouseY >= y && mouseY <= y + 18) { - if (Minecraft.getMinecraft().thePlayer.inventory.getItemStack() == null) { - int clickType = NotEnoughUpdates.INSTANCE.config.inventoryButtons.clickType; - if ((clickType == 0 && Mouse.getEventButtonState()) || - (clickType == 1 && !Mouse.getEventButtonState())) { - String command = button.command.trim(); - if (!command.startsWith("/")) { - command = "/" + command; - } - if (ClientCommandHandler.instance.executeCommand(Minecraft.getMinecraft().thePlayer, command) == 0) { - NotEnoughUpdates.INSTANCE.sendChatMessage(command); - } - } - } else { - event.setCanceled(true); - } - return; + if (ClientCommandHandler.instance.executeCommand(Minecraft.getMinecraft().thePlayer, command) == 0) { + NotEnoughUpdates.INSTANCE.sendChatMessage(command); } } + } else { + event.setCanceled(true); } - } + + }); } } diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/AbiphoneFavourites.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/AbiphoneFavourites.java index 3366fd1e..750e674d 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/AbiphoneFavourites.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/AbiphoneFavourites.java @@ -24,6 +24,7 @@ import io.github.moulberry.notenoughupdates.autosubscribe.NEUAutoSubscribe; import io.github.moulberry.notenoughupdates.core.config.KeybindHelper; import io.github.moulberry.notenoughupdates.core.util.StringUtils; import io.github.moulberry.notenoughupdates.core.util.render.RenderUtils; +import io.github.moulberry.notenoughupdates.events.GuiContainerBackgroundDrawnEvent; import io.github.moulberry.notenoughupdates.events.ReplaceItemEvent; import io.github.moulberry.notenoughupdates.events.SlotClickEvent; import io.github.moulberry.notenoughupdates.options.NEUConfig; @@ -202,10 +203,11 @@ public class AbiphoneFavourites { return isAbiphoneShowOnlyFavourites() && !getFavouriteContacts().contains(name); } - public void onDrawBackground(GuiScreen screen) { + @SubscribeEvent + public void onDrawBackground(GuiContainerBackgroundDrawnEvent event) { if (isWrongInventory()) return; - GuiContainer container = (GuiContainer) screen; + GuiContainer container = event.getContainer(); for (Slot slot : container.inventorySlots.inventorySlots) { if (slot == null) continue; diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/AuctionProfit.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/AuctionProfit.java index c0e40ec9..c761f847 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/AuctionProfit.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/AuctionProfit.java @@ -22,7 +22,9 @@ package io.github.moulberry.notenoughupdates.miscfeatures; import io.github.moulberry.notenoughupdates.NotEnoughUpdates; import io.github.moulberry.notenoughupdates.autosubscribe.NEUAutoSubscribe; import io.github.moulberry.notenoughupdates.core.util.StringUtils; +import io.github.moulberry.notenoughupdates.events.ButtonExclusionZoneEvent; import io.github.moulberry.notenoughupdates.mixins.AccessorGuiContainer; +import io.github.moulberry.notenoughupdates.util.Rectangle; import io.github.moulberry.notenoughupdates.util.Utils; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.FontRenderer; @@ -47,6 +49,20 @@ public class AuctionProfit { new ResourceLocation("notenoughupdates:auction_profit.png"); @SubscribeEvent + public void onButtonExclusionZones(ButtonExclusionZoneEvent event) { + if (inAuctionPage()) { + event.blockArea( + new Rectangle( + event.getGuiBaseRect().getRight(), + event.getGuiBaseRect().getTop(), + 128 /*width*/ + 4 /*space*/, 56 + ), + ButtonExclusionZoneEvent.PushDirection.TOWARDS_RIGHT + ); + } + } + + @SubscribeEvent public void onDrawBackground(GuiScreenEvent.BackgroundDrawnEvent event) { if (!inAuctionPage()) return; diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/DungeonNpcProfitOverlay.java b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/DungeonNpcProfitOverlay.java index c4b03b84..c06563c2 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/DungeonNpcProfitOverlay.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/miscfeatures/DungeonNpcProfitOverlay.java @@ -24,8 +24,10 @@ import com.google.gson.JsonObject; import io.github.moulberry.notenoughupdates.NotEnoughUpdates; import io.github.moulberry.notenoughupdates.autosubscribe.NEUAutoSubscribe; import io.github.moulberry.notenoughupdates.core.util.StringUtils; +import io.github.moulberry.notenoughupdates.events.ButtonExclusionZoneEvent; import io.github.moulberry.notenoughupdates.mixins.AccessorGuiContainer; import io.github.moulberry.notenoughupdates.util.ItemUtils; +import io.github.moulberry.notenoughupdates.util.Rectangle; import io.github.moulberry.notenoughupdates.util.SBInfo; import io.github.moulberry.notenoughupdates.util.Utils; import net.minecraft.client.Minecraft; @@ -100,6 +102,19 @@ public class DungeonNpcProfitOverlay { } @SubscribeEvent + public void onButtonExclusionZones(ButtonExclusionZoneEvent event) { + if (isRendering()) + event.blockArea( + new Rectangle( + event.getGuiBaseRect().getRight(), + event.getGuiBaseRect().getTop(), + 180 /*width*/ + 4 /*space*/, 101 + ), + ButtonExclusionZoneEvent.PushDirection.TOWARDS_RIGHT + ); + } + + @SubscribeEvent public void onDrawBackground(GuiScreenEvent.BackgroundDrawnEvent event) { if (!NotEnoughUpdates.INSTANCE.config.dungeons.croesusProfitOverlay || !(event.gui instanceof GuiChest)) { chestProfits = null; diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscgui/AccessoryBagOverlay.java b/src/main/java/io/github/moulberry/notenoughupdates/miscgui/AccessoryBagOverlay.java index 562eb1e0..908ae307 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/miscgui/AccessoryBagOverlay.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/miscgui/AccessoryBagOverlay.java @@ -25,9 +25,11 @@ import com.google.gson.JsonObject; import io.github.moulberry.notenoughupdates.NotEnoughUpdates; import io.github.moulberry.notenoughupdates.auction.APIManager; import io.github.moulberry.notenoughupdates.core.util.StringUtils; +import io.github.moulberry.notenoughupdates.events.ButtonExclusionZoneEvent; import io.github.moulberry.notenoughupdates.listener.RenderListener; import io.github.moulberry.notenoughupdates.profileviewer.PlayerStats; import io.github.moulberry.notenoughupdates.util.Constants; +import io.github.moulberry.notenoughupdates.util.Rectangle; import io.github.moulberry.notenoughupdates.util.Utils; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Gui; @@ -45,6 +47,7 @@ import net.minecraft.nbt.CompressedStreamTools; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import net.minecraft.util.EnumChatFormatting; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import org.lwjgl.input.Mouse; import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL14; @@ -73,6 +76,22 @@ public class AccessoryBagOverlay { private static final int TAB_MISSING = 4; private static final int TAB_OPTIMIZER = 5; + public static final AccessoryBagOverlay INSTANCE = new AccessoryBagOverlay(); + + @SubscribeEvent + public void onButtonExclusionZones(ButtonExclusionZoneEvent event) { + if (isInAccessoryBag()) { + event.blockArea( + new Rectangle( + event.getGuiBaseRect().getRight(), + event.getGuiBaseRect().getTop(), + 80 /*pane*/ + 24 /*tabs*/ + 4 /*space*/, 150 + ), + ButtonExclusionZoneEvent.PushDirection.TOWARDS_RIGHT + ); + } + } + private static final ItemStack[] TAB_STACKS = new ItemStack[]{ Utils.createItemStack(Items.dye, EnumChatFormatting.DARK_AQUA + "Basic Information", 10, EnumChatFormatting.GREEN + "- Talis count by rarity" @@ -475,15 +494,9 @@ public class AccessoryBagOverlay { int yIndex = 0; long currentTime = System.currentTimeMillis(); - int marqueeOffset = (int) (currentTime / 500 % 100); for (ItemStack missingStack : missing) { String s = missingStack.getDisplayName(); - //int marueeOffset - //if(s.length()) { - - //} - s = Minecraft.getMinecraft().fontRendererObj.trimStringToWidth(s, 70); String clean = StringUtils.cleanColourNotModifiers(s); @@ -844,51 +857,8 @@ public class AccessoryBagOverlay { } } - /*private static void renderAlignedString(String first, String second, float x, float y, int length) { - FontRenderer fontRendererObj = Minecraft.getMinecraft().fontRendererObj; - - if(fontRendererObj.getStringWidth(first + " " + second) >= length) { - for(int xOff=-2; xOff<=2; xOff++) { - for(int yOff=-2; yOff<=2; yOff++) { - if(Math.abs(xOff) != Math.abs(yOff)) { - Utils.drawStringCenteredScaledMaxWidth(Utils.cleanColourNotModifiers(first + " " + second), Minecraft.getMinecraft().fontRendererObj, - x+length/2f+xOff/2f, y+4+yOff/2f, false, length, - new Color(0, 0, 0, 200/Math.max(Math.abs(xOff), Math.abs(yOff))).getRGB()); - } - } - } - - GlStateManager.color(1, 1, 1, 1); - Utils.drawStringCenteredScaledMaxWidth(first + " " + second, Minecraft.getMinecraft().fontRendererObj, - x+length/2f, y+4, false, length, 4210752); - } else { - for(int xOff=-2; xOff<=2; xOff++) { - for(int yOff=-2; yOff<=2; yOff++) { - if(Math.abs(xOff) != Math.abs(yOff)) { - fontRendererObj.drawString(Utils.cleanColourNotModifiers(first), - x+xOff/2f, y+yOff/2f, - new Color(0, 0, 0, 200/Math.max(Math.abs(xOff), Math.abs(yOff))).getRGB(), false); - } - } - } - - int secondLen = fontRendererObj.getStringWidth(second); - GlStateManager.color(1, 1, 1, 1); - fontRendererObj.drawString(first, x, y, 4210752, false); - for(int xOff=-2; xOff<=2; xOff++) { - for(int yOff=-2; yOff<=2; yOff++) { - if(Math.abs(xOff) != Math.abs(yOff)) { - fontRendererObj.drawString(Utils.cleanColourNotModifiers(second), - x+length-secondLen+xOff/2f, y+yOff/2f, - new Color(0, 0, 0, 200/Math.max(Math.abs(xOff), Math.abs(yOff))).getRGB(), false); - } - } - } - - GlStateManager.color(1, 1, 1, 1); - fontRendererObj.drawString(second, x+length-secondLen, y, 4210752, false); - } - }*/ + + private static final HashMap<String, Pattern> STAT_PATTERN_MAP_BONUS = new HashMap<String, Pattern>() {{ String STAT_PATTERN_BONUS_END = ": (?:\\+|-)[0-9]+(?:\\.[0-9]+)?\\%? \\(((?:\\+|-)[0-9]+)%?"; diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscgui/TrophyRewardOverlay.java b/src/main/java/io/github/moulberry/notenoughupdates/miscgui/TrophyRewardOverlay.java index e13934e1..e8653d53 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/miscgui/TrophyRewardOverlay.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/miscgui/TrophyRewardOverlay.java @@ -23,9 +23,11 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import io.github.moulberry.notenoughupdates.NotEnoughUpdates; import io.github.moulberry.notenoughupdates.autosubscribe.NEUAutoSubscribe; +import io.github.moulberry.notenoughupdates.events.ButtonExclusionZoneEvent; import io.github.moulberry.notenoughupdates.events.RepositoryReloadEvent; import io.github.moulberry.notenoughupdates.mixins.AccessorGuiContainer; import io.github.moulberry.notenoughupdates.util.Constants; +import io.github.moulberry.notenoughupdates.util.Rectangle; import io.github.moulberry.notenoughupdates.util.Utils; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.FontRenderer; @@ -97,6 +99,21 @@ public class TrophyRewardOverlay { return line.get(1); } + @SubscribeEvent + public void onButtonExclusionZones(ButtonExclusionZoneEvent event) { + if (inTrophyFishingInventory()) { + event.blockArea( + new Rectangle( + event.getGuiBaseRect().getRight(), + event.getGuiBaseRect().getTop(), + 168 /*width*/ + 4 /*space*/, + 128 + ), + ButtonExclusionZoneEvent.PushDirection.TOWARDS_RIGHT + ); + } + } + @SubscribeEvent(priority = EventPriority.LOWEST) public void onDrawBackground(GuiScreenEvent.BackgroundDrawnEvent event) { if (!inTrophyFishingInventory()) return; diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscgui/minionhelper/render/MinionHelperOverlay.java b/src/main/java/io/github/moulberry/notenoughupdates/miscgui/minionhelper/render/MinionHelperOverlay.java index 1a625794..93a39ec0 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/miscgui/minionhelper/render/MinionHelperOverlay.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/miscgui/minionhelper/render/MinionHelperOverlay.java @@ -24,6 +24,7 @@ import com.google.common.collect.Lists; import io.github.moulberry.notenoughupdates.NotEnoughUpdates; import io.github.moulberry.notenoughupdates.core.util.ArrowPagesUtils; import io.github.moulberry.notenoughupdates.core.util.StringUtils; +import io.github.moulberry.notenoughupdates.events.ButtonExclusionZoneEvent; import io.github.moulberry.notenoughupdates.miscgui.TrophyRewardOverlay; import io.github.moulberry.notenoughupdates.miscgui.minionhelper.Minion; import io.github.moulberry.notenoughupdates.miscgui.minionhelper.MinionHelperManager; @@ -34,6 +35,7 @@ import io.github.moulberry.notenoughupdates.miscgui.minionhelper.sources.NpcSour import io.github.moulberry.notenoughupdates.mixins.AccessorGuiContainer; import io.github.moulberry.notenoughupdates.util.ItemUtils; import io.github.moulberry.notenoughupdates.util.NotificationHandler; +import io.github.moulberry.notenoughupdates.util.Rectangle; import io.github.moulberry.notenoughupdates.util.Utils; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.FontRenderer; @@ -94,6 +96,20 @@ public class MinionHelperOverlay { } @SubscribeEvent + public void onButtonExclusionZones(ButtonExclusionZoneEvent event) { + if (manager.inCraftedMinionsInventory() && NotEnoughUpdates.INSTANCE.config.minionHelper.gui) { + event.blockArea( + new Rectangle( + event.getGuiBaseRect().getRight(), + event.getGuiBaseRect().getTop(), + 168 /*width*/ + 4 /*space*/, 128 + ), + ButtonExclusionZoneEvent.PushDirection.TOWARDS_RIGHT + ); + } + } + + @SubscribeEvent public void onDrawBackground(GuiScreenEvent.BackgroundDrawnEvent event) { if (!manager.inCraftedMinionsInventory()) return; if (!NotEnoughUpdates.INSTANCE.config.minionHelper.gui) return; diff --git a/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinRenderItem.java b/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinRenderItem.java index 2173a3c8..633a82a2 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinRenderItem.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinRenderItem.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 NotEnoughUpdates contributors + * Copyright (C) 2022-2023 NotEnoughUpdates contributors * * This file is part of NotEnoughUpdates. * @@ -148,51 +148,6 @@ public abstract class MixinRenderItem { @Shadow abstract void renderModel(IBakedModel model, int color); - /*@Redirect(method="renderEffect", - at=@At( - value = "INVOKE", - target = "Lnet/minecraft/client/renderer/entity/RenderItem;renderModel(Lnet/minecraft/client/resources/model/IBakedModel;I)V" - ) - ) - public void renderEffect_renderModel(RenderItem renderItem, IBakedModel model, int colour) { - if(customEnchGlint != null) { - renderModel(model, ChromaColour.specialToChromaRGB(customEnchGlint)); - } else { - renderModel(model, colour); - } - } - - @Redirect(method="renderEffect", - at=@At( - value = "INVOKE", - target = "Lnet/minecraft/client/renderer/texture/TextureManager;bindTexture(Lnet/minecraft/util/ResourceLocation;)V" - ) - ) - public void renderEffect_bindTexture(TextureManager textureManager, ResourceLocation location) { - if(customEnchGlint != null) { - textureManager.bindTexture(GlintManager.getCustomGlintTexture()); - GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); - GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); - } else { - textureManager.bindTexture(location); - } - } - - @Redirect(method="renderEffect", - at=@At( - value = "INVOKE", - target = "Lnet/minecraft/client/renderer/GlStateManager;blendFunc(II)V" - ) - ) - public void renderEffect_blendFunc(int src, int dst) { - if(dst != 1) { - GlStateManager.blendFunc(src, dst); - } else if(customEnchGlint != null) { - GlintManager.setCustomBlendFunc(customEnchGlint); - } else { - GlStateManager.blendFunc(GL11.GL_SRC_COLOR, 1); - } - }*/ @Inject(method = "renderItemIntoGUI", at = @At("HEAD")) public void renderItemHead(ItemStack stack, int x, int y, CallbackInfo ci) { diff --git a/src/main/java/io/github/moulberry/notenoughupdates/options/NEUConfig.java b/src/main/java/io/github/moulberry/notenoughupdates/options/NEUConfig.java index 498b3b0d..53906cd8 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/options/NEUConfig.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/options/NEUConfig.java @@ -250,8 +250,8 @@ public class NEUConfig extends Config { @Expose @Category( - name = "Todo Overlay", - desc = "Todo Overlay" + name = "Todo Overlays", + desc = "Todo Overlays" ) public MiscOverlays miscOverlays = new MiscOverlays(); diff --git a/src/main/java/io/github/moulberry/notenoughupdates/options/seperateSections/Misc.java b/src/main/java/io/github/moulberry/notenoughupdates/options/seperateSections/Misc.java index affaa68a..02630df9 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/options/seperateSections/Misc.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/options/seperateSections/Misc.java @@ -319,5 +319,4 @@ public class Misc { ) @ConfigEditorBoolean public boolean oldSkyBlockMenu = false; - } diff --git a/src/main/java/io/github/moulberry/notenoughupdates/options/seperateSections/Museum.java b/src/main/java/io/github/moulberry/notenoughupdates/options/seperateSections/Museum.java index c476fc3b..6f03c2bb 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/options/seperateSections/Museum.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/options/seperateSections/Museum.java @@ -22,6 +22,7 @@ package io.github.moulberry.notenoughupdates.options.seperateSections; import com.google.gson.annotations.Expose; import io.github.moulberry.notenoughupdates.core.config.annotations.ConfigEditorBoolean; import io.github.moulberry.notenoughupdates.core.config.annotations.ConfigEditorColour; +import io.github.moulberry.notenoughupdates.core.config.annotations.ConfigEditorDropdown; import io.github.moulberry.notenoughupdates.core.config.annotations.ConfigOption; public class Museum { @@ -42,4 +43,22 @@ public class Museum { @ConfigEditorColour public String museumItemColor = "0:255:0:255:0"; + @Expose + @ConfigOption( + name = "Show Items to donate", + desc = "Show the cheapest items you have not yet donated to the Museum" + ) + @ConfigEditorBoolean + public boolean museumCheapestItemOverlay = true; + + @Expose + @ConfigOption( + name = "Value calculation", + desc = "Choose the source for the value calculation" + ) + @ConfigEditorDropdown( + values = {"Lowest BIN", "Craft cost"} + ) + public int museumCheapestItemOverlayValueSource = 0; + } diff --git a/src/main/java/io/github/moulberry/notenoughupdates/overlays/EquipmentOverlay.java b/src/main/java/io/github/moulberry/notenoughupdates/overlays/EquipmentOverlay.java index a812ff52..4d14e47f 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/overlays/EquipmentOverlay.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/overlays/EquipmentOverlay.java @@ -25,12 +25,14 @@ import com.google.gson.JsonPrimitive; import io.github.moulberry.notenoughupdates.NEUManager; import io.github.moulberry.notenoughupdates.NotEnoughUpdates; import io.github.moulberry.notenoughupdates.autosubscribe.NEUAutoSubscribe; +import io.github.moulberry.notenoughupdates.events.ButtonExclusionZoneEvent; import io.github.moulberry.notenoughupdates.events.GuiInventoryBackgroundDrawnEvent; import io.github.moulberry.notenoughupdates.miscfeatures.PetInfoOverlay; import io.github.moulberry.notenoughupdates.miscgui.GuiInvButtonEditor; import io.github.moulberry.notenoughupdates.mixins.AccessorGuiContainer; import io.github.moulberry.notenoughupdates.options.NEUConfig; import io.github.moulberry.notenoughupdates.util.ItemUtils; +import io.github.moulberry.notenoughupdates.util.Rectangle; import io.github.moulberry.notenoughupdates.util.SBInfo; import io.github.moulberry.notenoughupdates.util.Utils; import net.minecraft.client.Minecraft; @@ -149,6 +151,29 @@ public class EquipmentOverlay { public ItemStack petStack; //<editor-fold desc="events"> + @SubscribeEvent + public void onButtonExclusionZones(ButtonExclusionZoneEvent event) { + if (isRenderingArmorHud()) { + event.blockArea( + new Rectangle( + event.getGuiBaseRect().getRight() - 200, + event.getGuiBaseRect().getTop(), + 50, 84 + ), + ButtonExclusionZoneEvent.PushDirection.TOWARDS_LEFT + ); + } + if (isRenderingPetHud()) { + event.blockArea( + new Rectangle( + event.getGuiBaseRect().getRight() - 200, + event.getGuiBaseRect().getTop() + 60, + 50, 60 + ), + ButtonExclusionZoneEvent.PushDirection.TOWARDS_LEFT + ); + } + } @SubscribeEvent public void onGuiTick(TickEvent.ClientTickEvent event) { diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/events/ButtonExclusionZoneEvent.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/events/ButtonExclusionZoneEvent.kt new file mode 100644 index 00000000..3c8ce418 --- /dev/null +++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/events/ButtonExclusionZoneEvent.kt @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2023 Linnea Gräf + * + * This file is part of NotEnoughUpdates. + * + * NotEnoughUpdates is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * NotEnoughUpdates is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>. + */ + +package io.github.moulberry.notenoughupdates.events + +import io.github.moulberry.notenoughupdates.events.ButtonExclusionZoneEvent.PushDirection.* +import io.github.moulberry.notenoughupdates.util.Rectangle +import net.minecraft.client.gui.GuiScreen +import java.util.* + +class ButtonExclusionZoneEvent( + val gui: GuiScreen, + val guiBaseRect: Rectangle, +) : NEUEvent() { + enum class PushDirection { + TOWARDS_RIGHT, + TOWARDS_LEFT, + TOWARDS_TOP, + TOWARDS_BOTTOM, + } + + data class ExclusionZone( + val area: Rectangle, + val pushDirection: PushDirection, + ) + + val occupiedRects = mutableListOf<ExclusionZone>() + fun blockArea(area: Rectangle, direction: PushDirection) { + occupiedRects.add(ExclusionZone(area, direction)) + } + + @JvmOverloads + fun findButtonPosition(button: Rectangle, margin: Int = 0): Rectangle { + val processedAreas = IdentityHashMap<ExclusionZone, Unit>() + + var buttonPosition = button + while (true) { + val overlappingExclusionZone = + occupiedRects.find { it !in processedAreas && it.area.intersects(buttonPosition) } ?: break + buttonPosition = when (overlappingExclusionZone.pushDirection) { + TOWARDS_RIGHT -> buttonPosition.copy(x = overlappingExclusionZone.area.right + margin) + TOWARDS_LEFT -> buttonPosition.copy(x = overlappingExclusionZone.area.left - buttonPosition.width - margin) + TOWARDS_TOP -> buttonPosition.copy(y = overlappingExclusionZone.area.top - buttonPosition.height - margin) + TOWARDS_BOTTOM -> buttonPosition.copy(y = overlappingExclusionZone.area.bottom + margin) + } + processedAreas[overlappingExclusionZone] = Unit + } + + return buttonPosition + } + + +} diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/miscfeatures/inventory/MuseumCheapestItemOverlay.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/miscfeatures/inventory/MuseumCheapestItemOverlay.kt new file mode 100644 index 00000000..8a711230 --- /dev/null +++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/miscfeatures/inventory/MuseumCheapestItemOverlay.kt @@ -0,0 +1,574 @@ +/* + * Copyright (C) 2023 NotEnoughUpdates contributors + * + * This file is part of NotEnoughUpdates. + * + * NotEnoughUpdates is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * NotEnoughUpdates is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>. + */ + +package io.github.moulberry.notenoughupdates.miscfeatures.inventory + +import io.github.moulberry.notenoughupdates.NotEnoughUpdates +import io.github.moulberry.notenoughupdates.core.util.ArrowPagesUtils +import io.github.moulberry.notenoughupdates.core.util.render.TextRenderUtils +import io.github.moulberry.notenoughupdates.events.ButtonExclusionZoneEvent +import io.github.moulberry.notenoughupdates.mixins.AccessorGuiContainer +import io.github.moulberry.notenoughupdates.options.seperateSections.Museum +import io.github.moulberry.notenoughupdates.util.* +import io.github.moulberry.notenoughupdates.util.MuseumUtil.DonationState.MISSING +import net.minecraft.client.Minecraft +import net.minecraft.client.gui.GuiScreen +import net.minecraft.client.gui.ScaledResolution +import net.minecraft.client.gui.inventory.GuiChest +import net.minecraft.client.renderer.GlStateManager +import net.minecraft.client.renderer.RenderHelper +import net.minecraft.init.Blocks +import net.minecraft.init.Items +import net.minecraft.inventory.Slot +import net.minecraft.item.ItemStack +import net.minecraft.util.EnumChatFormatting +import net.minecraft.util.ResourceLocation +import net.minecraftforge.client.event.GuiScreenEvent +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import org.lwjgl.input.Mouse +import org.lwjgl.opengl.GL11 +import kotlin.math.ceil + + +object MuseumCheapestItemOverlay { + + enum class Category { + WEAPONS, + ARMOUR_SETS, + RARITIES, + NOT_APPLICABLE; // Either not a valid category or inside the "Special Items" category, which is not useful; + + /** + * Convert to readable String to be displayed to the user + */ + override fun toString(): String { + return when (this) { + WEAPONS -> "Weapons" + ARMOUR_SETS -> "Armour Sets" + RARITIES -> "Rarities" + NOT_APPLICABLE -> "Everything" + } + } + } + + data class MuseumItem( + var name: String, + var internalNames: List<String>, + var value: Double, + var priceRefreshedAt: Long, + var category: Category + ) + + private const val ITEMS_PER_PAGE = 10 + + private val backgroundResource: ResourceLocation = ResourceLocation("notenoughupdates:minion_overlay.png") + + val config: Museum get() = NotEnoughUpdates.INSTANCE.config.museum + + /** + * The top left position of the arrows to be drawn, used by [ArrowPagesUtils] + */ + private var topLeft = intArrayOf(237, 110) + private var currentPage: Int = 0 + private var previousSlots: List<Slot> = emptyList() + private var itemsToDonate: MutableList<MuseumItem> = emptyList<MuseumItem>().toMutableList() + private var leftButtonRect = Rectangle(0, 0, 0, 0) + private var rightButtonRect = Rectangle(0, 0, 0, 0) + private var selectedCategory = Category.NOT_APPLICABLE + private var totalPages = 0 + + /** + *category -> was the highest page visited? + */ + private var checkedPages: HashMap<Category, Boolean> = hashMapOf( + //this page only shows items when you have already donated them -> there is no useful information to gather + Category.WEAPONS to false, + Category.ARMOUR_SETS to false, + Category.RARITIES to false + ) + + /** + * Draw the overlay and parse items, if applicable + */ + @SubscribeEvent + fun onDrawBackground(event: GuiScreenEvent.BackgroundDrawnEvent) { + if (!shouldRender(event.gui)) return + val chest = event.gui as GuiChest + + val slots = chest.inventorySlots.inventorySlots + //check if there is any info to gather only when a category is currently open + if (!slots.equals(previousSlots) && Utils.getOpenChestName().startsWith("Museum ➜")) { + checkIfHighestPageWasVisited(slots) + parseItems(slots) + updateOutdatedValues() + } + previousSlots = slots + + val xSize = (event.gui as AccessorGuiContainer).xSize + val guiLeft = (event.gui as AccessorGuiContainer).guiLeft + val guiTop = (event.gui as AccessorGuiContainer).guiTop + + drawBackground(guiLeft, xSize, guiTop) + drawLines(guiLeft, guiTop) + drawButtons(guiLeft, xSize, guiTop) + } + + /** + * Pass on mouse clicks to [ArrowPagesUtils], if applicable + */ + @SubscribeEvent + fun onMouseClick(event: GuiScreenEvent.MouseInputEvent.Pre) { + if (!shouldRender(event.gui)) return + if (!Mouse.getEventButtonState()) return + val guiLeft = (event.gui as AccessorGuiContainer).guiLeft + val guiTop = (event.gui as AccessorGuiContainer).guiTop + ArrowPagesUtils.onPageSwitchMouse( + guiLeft, guiTop, topLeft, currentPage, totalPages + ) { pageChange: Int -> currentPage = pageChange } + } + + @SubscribeEvent + fun onButtonExclusionZones(event: ButtonExclusionZoneEvent) { + if (shouldRender(event.gui)) { + event.blockArea( + Rectangle( + event.guiBaseRect.right, + event.guiBaseRect.top, + 175, 130 + ), ButtonExclusionZoneEvent.PushDirection.TOWARDS_RIGHT + ) + } + } + + @SubscribeEvent + fun onMouseInput(event: GuiScreenEvent.MouseInputEvent.Pre) { + if (!shouldRender(event.gui)) return + val mouseX = Utils.getMouseX() + val mouseY = Utils.getMouseY() + if (Mouse.getEventButtonState() && leftButtonRect.contains(mouseX, mouseY)) { + config.museumCheapestItemOverlayValueSource = 1 - config.museumCheapestItemOverlayValueSource + updateAllValues() + } else if (Mouse.getEventButtonState() && rightButtonRect.contains(mouseX, mouseY)) { + advanceSelectedCategory() + } + } + + /** + * Move the selected category one index forward, or back to the start when already at the end + */ + private fun advanceSelectedCategory() { + val nextValueIndex = (selectedCategory.ordinal + 1) % 4 + selectedCategory = enumValues<Category>()[nextValueIndex] + } + + /** + * Draw the two clickable buttons on the bottom right and display a tooltip if needed + */ + private fun drawButtons(guiLeft: Int, xSize: Int, guiTop: Int) { + RenderHelper.enableGUIStandardItemLighting() + val useBIN = config.museumCheapestItemOverlayValueSource == 0 + val mouseX = Utils.getMouseX() + val mouseY = Utils.getMouseY() + val scaledResolution = ScaledResolution(Minecraft.getMinecraft()) + val width = scaledResolution.scaledWidth + val height = scaledResolution.scaledHeight + + // Left button + val leftItemStack = if (useBIN) { + ItemUtils.getCoinItemStack(100000.0) + } else { + ItemStack(Blocks.crafting_table) + } + leftButtonRect = Rectangle( + guiLeft + xSize + 131, + guiTop + 106, + 16, + 16 + ) + Minecraft.getMinecraft().renderItem.renderItemIntoGUI( + leftItemStack, + leftButtonRect.x, + leftButtonRect.y + ) + + if (leftButtonRect.contains(mouseX, mouseY)) { + val tooltip = if (useBIN) { + listOf( + "${EnumChatFormatting.GREEN}Using ${EnumChatFormatting.BLUE}lowest BIN ${EnumChatFormatting.GREEN}as price source!", + "", + "${EnumChatFormatting.YELLOW}Click to switch to craft cost!" + ) + } else { + listOf( + "${EnumChatFormatting.GREEN}Using ${EnumChatFormatting.AQUA}craft cost ${EnumChatFormatting.GREEN}as price source!", + "", + "${EnumChatFormatting.YELLOW}Click to switch to lowest BIN!" + ) + } + Utils.drawHoveringText( + tooltip, + mouseX, + mouseY, + width, + height, + -1, + Minecraft.getMinecraft().fontRendererObj + ) + } + + // Right button + val rightItemStack = when (selectedCategory) { + Category.WEAPONS -> ItemStack(Items.diamond_sword) + Category.ARMOUR_SETS -> ItemStack(Items.diamond_chestplate) + Category.RARITIES -> ItemStack(Items.emerald) + Category.NOT_APPLICABLE -> ItemStack(Items.filled_map) + } + rightButtonRect = Rectangle( + guiLeft + xSize + 150, + guiTop + 106, + 16, + 16 + ) + Minecraft.getMinecraft().renderItem.renderItemIntoGUI( + rightItemStack, + rightButtonRect.x, + rightButtonRect.y + ) + if (rightButtonRect.contains(mouseX, mouseY)) { + val tooltip = mutableListOf( + "${EnumChatFormatting.GREEN}Category Filter", + "", + ) + for (category in Category.values()) { + tooltip.add( + if (category == selectedCategory) { + "${EnumChatFormatting.BLUE}>$category" + } else { + category.toString() + } + ) + } + + tooltip.add("") + tooltip.add("${EnumChatFormatting.YELLOW}Click to advance!") + Utils.drawHoveringText( + tooltip, + mouseX, + mouseY, + width, + height, + -1, + Minecraft.getMinecraft().fontRendererObj + ) + } + RenderHelper.disableStandardItemLighting() + } + + /** + * Sort the collected items by their calculated value + */ + private fun sortByValue() { + itemsToDonate.sortBy { it.value } + } + + /** + * Update all values that have not been updated for the last minute + */ + private fun updateOutdatedValues() { + val time = System.currentTimeMillis() + itemsToDonate.filter { time - it.priceRefreshedAt >= 60000 } + .forEach { + it.value = calculateValue(it.internalNames) + it.priceRefreshedAt = time + } + } + + /** + * Update all values regardless of the time of the last update + */ + private fun updateAllValues() { + val time = System.currentTimeMillis() + itemsToDonate.forEach { + it.value = calculateValue(it.internalNames) + it.priceRefreshedAt = time + } + sortByValue() + } + + /** + * Calculate the value of an item as displayed in the museum, which may consist of multiple pieces + */ + private fun calculateValue(internalNames: List<String>): Double { + var totalValue = 0.0 + internalNames.forEach { + val itemValue: Double = + when (config.museumCheapestItemOverlayValueSource) { + 0 -> NotEnoughUpdates.INSTANCE.manager.auctionManager.getBazaarOrBin(it, false) + 1 -> NotEnoughUpdates.INSTANCE.manager.auctionManager.getCraftCost(it)?.craftCost ?: return@forEach + else -> -1.0 //unreachable + } + if (itemValue == -1.0 || itemValue == 0.0) { + totalValue = Double.MAX_VALUE + return@forEach + } else { + totalValue += itemValue + } + } + if (totalValue == 0.0) { + totalValue = Double.MAX_VALUE + } + + return totalValue + } + + /** + * Draw the lines containing the displayname and value over the background + */ + private fun drawLines(guiLeft: Int, guiTop: Int) { + val mouseX = Utils.getMouseX() + val mouseY = Utils.getMouseY() + val scaledResolution = ScaledResolution(Minecraft.getMinecraft()) + val width = scaledResolution.scaledWidth + val height = scaledResolution.scaledHeight + + val applicableItems = if (selectedCategory == Category.NOT_APPLICABLE) { + itemsToDonate + } else { + itemsToDonate.toList().filter { it.category == selectedCategory } + } + val lines = buildLines(applicableItems) + totalPages = ceil(applicableItems.size.toFloat() / ITEMS_PER_PAGE.toFloat()).toInt() + + lines.forEachIndexed { index, line -> + if (!visitedAllPages() && (index == ITEMS_PER_PAGE || index == lines.size - 1)) { + TextRenderUtils.drawStringScaledMaxWidth( + "${EnumChatFormatting.RED}Visit all pages for accurate info!", + Minecraft.getMinecraft().fontRendererObj, + (guiLeft + 185).toFloat(), + (guiTop + 95).toFloat(), + true, + 155, + 0 + ) + return@forEachIndexed + } else { + val x = (guiLeft + 187).toFloat() + val y = (guiTop + 5 + (index * 10)).toFloat() + Utils.renderAlignedString( + line.name, + if (line.value == Double.MAX_VALUE) "${EnumChatFormatting.RED}Unknown ${if (config.museumCheapestItemOverlayValueSource == 0) "BIN" else "Craft Cost"}" else "${EnumChatFormatting.AQUA}${ + Utils.shortNumberFormat( + line.value, + 0 + ) + }", + x, + y, + 156 + ) + + if (Utils.isWithinRect(mouseX, mouseY, x.toInt(), y.toInt(), 170, 10)) { + val tooltip = mutableListOf(line.name, "") + //armor set + if (line.internalNames.size > 1) { + tooltip.add("${EnumChatFormatting.AQUA}Consists of:") + line.internalNames.forEach { + val displayname = + NotEnoughUpdates.INSTANCE.manager.createItemResolutionQuery().withKnownInternalName(it) + .resolveToItemListJson() + ?.get("displayname")?.asString ?: "ERROR" + val value = calculateValue(listOf(it)) + + // Creates:" - displayname (price)" OR " - displayname (No BIN found!)" + tooltip.add( + " ${EnumChatFormatting.DARK_GRAY}-${EnumChatFormatting.RESET} $displayname${EnumChatFormatting.DARK_GRAY} (${EnumChatFormatting.GOLD}${ + if (value == Double.MAX_VALUE) { + "${EnumChatFormatting.RED}No BIN found!" + } else { + Utils.shortNumberFormat( + value, + 0 + ) + } + }${EnumChatFormatting.DARK_GRAY})" + ) + } + tooltip.add("") + } + + if (NotEnoughUpdates.INSTANCE.manager.getRecipesFor(line.internalNames[0]).isNotEmpty()) { + tooltip.add("${EnumChatFormatting.YELLOW}${EnumChatFormatting.BOLD}Click to open recipe!") + } else { + tooltip.add("${EnumChatFormatting.RED}${EnumChatFormatting.BOLD}No recipe available!") + } + + if (Mouse.getEventButtonState()) { + //TODO? this only opens the recipe for one of the armor pieces + NotEnoughUpdates.INSTANCE.manager.showRecipe(line.internalNames[0]) + } + + Utils.drawHoveringText( + tooltip, + mouseX, + mouseY, + width, + height, + -1, + Minecraft.getMinecraft().fontRendererObj + ) + } + } + } + + //no page has been visited yet + if (lines.isEmpty()) { + TextRenderUtils.drawStringScaledMaxWidth( + "${EnumChatFormatting.RED}No items matching filter!", + Minecraft.getMinecraft().fontRendererObj, + (guiLeft + 200).toFloat(), + (guiTop + 128 / 2).toFloat(), + true, + 155, + 0 + ) + } + + ArrowPagesUtils.onDraw(guiLeft, guiTop, topLeft, currentPage, totalPages) + return + } + + /** + * Create the list of [MuseumItem]s that should be displayed on the current page + */ + private fun buildLines(applicableItems: List<MuseumItem>): List<MuseumItem> { + val list = emptyList<MuseumItem>().toMutableList() + + for (i in (ITEMS_PER_PAGE * currentPage) until ((ITEMS_PER_PAGE * currentPage) + ITEMS_PER_PAGE)) { + if (i >= applicableItems.size) { + break + } + + list.add(applicableItems[i]) + } + return list + } + + /** + * Parse the not already donated items present in the currently open Museum page + */ + private fun parseItems(slots: List<Slot>) { + Thread { + val time = System.currentTimeMillis() + val category = getCategory() + if (category == Category.NOT_APPLICABLE) { + return@Thread + } + val armor = category == Category.ARMOUR_SETS + for (i in 0..53) { + val stack = slots[i].stack ?: continue + val parsedItems = MuseumUtil.findMuseumItem(stack, armor) ?: continue + when (parsedItems.state) { + MISSING -> { + val displayName = if (armor) { + // Use the provided displayname for armor sets but change the color to blue (from red) + "${EnumChatFormatting.BLUE}${stack.displayName.stripControlCodes()}" + } else { + // Find out the real displayname and use it for normal items, if possible + NotEnoughUpdates.INSTANCE.manager.createItemResolutionQuery() + .withKnownInternalName(parsedItems.skyblockItemIds.first()) + .resolveToItemListJson() + ?.get("displayname")?.asString ?: "${EnumChatFormatting.RED}ERROR" + } + + //if the list does not already contain it, insert this MuseumItem + if (itemsToDonate.none { it.internalNames == parsedItems.skyblockItemIds }) { + itemsToDonate.add( + MuseumItem( + displayName, + parsedItems.skyblockItemIds, + calculateValue(parsedItems.skyblockItemIds), + time, + category + ) + ) + } + } + + else -> itemsToDonate.retainAll { it.internalNames != parsedItems.skyblockItemIds } + } + } + sortByValue() + }.start() + } + + /** + * Check if the highest page for the current category is currently open and update [checkedPages] accordingly + */ + private fun checkIfHighestPageWasVisited(slots: List<Slot>) { + val category = getCategory() + val nextPageSlot = slots[53] + // If the "Next Page" arrow is missing, we are at the highest page + if ((nextPageSlot.stack ?: return).item != Items.arrow) { + checkedPages[category] = true + } + } + + /** + * Draw the background texture to the right side of the open Museum Page + */ + private fun drawBackground(guiLeft: Int, xSize: Int, guiTop: Int) { + Minecraft.getMinecraft().textureManager.bindTexture(backgroundResource) + GL11.glColor4f(1F, 1F, 1F, 1F) + GlStateManager.disableLighting() + Utils.drawTexturedRect( + (guiLeft + xSize + 4).toFloat(), + guiTop.toFloat(), + 168f, + 128f, + 0f, + 1f, + 0f, + 1f, + GL11.GL_NEAREST + ) + } + + /** + * Determine if the overlay should be active based on the config option and the currently open GuiChest, if applicable + */ + private fun shouldRender(gui: GuiScreen): Boolean = + config.museumCheapestItemOverlay && NotEnoughUpdates.INSTANCE.hasSkyblockScoreboard() && (gui is GuiChest && Utils.getOpenChestName() + .startsWith("Museum ➜") || Utils.getOpenChestName() == "Your Museum") + + /** + * Determine the currently open Museum Category + */ + private fun getCategory(): Category = + when (Utils.getOpenChestName().substring(9, Utils.getOpenChestName().length)) { + "Weapons" -> Category.WEAPONS + "Armor Sets" -> Category.ARMOUR_SETS + "Rarities" -> Category.RARITIES + else -> Category.NOT_APPLICABLE + } + + /** + * Determine if all useful pages have been visited + */ + private fun visitedAllPages(): Boolean = !checkedPages.containsValue(false) +} diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/util/Rectangle.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/Rectangle.kt new file mode 100644 index 00000000..d44b7721 --- /dev/null +++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/Rectangle.kt @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2023 NotEnoughUpdates contributors + * + * This file is part of NotEnoughUpdates. + * + * NotEnoughUpdates is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * NotEnoughUpdates is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>. + */ + +package io.github.moulberry.notenoughupdates.util + +/** + * An axis aligned rectangle in the following coordinate space: + * + * * The top direction is towards y=-INF + * * The bottom direction is towards y=+INF + * * The right direction is towards x=+INF + * * The left direction is towards x=-INF + */ +data class Rectangle( + val x: Int, val y: Int, + val width: Int, val height: Int, +) { + /** + * The left edge of this rectangle (Low X) + */ + val left get() = x + + /** + * The right edge of this rectangle (High X) + */ + val right get() = x + width + + /** + * The top edge of this rectangle (Low X) + */ + val top get() = y + + /** + * The bottom edge of this rectangle (High X) + */ + val bottom get() = y + height + + init { + require(width >= 0) + require(height >= 0) + } + + /** + * Check for intersections between two rectangles. Two rectangles with perfectly aligned edges do *not* count as + * intersecting. + */ + fun intersects(other: Rectangle): Boolean { + val intersectsX = !(right <= other.left || left >= other.right) + val intersectsY = !(top >= other.bottom || bottom <= other.top) + return intersectsX && intersectsY + } + + /** + * Check if this rectangle contains the given coordinate + */ + fun contains(x1: Int, y1: Int) :Boolean{ + return left <= x1 && x1 < left + width && top <= y1 && y1 < top + height + } +} diff --git a/src/test/kotlin/io/github/moulberry/notenoughupdates/util/RectangleTest.kt b/src/test/kotlin/io/github/moulberry/notenoughupdates/util/RectangleTest.kt new file mode 100644 index 00000000..6d4270dd --- /dev/null +++ b/src/test/kotlin/io/github/moulberry/notenoughupdates/util/RectangleTest.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2023 NotEnoughUpdates contributors + * + * This file is part of NotEnoughUpdates. + * + * NotEnoughUpdates is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * NotEnoughUpdates is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>. + */ + +package io.github.moulberry.notenoughupdates.util + +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +class RectangleTest { + + @Test + fun testNoNegativeSizes() { + assertThrows<IllegalArgumentException> { + Rectangle(0, 0, -1, 0) + } + assertThrows<IllegalArgumentException> { + Rectangle(0, 0, 0, -1) + } + } + + @Test + fun testOverlaps() { + val topLeft = Rectangle(0, 0, 10, 10) + assertTrue(topLeft.intersects(topLeft)) + assertTrue(topLeft.intersects(Rectangle(9, 2, 1, 1))) + assertTrue(topLeft.intersects(Rectangle(-2, -2, 4, 4))) + assertTrue(topLeft.intersects(Rectangle(4, 4, 1, 1))) + assertFalse(topLeft.intersects(Rectangle(-2,-2, 1,1))) + assertFalse(topLeft.intersects(Rectangle(-2,-2, 2,2))) + } +} |