From 8e487a87c51da89072c70f9c46e0b0e5d423d45f Mon Sep 17 00:00:00 2001
From: vicisacat <victor.branchu@gmail.com>
Date: Sat, 23 Mar 2024 21:19:33 +0100
Subject: it works ain't that GREAT

---
 .../skyblocker/mixin/ClientPlayerEntityMixin.java  |   7 +
 .../mixin/HandledScreenProviderMixin.java          |  23 ++-
 .../mixin/accessor/HandledScreenAccessor.java      |   6 +
 .../skyblock/auction/AuctionBrowserScreen.java     | 212 +++++++++++++++++----
 .../auction/AuctionHouseScreenHandler.java         |   4 +-
 .../skyblock/auction/AuctionViewScreen.java        | 201 +++++++++++++++++++
 .../skyblocker/skyblock/auction/EditBidPopup.java  |  99 ++++++++++
 .../utils/render/gui/AbstractCustomHypixelGUI.java |  56 ++++++
 .../utils/render/gui/BarebonesPopupScreen.java     |  56 ++++++
 9 files changed, 616 insertions(+), 48 deletions(-)
 create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/auction/AuctionViewScreen.java
 create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/auction/EditBidPopup.java
 create mode 100644 src/main/java/de/hysky/skyblocker/utils/render/gui/AbstractCustomHypixelGUI.java
 create mode 100644 src/main/java/de/hysky/skyblocker/utils/render/gui/BarebonesPopupScreen.java

(limited to 'src/main/java/de')

diff --git a/src/main/java/de/hysky/skyblocker/mixin/ClientPlayerEntityMixin.java b/src/main/java/de/hysky/skyblocker/mixin/ClientPlayerEntityMixin.java
index ceda9ed4..049443f7 100644
--- a/src/main/java/de/hysky/skyblocker/mixin/ClientPlayerEntityMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixin/ClientPlayerEntityMixin.java
@@ -2,6 +2,8 @@ package de.hysky.skyblocker.mixin;
 
 import com.mojang.authlib.GameProfile;
 import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.skyblock.auction.AuctionViewScreen;
+import de.hysky.skyblocker.skyblock.auction.EditBidPopup;
 import de.hysky.skyblocker.skyblock.dungeon.partyfinder.PartyFinderScreen;
 import de.hysky.skyblocker.skyblock.item.HotbarSlotLock;
 import de.hysky.skyblocker.skyblock.item.ItemProtection;
@@ -58,6 +60,11 @@ public abstract class ClientPlayerEntityMixin extends AbstractClientPlayerEntity
             return;
         }
 
+        if (client.currentScreen instanceof AuctionViewScreen auctionViewScreen) {
+            this.client.setScreen(new EditBidPopup(auctionViewScreen, sign, front, auctionViewScreen.minBid));
+            callbackInfo.cancel();
+        }
+
         // Search Overlay
         if (client.currentScreen != null) {
             if (SkyblockerConfigManager.get().general.searchOverlay.enableAuctionHouse && client.currentScreen.getTitle().getString().toLowerCase().contains("auction")) {
diff --git a/src/main/java/de/hysky/skyblocker/mixin/HandledScreenProviderMixin.java b/src/main/java/de/hysky/skyblocker/mixin/HandledScreenProviderMixin.java
index cd2baa4b..10d65669 100644
--- a/src/main/java/de/hysky/skyblocker/mixin/HandledScreenProviderMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixin/HandledScreenProviderMixin.java
@@ -2,8 +2,10 @@ package de.hysky.skyblocker.mixin;
 
 
 import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.mixin.accessor.HandledScreenAccessor;
 import de.hysky.skyblocker.skyblock.auction.AuctionBrowserScreen;
 import de.hysky.skyblocker.skyblock.auction.AuctionHouseScreenHandler;
+import de.hysky.skyblocker.skyblock.auction.AuctionViewScreen;
 import de.hysky.skyblocker.skyblock.dungeon.partyfinder.PartyFinderScreen;
 import de.hysky.skyblocker.skyblock.item.SkyblockCraftingTableScreenHandler;
 import de.hysky.skyblocker.skyblock.item.SkyblockCraftingTableScreen;
@@ -29,7 +31,8 @@ public interface HandledScreenProviderMixin<T extends ScreenHandler> {
         if (!Utils.isOnSkyblock()) return;
         T screenHandler = type.create(id, player.getInventory());
         if (!(screenHandler instanceof  GenericContainerScreenHandler containerScreenHandler)) return;
-        if (PartyFinderScreen.possibleInventoryNames.contains(name.getString().toLowerCase())) {
+        String nameLowercase = name.getString().toLowerCase();
+        if (PartyFinderScreen.possibleInventoryNames.contains(nameLowercase)) {
         if (SkyblockerConfigManager.get().general.betterPartyFinder && screenHandler instanceof GenericContainerScreenHandler containerScreenHandler && PartyFinderScreen.possibleInventoryNames.contains(name.getString().toLowerCase())) {
             if (client.currentScreen != null) {
                 String lowerCase = client.currentScreen.getTitle().getString().toLowerCase();
@@ -49,11 +52,23 @@ public interface HandledScreenProviderMixin<T extends ScreenHandler> {
             }
 
             ci.cancel();
-        } else if (name.getString().toLowerCase().contains("auctions browser")) {
-            System.out.println("another one");
+        } else if (nameLowercase.contains("auctions browser") || nameLowercase.contains("auctions: ")) {
             AuctionHouseScreenHandler auctionHouseScreenHandler = AuctionHouseScreenHandler.of(containerScreenHandler, false);
             client.player.currentScreenHandler = auctionHouseScreenHandler;
-            client.setScreen(new AuctionBrowserScreen(auctionHouseScreenHandler, client.player.getInventory()));
+            if (client.currentScreen instanceof AuctionBrowserScreen auctionBrowserScreen) {
+                auctionBrowserScreen.changeHandler(auctionHouseScreenHandler);
+            } else client.setScreen(new AuctionBrowserScreen(auctionHouseScreenHandler, client.player.getInventory()));
+            ci.cancel();
+        } else if (nameLowercase.contains("auction view")) {
+            AuctionHouseScreenHandler auctionHouseScreenHandler = AuctionHouseScreenHandler.of(containerScreenHandler, true);
+            client.player.currentScreenHandler = auctionHouseScreenHandler;
+            if (client.currentScreen instanceof AuctionViewScreen auctionViewScreen) {
+                auctionViewScreen.changeHandler(auctionHouseScreenHandler);
+            } else client.setScreen(new AuctionViewScreen(auctionHouseScreenHandler, client.player.getInventory(), name));
+            ci.cancel();
+        } else if ((nameLowercase.contains("confirm purchase") || nameLowercase.contains("confirm bid")) && client.currentScreen instanceof AuctionViewScreen auctionViewScreen) {
+            client.setScreen(auctionViewScreen.getConfirmPurchasePopup(name));
+            client.player.currentScreenHandler = containerScreenHandler;
             ci.cancel();
         } else if (SkyblockerConfigManager.get().general.fancyCraftingTable && screenHandler instanceof GenericContainerScreenHandler containerScreenHandler && name.getString().toLowerCase().contains("craft item")) {
             SkyblockCraftingTableScreenHandler skyblockCraftingTableScreenHandler = new SkyblockCraftingTableScreenHandler(containerScreenHandler, player.getInventory());
diff --git a/src/main/java/de/hysky/skyblocker/mixin/accessor/HandledScreenAccessor.java b/src/main/java/de/hysky/skyblocker/mixin/accessor/HandledScreenAccessor.java
index d82422cb..5b84072d 100644
--- a/src/main/java/de/hysky/skyblocker/mixin/accessor/HandledScreenAccessor.java
+++ b/src/main/java/de/hysky/skyblocker/mixin/accessor/HandledScreenAccessor.java
@@ -1,7 +1,9 @@
 package de.hysky.skyblocker.mixin.accessor;
 
 import net.minecraft.client.gui.screen.ingame.HandledScreen;
+import net.minecraft.screen.ScreenHandler;
 import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Mutable;
 import org.spongepowered.asm.mixin.gen.Accessor;
 
 @Mixin(HandledScreen.class)
@@ -17,4 +19,8 @@ public interface HandledScreenAccessor {
 
     @Accessor
     int getBackgroundHeight();
+
+    @Mutable
+    @Accessor("handler")
+    void setHandler(ScreenHandler handler);
 }
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/auction/AuctionBrowserScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/auction/AuctionBrowserScreen.java
index a06b9649..11a41d0c 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/auction/AuctionBrowserScreen.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/auction/AuctionBrowserScreen.java
@@ -5,21 +5,23 @@ import de.hysky.skyblocker.skyblock.auction.widgets.AuctionTypeWidget;
 import de.hysky.skyblocker.skyblock.auction.widgets.CategoryTabWidget;
 import de.hysky.skyblocker.skyblock.auction.widgets.RarityWidget;
 import de.hysky.skyblocker.skyblock.auction.widgets.SortWidget;
+import de.hysky.skyblocker.utils.render.gui.AbstractCustomHypixelGUI;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.font.TextRenderer;
 import net.minecraft.client.gui.DrawContext;
-import net.minecraft.client.gui.screen.ingame.HandledScreen;
 import net.minecraft.client.gui.tooltip.Tooltip;
 import net.minecraft.client.gui.widget.ButtonWidget;
 import net.minecraft.client.item.TooltipContext;
+import net.minecraft.client.texture.Sprite;
 import net.minecraft.client.util.math.MatrixStack;
 import net.minecraft.entity.player.PlayerInventory;
 import net.minecraft.item.ItemStack;
 import net.minecraft.item.Items;
-import net.minecraft.screen.ScreenHandler;
-import net.minecraft.screen.ScreenHandlerListener;
+import net.minecraft.screen.slot.Slot;
 import net.minecraft.screen.slot.SlotActionType;
+import net.minecraft.text.Style;
 import net.minecraft.text.Text;
+import net.minecraft.util.Colors;
 import net.minecraft.util.Identifier;
 import net.minecraft.util.math.MathHelper;
 
@@ -27,8 +29,27 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.function.Supplier;
 
-public class AuctionBrowserScreen extends HandledScreen<AuctionHouseScreenHandler> implements ScreenHandlerListener {
+public class AuctionBrowserScreen extends AbstractCustomHypixelGUI<AuctionHouseScreenHandler> {
     protected static final Identifier TEXTURE = new Identifier(SkyblockerMod.NAMESPACE, "textures/gui/auctions_gui/browser/background.png");
+    private static final Identifier SCROLLER_TEXTURE = new Identifier("container/creative_inventory/scroller");
+
+    private static final Identifier up_arrow_tex = new Identifier(SkyblockerMod.NAMESPACE, "up_arrow_even"); // Put them in their own fields to avoid object allocation on each frame
+    private static final Identifier down_arrow_tex = new Identifier(SkyblockerMod.NAMESPACE, "down_arrow_even");
+    public static final Supplier<Sprite> UP_ARROW = () -> MinecraftClient.getInstance().getGuiAtlasManager().getSprite(up_arrow_tex);
+    public static final Supplier<Sprite> DOWN_ARROW = () -> MinecraftClient.getInstance().getGuiAtlasManager().getSprite(down_arrow_tex);
+
+
+    // SLOTS
+    public static final int RESET_BUTTON_SLOT = 47;
+    public static final int SEARCH_BUTTON_SLOT = 48;
+    public static final int BACK_BUTTON_SLOT = 49;
+    public static final int SORT_BUTTON_SLOT = 50;
+    public static final int RARITY_BUTTON_SLOT = 51;
+    public static final int AUCTION_TYPE_BUTTON_SLOT = 52;
+
+    public static final int PREV_PAGE_BUTTON = 46;
+    public static final int NEXT_PAGE_BUTTON = 53;
+
 
     // WIDGETS
     private SortWidget sortWidget;
@@ -36,18 +57,13 @@ public class AuctionBrowserScreen extends HandledScreen<AuctionHouseScreenHandle
     private RarityWidget rarityWidget;
     private ButtonWidget resetFiltersButton;
     private final List<CategoryTabWidget> categoryTabWidgets = new ArrayList<>(6);
-
-    public static int RESET_SLOT_ID = 45;
-
-
-    boolean isWaitingForServer = false;
+    private String search;
 
     public AuctionBrowserScreen(AuctionHouseScreenHandler handler, PlayerInventory inventory) {
         super(handler, inventory, Text.literal("Auctions Browser"));
         this.backgroundHeight = 187;
         this.playerInventoryTitleY = 92;
         this.titleX = 999;
-        handler.addListener(this);
     }
 
     @Override
@@ -56,13 +72,13 @@ public class AuctionBrowserScreen extends HandledScreen<AuctionHouseScreenHandle
         x = (this.width - 176) / 2;
         y = (this.height - 187) / 2;
         sortWidget = new SortWidget(x + 25, y + 81, this::clickSlot);
-        sortWidget.setSlotId(50);
+        sortWidget.setSlotId(SORT_BUTTON_SLOT);
         addDrawableChild(sortWidget);
         auctionTypeWidget = new AuctionTypeWidget(x + 134, y + 77, this::clickSlot);
-        auctionTypeWidget.setSlotId(52);
+        auctionTypeWidget.setSlotId(AUCTION_TYPE_BUTTON_SLOT);
         addDrawableChild(auctionTypeWidget);
         rarityWidget = new RarityWidget(x + 73, y + 80, this::clickSlot);
-        rarityWidget.setSlotId(51);
+        rarityWidget.setSlotId(RARITY_BUTTON_SLOT);
         addDrawableChild(rarityWidget);
         resetFiltersButton = new ScaledTextButtonWidget(x + 10, y + 77, 12, 12, Text.literal("↻"), this::onResetPressed);
         addDrawableChild(resetFiltersButton);
@@ -84,17 +100,9 @@ public class AuctionBrowserScreen extends HandledScreen<AuctionHouseScreenHandle
             }
     }
 
-    protected void clickSlot(int slotID, int button) {
-        if (isWaitingForServer) return;
-        if (client == null) return;
-        assert this.client.interactionManager != null;
-        this.client.interactionManager.clickSlot(handler.syncId, slotID, button, SlotActionType.PICKUP, client.player);
-    }
-
     private void onResetPressed(ButtonWidget buttonWidget) {
         buttonWidget.setFocused(false); // Annoying.
-        if (RESET_SLOT_ID == -1) return;
-        this.clickSlot(RESET_SLOT_ID, 0);
+        this.clickSlot(RESET_BUTTON_SLOT, 0);
     }
 
     @Override
@@ -105,46 +113,168 @@ public class AuctionBrowserScreen extends HandledScreen<AuctionHouseScreenHandle
     @Override
     public void render(DrawContext context, int mouseX, int mouseY, float delta) {
         super.render(context, mouseX, mouseY, delta);
+        for (CategoryTabWidget categoryTabWidget : categoryTabWidgets) {
+            categoryTabWidget.render(context, mouseX, mouseY, delta);
+        }
+        if (isWaitingForServer) context.drawText(textRenderer, "Waiting...", 0, 0, Colors.WHITE, true);
+
+        MatrixStack matrices = context.getMatrices();
+        matrices.push();
+        matrices.translate(x,y,0);
+        // Search
+        context.enableScissor(x+7, y+4, x+97, y+16);
+        context.drawText(textRenderer, Text.literal(search).fillStyle(Style.EMPTY.withUnderline(onSearchField(mouseX, mouseY))), 9, 6, Colors.WHITE, true);
+        context.disableScissor();
+
+        // Scrollbar
+        if (prevPageVisible) {
+            if (onScrollbarTop(mouseX, mouseY))
+                context.drawSprite(159, 13, 0, 6, 3, UP_ARROW.get());
+            else context.drawSprite(159, 13, 0, 6, 3, UP_ARROW.get(), 0.54f, 0.54f, 0.54f, 1);
+        }
+
+        if (nextPageVisible) {
+            if (onScrollbarBottom(mouseX, mouseY))
+                context.drawSprite(159, 72, 0, 6, 3, DOWN_ARROW.get());
+            else context.drawSprite(159, 72, 0, 6, 3, DOWN_ARROW.get(), 0.54f, 0.54f, 0.54f, 1);
+        }
+        context.drawText(textRenderer, String.format("%d/%d", currentPage, totalPages), 99, 6, Colors.GRAY, false);
+        if (totalPages <= 1)
+            context.drawGuiTexture(SCROLLER_TEXTURE, 156, 18, 12, 15);
+        else
+            context.drawGuiTexture(SCROLLER_TEXTURE, 156, (int) (18 + (float)(Math.min(currentPage, totalPages)-1)/(totalPages-1)*37), 12, 15);
+
+        matrices.pop();
+
         this.drawMouseoverTooltip(context, mouseX, mouseY);
     }
 
-    private static int getOrdinal(List<Text> tooltip) {
-        int ordinal = 0;
-        for (int j = 0; j < tooltip.size()-3; j++) {
-            if (j+2 >= tooltip.size()) break;
-            if (tooltip.get(j+2).getString().contains("▶")) {
-                ordinal = j;
-                break;
-            }
+    @Override
+    protected void onMouseClick(Slot slot, int slotId, int button, SlotActionType actionType) {
+        if (slotId >= handler.getRows()*9) return;
+        super.onMouseClick(slot, slotId, button, actionType);
+    }
+
+    @Override
+    public boolean mouseClicked(double mouseX, double mouseY, int button) {
+        if (isWaitingForServer) return super.mouseClicked(mouseX, mouseY, button);
+        if (onScrollbarTop((int)mouseX, (int) mouseY) && prevPageVisible) {
+            clickSlot(PREV_PAGE_BUTTON);
+            return true;
         }
-        return ordinal;
+        if (onScrollbarBottom((int)mouseX, (int) mouseY) && nextPageVisible) {
+            clickSlot(NEXT_PAGE_BUTTON);
+            return true;
+        }
+
+        if (onSearchField((int)mouseX, (int) mouseY)) {
+            clickSlot(SEARCH_BUTTON_SLOT);
+            return true;
+        }
+        return super.mouseClicked(mouseX, mouseY, button);
+    }
+
+    private boolean onScrollbarTop(int mouseX, int mouseY) {
+        int localX = mouseX - x;
+        int localY = mouseY - y;
+        return localX > 154 && localX < 169 && localY > 6 && localY < 44;
+    }
+
+    private boolean onScrollbarBottom(int mouseX, int mouseY) {
+        int localX = mouseX - x;
+        int localY = mouseY - y;
+        return localX > 154 && localX < 169 && localY > 43 && localY < 80;
+    }
+    private boolean onSearchField(int mouseX, int mouseY) {
+        int localX = mouseX - x;
+        int localY = mouseY - y;
+        return localX > 6 && localX < 97 && localY > 3 && localY < 16;
     }
 
     @Override
-    public void onSlotUpdate(ScreenHandler handler, int slotId, ItemStack stack) {
+    public void onSlotChange(AuctionHouseScreenHandler handler, int slotId, ItemStack stack) {
         if (client == null || stack.isEmpty()) return;
-        if (slotId == 50) {
+        isWaitingForServer = false;
+        if (slotId == PREV_PAGE_BUTTON) prevPageVisible = false;
+        if (slotId == NEXT_PAGE_BUTTON) nextPageVisible = false;
+        if (slotId == SORT_BUTTON_SLOT) {
             sortWidget.setCurrent(SortWidget.Option.get(getOrdinal(stack.getTooltip(client.player, TooltipContext.BASIC))));
-        } else if (slotId == 52) {
+        } else if (slotId == AUCTION_TYPE_BUTTON_SLOT) {
             auctionTypeWidget.setCurrent(AuctionTypeWidget.Option.get(getOrdinal(stack.getTooltip(client.player, TooltipContext.BASIC))));
-        } else if (slotId == 51) {
+        } else if (slotId == RARITY_BUTTON_SLOT) {
             List<Text> tooltip = stack.getTooltip(client.player, TooltipContext.BASIC);
             int ordinal = getOrdinal(tooltip);
             String split = tooltip.get(ordinal+2).getString().substring(2);
             rarityWidget.setText(tooltip.subList(1, tooltip.size()-3), split);
-        } else if (slotId == 45) {
+        } else if (slotId == RESET_BUTTON_SLOT) {
             if (resetFiltersButton != null) resetFiltersButton.active = handler.getSlot(slotId).getStack().isOf(Items.ANVIL);
+        } else if (slotId < this.handler.getRows()*9 && slotId%9 == 0) {
+            CategoryTabWidget categoryTabWidget = categoryTabWidgets.get(slotId / 9);
+            categoryTabWidget.setSlotId(slotId);
+            categoryTabWidget.setIcon(handler.getSlot(slotId).getStack());
+            List<Text> tooltip = handler.getSlot(slotId).getStack().getTooltip(client.player, TooltipContext.BASIC);
+            for (int j = tooltip.size() - 1; j >= 0; j--) {
+                String lowerCase = tooltip.get(j).getString().toLowerCase();
+                if (lowerCase.contains("currently")) {
+                    categoryTabWidget.setToggled(true);
+                    break;
+                } else if (lowerCase.contains("click")) {
+                    categoryTabWidget.setToggled(false);
+                    break;
+                } else categoryTabWidget.setToggled(false);
+            }
+        } else if (slotId == PREV_PAGE_BUTTON && stack.isOf(Items.ARROW)) {
+            prevPageVisible = true;
+            parsePage(stack);
+        } else if (slotId == NEXT_PAGE_BUTTON && stack.isOf(Items.ARROW)) {
+            nextPageVisible = true;
+            parsePage(stack);
+        } else if (slotId == SEARCH_BUTTON_SLOT) {
+            List<Text> tooltip = stack.getTooltip(client.player, TooltipContext.BASIC);
+            for (Text text : tooltip) {
+                String string = text.getString();
+                if (string.contains("Filtered:")) {
+                    String[] split = string.split(":");
+                    if (split.length < 2) {
+                        search = "";
+                    } else search = split[1].trim();
+                    break;
+                }
+            }
         }
     }
 
-    @Override
-    public void removed() {
-        super.removed();
-        handler.removeListener(this);
+    private static int getOrdinal(List<Text> tooltip) {
+        int ordinal = 0;
+        for (int j = 0; j < tooltip.size()-3; j++) {
+            if (j+2 >= tooltip.size()) break;
+            if (tooltip.get(j+2).getString().contains("▶")) {
+                ordinal = j;
+                break;
+            }
+        }
+        return ordinal;
+    }
+
+    int currentPage = 0;
+    int totalPages = 1;
+    private boolean prevPageVisible = false;
+    private boolean nextPageVisible = false;
+    private void parsePage(ItemStack stack) {
+        List<Text> tooltip = stack.getTooltip(client.player, TooltipContext.BASIC);
+        String str = tooltip.get(1).getString().trim();
+        str = str.substring(1, str.length() - 1); // remove parentheses
+        String[] parts = str.split("/"); // split the string
+        try {
+            currentPage = Integer.parseInt(parts[0].replace(",", "")); // parse current page
+            totalPages = Integer.parseInt(parts[1].replace(",", "")); // parse total
+        } catch (NumberFormatException ignored) {}
     }
 
     @Override
-    public void onPropertyUpdate(ScreenHandler handler, int property, int value) {}
+    protected boolean isClickOutsideBounds(double mouseX, double mouseY, int left, int top, int button) {
+        return mouseX < (double)left - 32 || mouseY < (double)top || mouseX >= (double)(left + this.backgroundWidth) || mouseY >= (double)(top + this.backgroundHeight);
+    }
 
     private static class ScaledTextButtonWidget extends ButtonWidget {
 
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/auction/AuctionHouseScreenHandler.java b/src/main/java/de/hysky/skyblocker/skyblock/auction/AuctionHouseScreenHandler.java
index 7df3cef5..fbc9fb17 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/auction/AuctionHouseScreenHandler.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/auction/AuctionHouseScreenHandler.java
@@ -20,13 +20,11 @@ public class AuctionHouseScreenHandler extends GenericContainerScreenHandler {
             SlotAccessor slotAccessor = (SlotAccessor) slot;
             slotAccessor.setY(slot.y+2-yOffset);
         }
-
-        if (isView) return;
         // disable ALL THE OTHER SLOTS MWAHAHAHA and also move the good ones around and stuff
         for (int i = 0; i < rows*9; i++) {
             int lineI = i % 9;
             Slot slot = slots.get(i);
-            if (i>9 && i<(rows-1)*9 && lineI > 1 && lineI < 8) {
+            if (!isView && i>9 && i<(rows-1)*9 && lineI > 1 && lineI < 8) {
                 int miniInventorySlot = lineI - 2 + (i/9 - 1)*6;
                 SlotAccessor slotAccessor = (SlotAccessor) slot;
                 slotAccessor.setX(8 + miniInventorySlot%8 * 18);
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/auction/AuctionViewScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/auction/AuctionViewScreen.java
new file mode 100644
index 00000000..d482ccd8
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/auction/AuctionViewScreen.java
@@ -0,0 +1,201 @@
+package de.hysky.skyblocker.skyblock.auction;
+
+import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.utils.render.gui.AbstractCustomHypixelGUI;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.screen.PopupScreen;
+import net.minecraft.client.gui.widget.ButtonWidget;
+import net.minecraft.client.gui.widget.DirectionalLayoutWidget;
+import net.minecraft.client.gui.widget.SimplePositioningWidget;
+import net.minecraft.client.gui.widget.TextWidget;
+import net.minecraft.client.item.TooltipContext;
+import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.entity.player.PlayerInventory;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.Items;
+import net.minecraft.screen.slot.SlotActionType;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Style;
+import net.minecraft.text.Text;
+import net.minecraft.util.Colors;
+import net.minecraft.util.Formatting;
+import net.minecraft.util.Identifier;
+
+import java.util.List;
+
+public class AuctionViewScreen extends AbstractCustomHypixelGUI<AuctionHouseScreenHandler> {
+    protected static final Identifier BACKGROUND_TEXTURE = new Identifier(SkyblockerMod.NAMESPACE,"textures/gui/auctions_gui/browser/background_view.png");
+
+    DirectionalLayoutWidget verticalLayout = DirectionalLayoutWidget.vertical();
+
+    public final boolean isBinAuction;
+    private TextWidget priceWidget;
+    private final Text clickToEditBidText = Text.literal("Click to edit Bid!").setStyle(Style.EMPTY.withUnderline(true));
+
+    private TextWidget cantAffordText;
+    public String minBid = "";
+
+    private BuyState buyState = BuyState.CANT_AFFORD;
+    private MutableText priceText = Text.literal("?");
+
+    public AuctionViewScreen(AuctionHouseScreenHandler handler, PlayerInventory inventory, Text title) {
+        super(handler, inventory, title);
+        backgroundHeight = 187;
+        isBinAuction = this.getTitle().getString().toLowerCase().contains("bin");
+        playerInventoryTitleY = 93;
+    }
+
+    @Override
+    protected void init() {
+        super.init();
+        verticalLayout.spacing(2).getMainPositioner().alignHorizontalCenter();
+        verticalLayout.add(new TextWidget(Text.literal(isBinAuction ? "Price:" : "New Bid:"), textRenderer).alignCenter());
+
+        priceWidget = new TextWidget(Text.literal("?"), textRenderer).alignCenter();
+        priceWidget.setWidth(textRenderer.getWidth(clickToEditBidText));
+        priceWidget.active = true;
+        verticalLayout.add(priceWidget);
+
+        cantAffordText = new TextWidget(Text.literal("Can't Afford"), textRenderer).alignCenter();
+        verticalLayout.add(cantAffordText);
+
+        verticalLayout.add(ButtonWidget.builder(Text.literal(isBinAuction?"Buy!":"Bid!"), button -> {
+            if (buySlotID == -1) return;
+            clickSlot(buySlotID);
+        }).size(50, 12).build());
+        verticalLayout.forEachChild(this::addDrawableChild);
+        updateLayout();
+
+
+    }
+
+    private void changeState(BuyState newState) {
+        if (newState == buyState) return;
+        buyState = newState;
+        switch (buyState) {
+            case CANT_AFFORD -> cantAffordText.setMessage(Text.literal("Can't Afford!").withColor(Colors.RED));
+            case TOP_BID -> cantAffordText.setMessage(Text.literal("Already top bid!").withColor(Colors.LIGHT_YELLOW));
+            case AFFORD -> cantAffordText.setMessage(Text.empty());
+        }
+        cantAffordText.setWidth(textRenderer.getWidth(cantAffordText.getMessage()));
+        updateLayout();
+    }
+
+    private void updateLayout() {
+        verticalLayout.refreshPositions();
+        SimplePositioningWidget.setPos(verticalLayout, x, y+36, backgroundWidth, 60);
+    }
+
+    @Override
+    protected void drawBackground(DrawContext context, float delta, int mouseX, int mouseY) {
+        context.drawTexture(BACKGROUND_TEXTURE, this.x, this.y, 0, 0, this.backgroundWidth, this.backgroundHeight);
+    }
+
+    @Override
+    public void render(DrawContext context, int mouseX, int mouseY, float delta) {
+        super.render(context, mouseX, mouseY, delta);
+
+        MatrixStack matrices = context.getMatrices();
+
+        matrices.push();
+        matrices.translate(x+77, y+14, 0);
+        matrices.scale(1.375f, 1.375f, 1.375f);
+        //matrices.translate(0, 0, 100f);
+        ItemStack stack = handler.getSlot(13).getStack();
+        context.drawItem(stack, 0, 0);
+        context.drawItemInSlot(textRenderer, stack, 0, 0);
+        matrices.pop();
+
+        if (!isBinAuction) {
+            if (priceWidget.isMouseOver(mouseX, mouseY) && buyState != BuyState.CANT_AFFORD) {
+                priceWidget.setMessage(clickToEditBidText);
+            } else {
+                priceWidget.setMessage(priceText);
+            }
+        }
+
+        drawMouseoverTooltip(context, mouseX, mouseY);
+    }
+
+    @Override
+    protected void drawMouseoverTooltip(DrawContext context, int x, int y) {
+        super.drawMouseoverTooltip(context, x, y);
+        if (x>this.x+75 && x<this.x+75+26 && y>this.y+13 && y<this.y+13+26) {
+            context.drawTooltip(this.textRenderer, this.getTooltipFromItem(handler.getSlot(13).getStack()), x, y);
+        }
+    }
+
+    @Override
+    public boolean mouseClicked(double mouseX, double mouseY, int button) {
+        if (!isBinAuction && priceWidget.isMouseOver(mouseX, mouseY)) {
+            clickSlot(31);
+            return true;
+        }
+        return super.mouseClicked(mouseX, mouseY, button);
+    }
+
+    @Override
+    public void onSlotChange(AuctionHouseScreenHandler handler, int slotId, ItemStack stack) {
+        if (stack.isOf(Items.BLACK_STAINED_GLASS_PANE) || slotId == 13) return;
+        assert client != null;
+        if (priceParsed) return;
+        if (stack.isOf(Items.POISONOUS_POTATO)) {
+            changeState(BuyState.CANT_AFFORD);
+            getPriceFromTooltip(stack.getTooltip(client.player, TooltipContext.BASIC));
+            buySlotID = slotId;
+        } else if (stack.isOf(Items.GOLD_NUGGET)) {
+            changeState(BuyState.AFFORD);
+            getPriceFromTooltip(stack.getTooltip(client.player, TooltipContext.BASIC));
+            buySlotID = slotId;
+        } else if (stack.isOf(Items.GOLD_BLOCK)) {
+            changeState(BuyState.TOP_BID);
+            getPriceFromTooltip(stack.getTooltip(client.player, TooltipContext.BASIC));
+            buySlotID = slotId;
+        }
+    }
+
+    private int buySlotID = -1;
+    private boolean priceParsed = false;
+    private void getPriceFromTooltip(List<Text> tooltip) {
+        if (priceParsed) return;
+        String minBid = null;
+        String priceString = "???";
+        for (Text text : tooltip) {
+            String string = text.getString();
+            String thingToLookFor = (isBinAuction) ? "price:" : "new bid:";
+            if (string.toLowerCase().contains(thingToLookFor)) {
+                String[] split = string.split(":");
+                if (split.length < 2) continue;
+                priceString = split[1].trim();
+                break;
+            } else if (string.toLowerCase().contains("minimum bid:") && !isBinAuction) {
+                String[] split = string.split(":");
+                if (split.length < 2) continue;
+                minBid = split[1].replace("coins", "").replace(",", "").trim();
+            }
+        }
+        if (minBid != null) this.minBid = minBid;
+        else this.minBid = priceString;
+        priceText = Text.literal(priceString).setStyle(Style.EMPTY.withFormatting(Formatting.BOLD).withColor(Formatting.GOLD));
+        priceWidget.setMessage(priceText);
+        int width = textRenderer.getWidth(priceText);
+        if (width > priceWidget.getWidth()) priceWidget.setWidth(width);
+        priceParsed = true;
+        updateLayout();
+    }
+
+    public PopupScreen getConfirmPurchasePopup(Text title) {
+        // This really shouldn't be possible to be null in its ACTUAL use case.
+        //noinspection DataFlowIssue
+        return new PopupScreen.Builder(this, title)
+                .button(Text.literal("Confirm"), popupScreen -> this.client.interactionManager.clickSlot(this.client.player.currentScreenHandler.syncId, 11, 0, SlotActionType.PICKUP, client.player))
+                .button(Text.literal("Cancel"), popupScreen -> this.client.interactionManager.clickSlot(this.client.player.currentScreenHandler.syncId, 15, 0, SlotActionType.PICKUP, client.player))
+                .message(Text.literal(isBinAuction ? "Price: " : "New Bid: ").append(priceText)).build();
+    }
+
+    private enum BuyState {
+        CANT_AFFORD,
+        AFFORD,
+        TOP_BID
+    }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/auction/EditBidPopup.java b/src/main/java/de/hysky/skyblocker/skyblock/auction/EditBidPopup.java
new file mode 100644
index 00000000..ccdb7353
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/auction/EditBidPopup.java
@@ -0,0 +1,99 @@
+package de.hysky.skyblocker.skyblock.auction;
+
+import de.hysky.skyblocker.utils.render.gui.BarebonesPopupScreen;
+import net.minecraft.block.entity.SignBlockEntity;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.gui.widget.*;
+import net.minecraft.network.packet.c2s.play.UpdateSignC2SPacket;
+import net.minecraft.text.Style;
+import net.minecraft.text.Text;
+import org.jetbrains.annotations.NotNull;
+
+public class EditBidPopup extends BarebonesPopupScreen {
+    private DirectionalLayoutWidget layout = DirectionalLayoutWidget.vertical();
+    private final String minimumBid;
+    private final SignBlockEntity signBlockEntity;
+
+    private final boolean signFront;
+
+    private TextFieldWidget textFieldWidget;
+
+    private boolean packetSent = false;
+
+    public EditBidPopup(AuctionViewScreen auctionViewScreen, @NotNull SignBlockEntity signBlockEntity, boolean signFront, String minimumBid) {
+        super(Text.literal("Edit Bid"), auctionViewScreen);
+        this.minimumBid = minimumBid;
+        this.signBlockEntity = signBlockEntity;
+        this.signFront = signFront;
+    }
+
+    @Override
+    protected void init() {
+        super.init();
+        layout = DirectionalLayoutWidget.vertical();
+        layout.spacing(8).getMainPositioner().alignHorizontalCenter();
+        textFieldWidget = new TextFieldWidget(textRenderer, 120, 15, Text.empty());
+        textFieldWidget.setTextPredicate(this::isStringGood);
+        layout.add(new TextWidget(Text.literal("- Set Bid -").fillStyle(Style.EMPTY.withBold(true)), textRenderer));
+        layout.add(textFieldWidget);
+        layout.add(new TextWidget(Text.literal("Minimum Bid: " + minimumBid), textRenderer));
+        DirectionalLayoutWidget horizontal = DirectionalLayoutWidget.horizontal();
+        ButtonWidget buttonWidget = ButtonWidget.builder(Text.literal("Set Minimum Bid"), this::buttonMinimumBid).width(80).build();
+        buttonWidget.active = isStringGood(minimumBid);
+        horizontal.add(buttonWidget);
+        horizontal.add(ButtonWidget.builder(Text.literal("Done"), this::done).width(80).build());
+        layout.add(horizontal);
+        layout.forEachChild(this::addDrawableChild);
+        this.layout.refreshPositions();
+        SimplePositioningWidget.setPos(layout, this.getNavigationFocus());
+        setInitialFocus(textFieldWidget);
+    }
+
+    @Override
+    public void renderBackground(DrawContext context, int mouseX, int mouseY, float delta) {
+        super.renderBackground(context, mouseX, mouseY, delta);
+        drawPopupBackground(context, layout.getX(), layout.getY(), layout.getWidth(), layout.getHeight());
+    }
+
+    private boolean isStringGood(String s) {
+        assert this.client != null;
+        return this.client.textRenderer.getWidth(minimumBid) <= this.signBlockEntity.getMaxTextWidth();
+    }
+
+    private void buttonMinimumBid(ButtonWidget widget) {
+        if (!isStringGood(minimumBid)) return;
+        sendPacket(minimumBid);
+        this.close();
+    }
+
+    private void done(ButtonWidget widget) {
+        if(!isStringGood(textFieldWidget.getText().trim())) return;
+        sendPacket(textFieldWidget.getText().trim());
+        this.close();
+    }
+
+    private void sendPacket(String string) {
+        assert MinecraftClient.getInstance().player != null;
+        MinecraftClient.getInstance().player.networkHandler.sendPacket(new UpdateSignC2SPacket(signBlockEntity.getPos(), signFront,
+                string,
+                "",
+                "",
+                ""
+        ));
+        packetSent = true;
+    }
+
+    @Override
+    public void close() {
+        if (!packetSent) sendPacket("");
+        this.client.setScreen(null);
+    }
+
+    @Override
+    public void removed() {
+        if (!packetSent) sendPacket("");
+        super.removed();
+    }
+}
diff --git a/src/main/java/de/hysky/skyblocker/utils/render/gui/AbstractCustomHypixelGUI.java b/src/main/java/de/hysky/skyblocker/utils/render/gui/AbstractCustomHypixelGUI.java
new file mode 100644
index 00000000..4f648b8c
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/utils/render/gui/AbstractCustomHypixelGUI.java
@@ -0,0 +1,56 @@
+package de.hysky.skyblocker.utils.render.gui;
+
+import de.hysky.skyblocker.mixin.accessor.HandledScreenAccessor;
+import de.hysky.skyblocker.skyblock.auction.AuctionHouseScreenHandler;
+import net.minecraft.client.gui.screen.ingame.HandledScreen;
+import net.minecraft.entity.player.PlayerInventory;
+import net.minecraft.item.ItemStack;
+import net.minecraft.screen.ScreenHandler;
+import net.minecraft.screen.ScreenHandlerListener;
+import net.minecraft.screen.slot.SlotActionType;
+import net.minecraft.text.Text;
+
+public abstract class AbstractCustomHypixelGUI<T extends ScreenHandler> extends HandledScreen<T> implements ScreenHandlerListener {
+
+    public boolean isWaitingForServer = true;
+    public AbstractCustomHypixelGUI(T handler, PlayerInventory inventory, Text title) {
+        super(handler, inventory, title);
+        handler.addListener(this);
+    }
+
+    protected void clickSlot(int slotID, int button) {
+        if (isWaitingForServer) return;
+        if (client == null) return;
+        assert this.client.interactionManager != null;
+        this.client.interactionManager.clickSlot(handler.syncId, slotID, button, SlotActionType.PICKUP, client.player);
+        handler.getCursorStack().setCount(0);
+        isWaitingForServer = true;
+    }
+
+    protected void clickSlot(int slotID) {
+        clickSlot(slotID, 0);
+    }
+
+    public void changeHandler(AuctionHouseScreenHandler newHandler) {
+        handler.removeListener(this);
+        ((HandledScreenAccessor) this).setHandler(newHandler);
+        handler.addListener(this);
+    }
+
+    @Override
+    public void removed() {
+        super.removed();
+        handler.removeListener(this);
+    }
+
+    @Override
+    public final void onSlotUpdate(ScreenHandler handler, int slotId, ItemStack stack) {
+        onSlotChange(this.handler, slotId, stack);
+        isWaitingForServer = false;
+    }
+
+    protected abstract void onSlotChange(T handler, int slotID, ItemStack stack);
+
+    @Override
+    public void onPropertyUpdate(ScreenHandler handler, int property, int value) {}
+}
diff --git a/src/main/java/de/hysky/skyblocker/utils/render/gui/BarebonesPopupScreen.java b/src/main/java/de/hysky/skyblocker/utils/render/gui/BarebonesPopupScreen.java
new file mode 100644
index 00000000..56b07966
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/utils/render/gui/BarebonesPopupScreen.java
@@ -0,0 +1,56 @@
+package de.hysky.skyblocker.utils.render.gui;
+
+import com.mojang.blaze3d.platform.GlConst;
+import com.mojang.blaze3d.systems.RenderSystem;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.text.Text;
+import net.minecraft.util.Identifier;
+
+/**
+ * A more bare-bones version of Vanilla's Popup Screen. Meant to be extended.
+ */
+public class BarebonesPopupScreen extends Screen {
+    private static final Identifier BACKGROUND_TEXTURE = new Identifier("popup/background");
+    private final Screen backgroundScreen;
+
+    protected BarebonesPopupScreen(Text title, Screen backgroundScreen) {
+        super(title);
+        this.backgroundScreen = backgroundScreen;
+    }
+
+    @Override
+    public void close() {
+        assert this.client != null;
+        this.client.setScreen(this.backgroundScreen);
+    }
+
+    @Override
+    public void renderBackground(DrawContext context, int mouseX, int mouseY, float delta) {
+        this.backgroundScreen.render(context, -1, -1, delta);
+        context.draw();
+        RenderSystem.clear(GlConst.GL_DEPTH_BUFFER_BIT, MinecraftClient.IS_SYSTEM_MAC);
+        this.renderInGameBackground(context);
+    }
+
+    /**
+     * These are the inner positions and size of the popup, not outer
+     */
+    public static void drawPopupBackground(DrawContext context, int x, int y, int width, int height) {
+        context.drawGuiTexture(BACKGROUND_TEXTURE, x - 18, y - 18, width + 36, height + 36);
+    }
+
+    @Override
+    protected void init() {
+        super.init();
+        this.backgroundScreen.resize(this.client, width, height);
+
+    }
+
+    @Override
+    public void onDisplayed() {
+        super.onDisplayed();
+        this.backgroundScreen.blur();
+    }
+}
\ No newline at end of file
-- 
cgit