diff options
| author | Rime <81419447+Emirlol@users.noreply.github.com> | 2025-06-17 21:15:41 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-06-17 14:15:41 -0400 |
| commit | db6c2975ee129acfa3925a10f7d19cfaee1a3e00 (patch) | |
| tree | 9b3cda9019f33e1bd3c6ac016b7f9572c81ba170 /src/main/java | |
| parent | f6736848feed926026a4a6045bd31de91f503b82 (diff) | |
| download | Skyblocker-db6c2975ee129acfa3925a10f7d19cfaee1a3e00.tar.gz Skyblocker-db6c2975ee129acfa3925a10f7d19cfaee1a3e00.tar.bz2 Skyblocker-db6c2975ee129acfa3925a10f7d19cfaee1a3e00.zip | |
Add more slot text to CF (#1342)
* Add more slot text to CF
* Small tweaks
* Some more documentation
Diffstat (limited to 'src/main/java')
| -rw-r--r-- | src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/ChocolateFactorySolver.java | 167 | ||||
| -rw-r--r-- | src/main/java/de/hysky/skyblocker/utils/RegexUtils.java | 28 |
2 files changed, 156 insertions, 39 deletions
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 0dbf1923..0ae46db6 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/ChocolateFactorySolver.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/ChocolateFactorySolver.java @@ -20,7 +20,6 @@ import net.minecraft.sound.SoundEvents; import net.minecraft.text.MutableText; import net.minecraft.text.Text; import net.minecraft.util.Formatting; -import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -35,24 +34,30 @@ public class ChocolateFactorySolver extends SimpleContainerSolver implements Too private static final Pattern CPS_INCREASE_PATTERN = Pattern.compile("\\+([\\d,]+) Chocolate per second"); private static final Pattern COST_PATTERN = Pattern.compile("Cost ([\\d,]+) Chocolate"); private static final Pattern LEVEL_PATTERN = Pattern.compile("\\[(\\d+)]"); - private static final Pattern COACH_LEVEL_PATTERN = Pattern.compile("Coach Jackrabbit (\\w+)"); 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 CHOCOLATE_PATTERN = Pattern.compile("^([\\d,]+) Chocolate$"); private static final Pattern PRESTIGE_REQUIREMENT_PATTERN = Pattern.compile("Chocolate this Prestige: ([\\d,]+) +Requires (\\S+) Chocolate this Prestige!"); private static final Pattern TIME_TOWER_STATUS_PATTERN = Pattern.compile("Status: (ACTIVE|INACTIVE)"); private static final Pattern TIME_TOWER_MULTIPLIER_PATTERN = Pattern.compile("by \\+([\\d.]+)x for \\dh\\."); + private static final Pattern AVAILABLE_EGGS_PATTERN = Pattern.compile("Available eggs: (\\d+)"); + private static final Pattern ROMAN_LEVEL_PATTERN = Pattern.compile("(?:Chocolate Factory|Hand-Baked Chocolate|Time Tower|Rabbit Shrine|Coach Jackrabbit|Rabbit Barn) ?(?<level>\\w+)?"); + private static final Pattern PURCHASED_SLOTS_PATTERN = Pattern.compile("Purchased slots: (\\d+)/(\\d+)"); //Slots, for ease of maintenance rather than using magic numbers everywhere. + private static final byte STRAY_RABBIT_START = 0; + private static final byte STRAY_RABBIT_END = 26; + private static final byte CHOCOLATE_SLOT = 13; + private static final byte PRESTIGE_SLOT = 27; private static final byte RABBITS_START = 28; private static final byte RABBITS_END = 34; + private static final byte RABBIT_BARN_SLOT = 35; + private static final byte HAND_BAKED_CHOCOLATE_SLOT = 38; + private static final byte TIME_TOWER_SLOT = 39; + private static final byte RABBIT_SHRINE_SLOT = 41; private static final byte COACH_SLOT = 42; - private static final byte CHOCOLATE_SLOT = 13; private static final byte CPS_SLOT = 45; - private static final byte PRESTIGE_SLOT = 27; - private static final byte TIME_TOWER_SLOT = 39; - private static final byte STRAY_RABBIT_START = 0; - private static final byte STRAY_RABBIT_END = 26; + private static final byte RABBIT_HITMAN_SLOT = 51; private final ObjectArrayList<Rabbit> cpsIncreaseFactors = new ObjectArrayList<>(8); private long totalChocolate = -1L; @@ -66,6 +71,14 @@ public class ChocolateFactorySolver extends SimpleContainerSolver implements Too private boolean isTimeTowerActive = false; private int bestUpgrade = -1; private int bestAffordableUpgrade = -1; + private int prestigeLevel = -1; + private int hitmanAvailableEggs = -1; + private int purchasedHitmanSlots = -1; + private int maxHitmanSlots = -1; + private int rabbitShrineLevel = -1; + private int handBakedChocolateLevel = -1; + private int rabbitBarnLevel = -1; + private int timeTowerLevel = -1; private static StraySound ding = StraySound.NONE; private static int dingTick = 0; @@ -163,9 +176,16 @@ public class ChocolateFactorySolver extends SimpleContainerSolver implements Too canPrestige = false; reachedMaxPrestige = true; } - - //Time Tower is in slot 39 - isTimeTowerMaxed = StringUtils.substringAfterLast(slots.get(TIME_TOWER_SLOT).getName().getString(), ' ').equals("XV"); + Matcher upgradeMatcher = ROMAN_LEVEL_PATTERN.matcher(slots.get(PRESTIGE_SLOT).getName().getString()); + prestigeLevel = RegexUtils.findRomanNumeralFromMatcher(upgradeMatcher).orElse(-1); + + //Hand-Baked Chocolate + upgradeMatcher = ROMAN_LEVEL_PATTERN.matcher(slots.get(HAND_BAKED_CHOCOLATE_SLOT).getName().getString()); + handBakedChocolateLevel = RegexUtils.findRomanNumeralFromMatcher(upgradeMatcher).orElse(-1); + //Time Tower + upgradeMatcher.reset(slots.get(TIME_TOWER_SLOT).getName().getString()); + timeTowerLevel = RegexUtils.findRomanNumeralFromMatcher(upgradeMatcher).orElse(-1); + isTimeTowerMaxed = timeTowerLevel >= 15; String timeTowerLore = ItemUtils.getConcatenatedLore(slots.get(TIME_TOWER_SLOT)); Matcher timeTowerMultiplierMatcher = TIME_TOWER_MULTIPLIER_PATTERN.matcher(timeTowerLore); RegexUtils.findDoubleFromMatcher(timeTowerMultiplierMatcher).ifPresent(d -> timeTowerMultiplier = d); @@ -173,6 +193,21 @@ public class ChocolateFactorySolver extends SimpleContainerSolver implements Too if (timeTowerStatusMatcher.find(timeTowerMultiplierMatcher.hasMatch() ? timeTowerMultiplierMatcher.end() : 0)) { isTimeTowerActive = timeTowerStatusMatcher.group(1).equals("ACTIVE"); } + //Rabbit Shrine + upgradeMatcher.reset(slots.get(RABBIT_SHRINE_SLOT).getName().getString()); + rabbitShrineLevel = RegexUtils.findRomanNumeralFromMatcher(upgradeMatcher).orElse(-1); + //Rabbit Barn + upgradeMatcher.reset(slots.get(RABBIT_BARN_SLOT).getName().getString()); + rabbitBarnLevel = RegexUtils.findRomanNumeralFromMatcher(upgradeMatcher).orElse(-1); + + //Rabbit Hitman + Matcher hitmanMatcher = ItemUtils.getLoreLineIfMatch(slots.get(RABBIT_HITMAN_SLOT), AVAILABLE_EGGS_PATTERN); + if (hitmanMatcher != null) hitmanAvailableEggs = RegexUtils.parseIntFromMatcher(hitmanMatcher, 1); + Matcher purchasedSlotsMatcher = ItemUtils.getLoreLineIfMatch(slots.get(RABBIT_HITMAN_SLOT), PURCHASED_SLOTS_PATTERN); + if (purchasedSlotsMatcher != null) { + purchasedHitmanSlots = RegexUtils.parseIntFromMatcher(purchasedSlotsMatcher, 1); + maxHitmanSlots = RegexUtils.parseIntFromMatcher(purchasedSlotsMatcher, 2); + } //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 @@ -198,18 +233,17 @@ public class ChocolateFactorySolver extends SimpleContainerSolver implements Too currentCpsMultiplier = OptionalDouble.of(0.0); //And so, we can re-assign values to the variables to make the calculation more readable. } + Matcher levelMatcher = ROMAN_LEVEL_PATTERN.matcher(coachItem.getName().getString()); + int level = RegexUtils.findRomanNumeralFromMatcher(levelMatcher).orElse(-1); + Matcher costMatcher = COST_PATTERN.matcher(coachLore); - Matcher levelMatcher = COACH_LEVEL_PATTERN.matcher(coachItem.getName().getString()); OptionalLong cost = RegexUtils.findLongFromMatcher(costMatcher, multiplierIncreaseMatcher.hasMatch() ? multiplierIncreaseMatcher.end() : 0); //Cost comes after the multiplier line - int level = -1; - if (levelMatcher.find()) { - level = RomanNumerals.romanToDecimal(levelMatcher.group(1)); - } + if (cost.isEmpty()) return Optional.empty(); return Optional.of(new Rabbit(totalCps / totalCpsMultiplier * (nextCpsMultiplier.getAsDouble() - currentCpsMultiplier.getAsDouble()), cost.getAsLong(), COACH_SLOT, level)); } - private Optional<Rabbit> getRabbit(ItemStack item, int slot) { + private Optional<Rabbit> getRabbit(ItemStack item, int slot) { String lore = ItemUtils.getConcatenatedLore(item); Matcher cpsMatcher = CPS_INCREASE_PATTERN.matcher(lore); OptionalInt currentCps = RegexUtils.findIntFromMatcher(cpsMatcher); @@ -288,8 +322,8 @@ public class ChocolateFactorySolver extends SimpleContainerSolver implements Too private 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))); + .append(Text.literal("Time until upgrade: ").formatted(Formatting.GRAY)) + .append(formatTime((cost - totalChocolate) / totalCps))); return true; } @@ -297,12 +331,12 @@ public class ChocolateFactorySolver extends SimpleContainerSolver implements Too 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(Formatters.FLOAT_NUMBERS.format(requiredUntilNextPrestige)).formatted(Formatting.GOLD))); + .append(Text.literal("Chocolate until next prestige: ").formatted(Formatting.GRAY)) + .append(Text.literal(Formatters.FLOAT_NUMBERS.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))); + .append(Text.literal("Time until next prestige: ").formatted(Formatting.GRAY)) + .append(formatTime(requiredUntilNextPrestige / totalCps))); return true; } @@ -310,19 +344,19 @@ public class ChocolateFactorySolver extends SimpleContainerSolver implements Too 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(Formatters.FLOAT_NUMBERS.format(totalCps / totalCpsMultiplier * timeTowerMultiplier)).formatted(Formatting.GOLD))); + .append(Text.literal(" CPS increase: ").formatted(Formatting.GRAY)) + .append(Text.literal(Formatters.FLOAT_NUMBERS.format(totalCps / totalCpsMultiplier * timeTowerMultiplier)).formatted(Formatting.GOLD))); lines.add(Text.empty() - .append(Text.literal(" CPS when active: ").formatted(Formatting.GRAY)) - .append(Text.literal(Formatters.FLOAT_NUMBERS.format(isTimeTowerActive ? totalCps : totalCps / totalCpsMultiplier * (timeTowerMultiplier + totalCpsMultiplier))).formatted(Formatting.GOLD))); + .append(Text.literal(" CPS when active: ").formatted(Formatting.GRAY)) + .append(Text.literal(Formatters.FLOAT_NUMBERS.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(Formatters.FLOAT_NUMBERS.format(totalCps / (totalCpsMultiplier) * (timeTowerMultiplier + 0.1))).formatted(Formatting.GOLD))); + .append(Text.literal(" CPS increase: ").formatted(Formatting.GRAY)) + .append(Text.literal(Formatters.FLOAT_NUMBERS.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(Formatters.FLOAT_NUMBERS.format(isTimeTowerActive ? totalCps / totalCpsMultiplier * (totalCpsMultiplier + 0.1) : totalCps / totalCpsMultiplier * (timeTowerMultiplier + 0.1 + totalCpsMultiplier))).formatted(Formatting.GOLD))); + .append(Text.literal(" CPS when active: ").formatted(Formatting.GRAY)) + .append(Text.literal(Formatters.FLOAT_NUMBERS.format(isTimeTowerActive ? totalCps / totalCpsMultiplier * (totalCpsMultiplier + 0.1) : totalCps / totalCpsMultiplier * (timeTowerMultiplier + 0.1 + totalCpsMultiplier))).formatted(Formatting.GOLD))); } return true; } @@ -332,12 +366,12 @@ public class ChocolateFactorySolver extends SimpleContainerSolver implements Too for (Rabbit rabbit : cpsIncreaseFactors) { if (rabbit.slot == slot) { lines.add(Text.empty() - .append(Text.literal("CPS Increase: ").formatted(Formatting.GRAY)) - .append(Text.literal(Formatters.FLOAT_NUMBERS.format(rabbit.cpsIncrease)).formatted(Formatting.GOLD))); + .append(Text.literal("CPS Increase: ").formatted(Formatting.GRAY)) + .append(Text.literal(Formatters.FLOAT_NUMBERS.format(rabbit.cpsIncrease)).formatted(Formatting.GOLD))); lines.add(Text.empty() - .append(Text.literal("Cost per CPS: ").formatted(Formatting.GRAY)) - .append(Text.literal(Formatters.FLOAT_NUMBERS.format(rabbit.cost / rabbit.cpsIncrease)).formatted(Formatting.GOLD))); + .append(Text.literal("Cost per CPS: ").formatted(Formatting.GRAY)) + .append(Text.literal(Formatters.FLOAT_NUMBERS.format(rabbit.cost / rabbit.cpsIncrease)).formatted(Formatting.GOLD))); if (rabbit.slot == bestUpgrade) { if (rabbit.cost <= totalChocolate) { @@ -365,16 +399,62 @@ public class ChocolateFactorySolver extends SimpleContainerSolver implements Too // ======== Slot Text Adder ======== - @Override - public @NotNull List<SlotText> getText(@Nullable Slot slot, @NotNull ItemStack stack, int slotId) { - for (ChocolateFactorySolver.Rabbit rabbit : cpsIncreaseFactors) { + @Override + public @NotNull List<SlotText> getText(@Nullable Slot slot, @NotNull ItemStack stack, int slotId) { + for (ChocolateFactorySolver.Rabbit rabbit : cpsIncreaseFactors) { // Coach is included in these if (slotId == rabbit.slot()) { // Use SlotText#topLeft for positioning and add color to the text. Text levelText = Text.literal(String.valueOf(rabbit.level())).formatted(Formatting.GOLD); - return List.of(SlotText.topLeft(levelText)); + return SlotText.topLeftList(levelText); } } - return List.of(); // Return an empty list if the slot does not correspond to a rabbit slot. + return switch (slotId) { + case HAND_BAKED_CHOCOLATE_SLOT -> { + if (handBakedChocolateLevel < 0 || handBakedChocolateLevel == 10) yield List.of(); + Text levelText = Text.literal(String.valueOf(handBakedChocolateLevel)).formatted(Formatting.GOLD); + yield SlotText.topLeftList(levelText); + } + case RABBIT_SHRINE_SLOT -> { + if (rabbitShrineLevel < 0 || rabbitShrineLevel == 20) yield List.of(); + Text levelText = Text.literal(String.valueOf(rabbitShrineLevel)).formatted(Formatting.GOLD); + yield SlotText.topLeftList(levelText); + } + case RABBIT_BARN_SLOT -> { + if (rabbitBarnLevel < 0 || rabbitBarnLevel == 245) yield List.of(); + Text levelText = Text.literal(String.valueOf(rabbitBarnLevel)).formatted(Formatting.GOLD); + yield SlotText.topLeftList(levelText); + } + case TIME_TOWER_SLOT -> { + if (timeTowerLevel < 0 || isTimeTowerMaxed) yield List.of(); + Text levelText = Text.literal(String.valueOf(timeTowerLevel)).formatted(Formatting.GOLD); + yield SlotText.topLeftList(levelText); + } + case RABBIT_HITMAN_SLOT -> { + if (hitmanAvailableEggs < 0 || purchasedHitmanSlots < 0 || maxHitmanSlots < 0) yield List.of(); + if (purchasedHitmanSlots == 0) yield SlotText.topLeftList(Text.literal("0/0").formatted(Formatting.GRAY)); + + MutableText levelText = Text.literal(String.valueOf(hitmanAvailableEggs)); + if (hitmanAvailableEggs == purchasedHitmanSlots) levelText = levelText.formatted(Formatting.GOLD, Formatting.BOLD); + else if (hitmanAvailableEggs == 0) levelText = levelText.formatted(Formatting.GRAY); + else levelText = levelText.formatted(Formatting.YELLOW); // Some amount that is neither 0 nor the max + + MutableText result = Text.empty() + .append(levelText); + if (purchasedHitmanSlots < maxHitmanSlots) { + result.append(Text.literal("/").formatted(Formatting.GRAY)) + .append(Text.literal(String.valueOf(purchasedHitmanSlots)).formatted(Formatting.YELLOW)); + } + + yield SlotText.topLeftList(result); + } + case PRESTIGE_SLOT -> { + if (prestigeLevel < 0) yield List.of(); + Text levelText = Text.literal(String.valueOf(prestigeLevel)).formatted(Formatting.GOLD); + yield SlotText.topLeftList(levelText); + } + + default -> List.of(); // Return an empty list if the slot does not correspond to a rabbit slot. + }; } // ======== Reset and Other Classes ======== @@ -393,6 +473,15 @@ public class ChocolateFactorySolver extends SimpleContainerSolver implements Too isTimeTowerActive = false; bestUpgrade = -1; bestAffordableUpgrade = -1; + prestigeLevel = -1; + hitmanAvailableEggs = -1; + purchasedHitmanSlots = -1; + maxHitmanSlots = -1; + rabbitShrineLevel = -1; + handBakedChocolateLevel = -1; + rabbitBarnLevel = -1; + timeTowerLevel = -1; + dingTick = 0; ding = StraySound.NONE; } diff --git a/src/main/java/de/hysky/skyblocker/utils/RegexUtils.java b/src/main/java/de/hysky/skyblocker/utils/RegexUtils.java index 4fe4b047..37839368 100644 --- a/src/main/java/de/hysky/skyblocker/utils/RegexUtils.java +++ b/src/main/java/de/hysky/skyblocker/utils/RegexUtils.java @@ -1,5 +1,7 @@ package de.hysky.skyblocker.utils; +import org.apache.commons.lang3.StringUtils; + import java.util.OptionalDouble; import java.util.OptionalInt; import java.util.OptionalLong; @@ -37,6 +39,32 @@ public class RegexUtils { return OptionalInt.of(parseIntFromMatcher(matcher, 1)); } + /** + * Tries to {@link Matcher#find()} a match in the matcher, and parses the first group as a roman numeral. + * @return An OptionalInt of the first group in the matcher, or an empty OptionalInt if the matcher doesn't find anything. + * @implNote Hypixel generally has optional roman numerals in the case of level 0, so this method will return a 0 if the matcher finds a match with an empty first group. + * Example pattern: {@code Level ?([IVXLCDM]+)?} + */ + public static OptionalInt findRomanNumeralFromMatcher(Matcher matcher) { + return findRomanNumeralFromMatcher(matcher, matcher.hasMatch() ? matcher.end() : 0); + } + + /** + * Tries to {@link Matcher#find()} a match in the matcher from a starting index, and parses the first group as a roman numeral. + * @return An OptionalInt of the first group in the matcher after parsing via {@link RomanNumerals#romanToDecimal(String)}, or an empty OptionalInt if the matcher doesn't find anything / finds invalid roman numerals. + * @implNote Hypixel generally has optional roman numerals in the case of level 0, so this method will return a 0 if the matcher finds a match with an empty first group. + * Example pattern: {@code Level ?([IVXLCDM]+)?} + */ + public static OptionalInt findRomanNumeralFromMatcher(Matcher matcher, int startingIndex) { + if (!matcher.find(startingIndex)) return OptionalInt.empty(); + String result = matcher.group(1); + if (StringUtils.isEmpty(result)) return OptionalInt.of(0); // Special case for level 0 + if (!RomanNumerals.isValidRomanNumeral(result)) return OptionalInt.empty(); + int resultInt = RomanNumerals.romanToDecimal(result); + if (resultInt <= 0) return OptionalInt.empty(); // This shouldn't happen since we checked above, but just in case. + return OptionalInt.of(resultInt); + } + public static OptionalInt parseOptionalIntFromMatcher(Matcher matcher, int group) { String s = matcher.group(group); if (s == null) return OptionalInt.empty(); |
