From 30ecf3e856d4d10a4dfe1b0050ec71e70e4647a9 Mon Sep 17 00:00:00 2001 From: Rime <81419447+Emirlol@users.noreply.github.com> Date: Wed, 15 May 2024 05:21:13 +0300 Subject: Add tooltips for the chocolate factory Also fix unemployed coach crashing the game --- .../chocolatefactory/ChocolateFactorySolver.java | 165 ++++++++++++++------- .../skyblock/item/tooltip/ItemTooltip.java | 6 +- 2 files changed, 115 insertions(+), 56 deletions(-) (limited to 'src/main/java/de/hysky/skyblocker/skyblock') 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 6606f69c..b0bf3a56 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/ChocolateFactorySolver.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/ChocolateFactorySolver.java @@ -1,15 +1,25 @@ package de.hysky.skyblocker.skyblock.chocolatefactory; 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.RegexUtils; import de.hysky.skyblocker.utils.render.gui.ColorHighlight; import de.hysky.skyblocker.utils.render.gui.ContainerSolver; import it.unimi.dsi.fastutil.ints.*; 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.text.MutableText; import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import java.text.DecimalFormat; import java.util.*; import java.util.List; import java.util.regex.Matcher; @@ -21,12 +31,18 @@ public class ChocolateFactorySolver extends ContainerSolver { private static final Pattern COST_PATTERN = Pattern.compile("Cost ([\\d,]+) Chocolate"); private static final Pattern TOTAL_MULTIPLIER_PATTERN = Pattern.compile("Total Multiplier: ([\\d.]+)x"); private static final Pattern MULTIPLIER_INCREASE_PATTERN = Pattern.compile("\\+([\\d.]+)x Chocolate per second"); - private static final Pattern TOTAL_CHOCOLATE_PATTERN = Pattern.compile("([\\d,]+) Chocolate"); + private static final Pattern CHOCOLATE_PATTERN = Pattern.compile("^([\\d,]+) Chocolate$"); private static final ObjectArrayList cpsIncreaseFactors = new ObjectArrayList<>(6); private static long totalChocolate = -1L; + private static double totalCps = -1.0; + private static double totalCpsMultiplier = -1.0; + private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.#"); + private static ItemStack bestUpgrade = null; + private static ItemStack bestAffordableUpgrade = null; public ChocolateFactorySolver() { super("^Chocolate Factory$"); + ItemTooltipCallback.EVENT.register(ChocolateFactorySolver::handleTooltip); } @Override @@ -40,11 +56,12 @@ public class ChocolateFactorySolver extends ContainerSolver { if (totalChocolate <= 0 || cpsIncreaseFactors.isEmpty()) return List.of(); //Something went wrong or there's nothing we can afford. Rabbit bestRabbit = cpsIncreaseFactors.getFirst(); - + bestUpgrade = bestRabbit.itemStack; if (bestRabbit.cost <= totalChocolate) return List.of(ColorHighlight.green(bestRabbit.slot)); for (Rabbit rabbit : cpsIncreaseFactors.subList(1, cpsIncreaseFactors.size())) { if (rabbit.cost <= totalChocolate) { + bestAffordableUpgrade = rabbit.itemStack; return List.of(ColorHighlight.green(rabbit.slot), ColorHighlight.yellow(bestRabbit.slot)); } } @@ -52,7 +69,7 @@ public class ChocolateFactorySolver extends ContainerSolver { return List.of(ColorHighlight.yellow(bestRabbit.slot)); } - private void updateFactoryInfo(Int2ObjectMap slots) { + private static void updateFactoryInfo(Int2ObjectMap slots) { cpsIncreaseFactors.clear(); for (int i = 29; i <= 33; i++) { // The 5 rabbits slots are in 29, 30, 31, 32 and 33. @@ -62,18 +79,89 @@ public class ChocolateFactorySolver extends ContainerSolver { } } - //Coach is in slot 42 while the factory info item is in slot 45. - getCoach(slots.get(45), slots.get(42)).ifPresent(cpsIncreaseFactors::add); - getTotalChocolate(slots.get(13)).ifPresent(l -> totalChocolate = l); + //Coach is in slot 42 + getCoach(slots.get(42)).ifPresent(cpsIncreaseFactors::add); + RegexUtils.getLongFromMatcher(CHOCOLATE_PATTERN.matcher(slots.get(13).getName().getString())).ifPresent(l -> totalChocolate = l); + + //Cps item (cocoa bean) is in slot 45 + String cpsItemLore = getConcatenatedLore(slots.get(45)); + 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); //Compare cost/cpsIncrease rather than cpsIncrease/cost to avoid getting close to 0 and losing precision. 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 lines) { + if (!SkyblockerConfigManager.get().helpers.chocolateFactory.enableChocolateFactoryHelper) return; + if (!(MinecraftClient.getInstance().currentScreen instanceof GenericContainerScreen screen) || !screen.getTitle().getString().equals("Chocolate Factory") ) return; + + String lore = concatenateLore(lines); + Matcher costMatcher = COST_PATTERN.matcher(lore); + OptionalLong cost = RegexUtils.getLongFromMatcher(costMatcher); + if (cost.isEmpty() || totalChocolate == -1L || totalCps == -1.0) return; + + lines.add(ItemTooltip.createSmoothLine()); + + lines.add(Text.literal("") + .append(Text.literal("Time until upgrade: ").formatted(Formatting.GRAY)) + .append(formatTime((cost.getAsLong() - totalChocolate) / totalCps))); + + if (cpsIncreaseFactors.isEmpty()) return; + + for (int j = 0; j < cpsIncreaseFactors.size(); j++) { + Rabbit rabbit = cpsIncreaseFactors.get(j); + if (rabbit.itemStack != stack) continue; + + lines.add(Text.literal("") + .append(Text.literal("CPS Increase: ").formatted(Formatting.GRAY)) + .append(Text.literal(DECIMAL_FORMAT.format(rabbit.cpsIncrease)).formatted(Formatting.GOLD))); + + lines.add(Text.literal("") + .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 (cost.getAsLong() <= 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 && cost.getAsLong() <= totalChocolate) { + lines.add(Text.literal("Best upgrade you can afford").formatted(Formatting.GREEN)); + } + } + } + + private static MutableText formatTime(double 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. */ - private String getConcattedLore(ItemStack item) { + private static String getConcatenatedLore(ItemStack item) { return concatenateLore(ItemUtils.getLore(item)); } @@ -81,7 +169,7 @@ public class ChocolateFactorySolver extends ContainerSolver { * 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 String concatenateLore(List lore) { + private static String concatenateLore(List lore) { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < lore.size(); i++) { stringBuilder.append(lore.get(i).getString()); @@ -90,25 +178,17 @@ public class ChocolateFactorySolver extends ContainerSolver { return stringBuilder.toString(); } - private Optional getCoach(ItemStack cpsItem, ItemStack coachItem) { - if (!coachItem.isOf(Items.PLAYER_HEAD) || !cpsItem.isOf(Items.COCOA_BEANS)) return Optional.empty(); - String cpsItemLore = getConcattedLore(cpsItem); - - Matcher cpsMatcher = CPS_PATTERN.matcher(cpsItemLore); - OptionalDouble currentCps = getDoubleFromMatcher(cpsMatcher); - if (currentCps.isEmpty()) return Optional.empty(); - - Matcher multiplierMatcher = TOTAL_MULTIPLIER_PATTERN.matcher(cpsItemLore); - OptionalDouble totalMultiplier = getDoubleFromMatcher(multiplierMatcher, cpsMatcher.end()); - if (totalMultiplier.isEmpty()) return Optional.empty(); + private static Optional getCoach(ItemStack coachItem) { + if (!coachItem.isOf(Items.PLAYER_HEAD)) return Optional.empty(); + String coachLore = getConcatenatedLore(coachItem); - String coachLore = getConcattedLore(coachItem); + if (totalCpsMultiplier == -1.0) return Optional.empty(); //We need the total multiplier to calculate the increase in cps. Matcher multiplierIncreaseMatcher = MULTIPLIER_INCREASE_PATTERN.matcher(coachLore); - OptionalDouble currentCpsMultiplier = getDoubleFromMatcher(multiplierIncreaseMatcher); + OptionalDouble currentCpsMultiplier = RegexUtils.getDoubleFromMatcher(multiplierIncreaseMatcher); if (currentCpsMultiplier.isEmpty()) return Optional.empty(); - OptionalDouble nextCpsMultiplier = getDoubleFromMatcher(multiplierIncreaseMatcher); + OptionalDouble nextCpsMultiplier = RegexUtils.getDoubleFromMatcher(multiplierIncreaseMatcher); if (nextCpsMultiplier.isEmpty()) { //This means that the coach isn't hired yet. nextCpsMultiplier = currentCpsMultiplier; //So the first instance of the multiplier is actually the amount we'll get upon upgrading. currentCpsMultiplier = OptionalDouble.of(0.0); //And so, we can re-assign values to the variables to make the calculation more readable. @@ -118,51 +198,26 @@ public class ChocolateFactorySolver extends ContainerSolver { OptionalInt cost = getIntFromMatcher(costMatcher, multiplierIncreaseMatcher.hasMatch() ? multiplierIncreaseMatcher.end() : 0); //Cost comes after the multiplier line if (cost.isEmpty()) return Optional.empty(); - return Optional.of(new Rabbit(currentCps.getAsDouble() / totalMultiplier.getAsDouble() * (nextCpsMultiplier.getAsDouble() - currentCpsMultiplier.getAsDouble()), cost.getAsInt(), 42)); + return Optional.of(new Rabbit(totalCps / totalCpsMultiplier * (nextCpsMultiplier.getAsDouble() - currentCpsMultiplier.getAsDouble()), cost.getAsInt(), 42, coachItem)); } - private Optional getRabbit(ItemStack item, int slot) { - String lore = getConcattedLore(item); + private static Optional getRabbit(ItemStack item, int slot) { + String lore = getConcatenatedLore(item); Matcher cpsMatcher = CPS_INCREASE_PATTERN.matcher(lore); - OptionalInt currentCps = getIntFromMatcher(cpsMatcher); + OptionalInt currentCps = RegexUtils.getIntFromMatcher(cpsMatcher); if (currentCps.isEmpty()) return Optional.empty(); - OptionalInt nextCps = getIntFromMatcher(cpsMatcher); + OptionalInt nextCps = RegexUtils.getIntFromMatcher(cpsMatcher); if (nextCps.isEmpty()) { nextCps = currentCps; //This means that the rabbit isn't hired yet. currentCps = OptionalInt.of(0); //So the first instance of the cps is actually the amount we'll get upon hiring. } Matcher costMatcher = COST_PATTERN.matcher(lore); - OptionalInt cost = getIntFromMatcher(costMatcher, cpsMatcher.hasMatch() ? cpsMatcher.end() : 0); //Cost comes after the cps line + 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)); - } - - private OptionalLong getTotalChocolate(ItemStack item) { - if (!item.isOf(Items.PLAYER_HEAD)) return OptionalLong.empty(); - Matcher matcher = TOTAL_CHOCOLATE_PATTERN.matcher(item.getName().getString()); - if (!matcher.find()) return OptionalLong.empty(); - return OptionalLong.of(Long.parseLong(matcher.group(1).replace(",", ""))); - } - - private OptionalInt getIntFromMatcher(Matcher matcher) { - return getIntFromMatcher(matcher, matcher.hasMatch() ? matcher.end() : 0); - } - - private OptionalInt getIntFromMatcher(Matcher matcher, int startingIndex) { - if (!matcher.find(startingIndex)) return OptionalInt.empty(); - return OptionalInt.of(Integer.parseInt(matcher.group(1).replace(",", ""))); - } - - private OptionalDouble getDoubleFromMatcher(Matcher matcher) { - return getDoubleFromMatcher(matcher, matcher.hasMatch() ? matcher.end() : 0); - } - - private OptionalDouble getDoubleFromMatcher(Matcher matcher, int startingIndex) { - if (!matcher.find(startingIndex)) return OptionalDouble.empty(); - return OptionalDouble.of(Double.parseDouble(matcher.group(1).replace(",", ""))); + return Optional.of(new Rabbit(nextCps.getAsInt() - currentCps.getAsInt(), cost.getAsInt(), slot, item)); } - private record Rabbit(double cpsIncrease, int cost, int slot) { + private record Rabbit(double cpsIncrease, int cost, int slot, ItemStack itemStack) { } } 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 c6caaf41..6e269790 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 @@ -398,11 +398,15 @@ public class ItemTooltip { for (int i = 0; i < lines.size(); i++) { Text line = lines.get(i); if (line.getString().equals("-----------------")) { - lines.set(i, Text.literal(" ").formatted(Formatting.DARK_GRAY, Formatting.STRIKETHROUGH, Formatting.BOLD)); + lines.set(i, createSmoothLine()); } } } + public static Text createSmoothLine() { + return Text.literal(" ").formatted(Formatting.DARK_GRAY, Formatting.STRIKETHROUGH, Formatting.BOLD); + } + // If these options is true beforehand, the client will get first data of these options while loading. // After then, it will only fetch the data if it is on Skyblock. public static int minute = 0; -- cgit