diff options
author | Kevin <92656833+kevinthegreat1@users.noreply.github.com> | 2024-06-10 12:12:29 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-06-10 12:12:29 +0800 |
commit | c29b55bc64fdf8717b42f1a5f7e0d18895007fb3 (patch) | |
tree | 382a7d4f3fa4b40fcbfe2c752cf581c36382bc52 /src/main/java/de/hysky/skyblocker/skyblock | |
parent | ec1c0104a17d9e3a5741efa38528d628b53d940d (diff) | |
parent | 48430e36a87c09e033c0bd43e65b70bbac0e2664 (diff) | |
download | Skyblocker-c29b55bc64fdf8717b42f1a5f7e0d18895007fb3.tar.gz Skyblocker-c29b55bc64fdf8717b42f1a5f7e0d18895007fb3.tar.bz2 Skyblocker-c29b55bc64fdf8717b42f1a5f7e0d18895007fb3.zip |
Merge pull request #735 from Emirlol/tooltips-galore
Tooltip refactors
Diffstat (limited to 'src/main/java/de/hysky/skyblocker/skyblock')
36 files changed, 1497 insertions, 642 deletions
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/ChestValue.java b/src/main/java/de/hysky/skyblocker/skyblock/ChestValue.java index 7b36dd78..908525e1 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/ChestValue.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/ChestValue.java @@ -79,7 +79,7 @@ public class ChestValue { } String name = stack.getName().getString(); - String id = ItemTooltip.getInternalNameFromNBT(stack, false); + String id = stack.getSkyblockApiId(); //Regular item price if (id != null) { @@ -158,7 +158,7 @@ public class ChestValue { continue; } - String id = ItemTooltip.getInternalNameFromNBT(stack, false); + String id = stack.getSkyblockApiId(); if (id != null) { DoubleBooleanPair priceData = ItemUtils.getItemPrice(id); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/TeleportOverlay.java b/src/main/java/de/hysky/skyblocker/skyblock/TeleportOverlay.java index 042b126b..a8155b43 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/TeleportOverlay.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/TeleportOverlay.java @@ -1,7 +1,6 @@ package de.hysky.skyblocker.skyblock; import de.hysky.skyblocker.config.SkyblockerConfigManager; -import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip; import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Utils; import de.hysky.skyblocker.utils.render.RenderHelper; @@ -27,7 +26,7 @@ public class TeleportOverlay { private static void render(WorldRenderContext wrc) { if (Utils.isOnSkyblock() && SkyblockerConfigManager.get().uiAndVisuals.teleportOverlay.enableTeleportOverlays && client.player != null && client.world != null) { ItemStack heldItem = client.player.getMainHandStack(); - String itemId = ItemTooltip.getInternalNameFromNBT(heldItem, true); + String itemId = heldItem.getSkyblockId(); NbtCompound customData = ItemUtils.getCustomData(heldItem); if (itemId != null) { diff --git a/src/main/java/de/hysky/skyblocker/skyblock/auction/AuctionBrowserScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/auction/AuctionBrowserScreen.java index fd69d886..557cb6c9 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/auction/AuctionBrowserScreen.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/auction/AuctionBrowserScreen.java @@ -295,8 +295,8 @@ public class AuctionBrowserScreen extends AbstractCustomHypixelGUI<AuctionHouseS String coins = split[1].replace(",", "").replace("coins", "").trim(); try { long parsed = Long.parseLong(coins); - String name = ItemTooltip.getInternalNameFromNBT(stack, false); - String internalID = ItemTooltip.getInternalNameFromNBT(stack, true); + String name = stack.getSkyblockApiId(); + String internalID = stack.getSkyblockId(); String neuName = name; if (name == null || internalID == null) break; if (name.startsWith("ISSHINY_")) { diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/ChocolateFactorySolver.java b/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/ChocolateFactorySolver.java index e04e632a..d33a83e9 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/ChocolateFactorySolver.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/ChocolateFactorySolver.java @@ -1,20 +1,18 @@ package de.hysky.skyblocker.skyblock.chocolatefactory; import de.hysky.skyblocker.config.SkyblockerConfigManager; -import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip; +import de.hysky.skyblocker.skyblock.item.tooltip.adders.LineSmoothener; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder; import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.RegexUtils; +import de.hysky.skyblocker.utils.RomanNumerals; import de.hysky.skyblocker.utils.render.gui.ColorHighlight; import de.hysky.skyblocker.utils.render.gui.ContainerSolver; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import net.fabricmc.fabric.api.client.item.v1.ItemTooltipCallback; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.screen.ingame.GenericContainerScreen; -import net.minecraft.client.item.TooltipType; -import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.item.Items; +import net.minecraft.screen.slot.Slot; import net.minecraft.text.MutableText; import net.minecraft.text.Text; import net.minecraft.util.Formatting; @@ -46,10 +44,27 @@ public class ChocolateFactorySolver extends ContainerSolver { private static boolean canPrestige = false; private static boolean reachedMaxPrestige = false; private static double timeTowerMultiplier = -1.0; + private static boolean isTimeTowerMaxed = false; private static boolean isTimeTowerActive = false; + private static int bestUpgrade = -1; + private static int bestAffordableUpgrade = -1; private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#,###.#", DecimalFormatSymbols.getInstance(Locale.ENGLISH)); - private static ItemStack bestUpgrade = null; - private static ItemStack bestAffordableUpgrade = null; + + @Override + protected void reset() { + cpsIncreaseFactors.clear(); + totalChocolate = -1L; + totalCps = -1.0; + totalCpsMultiplier = -1.0; + requiredUntilNextPrestige = -1L; + canPrestige = false; + reachedMaxPrestige = false; + timeTowerMultiplier = -1.0; + isTimeTowerMaxed = false; + isTimeTowerActive = false; + bestUpgrade = -1; + bestAffordableUpgrade = -1; + } //Slots, for ease of maintenance rather than using magic numbers everywhere. private static final byte RABBITS_START = 28; @@ -63,8 +78,7 @@ public class ChocolateFactorySolver extends ContainerSolver { private static final byte STRAY_RABBIT_END = 26; public ChocolateFactorySolver() { - super("^Chocolate Factory$"); - ItemTooltipCallback.EVENT.register(ChocolateFactorySolver::handleTooltip); + super("^Chocolate Factory$"); //There are multiple screens that fit the pattern `^Chocolate Factory`, so the $ is required } @Override @@ -82,7 +96,7 @@ public class ChocolateFactorySolver extends ContainerSolver { if (totalChocolate <= 0 || cpsIncreaseFactors.isEmpty()) return highlights; //Something went wrong or there's nothing we can afford. Rabbit bestRabbit = cpsIncreaseFactors.getFirst(); - bestUpgrade = bestRabbit.itemStack; + bestUpgrade = bestRabbit.slot; if (bestRabbit.cost <= totalChocolate) { highlights.add(ColorHighlight.green(bestRabbit.slot)); return highlights; @@ -91,7 +105,7 @@ public class ChocolateFactorySolver extends ContainerSolver { for (Rabbit rabbit : cpsIncreaseFactors.subList(1, cpsIncreaseFactors.size())) { if (rabbit.cost <= totalChocolate) { - bestAffordableUpgrade = rabbit.itemStack; + bestAffordableUpgrade = rabbit.slot; highlights.add(ColorHighlight.green(rabbit.slot)); break; } @@ -143,7 +157,8 @@ public class ChocolateFactorySolver extends ContainerSolver { } //Time Tower is in slot 39 - timeTowerMultiplier = romanToDecimal(StringUtils.substringAfterLast(slots.get(TIME_TOWER_SLOT).getName().getString(), ' ')) / 10.0; //The name holds the level, which is multiplier * 10 in roman numerals + isTimeTowerMaxed = StringUtils.substringAfterLast(slots.get(TIME_TOWER_SLOT).getName().getString(), ' ').equals("XV"); + timeTowerMultiplier = RomanNumerals.romanToDecimal(StringUtils.substringAfterLast(slots.get(TIME_TOWER_SLOT).getName().getString(), ' ')) / 10.0; //The name holds the level, which is multiplier * 10 in roman numerals Matcher timeTowerStatusMatcher = TIME_TOWER_STATUS_PATTERN.matcher(getConcatenatedLore(slots.get(TIME_TOWER_SLOT))); if (timeTowerStatusMatcher.find()) { isTimeTowerActive = timeTowerStatusMatcher.group(1).equals("ACTIVE"); @@ -153,130 +168,6 @@ public class ChocolateFactorySolver extends ContainerSolver { cpsIncreaseFactors.sort(Comparator.comparingDouble(rabbit -> rabbit.cost() / rabbit.cpsIncrease())); //Ascending order, lower = better } - private static void handleTooltip(ItemStack stack, Item.TooltipContext tooltipContext, TooltipType tooltipType, List<Text> lines) { - if (!SkyblockerConfigManager.get().helpers.chocolateFactory.enableChocolateFactoryHelper) return; - if (!(MinecraftClient.getInstance().currentScreen instanceof GenericContainerScreen screen) || !screen.getTitle().getString().equals("Chocolate Factory")) return; - - int lineIndex = lines.size(); - //This boolean is used to determine if we should add a smooth line to separate the added information from the rest of the tooltip. - //It should be set to true if there's any information added, false otherwise. - boolean shouldAddLine = false; - - String lore = concatenateLore(lines); - Matcher costMatcher = COST_PATTERN.matcher(lore); - OptionalLong cost = RegexUtils.getLongFromMatcher(costMatcher); - //Available on all items with a chocolate cost - if (cost.isPresent()) shouldAddLine = addUpgradeTimerToLore(lines, cost.getAsLong()); - - //Prestige item - if (stack.isOf(Items.DROPPER)) { - shouldAddLine = addPrestigeTimerToLore(lines) || shouldAddLine; - } - //Time tower - else if (stack.isOf(Items.CLOCK)) { - shouldAddLine = addTimeTowerStatsToLore(lines) || shouldAddLine; - } - //Rabbits - else if (stack.isOf(Items.PLAYER_HEAD)) { - shouldAddLine = addRabbitStatsToLore(lines, stack) || shouldAddLine; - } - - //This is an ArrayList, so this operation is probably not very efficient, but logically it's pretty much the only way I can think of - if (shouldAddLine) lines.add(lineIndex, ItemTooltip.createSmoothLine()); - } - - private static boolean addUpgradeTimerToLore(List<Text> lines, long cost) { - if (totalChocolate < 0L || totalCps < 0.0) return false; - lines.add(Text.empty() - .append(Text.literal("Time until upgrade: ").formatted(Formatting.GRAY)) - .append(formatTime((cost - totalChocolate) / totalCps))); - return true; - } - - private static boolean addPrestigeTimerToLore(List<Text> lines) { - if (totalCps < 0.0 || reachedMaxPrestige) return false; - if (requiredUntilNextPrestige > 0 && !canPrestige) { - lines.add(Text.empty() - .append(Text.literal("Chocolate until next prestige: ").formatted(Formatting.GRAY)) - .append(Text.literal(DECIMAL_FORMAT.format(requiredUntilNextPrestige)).formatted(Formatting.GOLD))); - } - lines.add(Text.empty() //Keep this outside of the `if` to match the format of the upgrade tooltips, that say "Time until upgrade: Now" when it's possible - .append(Text.literal("Time until next prestige: ").formatted(Formatting.GRAY)) - .append(formatTime(requiredUntilNextPrestige / totalCps))); - return true; - } - - private static boolean addTimeTowerStatsToLore(List<Text> lines) { - if (totalCps < 0.0 || totalCpsMultiplier < 0.0 || timeTowerMultiplier < 0.0) return false; - lines.add(Text.literal("Current stats:").formatted(Formatting.GRAY)); - lines.add(Text.empty() - .append(Text.literal(" CPS increase: ").formatted(Formatting.GRAY)) - .append(Text.literal(DECIMAL_FORMAT.format(totalCps / totalCpsMultiplier * timeTowerMultiplier)).formatted(Formatting.GOLD))); - lines.add(Text.empty() - .append(Text.literal(" CPS when active: ").formatted(Formatting.GRAY)) - .append(Text.literal(DECIMAL_FORMAT.format(isTimeTowerActive ? totalCps : totalCps / totalCpsMultiplier * (timeTowerMultiplier + totalCpsMultiplier))).formatted(Formatting.GOLD))); - if (timeTowerMultiplier < 1.5) { - lines.add(Text.literal("Stats after upgrade:").formatted(Formatting.GRAY)); - lines.add(Text.empty() - .append(Text.literal(" CPS increase: ").formatted(Formatting.GRAY)) - .append(Text.literal(DECIMAL_FORMAT.format(totalCps / (totalCpsMultiplier) * (timeTowerMultiplier + 0.1))).formatted(Formatting.GOLD))); - lines.add(Text.empty() - .append(Text.literal(" CPS when active: ").formatted(Formatting.GRAY)) - .append(Text.literal(DECIMAL_FORMAT.format(isTimeTowerActive ? totalCps / totalCpsMultiplier * (totalCpsMultiplier + 0.1) : totalCps / totalCpsMultiplier * (timeTowerMultiplier + 0.1 + totalCpsMultiplier))).formatted(Formatting.GOLD))); - } - return true; - } - - private static boolean addRabbitStatsToLore(List<Text> lines, ItemStack stack) { - if (cpsIncreaseFactors.isEmpty()) return false; - boolean changed = false; - for (Rabbit rabbit : cpsIncreaseFactors) { - if (rabbit.itemStack != stack) continue; - changed = true; - lines.add(Text.empty() - .append(Text.literal("CPS Increase: ").formatted(Formatting.GRAY)) - .append(Text.literal(DECIMAL_FORMAT.format(rabbit.cpsIncrease)).formatted(Formatting.GOLD))); - - lines.add(Text.empty() - .append(Text.literal("Cost per CPS: ").formatted(Formatting.GRAY)) - .append(Text.literal(DECIMAL_FORMAT.format(rabbit.cost / rabbit.cpsIncrease)).formatted(Formatting.GOLD))); - - if (rabbit.itemStack == bestUpgrade) { - if (rabbit.cost <= totalChocolate) { - lines.add(Text.literal("Best upgrade").formatted(Formatting.GREEN)); - } else { - lines.add(Text.literal("Best upgrade, can't afford").formatted(Formatting.YELLOW)); - } - } else if (rabbit.itemStack == bestAffordableUpgrade && rabbit.cost <= totalChocolate) { - lines.add(Text.literal("Best upgrade you can afford").formatted(Formatting.GREEN)); - } - } - return changed; - } - - private static MutableText formatTime(double seconds) { - seconds = Math.ceil(seconds); - if (seconds <= 0) return Text.literal("Now").formatted(Formatting.GREEN); - - StringBuilder builder = new StringBuilder(); - if (seconds >= 86400) { - builder.append((int) (seconds / 86400)).append("d "); - seconds %= 86400; - } - if (seconds >= 3600) { - builder.append((int) (seconds / 3600)).append("h "); - seconds %= 3600; - } - if (seconds >= 60) { - builder.append((int) (seconds / 60)).append("m "); - seconds %= 60; - } - if (seconds >= 1) { - builder.append((int) seconds).append("s"); - } - return Text.literal(builder.toString()).formatted(Formatting.GOLD); - } - /** * Utility method. */ @@ -317,7 +208,7 @@ public class ChocolateFactorySolver extends ContainerSolver { OptionalInt cost = RegexUtils.getIntFromMatcher(costMatcher, multiplierIncreaseMatcher.hasMatch() ? multiplierIncreaseMatcher.end() : 0); //Cost comes after the multiplier line if (cost.isEmpty()) return Optional.empty(); - return Optional.of(new Rabbit(totalCps / totalCpsMultiplier * (nextCpsMultiplier.getAsDouble() - currentCpsMultiplier.getAsDouble()), cost.getAsInt(), COACH_SLOT, coachItem)); + return Optional.of(new Rabbit(totalCps / totalCpsMultiplier * (nextCpsMultiplier.getAsDouble() - currentCpsMultiplier.getAsDouble()), cost.getAsInt(), COACH_SLOT)); } private static Optional<Rabbit> getRabbit(ItemStack item, int slot) { @@ -334,7 +225,7 @@ public class ChocolateFactorySolver extends ContainerSolver { Matcher costMatcher = COST_PATTERN.matcher(lore); OptionalInt cost = RegexUtils.getIntFromMatcher(costMatcher, cpsMatcher.hasMatch() ? cpsMatcher.end() : 0); //Cost comes after the cps line if (cost.isEmpty()) return Optional.empty(); - return Optional.of(new Rabbit(nextCps.getAsInt() - currentCps.getAsInt(), cost.getAsInt(), slot, item)); + return Optional.of(new Rabbit(nextCps.getAsInt() - currentCps.getAsInt(), cost.getAsInt(), slot)); } private static Optional<ColorHighlight> getPrestigeHighlight() { @@ -356,28 +247,137 @@ public class ChocolateFactorySolver extends ContainerSolver { return highlights; } - private record Rabbit(double cpsIncrease, int cost, int slot, ItemStack itemStack) { - } + private record Rabbit(double cpsIncrease, int cost, int slot) { } + + public static final class Tooltip extends TooltipAdder { + public Tooltip() { + super("^Chocolate Factory$", 0); //The priority doesn't really matter here as this is the only tooltip adder for the Chocolate Factory. + } + + @Override + public void addToTooltip(List<Text> lines, Slot focusedSlot) { + if (!SkyblockerConfigManager.get().helpers.chocolateFactory.enableChocolateFactoryHelper) return; + + int lineIndex = lines.size(); + //This boolean is used to determine if we should add a smooth line to separate the added information from the rest of the tooltip. + //It should be set to true if there's any information added, false otherwise. + boolean shouldAddLine = false; + + String lore = concatenateLore(lines); + Matcher costMatcher = COST_PATTERN.matcher(lore); + OptionalLong cost = RegexUtils.getLongFromMatcher(costMatcher); + //Available on all items with a chocolate cost + if (cost.isPresent()) shouldAddLine |= addUpgradeTimerToLore(lines, cost.getAsLong()); + + int index = focusedSlot.id; + + //Prestige item + if (index == PRESTIGE_SLOT) { + shouldAddLine |= addPrestigeTimerToLore(lines); + } + //Time tower + else if (index == TIME_TOWER_SLOT) { + shouldAddLine |= addTimeTowerStatsToLore(lines); + } + //Rabbits + else if (index == COACH_SLOT || (index >= RABBITS_START && index <= RABBITS_END)) { + shouldAddLine |= addRabbitStatsToLore(lines, index); + } + + //This is an ArrayList, so this operation is probably not very efficient, but logically it's pretty much the only way I can think of + if (shouldAddLine) lines.add(lineIndex, LineSmoothener.createSmoothLine()); + } + + private static boolean addUpgradeTimerToLore(List<Text> lines, long cost) { + if (totalChocolate < 0L || totalCps < 0.0) return false; + lines.add(Text.empty() + .append(Text.literal("Time until upgrade: ").formatted(Formatting.GRAY)) + .append(formatTime((cost - totalChocolate) / totalCps))); + return true; + } - //Perhaps the part below can go to a separate file later on, but I couldn't find a proper name for the class, so they're staying here. - private static final Map<Character, Integer> romanMap = Map.of( - 'I', 1, - 'V', 5, - 'X', 10, - 'L', 50, - 'C', 100, - 'D', 500, - 'M', 1000 - ); - - public static int romanToDecimal(String romanNumeral) { - int decimal = 0; - int lastNumber = 0; - for (int i = romanNumeral.length() - 1; i >= 0; i--) { - char ch = romanNumeral.charAt(i); - decimal = romanMap.get(ch) >= lastNumber ? decimal + romanMap.get(ch) : decimal - romanMap.get(ch); - lastNumber = romanMap.get(ch); + private static boolean addPrestigeTimerToLore(List<Text> lines) { + if (totalCps < 0.0 || reachedMaxPrestige) return false; + if (requiredUntilNextPrestige > 0 && !canPrestige) { + lines.add(Text.empty() + .append(Text.literal("Chocolate until next prestige: ").formatted(Formatting.GRAY)) + .append(Text.literal(DECIMAL_FORMAT.format(requiredUntilNextPrestige)).formatted(Formatting.GOLD))); + } + lines.add(Text.empty() //Keep this outside of the `if` to match the format of the upgrade tooltips, that say "Time until upgrade: Now" when it's possible + .append(Text.literal("Time until next prestige: ").formatted(Formatting.GRAY)) + .append(formatTime(requiredUntilNextPrestige / totalCps))); + return true; + } + + private static boolean addTimeTowerStatsToLore(List<Text> lines) { + if (totalCps < 0.0 || totalCpsMultiplier < 0.0 || timeTowerMultiplier < 0.0) return false; + lines.add(Text.literal("Current stats:").formatted(Formatting.GRAY)); + lines.add(Text.empty() + .append(Text.literal(" CPS increase: ").formatted(Formatting.GRAY)) + .append(Text.literal(DECIMAL_FORMAT.format(totalCps / totalCpsMultiplier * timeTowerMultiplier)).formatted(Formatting.GOLD))); + lines.add(Text.empty() + .append(Text.literal(" CPS when active: ").formatted(Formatting.GRAY)) + .append(Text.literal(DECIMAL_FORMAT.format(isTimeTowerActive ? totalCps : totalCps / totalCpsMultiplier * (timeTowerMultiplier + totalCpsMultiplier))).formatted(Formatting.GOLD))); + if (!isTimeTowerMaxed) { + lines.add(Text.literal("Stats after upgrade:").formatted(Formatting.GRAY)); + lines.add(Text.empty() + .append(Text.literal(" CPS increase: ").formatted(Formatting.GRAY)) + .append(Text.literal(DECIMAL_FORMAT.format(totalCps / (totalCpsMultiplier) * (timeTowerMultiplier + 0.1))).formatted(Formatting.GOLD))); + lines.add(Text.empty() + .append(Text.literal(" CPS when active: ").formatted(Formatting.GRAY)) + .append(Text.literal(DECIMAL_FORMAT.format(isTimeTowerActive ? totalCps / totalCpsMultiplier * (totalCpsMultiplier + 0.1) : totalCps / totalCpsMultiplier * (timeTowerMultiplier + 0.1 + totalCpsMultiplier))).formatted(Formatting.GOLD))); + } + return true; + } + + private static boolean addRabbitStatsToLore(List<Text> lines, int slot) { + if (cpsIncreaseFactors.isEmpty()) return false; + for (Rabbit rabbit : cpsIncreaseFactors) { + if (rabbit.slot == slot) { + lines.add(Text.empty() + .append(Text.literal("CPS Increase: ").formatted(Formatting.GRAY)) + .append(Text.literal(DECIMAL_FORMAT.format(rabbit.cpsIncrease)).formatted(Formatting.GOLD))); + + lines.add(Text.empty() + .append(Text.literal("Cost per CPS: ").formatted(Formatting.GRAY)) + .append(Text.literal(DECIMAL_FORMAT.format(rabbit.cost / rabbit.cpsIncrease)).formatted(Formatting.GOLD))); + + if (rabbit.slot == bestUpgrade) { + if (rabbit.cost <= totalChocolate) { + lines.add(Text.literal("Best upgrade").formatted(Formatting.GREEN)); + } else { + lines.add(Text.literal("Best upgrade, can't afford").formatted(Formatting.YELLOW)); + } + } else if (rabbit.slot == bestAffordableUpgrade && rabbit.cost <= totalChocolate) { + lines.add(Text.literal("Best upgrade you can afford").formatted(Formatting.GREEN)); + } + return true; + } + } + return false; + } + + private static MutableText formatTime(double seconds) { + seconds = Math.ceil(seconds); + if (seconds <= 0) return Text.literal("Now").formatted(Formatting.GREEN); + + StringBuilder builder = new StringBuilder(); + if (seconds >= 86400) { + builder.append((int) (seconds / 86400)).append("d "); + seconds %= 86400; + } + if (seconds >= 3600) { + builder.append((int) (seconds / 3600)).append("h "); + seconds %= 3600; + } + if (seconds >= 60) { + builder.append((int) (seconds / 60)).append("m "); + seconds %= 60; + } + if (seconds >= 1) { + builder.append((int) seconds).append("s"); + } + return Text.literal(builder.toString()).formatted(Formatting.GOLD); } - return decimal; } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotText.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotText.java new file mode 100644 index 00000000..66c02ca1 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotText.java @@ -0,0 +1,21 @@ +package de.hysky.skyblocker.skyblock.item.slottext; + +import net.minecraft.text.Text; + +public record SlotText(Text text, TextPosition position) { + public static SlotText bottomLeft(Text text) { + return new SlotText(text, TextPosition.BOTTOM_LEFT); + } + + public static SlotText bottomRight(Text text) { + return new SlotText(text, TextPosition.BOTTOM_RIGHT); + } + + public static SlotText topLeft(Text text) { + return new SlotText(text, TextPosition.TOP_LEFT); + } + + public static SlotText topRight(Text text) { + return new SlotText(text, TextPosition.TOP_RIGHT); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotTextAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotTextAdder.java new file mode 100644 index 00000000..18bf1dc1 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotTextAdder.java @@ -0,0 +1,64 @@ +package de.hysky.skyblocker.skyblock.item.slottext; + +import de.hysky.skyblocker.skyblock.ChestValue; +import net.minecraft.screen.slot.Slot; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.regex.Pattern; + +/** + * Extend this class and add it to {@link SlotTextManager#adders} to add text to any arbitrary slot. + */ +public abstract class SlotTextAdder { + /** + * The title of the screen must match this pattern for this adder to be applied. Null means it will be applied to all screens. + * @implNote Don't end your regex with a {@code $} as {@link ChestValue} appends text to the end of the title, + * so the regex will stop matching if the player uses it. + */ + public final @Nullable Pattern titlePattern; + + /** + * Utility constructor that will compile the given string into a pattern. + * + * @see #SlotTextAdder(Pattern) + */ + protected SlotTextAdder(@NotNull String titlePattern) { + this(Pattern.compile(titlePattern)); + } + + /** + * Creates a SlotTextAdder that will be applied to screens with titles that match the given pattern. + * + * @param titlePattern The pattern to match the screen title against. + */ + protected SlotTextAdder(@NotNull Pattern titlePattern) { + this.titlePattern = titlePattern; + } + + /** + * Creates a SlotTextAdder that will be applied to all screens. + */ + protected SlotTextAdder() { + this.titlePattern = null; + } + + /** + * This method will be called for each rendered slot. Consider using a switch statement on {@link Slot#id} if you wish to add different text to different slots. + * + * @return A list of positioned text to be rendered. Return {@link List#of()} if no text should be rendered. + * @implNote By minecraft's design, scaled text inexplicably moves around. + * So, limit your text to 3 characters (or roughly less than 20 width) if you want it to not look horrible. + */ + public abstract @NotNull List<SlotText> getText(Slot slot); + + /** + * Override this method to add conditions to enable or disable this adder. + * @return Whether this adder is enabled. + * @implNote The slot text adders only work while in skyblock, so no need to check for that again. + */ + public boolean isEnabled() { + return true; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotTextManager.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotTextManager.java new file mode 100644 index 00000000..7b4b34cf --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/SlotTextManager.java @@ -0,0 +1,68 @@ +package de.hysky.skyblocker.skyblock.item.slottext; + +import de.hysky.skyblocker.skyblock.item.slottext.adders.*; +import de.hysky.skyblocker.utils.Utils; +import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.ingame.HandledScreen; +import net.minecraft.screen.slot.Slot; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +public class SlotTextManager { + private static final SlotTextAdder[] adders = new SlotTextAdder[]{ + new EnchantmentLevelAdder(), + new MinionLevelAdder(), + new PetLevelAdder(), + new SkyblockLevelAdder(), + new SkillLevelAdder(), + new CatacombsLevelAdder.Dungeoneering(), + new CatacombsLevelAdder.DungeonClasses(), + new CatacombsLevelAdder.ReadyUp(), + new RancherBootsSpeedAdder(), + new AttributeShardAdder(), + new PrehistoricEggAdder() + }; + private static final ArrayList<SlotTextAdder> currentScreenAdders = new ArrayList<>(); + + private SlotTextManager() { + } + + public static void init() { + ScreenEvents.AFTER_INIT.register((client, screen, width, height) -> { + if (screen instanceof HandledScreen<?> && Utils.isOnSkyblock()) { + onScreenChange(screen); + ScreenEvents.remove(screen).register(ignored -> currentScreenAdders.clear()); + } + }); + } + + private static void onScreenChange(Screen screen) { + final String title = screen.getTitle().getString(); + for (SlotTextAdder adder : adders) { + if (!adder.isEnabled()) continue; + if (adder.titlePattern == null || adder.titlePattern.matcher(title).find()) { + currentScreenAdders.add(adder); + } + } + } + + /** + * The returned text is rendered on top of the slot. The text will be scaled if it doesn't fit in the slot, + * but 3 characters should be seen as the maximum to keep it readable and in place as it tends to move around when scaled. + * + * @implNote Only the first adder that returns a non-null text will be used. + * The order of the adders remains the same as they were added to the {@link SlotTextManager#adders} array. + */ + @NotNull + public static List<SlotText> getText(Slot slot) { + if (currentScreenAdders.isEmpty()) return List.of(); + for (SlotTextAdder adder : currentScreenAdders) { + List<SlotText> text = adder.getText(slot); + if (!text.isEmpty()) return text; + } + return List.of(); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/TextPosition.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/TextPosition.java new file mode 100644 index 00000000..052b228d --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/TextPosition.java @@ -0,0 +1,8 @@ +package de.hysky.skyblocker.skyblock.item.slottext; + +public enum TextPosition { + TOP_LEFT, + TOP_RIGHT, + BOTTOM_LEFT, + BOTTOM_RIGHT +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/AttributeShards.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/AttributeShardAdder.java index ed650e26..811677d7 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/AttributeShards.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/AttributeShardAdder.java @@ -1,9 +1,22 @@ -package de.hysky.skyblocker.skyblock.item; +package de.hysky.skyblocker.skyblock.item.slottext.adders; +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.skyblock.item.slottext.SlotText; +import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder; +import de.hysky.skyblocker.utils.ItemUtils; +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.jetbrains.annotations.NotNull; -public class AttributeShards { - private static final Object2ObjectOpenHashMap<String, String> ID_2_SHORT_NAME = new Object2ObjectOpenHashMap<>(); +import java.util.List; + +public class AttributeShardAdder extends SlotTextAdder { + private static final Object2ObjectMap<String, String> ID_2_SHORT_NAME = new Object2ObjectOpenHashMap<>(); static { //Weapons @@ -50,10 +63,35 @@ public class AttributeShards { ID_2_SHORT_NAME.put("fishing_speed", "FS"); ID_2_SHORT_NAME.put("hunter", "H"); ID_2_SHORT_NAME.put("trophy_hunter", "TH"); + } + + public AttributeShardAdder() { + super(); + } + + @Override + public @NotNull List<SlotText> getText(Slot slot) { + final ItemStack stack = slot.getStack(); + NbtCompound customData = ItemUtils.getCustomData(stack); + + if (!ItemUtils.getItemId(stack).equals("ATTRIBUTE_SHARD")) return List.of(); + + NbtCompound attributesTag = customData.getCompound("attributes"); + String[] attributes = attributesTag.getKeys().toArray(String[]::new); + + if (attributes.length != 1) return List.of(); + String attributeId = attributes[0]; + int attributeLevel = attributesTag.getInt(attributeId); + String attributeInitials = ID_2_SHORT_NAME.getOrDefault(attributeId, ""); + return List.of( + SlotText.bottomRight(Text.literal(String.valueOf(attributeLevel)).withColor(0x34eb77)), + SlotText.topLeft(Text.literal(attributeInitials).formatted(Formatting.AQUA)) + ); } - public static String getShortName(String id) { - return ID_2_SHORT_NAME.getOrDefault(id, ""); + @Override + public boolean isEnabled() { + return SkyblockerConfigManager.get().general.itemInfoDisplay.attributeShardInfo; } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/CatacombsLevelAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/CatacombsLevelAdder.java new file mode 100644 index 00000000..31e0d110 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/CatacombsLevelAdder.java @@ -0,0 +1,92 @@ +package de.hysky.skyblocker.skyblock.item.slottext.adders; + +import de.hysky.skyblocker.skyblock.item.slottext.SlotText; +import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.apache.commons.lang3.math.NumberUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +//This class is split into 3 inner classes as there are multiple screens for showing catacombs levels, each with different slot ids or different style of showing the level. +//It's still kept in 1 main class for organization purposes. +public class CatacombsLevelAdder { + private CatacombsLevelAdder() { + } + + public static class Dungeoneering extends SlotTextAdder { + public Dungeoneering() { + super("^Dungeoneering"); + } + + @Override + public @NotNull List<SlotText> getText(Slot slot) { + switch (slot.id) { + case 12, 29, 30, 31, 32, 33 -> { + String name = slot.getStack().getName().getString(); + int lastIndex = name.lastIndexOf(' '); + if (lastIndex == -1) return List.of(SlotText.bottomLeft(Text.literal("0").formatted(Formatting.RED))); + String level = name.substring(lastIndex + 1); + if (!NumberUtils.isDigits(level)) return List.of(); //Sanity check, just in case. + return List.of(SlotText.bottomRight(Text.literal(level).formatted(Formatting.RED))); + } + default -> { + return List.of(); + } + } + } + } + + public static class DungeonClasses extends SlotTextAdder { + + public DungeonClasses() { + super("^Dungeon Classes"); //Applies to both screens as they are same in both the placement and the style of the level text. + } + + @Override + public @NotNull List<SlotText> getText(Slot slot) { + switch (slot.id) { + case 11, 12, 13, 14, 15 -> { + String level = getBracketedLevelFromName(slot.getStack()); + if (!NumberUtils.isDigits(level)) return List.of(); + return List.of(SlotText.bottomLeft(Text.literal(level).formatted(Formatting.RED))); + } + default -> { + return List.of(); + } + } + } + } + + public static class ReadyUp extends SlotTextAdder { + + public ReadyUp() { + super("^Ready Up"); + } + + @Override + public @NotNull List<SlotText> getText(Slot slot) { + switch (slot.id) { + case 29, 30, 31, 32, 33 -> { + String level = getBracketedLevelFromName(slot.getStack()); + if (!NumberUtils.isDigits(level)) return List.of(); + return List.of(SlotText.bottomLeft(Text.literal(level).formatted(Formatting.RED))); + } + default -> { + return List.of(); + } + } + } + } + + public static String getBracketedLevelFromName(ItemStack itemStack) { + String name = itemStack.getName().getString(); + if (!name.startsWith("[Lvl ")) return null; + int index = name.indexOf(']'); + if (index == -1) return null; + return name.substring(5, index); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/EnchantmentLevelAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/EnchantmentLevelAdder.java new file mode 100644 index 00000000..9c85ae61 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/EnchantmentLevelAdder.java @@ -0,0 +1,46 @@ +package de.hysky.skyblocker.skyblock.item.slottext.adders; + +import de.hysky.skyblocker.skyblock.item.slottext.SlotText; +import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder; +import de.hysky.skyblocker.utils.ItemUtils; +import de.hysky.skyblocker.utils.RomanNumerals; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class EnchantmentLevelAdder extends SlotTextAdder { + public EnchantmentLevelAdder() { + super(); + } + + @Override + public @NotNull List<SlotText> getText(Slot slot) { + final ItemStack itemStack = slot.getStack(); + if (!itemStack.isOf(Items.ENCHANTED_BOOK)) return List.of(); + String name = itemStack.getName().getString(); + if (name.equals("Enchanted Book")) { + NbtCompound nbt = ItemUtils.getCustomData(itemStack); + if (nbt.isEmpty() || !nbt.contains("enchantments", NbtElement.COMPOUND_TYPE)) return List.of(); + NbtCompound enchantments = nbt.getCompound("enchantments"); + if (enchantments.getSize() != 1) return List.of(); //Only makes sense to display the level when there's one enchant. + int level = enchantments.getInt(enchantments.getKeys().iterator().next()); + return List.of(SlotText.bottomLeft(Text.literal(String.valueOf(level)).formatted(Formatting.GREEN))); + } else { //In bazaar, the books have the enchantment level in the name + int level = getEnchantLevelFromString(name); + if (level == 0) return List.of(); + return List.of(SlotText.bottomLeft(Text.literal(String.valueOf(level)).formatted(Formatting.GREEN))); + } + } + + private static int getEnchantLevelFromString(String str) { + String romanNumeral = str.substring(str.lastIndexOf(' ') + 1); //+1 because we don't need the space itself + return RomanNumerals.romanToDecimal(romanNumeral); //Temporary line. The method will be moved out later. + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/MinionLevelAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/MinionLevelAdder.java new file mode 100644 index 00000000..b54b6a89 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/MinionLevelAdder.java @@ -0,0 +1,31 @@ +package de.hysky.skyblocker.skyblock.item.slottext.adders; + +import de.hysky.skyblocker.skyblock.item.slottext.SlotText; +import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder; +import de.hysky.skyblocker.utils.RomanNumerals; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class MinionLevelAdder extends SlotTextAdder { + public MinionLevelAdder() { + super(); + } + + @Override + public @NotNull List<SlotText> getText(Slot slot) { + ItemStack itemStack = slot.getStack(); + if (!itemStack.isOf(Items.PLAYER_HEAD)) return List.of(); + String name = itemStack.getName().getString(); + if (!name.contains("Minion")) return List.of(); + String romanNumeral = name.substring(name.lastIndexOf(' ') + 1); //+1 because we don't need the space itself + int level = RomanNumerals.romanToDecimal(romanNumeral); + if (level == 0) return List.of(); + return List.of(SlotText.topRight(Text.literal(String.valueOf(level)).formatted(Formatting.AQUA))); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PetLevelAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PetLevelAdder.java new file mode 100644 index 00000000..3813563a --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PetLevelAdder.java @@ -0,0 +1,28 @@ +package de.hysky.skyblocker.skyblock.item.slottext.adders; + +import de.hysky.skyblocker.skyblock.item.slottext.SlotText; +import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.apache.commons.lang3.math.NumberUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class PetLevelAdder extends SlotTextAdder { + public PetLevelAdder() { + super(); + } + + @Override + public @NotNull List<SlotText> getText(Slot slot) { + ItemStack itemStack = slot.getStack(); + if (!itemStack.isOf(Items.PLAYER_HEAD)) return List.of(); + String level = CatacombsLevelAdder.getBracketedLevelFromName(itemStack); + if (!NumberUtils.isDigits(level)) return List.of(); + return List.of(SlotText.topLeft(Text.literal(level).formatted(Formatting.GOLD))); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PrehistoricEggAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PrehistoricEggAdder.java new file mode 100644 index 00000000..a157efee --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PrehistoricEggAdder.java @@ -0,0 +1,34 @@ +package de.hysky.skyblocker.skyblock.item.slottext.adders; + +import de.hysky.skyblocker.skyblock.item.slottext.SlotText; +import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder; +import de.hysky.skyblocker.utils.ItemUtils; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class PrehistoricEggAdder extends SlotTextAdder { + @Override + public @NotNull List<SlotText> getText(Slot slot) { + final ItemStack stack = slot.getStack(); + if (!stack.isOf(Items.PLAYER_HEAD) || !StringUtils.equals(stack.getSkyblockId(), "PREHISTORIC_EGG")) return List.of(); + NbtCompound nbt = ItemUtils.getCustomData(stack); + if (!nbt.contains("blocks_walked", NbtElement.INT_TYPE)) return List.of(); + int walked = nbt.getInt("blocks_walked"); + + String walkedstr; + if (walked < 1000) walkedstr = String.valueOf(walked); + else if (walked < 10000) walkedstr = String.format("%.1fk", walked/1000.0f); + else walkedstr = walked / 1000 + "k"; + + return List.of(SlotText.bottomLeft(Text.literal(walkedstr).formatted(Formatting.GOLD))); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/RancherBootsSpeedAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/RancherBootsSpeedAdder.java new file mode 100644 index 00000000..1f92fb8a --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/RancherBootsSpeedAdder.java @@ -0,0 +1,36 @@ +package de.hysky.skyblocker.skyblock.item.slottext.adders; + +import de.hysky.skyblocker.skyblock.item.slottext.SlotText; +import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder; +import de.hysky.skyblocker.utils.ItemUtils; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class RancherBootsSpeedAdder extends SlotTextAdder { + private static final Pattern SPEED_PATTERN = Pattern.compile("Current Speed Cap: (\\d+) ?(\\d+)?"); + + public RancherBootsSpeedAdder() { + super(); + } + + @Override + public @NotNull List<SlotText> getText(Slot slot) { + final ItemStack itemStack = slot.getStack(); + // V null-safe equals. + if (!itemStack.isOf(Items.LEATHER_BOOTS) && !StringUtils.equals(itemStack.getSkyblockId(), "RANCHERS_BOOTS")) return List.of(); + Matcher matcher = ItemUtils.getLoreLineIfMatch(slot.getStack(), SPEED_PATTERN); + if (matcher == null) return List.of(); + String speed = matcher.group(2); + if (speed == null) speed = matcher.group(1); //2nd group only matches when the speed cap is set to a number beyond the player's actual speed cap. + return List.of(SlotText.bottomLeft(Text.literal(speed).formatted(Formatting.GREEN))); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/SkillLevelAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/SkillLevelAdder.java new file mode 100644 index 00000000..095982af --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/SkillLevelAdder.java @@ -0,0 +1,34 @@ +package de.hysky.skyblocker.skyblock.item.slottext.adders; + +import de.hysky.skyblocker.skyblock.item.slottext.SlotText; +import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder; +import de.hysky.skyblocker.utils.RomanNumerals; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class SkillLevelAdder extends SlotTextAdder { + public SkillLevelAdder() { + super("^Your Skills"); + } + + @Override + public @NotNull List<SlotText> getText(Slot slot) { + switch (slot.id) { + case 19, 20, 21, 22, 23, 24, 25, 29, 30, 31, 32 -> { //These are the slots that contain the skill items. Note that they aren't continuous, as there are 2 rows. + String name = slot.getStack().getName().getString(); + int lastIndex = name.lastIndexOf(' '); + if (lastIndex == -1) return List.of(SlotText.bottomLeft(Text.literal("0").formatted(Formatting.LIGHT_PURPLE))); //Skills without any levels don't display any roman numerals. Probably because 0 doesn't exist. + String romanNumeral = name.substring(lastIndex + 1); //+1 because we don't need the space itself + //The "romanNumeral" might be a latin numeral, too. There's a skyblock setting for this, so we have to do it this way V + return List.of(SlotText.bottomLeft(Text.literal(String.valueOf(RomanNumerals.isValidRomanNumeral(romanNumeral) ? RomanNumerals.romanToDecimal(romanNumeral) : Integer.parseInt(romanNumeral))).formatted(Formatting.LIGHT_PURPLE))); + } + default -> { + return List.of(); + } + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/SkyblockLevelAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/SkyblockLevelAdder.java new file mode 100644 index 00000000..8b528508 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/SkyblockLevelAdder.java @@ -0,0 +1,29 @@ +package de.hysky.skyblocker.skyblock.item.slottext.adders; + +import de.hysky.skyblocker.skyblock.item.slottext.SlotText; +import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder; +import de.hysky.skyblocker.utils.ItemUtils; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import org.apache.commons.lang3.math.NumberUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class SkyblockLevelAdder extends SlotTextAdder { + public SkyblockLevelAdder() { + super("^SkyBlock Menu"); + } + + @Override + public @NotNull List<SlotText> getText(Slot slot) { + if (slot.getIndex() != 22) return List.of(); + List<Text> lore = ItemUtils.getLore(slot.getStack()); + if (lore.isEmpty()) return List.of(); + List<Text> siblings = lore.getFirst().getSiblings(); + if (siblings.size() < 3) return List.of(); + Text levelText = siblings.get(2); //The 3rd child is the level text itself + if (!NumberUtils.isDigits(levelText.getString())) return List.of(); + return List.of(SlotText.bottomLeft(levelText)); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java index 8798a139..992206ad 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/AccessoriesHelper.java @@ -108,7 +108,7 @@ public class AccessoriesHelper { .put(page, new ObjectOpenHashSet<>(accessoryIds)); } - static Pair<AccessoryReport, String> calculateReport4Accessory(String accessoryId) { + public static Pair<AccessoryReport, String> calculateReport4Accessory(String accessoryId) { if (!ACCESSORY_DATA.containsKey(accessoryId) || Utils.getProfileId().isEmpty()) return Pair.of(AccessoryReport.INELIGIBLE, null); Accessory accessory = ACCESSORY_DATA.get(accessoryId); @@ -208,7 +208,7 @@ public class AccessoriesHelper { } } - enum AccessoryReport { + public enum AccessoryReport { HAS_HIGHEST_TIER, //You've collected the highest tier - Collected IS_GREATER_TIER, //This accessory is an upgrade from the one in the same family that you already have - Upgrade -- Shows you what tier this accessory is in its family HAS_GREATER_TIER, //This accessory has a higher tier upgrade - Upgradable -- Shows you the highest tier accessory you've collected in that family diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/CompactorDeletorPreview.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/CompactorDeletorPreview.java index c5279c61..b93ca77a 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/CompactorDeletorPreview.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/CompactorDeletorPreview.java @@ -7,7 +7,6 @@ import it.unimi.dsi.fastutil.ints.IntIntPair; import it.unimi.dsi.fastutil.ints.IntObjectPair; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.tooltip.HoveredTooltipPositioner; import net.minecraft.client.gui.tooltip.TooltipComponent; import net.minecraft.item.ItemStack; @@ -34,8 +33,7 @@ public class CompactorDeletorPreview { public static final Pattern NAME = Pattern.compile("PERSONAL_(?<type>COMPACTOR|DELETOR)_(?<size>\\d+)"); private static final MinecraftClient client = MinecraftClient.getInstance(); - public static boolean drawPreview(DrawContext context, ItemStack stack, String type, String size, int x, int y) { - List<Text> tooltips = Screen.getTooltipFromItem(client, stack); + public static boolean drawPreview(DrawContext context, ItemStack stack, List<Text> tooltips, String type, String size, int x, int y) { int targetIndex = getTargetIndex(tooltips); if (targetIndex == -1) return false; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ExoticTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ExoticTooltip.java deleted file mode 100644 index 46babc8b..00000000 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ExoticTooltip.java +++ /dev/null @@ -1,96 +0,0 @@ -package de.hysky.skyblocker.skyblock.item.tooltip; - -import de.hysky.skyblocker.utils.Constants; -import net.minecraft.nbt.NbtCompound; -import net.minecraft.text.MutableText; -import net.minecraft.text.Text; -import net.minecraft.util.Formatting; -import net.minecraft.util.StringIdentifiable; - -public class ExoticTooltip { - public static String getExpectedHex(String id) { - String color = TooltipInfoType.COLOR.getData().get(id).getAsString(); - if (color != null) { - String[] RGBValues = color.split(","); - return String.format("%02X%02X%02X", Integer.parseInt(RGBValues[0]), Integer.parseInt(RGBValues[1]), Integer.parseInt(RGBValues[2])); - } else { - ItemTooltip.LOGGER.warn("[Skyblocker Exotics] No expected color data found for id {}", id); - return null; - } - } - - public static boolean isException(String id, String hex) { - if (id.startsWith("LEATHER") || id.equals("GHOST_BOOTS") || Constants.SEYMOUR_IDS.contains(id)) { - return true; - } - if (id.startsWith("RANCHER")) { - return Constants.RANCHERS.contains(hex); - } - if (id.contains("ADAPTIVE_CHESTPLATE")) { - return Constants.ADAPTIVE_CHEST.contains(hex); - } else if (id.contains("ADAPTIVE")) { - return Constants.ADAPTIVE.contains(hex); - } - if (id.startsWith("REAPER")) { - return Constants.REAPER.contains(hex); - } - if (id.startsWith("FAIRY")) { - return Constants.FAIRY_HEXES.contains(hex); - } - if (id.startsWith("CRYSTAL")) { - return Constants.CRYSTAL_HEXES.contains(hex); - } - if (id.contains("SPOOK")) { - return Constants.SPOOK.contains(hex); - } - return false; - } - - public static DyeType checkDyeType(String hex) { - if (Constants.CRYSTAL_HEXES.contains(hex)) { - return DyeType.CRYSTAL; - } - if (Constants.FAIRY_HEXES.contains(hex)) { - return DyeType.FAIRY; - } - if (Constants.OG_FAIRY_HEXES.contains(hex)) { - return DyeType.OG_FAIRY; - } - if (Constants.SPOOK.contains(hex)) { - return DyeType.SPOOK; - } - if (Constants.GLITCHED.contains(hex)) { - return DyeType.GLITCHED; - } - return DyeType.EXOTIC; - } - - public static boolean intendedDyed(NbtCompound customData) { - return customData.contains("dye_item"); - } - - public enum DyeType implements StringIdentifiable { - CRYSTAL("crystal", Formatting.AQUA), - FAIRY("fairy", Formatting.LIGHT_PURPLE), - OG_FAIRY("og_fairy", Formatting.DARK_PURPLE), - SPOOK("spook", Formatting.RED), - GLITCHED("glitched", Formatting.BLUE), - EXOTIC("exotic", Formatting.GOLD); - private final String name; - private final Formatting formatting; - - DyeType(String name, Formatting formatting) { - this.name = name; - this.formatting = formatting; - } - - @Override - public String asString() { - return name; - } - - public MutableText getTranslatedText() { - return Text.translatable("skyblocker.exotic." + name).formatted(formatting); - } - } -} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java index 031817ac..e6a364e4 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java @@ -1,260 +1,28 @@ package de.hysky.skyblocker.skyblock.item.tooltip; -import com.google.gson.JsonObject; -import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.config.configs.GeneralConfig; -import de.hysky.skyblocker.skyblock.item.MuseumItemCache; -import de.hysky.skyblocker.skyblock.item.tooltip.AccessoriesHelper.AccessoryReport; import de.hysky.skyblocker.utils.Constants; -import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Utils; import de.hysky.skyblocker.utils.scheduler.Scheduler; -import it.unimi.dsi.fastutil.Pair; import net.minecraft.client.MinecraftClient; -import net.minecraft.client.item.TooltipType; -import net.minecraft.component.DataComponentTypes; -import net.minecraft.component.type.DyedColorComponent; -import net.minecraft.item.Item; -import net.minecraft.item.ItemStack; -import net.minecraft.nbt.NbtCompound; -import net.minecraft.nbt.NbtElement; -import net.minecraft.text.MutableText; import net.minecraft.text.Text; import net.minecraft.util.Formatting; - import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; import java.util.concurrent.CompletableFuture; public class ItemTooltip { protected static final Logger LOGGER = LoggerFactory.getLogger(ItemTooltip.class.getName()); private static final MinecraftClient client = MinecraftClient.getInstance(); - protected static final GeneralConfig.ItemTooltip config = SkyblockerConfigManager.get().general.itemTooltip; + public static final GeneralConfig.ItemTooltip config = SkyblockerConfigManager.get().general.itemTooltip; private static volatile boolean sentNullWarning = false; - public static void getTooltip(ItemStack stack, Item.TooltipContext tooltipContext, TooltipType tooltipType, List<Text> lines) { - if (!Utils.isOnSkyblock() || client.player == null) return; - - smoothenLines(lines); - - String name = getInternalNameFromNBT(stack, false); - String internalID = getInternalNameFromNBT(stack, true); - String neuName = name; - if (name == null || internalID == null) return; - - if (name.startsWith("ISSHINY_")) { - name = "SHINY_" + internalID; - neuName = internalID; - } - - if (lines.isEmpty()) { - return; - } - - int count = stack.getCount(); - boolean bazaarOpened = lines.stream().anyMatch(each -> each.getString().contains("Buy price:") || each.getString().contains("Sell price:")); - - if (TooltipInfoType.NPC.isTooltipEnabledAndHasOrNullWarning(internalID)) { - lines.add(Text.literal(String.format("%-21s", "NPC Sell Price:")) - .formatted(Formatting.YELLOW) - .append(getCoinsMessage(TooltipInfoType.NPC.getData().get(internalID).getAsDouble(), count))); - } - - boolean bazaarExist = false; - - if (TooltipInfoType.BAZAAR.isTooltipEnabledAndHasOrNullWarning(name) && !bazaarOpened) { - JsonObject getItem = TooltipInfoType.BAZAAR.getData().getAsJsonObject(name); - lines.add(Text.literal(String.format("%-18s", "Bazaar buy Price:")) - .formatted(Formatting.GOLD) - .append(getItem.get("buyPrice").isJsonNull() - ? Text.literal("No data").formatted(Formatting.RED) - : getCoinsMessage(getItem.get("buyPrice").getAsDouble(), count))); - lines.add(Text.literal(String.format("%-19s", "Bazaar sell Price:")) - .formatted(Formatting.GOLD) - .append(getItem.get("sellPrice").isJsonNull() - ? Text.literal("No data").formatted(Formatting.RED) - : getCoinsMessage(getItem.get("sellPrice").getAsDouble(), count))); - bazaarExist = true; - } - - // bazaarOpened & bazaarExist check for lbin, because Skytils keeps some bazaar item data in lbin api - boolean lbinExist = false; - if (TooltipInfoType.LOWEST_BINS.isTooltipEnabledAndHasOrNullWarning(name) && !bazaarOpened && !bazaarExist) { - lines.add(Text.literal(String.format("%-19s", "Lowest BIN Price:")) - .formatted(Formatting.GOLD) - .append(getCoinsMessage(TooltipInfoType.LOWEST_BINS.getData().get(name).getAsDouble(), count))); - lbinExist = true; - } - - if (SkyblockerConfigManager.get().general.itemTooltip.enableAvgBIN) { - if (TooltipInfoType.ONE_DAY_AVERAGE.getData() == null || TooltipInfoType.THREE_DAY_AVERAGE.getData() == null) { - nullWarning(); - } else { - /* - We are skipping check average prices for potions, runes - and enchanted books because there is no data for their in API. - */ - neuName = getNeuName(internalID, neuName); - - if (!neuName.isEmpty() && lbinExist) { - GeneralConfig.Average type = config.avg; - - // "No data" line because of API not keeping old data, it causes NullPointerException - if (type == GeneralConfig.Average.ONE_DAY || type == GeneralConfig.Average.BOTH) { - lines.add( - Text.literal(String.format("%-19s", "1 Day Avg. Price:")) - .formatted(Formatting.GOLD) - .append(TooltipInfoType.ONE_DAY_AVERAGE.getData().get(neuName) == null - ? Text.literal("No data").formatted(Formatting.RED) - : getCoinsMessage(TooltipInfoType.ONE_DAY_AVERAGE.getData().get(neuName).getAsDouble(), count) - ) - ); - } - if (type == GeneralConfig.Average.THREE_DAY || type == GeneralConfig.Average.BOTH) { - lines.add( - Text.literal(String.format("%-19s", "3 Day Avg. Price:")) - .formatted(Formatting.GOLD) - .append(TooltipInfoType.THREE_DAY_AVERAGE.getData().get(neuName) == null - ? Text.literal("No data").formatted(Formatting.RED) - : getCoinsMessage(TooltipInfoType.THREE_DAY_AVERAGE.getData().get(neuName).getAsDouble(), count) - ) - ); - } - } - } - } - - final Map<Integer, String> itemTierFloors = Map.ofEntries( - Map.entry(0, "E"), - Map.entry(1, "F1"), - Map.entry(2, "F2"), - Map.entry(3, "F3"), - Map.entry(4, "F4/M1"), - Map.entry(5, "F5/M2"), - Map.entry(6, "F6/M3"), - Map.entry(7, "F7/M4"), - Map.entry(8, "M5"), - Map.entry(9, "M6"), - Map.entry(10, "M7") - ); - - if (SkyblockerConfigManager.get().general.itemTooltip.dungeonQuality) { - NbtCompound customData = ItemUtils.getCustomData(stack); - if (customData != null && customData.contains("baseStatBoostPercentage")) { - int baseStatBoostPercentage = customData.getInt("baseStatBoostPercentage"); - boolean maxQuality = baseStatBoostPercentage == 50; - if (maxQuality) { - lines.add(Text.literal(String.format("%-17s", "Item Quality:") + baseStatBoostPercentage + "/50").formatted(Formatting.RED).formatted(Formatting.BOLD)); - } else { - lines.add(Text.literal(String.format("%-21s", "Item Quality:") + baseStatBoostPercentage + "/50").formatted(Formatting.BLUE)); - } - if (customData.contains("item_tier")) { // sometimes it just isn't here? - int itemTier = customData.getInt("item_tier"); - if (maxQuality) { - lines.add(Text.literal(String.format("%-17s", "Floor Tier:") + itemTier + " (" + itemTierFloors.get(itemTier) + ")").formatted(Formatting.RED).formatted(Formatting.BOLD)); - } else { - lines.add(Text.literal(String.format("%-21s", "Floor Tier:") + itemTier + " (" + itemTierFloors.get(itemTier) + ")").formatted(Formatting.BLUE)); - } - } - } - } - - if (TooltipInfoType.MOTES.isTooltipEnabledAndHasOrNullWarning(internalID)) { - lines.add(Text.literal(String.format("%-20s", "Motes Price:")) - .formatted(Formatting.LIGHT_PURPLE) - .append(getMotesMessage(TooltipInfoType.MOTES.getData().get(internalID).getAsInt(), count))); - } - - if (TooltipInfoType.OBTAINED.isTooltipEnabled()) { - String timestamp = ItemUtils.getTimestamp(stack); - - if (!timestamp.isEmpty()) { - lines.add(Text.literal(String.format("%-21s", "Obtained: ")) - .formatted(Formatting.LIGHT_PURPLE) - .append(Text.literal(timestamp).formatted(Formatting.RED))); - } - } - - if (TooltipInfoType.MUSEUM.isTooltipEnabledAndHasOrNullWarning(internalID) && !bazaarOpened) { - String itemCategory = TooltipInfoType.MUSEUM.getData().get(internalID).getAsString(); - String format = switch (itemCategory) { - case "Weapons" -> "%-18s"; - case "Armor" -> "%-19s"; - default -> "%-20s"; - }; - - //Special case the special category so that it doesn't always display not donated - if (itemCategory.equals("Special")) { - lines.add(Text.literal(String.format(format, "Museum: (" + itemCategory + ")")) - .formatted(Formatting.LIGHT_PURPLE)); - } else { - NbtCompound customData = ItemUtils.getCustomData(stack); - boolean isInMuseum = (customData.contains("donated_museum") && customData.getBoolean("donated_museum")) || MuseumItemCache.hasItemInMuseum(internalID); - - Formatting donatedIndicatorFormatting = isInMuseum ? Formatting.GREEN : Formatting.RED; - - lines.add(Text.literal(String.format(format, "Museum (" + itemCategory + "):")) - .formatted(Formatting.LIGHT_PURPLE) - .append(Text.literal(isInMuseum ? "✔" : "✖").formatted(donatedIndicatorFormatting, Formatting.BOLD)) - .append(Text.literal(isInMuseum ? " Donated" : " Not Donated").formatted(donatedIndicatorFormatting))); - } - } - - if (TooltipInfoType.COLOR.isTooltipEnabledAndHasOrNullWarning(internalID) && stack.contains(DataComponentTypes.DYED_COLOR)) { - String uuid = ItemUtils.getItemUuid(stack); - boolean hasCustomDye = SkyblockerConfigManager.get().general.customDyeColors.containsKey(uuid) || SkyblockerConfigManager.get().general.customAnimatedDyes.containsKey(uuid); - //DyedColorComponent#getColor returns ARGB so we mask out the alpha bits - int dyeColor = DyedColorComponent.getColor(stack, 0); - - // dyeColor will have alpha = 255 if it's dyed, and alpha = 0 if it's not dyed, - if (!hasCustomDye && dyeColor != 0) { - dyeColor = dyeColor & 0x00FFFFFF; - String colorHex = String.format("%06X", dyeColor); - String expectedHex = ExoticTooltip.getExpectedHex(internalID); - - boolean correctLine = false; - for (Text text : lines) { - String existingTooltip = text.getString() + " "; - if (existingTooltip.startsWith("Color: ")) { - correctLine = true; - - addExoticTooltip(lines, internalID, ItemUtils.getCustomData(stack), colorHex, expectedHex, existingTooltip); - break; - } - } - - if (!correctLine) { - addExoticTooltip(lines, internalID, ItemUtils.getCustomData(stack), colorHex, expectedHex, ""); - } - } - } - - if (TooltipInfoType.ACCESSORIES.isTooltipEnabledAndHasOrNullWarning(internalID)) { - Pair<AccessoryReport, String> report = AccessoriesHelper.calculateReport4Accessory(internalID); - - if (report.left() != AccessoryReport.INELIGIBLE) { - MutableText title = Text.literal(String.format("%-19s", "Accessory: ")).withColor(0xf57542); - - Text stateText = switch (report.left()) { - case HAS_HIGHEST_TIER -> Text.literal("✔ Collected").formatted(Formatting.GREEN); - case IS_GREATER_TIER -> Text.literal("✦ Upgrade ").withColor(0x218bff).append(Text.literal(report.right()).withColor(0xf8f8ff)); - case HAS_GREATER_TIER -> Text.literal("↑ Upgradable ").withColor(0xf8d048).append(Text.literal(report.right()).withColor(0xf8f8ff)); - case OWNS_BETTER_TIER -> Text.literal("↓ Downgrade ").formatted(Formatting.GRAY).append(Text.literal(report.right()).withColor(0xf8f8ff)); - case MISSING -> Text.literal("✖ Missing ").formatted(Formatting.RED).append(Text.literal(report.right()).withColor(0xf8f8ff)); - - //Should never be the case - default -> Text.literal("? Unknown").formatted(Formatting.GRAY); - }; - - lines.add(title.append(stateText)); - } - } - } - @NotNull public static String getNeuName(String internalID, String neuName) { switch (internalID) { @@ -281,13 +49,6 @@ public class ItemTooltip { return neuName; } - private static void addExoticTooltip(List<Text> lines, String internalID, NbtCompound customData, String colorHex, String expectedHex, String existingTooltip) { - if (expectedHex != null && !colorHex.equalsIgnoreCase(expectedHex) && !ExoticTooltip.isException(internalID, colorHex) && !ExoticTooltip.intendedDyed(customData)) { - final ExoticTooltip.DyeType type = ExoticTooltip.checkDyeType(colorHex); - lines.add(1, Text.literal(existingTooltip + Formatting.DARK_GRAY + "(").append(type.getTranslatedText()).append(Formatting.DARK_GRAY + ")")); - } - } - public static void nullWarning() { if (!sentNullWarning && client.player != null) { LOGGER.warn(Constants.PREFIX.get().append(Text.translatable("skyblocker.itemTooltip.nullMessage")).getString()); @@ -295,69 +56,7 @@ public class ItemTooltip { } } - // TODO What in the world is this? - public static String getInternalNameFromNBT(ItemStack stack, boolean internalIDOnly) { - NbtCompound customData = ItemUtils.getCustomData(stack); - - if (customData == null || !customData.contains(ItemUtils.ID, NbtElement.STRING_TYPE)) { - return null; - } - String internalName = customData.getString(ItemUtils.ID); - - if (internalIDOnly) { - return internalName; - } - - // Transformation to API format. - if (customData.contains("is_shiny")) { - return "ISSHINY_" + internalName; - } - - switch (internalName) { - case "ENCHANTED_BOOK" -> { - if (customData.contains("enchantments")) { - NbtCompound enchants = customData.getCompound("enchantments"); - Optional<String> firstEnchant = enchants.getKeys().stream().findFirst(); - String enchant = firstEnchant.orElse(""); - return "ENCHANTMENT_" + enchant.toUpperCase(Locale.ENGLISH) + "_" + enchants.getInt(enchant); - } - } - case "PET" -> { - if (customData.contains("petInfo")) { - JsonObject petInfo = SkyblockerMod.GSON.fromJson(customData.getString("petInfo"), JsonObject.class); - return "LVL_1_" + petInfo.get("tier").getAsString() + "_" + petInfo.get("type").getAsString(); - } - } - case "POTION" -> { - String enhanced = customData.contains("enhanced") ? "_ENHANCED" : ""; - String extended = customData.contains("extended") ? "_EXTENDED" : ""; - String splash = customData.contains("splash") ? "_SPLASH" : ""; - if (customData.contains("potion") && customData.contains("potion_level")) { - return (customData.getString("potion") + "_" + internalName + "_" + customData.getInt("potion_level") - + enhanced + extended + splash).toUpperCase(Locale.ENGLISH); - } - } - case "RUNE" -> { - if (customData.contains("runes")) { - NbtCompound runes = customData.getCompound("runes"); - Optional<String> firstRunes = runes.getKeys().stream().findFirst(); - String rune = firstRunes.orElse(""); - return rune.toUpperCase(Locale.ENGLISH) + "_RUNE_" + runes.getInt(rune); - } - } - case "ATTRIBUTE_SHARD" -> { - if (customData.contains("attributes")) { - NbtCompound shards = customData.getCompound("attributes"); - Optional<String> firstShards = shards.getKeys().stream().findFirst(); - String shard = firstShards.orElse(""); - return internalName + "-" + shard.toUpperCase(Locale.ENGLISH) + "_" + shards.getInt(shard); - } - } - } - return internalName; - } - - private static Text getCoinsMessage(double price, int count) { + public static Text getCoinsMessage(double price, int count) { // Format the price string once String priceString = String.format(Locale.ENGLISH, "%1$,.1f", price); @@ -368,47 +67,9 @@ public class ItemTooltip { // If count is greater than 1, include the "each" information String priceStringTotal = String.format(Locale.ENGLISH, "%1$,.1f", price * count); - MutableText message = Text.literal(priceStringTotal + " Coins ").formatted(Formatting.DARK_AQUA); - message.append(Text.literal("(" + priceString + " each)").formatted(Formatting.GRAY)); - - return message; - } - - private static Text getMotesMessage(int price, int count) { - float motesMultiplier = SkyblockerConfigManager.get().otherLocations.rift.mcGrubberStacks * 0.05f + 1; - - // Calculate the total price - int totalPrice = price * count; - String totalPriceString = String.format(Locale.ENGLISH, "%1$,.1f", totalPrice * motesMultiplier); - - // If count is 1, return a simple message - if (count == 1) { - return Text.literal(totalPriceString.replace(".0", "") + " Motes").formatted(Formatting.DARK_AQUA); - } - - // If count is greater than 1, include the "each" information - String eachPriceString = String.format(Locale.ENGLISH, "%1$,.1f", price * motesMultiplier); - MutableText message = Text.literal(totalPriceString.replace(".0", "") + " Motes ").formatted(Formatting.DARK_AQUA); - message.append(Text.literal("(" + eachPriceString.replace(".0", "") + " each)").formatted(Formatting.GRAY)); - - return message; - } - - //This is static to not create a new text object for each line in every item - private static final Text BUMPY_LINE = Text.literal("-----------------").formatted(Formatting.DARK_GRAY, Formatting.STRIKETHROUGH); - - private static void smoothenLines(List<Text> lines) { - for (int i = 0; i < lines.size(); i++) { - List<Text> lineSiblings = lines.get(i).getSiblings(); - //Compare the first sibling rather than the whole object as the style of the root object can change while visually staying the same - if (lineSiblings.size() == 1 && lineSiblings.getFirst().equals(BUMPY_LINE)) { - lines.set(i, createSmoothLine()); - } - } - } - public static Text createSmoothLine() { - return Text.literal(" ").formatted(Formatting.DARK_GRAY, Formatting.STRIKETHROUGH, Formatting.BOLD); + return Text.literal(priceStringTotal + " Coins ").formatted(Formatting.DARK_AQUA) + .append(Text.literal("(" + priceString + " each)").formatted(Formatting.GRAY)); } // If these options is true beforehand, the client will get first data of these options while loading. @@ -456,4 +117,4 @@ public class ItemTooltip { }); }, 1200, true); } -} +}
\ No newline at end of file diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipAdder.java new file mode 100644 index 00000000..7c43957e --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipAdder.java @@ -0,0 +1,48 @@ +package de.hysky.skyblocker.skyblock.item.tooltip; + +import de.hysky.skyblocker.skyblock.ChestValue; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; + +import java.util.List; +import java.util.regex.Pattern; + +/** + * Extend this class and add it to {@link TooltipManager#adders} to add additional text to tooltips. + */ +public abstract class TooltipAdder { + /** + * The title of the screen must match this pattern for this adder to be applied. Null means it will be applied to all screens. + * @implNote Don't end your regex with a {@code $} as {@link ChestValue} appends text to the end of the title, + * so the regex will stop matching if the player uses it. + */ + public final Pattern titlePattern; + /** + * The priority of this adder. Lower priority means it will be applied first. + * @apiNote Consider taking this value on your class' constructor and setting it from {@link TooltipManager#adders} to make it easy to read and maintain. + */ + public final int priority; + + protected TooltipAdder(String titlePattern, int priority) { + this(Pattern.compile(titlePattern), priority); + } + + protected TooltipAdder(Pattern titlePattern, int priority) { + this.titlePattern = titlePattern; + this.priority = priority; + } + + /** + * Creates a TooltipAdder that will be applied to all screens. + */ + protected TooltipAdder(int priority) { + this.titlePattern = null; + this.priority = priority; + } + + /** + * @implNote The first element of the lines list holds the item's display name, + * as it's a list of all lines that will be displayed in the tooltip. + */ + public abstract void addToTooltip(List<Text> lines, Slot focusedSlot); +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipManager.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipManager.java new file mode 100644 index 00000000..319df71a --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipManager.java @@ -0,0 +1,72 @@ +package de.hysky.skyblocker.skyblock.item.tooltip; + +import de.hysky.skyblocker.skyblock.chocolatefactory.ChocolateFactorySolver; +import de.hysky.skyblocker.skyblock.item.tooltip.adders.*; +import de.hysky.skyblocker.utils.Utils; +import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +public class TooltipManager { + private static final TooltipAdder[] adders = new TooltipAdder[]{ + new LineSmoothener(), // Applies before anything else + new SupercraftReminder(), + new ChocolateFactorySolver.Tooltip(), + new NpcPriceTooltip(1), + new BazaarPriceTooltip(2), + new LBinTooltip(3), + new AvgBinTooltip(4), + new DungeonQualityTooltip(5), + new MotesTooltip(6), + new ObtainedDateTooltip(7), + new MuseumTooltip(8), + new ColorTooltip(9), + new AccessoryTooltip(10), + }; + private static final ArrayList<TooltipAdder> currentScreenAdders = new ArrayList<>(); + + private TooltipManager() { + } + + public static void init() { + ScreenEvents.AFTER_INIT.register((client, screen, width, height) -> { + onScreenChange(screen); + ScreenEvents.remove(screen).register(ignored -> currentScreenAdders.clear()); + }); + } + + private static void onScreenChange(Screen screen) { + final String title = screen.getTitle().getString(); + for (TooltipAdder adder : adders) { + if (adder.titlePattern == null || adder.titlePattern.matcher(title).find()) { + currentScreenAdders.add(adder); + } + } + currentScreenAdders.sort(Comparator.comparingInt(adder -> adder.priority)); + } + + /** + * <p>Adds additional text from all adders that are applicable to the current screen. + * This method is run on each tooltip render, so don't do any heavy calculations here.</p> + * + * <p>If you want to add info to the tooltips of multiple items, consider using a switch statement with {@code focusedSlot.getIndex()}</p> + * + * @param lines The tooltip lines of the focused item. This includes the display name, as it's a part of the tooltip (at index 0). + * @param focusedSlot The slot that is currently focused by the cursor. + * @return The lines list itself after all adders have added their text. + * @deprecated This method is public only for the sake of the mixin. Don't call directly, not that there is any point to it. + */ + @Deprecated + public static List<Text> addToTooltip(List<Text> lines, Slot focusedSlot) { + if (!Utils.isOnSkyblock()) return lines; + for (TooltipAdder adder : currentScreenAdders) { + adder.addToTooltip(lines, focusedSlot); + } + return lines; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/AccessoryTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/AccessoryTooltip.java new file mode 100644 index 00000000..3b150488 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/AccessoryTooltip.java @@ -0,0 +1,43 @@ +package de.hysky.skyblocker.skyblock.item.tooltip.adders; + +import de.hysky.skyblocker.skyblock.item.tooltip.AccessoriesHelper; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType; +import it.unimi.dsi.fastutil.Pair; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +import java.util.List; + +public class AccessoryTooltip extends TooltipAdder { + public AccessoryTooltip(int priority) { + super(priority); + } + + @Override + public void addToTooltip(List<Text> lines, Slot focusedSlot) { + final String internalID = focusedSlot.getStack().getSkyblockId(); + if (TooltipInfoType.ACCESSORIES.isTooltipEnabledAndHasOrNullWarning(internalID)) { + Pair<AccessoriesHelper.AccessoryReport, String> report = AccessoriesHelper.calculateReport4Accessory(internalID); + + if (report.left() != AccessoriesHelper.AccessoryReport.INELIGIBLE) { + MutableText title = Text.literal(String.format("%-19s", "Accessory: ")).withColor(0xf57542); + + Text stateText = switch (report.left()) { + case HAS_HIGHEST_TIER -> Text.literal("✔ Collected").formatted(Formatting.GREEN); + case IS_GREATER_TIER -> Text.literal("✦ Upgrade ").withColor(0x218bff).append(Text.literal(report.right()).withColor(0xf8f8ff)); + case HAS_GREATER_TIER -> Text.literal("↑ Upgradable ").withColor(0xf8d048).append(Text.literal(report.right()).withColor(0xf8f8ff)); + case OWNS_BETTER_TIER -> Text.literal("↓ Downgrade ").formatted(Formatting.GRAY).append(Text.literal(report.right()).withColor(0xf8f8ff)); + case MISSING -> Text.literal("✖ Missing ").formatted(Formatting.RED).append(Text.literal(report.right()).withColor(0xf8f8ff)); + + //Should never be the case + default -> Text.literal("? Unknown").formatted(Formatting.GRAY); + }; + + lines.add(title.append(stateText)); + } + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/AvgBinTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/AvgBinTooltip.java new file mode 100644 index 00000000..a36f30e9 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/AvgBinTooltip.java @@ -0,0 +1,63 @@ +package de.hysky.skyblocker.skyblock.item.tooltip.adders; + +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.config.configs.GeneralConfig; +import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +import java.util.List; + +public class AvgBinTooltip extends TooltipAdder { + public AvgBinTooltip(int priority) { + super(priority); + } + + @Override + public void addToTooltip(List<Text> lines, Slot focusedSlot) { + final ItemStack itemStack = focusedSlot.getStack(); + String neuName = itemStack.getNeuName(); + String internalID = itemStack.getSkyblockId(); + if (neuName == null || internalID == null) return; + + if (SkyblockerConfigManager.get().general.itemTooltip.enableAvgBIN) { + if (TooltipInfoType.ONE_DAY_AVERAGE.getData() == null || TooltipInfoType.THREE_DAY_AVERAGE.getData() == null) { + ItemTooltip.nullWarning(); + } else { + /* + We are skipping check average prices for potions, runes + and enchanted books because there is no data for their in API. + */ + if (!neuName.isEmpty() && LBinTooltip.lbinExist) { + GeneralConfig.Average type = ItemTooltip.config.avg; + + // "No data" line because of API not keeping old data, it causes NullPointerException + if (type == GeneralConfig.Average.ONE_DAY || type == GeneralConfig.Average.BOTH) { + lines.add( + Text.literal(String.format("%-19s", "1 Day Avg. Price:")) + .formatted(Formatting.GOLD) + .append(TooltipInfoType.ONE_DAY_AVERAGE.getData().get(neuName) == null + ? Text.literal("No data").formatted(Formatting.RED) + : ItemTooltip.getCoinsMessage(TooltipInfoType.ONE_DAY_AVERAGE.getData().get(neuName).getAsDouble(), itemStack.getCount()) + ) + ); + } + if (type == GeneralConfig.Average.THREE_DAY || type == GeneralConfig.Average.BOTH) { + lines.add( + Text.literal(String.format("%-19s", "3 Day Avg. Price:")) + .formatted(Formatting.GOLD) + .append(TooltipInfoType.THREE_DAY_AVERAGE.getData().get(neuName) == null + ? Text.literal("No data").formatted(Formatting.RED) + : ItemTooltip.getCoinsMessage(TooltipInfoType.THREE_DAY_AVERAGE.getData().get(neuName).getAsDouble(), itemStack.getCount()) + ) + ); + } + } + } + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/BazaarPriceTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/BazaarPriceTooltip.java new file mode 100644 index 00000000..0aab21c0 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/BazaarPriceTooltip.java @@ -0,0 +1,57 @@ +package de.hysky.skyblocker.skyblock.item.tooltip.adders; + +import com.google.gson.JsonObject; +import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.apache.commons.lang3.math.NumberUtils; + +import java.util.List; + +public class BazaarPriceTooltip extends TooltipAdder { + public static boolean bazaarExist = false; + + public BazaarPriceTooltip(int priority) { + super(priority); + } + + @Override + public void addToTooltip(List<Text> lines, Slot focusedSlot) { + bazaarExist = false; + final ItemStack itemStack = focusedSlot.getStack(); + final String internalID = itemStack.getSkyblockId(); + if (internalID == null) return; + String name = itemStack.getSkyblockApiId(); + if (name == null) return; + + if (name.startsWith("ISSHINY_")) name = "SHINY_" + internalID; + + if (TooltipInfoType.BAZAAR.isTooltipEnabledAndHasOrNullWarning(name)) { + int amount; + if (lines.get(1).getString().endsWith("Sack")) { + //The amount is in the 2nd sibling of the 3rd line of the lore. here V + //Example line: empty[style={color=dark_purple,!italic}, siblings=[literal{Stored: }[style={color=gray}], literal{0}[style={color=dark_gray}], literal{/20k}[style={color=gray}]] + String line = lines.get(3).getSiblings().get(1).getString().replace(",", ""); + amount = NumberUtils.isParsable(line) && !line.equals("0") ? Integer.parseInt(line) : itemStack.getCount(); + } else { + amount = itemStack.getCount(); + } + JsonObject getItem = TooltipInfoType.BAZAAR.getData().getAsJsonObject(name); + lines.add(Text.literal(String.format("%-18s", "Bazaar buy Price:")) + .formatted(Formatting.GOLD) + .append(getItem.get("buyPrice").isJsonNull() + ? Text.literal("No data").formatted(Formatting.RED) + : ItemTooltip.getCoinsMessage(getItem.get("buyPrice").getAsDouble(), amount))); + lines.add(Text.literal(String.format("%-19s", "Bazaar sell Price:")) + .formatted(Formatting.GOLD) + .append(getItem.get("sellPrice").isJsonNull() + ? Text.literal("No data").formatted(Formatting.RED) + : ItemTooltip.getCoinsMessage(getItem.get("sellPrice").getAsDouble(), amount))); + bazaarExist = true; + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/ColorTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/ColorTooltip.java new file mode 100644 index 00000000..2b576be6 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/ColorTooltip.java @@ -0,0 +1,135 @@ +package de.hysky.skyblocker.skyblock.item.tooltip.adders; + +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType; +import de.hysky.skyblocker.utils.Constants; +import de.hysky.skyblocker.utils.ItemUtils; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.component.type.DyedColorComponent; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.StringIdentifiable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +public class ColorTooltip extends TooltipAdder { + private static final Logger LOGGER = LoggerFactory.getLogger(ColorTooltip.class); + + public ColorTooltip(int priority) { + super(priority); + } + + @Override + public void addToTooltip(List<Text> lines, Slot focusedSlot) { + final ItemStack itemStack = focusedSlot.getStack(); + final String internalID = itemStack.getSkyblockId(); + if (TooltipInfoType.COLOR.isTooltipEnabledAndHasOrNullWarning(internalID) && itemStack.contains(DataComponentTypes.DYED_COLOR)) { + String uuid = ItemUtils.getItemUuid(itemStack); + boolean hasCustomDye = SkyblockerConfigManager.get().general.customDyeColors.containsKey(uuid) || SkyblockerConfigManager.get().general.customAnimatedDyes.containsKey(uuid); + //DyedColorComponent#getColor returns ARGB so we mask out the alpha bits + int dyeColor = DyedColorComponent.getColor(itemStack, 0); + + // dyeColor will have alpha = 255 if it's dyed, and alpha = 0 if it's not dyed, + if (!hasCustomDye && dyeColor != 0) { + dyeColor = dyeColor & 0x00FFFFFF; + String colorHex = String.format("%06X", dyeColor); + String expectedHex = getExpectedHex(internalID); + + boolean correctLine = false; + for (Text text : lines) { + String existingTooltip = text.getString() + " "; + if (existingTooltip.startsWith("Color: ")) { + correctLine = true; + + addExoticTooltip(lines, internalID, ItemUtils.getCustomData(itemStack), colorHex, expectedHex, existingTooltip); + break; + } + } + + if (!correctLine) { + addExoticTooltip(lines, internalID, ItemUtils.getCustomData(itemStack), colorHex, expectedHex, ""); + } + } + } + } + + private static void addExoticTooltip(List<Text> lines, String internalID, NbtCompound customData, String colorHex, String expectedHex, String existingTooltip) { + if (expectedHex != null && !colorHex.equalsIgnoreCase(expectedHex) && !isException(internalID, colorHex) && !intendedDyed(customData)) { + final DyeType type = checkDyeType(colorHex); + lines.add(1, Text.literal(existingTooltip + Formatting.DARK_GRAY + "(").append(type.getTranslatedText()).append(Formatting.DARK_GRAY + ")")); + } + } + + public static String getExpectedHex(String id) { + String color = TooltipInfoType.COLOR.getData().get(id).getAsString(); + if (color != null) { + String[] RGBValues = color.split(","); + return String.format("%02X%02X%02X", Integer.parseInt(RGBValues[0]), Integer.parseInt(RGBValues[1]), Integer.parseInt(RGBValues[2])); + } else { + LOGGER.warn("[Skyblocker Exotics] No expected color data found for id {}", id); + return null; + } + } + + public static boolean isException(String id, String hex) { + return switch (id) { + case String it when it.startsWith("LEATHER") || it.equals("GHOST_BOOTS") || Constants.SEYMOUR_IDS.contains(it) -> true; + case String it when it.startsWith("RANCHER") -> Constants.RANCHERS.contains(hex); + case String it when it.contains("ADAPTIVE_CHESTPLATE") -> Constants.ADAPTIVE_CHEST.contains(hex); + case String it when it.contains("ADAPTIVE") -> Constants.ADAPTIVE.contains(hex); + case String it when it.contains("REAPER") -> Constants.REAPER.contains(hex); + case String it when it.contains("FAIRY") -> Constants.FAIRY_HEXES.contains(hex); + case String it when it.contains("CRYSTAL") -> Constants.CRYSTAL_HEXES.contains(hex); + case String it when it.contains("SPOOK") -> Constants.SPOOK.contains(hex); + default -> false; + }; + } + + public static DyeType checkDyeType(String hex) { + return switch (hex) { + case String it when Constants.CRYSTAL_HEXES.contains(it) -> DyeType.CRYSTAL; + case String it when Constants.FAIRY_HEXES.contains(it) -> DyeType.FAIRY; + case String it when Constants.OG_FAIRY_HEXES.contains(it) -> DyeType.OG_FAIRY; + case String it when Constants.SPOOK.contains(it) -> DyeType.SPOOK; + case String it when Constants.GLITCHED.contains(it) -> DyeType.GLITCHED; + default -> DyeType.EXOTIC; + }; + } + + public static boolean intendedDyed(NbtCompound customData) { + return customData.contains("dye_item"); + } + + public enum DyeType implements StringIdentifiable { + CRYSTAL("crystal", Formatting.AQUA), + FAIRY("fairy", Formatting.LIGHT_PURPLE), + OG_FAIRY("og_fairy", Formatting.DARK_PURPLE), + SPOOK("spook", Formatting.RED), + GLITCHED("glitched", Formatting.BLUE), + EXOTIC("exotic", Formatting.GOLD); + private final String name; + private final Formatting formatting; + + DyeType(String name, Formatting formatting) { + this.name = name; + this.formatting = formatting; + } + + @Override + public String asString() { + return name; + } + + public MutableText getTranslatedText() { + return Text.translatable("skyblocker.exotic." + name).formatted(formatting); + } + } + +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/DungeonQualityTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/DungeonQualityTooltip.java new file mode 100644 index 00000000..05e9887c --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/DungeonQualityTooltip.java @@ -0,0 +1,57 @@ +package de.hysky.skyblocker.skyblock.item.tooltip.adders; + +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder; +import de.hysky.skyblocker.utils.ItemUtils; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +import java.util.List; + +public class DungeonQualityTooltip extends TooltipAdder { + public DungeonQualityTooltip(int priority) { + super(priority); + } + + @Override + public void addToTooltip(List<Text> lines, Slot focusedSlot) { + if (!SkyblockerConfigManager.get().general.itemTooltip.dungeonQuality) return; + NbtCompound customData = ItemUtils.getCustomData(focusedSlot.getStack()); + if (customData == null || !customData.contains("baseStatBoostPercentage")) return; + int baseStatBoostPercentage = customData.getInt("baseStatBoostPercentage"); + boolean maxQuality = baseStatBoostPercentage == 50; + if (maxQuality) { + lines.add(Text.literal(String.format("%-17s", "Item Quality:") + baseStatBoostPercentage + "/50").formatted(Formatting.RED).formatted(Formatting.BOLD)); + } else { + lines.add(Text.literal(String.format("%-21s", "Item Quality:") + baseStatBoostPercentage + "/50").formatted(Formatting.BLUE)); + } + + if (customData.contains("item_tier")) { // sometimes it just isn't here? + int itemTier = customData.getInt("item_tier"); + if (maxQuality) { + lines.add(Text.literal(String.format("%-17s", "Floor Tier:") + itemTier + " (" + getItemTierFloor(itemTier) + ")").formatted(Formatting.RED).formatted(Formatting.BOLD)); + } else { + lines.add(Text.literal(String.format("%-21s", "Floor Tier:") + itemTier + " (" + getItemTierFloor(itemTier) + ")").formatted(Formatting.BLUE)); + } + } + } + + final String getItemTierFloor(int tier) { + return switch (tier) { + case 0 -> "E"; + case 1 -> "F1"; + case 2 -> "F2"; + case 3 -> "F3"; + case 4 -> "F4/M1"; + case 5 -> "F5/M2"; + case 6 -> "F6/M3"; + case 7 -> "F7/M4"; + case 8 -> "M5"; + case 9 -> "M6"; + case 10 -> "M7"; + default -> "Unknown"; + }; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/LBinTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/LBinTooltip.java new file mode 100644 index 00000000..45cfe3e4 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/LBinTooltip.java @@ -0,0 +1,40 @@ +package de.hysky.skyblocker.skyblock.item.tooltip.adders; + +import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +import java.util.List; + +public class LBinTooltip extends TooltipAdder { + public static boolean lbinExist = false; + + public LBinTooltip(int priority) { + super(priority); + } + + @Override + public void addToTooltip(List<Text> lines, Slot focusedSlot) { + lbinExist = false; + final ItemStack itemStack = focusedSlot.getStack(); + final String internalID = itemStack.getSkyblockId(); + if (internalID == null) return; + String name = itemStack.getSkyblockApiId(); + if (name == null) return; + + if (name.startsWith("ISSHINY_")) name = "SHINY_" + internalID; + + // bazaarOpened & bazaarExist check for lbin, because Skytils keeps some bazaar item data in lbin api + + if (TooltipInfoType.LOWEST_BINS.isTooltipEnabledAndHasOrNullWarning(name) && !BazaarPriceTooltip.bazaarExist) { + lines.add(Text.literal(String.format("%-19s", "Lowest BIN Price:")) + .formatted(Formatting.GOLD) + .append(ItemTooltip.getCoinsMessage(TooltipInfoType.LOWEST_BINS.getData().get(name).getAsDouble(), itemStack.getCount()))); + lbinExist = true; + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/LineSmoothener.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/LineSmoothener.java new file mode 100644 index 00000000..e3ce12df --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/LineSmoothener.java @@ -0,0 +1,32 @@ +package de.hysky.skyblocker.skyblock.item.tooltip.adders; + +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +import java.util.List; + +public class LineSmoothener extends TooltipAdder { + //This is static to not create a new text object for each line in every item + private static final Text BUMPY_LINE = Text.literal("-----------------").formatted(Formatting.DARK_GRAY, Formatting.STRIKETHROUGH); + + public static Text createSmoothLine() { + return Text.literal(" ").formatted(Formatting.DARK_GRAY, Formatting.STRIKETHROUGH, Formatting.BOLD); + } + + public LineSmoothener() { + super(Integer.MIN_VALUE); + } + + @Override + public void addToTooltip(List<Text> lines, Slot focusedSlot) { + for (int i = 0; i < lines.size(); i++) { + List<Text> lineSiblings = lines.get(i).getSiblings(); + //Compare the first sibling rather than the whole object as the style of the root object can change while visually staying the same + if (lineSiblings.size() == 1 && lineSiblings.getFirst().equals(BUMPY_LINE)) { + lines.set(i, createSmoothLine()); + } + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/MotesTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/MotesTooltip.java new file mode 100644 index 00000000..64640b95 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/MotesTooltip.java @@ -0,0 +1,50 @@ +package de.hysky.skyblocker.skyblock.item.tooltip.adders; + +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +import java.util.List; +import java.util.Locale; + +public class MotesTooltip extends TooltipAdder { + public MotesTooltip(int priority) { + super(priority); + } + + @Override + public void addToTooltip(List<Text> lines, Slot focusedSlot) { + final ItemStack itemStack = focusedSlot.getStack(); + final String internalID = itemStack.getSkyblockId(); + if (internalID != null && TooltipInfoType.MOTES.isTooltipEnabledAndHasOrNullWarning(internalID)) { + lines.add(Text.literal(String.format("%-20s", "Motes Price:")) + .formatted(Formatting.LIGHT_PURPLE) + .append(getMotesMessage(TooltipInfoType.MOTES.getData().get(internalID).getAsInt(), itemStack.getCount()))); + } + } + + private static Text getMotesMessage(int price, int count) { + float motesMultiplier = SkyblockerConfigManager.get().otherLocations.rift.mcGrubberStacks * 0.05f + 1; + + // Calculate the total price + int totalPrice = price * count; + String totalPriceString = String.format(Locale.ENGLISH, "%1$,.1f", totalPrice * motesMultiplier); + + // If count is 1, return a simple message + if (count == 1) { + return Text.literal(totalPriceString.replace(".0", "") + " Motes").formatted(Formatting.DARK_AQUA); + } + + // If count is greater than 1, include the "each" information + String eachPriceString = String.format(Locale.ENGLISH, "%1$,.1f", price * motesMultiplier); + MutableText message = Text.literal(totalPriceString.replace(".0", "") + " Motes ").formatted(Formatting.DARK_AQUA); + message.append(Text.literal("(" + eachPriceString.replace(".0", "") + " each)").formatted(Formatting.GRAY)); + + return message; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/MuseumTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/MuseumTooltip.java new file mode 100644 index 00000000..1c64760a --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/MuseumTooltip.java @@ -0,0 +1,49 @@ +package de.hysky.skyblocker.skyblock.item.tooltip.adders; + +import de.hysky.skyblocker.skyblock.item.MuseumItemCache; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType; +import de.hysky.skyblocker.utils.ItemUtils; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +import java.util.List; + +public class MuseumTooltip extends TooltipAdder { + public MuseumTooltip(int priority) { + super(priority); + } + + @Override + public void addToTooltip(List<Text> lines, Slot focusedSlot) { + final ItemStack itemStack = focusedSlot.getStack(); + final String internalID = itemStack.getSkyblockId(); + if (TooltipInfoType.MUSEUM.isTooltipEnabledAndHasOrNullWarning(internalID)) { + String itemCategory = TooltipInfoType.MUSEUM.getData().get(internalID).getAsString(); + String format = switch (itemCategory) { + case "Weapons" -> "%-18s"; + case "Armor" -> "%-19s"; + default -> "%-20s"; + }; + + //Special case the special category so that it doesn't always display not donated + if (itemCategory.equals("Special")) { + lines.add(Text.literal(String.format(format, "Museum: (" + itemCategory + ")")) + .formatted(Formatting.LIGHT_PURPLE)); + } else { + NbtCompound customData = ItemUtils.getCustomData(itemStack); + boolean isInMuseum = (customData.contains("donated_museum") && customData.getBoolean("donated_museum")) || MuseumItemCache.hasItemInMuseum(internalID); + + Formatting donatedIndicatorFormatting = isInMuseum ? Formatting.GREEN : Formatting.RED; + + lines.add(Text.literal(String.format(format, "Museum (" + itemCategory + "):")) + .formatted(Formatting.LIGHT_PURPLE) + .append(Text.literal(isInMuseum ? "✔" : "✖").formatted(donatedIndicatorFormatting, Formatting.BOLD)) + .append(Text.literal(isInMuseum ? " Donated" : " Not Donated").formatted(donatedIndicatorFormatting))); + } + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/NpcPriceTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/NpcPriceTooltip.java new file mode 100644 index 00000000..3ac7d298 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/NpcPriceTooltip.java @@ -0,0 +1,28 @@ +package de.hysky.skyblocker.skyblock.item.tooltip.adders; + +import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +import java.util.List; + +public class NpcPriceTooltip extends TooltipAdder { + public NpcPriceTooltip(int priority) { + super(priority); + } + + @Override + public void addToTooltip(List<Text> lines, Slot focusedSlot) { + final ItemStack stack = focusedSlot.getStack(); + final String internalID = stack.getSkyblockId(); + if (internalID != null && TooltipInfoType.NPC.isTooltipEnabledAndHasOrNullWarning(internalID)) { + lines.add(Text.literal(String.format("%-21s", "NPC Sell Price:")) + .formatted(Formatting.YELLOW) + .append(ItemTooltip.getCoinsMessage(TooltipInfoType.NPC.getData().get(internalID).getAsDouble(), stack.getCount()))); + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/ObtainedDateTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/ObtainedDateTooltip.java new file mode 100644 index 00000000..9cc03b4d --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/ObtainedDateTooltip.java @@ -0,0 +1,72 @@ +package de.hysky.skyblocker.skyblock.item.tooltip.adders; + +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType; +import de.hysky.skyblocker.utils.ItemUtils; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; +import java.util.List; +import java.util.Locale; + +public class ObtainedDateTooltip extends TooltipAdder { + private static final DateTimeFormatter OBTAINED_DATE_FORMATTER = DateTimeFormatter.ofPattern("MMMM d, yyyy").withZone(ZoneId.systemDefault()).localizedBy(Locale.ENGLISH); + private static final DateTimeFormatter OLD_OBTAINED_DATE_FORMAT = DateTimeFormatter.ofPattern("M/d/yy h:m a").withZone(ZoneId.of("UTC")).localizedBy(Locale.ENGLISH); + + public ObtainedDateTooltip(int priority) { + super(priority); + } + + @Override + public void addToTooltip(List<Text> lines, Slot focusedSlot) { + if (TooltipInfoType.OBTAINED.isTooltipEnabled()) { + String timestamp = getTimestamp(focusedSlot.getStack()); + + if (!timestamp.isEmpty()) { + lines.add(Text.empty() + .append(Text.literal(String.format("%-21s", "Obtained: ")).formatted(Formatting.LIGHT_PURPLE)) + .append(Text.literal(timestamp).formatted(Formatting.RED))); + } + } + } + + /** + * This method converts the "timestamp" variable into the same date format as Hypixel represents it in the museum. + * Currently, there are two types of string timestamps the legacy which is built like this + * "dd/MM/yy hh:mm" ("25/04/20 16:38") and the current which is built like this + * "MM/dd/yy hh:mm aa" ("12/24/20 11:08 PM"). Since Hypixel transforms the two formats into one format without + * taking into account of their formats, we do the same. The final result looks like this + * "MMMM dd, yyyy" (December 24, 2020). + * Since the legacy format has a 25 as "month" SimpleDateFormat converts the 25 into 2 years and 1 month and makes + * "25/04/20 16:38" -> "January 04, 2022" instead of "April 25, 2020". + * This causes the museum rank to be much worse than it should be. + * <p> + * This also handles the long timestamp format introduced in January 2024 where the timestamp is in epoch milliseconds. + * + * @param stack the item under the pointer + * @return if the item have a "Timestamp" it will be shown formated on the tooltip + */ + public static String getTimestamp(ItemStack stack) { + NbtCompound customData = ItemUtils.getCustomData(stack); + + if (customData != null && customData.contains("timestamp", NbtElement.LONG_TYPE)) { + Instant date = Instant.ofEpochMilli(customData.getLong("timestamp")); + return OBTAINED_DATE_FORMATTER.format(date); + } + + if (customData != null && customData.contains("timestamp", NbtElement.STRING_TYPE)) { + TemporalAccessor date = OLD_OBTAINED_DATE_FORMAT.parse(customData.getString("timestamp")); + return OBTAINED_DATE_FORMATTER.format(date); + } + + return ""; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/SupercraftReminder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/SupercraftReminder.java new file mode 100644 index 00000000..8a8f198c --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/SupercraftReminder.java @@ -0,0 +1,30 @@ +package de.hysky.skyblocker.skyblock.item.tooltip.adders; + +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder; +import de.hysky.skyblocker.utils.ItemUtils; +import net.minecraft.item.Items; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +import java.util.List; +import java.util.regex.Pattern; + +public class SupercraftReminder extends TooltipAdder { + private static final byte SUPERCRAFT_SLOT = 32; + private static final byte RECIPE_RESULT_SLOT = 25; + + public SupercraftReminder() { + super(Pattern.compile("^.+ Recipe$"), Integer.MIN_VALUE); + } + + @Override + public void addToTooltip(List<Text> lines, Slot focusedSlot) { + if (focusedSlot.id != SUPERCRAFT_SLOT || !focusedSlot.getStack().isOf(Items.GOLDEN_PICKAXE)) return; + String uuid = ItemUtils.getItemUuid(focusedSlot.inventory.getStack(RECIPE_RESULT_SLOT)); + if (!uuid.isEmpty()) return; //Items with UUID can't be stacked, and therefore the shift-click feature doesn't matter + int index = lines.size() - 1; + if (lines.get(lines.size() - 2).getString().equals("Recipe not unlocked!")) index--; //Place it right below the "Right-Click to set amount" line + lines.add(index, Text.literal("Shift-Click to maximize the amount!").formatted(Formatting.GOLD)); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ResultButtonWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ResultButtonWidget.java index d2d463c7..9e2ec0b3 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ResultButtonWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ResultButtonWidget.java @@ -1,30 +1,29 @@ package de.hysky.skyblocker.skyblock.itemlist; -import java.util.List; -import java.util.ArrayList; - +import de.hysky.skyblocker.mixins.accessors.HandledScreenAccessor; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.ingame.HandledScreen; import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; import net.minecraft.client.gui.widget.ClickableWidget; import net.minecraft.item.ItemStack; -import net.minecraft.item.Items; -import net.minecraft.text.OrderedText; import net.minecraft.text.Text; import net.minecraft.util.Identifier; +import java.util.List; + public class ResultButtonWidget extends ClickableWidget { private static final Identifier BACKGROUND_TEXTURE = new Identifier("recipe_book/slot_craftable"); protected ItemStack itemStack = null; public ResultButtonWidget(int x, int y) { - super(x, y, 25, 25, Text.of("")); + super(x, y, 25, 25, Text.literal("")); } protected void setItemStack(ItemStack itemStack) { - this.active = !itemStack.getItem().equals(Items.AIR); + this.active = !itemStack.isEmpty(); this.visible = true; this.itemStack = itemStack; } @@ -37,29 +36,18 @@ public class ResultButtonWidget extends ClickableWidget { @Override public void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { MinecraftClient client = MinecraftClient.getInstance(); - // this.drawTexture(matrices, this.x, this.y, 29, 206, this.width, this.height); context.drawGuiTexture(BACKGROUND_TEXTURE, this.getX(), this.getY(), this.getWidth(), this.getHeight()); - // client.getItemRenderer().renderInGui(this.itemStack, this.x + 4, this.y + 4); context.drawItem(this.itemStack, this.getX() + 4, this.getY() + 4); - // client.getItemRenderer().renderGuiItemOverlay(client.textRenderer, itemStack, this.x + 4, this.y + 4); context.drawItemInSlot(client.textRenderer, itemStack, this.getX() + 4, this.getY() + 4); } public void renderTooltip(DrawContext context, int mouseX, int mouseY) { MinecraftClient client = MinecraftClient.getInstance(); - List<Text> tooltip = Screen.getTooltipFromItem(client, this.itemStack); - List<OrderedText> orderedTooltip = new ArrayList<>(); - - for(int i = 0; i < tooltip.size(); i++) { - orderedTooltip.add(tooltip.get(i).asOrderedText()); - } - - client.currentScreen.setTooltip(orderedTooltip); + if (client.currentScreen == null) return; + List<Text> tooltip = client.currentScreen instanceof HandledScreen<?> handledScreen ? ((HandledScreenAccessor) handledScreen).invokeGetTooltipFromItem(this.itemStack) : Screen.getTooltipFromItem(client, this.itemStack); + client.currentScreen.setTooltip(tooltip.stream().map(Text::asOrderedText).toList()); } - @Override - protected void appendClickableNarrations(NarrationMessageBuilder builder) { - // TODO Auto-generated method stub - - } + @Override + protected void appendClickableNarrations(NarrationMessageBuilder builder) {} } |