diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemCutter.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemCutter.java new file mode 100644 index 0000000000..7e4aba37a7 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemCutter.java @@ -0,0 +1,262 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.coords.WorldPoint; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; +import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; +import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; +import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; +import net.runelite.client.plugins.microbot.util.math.Rs2Random; +import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; +import net.runelite.client.plugins.microbot.util.shop.Rs2Shop; +import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; + +import java.awt.event.KeyEvent; + +import static net.runelite.client.plugins.microbot.util.Global.sleep; +import static net.runelite.client.plugins.microbot.util.Global.sleepUntil; + +@Slf4j +public class GemCutter { + + // Toci's actual tile in Aldarin, Varlamore + private static final WorldPoint TOCI_LOCATION = new WorldPoint(1428, 2975, 0); + private static final String TOCI_NPC_NAME = "Toci"; + private static final String CHISEL_NAME = "Chisel"; + private static final int COINS_ID = 995; + private static final String BRIEFCASE_NAME = "Banker's briefcase"; + + @Getter + private GemCutterState state = GemCutterState.WALKING_TO_SHOP; + @Getter + private String status = "Idle"; + + public void reset() { + state = GemCutterState.WALKING_TO_SHOP; + status = "Idle"; + } + + public boolean tick(LeaguesToolkitConfig config) { + GemType gem = config.gemType(); + if (gem == null) { + status = "No gem selected"; + return false; + } + + if (!gem.hasRequiredLevel()) { + status = "Crafting level too low for " + gem.getCutName() + " (need " + gem.getCraftingLevel() + ")"; + return false; + } + + if (!Rs2Inventory.hasItem(CHISEL_NAME)) { + status = "No chisel in inventory"; + return false; + } + + // Need to bank for coins if we're idle (no gems in hand) and low on coins + if (state == GemCutterState.WALKING_TO_SHOP + && Rs2Inventory.itemQuantity(COINS_ID) < config.gemCutterMinCoins() + && !Rs2Inventory.hasItem(gem.getUncutName()) + && !Rs2Inventory.hasItem(gem.getCutName())) { + state = GemCutterState.BANKING; + } + + switch (state) { + case BANKING: + return handleBanking(config); + case WALKING_TO_SHOP: + return handleWalkingToShop(); + case BUYING: + return handleBuying(gem); + case CUTTING: + return handleCutting(gem); + case SELLING: + return handleSelling(gem, config); + } + return true; + } + + private boolean handleBanking(LeaguesToolkitConfig config) { + if (Rs2Shop.isOpen()) { + Rs2Shop.closeShop(); + return true; + } + + if (config.gemCutterUseBriefcase() && Rs2Inventory.hasItem(BRIEFCASE_NAME)) { + if (!Rs2Bank.isOpen()) { + status = "Using briefcase to bank"; + Rs2Inventory.interact(BRIEFCASE_NAME, "Bank"); + sleepUntil(Rs2Bank::isOpen, 5000); + return true; + } + } else { + if (!Rs2Bank.isOpen()) { + status = "Walking to bank"; + if (!Rs2Bank.walkToBankAndUseBank()) return true; + } + } + + if (!Rs2Bank.isOpen()) return true; + + status = "Withdrawing coins"; + if (!Rs2Bank.hasItem(COINS_ID)) { + status = "No coins in bank — stopping"; + Rs2Bank.closeBank(); + return false; + } + + Rs2Bank.withdrawAll(COINS_ID); + Rs2Bank.closeBank(); + state = GemCutterState.WALKING_TO_SHOP; + return true; + } + + private boolean handleWalkingToShop() { + if (Rs2Npc.getNpc(TOCI_NPC_NAME) != null) { + status = "At Toci"; + state = GemCutterState.BUYING; + return true; + } + status = "Walking to Toci"; + if (!Rs2Player.isMoving()) { + Rs2Walker.walkTo(TOCI_LOCATION, 6); + } + return true; + } + + private boolean handleBuying(GemType gem) { + if (!Rs2Shop.isOpen()) { + status = "Opening Toci's shop"; + if (!Rs2Shop.openShop(TOCI_NPC_NAME)) { + status = "Could not open shop"; + return true; + } + sleepUntil(Rs2Shop::isOpen, 3000); + return true; + } + + int uncutCount = Rs2Inventory.count(gem.getUncutName()); + + if (Rs2Inventory.isFull()) { + status = "Inventory full — moving to cut"; + Rs2Shop.closeShop(); + state = GemCutterState.CUTTING; + return true; + } + + if (!Rs2Shop.hasStock(gem.getUncutName())) { + if (uncutCount > 0) { + status = "Shop out of stock — cutting what we have"; + Rs2Shop.closeShop(); + state = GemCutterState.CUTTING; + return true; + } + status = "Shop out of " + gem.getUncutName() + " — waiting"; + sleep(1500, 2500); + return true; + } + + // Mass-click buy at 100-250ms intervals. Stop when 2 consecutive clicks + // fail to add a ruby to inventory (inventory full OR shop out of stock). + status = "Rapid-buying " + gem.getUncutName(); + int safetyMax = 32; + int missedInRow = 0; + for (int i = 0; i < safetyMax; i++) { + if (!Rs2Shop.isOpen()) break; + int before = Rs2Inventory.count(gem.getUncutName()); + Rs2Shop.buyItem(gem.getUncutName(), "1"); + sleep(Rs2Random.between(100, 250)); + if (Rs2Inventory.count(gem.getUncutName()) > before) { + missedInRow = 0; + } else { + missedInRow++; + if (missedInRow >= 2) break; + } + } + return true; + } + + private boolean handleCutting(GemType gem) { + if (!Rs2Inventory.hasItem(gem.getUncutName())) { + status = "All gems cut — moving to sell"; + state = GemCutterState.SELLING; + return true; + } + + // Start the cut: chisel on uncut gem + status = "Starting to cut " + gem.getCutName(); + Rs2Inventory.use(CHISEL_NAME); + sleep(300, 500); + Rs2Inventory.use(gem.getUncutName()); + + // Wait for "How many do you wish to make?" dialog, then press space for All + sleep(600, 900); + Rs2Keyboard.keyPress(KeyEvent.VK_SPACE); + + // Wait for cutting to finish (XP stops flowing OR all uncut gems gone) + sleep(2000, 3000); + status = "Cutting " + gem.getCutName() + "..."; + sleepUntil(() -> !Microbot.isGainingExp || !Rs2Inventory.hasItem(gem.getUncutName()), 60000); + + return true; + } + + private boolean handleSelling(GemType gem, LeaguesToolkitConfig config) { + if (!Rs2Inventory.hasItem(gem.getCutName())) { + status = "All cut gems sold — looping"; + if (Rs2Inventory.itemQuantity(COINS_ID) < config.gemCutterMinCoins()) { + // Only close when we need to walk somewhere (banking) + if (Rs2Shop.isOpen()) Rs2Shop.closeShop(); + state = GemCutterState.BANKING; + } else { + // Leave shop open — handleBuying will use the already-open shop next tick + state = GemCutterState.BUYING; + } + return true; + } + + if (!Rs2Shop.isOpen()) { + status = "Reopening shop to sell"; + if (!Rs2Shop.openShop(TOCI_NPC_NAME)) { + status = "Could not reopen shop"; + return true; + } + sleepUntil(Rs2Shop::isOpen, 3000); + return true; + } + + // Mass-click sell at 100-250ms intervals — click the LAST slot containing + // a cut gem, repeat until inventory runs out. Two consecutive misses = stop. + int initialCount = Rs2Inventory.count(gem.getCutName()); + status = "Rapid-selling " + gem.getCutName() + " x" + initialCount; + + int safetyMax = initialCount + 5; + int missedInRow = 0; + for (int i = 0; i < safetyMax; i++) { + if (!Rs2Shop.isOpen()) break; + if (!Rs2Inventory.hasItem(gem.getCutName())) break; + + Rs2ItemModel last = Rs2Inventory.items(item -> + gem.getCutName().equalsIgnoreCase(item.getName())) + .reduce((a, b) -> b) + .orElse(null); + if (last == null) break; + int before = Rs2Inventory.count(gem.getCutName()); + + Rs2Inventory.slotInteract(last.getSlot(), "Sell 1"); + sleep(Rs2Random.between(100, 250)); + + if (Rs2Inventory.count(gem.getCutName()) < before) { + missedInRow = 0; + } else { + missedInRow++; + if (missedInRow >= 2) break; + } + } + + return true; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemCutterState.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemCutterState.java new file mode 100644 index 0000000000..d08593c9f6 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemCutterState.java @@ -0,0 +1,9 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +public enum GemCutterState { + BANKING, + WALKING_TO_SHOP, + BUYING, + CUTTING, + SELLING +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemType.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemType.java new file mode 100644 index 0000000000..9e29e52f1e --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemType.java @@ -0,0 +1,27 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.runelite.api.Skill; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; + +@Getter +@RequiredArgsConstructor +public enum GemType { + SAPPHIRE("Uncut sapphire", "Sapphire", 20), + EMERALD("Uncut emerald", "Emerald", 27), + RUBY("Uncut ruby", "Ruby", 34); + + private final String uncutName; + private final String cutName; + private final int craftingLevel; + + public boolean hasRequiredLevel() { + return Rs2Player.getSkillRequirement(Skill.CRAFTING, craftingLevel); + } + + @Override + public String toString() { + return cutName; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitConfig.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitConfig.java index 0e6dbc4fb3..68b324196d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitConfig.java @@ -66,4 +66,57 @@ default int antiAfkBufferMin() { default int antiAfkBufferMax() { return 1500; } + + @ConfigSection( + name = "Toci's Gem Cutter", + description = "Buys uncut gems from Toci in Aldarin, cuts them, sells them back", + position = 1, + closedByDefault = true + ) + String gemCutterSection = "gemCutterSection"; + + @ConfigItem( + keyName = "enableGemCutter", + name = "Enable gem cutter", + description = "Walks to Toci, buys uncut gems, cuts them, sells cut gems back — repeats", + position = 0, + section = gemCutterSection + ) + default boolean enableGemCutter() { + return false; + } + + @ConfigItem( + keyName = "gemType", + name = "Gem", + description = "Which gem to cut (requires chisel + coins + crafting level)", + position = 1, + section = gemCutterSection + ) + default GemType gemType() { + return GemType.RUBY; + } + + @Range(min = 1000, max = 1_000_000) + @ConfigItem( + keyName = "gemCutterMinCoins", + name = "Min coins to keep", + description = "When coins drop below this, withdraw more from the bank", + position = 2, + section = gemCutterSection + ) + default int gemCutterMinCoins() { + return 10_000; + } + + @ConfigItem( + keyName = "gemCutterUseBriefcase", + name = "Use Bank Heist briefcase", + description = "Use the banker's briefcase to bank (Leagues relic) instead of walking to a bank", + position = 3, + section = gemCutterSection + ) + default boolean gemCutterUseBriefcase() { + return false; + } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitPlugin.java index bbec460109..e766bbf2fd 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitPlugin.java @@ -20,7 +20,7 @@ ) @Slf4j public class LeaguesToolkitPlugin extends Plugin { - public static final String version = "1.0.0"; + public static final String version = "1.1.0"; @Inject private LeaguesToolkitConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitScript.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitScript.java index fafd6f3760..604a68cc3a 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitScript.java @@ -1,5 +1,6 @@ package net.runelite.client.plugins.microbot.leaguestoolkit; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; @@ -18,6 +19,11 @@ public class LeaguesToolkitScript extends Script { KeyEvent.VK_LEFT, KeyEvent.VK_RIGHT, KeyEvent.VK_UP, KeyEvent.VK_DOWN }; + @Getter + private final GemCutter gemCutter = new GemCutter(); + + private boolean gemCutterWasEnabled = false; + public boolean run(LeaguesToolkitConfig config) { mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { try { @@ -27,6 +33,16 @@ public boolean run(LeaguesToolkitConfig config) { if (config.enableAntiAfk()) { runAntiAfk(config); } + + if (config.enableGemCutter()) { + if (!gemCutterWasEnabled) { + gemCutter.reset(); + gemCutterWasEnabled = true; + } + gemCutter.tick(config); + } else { + gemCutterWasEnabled = false; + } } catch (Exception ex) { log.error("LeaguesToolkitScript loop error", ex); }