From e7a06a3cd3960ba655eb353f6e6b9b6e4ddc44ea Mon Sep 17 00:00:00 2001 From: LJNeon <23249107+LJNeon@users.noreply.github.com> Date: Sat, 7 Mar 2026 16:23:42 -0700 Subject: [PATCH 1/2] Auto-detect McGrubber stacks --- .../categories/OtherLocationsCategory.java | 8 ++ .../config/configs/OtherLocationsConfig.java | 2 + .../skyblock/rift/McGrubberUpdater.java | 131 ++++++++++++++++++ .../assets/skyblocker/lang/en_us.json | 2 + 4 files changed, 143 insertions(+) create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/rift/McGrubberUpdater.java diff --git a/src/main/java/de/hysky/skyblocker/config/categories/OtherLocationsCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/OtherLocationsCategory.java index 2ddf677d315..9ff42a1da8c 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/OtherLocationsCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/OtherLocationsCategory.java @@ -99,6 +99,14 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig newValue -> config.otherLocations.rift.highlightFoundEnigmaSouls = newValue) .controller(ConfigUtils.createBooleanController()) .build()) + .option(Option.createBuilder() + .name(Component.translatable("skyblocker.config.otherLocations.rift.autoDetectMcGrubber")) + .description(Component.translatable("skyblocker.config.otherLocations.rift.autoDetectMcGrubber.@Tooltip")) + .binding(defaults.otherLocations.rift.autoDetectMcGrubber, + () -> config.otherLocations.rift.autoDetectMcGrubber, + newValue -> config.otherLocations.rift.autoDetectMcGrubber = newValue) + .controller(ConfigUtils.createBooleanController()) + .build()) .option(Option.createBuilder() .name(Component.translatable("skyblocker.config.otherLocations.rift.mcGrubberStacks")) .description(Component.translatable("skyblocker.config.otherLocations.rift.mcGrubberStacks.@Tooltip")) diff --git a/src/main/java/de/hysky/skyblocker/config/configs/OtherLocationsConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/OtherLocationsConfig.java index a662b4196cd..ae1ccaf869e 100644 --- a/src/main/java/de/hysky/skyblocker/config/configs/OtherLocationsConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/configs/OtherLocationsConfig.java @@ -32,6 +32,8 @@ public static class Rift { public boolean highlightFoundEnigmaSouls = true; + public boolean autoDetectMcGrubber = true; + public int mcGrubberStacks = 0; } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/rift/McGrubberUpdater.java b/src/main/java/de/hysky/skyblocker/skyblock/rift/McGrubberUpdater.java new file mode 100644 index 00000000000..71ecf310e7d --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/rift/McGrubberUpdater.java @@ -0,0 +1,131 @@ +package de.hysky.skyblocker.skyblock.rift; + +import de.hysky.skyblocker.annotations.Init; +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.skyblock.item.tooltip.info.TooltipInfoType; +import de.hysky.skyblocker.utils.ItemUtils; +import de.hysky.skyblocker.utils.RegexUtils; +import de.hysky.skyblocker.utils.Utils; +import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents; +import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.inventory.ContainerScreen; +import net.minecraft.network.chat.Component; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; + +import org.apache.commons.lang3.math.NumberUtils; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Updates the McGrubber Burger count for players automatically when the + * related config option is enabled. + */ +public class McGrubberUpdater { + private static final String MOTES_GRUBBER = "Motes Grubber"; + private static final String CONSUMABLE_ITEMS = "Miscellaneous ➜ Consumable Items"; + private static final Pattern ORB_PICKUP = Pattern.compile("ORB! Picked up \\+(?\\d+) Motes, recovered \\+2ф Rift Time!"); + private static final Pattern MOTES_PRICE = Pattern.compile("(?\\d+(\\.\\d)?) Motes"); + private static final Pattern MCGRUBBER_PROGRESS = Pattern.compile("Total Progress: (?\\d+)%"); + + private McGrubberUpdater() {} + + @Init + public static void init() { + ScreenEvents.BEFORE_INIT.register((_client, screen, _scaledWidth, _scaledHeight) -> { + if (!Utils.isOnSkyblock() || !(screen instanceof ContainerScreen containerScreen)) { + return; + } else if (!SkyblockerConfigManager.get().otherLocations.rift.autoDetectMcGrubber) { + return; + } + + if (Utils.isInTheRift() && containerScreen.getTitle().getString().equals(MOTES_GRUBBER)) { + ScreenEvents.remove(screen).register(_screen -> McGrubberUpdater.fromMotesGrubber(containerScreen)); + } else if (!Utils.isInTheRift() && containerScreen.getTitle().getString().equals(CONSUMABLE_ITEMS)) { + ScreenEvents.remove(screen).register(_screen -> McGrubberUpdater.fromConsumableItems(containerScreen)); + } + }); + ClientReceiveMessageEvents.ALLOW_GAME.register(McGrubberUpdater::fromOrbPickup); + } + + /** + * Detects McGrubber stacks when selling to Motes Grubber NPC + */ + private static void fromMotesGrubber(ContainerScreen screen) { + for (Slot slot : screen.getMenu().slots) { + if (slot.container != Minecraft.getInstance().player.getInventory()) continue; + + ItemStack item = slot.getItem(); + Matcher matcher = ItemUtils.getLoreLineIfMatch(item, MOTES_PRICE); + + if (matcher == null) continue; + + float price = NumberUtils.toFloat(matcher.group("price")); + final String internalID = item.getSkyblockId(); + + // Compare against base price to determine mcgrubber stacks + if (TooltipInfoType.MOTES.hasOrNullWarning(internalID)) { + // x is mcgrubber stacks, y is final price, & z is initial price + // y = (1 + 0.05x)z -> reorder to solve for x -> x = 20y / z - 20 + int mcGrubberStacks = Math.round(20 * price / TooltipInfoType.MOTES.getData().getInt(internalID) - 20); + + if (SkyblockerConfigManager.get().otherLocations.rift.mcGrubberStacks != mcGrubberStacks) { + SkyblockerConfigManager.updateOnly(config -> config.otherLocations.rift.mcGrubberStacks = mcGrubberStacks); + } + + break; + } + } + } + + /** + * Detects McGrubber stacks from the /sblevels GUI + */ + private static void fromConsumableItems(ContainerScreen screen) { + for (Slot slot : screen.getMenu().slots) { + ItemStack item = slot.getItem(); + + if (!item.getSkyblockId().equals("MCGRUBBER_BURGER")) continue; + + Matcher matcher = ItemUtils.getLoreLineIfMatch(item, MCGRUBBER_PROGRESS); + + if (matcher == null) continue; + + // 20% progress per mcgrubber stack + int mcGrubberStacks = RegexUtils.parseIntFromMatcher(matcher, "percent") / 20; + + if (SkyblockerConfigManager.get().otherLocations.rift.mcGrubberStacks != mcGrubberStacks) { + SkyblockerConfigManager.updateOnly(config -> config.otherLocations.rift.mcGrubberStacks = mcGrubberStacks); + } + + break; + } + } + + /** + * Detects McGrubber stacks from picking up orbs in the rift + */ + private static boolean fromOrbPickup(Component text, boolean overlay) { + if (!SkyblockerConfigManager.get().otherLocations.rift.autoDetectMcGrubber) { + return true; + } else if (!Utils.isOnSkyblock() || !Utils.isInTheRift() || overlay) { + return true; + } + + Matcher matcher = ORB_PICKUP.matcher(text.getString()); + + if (matcher.matches()) { + int motes = RegexUtils.parseIntFromMatcher(matcher, "motes"); + // Base of 5 motes, +60 motes per mcGrubber stack. + int mcGrubberStacks = (motes - 5) / 60; + + if (SkyblockerConfigManager.get().otherLocations.rift.mcGrubberStacks != mcGrubberStacks) { + SkyblockerConfigManager.updateOnly(config -> config.otherLocations.rift.mcGrubberStacks = mcGrubberStacks); + } + } + + return true; + } +} diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index 31f14f5edaf..15e5abf9308 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -980,6 +980,8 @@ "skyblocker.config.otherLocations.end.zealotKillsEnabled.@Tooltip": "Only counts melee kills!\n\nKills can be updated by going to the Zealot page in the Bestiary.", "skyblocker.config.otherLocations.rift": "The Rift", + "skyblocker.config.otherLocations.rift.autoDetectMcGrubber": "Autodetect McGrubber Stacks", + "skyblocker.config.otherLocations.rift.autoDetectMcGrubber.@Tooltip": "Detects McGrubber stacks (e.g. from Orb pickup messages) and adjusts the stack count below automatically.", "skyblocker.config.otherLocations.rift.blobbercystGlow": "Blobbercyst Glow", "skyblocker.config.otherLocations.rift.blobbercystGlow.@Tooltip": "Applies the glowing effect to the Blobbercysts from the BACTE fight.", "skyblocker.config.otherLocations.rift.enigmaSoulWaypoints": "Enable Enigma Soul Waypoints", From 541c3b47467bd8558ea76ef0ee65ece561e82077 Mon Sep 17 00:00:00 2001 From: LJNeon <23249107+LJNeon@users.noreply.github.com> Date: Fri, 27 Mar 2026 14:02:20 -0700 Subject: [PATCH 2/2] Fix style issues --- .../de/hysky/skyblocker/skyblock/rift/McGrubberUpdater.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/rift/McGrubberUpdater.java b/src/main/java/de/hysky/skyblocker/skyblock/rift/McGrubberUpdater.java index 71ecf310e7d..d7af000388d 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/rift/McGrubberUpdater.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/rift/McGrubberUpdater.java @@ -34,7 +34,7 @@ private McGrubberUpdater() {} @Init public static void init() { - ScreenEvents.BEFORE_INIT.register((_client, screen, _scaledWidth, _scaledHeight) -> { + ScreenEvents.BEFORE_INIT.register((_, screen, _, _) -> { if (!Utils.isOnSkyblock() || !(screen instanceof ContainerScreen containerScreen)) { return; } else if (!SkyblockerConfigManager.get().otherLocations.rift.autoDetectMcGrubber) { @@ -42,9 +42,9 @@ public static void init() { } if (Utils.isInTheRift() && containerScreen.getTitle().getString().equals(MOTES_GRUBBER)) { - ScreenEvents.remove(screen).register(_screen -> McGrubberUpdater.fromMotesGrubber(containerScreen)); + ScreenEvents.remove(screen).register(_ -> McGrubberUpdater.fromMotesGrubber(containerScreen)); } else if (!Utils.isInTheRift() && containerScreen.getTitle().getString().equals(CONSUMABLE_ITEMS)) { - ScreenEvents.remove(screen).register(_screen -> McGrubberUpdater.fromConsumableItems(containerScreen)); + ScreenEvents.remove(screen).register(_ -> McGrubberUpdater.fromConsumableItems(containerScreen)); } }); ClientReceiveMessageEvents.ALLOW_GAME.register(McGrubberUpdater::fromOrbPickup);