From 098b003a641225b97ffd05c12df3954db901397a Mon Sep 17 00:00:00 2001 From: vicisacat Date: Fri, 12 Jan 2024 18:11:35 +0100 Subject: Adds a custom GUI for the Party Finder in dungeons. Is it good? Debatable. --- .../hysky/skyblocker/config/SkyblockerConfig.java | 3 + .../config/categories/GeneralCategory.java | 7 + .../skyblocker/mixin/ClientPlayerEntityMixin.java | 17 + .../mixin/GenericContainerScreenHandlerMixin.java | 8 + .../skyblocker/mixin/HandledScreensMixin.java | 41 ++ .../mixin/accessor/SkullBlockEntityAccessor.java | 29 ++ .../partyfinder/FinderSettingsContainer.java | 282 ++++++++++++++ .../dungeon/partyfinder/OptionDropdownWidget.java | 203 ++++++++++ .../skyblock/dungeon/partyfinder/PartyEntry.java | 261 +++++++++++++ .../dungeon/partyfinder/PartyEntryListWidget.java | 99 +++++ .../dungeon/partyfinder/PartyFinderScreen.java | 424 +++++++++++++++++++++ .../dungeon/partyfinder/RangedValueWidget.java | 268 +++++++++++++ 12 files changed, 1642 insertions(+) create mode 100644 src/main/java/de/hysky/skyblocker/mixin/HandledScreensMixin.java create mode 100644 src/main/java/de/hysky/skyblocker/mixin/accessor/SkullBlockEntityAccessor.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/dungeon/partyfinder/FinderSettingsContainer.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/dungeon/partyfinder/OptionDropdownWidget.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/dungeon/partyfinder/PartyEntry.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/dungeon/partyfinder/PartyEntryListWidget.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/dungeon/partyfinder/PartyFinderScreen.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/dungeon/partyfinder/RangedValueWidget.java (limited to 'src/main/java/de/hysky/skyblocker') diff --git a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java index 609e7c2f..2eddea23 100644 --- a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java @@ -155,6 +155,9 @@ public class SkyblockerConfig { @SerialEntry public boolean acceptReparty = true; + @SerialEntry + public boolean betterPartyFinder = true; + @SerialEntry public boolean backpackPreviewWithoutShift = false; diff --git a/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java index 3b9cbe60..406dd885 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java @@ -34,6 +34,13 @@ public class GeneralCategory { newValue -> config.general.acceptReparty = newValue) .controller(ConfigUtils::createBooleanController) .build()) + .option(Option.createBuilder() + .name(Text.translatable("text.autoconfig.skyblocker.option.general.betterPartyFinder")) + .binding(defaults.general.betterPartyFinder, + () -> config.general.betterPartyFinder, + newValue -> config.general.betterPartyFinder = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) .option(Option.createBuilder() .name(Text.translatable("text.autoconfig.skyblocker.option.general.backpackPreviewWithoutShift")) .binding(defaults.general.backpackPreviewWithoutShift, diff --git a/src/main/java/de/hysky/skyblocker/mixin/ClientPlayerEntityMixin.java b/src/main/java/de/hysky/skyblocker/mixin/ClientPlayerEntityMixin.java index 4abadcea..68440874 100644 --- a/src/main/java/de/hysky/skyblocker/mixin/ClientPlayerEntityMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixin/ClientPlayerEntityMixin.java @@ -2,14 +2,19 @@ package de.hysky.skyblocker.mixin; import com.mojang.authlib.GameProfile; +import de.hysky.skyblocker.skyblock.dungeon.partyfinder.PartyFinderScreen; import de.hysky.skyblocker.skyblock.item.HotbarSlotLock; import de.hysky.skyblocker.skyblock.item.ItemProtection; import de.hysky.skyblocker.skyblock.rift.HealingMelonIndicator; import de.hysky.skyblocker.utils.Utils; +import net.minecraft.block.entity.SignBlockEntity; +import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.AbstractClientPlayerEntity; import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.client.world.ClientWorld; +import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @@ -17,6 +22,8 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(ClientPlayerEntity.class) public abstract class ClientPlayerEntityMixin extends AbstractClientPlayerEntity { + @Shadow @Final protected MinecraftClient client; + public ClientPlayerEntityMixin(ClientWorld world, GameProfile profile) { super(world, profile); } @@ -33,4 +40,14 @@ public abstract class ClientPlayerEntityMixin extends AbstractClientPlayerEntity public void skyblocker$updateHealth(CallbackInfo ci) { HealingMelonIndicator.updateHealth(); } + + @Inject(method = "openEditSignScreen", at = @At("HEAD"), cancellable = true) + public void skyblocker$partyFinderRange(SignBlockEntity sign, boolean front, CallbackInfo callbackInfo) { + if (client.currentScreen instanceof PartyFinderScreen partyFinderScreen && !partyFinderScreen.isAborted()) { + if (sign.getText(front).getMessage(3, false).getString().toLowerCase().contains("level")) { + partyFinderScreen.updateSign(sign, front); + callbackInfo.cancel(); + } + } + } } \ No newline at end of file diff --git a/src/main/java/de/hysky/skyblocker/mixin/GenericContainerScreenHandlerMixin.java b/src/main/java/de/hysky/skyblocker/mixin/GenericContainerScreenHandlerMixin.java index 2608b69f..a7843ba2 100644 --- a/src/main/java/de/hysky/skyblocker/mixin/GenericContainerScreenHandlerMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixin/GenericContainerScreenHandlerMixin.java @@ -1,6 +1,8 @@ package de.hysky.skyblocker.mixin; import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.skyblock.dungeon.partyfinder.PartyFinderScreen; +import net.minecraft.client.MinecraftClient; import net.minecraft.item.ItemStack; import net.minecraft.screen.GenericContainerScreenHandler; import net.minecraft.screen.ScreenHandler; @@ -20,11 +22,17 @@ public abstract class GenericContainerScreenHandlerMixin extends ScreenHandler { public void setStackInSlot(int slot, int revision, ItemStack stack) { super.setStackInSlot(slot, revision, stack); SkyblockerMod.getInstance().containerSolverManager.markDirty(); + if (MinecraftClient.getInstance().currentScreen instanceof PartyFinderScreen screen) { + screen.markDirty(); + } } @Override public void updateSlotStacks(int revision, List stacks, ItemStack cursorStack) { super.updateSlotStacks(revision, stacks, cursorStack); SkyblockerMod.getInstance().containerSolverManager.markDirty(); + if (MinecraftClient.getInstance().currentScreen instanceof PartyFinderScreen screen) { + screen.markDirty(); + } } } diff --git a/src/main/java/de/hysky/skyblocker/mixin/HandledScreensMixin.java b/src/main/java/de/hysky/skyblocker/mixin/HandledScreensMixin.java new file mode 100644 index 00000000..9ff92a20 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/mixin/HandledScreensMixin.java @@ -0,0 +1,41 @@ +package de.hysky.skyblocker.mixin; + + +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.skyblock.dungeon.partyfinder.PartyFinderScreen; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.ingame.HandledScreens; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.screen.GenericContainerScreenHandler; +import net.minecraft.screen.ScreenHandler; +import net.minecraft.screen.ScreenHandlerType; +import net.minecraft.text.Text; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(HandledScreens.Provider.class) +public interface HandledScreensMixin { + //value = "INVOKE", target = "Lnet/minecraft/client/MinecraftClient;setScreen(Lnet/minecraft/client/gui/screen/Screen;)V"), cancellable = true + @Inject(method = "open", at = @At("HEAD"), cancellable = true) + default void skyblocker$open(Text name, ScreenHandlerType type, MinecraftClient client, int id, CallbackInfo ci) { + if (!SkyblockerConfigManager.get().general.betterPartyFinder) return; + ClientPlayerEntity player = client.player; + if (player == null) return; + T screenHandler = type.create(id, player.getInventory()); + if ((screenHandler instanceof GenericContainerScreenHandler containerScreenHandler) && PartyFinderScreen.possibleInventoryNames.contains(name.getString().toLowerCase())) { + //player.sendMessage(Text.of("LESSGOOOOO " + containerScreenHandler.getRows())); + client.player.currentScreenHandler = (containerScreenHandler); + if (client.currentScreen instanceof PartyFinderScreen screen) { + screen.updateHandler(containerScreenHandler, name); + } else { + client.setScreen(new PartyFinderScreen(containerScreenHandler, player.getInventory(), name)); + } + + ci.cancel(); + } + + } +} diff --git a/src/main/java/de/hysky/skyblocker/mixin/accessor/SkullBlockEntityAccessor.java b/src/main/java/de/hysky/skyblocker/mixin/accessor/SkullBlockEntityAccessor.java new file mode 100644 index 00000000..2714c4ee --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/mixin/accessor/SkullBlockEntityAccessor.java @@ -0,0 +1,29 @@ +package de.hysky.skyblocker.mixin.accessor; + +import com.google.common.cache.LoadingCache; +import com.mojang.authlib.GameProfile; +import net.minecraft.block.entity.SkullBlockEntity; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.util.UserCache; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; +import org.spongepowered.asm.mixin.gen.Invoker; + +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +@Mixin(SkullBlockEntity.class) +public interface SkullBlockEntityAccessor { + @Contract(pure = true) + @Accessor("userCache") + public static @Nullable LoadingCache>> getUserCache() { + return null; + } + + @Invoker + public static CompletableFuture> invokeFetchProfile(String name) { + return new CompletableFuture<>(); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/partyfinder/FinderSettingsContainer.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/partyfinder/FinderSettingsContainer.java new file mode 100644 index 00000000..18cebff7 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/partyfinder/FinderSettingsContainer.java @@ -0,0 +1,282 @@ +package de.hysky.skyblocker.skyblock.dungeon.partyfinder; + +import net.minecraft.block.entity.SignBlockEntity; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.Element; +import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; +import net.minecraft.client.gui.widget.ContainerWidget; +import net.minecraft.client.item.TooltipContext; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.screen.GenericContainerScreenHandler; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; + +import java.util.ArrayList; +import java.util.List; + +public class FinderSettingsContainer extends ContainerWidget{ + + private boolean isInitialized = false; + private OptionDropdownWidget floorSelector; + private OptionDropdownWidget dungeonTypeSelector; + private OptionDropdownWidget sortGroupsSelector; + + private RangedValueWidget classLevelRange; + private RangedValueWidget dungeonLevelRange; + + private ContainerWidget currentlyOpenedOption = null; + + private final List initializedWidgets = new ArrayList<>(); + + + public FinderSettingsContainer(int x, int y, int height) { + super(x, y, 336, height, Text.empty()); + } + + @Override + public void setDimensionsAndPosition(int width, int height, int x, int y) { + super.setDimensionsAndPosition(width, height, x, y); + if (this.floorSelector != null) floorSelector.setPosition(x + width/4-70, y + 20); + if (this.dungeonTypeSelector != null) dungeonTypeSelector.setPosition(x + 3*width/4-70, y + 20); + if (this.sortGroupsSelector != null) sortGroupsSelector.setPosition(x + width/2-70, y + 120); + if (this.classLevelRange != null) classLevelRange.setPosition(x + width/4-50, y + 70); + if (this.dungeonLevelRange != null) dungeonLevelRange.setPosition(x + 3*width/4-50, y + 70); + + } + + /** + * Handles everything in the Settings page + * @param screen the parent Party Finder screen + * @param inventoryName le inventory name + * @return returns false if it doesn't know what's happening + */ + public boolean handle(PartyFinderScreen screen, String inventoryName) { + String nameLowerCase = inventoryName.toLowerCase(); + GenericContainerScreenHandler handler = screen.getHandler(); + if (!isInitialized) { + if (!nameLowerCase.contains("search settings")) return false; + isInitialized = true; + //System.out.println("initializing"); + for (Slot slot : handler.slots) { + if (slot.id > handler.getRows() * 9) break; + if (!slot.hasStack()) continue; + ItemStack stack = slot.getStack(); + //System.out.println(stack.toString()); + String name = stack.getName().getString().toLowerCase(); + if (name.contains("floor")) { + + //System.out.println("Floor selector created"); + this.floorSelector = new OptionDropdownWidget(screen, stack.getName(), null, getX() + getWidth()/4-70, getY() + 20, 140, 170, slot.id); + if (!setSelectedElementFromTooltip(slot, stack, floorSelector)) return false; + + initializedWidgets.add(floorSelector); + + } else if (name.contains("dungeon type")) { + + this.dungeonTypeSelector = new OptionDropdownWidget(screen, stack.getName(), null, getX() + (3*getWidth())/4-70, getY() + 20, 140, 100, slot.id); + if (!setSelectedElementFromTooltip(slot, stack, dungeonTypeSelector)) return false; + + initializedWidgets.add(dungeonTypeSelector); + + } else if (name.contains("groups")) { + + this.sortGroupsSelector = new OptionDropdownWidget(screen, stack.getName(), null, getX() + getWidth()/2-70, getY() + 120, 140, 100, slot.id); + if (!setSelectedElementFromTooltip(slot, stack, sortGroupsSelector)) return false; + + initializedWidgets.add(sortGroupsSelector); + + } else if (name.contains("class level")) { + + this.classLevelRange = new RangedValueWidget(screen, stack.getName(), getX() + getWidth()/4-50, getY() + 70, 100, slot.id); + if (!setRangeFromTooltip(stack, classLevelRange)) return false; + + initializedWidgets.add(classLevelRange); + + } else if (name.contains("dungeon level")) { + + this.dungeonLevelRange = new RangedValueWidget(screen, stack.getName(), getX() + 3*(getWidth())/4-50, getY() + 70, 100, slot.id); + if (!setRangeFromTooltip(stack, dungeonLevelRange)) return false; + + initializedWidgets.add(dungeonLevelRange); + + } + } + } + if (nameLowerCase.contains("search settings")) { + if (floorSelector != null) floorSelector.close(); + if (dungeonTypeSelector != null) dungeonTypeSelector.close(); + if (sortGroupsSelector != null) sortGroupsSelector.close(); + if (classLevelRange != null) classLevelRange.setState(RangedValueWidget.State.CLOSED); + if (dungeonLevelRange != null) dungeonLevelRange.setState(RangedValueWidget.State.CLOSED); + + screen.partyFinderButton.active = true; + currentlyOpenedOption = null; + + for (int i = (handler.getRows() - 1) * 9; i < handler.getRows() * 9; i++) { + Slot slot = handler.slots.get(i); + if (slot.hasStack() && slot.getStack().isOf(Items.ARROW)) { + screen.partyButtonSlotId = slot.id; + } + } + return true; + } else { + screen.partyFinderButton.active = false; + + if (nameLowerCase.contains("floor")) { + updateDropdownOptionWidget(handler, floorSelector); + currentlyOpenedOption = floorSelector; + return true; + } else if (nameLowerCase.contains("select type")) { + updateDropdownOptionWidget(handler, dungeonTypeSelector); + currentlyOpenedOption = dungeonTypeSelector; + return true; + } else if (nameLowerCase.contains("class level range")) { + updateRangedValue(handler, classLevelRange); + return true; + } else if (nameLowerCase.contains("dungeon level range")) { + updateRangedValue(handler, dungeonLevelRange); + return true; + } else if (nameLowerCase.contains("sort")) { + updateDropdownOptionWidget(handler, sortGroupsSelector); + currentlyOpenedOption = sortGroupsSelector; + return true; + } + } + return false; + } + + private int findBackSlotId(GenericContainerScreenHandler handler) { + int backId = -1; + for (int i = (handler.getRows() - 1) * 9; i < handler.getRows() * 9; i++) { + Slot slot = handler.slots.get(i); + if (slot.hasStack() && slot.getStack().isOf(Items.ARROW)) { + backId = slot.id; + break; + } + } + return backId; + } + + /** + * @return true if all goes well + */ + private boolean setRangeFromTooltip(ItemStack stack, RangedValueWidget widget) { + for (Text text : stack.getTooltip(null, TooltipContext.BASIC)) { + String textLowerCase = text.getString().toLowerCase(); + if (textLowerCase.contains("selected:")) { + String[] split = text.getString().split(":"); + if (split.length < 2) return false; + String[] minAndMax = split[1].split("-"); + if (minAndMax.length < 2) return false; + //System.out.println(textLowerCase); + //System.out.println("Min and max: " + minAndMax[0] + " " + minAndMax[1]); + int leMin = -1; + int leMax = -1; + try {leMin = Integer.parseInt(minAndMax[0].trim());} catch (NumberFormatException ignored) {} + try {leMax = Integer.parseInt(minAndMax[1].trim());} catch (NumberFormatException ignored) {} + + widget.setMinAndMax(leMin, leMax); + return true; + } + } + return false; + } + /** + * @return true if all goes well + */ + private boolean setSelectedElementFromTooltip(Slot slot, ItemStack stack, OptionDropdownWidget dropdownWidget) { + for (Text text : stack.getTooltip(null, TooltipContext.BASIC)) { + String textLowerCase = text.getString().toLowerCase(); + if (textLowerCase.contains("selected:")) { + String[] split = text.getString().split(":"); + if (split.length < 2) return false; + String floorName = split[1].trim(); + dropdownWidget.setSelectedOption(dropdownWidget.new Option(floorName, stack, slot.id)); + return true; + } + } + return false; + } + + public boolean handleSign(SignBlockEntity sign, boolean front) { + if (!isInitialized) return false; + if (currentlyOpenedOption == classLevelRange) { + return updateValues(sign, front, classLevelRange); + } else if (currentlyOpenedOption == dungeonLevelRange) { + return updateValues(sign, front, dungeonLevelRange); + } + return false; + } + + private boolean updateValues(SignBlockEntity sign, boolean front, RangedValueWidget valueWidget) { + RangedValueWidget.State state; + String lowerCase = sign.getText(front).getMessage(3, false).getString().toLowerCase(); + if (lowerCase.contains("max")) { + state = RangedValueWidget.State.MODIFYING_MAX; + } else if (lowerCase.contains("min")) { + state = RangedValueWidget.State.MODIFYING_MIN; + } else return false; + valueWidget.setState(state); + this.setFocused(valueWidget); + return true; + } + + private void updateDropdownOptionWidget(GenericContainerScreenHandler handler, OptionDropdownWidget dropdownWidget) { + List entries = new ArrayList<>(); + for (Slot slot : handler.slots) { + if (slot.id > (handler.getRows() - 1) * 9) break; + if (slot.hasStack() && !slot.getStack().isOf(Items.BLACK_STAINED_GLASS_PANE)) { + entries.add(dropdownWidget.new Option(slot.getStack().getName().getString(), slot.getStack(), slot.id)); + } + } + int backId = findBackSlotId(handler); + dropdownWidget.open(entries, backId); + } + + private void updateRangedValue(GenericContainerScreenHandler handler, RangedValueWidget valueWidget) { + currentlyOpenedOption = valueWidget; + int min = -1; + int max = -1; + for (Slot slot : handler.slots) { + if (slot.id > (handler.getRows() - 1) * 9) break; + if (slot.hasStack() && slot.getStack().getName().getString().toLowerCase().contains("min")) { + min = slot.id; + } else if (slot.hasStack() && slot.getStack().getName().getString().toLowerCase().contains("max")) { + max = slot.id; + } + } + int backId = findBackSlotId(handler); + + valueWidget.setStateAndSlots(RangedValueWidget.State.OPEN, min, max, backId); + } + + public void setVisible(boolean visible) { + this.visible = visible; + if (floorSelector != null) this.floorSelector.visible = visible; + if (dungeonTypeSelector != null) this.dungeonTypeSelector.visible = visible; + if (classLevelRange != null) this.classLevelRange.visible = visible; + if (dungeonLevelRange != null) this.dungeonLevelRange.visible = visible; + if (sortGroupsSelector != null) this.sortGroupsSelector.visible = visible; + } + + public boolean canInteract(ContainerWidget widget) { + return currentlyOpenedOption == null || currentlyOpenedOption == widget; + } + + @Override + protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { + if (!visible) return; + for (ContainerWidget initializedWidget : initializedWidgets) { + initializedWidget.render(context, mouseX, mouseY, delta); + } + } + + @Override + public List children() { + return initializedWidgets; + } + + @Override + protected void appendClickableNarrations(NarrationMessageBuilder builder) {} +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/partyfinder/OptionDropdownWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/partyfinder/OptionDropdownWidget.java new file mode 100644 index 00000000..ff15464b --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/partyfinder/OptionDropdownWidget.java @@ -0,0 +1,203 @@ +package de.hysky.skyblocker.skyblock.dungeon.partyfinder; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.Element; +import net.minecraft.client.gui.Selectable; +import net.minecraft.client.gui.widget.ElementListWidget; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.item.ItemStack; +import net.minecraft.text.Style; +import net.minecraft.text.Text; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class OptionDropdownWidget extends ElementListWidget { + private final int slotId; + private int backButtonId = -1; + private final Text name; + private @Nullable Option selectedOption; + protected final PartyFinderScreen screen; + private boolean isOpen = false; + + private float animationProgress = 0f; + + public OptionDropdownWidget(PartyFinderScreen screen, Text name, @Nullable Option selectedOption, int x, int y, int width, int height, int slotId) { + super(screen.getClient(), width, height, y, 15); + this.screen = screen; + this.slotId = slotId; + setX(x); + setRenderBackground(false); + setRenderHeader(true, 25); + this.name = name; + this.selectedOption = selectedOption; + } + + @Override + protected boolean clickedHeader(int x, int y) { + if (!(x >= 0 && y >= 10 && x < getWidth() && y < 26)) return false; + if (screen.isWaitingForServer()) return false; + if (isOpen) { + if (backButtonId != -1) screen.clickAndWaitForServer(backButtonId); + } else { + screen.clickAndWaitForServer(slotId); + screen.partyFinderButton.active = false; + } + animationProgress = 0f; + return true; + } + + @Override + public int getRowLeft() { + return getX() + 2; + } + + @Override + protected int getScrollbarPositionX() { + return getRowLeft() + getRowWidth(); + } + + @Override + public int getRowWidth() { + return getWidth() - 6; + } + + public void setSelectedOption(@NotNull OptionDropdownWidget.Option entry) { + selectedOption = entry; + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (!screen.getSettingsContainer().canInteract(this)) return false; + if (isOpen && !isMouseOver(mouseX, mouseY) && backButtonId != -1) { + screen.clickAndWaitForServer(backButtonId); + return true; + } + return super.mouseClicked(mouseX, mouseY, button); + } + + @Override + public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { + if (screen.getSettingsContainer().canInteract(this)) return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY); + return false; + } + + @Override + protected void renderHeader(DrawContext context, int x, int y) { + context.drawText(MinecraftClient.getInstance().textRenderer, name, x, y+1, 0xFFD0D0D0, false); + int offset = 10; + context.fill(x-2, y + offset, x - 3 + getWidth(), y + 15 + offset, 0xFFF0F0F0); + context.fill(x-1, y+1+offset, x-3 + getWidth()-1, y + 14 + offset, 0xFF000000); + if (selectedOption != null) { + context.drawText(MinecraftClient.getInstance().textRenderer, selectedOption.message, x+2, y+3+offset, 0xFFFFFFFF, true); + } + else context.drawText(MinecraftClient.getInstance().textRenderer, "???", x+2, y+3+offset, 0xFFFFFFFF, true); + } + + @Override + public void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { + MatrixStack matrices = context.getMatrices(); + if (isOpen) { + matrices.push(); + matrices.translate(0, 0, 100); + } + if (animationProgress < 1) animationProgress += delta * 0.5f;else if (animationProgress != 1) animationProgress = 1; + if (PartyFinderScreen.DEBUG) { + context.drawText(MinecraftClient.getInstance().textRenderer, String.valueOf(slotId), getX(), getY() - 10, 0xFFFF0000, true); + context.drawText(MinecraftClient.getInstance().textRenderer, String.valueOf(backButtonId), getX() + 50, getY() - 10, 0xFFFF0000, true); + } + + int height1 = Math.min(getHeight(), getEntryCount()*itemHeight+4); + int idk = isOpen ? (int) (height1 * animationProgress) : (int) (height1 * (1 - animationProgress)); + context.fill(getX(), getY() + headerHeight, getX() + getWidth()-1, getY() + idk + headerHeight, 0xFFE0E0E0); + context.fill(getX()+1, getY()+headerHeight+1, getX() + getWidth()-2, getY() + idk + headerHeight - 1, 0xFF000000); + + super.renderWidget(context, mouseX, mouseY, delta); + if (isOpen) { + matrices.pop(); + } + } + + public void open(List