diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java index 8dd1419d66..08b0915a57 100644 --- a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java +++ b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java @@ -27,8 +27,8 @@ import de.hysky.skyblocker.skyblock.end.EnderNodes; import de.hysky.skyblocker.skyblock.end.TheEnd; import de.hysky.skyblocker.skyblock.entity.MobBoundingBoxes; -import de.hysky.skyblocker.skyblock.fancybars.FancyStatusBars; import de.hysky.skyblocker.skyblock.events.EventNotifications; +import de.hysky.skyblocker.skyblock.fancybars.FancyStatusBars; import de.hysky.skyblocker.skyblock.garden.FarmingHud; import de.hysky.skyblocker.skyblock.garden.LowerSensitivity; import de.hysky.skyblocker.skyblock.garden.VisitorHelper; @@ -48,11 +48,7 @@ import de.hysky.skyblocker.skyblock.tabhud.TabHud; import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.ScreenMaster; import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr; -import de.hysky.skyblocker.skyblock.waypoint.FairySouls; -import de.hysky.skyblocker.skyblock.waypoint.MythologicalRitual; -import de.hysky.skyblocker.skyblock.waypoint.OrderedWaypoints; -import de.hysky.skyblocker.skyblock.waypoint.Relics; -import de.hysky.skyblocker.skyblock.waypoint.Waypoints; +import de.hysky.skyblocker.skyblock.waypoint.*; import de.hysky.skyblocker.utils.*; import de.hysky.skyblocker.utils.chat.ChatMessageListener; import de.hysky.skyblocker.utils.discord.DiscordRPCManager; diff --git a/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java index 96bb226d96..ab674b185b 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java @@ -221,6 +221,13 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig newValue -> config.general.itemTooltip.dungeonQuality = newValue) .controller(ConfigUtils::createBooleanController) .build()) + .option(Option.createBuilder() + .name(Text.translatable("skyblocker.config.general.itemTooltip.showEssenceCost")) + .binding(defaults.general.itemTooltip.showEssenceCost, + () -> config.general.itemTooltip.showEssenceCost, + newValue -> config.general.itemTooltip.showEssenceCost = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) .build()) //Item Info Display diff --git a/src/main/java/de/hysky/skyblocker/config/configs/GeneralConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/GeneralConfig.java index 754e15f152..691e6f794b 100644 --- a/src/main/java/de/hysky/skyblocker/config/configs/GeneralConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/configs/GeneralConfig.java @@ -131,6 +131,9 @@ public static class ItemTooltip { @SerialEntry public boolean dungeonQuality = true; + + @SerialEntry + public boolean showEssenceCost = false; } public enum Average { 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 db81382c6e..cd4db72725 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/ChocolateFactorySolver.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/ChocolateFactorySolver.java @@ -93,7 +93,7 @@ public ChocolateFactorySolver() { private static void onTick(MinecraftClient client) { if (ding != StraySound.NONE) { - dingTick = (++dingTick) % (ding == StraySound.NORMAL ? 5 : 3); + dingTick = (++dingTick) % (ding == StraySound.NORMAL ? 5 : 3); if (dingTick == 0) { client.getSoundManager().play(PositionedSoundInstance.master(ding == StraySound.NORMAL ? SoundEvents.BLOCK_NOTE_BLOCK_PLING.value() : SoundEvents.BLOCK_NOTE_BLOCK_HARP.value(), 1.f, 1.f)); } @@ -150,14 +150,14 @@ private static void updateFactoryInfo(Int2ObjectMap slots) { RegexUtils.getLongFromMatcher(CHOCOLATE_PATTERN.matcher(slots.get(CHOCOLATE_SLOT).getName().getString())).ifPresent(l -> totalChocolate = l); //Cps item (cocoa bean) is in slot 45 - String cpsItemLore = getConcatenatedLore(slots.get(CPS_SLOT)); + String cpsItemLore = ItemUtils.getConcatenatedLore(slots.get(CPS_SLOT)); Matcher cpsMatcher = CPS_PATTERN.matcher(cpsItemLore); RegexUtils.getDoubleFromMatcher(cpsMatcher).ifPresent(d -> totalCps = d); Matcher multiplierMatcher = TOTAL_MULTIPLIER_PATTERN.matcher(cpsItemLore); RegexUtils.getDoubleFromMatcher(multiplierMatcher, cpsMatcher.hasMatch() ? cpsMatcher.end() : 0).ifPresent(d -> totalCpsMultiplier = d); //Prestige item is in slot 28 - String prestigeLore = getConcatenatedLore(slots.get(PRESTIGE_SLOT)); + String prestigeLore = ItemUtils.getConcatenatedLore(slots.get(PRESTIGE_SLOT)); Matcher prestigeMatcher = PRESTIGE_REQUIREMENT_PATTERN.matcher(prestigeLore); OptionalLong currentChocolate = RegexUtils.getLongFromMatcher(prestigeMatcher); if (currentChocolate.isPresent()) { @@ -178,7 +178,7 @@ private static void updateFactoryInfo(Int2ObjectMap slots) { //Time Tower is in slot 39 isTimeTowerMaxed = StringUtils.substringAfterLast(slots.get(TIME_TOWER_SLOT).getName().getString(), ' ').equals("XV"); - String timeTowerLore = getConcatenatedLore(slots.get(TIME_TOWER_SLOT)); + String timeTowerLore = ItemUtils.getConcatenatedLore(slots.get(TIME_TOWER_SLOT)); Matcher timeTowerMultiplierMatcher = TIME_TOWER_MULTIPLIER_PATTERN.matcher(timeTowerLore); RegexUtils.getDoubleFromMatcher(timeTowerMultiplierMatcher).ifPresent(d -> timeTowerMultiplier = d); Matcher timeTowerStatusMatcher = TIME_TOWER_STATUS_PATTERN.matcher(timeTowerLore); @@ -190,29 +190,9 @@ private static void updateFactoryInfo(Int2ObjectMap slots) { cpsIncreaseFactors.sort(Comparator.comparingDouble(rabbit -> rabbit.cost() / rabbit.cpsIncrease())); //Ascending order, lower = better } - /** - * Utility method. - */ - private static String getConcatenatedLore(ItemStack item) { - return concatenateLore(ItemUtils.getLore(item)); - } - - /** - * Concatenates the lore of an item into one string. - * This is useful in case some pattern we're looking for is split into multiple lines, which would make it harder to regex. - */ - private static String concatenateLore(List lore) { - StringBuilder stringBuilder = new StringBuilder(); - for (int i = 0; i < lore.size(); i++) { - stringBuilder.append(lore.get(i).getString()); - if (i != lore.size() - 1) stringBuilder.append(" "); - } - return stringBuilder.toString(); - } - private static Optional getCoach(ItemStack coachItem) { if (!coachItem.isOf(Items.PLAYER_HEAD)) return Optional.empty(); - String coachLore = getConcatenatedLore(coachItem); + String coachLore = ItemUtils.getConcatenatedLore(coachItem); if (totalCps < 0 || totalCpsMultiplier < 0) return Optional.empty(); //We need these 2 to calculate the increase in cps. @@ -234,7 +214,7 @@ private static Optional getCoach(ItemStack coachItem) { } private static Optional getRabbit(ItemStack item, int slot) { - String lore = getConcatenatedLore(item); + String lore = ItemUtils.getConcatenatedLore(item); Matcher cpsMatcher = CPS_INCREASE_PATTERN.matcher(lore); OptionalInt currentCps = RegexUtils.getIntFromMatcher(cpsMatcher); if (currentCps.isEmpty()) return Optional.empty(); @@ -247,7 +227,7 @@ private static Optional getRabbit(ItemStack item, int slot) { Matcher costMatcher = COST_PATTERN.matcher(lore); OptionalLong cost = RegexUtils.getLongFromMatcher(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())*(totalCpsMultiplier < 0 ? 1 : totalCpsMultiplier), cost.getAsLong(), slot)); + return Optional.of(new Rabbit((nextCps.getAsInt() - currentCps.getAsInt()) * (totalCpsMultiplier < 0 ? 1 : totalCpsMultiplier), cost.getAsLong(), slot)); } private static Optional getPrestigeHighlight() { @@ -271,7 +251,7 @@ private static List getStrayRabbitHighlight(Int2ObjectMap //It should be set to true if there's any information added, false otherwise. boolean shouldAddLine = false; - String lore = concatenateLore(lines); + String lore = ItemUtils.concatenateLore(lines); Matcher costMatcher = COST_PATTERN.matcher(lore); OptionalLong cost = RegexUtils.getLongFromMatcher(costMatcher); //Available on all items with a chocolate cost diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java index 92adf49dce..46f4cb589f 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java @@ -4,6 +4,7 @@ import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.config.configs.GeneralConfig; +import de.hysky.skyblocker.skyblock.item.tooltip.adders.EssenceShopPrice; import de.hysky.skyblocker.utils.Http; import de.hysky.skyblocker.utils.Utils; import org.jetbrains.annotations.Nullable; @@ -16,7 +17,7 @@ public enum TooltipInfoType implements Runnable { NPC("https://hysky.de/api/npcprice", itemTooltip -> itemTooltip.enableNPCPrice, true), - BAZAAR("https://hysky.de/api/bazaar", itemTooltip -> itemTooltip.enableBazaarPrice || itemTooltip.enableCraftingCost.getOrder() != null || SkyblockerConfigManager.get().dungeons.dungeonChestProfit.enableProfitCalculator || SkyblockerConfigManager.get().dungeons.dungeonChestProfit.croesusProfit || SkyblockerConfigManager.get().uiAndVisuals.chestValue.enableChestValue, itemTooltip -> itemTooltip.enableBazaarPrice, false), + BAZAAR("https://hysky.de/api/bazaar", itemTooltip -> itemTooltip.enableBazaarPrice || itemTooltip.enableCraftingCost.getOrder() != null || SkyblockerConfigManager.get().dungeons.dungeonChestProfit.enableProfitCalculator || SkyblockerConfigManager.get().dungeons.dungeonChestProfit.croesusProfit || SkyblockerConfigManager.get().uiAndVisuals.chestValue.enableChestValue || itemTooltip.showEssenceCost, itemTooltip -> itemTooltip.enableBazaarPrice, false, EssenceShopPrice::refreshEssencePrices), LOWEST_BINS("https://hysky.de/api/auctions/lowestbins", itemTooltip -> itemTooltip.enableLowestBIN || itemTooltip.enableCraftingCost.getOrder() != null || SkyblockerConfigManager.get().dungeons.dungeonChestProfit.enableProfitCalculator || SkyblockerConfigManager.get().dungeons.dungeonChestProfit.croesusProfit || SkyblockerConfigManager.get().uiAndVisuals.chestValue.enableChestValue, itemTooltip -> itemTooltip.enableLowestBIN, false), ONE_DAY_AVERAGE("https://hysky.de/api/auctions/lowestbins/average/1day.json", itemTooltip -> itemTooltip.enableAvgBIN, false), THREE_DAY_AVERAGE("https://hysky.de/api/auctions/lowestbins/average/3day.json", itemTooltip -> itemTooltip.enableAvgBIN || SkyblockerConfigManager.get().uiAndVisuals.searchOverlay.enableAuctionHouse, itemTooltip -> itemTooltip.enableAvgBIN, false), @@ -29,10 +30,10 @@ public enum TooltipInfoType implements Runnable { private final String address; private final Predicate dataEnabled; private final Predicate tooltipEnabled; - private JsonObject data; + private @Nullable JsonObject data; private final boolean cacheable; private long hash; - private final Consumer callback; + private final @Nullable Consumer callback; /** * Use this for when you're adding tooltip info that has no data associated with it @@ -45,19 +46,19 @@ public enum TooltipInfoType implements Runnable { * @param address the address to download the data from * @param enabled the predicate to check if the data should be downloaded and the tooltip should be shown * @param cacheable whether the data should be cached - * @param callback called when the {@code data} is refreshed */ - TooltipInfoType(String address, Predicate enabled, boolean cacheable, Consumer callback) { - this(address, enabled, enabled, cacheable, callback); + TooltipInfoType(String address, Predicate enabled, boolean cacheable) { + this(address, enabled, enabled, cacheable, null); } /** * @param address the address to download the data from * @param enabled the predicate to check if the data should be downloaded and the tooltip should be shown * @param cacheable whether the data should be cached + * @param callback called when the {@code data} is refreshed */ - TooltipInfoType(String address, Predicate enabled, boolean cacheable) { - this(address, enabled, enabled, cacheable, null); + TooltipInfoType(String address, Predicate enabled, boolean cacheable, Consumer callback) { + this(address, enabled, enabled, cacheable, callback); } /** @@ -99,7 +100,7 @@ public boolean isTooltipEnabled() { return tooltipEnabled.test(ItemTooltip.config); } - public JsonObject getData() { + public @Nullable JsonObject getData() { return data; } @@ -160,14 +161,14 @@ public void run() { } String response = Http.sendGetRequest(address); if (response.trim().startsWith(" currentScreenAdders = new ArrayList<>(); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/EssenceShopPrice.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/EssenceShopPrice.java new file mode 100644 index 0000000000..5a7051d3cf --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/EssenceShopPrice.java @@ -0,0 +1,67 @@ +package de.hysky.skyblocker.skyblock.item.tooltip.adders; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder; +import de.hysky.skyblocker.utils.ItemUtils; +import de.hysky.skyblocker.utils.RegexUtils; +import it.unimi.dsi.fastutil.objects.Object2LongArrayMap; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.slot.Slot; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.jetbrains.annotations.Nullable; + +import java.text.NumberFormat; +import java.util.List; +import java.util.Locale; +import java.util.OptionalLong; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class EssenceShopPrice extends TooltipAdder { + private static final Pattern ESSENCE_PATTERN = Pattern.compile("Cost (?[\\d,]+) (?[A-Za-z]+) Essence"); + private static final NumberFormat DECIMAL_FORMAT = NumberFormat.getInstance(Locale.US); + private static final String[] ESSENCE_TYPES = {"WITHER", "SPIDER", "UNDEAD", "DRAGON", "GOLD", "DIAMOND", "ICE", "CRIMSON"}; + private static final Object2LongArrayMap ESSENCE_PRICES = new Object2LongArrayMap<>(ESSENCE_TYPES, new long[8]); + + public EssenceShopPrice(int priority) { + super("\\S+ Essence Shop", priority); + } + + public static void refreshEssencePrices(JsonObject data) { + for (String essenceType : ESSENCE_TYPES) { + JsonElement price = data.get("ESSENCE_" + essenceType); + if (price == null || !price.isJsonObject()) continue; + JsonElement sellPrice = price.getAsJsonObject().get("sellPrice"); + if (sellPrice == null) continue; + if (sellPrice.isJsonPrimitive()) { + ESSENCE_PRICES.put(essenceType, sellPrice.getAsLong()); + } + } + } + + //Todo: maybe move the price value right after the essence amount ex: "1,500 Wither Essence (645k coins)" + @Override + public void addToTooltip(@Nullable Slot focusedSlot, ItemStack stack, List lines) { + if (!SkyblockerConfigManager.get().general.itemTooltip.showEssenceCost) return; + + String lore = ItemUtils.concatenateLore(lines); + Matcher essenceMatcher = ESSENCE_PATTERN.matcher(lore); + OptionalLong cost = RegexUtils.getLongFromMatcher(essenceMatcher); + if (cost.isEmpty()) return; + + String type = essenceMatcher.group("type"); + long priceData = ESSENCE_PRICES.getLong(type.toUpperCase(Locale.ROOT)); + if (priceData == 0) return; //Default value for getLong is 0 if no value exists for that key + + lines.add(Text.empty() + .append(Text.literal("Essence Cost: ").formatted(Formatting.AQUA)) + .append(Text.literal(DECIMAL_FORMAT.format(priceData * cost.getAsLong()) + " coins").formatted(Formatting.DARK_AQUA)) + .append(Text.literal(" (").formatted(Formatting.GRAY)) + .append(Text.literal(DECIMAL_FORMAT.format(priceData) + " each").formatted(Formatting.GRAY)) + .append(Text.literal(")").formatted(Formatting.GRAY)) + ); + } +} diff --git a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java index 658078868e..894ef8ecd2 100644 --- a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java +++ b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java @@ -283,4 +283,24 @@ public static Matcher getLoreLineIfContainsMatch(ItemStack stack, Pattern patter throw new RuntimeException(e); } } + + /** + * Utility method. + */ + public static @NotNull String getConcatenatedLore(@NotNull ItemStack item) { + return concatenateLore(getLore(item)); + } + + /** + * Concatenates the lore of an item into one string. + * This is useful in case some pattern we're looking for is split into multiple lines, which would make it harder to regex. + */ + public static @NotNull String concatenateLore(@NotNull List lore) { + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < lore.size(); i++) { + stringBuilder.append(lore.get(i).getString()); + if (i != lore.size() - 1) stringBuilder.append(" "); + } + return stringBuilder.toString(); + } } diff --git a/src/main/java/de/hysky/skyblocker/utils/Utils.java b/src/main/java/de/hysky/skyblocker/utils/Utils.java index bbeb11b582..042095d1dd 100644 --- a/src/main/java/de/hysky/skyblocker/utils/Utils.java +++ b/src/main/java/de/hysky/skyblocker/utils/Utils.java @@ -158,7 +158,7 @@ public static Location getLocation() { /** * Can be used to restrict features to being active only on the Alpha network. - * + * * @return the current environment parsed from the Mod API. */ @NotNull diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index b709642483..875b45ddb3 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -263,6 +263,7 @@ "skyblocker.config.general.itemTooltip.enableMuseumInfo.@Tooltip": "If this item is donatable to the museum, then the item's category in the museum is displayed. It also displays a marker indicating whether you've donated that item to your museum or not (freebies not yet supported).\n\nMake sure to enable your Museum API for accurate information!", "skyblocker.config.general.itemTooltip.enableNPCPrice": "Enable NPC Price", "skyblocker.config.general.itemTooltip.enableObtainedDate": "Enable Obtained Date", + "skyblocker.config.general.itemTooltip.showEssenceCost": "Show Essence Cost", "skyblocker.config.general.quiverWarning": "Quiver Warning", "skyblocker.config.general.quiverWarning.enableQuiverWarning": "Enable Quiver Warning",