From de35d5ca0070cb562b63921e9911d33b7a64e5f8 Mon Sep 17 00:00:00 2001 From: KSPOG Date: Thu, 21 Aug 2025 16:39:29 +0200 Subject: [PATCH] Wrap flip loop with try-catch --- .../microbot/kspflip/KSPFlipConfig.java | 19 ++ .../microbot/kspflip/KSPFlipOverlay.java | 31 +++ .../microbot/kspflip/KSPFlipPanel.java | 26 +++ .../microbot/kspflip/KSPFlipPlugin.java | 55 +++++ .../microbot/kspflip/KSPFlipScript.java | 191 ++++++++++++++++++ 5 files changed, 322 insertions(+) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/microbot/kspflip/KSPFlipConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/microbot/kspflip/KSPFlipOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/microbot/kspflip/KSPFlipPanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/microbot/kspflip/KSPFlipPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/microbot/kspflip/KSPFlipScript.java diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/kspflip/KSPFlipConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/kspflip/KSPFlipConfig.java new file mode 100644 index 0000000..a0a00dc --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/kspflip/KSPFlipConfig.java @@ -0,0 +1,19 @@ +package net.runelite.client.plugins.microbot.kspflip; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup("kspflip") +public interface KSPFlipConfig extends Config +{ + @ConfigItem( + keyName = "guide", + name = "Guide", + description = "How to use the KSP Flipper plugin" + ) + default String guide() + { + return "Start with coins in your inventory at the Grand Exchange."; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/kspflip/KSPFlipOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/kspflip/KSPFlipOverlay.java new file mode 100644 index 0000000..c19dc80 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/kspflip/KSPFlipOverlay.java @@ -0,0 +1,31 @@ +package net.runelite.client.plugins.microbot.kspflip; + +import java.awt.Dimension; +import java.awt.Graphics2D; +import net.runelite.client.ui.overlay.OverlayPanel; +import net.runelite.client.ui.overlay.components.LineComponent; + +public class KSPFlipOverlay extends OverlayPanel +{ + private String status = "Idle"; + private long profit = 0; + private String runtime = "00:00:00"; + + public void updateOverlay(long profit, String status, String runtime) + { + this.profit = profit; + this.status = status; + this.runtime = runtime; + } + + @Override + public Dimension render(Graphics2D graphics) + { + panelComponent.getChildren().clear(); + panelComponent.getChildren().add(LineComponent.builder().left("Plugin: KSP Flipper v1.0").build()); + panelComponent.getChildren().add(LineComponent.builder().left("Profit: " + profit).build()); + panelComponent.getChildren().add(LineComponent.builder().left("Status: " + status).build()); + panelComponent.getChildren().add(LineComponent.builder().left("Run Time: " + runtime).build()); + return super.render(graphics); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/kspflip/KSPFlipPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/kspflip/KSPFlipPanel.java new file mode 100644 index 0000000..9e2c6bb --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/kspflip/KSPFlipPanel.java @@ -0,0 +1,26 @@ +package net.runelite.client.plugins.microbot.kspflip; + +import java.awt.BorderLayout; +import java.awt.GridLayout; +import javax.swing.JLabel; +import javax.swing.JPanel; +import net.runelite.client.ui.PluginPanel; + +public class KSPFlipPanel extends PluginPanel +{ + private final JLabel lastFlipLabel = new JLabel("Last Flip: - gp"); + + public KSPFlipPanel() + { + setLayout(new BorderLayout()); + JPanel panel = new JPanel(); + panel.setLayout(new GridLayout(0, 1)); + panel.add(lastFlipLabel); + add(panel, BorderLayout.NORTH); + } + + public void updateLastFlip(long profit) + { + lastFlipLabel.setText("Last Flip: " + profit + " gp"); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/kspflip/KSPFlipPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/kspflip/KSPFlipPlugin.java new file mode 100644 index 0000000..bdab9a3 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/kspflip/KSPFlipPlugin.java @@ -0,0 +1,55 @@ +package net.runelite.client.plugins.microbot.kspflip; + +import com.google.inject.Provides; +import java.awt.AWTException; +import javax.inject.Inject; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Client; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.ui.overlay.OverlayManager; + +@Slf4j +@PluginDescriptor( + name = PluginDescriptor.Maxxin + "KSP Flipper", + description = "Automated GE flipper with dynamic item scanning", + tags = {"grand exchange", "flipping", "profit"}, + enabledByDefault = false +) +public class KSPFlipPlugin extends Plugin +{ + @Inject private Client client; + @Inject private OverlayManager overlayManager; + + private KSPFlipOverlay overlay; + private KSPFlipPanel panel; + private KSPFlipScript script; + + @Inject private KSPFlipConfig config; + + @Provides + KSPFlipConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(KSPFlipConfig.class); + } + + @Override + protected void startUp() throws AWTException + { + overlay = new KSPFlipOverlay(); + panel = new KSPFlipPanel(); + overlayManager.add(overlay); + + script = new KSPFlipScript(panel, overlay); + script.run(); + } + + @Override + protected void shutDown() + { + overlayManager.remove(overlay); + script.shutdown(); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/kspflip/KSPFlipScript.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/kspflip/KSPFlipScript.java new file mode 100644 index 0000000..6d1cd47 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/kspflip/KSPFlipScript.java @@ -0,0 +1,191 @@ +package net.runelite.client.plugins.microbot.kspflip; + +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.ItemID; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.breakhandler.BreakHandlerScript; +import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; +import net.runelite.client.plugins.microbot.util.grandexchange.Rs2GrandExchange; +import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; +import net.runelite.client.plugins.microbot.util.math.Rs2Random; + +@Slf4j +public class KSPFlipScript extends Script +{ + private Instant startTime; + private long totalProfit = 0; + private String status = "Idle"; + + private final KSPFlipPanel panel; + private final KSPFlipOverlay overlay; + + private final List itemPool = new ArrayList<>(); + private final Map lastFlipped = new ConcurrentHashMap<>(); + + public KSPFlipScript(KSPFlipPanel panel, KSPFlipOverlay overlay) + { + this.panel = panel; + this.overlay = overlay; + } + + public boolean run() + { + Microbot.enableAutoRunOn = true; + startTime = Instant.now(); + mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { + try { + if (!super.run()) { + return; + } + + if (BreakHandlerScript.breakIn > 0 && BreakHandlerScript.breakIn < 60) { + status = "Preparing for break"; + Rs2GrandExchange.closeExchange(); + sleep(2000); + return; + } + + if (itemPool.isEmpty() || Rs2Random.betweenInclusive(0, 100) < 5) { + refreshItemPool(); + } + + if (!Rs2Bank.isOpen() && Rs2Inventory.hasItem(ItemID.COINS_995)) { + flipLoop(); + } + + overlay.updateOverlay(getProfit(), getStatus(), getRuntime()); + } catch (Exception e) { + log.error("Error in flip loop", e); + status = "Error - retrying"; + } + }, 0, 600, TimeUnit.MILLISECONDS); + return true; + } + + private void refreshItemPool() + { + itemPool.clear(); + status = "Refreshing items"; + + for (int itemId = 0; itemId < 30000; itemId++) + { + long buy = Rs2GrandExchange.getBuyPrice(itemId); + long sell = Rs2GrandExchange.getSellPrice(itemId); + long vol = Rs2GrandExchange.getVolume(itemId); + + if (buy <= 0 || sell <= 0) + { + continue; + } + if (sell < buy) + { + continue; + } + if (vol < 5000) + { + continue; + } + if (isTradeLimitReached(itemId)) + { + continue; + } + + itemPool.add(itemId); + } + + status = "Items found: " + itemPool.size(); + } + + private void flipLoop() + { + if (itemPool.isEmpty()) + { + status = "No items found"; + return; + } + + for (int itemId : itemPool) + { + if (Rs2GrandExchange.getAvailableSlot() == null) + { + break; + } + + status = "Flipping " + itemId; + long buyPrice = Rs2GrandExchange.getSellPrice(itemId); + + int quantity = Math.min(getAffordableQuantity(buyPrice), 1000); + if (quantity <= 0) + { + continue; + } + + Rs2GrandExchange.buyItem(itemId, quantity, buyPrice); + sleepUntil(() -> Rs2GrandExchange.findSlotForItem(itemId, false) != null, 3000); + lastFlipped.put(itemId, System.currentTimeMillis()); + } + + if (Rs2GrandExchange.hasFinishedBuyingOffers() || Rs2GrandExchange.hasFinishedSellingOffers()) + { + collectProfit(); + } + } + + private int getAffordableQuantity(long price) + { + int coins = Rs2Inventory.count(ItemID.COINS_995); + return (int) (coins / price); + } + + private void collectProfit() + { + int coinsBefore = Rs2Inventory.count(ItemID.COINS_995); + Rs2GrandExchange.collectAllToInventory(); + int coinsAfter = Rs2Inventory.count(ItemID.COINS_995); + long profit = coinsAfter - coinsBefore; + if (profit > 0) + { + totalProfit += profit; + panel.updateLastFlip(profit); + } + } + + private boolean isTradeLimitReached(int itemId) + { + if (!lastFlipped.containsKey(itemId)) + { + return false; + } + long last = lastFlipped.get(itemId); + return System.currentTimeMillis() - last < Duration.ofHours(4).toMillis(); + } + + public void shutdown() + { + super.shutdown(); + } + + public String getStatus() + { + return status; + } + + public long getProfit() + { + return totalProfit; + } + + public String getRuntime() + { + Duration d = Duration.between(startTime, Instant.now()); + return String.format("%02d:%02d:%02d", d.toHours(), d.toMinutesPart(), d.toSecondsPart()); + } +}