diff options
author | Kevin <92656833+kevinthegreat1@users.noreply.github.com> | 2024-02-14 14:56:55 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-14 14:56:55 -0500 |
commit | 41d02daf5f53a761043ae76f5be484f94ceb0128 (patch) | |
tree | e5c5028c11c135c187477ece01ef3b3c3d809e47 /src/main/java/de | |
parent | fd77289b711923ff23c31cc6a26f85fb96e68d4f (diff) | |
parent | cffa68314fc74fbedd97708df5ccb7c098eb638f (diff) | |
download | Skyblocker-41d02daf5f53a761043ae76f5be484f94ceb0128.tar.gz Skyblocker-41d02daf5f53a761043ae76f5be484f94ceb0128.tar.bz2 Skyblocker-41d02daf5f53a761043ae76f5be484f94ceb0128.zip |
Merge pull request #537 from olim88/Search-overlays
Search overlays for bz and ah
Diffstat (limited to 'src/main/java/de')
7 files changed, 663 insertions, 28 deletions
diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java index a438419b..71623ea7 100644 --- a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java +++ b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java @@ -24,6 +24,7 @@ import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip; import de.hysky.skyblocker.skyblock.itemlist.ItemRepository; import de.hysky.skyblocker.skyblock.quicknav.QuickNav; import de.hysky.skyblocker.skyblock.rift.TheRift; +import de.hysky.skyblocker.skyblock.searchOverlay.SearchOverManager; import de.hysky.skyblocker.skyblock.shortcut.Shortcuts; import de.hysky.skyblocker.skyblock.special.SpecialEffects; import de.hysky.skyblocker.skyblock.tabhud.TabHud; @@ -120,6 +121,7 @@ public class SkyblockerMod implements ClientModInitializer { FireFreezeStaffTimer.init(); GuardianHealth.init(); TheRift.init(); + SearchOverManager.init(); TitleContainer.init(); ScreenMaster.init(); DungeonTextures.init(); diff --git a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java index 6411d6e4..175c3bdf 100644 --- a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java @@ -241,6 +241,9 @@ public class SkyblockerConfig { public FlameOverlay flameOverlay = new FlameOverlay(); @SerialEntry + public SearchOverlay searchOverlay = new SearchOverlay(); + + @SerialEntry public List<Integer> lockedSlots = new ArrayList<>(); @SerialEntry @@ -415,6 +418,31 @@ public class SkyblockerConfig { public Alignment alignment = Alignment.MIDDLE; } + public enum Direction { + HORIZONTAL, VERTICAL; + + @Override + public String toString() { + return switch (this) { + case HORIZONTAL -> "Horizontal"; + case VERTICAL -> "Vertical"; + }; + } + } + + public enum Alignment { + LEFT, RIGHT, MIDDLE; + + @Override + public String toString() { + return switch (this) { + case LEFT -> "Left"; + case RIGHT -> "Right"; + case MIDDLE -> "Middle"; + }; + } + } + public static class TeleportOverlay { @SerialEntry public boolean enableTeleportOverlays = true; @@ -443,29 +471,30 @@ public class SkyblockerConfig { public float flameOpacity = 0f; } - public enum Direction { - HORIZONTAL, VERTICAL; + public static class SearchOverlay { + @SerialEntry + public boolean enableBazaar = true; - @Override - public String toString() { - return switch (this) { - case HORIZONTAL -> "Horizontal"; - case VERTICAL -> "Vertical"; - }; - } - } + @SerialEntry + public boolean enableAuctionHouse = true; - public enum Alignment { - LEFT, RIGHT, MIDDLE; + @SerialEntry + public boolean keepPreviousSearches = false; - @Override - public String toString() { - return switch (this) { - case LEFT -> "Left"; - case RIGHT -> "Right"; - case MIDDLE -> "Middle"; - }; - } + @SerialEntry + public int maxSuggestions = 3; + + @SerialEntry + public int historyLength = 3; + + @SerialEntry + public boolean enableCommands = false; + + @SerialEntry + public List<String> bazaarHistory = new ArrayList<>(); + + @SerialEntry + public List<String> auctionHistory = new ArrayList<>(); } public static class RichPresence { 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 bb2fdee5..bb333f79 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java @@ -649,6 +649,60 @@ public class GeneralCategory { .controller(opt -> FloatSliderControllerBuilder.create(opt).range(0.0f, 0.8f).step(0.1f)) .build()) .build()) + + //Search overlay + .group(OptionGroup.createBuilder() + .name(Text.translatable("text.autoconfig.skyblocker.option.general.searchOverlay")) + .collapsed(true) + .option(Option.<Boolean>createBuilder() + .name(Text.translatable("text.autoconfig.skyblocker.option.general.searchOverlay.enableBazaar")) + .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.general.searchOverlay.enableBazaar.@Tooltip"))) + .binding(defaults.general.searchOverlay.enableBazaar, + () -> config.general.searchOverlay.enableBazaar, + newValue -> config.general.searchOverlay.enableBazaar = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.<Boolean>createBuilder() + .name(Text.translatable("text.autoconfig.skyblocker.option.general.searchOverlay.enableAuctionHouse")) + .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.general.searchOverlay.enableAuctionHouse.@Tooltip"))) + .binding(defaults.general.searchOverlay.enableAuctionHouse, + () -> config.general.searchOverlay.enableAuctionHouse, + newValue -> config.general.searchOverlay.enableAuctionHouse = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.<Boolean>createBuilder() + .name(Text.translatable("text.autoconfig.skyblocker.option.general.searchOverlay.keepPreviousSearches")) + .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.general.searchOverlay.keepPreviousSearches.@Tooltip"))) + .binding(defaults.general.searchOverlay.keepPreviousSearches, + () -> config.general.searchOverlay.keepPreviousSearches, + newValue -> config.general.searchOverlay.keepPreviousSearches = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.<Integer>createBuilder() + .name(Text.translatable("text.autoconfig.skyblocker.option.general.searchOverlay.maxSuggestions")) + .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.general.searchOverlay.maxSuggestions.@Tooltip"))) + .binding(defaults.general.searchOverlay.maxSuggestions, + () -> config.general.searchOverlay.maxSuggestions, + newValue -> config.general.searchOverlay.maxSuggestions = newValue) + .controller(opt -> IntegerSliderControllerBuilder.create(opt).range(0, 5).step(1)) + .build()) + .option(Option.<Integer>createBuilder() + .name(Text.translatable("text.autoconfig.skyblocker.option.general.searchOverlay.historyLength")) + .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.general.searchOverlay.historyLength.@Tooltip"))) + .binding(defaults.general.searchOverlay.historyLength, + () -> config.general.searchOverlay.historyLength, + newValue -> config.general.searchOverlay.historyLength = newValue) + .controller(opt -> IntegerSliderControllerBuilder.create(opt).range(0, 5).step(1)) + .build()) + .option(Option.<Boolean>createBuilder() + .name(Text.translatable("text.autoconfig.skyblocker.option.general.searchOverlay.enableCommands")) + .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.general.searchOverlay.enableCommands.@Tooltip"))) + .binding(defaults.general.searchOverlay.enableCommands, + () -> config.general.searchOverlay.enableCommands, + newValue -> config.general.searchOverlay.enableCommands = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .build()) .build(); } } diff --git a/src/main/java/de/hysky/skyblocker/mixin/ClientPlayerEntityMixin.java b/src/main/java/de/hysky/skyblocker/mixin/ClientPlayerEntityMixin.java index 1d54b02c..2a4c38a7 100644 --- a/src/main/java/de/hysky/skyblocker/mixin/ClientPlayerEntityMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixin/ClientPlayerEntityMixin.java @@ -1,18 +1,20 @@ package de.hysky.skyblocker.mixin; import com.mojang.authlib.GameProfile; - import de.hysky.skyblocker.config.SkyblockerConfigManager; 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.skyblock.searchOverlay.OverlayScreen; +import de.hysky.skyblocker.skyblock.searchOverlay.SearchOverManager; 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 net.minecraft.text.Text; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -48,12 +50,28 @@ public abstract class ClientPlayerEntityMixin extends AbstractClientPlayerEntity } @Inject(method = "openEditSignScreen", at = @At("HEAD"), cancellable = true) - public void skyblocker$partyFinderRange(SignBlockEntity sign, boolean front, CallbackInfo callbackInfo) { - if (PartyFinderScreen.isInKuudraPartyFinder) return; - 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(); + public void skyblocker$redirectEditSignScreen(SignBlockEntity sign, boolean front, CallbackInfo callbackInfo) { + // Fancy Party Finder + if (!PartyFinderScreen.isInKuudraPartyFinder && client.currentScreen instanceof PartyFinderScreen partyFinderScreen && !partyFinderScreen.isAborted() && sign.getText(front).getMessage(3, false).getString().toLowerCase().contains("level")) { + partyFinderScreen.updateSign(sign, front); + callbackInfo.cancel(); + return; + } + + // Search Overlay + if (client.currentScreen != null) { + if (SkyblockerConfigManager.get().general.searchOverlay.enableAuctionHouse && client.currentScreen.getTitle().getString().toLowerCase().contains("auction")) { + if (sign.getText(front).getMessage(3, false).getString().equalsIgnoreCase("enter query")) { + SearchOverManager.updateSign(sign, front, true); + client.setScreen(new OverlayScreen(Text.of(""))); + callbackInfo.cancel(); + } + } else if (SkyblockerConfigManager.get().general.searchOverlay.enableBazaar && client.currentScreen.getTitle().getString().toLowerCase().contains("bazaar")) { + if (sign.getText(front).getMessage(3, false).getString().equalsIgnoreCase("enter query")) { + SearchOverManager.updateSign(sign, front, false); + client.setScreen(new OverlayScreen(Text.of(""))); + callbackInfo.cancel(); + } } } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java index 9b83f276..fc5c7087 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java @@ -18,7 +18,7 @@ public enum TooltipInfoType implements Runnable { BAZAAR("https://hysky.de/api/bazaar", itemTooltip -> itemTooltip.enableBazaarPrice || SkyblockerConfigManager.get().locations.dungeons.dungeonChestProfit.enableProfitCalculator || SkyblockerConfigManager.get().general.chestValue.enableChestValue, itemTooltip -> itemTooltip.enableBazaarPrice, false), LOWEST_BINS("https://hysky.de/api/auctions/lowestbins", itemTooltip -> itemTooltip.enableLowestBIN || SkyblockerConfigManager.get().locations.dungeons.dungeonChestProfit.enableProfitCalculator || SkyblockerConfigManager.get().general.chestValue.enableChestValue, itemTooltip -> itemTooltip.enableLowestBIN, false), ONE_DAY_AVERAGE("https://moulberry.codes/auction_averages_lbin/1day.json", itemTooltip -> itemTooltip.enableAvgBIN, false), - THREE_DAY_AVERAGE("https://moulberry.codes/auction_averages_lbin/3day.json", itemTooltip -> itemTooltip.enableAvgBIN, false), + THREE_DAY_AVERAGE("https://moulberry.codes/auction_averages_lbin/3day.json", itemTooltip -> itemTooltip.enableAvgBIN || SkyblockerConfigManager.get().general.searchOverlay.enableAuctionHouse, itemTooltip -> itemTooltip.enableAvgBIN, false), MOTES("https://hysky.de/api/motesprice", itemTooltip -> itemTooltip.enableMotesPrice, itemTooltip -> itemTooltip.enableMotesPrice && Utils.isInTheRift(), true), OBTAINED(itemTooltip -> itemTooltip.enableObtainedDate), MUSEUM("https://hysky.de/api/museum", itemTooltip -> itemTooltip.enableMuseumInfo, true), diff --git a/src/main/java/de/hysky/skyblocker/skyblock/searchOverlay/OverlayScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/searchOverlay/OverlayScreen.java new file mode 100644 index 00000000..e1545c6c --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/searchOverlay/OverlayScreen.java @@ -0,0 +1,185 @@ +package de.hysky.skyblocker.skyblock.searchOverlay; + +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.gui.widget.TextFieldWidget; +import net.minecraft.item.ItemStack; +import net.minecraft.text.Style; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.Identifier; +import org.lwjgl.glfw.GLFW; + +import static de.hysky.skyblocker.skyblock.itemlist.ItemRepository.getItemStack; + +public class OverlayScreen extends Screen { + + protected static final Identifier SEARCH_ICON_TEXTURE = new Identifier("icon/search"); + private static final int rowHeight = 20; + private TextFieldWidget searchField; + private ButtonWidget finishedButton; + private ButtonWidget[] suggestionButtons; + private ButtonWidget[] historyButtons; + + public OverlayScreen(Text title) { + super(title); + } + + /** + * Creates the layout for the overlay screen. + */ + @Override + protected void init() { + super.init(); + int rowWidth = (int) (this.width * 0.4); + int startX = (int) (this.width * 0.5) - rowWidth / 2; + int startY = (int) ((int) (this.height * 0.5) - (rowHeight * (1 + SkyblockerConfigManager.get().general.searchOverlay.maxSuggestions + 0.75 + SkyblockerConfigManager.get().general.searchOverlay.historyLength)) / 2); + + // Search field + this.searchField = new TextFieldWidget(textRenderer, startX, startY, rowWidth - rowHeight, rowHeight, Text.translatable("gui.recipebook.search_hint")); + searchField.setText(SearchOverManager.search); + searchField.setChangedListener(SearchOverManager::updateSearch); + searchField.setMaxLength(30); + + // finish buttons + finishedButton = ButtonWidget.builder(Text.literal("").setStyle(Style.EMPTY.withColor(Formatting.GREEN)), a -> close()) + .position(startX + rowWidth - rowHeight, startY) + .size(rowHeight, rowHeight).build(); + + // suggested item buttons + int rowOffset = rowHeight; + int totalSuggestions = SkyblockerConfigManager.get().general.searchOverlay.maxSuggestions; + this.suggestionButtons = new ButtonWidget[totalSuggestions]; + for (int i = 0; i < totalSuggestions; i++) { + suggestionButtons[i] = ButtonWidget.builder(Text.literal(SearchOverManager.getSuggestion(i)).setStyle(Style.EMPTY), a -> { + SearchOverManager.updateSearch(a.getMessage().getString()); + close(); + }) + .position(startX, startY + rowOffset) + .size(rowWidth, rowHeight).build(); + suggestionButtons[i].visible = false; + rowOffset += rowHeight; + } + // history item buttons + rowOffset += (int) (rowHeight * 0.75); + int historyLength = SkyblockerConfigManager.get().general.searchOverlay.historyLength; + this.historyButtons = new ButtonWidget[historyLength]; + for (int i = 0; i < historyLength; i++) { + String text = SearchOverManager.getHistory(i); + if (text != null) { + historyButtons[i] = ButtonWidget.builder(Text.literal(text).setStyle(Style.EMPTY), (a) -> { + SearchOverManager.updateSearch(a.getMessage().getString()); + close(); + }) + .position(startX, startY + rowOffset) + .size(rowWidth, rowHeight).build(); + rowOffset += rowHeight; + } else { + break; + } + } + + //add drawables in order to make tab navigation sensible + addDrawableChild(searchField); + for (ButtonWidget suggestion : suggestionButtons) { + addDrawableChild(suggestion); + } + for (ButtonWidget historyOption : historyButtons) { + if (historyOption != null) { + addDrawableChild(historyOption); + } + } + addDrawableChild(finishedButton); + + //focus the search box + this.setInitialFocus(searchField); + } + + /** + * Renders the search icon, label for the history and item Stacks for item names + */ + @Override + public void render(DrawContext context, int mouseX, int mouseY, float delta) { + super.render(context, mouseX, mouseY, delta); + int renderOffset = (rowHeight - 16) / 2; + context.drawGuiTexture(SEARCH_ICON_TEXTURE, finishedButton.getX() + renderOffset, finishedButton.getY() + renderOffset, 16, 16); + if (historyButtons.length > 0 && historyButtons[0] != null) { + context.drawText(textRenderer, Text.translatable("text.autoconfig.skyblocker.option.general.searchOverlay.historyLabel"), historyButtons[0].getX() + renderOffset, historyButtons[0].getY() - rowHeight / 2, 0xFFFFFFFF, true); + } + + //draw item stacks and tooltip to buttons + for (int i = 0; i < suggestionButtons.length; i++) { + drawItemAndTooltip(context, mouseX, mouseY, SearchOverManager.getSuggestionId(i), suggestionButtons[i], renderOffset); + } + for (int i = 0; i < historyButtons.length; i++) { + drawItemAndTooltip(context, mouseX, mouseY, SearchOverManager.getHistoryId(i), historyButtons[i], renderOffset); + } + } + + /** + * Draws the item and tooltip for the given button + */ + private void drawItemAndTooltip(DrawContext context, int mouseX, int mouseY, String id, ButtonWidget button, int renderOffset) { + if (id == null || id.isEmpty()) return; + ItemStack item = getItemStack(id); + if (item == null) return; + context.drawItem(item, button.getX() + renderOffset, button.getY() + renderOffset); + + // Draw tooltip + if (button.isMouseOver(mouseX, mouseY)) { + context.drawItemTooltip(textRenderer, item, mouseX, mouseY); + } + } + + /** + * updates if the suggestions buttons should be visible based on if they have a value + */ + @Override + public final void tick() { + super.tick(); + //update suggestion buttons text + for (int i = 0; i < SkyblockerConfigManager.get().general.searchOverlay.maxSuggestions; i++) { + String text = SearchOverManager.getSuggestion(i); + if (!text.isEmpty()) { + suggestionButtons[i].visible = true; + + boolean isNewText = !text.equals(suggestionButtons[i].getMessage().getString()); + if (!isNewText) continue; + + suggestionButtons[i].setMessage(Text.literal(text).setStyle(Style.EMPTY)); + } else { + suggestionButtons[i].visible = false; + } + } + } + + /** + * When a key is pressed. If enter key pressed and search box selected close + */ + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (keyCode == GLFW.GLFW_KEY_ENTER && searchField.isActive()) { + close(); + return true; + } + return super.keyPressed(keyCode, scanCode, modifiers); + } + + @Override + public boolean shouldPause() { + return false; + } + + /** + * Closes the overlay screen and gets the manager to send a packet update about the sign + */ + @Override + public void close() { + assert this.client != null; + assert this.client.player != null; + SearchOverManager.pushSearch(); + super.close(); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/searchOverlay/SearchOverManager.java b/src/main/java/de/hysky/skyblocker/skyblock/searchOverlay/SearchOverManager.java new file mode 100644 index 00000000..b2a453a9 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/searchOverlay/SearchOverManager.java @@ -0,0 +1,347 @@ +package de.hysky.skyblocker.skyblock.searchOverlay; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.CommandDispatcher; +import de.hysky.skyblocker.config.SkyblockerConfig; +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType; +import de.hysky.skyblocker.utils.Http; +import de.hysky.skyblocker.utils.NEURepoManager; +import de.hysky.skyblocker.utils.scheduler.MessageScheduler; +import io.github.moulberry.repo.data.NEUItem; +import it.unimi.dsi.fastutil.Pair; +import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.minecraft.block.entity.SignBlockEntity; +import net.minecraft.client.MinecraftClient; +import net.minecraft.command.CommandRegistryAccess; +import net.minecraft.network.packet.c2s.play.UpdateSignC2SPacket; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal; + +public class SearchOverManager { + + private static final MinecraftClient CLIENT = MinecraftClient.getInstance(); + private static final Logger LOGGER = LoggerFactory.getLogger("Skyblocker Search Overlay"); + + private static final Pattern BAZAAR_ENCHANTMENT_PATTERN = Pattern.compile("ENCHANTMENT_(\\D*)_(\\d+)"); + private static final Pattern AUCTION_PET_AND_RUNE_PATTERN = Pattern.compile("([A-Z0-9_]+);(\\d+)"); + + /** + * converts index (in array) +1 to a roman numeral + */ + private static final String[] ROMAN_NUMERALS = new String[]{ + "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X", "XI", + "XII", "XIII", "XIV", "XV", "XVI", "XVII", "XVIII", "XIX", "XX" + }; + + private static @Nullable SignBlockEntity sign = null; + private static boolean signFront = true; + private static boolean isAuction; + private static boolean isCommand; + + protected static String search = ""; + + // Use non-final variables and swap them to prevent concurrent modification + private static HashSet<String> bazaarItems; + private static HashSet<String> auctionItems; + private static HashMap<String, String> namesToId; + + public static String[] suggestionsArray = {}; + + /** + * uses the skyblock api and Moulberry auction to load a list of items in bazaar and auction house + */ + public static void init() { + ClientCommandRegistrationCallback.EVENT.register(SearchOverManager::registerSearchCommands); + NEURepoManager.runAsyncAfterLoad(SearchOverManager::loadItems); + } + + private static void registerSearchCommands(CommandDispatcher<FabricClientCommandSource> dispatcher, CommandRegistryAccess registryAccess) { + if (SkyblockerConfigManager.get().general.searchOverlay.enableCommands) { + dispatcher.register(literal("ahs").executes(context -> startCommand(true))); + dispatcher.register(literal("bzs").executes(context -> startCommand(false))); + } + } + + private static int startCommand(boolean isAuction) { + isCommand = true; + SearchOverManager.isAuction = isAuction; + search = ""; + suggestionsArray = new String[]{}; + CLIENT.send(() -> CLIENT.setScreen(new OverlayScreen(Text.of("")))); + return Command.SINGLE_SUCCESS; + } + + private static void loadItems() { + HashSet<String> bazaarItems = new HashSet<>(); + HashSet<String> auctionItems = new HashSet<>(); + HashMap<String, String> namesToId = new HashMap<>(); + + //get bazaar items + try (Http.ApiResponse response = Http.sendHypixelRequest("skyblock/bazaar", "")) { + JsonObject products = JsonParser.parseString(response.content()).getAsJsonObject().get("products").getAsJsonObject(); + for (Map.Entry<String, JsonElement> entry : products.entrySet()) { + if (entry.getValue().isJsonObject()) { + JsonObject product = entry.getValue().getAsJsonObject(); + String id = product.get("product_id").getAsString(); + int sellVolume = product.get("quick_status").getAsJsonObject().get("sellVolume").getAsInt(); + if (sellVolume == 0) + continue; //do not add items that do not sell e.g. they are not actual in the bazaar + Matcher matcher = BAZAAR_ENCHANTMENT_PATTERN.matcher(id); + if (matcher.matches()) {//format enchantments + //remove ultimate if in name + String name = matcher.group(1); + if (!name.contains("WISE")) { //only way found to remove ultimate from everything but ultimate wise + name = name.replace("ULTIMATE_", ""); + } + name = name.replace("_", " "); + name = capitalizeFully(name); + int enchantLevel = Integer.parseInt(matcher.group(2)); + String level = ""; + if (enchantLevel > 0) { + level = ROMAN_NUMERALS[enchantLevel - 1]; + } + bazaarItems.add(name + " " + level); + namesToId.put(name + " " + level, matcher.group(1) + ";" + matcher.group(2)); + continue; + } + //look up id for name + NEUItem neuItem = NEURepoManager.NEU_REPO.getItems().getItemBySkyblockId(id); + if (neuItem != null) { + String name = Formatting.strip(neuItem.getDisplayName()); + bazaarItems.add(name); + namesToId.put(name, id); + continue; + } + } + } + } catch (Exception e) { + LOGGER.error("[Skyblocker] Failed to load bazaar item list! ", e); + } + + //get auction items + try { + if (TooltipInfoType.THREE_DAY_AVERAGE.getData() == null) { + TooltipInfoType.THREE_DAY_AVERAGE.run(); + } + for (Map.Entry<String, JsonElement> entry : TooltipInfoType.THREE_DAY_AVERAGE.getData().entrySet()) { + String id = entry.getKey(); + + Matcher matcher = AUCTION_PET_AND_RUNE_PATTERN.matcher(id); + if (matcher.find()) {//is a pet or rune convert id to name + String name = matcher.group(1).replace("_", " "); + name = capitalizeFully(name); + auctionItems.add(name); + namesToId.put(name, id); + continue; + } + //something else look up in NEU repo. + id = id.split("[+-]")[0]; + NEUItem neuItem = NEURepoManager.NEU_REPO.getItems().getItemBySkyblockId(id); + if (neuItem != null) { + String name = Formatting.strip(neuItem.getDisplayName()); + auctionItems.add(name); + namesToId.put(name, id); + continue; + } + } + } catch (Exception e) { + LOGGER.error("[Skyblocker] Failed to load auction house item list! ", e); + } + + SearchOverManager.bazaarItems = bazaarItems; + SearchOverManager.auctionItems = auctionItems; + SearchOverManager.namesToId = namesToId; + } + + /** + * Capitalizes the first letter off every word in a string + * @param str string to capitalize + */ + private static String capitalizeFully(String str) { + if (str == null || str.isEmpty()) { + return str; + } + + return Arrays.stream(str.split("\\s+")) + .map(t -> t.substring(0, 1).toUpperCase() + t.substring(1).toLowerCase()) + .collect(Collectors.joining(" ")); + } + + /** + * Receives data when a search is started and resets values + * @param sign the sign that is being edited + * @param front if it's the front of the sign + * @param isAuction if the sign is loaded from the auction house menu or bazaar + */ + public static void updateSign(@NotNull SignBlockEntity sign, boolean front, boolean isAuction) { + signFront = front; + SearchOverManager.sign = sign; + isCommand = false; + SearchOverManager.isAuction = isAuction; + if (SkyblockerConfigManager.get().general.searchOverlay.keepPreviousSearches) { + Text[] messages = SearchOverManager.sign.getText(signFront).getMessages(CLIENT.shouldFilterText()); + search = messages[0].getString(); + if (!messages[1].getString().isEmpty()) { + if (!search.endsWith(" ")) { + search += " "; + } + search += messages[1].getString(); + } + } else { + search = ""; + } + suggestionsArray = new String[]{}; + } + + /** + * Updates the search value and the suggestions based on that value. + * @param newValue new search value + */ + protected static void updateSearch(String newValue) { + search = newValue; + //update the suggestion values + int totalSuggestions = SkyblockerConfigManager.get().general.searchOverlay.maxSuggestions; + if (newValue.isBlank() || totalSuggestions == 0) return; //do not search for empty value + suggestionsArray = (isAuction ? auctionItems : bazaarItems).stream().filter(item -> item.toLowerCase().contains(search.toLowerCase())).limit(totalSuggestions).toArray(String[]::new); + } + + /** + * Gets the suggestion in the suggestion array at the index + * @param index index of suggestion + */ + protected static String getSuggestion(int index) { + if (suggestionsArray.length > index && suggestionsArray[index] != null) { + return suggestionsArray[index]; + } else {//there are no suggestions yet + return ""; + } + } + + protected static String getSuggestionId(int index) { + return namesToId.get(getSuggestion(index)); + } + + /** + * Gets the item name in the history array at the index + * @param index index of suggestion + */ + protected static String getHistory(int index) { + if (isAuction) { + if (SkyblockerConfigManager.get().general.searchOverlay.auctionHistory.size() > index) { + return SkyblockerConfigManager.get().general.searchOverlay.auctionHistory.get(index); + } + } else { + if (SkyblockerConfigManager.get().general.searchOverlay.bazaarHistory.size() > index) { + return SkyblockerConfigManager.get().general.searchOverlay.bazaarHistory.get(index); + } + } + return null; + } + + protected static String getHistoryId(int index) { + return namesToId.get(getHistory(index)); + } + + /** + * Add the current search value to the start of the history list and truncate to the max history value and save this to the config + */ + private static void saveHistory() { + //save to history + SkyblockerConfig.SearchOverlay config = SkyblockerConfigManager.get().general.searchOverlay; + if (isAuction) { + if (config.auctionHistory.isEmpty() || !config.auctionHistory.get(0).equals(search)) { + config.auctionHistory.add(0, search); + if (config.auctionHistory.size() > config.historyLength) { + config.auctionHistory = config.auctionHistory.subList(0, config.historyLength); + } + } + } else { + if (config.bazaarHistory.isEmpty() || !config.bazaarHistory.get(0).equals(search)) { + config.bazaarHistory.add(0, search); + if (config.bazaarHistory.size() > config.historyLength) { + config.bazaarHistory = config.bazaarHistory.subList(0, config.historyLength); + } + } + } + SkyblockerConfigManager.save(); + } + + /** + *Saves the current value of ({@link SearchOverManager#search}) then pushes it to a command or sign depending on how the gui was opened + */ + protected static void pushSearch() { + //save to history + if (!search.isEmpty()) { + saveHistory(); + } + if (isCommand) { + pushCommand(); + } else { + pushSign(); + } + } + + /** + * runs the command to search for the value in ({@link SearchOverManager#search}) + */ + private static void pushCommand() { + if (search.isEmpty()) return; + + String command; + if (isAuction) { + command = "/ahSearch " + search; + } else { + command = "/bz " + search; + } + MessageScheduler.INSTANCE.sendMessageAfterCooldown(command); + } + + /** + * pushes the ({@link SearchOverManager#search}) to the sign. It needs to split it over two lines without splitting a word + */ + private static void pushSign() { + //splits text into 2 lines max = 30 chars + Pair<String, String> split = splitString(search); + + // send packet to update sign + if (CLIENT.player != null && sign != null) { + Text[] messages = sign.getText(signFront).getMessages(CLIENT.shouldFilterText()); + CLIENT.player.networkHandler.sendPacket(new UpdateSignC2SPacket(sign.getPos(), signFront, + split.left(), + split.right(), + messages[2].getString(), + messages[3].getString() + )); + } + } + + static Pair<String, String> splitString(String s) { + if (s.length() <= 15) { + return Pair.of(s, ""); + } + int index = s.lastIndexOf(' ', 15); + if (index == -1) { + return Pair.of(s.substring(0, 15), ""); + } + return Pair.of(s.substring(0, index), s.substring(index + 1, Math.min(index + 16, s.length())).trim()); + } +} |