diff options
18 files changed, 1681 insertions, 2 deletions
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 @@ -156,6 +156,9 @@ public class SkyblockerConfig { public boolean acceptReparty = true; @SerialEntry + public boolean betterPartyFinder = true; + + @SerialEntry public boolean backpackPreviewWithoutShift = false; @SerialEntry 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 @@ -35,6 +35,13 @@ public class GeneralCategory { .controller(ConfigUtils::createBooleanController) .build()) .option(Option.<Boolean>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.<Boolean>createBuilder() .name(Text.translatable("text.autoconfig.skyblocker.option.general.backpackPreviewWithoutShift")) .binding(defaults.general.backpackPreviewWithoutShift, () -> config.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<ItemStack> 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<T extends ScreenHandler, U extends Screen> { + //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<T> 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<String, CompletableFuture<Optional<GameProfile>>> getUserCache() { + return null; + } + + @Invoker + public static CompletableFuture<Optional<GameProfile>> 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<ContainerWidget> 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<OptionDropdownWidget.Option> 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<? extends Element> 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<OptionDropdownWidget.Option> { + 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<Option> entries, int backButtonId) { + isOpen = true; + this.replaceEntries(entries); + animationProgress = 0f; + this.backButtonId = backButtonId; + } + + public void close() { + isOpen = false; + this.clearEntries(); + + } + + public class Option extends ElementListWidget.Entry<Option> { + + private final String message; + private final ItemStack icon; + private final int optionSlotId; + + public Option(@NotNull String message, @Nullable ItemStack icon, int slotId) { + + this.message = message; + this.icon = icon; + this.optionSlotId = slotId; + } + + + @Override + public List<? extends Selectable> selectableChildren() { + return List.of(); + } + + @Override + public List<? extends Element> children() { + return List.of(); + } + + @Override + public void render(DrawContext context, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { + /*if (hovered) { + context.fill(x, y, x + entryWidth, y + 13, 0xFFF0F0F0); + context.fill(x+1, y+1, x + entryWidth-1, y + 12, 0xFF000000); + } else context.fill(x, y, x + entryWidth, y + 13, 0xFF000000);*/ + MatrixStack matrices = context.getMatrices(); + matrices.push(); + int iconY = y + 1; + matrices.translate(x, iconY, 0); + matrices.scale(0.8f, 0.8f, 1f); + matrices.translate(-x, -iconY, 0); + context.drawItem(icon, x, iconY); + matrices.pop(); + if (PartyFinderScreen.DEBUG) context.drawText(MinecraftClient.getInstance().textRenderer, String.valueOf(optionSlotId), x+8, y, 0xFFFF0000, true); + context.drawText(MinecraftClient.getInstance().textRenderer, Text.literal(message).fillStyle(Style.EMPTY.withUnderline(hovered)), x + 14, y + 3, 0xFFFFFFFF, false); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Option that = (Option) o; + + return message.equals(that.message); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (screen.isWaitingForServer()) return false; + if (button == 0) { + screen.clickAndWaitForServer(this.optionSlotId); + setSelectedOption(this); + } + return true; + } + + @Override + public int hashCode() { + return message.hashCode(); + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/partyfinder/PartyEntry.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/partyfinder/PartyEntry.java new file mode 100644 index 00000000..35180ef3 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/partyfinder/PartyEntry.java @@ -0,0 +1,261 @@ +package de.hysky.skyblocker.skyblock.dungeon.partyfinder; + +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.mixin.accessor.SkullBlockEntityAccessor; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.Element; +import net.minecraft.client.gui.PlayerSkinDrawer; +import net.minecraft.client.gui.Selectable; +import net.minecraft.client.gui.widget.ElementListWidget; +import net.minecraft.client.util.DefaultSkinHelper; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.StringNbtReader; +import net.minecraft.screen.slot.SlotActionType; +import net.minecraft.text.Style; +import net.minecraft.text.Text; +import net.minecraft.text.TextColor; +import net.minecraft.util.Formatting; +import net.minecraft.util.Identifier; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class PartyEntry extends ElementListWidget.Entry<PartyEntry> { + + private static final Identifier PARTY_CARD_TEXTURE = new Identifier(SkyblockerMod.NAMESPACE, "textures/gui/party_card.png"); + private static final Identifier PARTY_CARD_TEXTURE_HOVER = new Identifier(SkyblockerMod.NAMESPACE, "textures/gui/party_card_hover.png"); + private final PartyFinderScreen screen; + private final int slotID; + Player partyLeader; + String floor = "???"; + String dungeon = "???"; + String note = ""; + NbtCompound floorSkullNBT = new NbtCompound(); + Identifier partyLeaderSkin = DefaultSkinHelper.getTexture(); + Player[] partyMembers = new Player[4]; + + int minClassLevel = -1; + int minCatacombsLevel = -1; + + public boolean isLocked() { + return isLocked; + } + + boolean isLocked = false; + Text lockReason = Text.empty(); + + + public PartyEntry(List<Text> tooltips, PartyFinderScreen screen, int slotID) { + this.screen = screen; + this.slotID = slotID; + + Arrays.fill(partyMembers, null); + if (tooltips.isEmpty()) return; + //System.out.println(tooltips); + + MinecraftClient client = MinecraftClient.getInstance(); + Text title = tooltips.get(0); + String partyHost = title.getString().split("'s")[0]; + + int membersIndex = -1; + for (int i = 1; i < tooltips.size(); i++) { + Text text = tooltips.get(i); + String tooltipText = text.getString(); + String lowerCase = tooltipText.toLowerCase(); + //System.out.println("TOOLTIP"+i); + //System.out.println(text.getSiblings()); + if (lowerCase.contains("members:") && membersIndex == -1) { + membersIndex = i + 1; + } else if (lowerCase.contains("class level")) { + Matcher matcher = Pattern.compile("\\d+$").matcher(lowerCase); + if (matcher.find()) minClassLevel = Integer.parseInt(matcher.group()); + } else if (lowerCase.contains("dungeon level")) { + Matcher matcher = Pattern.compile("\\d+$").matcher(lowerCase); + if (matcher.find()) minCatacombsLevel = Integer.parseInt(matcher.group()); + } else if (lowerCase.contains("floor:")) { + floor = tooltipText.split(":")[1].trim(); + if (dungeon.equals("???")) continue; + if (PartyFinderScreen.floorIconsMaster == null || PartyFinderScreen.floorIconsNormal == null) continue; + if (dungeon.contains("master")) { + try { + floorSkullNBT = StringNbtReader.parse(PartyEntryListWidget.BASE_SKULL_NBT.replace("%TEXTURE%", PartyFinderScreen.floorIconsMaster.getOrDefault(floor.toLowerCase(), ""))); + } catch (CommandSyntaxException e) { + throw new RuntimeException(e); + } + } else { + try { + floorSkullNBT = StringNbtReader.parse(PartyEntryListWidget.BASE_SKULL_NBT.replace("%TEXTURE%", PartyFinderScreen.floorIconsNormal.getOrDefault(floor.toLowerCase(), ""))); + } catch (CommandSyntaxException e) { + throw new RuntimeException(e); + } + } + + } else if (lowerCase.contains("dungeon:")) { + dungeon = tooltipText.split(":")[1].trim(); + } else if (!text.getSiblings().isEmpty() && Objects.equals(text.getSiblings().get(0).getStyle().getColor(), TextColor.fromRgb(Formatting.RED.getColorValue())) && !lowerCase.startsWith(" ")) { + isLocked = true; + lockReason = text; + } else if (lowerCase.contains("note:")) { + note = tooltipText.split(":")[1].trim(); + } + } + if (membersIndex != -1) { + for (int i = membersIndex, j = 0; i < membersIndex + 5; i++, j++) { + if (i >= tooltips.size()) continue; + + Text text = tooltips.get(i); + String memberText = text.getString(); + if (!memberText.startsWith(" ")) continue; // Member thingamajigs start with a space + + String[] parts = memberText.split(":", 2); + String playerNameTrim = parts[0].trim(); + + if (playerNameTrim.equals("Empty")) continue; // Don't care about these idiots lol + + List<Text> siblings = text.getSiblings(); + Style nameStyle = !siblings.isEmpty() ? siblings.get(Math.min(1, siblings.size() - 1)).getStyle() : text.getStyle(); + Text playerName = Text.literal(playerNameTrim).setStyle(nameStyle); + String className = parts[1].trim().split(" ")[0]; + int classLevel = -1; + Matcher matcher = Pattern.compile("\\((\\d+)\\)").matcher(parts[1]); + if (matcher.find()) classLevel = Integer.parseInt(matcher.group(1)); + Player player = new Player(playerName, className, classLevel); + + SkullBlockEntityAccessor.invokeFetchProfile(playerNameTrim).thenAccept( + (gameProfile) -> gameProfile.ifPresent(profile -> player.skinTexture = (client.getSkinProvider().getSkinTextures(profile).texture()))); + if (playerNameTrim.equals(partyHost)) { + partyLeader = player; + j--; + } else partyMembers[j] = player; + } + } + + SkullBlockEntityAccessor.invokeFetchProfile(partyHost).thenAccept( + (gameProfile) -> gameProfile.ifPresent(profile -> partyLeaderSkin = client.getSkinProvider().getSkinTextures(profile).texture())); + } + + @Override + public List<? extends Selectable> selectableChildren() { + return List.of(); + } + + @Override + public List<? extends Element> children() { + return List.of(); + } + + @Override + public void render(DrawContext context, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { + MatrixStack matrices = context.getMatrices(); + matrices.push(); + matrices.translate(x, y, 0); + + if (hovered && !isLocked) context.drawTexture(PARTY_CARD_TEXTURE_HOVER, 0, 0, 0, 0, 336, 64, 336, 64); + else context.drawTexture(PARTY_CARD_TEXTURE, 0, 0, 0, 0, 336, 64, 336, 64); + int mouseXLocal = mouseX - x; + int mouseYLocal = mouseY - y; + + TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; + context.drawText(textRenderer, this.partyLeader.toText(), 18, 6, 0xFFFFFFFF, true); + + if (PartyFinderScreen.DEBUG) { + context.drawText(textRenderer, String.valueOf(slotID), 166, 6, 0xFFFFFFFF, true); + if (hovered) { + context.drawText(textRenderer, "H", 160, 6, 0xFFFFFFFF, true); + } + } + PlayerSkinDrawer.draw(context, partyLeaderSkin, 6, 6, 8, true, false); + for (int i = 0; i < partyMembers.length; i++) { + Player partyMember = partyMembers[i]; + if (partyMember == null) continue; + context.drawText(textRenderer, partyMember.toText(), 17 + 136 * (i % 2), 24 + 14 * (i / 2), 0xFFFFFFFF, true); + PlayerSkinDrawer.draw(context, partyMember.skinTexture, 6 + 136 * (i % 2), 24 + 14 * (i / 2), 8, true, false); + } + if (minClassLevel > 0) { + context.drawText(textRenderer, "Class " + minClassLevel, 277, 25, 0xFFFFFF00, true); + if (!isLocked && hovered && mouseXLocal >= 276 && mouseXLocal <= 331 && mouseYLocal >= 22 && mouseYLocal <= 35) { + context.drawTooltip(textRenderer, Text.translatable("skyblocker.partyFinder.partyCard.minClassLevel", minClassLevel), mouseXLocal, mouseYLocal); + } + } + if (minCatacombsLevel > 0) { + context.drawText(textRenderer, "Cata " + minCatacombsLevel, 277, 43, 0xFFFFFF00, true); + if (!isLocked && hovered && mouseXLocal >= 276 && mouseXLocal <= 331 && mouseYLocal >= 40 && mouseYLocal <= 53) { + context.drawTooltip(textRenderer, Text.translatable("skyblocker.partyFinder.partyCard.minDungeonLevel", minCatacombsLevel), mouseXLocal, mouseYLocal); + } + } + ItemStack stack = new ItemStack(Items.PLAYER_HEAD); + stack.setNbt(floorSkullNBT); + context.drawItem(stack, 317, 3); + int textWidth = textRenderer.getWidth(floor); + context.drawText(textRenderer, floor, 316 - textWidth, 7, 0x70000000, false); + + context.drawText(textRenderer, note, 5, 51, 0xFFFFFFFF, true); + + if (isLocked) { + matrices.push(); + matrices.translate(0, 0, 100f); + context.fill(0, 0, entryWidth, entryHeight, 0x90000000); + context.drawText(textRenderer, lockReason, entryWidth/2 - textRenderer.getWidth(lockReason)/2, entryHeight/2 - textRenderer.fontHeight/2, 0xFFFFFF, true); + matrices.pop(); + } + + matrices.pop(); + + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + System.out.println("To be clicked" + slotID); + + if (button == 0 && !screen.isWaitingForServer()) { + screen.clickAndWaitForServer(slotID); + return true; + } + return super.mouseClicked(mouseX, mouseY, button); + } + + public static class Player { + public final Text name; + public final String dungeonClass; + public final int classLevel; + public Identifier skinTexture = DefaultSkinHelper.getTexture(); + + Player(Text name, String dungeonClass, int classLevel) { + this.name = name; + this.dungeonClass = dungeonClass; + this.classLevel = classLevel; + } + + public Text toText() { + char dClass = dungeonClass.isEmpty() ? '?' : dungeonClass.charAt(0); + return name.copy().append(Text.literal(" " + dClass + classLevel).setStyle(Style.EMPTY.withColor(Formatting.WHITE))); + } + } + + public static class NoParties extends PartyEntry { + + public NoParties() { + super(List.of(), null, -1); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + return false; + } + + @Override + public void render(DrawContext context, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { + TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; + context.drawCenteredTextWithShadow(textRenderer, Text.translatable("skyblocker.partyFinder.noParties"), x+entryWidth/2, y+entryHeight/2-textRenderer.fontHeight/2, 0xFFFFFFFF); + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/partyfinder/PartyEntryListWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/partyfinder/PartyEntryListWidget.java new file mode 100644 index 00000000..de430ae5 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/partyfinder/PartyEntryListWidget.java @@ -0,0 +1,99 @@ +package de.hysky.skyblocker.skyblock.dungeon.partyfinder; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.widget.ElementListWidget; +import net.minecraft.text.Text; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +public class PartyEntryListWidget extends ElementListWidget<PartyEntry> { + + protected List<PartyEntry> partyEntries; + + protected boolean isActive = true; + + private String search = ""; + + public static String BASE_SKULL_NBT = """ + { + "SkullOwner": { + "Id": [ + 1215241996, + -1849412511, + -1161255720, + -889217537 + ], + "Properties": { + "textures": [ + { + "Value": "%TEXTURE%" + } + ] + } + } + } + """; + public PartyEntryListWidget(MinecraftClient minecraftClient, int width, int height, int y, int itemHeight) { + super(minecraftClient, width, height, y, itemHeight); + } + + @Override + public int getRowWidth() { + return 336; + } + + public void setEntries(List<PartyEntry> partyEntries) { + this.partyEntries = partyEntries; + updateDisplay(); + } + + public void updateDisplay() { + List<PartyEntry> entries = new ArrayList<>(partyEntries); + entries.removeIf(partyEntry -> !partyEntry.note.contains(search)); + entries.sort(Comparator.comparing(PartyEntry::isLocked)); + if (entries.isEmpty() && !partyEntries.isEmpty()) { + entries.add(new PartyEntry.NoParties()); + } + replaceEntries(entries); + } + + public void setSearch(String s) { + search = s; + updateDisplay(); + } + + @Override + protected int getScrollbarPositionX() { + return this.width/2 + getRowWidth()/2 + 2; + } + + + public void setActive(boolean active) { + isActive = active; + } + + public boolean isActive() { + return isActive; + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (!visible) return false; + return super.mouseClicked(mouseX, mouseY, button); + } + + @Override + public void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { + //context.drawGuiTexture(BACKGROUND_TEXTURE, x, top-8, getRowWidth()+16+6, bottom-top+16); + + if (children().isEmpty()) { + Text string = Text.translatable("skyblocker.partyFinder.loadingError"); + TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; + context.drawTextWrapped(textRenderer, string, getRowLeft(), getY()+10, getRowWidth(), 0xFFFFFFFF); + } else super.renderWidget(context, mouseX, mouseY, delta); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/partyfinder/PartyFinderScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/partyfinder/PartyFinderScreen.java new file mode 100644 index 00000000..9273f32e --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/partyfinder/PartyFinderScreen.java @@ -0,0 +1,424 @@ +package de.hysky.skyblocker.skyblock.dungeon.partyfinder; + +import com.google.gson.JsonObject; +import de.hysky.skyblocker.SkyblockerMod; +import net.minecraft.block.entity.SignBlockEntity; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.Drawable; +import net.minecraft.client.gui.Element; +import net.minecraft.client.gui.Selectable; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.ingame.GenericContainerScreen; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.gui.widget.TextFieldWidget; +import net.minecraft.client.item.TooltipContext; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.client.toast.SystemToast; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.network.packet.c2s.play.UpdateSignC2SPacket; +import net.minecraft.screen.GenericContainerScreenHandler; +import net.minecraft.screen.ScreenHandler; +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.Formatting; +import net.minecraft.util.Identifier; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedReader; +import java.util.*; + +public class PartyFinderScreen extends Screen { + protected static final Logger LOGGER = LoggerFactory.getLogger(PartyFinderScreen.class); + protected static final Identifier SEARCH_ICON_TEXTURE = new Identifier("icon/search"); + protected static final Text SEARCH_TEXT = Text.translatable("gui.socialInteractions.search_hint").formatted(Formatting.ITALIC).formatted(Formatting.GRAY); + + public static boolean DEBUG = false; + public static final List<String> possibleInventoryNames = List.of( + "party finder", + "search settings", + "select floor", + "select type", + "class level range", + "dungeon level range", + "sort" + ); + + public GenericContainerScreenHandler getHandler() { + return handler; + } + + private Text name; + private GenericContainerScreenHandler handler; + private final PlayerInventory inventory; + private Page currentPage; + public PartyEntryListWidget partyEntryListWidget; + + public FinderSettingsContainer getSettingsContainer() { + return settingsContainer; + } + + private FinderSettingsContainer settingsContainer; + + private int refreshSlotId = -1; + + + private TextFieldWidget searchField; + private ButtonWidget refreshButton; + + protected ButtonWidget partyFinderButton; protected int partyButtonSlotId = -1; + private ButtonWidget settingsButton; private int settingsButtonSlotId = -1; + private ButtonWidget createPartyButton; private int createPartyButtonSlotId = -1; + + + + private boolean dirty = false; + private long dirtiedTime; + public boolean justOpenedSign = false; + + public void markDirty() { + if (justOpenedSign) return; + dirtiedTime = System.currentTimeMillis(); + dirty = true; + } + + private boolean waitingForServer = false; + + public static Map<String, String> floorIconsNormal = null; + public static Map<String, String> floorIconsMaster = null; + protected static final Identifier BACKGROUND_TEXTURE = new Identifier("social_interactions/background"); + + + + + + + public PartyFinderScreen(GenericContainerScreenHandler handler, PlayerInventory inventory, Text title) { + super(title); + this.handler = handler; + this.inventory = inventory; + name = title; + if (floorIconsNormal == null || floorIconsMaster == null) { + floorIconsNormal = new HashMap<>(); + floorIconsMaster = new HashMap<>(); + try (BufferedReader skullTextureReader = MinecraftClient.getInstance().getResourceManager().openAsReader(new Identifier(SkyblockerMod.NAMESPACE, "dungeons/catacombs/floorskulls.json"))) { + JsonObject json = SkyblockerMod.GSON.fromJson(skullTextureReader, JsonObject.class); + json.getAsJsonObject("normal").asMap().forEach((s, jsonElement) -> floorIconsNormal.put(s, jsonElement.getAsString())); + json.getAsJsonObject("master").asMap().forEach((s, jsonElement) -> floorIconsMaster.put(s, jsonElement.getAsString())); + LOGGER.debug("[Skyblocker] Dungeons floor skull textures json loaded"); + } catch (Exception e) { + LOGGER.error("[Skyblocker] Failed to load dungeons floor skull textures json"); + e.printStackTrace(); + } + } + } + + @Override + protected void init() { + super.init(); + int topRowButtonsHeight = 20; + + // Entry list widget, pretty much every position is based on this guy since it centers automagically + int widget_height = (int) (this.height * 0.8); + int entryListTopY = Math.max(43, (int) (height * 0.1)); + this.partyEntryListWidget = new PartyEntryListWidget(client, width, widget_height, entryListTopY, 68); + partyEntryListWidget.setRenderBackground(false); + + // Search field + this.searchField = new TextFieldWidget(textRenderer, partyEntryListWidget.getRowLeft()+12, entryListTopY - 12, partyEntryListWidget.getRowWidth()-12-6, 12, Text.literal("Search...")); + searchField.setPlaceholder(SEARCH_TEXT); + searchField.setChangedListener(s -> partyEntryListWidget.setSearch(s)); + // Refresh button + refreshButton = ButtonWidget.builder(Text.literal("⟳").setStyle(Style.EMPTY.withColor(Formatting.GREEN)), (a) -> { + if (refreshSlotId != -1) { + clickAndWaitForServer(refreshSlotId); + } + }) + .position(searchField.getX() + searchField.getWidth(), searchField.getY()) + .size(12, 12).build(); + refreshButton.active = false; + + // Settings container + if (this.settingsContainer == null) this.settingsContainer = new FinderSettingsContainer(partyEntryListWidget.getRowLeft(), entryListTopY-12, widget_height+12); + else settingsContainer.setDimensionsAndPosition(partyEntryListWidget.getRowWidth()-2, widget_height+12, partyEntryListWidget.getRowLeft(), entryListTopY-12); + + + // Buttons at the top + int searchButtonMargin = 2; + int searchButtonWidth = (partyEntryListWidget.getRowWidth() + 6) / 3 - 2*searchButtonMargin; + + + partyFinderButton = ButtonWidget.builder(Text.translatable("skyblocker.partyFinder.tabs.partyFinder"), (a) -> { + if (partyButtonSlotId != -1) { + setCurrentPage(Page.FINDER); + clickAndWaitForServer(partyButtonSlotId); + } + }) + .position(partyEntryListWidget.getRowLeft(), entryListTopY - 39) + .size(searchButtonWidth + searchButtonMargin, topRowButtonsHeight).build(); + + settingsButton = ButtonWidget.builder(Text.translatable("skyblocker.partyFinder.tabs.searchSettings"), (a) -> { + if (settingsButtonSlotId != -1) { + setCurrentPage(Page.SETTINGS); + clickAndWaitForServer(settingsButtonSlotId); + } + }) + .position(partyEntryListWidget.getRowLeft() + searchButtonWidth + 3*searchButtonMargin, entryListTopY - 39) + .size(searchButtonWidth, topRowButtonsHeight).build(); + + createPartyButton = ButtonWidget.builder(Text.translatable("skyblocker.partyFinder.tabs.createParty"), (a) -> { + if (createPartyButtonSlotId != -1) { + clickAndWaitForServer(createPartyButtonSlotId); + } + }) + .position(partyEntryListWidget.getRowLeft() + searchButtonWidth*2 + 5*searchButtonMargin, entryListTopY - 39) + .size(searchButtonWidth, topRowButtonsHeight).build(); + createPartyButton.active = false; + + + + addDrawableChild(partyEntryListWidget); + addDrawableChild(searchField); + addDrawableChild(refreshButton); + addDrawableChild(partyFinderButton); + addDrawableChild(settingsButton); + addDrawableChild(createPartyButton); + addDrawableChild(settingsContainer); + addDrawableChild(ButtonWidget.builder(Text.of("DEBUG"), (a) -> DEBUG = !DEBUG).dimensions(width-40, 0, 40, 20).build()); + + dirtiedTime = System.currentTimeMillis(); + + + // Used when resizing + setCurrentPage(currentPage); + + if (currentPage != Page.SIGN) update(); + } + + @Override + public boolean shouldPause() { + return false; + } + + @Override + protected <T extends Element & Drawable & Selectable> T addDrawableChild(T drawableElement) { + return super.addDrawableChild(drawableElement); + } + + @Override + public void render(DrawContext context, int mouseX, int mouseY, float delta) { + super.render(context, mouseX, mouseY, delta); + if (searchField.visible) { + context.drawGuiTexture(SEARCH_ICON_TEXTURE, partyEntryListWidget.getRowLeft()+1, searchField.getY()+1, 10, 10); + } + if (DEBUG) { + context.drawText(textRenderer, "Truly a party finder", 20, 20, 0xFFFFFFFF, true); + context.drawText(textRenderer, currentPage.toString(), 0, 0, 0xFFFFFFFF, true); + context.drawText(textRenderer, String.valueOf(refreshSlotId), width - 25, 30, 0xFFFFFFFF, true); + for (int i = 0; i < handler.slots.size(); i++) { + context.drawItem(handler.slots.get(i).getStack(), (i % 9) * 16, (i / 9) * 16); + } + } + if (isWaitingForServer()) { + String s = "Waiting for server..."; + context.drawText(textRenderer, s, this.width - textRenderer.getWidth(s) - 5, this.height-textRenderer.fontHeight-2, 0xFFFFFFFF, true); + } + if (!settingsContainer.canInteract(null)) { + context.fill(0, 0, width, height, 50, 0x40000000); + } + } + + @Override + public void renderBackground(DrawContext context, int mouseX, int mouseY, float delta) { + super.renderBackground(context, mouseX, mouseY, delta); + int i = partyEntryListWidget.getRowWidth() + 16 + 6; + context.drawGuiTexture(BACKGROUND_TEXTURE, partyEntryListWidget.getRowLeft() - 8, partyEntryListWidget.getY() - 12 - 8, i, partyEntryListWidget.getBottom() - partyEntryListWidget.getY() + 16 + 12); + } + + @Override + public void close() { + assert this.client != null; + assert this.client.player != null; + if (currentPage != Page.SIGN) + this.client.player.closeHandledScreen(); + else { + ClientPlayNetworkHandler networkHandler = this.client.getNetworkHandler(); + if (networkHandler != null && sign != null) { + List<String> originalText = Arrays.stream(sign.getText(signFront).getMessages(true)).map(Text::getString).toList(); + networkHandler.sendPacket(new UpdateSignC2SPacket(sign.getPos(), signFront, originalText.get(0), originalText.get(1), originalText.get(2), originalText.get(3))); + } + } + super.close(); + } + + public void setCurrentPage(Page page) { + this.currentPage = page; + if (page == Page.FINDER) { + partyEntryListWidget.visible = true; + + partyFinderButton.active = false; + partyFinderButton.setMessage(partyFinderButton.getMessage().copy().setStyle(Style.EMPTY.withUnderline(true))); + settingsButton.active = true; + settingsButton.setMessage(settingsButton.getMessage().copy().setStyle(Style.EMPTY.withUnderline(false))); + createPartyButton.active = true; + + searchField.active = true; + searchField.visible = true; + settingsContainer.setVisible(false); + refreshButton.visible = true; + } else if (page == Page.SETTINGS || page == Page.SIGN) { + partyEntryListWidget.visible = false; + + partyFinderButton.active = page != Page.SIGN; + partyFinderButton.setMessage(partyFinderButton.getMessage().copy().setStyle(Style.EMPTY.withUnderline(false))); + settingsButton.active = false; + settingsButton.setMessage(settingsButton.getMessage().copy().setStyle(Style.EMPTY.withUnderline(true))); + createPartyButton.active = false; + + searchField.active = false; + searchField.visible = false; + settingsContainer.setVisible(true); + refreshButton.visible = false; + } + } + + // Called when the handler object/title gets changed + public void updateHandler(GenericContainerScreenHandler handler, Text name) { + this.handler = handler; + this.name = name; + markDirty(); + } + + public boolean isSignFront() { + return signFront; + } + + public @Nullable SignBlockEntity getSign() { + return sign; + } + + private boolean signFront = true; + private @Nullable SignBlockEntity sign = null; + public void updateSign(SignBlockEntity sign, boolean front) { + setCurrentPage(Page.SIGN); + signFront = front; + this.sign = sign; + justOpenedSign = true; + waitingForServer = false; + if (!settingsContainer.handleSign(sign, front)) abort(); + } + + public void update() { + dirty = false; + waitingForServer = false; + String titleText = name.getString(); + if (titleText.contains("Party Finder")) { + updatePartyFinderPage(); + } else { + if (currentPage != Page.SETTINGS) setCurrentPage(Page.SETTINGS); + if (!this.settingsContainer.handle(this, titleText)) { + abort(); + } + } + + } + + private void updatePartyFinderPage() { + List<PartyEntry> parties = new ArrayList<>(); + if (currentPage != Page.FINDER) setCurrentPage(Page.FINDER); + if (handler.slots.stream().anyMatch(slot -> slot.hasStack() && slot.getStack().isOf(Items.BEDROCK))) { + parties.add(new PartyEntry.NoParties()); + }else { + for (Slot slot : handler.slots) { + if (slot.id > handler.getRows() * 9 || !slot.hasStack()) continue; + if (slot.getStack().isOf(Items.PLAYER_HEAD)) { + assert this.client != null; + parties.add(new PartyEntry(slot.getStack().getTooltip(this.client.player, TooltipContext.BASIC), this, slot.id)); + } else if (slot.getStack().isOf(Items.NETHER_STAR)) { + settingsButtonSlotId = slot.id; + if (DEBUG) + settingsButton.setMessage(settingsButton.getMessage().copy().append(Text.of(" " + settingsButtonSlotId))); + } + } + } + for (int i = (handler.getRows()-1) * 9; i < handler.getRows() * 9; i++) { + Slot slot = handler.slots.get(i); + int done = 0; + if (slot.hasStack() && slot.getStack().isOf(Items.EMERALD_BLOCK)) { + refreshSlotId = slot.id; + refreshButton.active = true; + done += 1; + } else if (slot.hasStack() && slot.getStack().isOf(Items.REDSTONE_BLOCK)) { + createPartyButtonSlotId = slot.id; + createPartyButton.active = true; + done += 1; + } + if (done == 2) break; + } + this.partyEntryListWidget.setEntries(parties); + List<ItemStack> temp = handler.slots.stream().map(Slot::getStack).toList(); + //for (int i = 0; i < temp.size(); i++) System.out.println(i + " " + temp.get(i).toString() + " " + temp.get(i).getName().getString()); + + } + + private boolean aborted = false; public boolean isAborted() {return aborted;} + public void abort() { + assert this.client != null; + if (currentPage == Page.SIGN) { + assert this.client.player != null; + this.client.player.openEditSignScreen(sign, signFront); + }else this.client.setScreen(new GenericContainerScreen(handler, inventory, title)); + this.client.getToastManager().add(new SystemToast(SystemToast.Type.PERIODIC_NOTIFICATION, Text.translatable("skyblocker.partyFinder.error.name"), Text.translatable("skyblocker.partyFinder.error.message"))); + aborted = true; + } + + @Override + public void removed() { + assert this.client != null; + if (this.client.player == null || aborted || currentPage == Page.SIGN) { + return; + } + ((ScreenHandler) this.handler).onClosed(this.client.player); + } + + @Override + public final void tick() { + super.tick(); + // Slight delay to make sure all slots are received, because they are most of the time sent one at a time + if (dirty && System.currentTimeMillis() - dirtiedTime > 60) update(); + assert this.client != null && this.client.player != null; + if (!this.client.player.isAlive() || this.client.player.isRemoved() && currentPage != Page.SIGN) { + this.client.player.closeHandledScreen(); + } + } + + public void clickAndWaitForServer(int slotID) { + //System.out.println("hey"); + assert client != null; + assert client.interactionManager != null; + client.interactionManager.clickSlot(handler.syncId, slotID, 0, SlotActionType.PICKUP, client.player); + waitingForServer = true; + } + + public boolean isWaitingForServer() { + return waitingForServer; + } + + public @NotNull MinecraftClient getClient() { + assert this.client != null; + return this.client; + } + + public enum Page { + FINDER, + SETTINGS, + SIGN + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/partyfinder/RangedValueWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/partyfinder/RangedValueWidget.java new file mode 100644 index 00000000..6e9f615c --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/partyfinder/RangedValueWidget.java @@ -0,0 +1,268 @@ +package de.hysky.skyblocker.skyblock.dungeon.partyfinder; + +import net.minecraft.block.entity.SignBlockEntity; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +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.ButtonWidget; +import net.minecraft.client.gui.widget.ContainerWidget; +import net.minecraft.client.gui.widget.TextFieldWidget; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.network.packet.c2s.play.UpdateSignC2SPacket; +import net.minecraft.text.Text; + +import java.util.List; +import java.util.Objects; + +public class RangedValueWidget extends ContainerWidget { + + private final PartyFinderScreen screen; + + private final int slotId; + private final Text name; + private int minSlotId = -1; + private int maxSlotId = -1; + private int backSlotId = -1; + + private int min = -1; + private int max = -1; + + private State state = State.CLOSED; + + private final ModifiedTextFieldWidget input; + private final ButtonWidget okButton; + + + public RangedValueWidget(PartyFinderScreen screen, Text name, int x, int y, int width, int slotId) { + super(x, y, width, 45, Text.empty()); + this.slotId = slotId; + this.screen = screen; + this.name = name; + + this.input = new ModifiedTextFieldWidget(MinecraftClient.getInstance().textRenderer, x, y+25, width-15, 15, Text.empty()); + this.input.setVisible(false); + this.input.setMaxLength(3); + input.setChangedListener(this::updateConfirmButton); + this.okButton = ButtonWidget.builder(Text.literal("✔"), (a) -> sendPacket()) + .dimensions(x+width-15, y+25, 15, 15) + .build(); + this.okButton.visible = false; + } + + @Override + public List<? extends Element> children() { + return List.of(this.input, this.okButton); + } + + public void updateConfirmButton(String string) { + try { + int i = Integer.parseInt(string.trim()); + if (i < 0 || i > 999) { // Too beeg or too smol + this.okButton.active = false; + this.input.setGood(false); + } + else if (state == State.MODIFYING_MIN && i > max) { // If editing min and bigger than max + this.okButton.active = false; + this.input.setGood(false); + } + else { // If editing max and smaller than min + boolean active1 = state != State.MODIFYING_MAX || i >= min; + this.okButton.active = active1; + this.input.setGood(active1); + } + } catch (NumberFormatException e) { + this.okButton.active = false; + this.input.setGood(false); + } + } + + @Override + protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { + context.drawText(MinecraftClient.getInstance().textRenderer, name, getX(), getY(), 0xFFD0D0D0, false); + int textOffset = 10; + MatrixStack matrices = context.getMatrices(); + if (!visible) return; + if (state != State.CLOSED) { + matrices.push(); + matrices.translate(0, 0, 100); + } + final TextRenderer textRenderer = screen.getClient().textRenderer; + if (PartyFinderScreen.DEBUG) { + context.drawText(textRenderer, String.valueOf(slotId), getX(), getY() - 10, 0xFFFF0000, true); + context.drawText(textRenderer, String.valueOf(minSlotId), getX() + 20, getY() - 10, 0xFFFF0000, true); + context.drawText(textRenderer, String.valueOf(maxSlotId), getX() + 40, getY() - 10, 0xFFFF0000, true); + context.drawText(textRenderer, String.valueOf(backSlotId), getX() + 60, getY() - 10, 0xFFFF0000, true); + } + this.input.render(context, mouseX, mouseY, delta); + this.okButton.render(context, mouseX, mouseY, delta); + if (Objects.requireNonNull(this.state) == State.CLOSED) { + context.fill(getX(), getY() + textOffset, getX() + width, getY() + 15 + textOffset, 0xFFFFFFFF); + context.fill(getX() + 1, getY() + 1 + textOffset, getX() + width - 1, getY() + 14 + textOffset, 0xFF000000); + context.drawText(textRenderer, min + " - " + max, getX() + 3, getY() + 3 + textOffset, 0xFFFFFFFF, false); + } else { + context.fill(getX(), getY() + textOffset, getX() + width, getY() + 15 + textOffset, 0xFFFFFFFF); + context.fill(getX() + 1, getY() + 1 + textOffset, getX() + width - 1, getY() + 14 + textOffset, 0xFF000000); + context.drawCenteredTextWithShadow(textRenderer, "-", getX() + (width >> 1), getY() + 3 + textOffset, 0xFFFFFFFF); + int selectedColor = 0xFFFFFF00; + int unselectedColor = 0xFFD0D0D0; + + boolean mouseOverMin = mouseOverMinButton(mouseX, mouseY); + boolean mouseOverMax = mouseOverMaxButton(mouseX, mouseY); + + // Minimum + int minStartX = getX() + 1; + int minEndX = getX() + (width >> 1) - 6; + context.fill(minStartX, getY() + 1 + textOffset, minEndX, getY() + 14 + textOffset, state == State.MODIFYING_MIN ? selectedColor: (mouseOverMin ? 0xFFFFFFFF: unselectedColor)); + context.fill(minStartX + 1, getY() + 2 + textOffset, minEndX - 1, getY() + 13 + textOffset, 0xFF000000); + + context.drawCenteredTextWithShadow(textRenderer, String.valueOf(min), (minStartX + minEndX) >> 1, getY() + 3 + textOffset, 0xFFFFFFFF); + + // Maximum + int maxStartX = getX() + (width >> 1) + 5; + int maxEndX = getX() + width - 1; + context.fill(maxStartX, getY() + 1 + textOffset, maxEndX, getY() + 14 + textOffset, state == State.MODIFYING_MAX ? selectedColor: (mouseOverMax ? 0xFFFFFFFF: unselectedColor)); + context.fill(maxStartX + 1, getY() + 2 + textOffset, maxEndX - 1, getY() + 13 + textOffset, 0xFF000000); + + context.drawCenteredTextWithShadow(textRenderer, String.valueOf(max), (maxStartX + maxEndX) >> 1, getY() + 3 + textOffset, 0xFFFFFFFF); + } + if (state != State.CLOSED) { + matrices.pop(); + } + } + + private boolean mouseOverMinButton(int mouseX, int mouseY) {return isMouseOver(mouseX, mouseY) && mouseX < getX() + (width >> 1) - 5 && mouseY < getY() + 25 && mouseY > getY() + 10;} + private boolean mouseOverMaxButton(int mouseX, int mouseY) {return isMouseOver(mouseX, mouseY) && mouseX > getX() + (width >> 1) + 5 && mouseY < getY() + 25 && mouseY > getY() + 10;} + + public void setState(State state) { + this.state = state; + switch (state) { + case CLOSED, OPEN -> { + this.input.setVisible(false); + this.input.setFocused(false); + this.okButton.visible = false; + this.okButton.setFocused(false); + } + case MODIFYING_MAX, MODIFYING_MIN -> { + this.input.setVisible(true); + this.input.setFocused(true); + this.input.setText(""); + this.input.setCursor(0, false); + this.okButton.visible = true; + } + } + } + + public void setStateAndSlots(State state, int minSlotId, int maxSlotId, int backSlotId) { + setState(state); + this.minSlotId = minSlotId; + this.maxSlotId = maxSlotId; + this.backSlotId = backSlotId; + } + + public void setMinAndMax(int min, int max) { + this.min = min; + this.max = max; + } + + private void sendPacket() { + SignBlockEntity sign = screen.getSign(); + String inputTrimmed = input.getText().trim(); + if (state == State.MODIFYING_MIN) { + try {min = Integer.parseInt(inputTrimmed);} catch (NumberFormatException ignored) {} + } else if (state == State.MODIFYING_MAX) { + try {max = Integer.parseInt(inputTrimmed);} catch (NumberFormatException ignored) {} + } + if (sign != null) { + Text[] messages = sign.getText(screen.isSignFront()).getMessages(screen.getClient().shouldFilterText()); + screen.getClient().player.networkHandler.sendPacket(new UpdateSignC2SPacket(sign.getPos(), screen.isSignFront(), + inputTrimmed, + messages[1].getString(), + messages[2].getString(), + messages[3].getString() + )); + } + screen.justOpenedSign = false; + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (screen.isWaitingForServer() || !screen.getSettingsContainer().canInteract(this)) return false; + if (!visible) return false; + if (!isMouseOver(mouseX, mouseY)) { + if (state == State.OPEN && backSlotId != -1) { + screen.clickAndWaitForServer(backSlotId); + return true; + } else return false; + } + switch (state) { + case CLOSED -> { + if (mouseY > getY() + 25) return false; + screen.clickAndWaitForServer(slotId); + return true; + } + case OPEN -> { + + if (mouseOverMinButton((int) mouseX, (int) mouseY)) { + if (minSlotId == -1) return false; + screen.clickAndWaitForServer(minSlotId); + } else if (mouseOverMaxButton((int) mouseX, (int) mouseY)) { + if (maxSlotId == -1) return false; + screen.clickAndWaitForServer(maxSlotId); + } else return !(mouseY > getY() + 25); + return true; + } + default -> { + return super.mouseClicked(mouseX, mouseY, button); + } + } + } + + @Override + public void setX(int x) { + super.setX(x); + this.input.setX(getX()); + this.okButton.setX(getX()+getWidth()-15); + } + + @Override + public void setY(int y) { + super.setY(y); + this.input.setY(getY()+25); + this.okButton.setY(getY()+ 25); + } + + public enum State { + CLOSED, + OPEN, + MODIFYING_MIN, + MODIFYING_MAX; + } + protected class ModifiedTextFieldWidget extends TextFieldWidget { + + + private boolean isGood = false; + + public ModifiedTextFieldWidget(TextRenderer textRenderer, int x, int y, int width, int height, Text text) { + super(textRenderer, x, y, width, height, text); + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (!this.isNarratable() || !this.isFocused()) return false; + if (keyCode == 257 && isGood) { + sendPacket(); + return true; + } + return super.keyPressed(keyCode, scanCode, modifiers); + } + + public void setGood(boolean good) { + isGood = good; + } + + } + @Override + protected void appendClickableNarrations(NarrationMessageBuilder builder) {} +} diff --git a/src/main/resources/assets/skyblocker/dungeons/catacombs/floorskulls.json b/src/main/resources/assets/skyblocker/dungeons/catacombs/floorskulls.json new file mode 100644 index 00000000..4bb52026 --- /dev/null +++ b/src/main/resources/assets/skyblocker/dungeons/catacombs/floorskulls.json @@ -0,0 +1,22 @@ +{ + "normal": { + "entrance": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNWEyZjY3NTAwYTY1ZjNjZTc5ZDM0ZWMxNTBkZTkzZGY4ZjYwZWJlNTJlMjQ4ZjVlMWNkYjY5YjA3MjYyNTZmNyJ9fX0K", + "floor i": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNTcyMDkxN2NkYTA1Njc0NDI2MTdmMjcyMWU4OGJlOWQyZmZiYjBiMjZhM2Y0YzJmZTIxNjU1ODE0ZDRmNDQ3NiJ9fX0K", + "floor ii": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNTcyMDkxN2NkYTA1Njc0NDI2MTdmMjcyMWU4OGJlOWQyZmZiYjBiMjZhM2Y0YzJmZTIxNjU1ODE0ZDRmNDQ3NiJ9fX0K", + "floor iii": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvM2NlNjlkMmRkY2M4MWM5ZmMyZTk5NDhjOTIwMDNlYjBmN2ViZjBlN2U5NTJlODAxYjdmMjA2OWRjZWU3NmQ4NSJ9fX0K", + "floor iv": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzY5NjVlM2ZkNjE5ZGU2YjBhN2NlMTY3MzA3MjUyMGE5MzYwMzc4ZTFjYjhjMTlkNGJhZjBjODY3NjlkMzc2NCJ9fX0K", + "floor v": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMzNhMDI0NmQ1MTM1MGI3NzBkZTI3ZjFhMTkyNWIxMWNlY2U1N2IzYzliMjA5ZTJiNmJmMjc1ZDMyZDAxNTRjMyJ9fX0K", + "floor vi": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYTFhODFmOWIwYTEzNzU2ODFhZTdkOWRmNjVlNGQzNzIzYjFmMGZhNzQ0M2U3NmRkM2JjNjNkZDM4NjRmZjM0MyJ9fX0K", + "floor vii": "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNmE0MDY3NWQzMzNhZGNjYTc1YjM1YjZiYTI5YjRmZDdlNWVhZWI5OTgzOTUyOTUzODViNDk0MTI4NzE1ZGFiMyJ9fX0K" + }, + "master": { + "entrance": "ewogICJ0aW1lc3RhbXAiIDogMTYxNzExMDU1MDEyNSwKICAicHJvZmlsZUlkIiA6ICI5ZDQyNWFiOGFmZjg0MGU1OWM3NzUzZjc5Mjg5YjMyZSIsCiAgInByb2ZpbGVOYW1lIiA6ICJUb21wa2luNDIiLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMWViNWIyMWFmMzMwYWYxMjJiMjY4YjdhYTM5MDczM2JkMWI2OTliNGQwOTIzMjMzZWNkMjRmODFlMDhiOWJjZSIsCiAgICAgICJtZXRhZGF0YSIgOiB7CiAgICAgICAgIm1vZGVsIiA6ICJzbGltIgogICAgICB9CiAgICB9CiAgfQp9", + "floor i": "ewogICJ0aW1lc3RhbXAiIDogMTYxNzExMDU1MDEyNSwKICAicHJvZmlsZUlkIiA6ICI5ZDQyNWFiOGFmZjg0MGU1OWM3NzUzZjc5Mjg5YjMyZSIsCiAgInByb2ZpbGVOYW1lIiA6ICJUb21wa2luNDIiLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMWViNWIyMWFmMzMwYWYxMjJiMjY4YjdhYTM5MDczM2JkMWI2OTliNGQwOTIzMjMzZWNkMjRmODFlMDhiOWJjZSIsCiAgICAgICJtZXRhZGF0YSIgOiB7CiAgICAgICAgIm1vZGVsIiA6ICJzbGltIgogICAgICB9CiAgICB9CiAgfQp9", + "floor ii": "ewogICJ0aW1lc3RhbXAiIDogMTYxNzExMDcxNzIxMywKICAicHJvZmlsZUlkIiA6ICI5MzZmMTA3MTEzOGM0YjMyYTg0OGY2NmE5Nzc2NDJhMiIsCiAgInByb2ZpbGVOYW1lIiA6ICIwMDAwMDAwMDAwMDAwMDB4IiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzMyMjkyZTRlMGZhNjI2NjcyNTZlZjhkYTBmMDE5ODJhOTk2NDk5ZjRkNWQ4OTRiZDA1OGMzZTZmM2QyZmIyZDkiCiAgICB9CiAgfQp9", + "floor iii": "ewogICJ0aW1lc3RhbXAiIDogMTYxNzExMDczNDE2NiwKICAicHJvZmlsZUlkIiA6ICJjMGYzYjI3YTUwMDE0YzVhYjIxZDc5ZGRlMTAxZGZlMiIsCiAgInByb2ZpbGVOYW1lIiA6ICJDVUNGTDEzIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2M5NjlmNmIxNDg2NDhhYThkMDI3MjI4YTUyZmI1YTNjYTFlZTg0ZGM3NmU0Nzg1MWYxNGRiMDI5YTczMGE4YTMiCiAgICB9CiAgfQp9", + "floor iv": "ewogICJ0aW1lc3RhbXAiIDogMTYxNzExMDc1MDAxMiwKICAicHJvZmlsZUlkIiA6ICI1N2IzZGZiNWY4YTY0OWUyOGI1NDRlNGZmYzYzMjU2ZiIsCiAgInByb2ZpbGVOYW1lIiA6ICJYaWthcm8iLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDdiNjkwMjFmOWMwOTY0N2RmZDliMzRkZjNkZWFmZjcwY2ZjNzQwZjZhMjZmNjEyZGQ0NzUwM2ZjMzRjOTdmMCIKICAgIH0KICB9Cn0", + "floor v": "ewogICJ0aW1lc3RhbXAiIDogMTYxNzExMDc2NTU5OCwKICAicHJvZmlsZUlkIiA6ICI3NzI3ZDM1NjY5Zjk0MTUxODAyM2Q2MmM2ODE3NTkxOCIsCiAgInByb2ZpbGVOYW1lIiA6ICJsaWJyYXJ5ZnJlYWsiLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDY1Y2JjZTQwZTYwZTdhNTlhODdmYThmNGVjYjZjY2ZjMTUxNDMzOGMyNjI2MTRiZjMzNzM5YTYyNjNmNTQwNSIKICAgIH0KICB9Cn0", + "floor vi": "ewogICJ0aW1lc3RhbXAiIDogMTYxNzExMDc4MTM4OCwKICAicHJvZmlsZUlkIiA6ICJmZDYwZjM2ZjU4NjE0ZjEyYjNjZDQ3YzJkODU1Mjk5YSIsCiAgInByb2ZpbGVOYW1lIiA6ICJSZWFkIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2FjM2Q0NWZmNjcyOTg4YjQ2MzU4MTM3YmUzMmMzYzlhZGZkOWE2MjkxOTVjMmI2NDgxNGE5MzQ5OWRkNjYwNiIKICAgIH0KICB9Cn0", + "floor vii": "ewogICJ0aW1lc3RhbXAiIDogMTYxNzExMDc5MzYwNSwKICAicHJvZmlsZUlkIiA6ICIxZDUyMzNkMzg4NjI0YmFmYjAwZTMxNTBhN2FhM2E4OSIsCiAgInByb2ZpbGVOYW1lIiA6ICIwMDAwMDAwMDAwMDAwMDBKIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2YwYzY4N2EyMDI2YjY5MzQ0YjVhZGRiYmVmYTM4MWQxNTRkNmJjODBjOGMwMTc0NTMxMWJmYmJmOGJiZjAwOWMiCiAgICB9CiAgfQp9" + } +}
\ No newline at end of file diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index d6f09de9..e9ce9ffc 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -127,9 +127,10 @@ "text.autoconfig.skyblocker.option.general.flameOverlay": "Flame Overlay", "text.autoconfig.skyblocker.option.general.flameOverlay.flameHeight": "Flame Height", "text.autoconfig.skyblocker.option.general.flameOverlay.flameOpacity": "Flame Opacity", + "text.autoconfig.skyblocker.option.general.betterPartyFinder": "Better Party Finder", "skyblocker.itemTooltip.nullMessage": "§cItem price information on tooltip will renew in max 60 seconds. If not, check latest.log", "skyblocker.itemTooltip.noData": "§cNo Data", - + "skyblocker.wikiLookup.noArticleFound": "§rUnable to locate a wiki article for this item...", "text.autoconfig.skyblocker.category.richPresence": "Discord Rich Presence", @@ -420,5 +421,15 @@ "skyblocker.tips.fairySoulsEnigmaSoulsRelics": "Don't know where to find Fairy Souls, Enigma Souls, or Relics? Enable the helpers to aid your exploration, they'll remember which souls you've already found.", "skyblocker.tips.quickNav": "You can customize the QuickNav buttons in the config.", + "skyblocker.partyFinder.tabs.partyFinder": "Party Finder", + "skyblocker.partyFinder.tabs.searchSettings": "Search Filters", + "skyblocker.partyFinder.tabs.createParty": "Create Party", + "skyblocker.partyFinder.partyCard.minClassLevel": "Min class level required: %d", + "skyblocker.partyFinder.partyCard.minDungeonLevel": "Min dungeon level required: %d", + "skyblocker.partyFinder.noParties": "No parties found. Big sad :(", + "skyblocker.partyFinder.error.name" : "Party Finder error!", + "skyblocker.partyFinder.error.message": "An error occurred so you've been sent to the vanilla party finder", + "skyblocker.partyFinder.loadingError": "If you see this for more than 5 seconds, something probably went wrong...", + "emi.category.skyblocker.skyblock": "Skyblock" }
\ No newline at end of file diff --git a/src/main/resources/assets/skyblocker/textures/gui/party_card.png b/src/main/resources/assets/skyblocker/textures/gui/party_card.png Binary files differnew file mode 100644 index 00000000..191200c8 --- /dev/null +++ b/src/main/resources/assets/skyblocker/textures/gui/party_card.png diff --git a/src/main/resources/assets/skyblocker/textures/gui/party_card_hover.png b/src/main/resources/assets/skyblocker/textures/gui/party_card_hover.png Binary files differnew file mode 100644 index 00000000..05aace37 --- /dev/null +++ b/src/main/resources/assets/skyblocker/textures/gui/party_card_hover.png diff --git a/src/main/resources/skyblocker.accesswidener b/src/main/resources/skyblocker.accesswidener index 16130ceb..156ced34 100644 --- a/src/main/resources/skyblocker.accesswidener +++ b/src/main/resources/skyblocker.accesswidener @@ -1,2 +1,3 @@ accessWidener v2 named +extendable method net/minecraft/client/gui/widget/ScrollableWidget drawScrollbar (Lnet/minecraft/client/gui/DrawContext;)V diff --git a/src/main/resources/skyblocker.mixins.json b/src/main/resources/skyblocker.mixins.json index 22480cc7..d56f6317 100644 --- a/src/main/resources/skyblocker.mixins.json +++ b/src/main/resources/skyblocker.mixins.json @@ -16,6 +16,7 @@ "FarmlandBlockMixin", "GenericContainerScreenHandlerMixin", "HandledScreenMixin", + "HandledScreensMixin", "InGameHudMixin", "InGameOverlayRendererMixin", "InventoryScreenMixin", @@ -33,7 +34,6 @@ "YggdrasilMinecraftSessionServiceMixin", "YggdrasilServicesKeyInfoMixin", "accessor.BeaconBlockEntityRendererInvoker", - "accessor.DrawContextInvoker", "accessor.FrustumInvoker", "accessor.HandledScreenAccessor", "accessor.ItemStackAccessor", @@ -41,6 +41,9 @@ "accessor.RecipeBookWidgetAccessor", "accessor.ScreenAccessor", "accessor.WorldRendererAccessor", + "accessor.SkullBlockEntityAccessor", + "accessor.DrawContextInvoker", + "accessor.WorldRendererAccessor", "discordipc.ConnectionMixin" ], "injectors": { |