From 9219939d0769b10f4ec504f57d98a60c24247185 Mon Sep 17 00:00:00 2001 From: Rime <81419447+Emirlol@users.noreply.github.com> Date: Thu, 27 Feb 2025 23:50:44 +0300 Subject: Initial (and hopefully last) commit for npc price fix (#1176) --- src/main/java/de/hysky/skyblocker/debug/Debug.java | 6 +- .../item/tooltip/adders/NpcPriceTooltip.java | 53 +++++++--- .../java/de/hysky/skyblocker/utils/ItemUtils.java | 23 +---- .../java/de/hysky/skyblocker/utils/TextUtils.java | 112 +++++++++++++++++++++ 4 files changed, 160 insertions(+), 34 deletions(-) create mode 100644 src/main/java/de/hysky/skyblocker/utils/TextUtils.java (limited to 'src/main/java') diff --git a/src/main/java/de/hysky/skyblocker/debug/Debug.java b/src/main/java/de/hysky/skyblocker/debug/Debug.java index cfd0b528..03b49532 100644 --- a/src/main/java/de/hysky/skyblocker/debug/Debug.java +++ b/src/main/java/de/hysky/skyblocker/debug/Debug.java @@ -145,16 +145,16 @@ public class Debug { JSON { @Override public Text format(ItemStack stack) { - return Text.literal(SkyblockerMod.GSON_COMPACT.toJson(ItemStack.CODEC.encodeStart(ItemStackComponentizationFixer.getRegistryLookup().getOps(JsonOps.INSTANCE), stack).getOrThrow())); + return Text.literal(SkyblockerMod.GSON_COMPACT.toJson(ItemUtils.EMPTY_ALLOWING_ITEMSTACK_CODEC.encodeStart(ItemStackComponentizationFixer.getRegistryLookup().getOps(JsonOps.INSTANCE), stack).getOrThrow())); } }, SNBT { @Override public Text format(ItemStack stack) { - return NbtHelper.toPrettyPrintedText(ItemStack.CODEC.encodeStart(MinecraftClient.getInstance().player.getRegistryManager().getOps(NbtOps.INSTANCE), stack).getOrThrow()); + return NbtHelper.toPrettyPrintedText(ItemUtils.EMPTY_ALLOWING_ITEMSTACK_CODEC.encodeStart(MinecraftClient.getInstance().player.getRegistryManager().getOps(NbtOps.INSTANCE), stack).getOrThrow()); } }; - abstract Text format(ItemStack stack); + public abstract Text format(ItemStack stack); } } 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 index 6bb1bcdb..9b019531 100644 --- 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 @@ -1,18 +1,31 @@ package de.hysky.skyblocker.skyblock.item.tooltip.adders; +import de.hysky.skyblocker.debug.Debug; import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip; import de.hysky.skyblocker.skyblock.item.tooltip.SimpleTooltipAdder; import de.hysky.skyblocker.skyblock.item.tooltip.info.TooltipInfoType; +import de.hysky.skyblocker.utils.RegexUtils; +import de.hysky.skyblocker.utils.TextUtils; 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 net.minecraft.util.Util; import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.List; +import java.util.OptionalInt; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class NpcPriceTooltip extends SimpleTooltipAdder { + private static final Pattern STORED_PATTERN = Pattern.compile("Stored: ([\\d,]+)/\\S+"); + private static final Logger LOGGER = LoggerFactory.getLogger(NpcPriceTooltip.class); + private static final short LOG_INTERVAL = 1000; + private static long lastLog = Util.getMeasuringTimeMs(); + public NpcPriceTooltip(int priority) { super(priority); } @@ -26,19 +39,35 @@ public class NpcPriceTooltip extends SimpleTooltipAdder { public void addToTooltip(@Nullable Slot focusedSlot, ItemStack stack, List lines) { // NPC prices seem to use the Skyblock item id, not the Skyblock api id. final String internalID = stack.getSkyblockId(); - if (TooltipInfoType.NPC.hasOrNullWarning(internalID)) { - 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) : stack.getCount(); + if (TooltipInfoType.NPC.getData() == null) { + ItemTooltip.nullWarning(); + return; + } + double price = TooltipInfoType.NPC.getData().getOrDefault(internalID, -1); // The original default return value of 0 can be an actual price, so we use a value that can't be a price + if (price < 0) return; + + int amount = parseAmount(stack, lines); + lines.add(Text.literal(String.format("%-21s", "NPC Sell Price:")) + .formatted(Formatting.YELLOW) + .append(ItemTooltip.getCoinsMessage(price, amount))); + } + + private int parseAmount(ItemStack stack, List lines) { + if (lines.size() >= 2 && lines.get(1).getString().endsWith("Sack")) { + //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}]] + Matcher matcher = TextUtils.matchInList(lines, STORED_PATTERN); + if (matcher == null) { + // Log a warning every second if the amount couldn't be found, to prevent spamming the logs every frame (which can be hundreds of times per second) + if (Util.getMeasuringTimeMs() - lastLog > LOG_INTERVAL) { + LOGGER.warn("Failed to find stored amount in sack tooltip for item `{}`", Debug.DumpFormat.JSON.format(stack).getString()); // This is a very unintended way of serializing the item stack, but it's so much cleaner than actually using the codec + lastLog = Util.getMeasuringTimeMs(); + } + return stack.getCount(); } else { - amount = stack.getCount(); + OptionalInt amount = RegexUtils.findIntFromMatcher(matcher); + return amount.isPresent() ? amount.getAsInt() : stack.getCount(); } - lines.add(Text.literal(String.format("%-21s", "NPC Sell Price:")) - .formatted(Formatting.YELLOW) - .append(ItemTooltip.getCoinsMessage(TooltipInfoType.NPC.getData().getDouble(internalID), amount))); } + return stack.getCount(); } } diff --git a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java index b886f40f..385e1fd1 100644 --- a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java +++ b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java @@ -39,10 +39,7 @@ import net.minecraft.util.dynamic.Codecs; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.List; -import java.util.Locale; -import java.util.Optional; -import java.util.OptionalDouble; +import java.util.*; import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -259,7 +256,7 @@ public final class ItemUtils { /** * Parses the {@code petInfo} field from a pet item that has it into the {@link PetInfo} record. - * + * * @return the parsed {@link PetInfo} if successful, or {@link PetInfo#EMPTY} */ @NotNull @@ -394,13 +391,7 @@ public final class ItemUtils { */ @Nullable public static Matcher getLoreLineIfMatch(ItemStack stack, Pattern pattern) { - Matcher matcher = pattern.matcher(""); - for (Text line : getLore(stack)) { - if (matcher.reset(line.getString()).matches()) { - return matcher; - } - } - return null; + return TextUtils.matchInList(getLore(stack), pattern); } /** @@ -411,13 +402,7 @@ public final class ItemUtils { */ @Nullable public static Matcher getLoreLineIfContainsMatch(ItemStack stack, Pattern pattern) { - Matcher matcher = pattern.matcher(""); - for (Text line : getLore(stack)) { - if (matcher.reset(line.getString()).find()) { - return matcher; - } - } - return null; + return TextUtils.findInList(getLore(stack), pattern); } public static @NotNull List getLore(ItemStack stack) { diff --git a/src/main/java/de/hysky/skyblocker/utils/TextUtils.java b/src/main/java/de/hysky/skyblocker/utils/TextUtils.java new file mode 100644 index 00000000..4c641952 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/utils/TextUtils.java @@ -0,0 +1,112 @@ +package de.hysky.skyblocker.utils; + +import net.minecraft.text.Text; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Utility class for text operations. + */ +public final class TextUtils { + private TextUtils() {} + + /** + * Finds the first occurrence of the pattern in the list of texts, using {@link Matcher#matches()} ()}. + * + * @param list the list of texts to search in + * @param pattern the pattern to search for + * @return the index of the first occurrence if found, -1 otherwise + */ + public static int indexOfInList(List list, Pattern pattern) { + return indexOfInList(list, pattern, 0); + } + + /** + * Finds the first occurrence of the pattern in the list of texts starting from the specified index, using {@link Matcher#matches()}}. + * + * @param list the list of texts to search in + * @param pattern the pattern to search for + * @param startIndex the index to start searching from (inclusive) + * @return the index of the first occurrence if found, -1 otherwise + */ + public static int indexOfInList(List list, Pattern pattern, int startIndex) { + if (startIndex >= list.size()) return -1; // Start index is out of bounds, or the list is empty + + Matcher matcher = pattern.matcher(""); // Empty matcher + for (int i = startIndex, listSize = list.size(); i < listSize; i++) { + Text text = list.get(i); + if (matcher.reset(text.getString()).matches()) return i; + } + + return -1; + } + + /** + * Finds the first occurrence of the pattern in the list of texts using {@link Matcher#find()}. + * + * @param list the list of texts to search in + * @param pattern the pattern to search for + * @return the matcher if found, {@code null} otherwise + */ + @Nullable + public static Matcher findInList(List list, Pattern pattern) { + return findInList(list, pattern, 0); + } + + /** + * Finds the first occurrence of the pattern in the list of texts starting from the specified index using {@link Matcher#find()}. + * + * @param list the list of texts to search in + * @param pattern the pattern to search for + * @param startIndex the index to start searching from (inclusive) + * @return the matcher if found, {@code null} otherwise + */ + @Nullable + public static Matcher findInList(List list, Pattern pattern, int startIndex) { + if (startIndex >= list.size()) return null; // Start index is out of bounds, or the list is empty + + Matcher matcher = pattern.matcher(""); // Empty matcher + for (int i = startIndex, listSize = list.size(); i < listSize; i++) { + Text text = list.get(i); + if (matcher.reset(text.getString()).find()) return matcher; + } + + return null; + } + + /** + * Finds the first occurrence of the pattern in the list of texts using {@link Matcher#matches()}. + * + * @param list the list of texts to search in + * @param pattern the pattern to search for + * @return the matcher if found, {@code null} otherwise + */ + @Nullable + public static Matcher matchInList(List list, Pattern pattern) { + return matchInList(list, pattern, 0); + } + + /** + * Finds the first occurrence of the pattern in the list of texts starting from the specified index using {@link Matcher#matches()}. + * + * @param list the list of texts to search in + * @param pattern the pattern to search for + * @param startIndex the index to start searching from (inclusive) + * @return the matcher if found, {@code null} otherwise + */ + @Nullable + public static Matcher matchInList(List list, Pattern pattern, int startIndex) { + if (startIndex >= list.size()) return null; // Start index is out of bounds, or the list is empty + + Matcher matcher = pattern.matcher(""); // Empty matcher + for (int i = startIndex, listSize = list.size(); i < listSize; i++) { + Text text = list.get(i); + if (matcher.reset(text.getString()).matches()) return matcher; + } + + return null; + } +} -- cgit