diff --git a/README.md b/README.md index ed3a98ea704..0756c9fd358 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,32 @@ If you have any questions, please join our [Discord](https://discord.gg/zaGrfqFE If you enjoy my open source work and would like to support me, consider buying me a coffee! Your support helps me stay caffeinated and motivated to keep improving and creating awesome projects. +## Quest Helper Integration + +The integrated Quest Helper works together with other plugins to give a nice unified experience while questing. + +### Not Enough Runes + +If you have the [Not Enough Runes](https://runelite.net/plugin-hub/show/not-enough-runes) plugin installed, you can +right-click item requirements and click `Go to NER...` to look that item up in Not Enough Runes. + +![Not Enough Runes context menu](./images/not-enough-runes-01.png) + +### Shortest Path + +If you have the [Shortest Path](https://runelite.net/plugin-hub/show/shortest-path) plugin installed, you can enable the +the "Use 'Shortest Path' plugin" in the Quest Helper config. + +![](./images/shortest-path-01.png) + +Next time you start a quest, the Shortest Path plugin will help you take the shortest path to the destination. + +![Shortest Path path overlay example](./images/shortest-path-02.png) + +You can configure what teleportation methods, or the aesthetic of the path in the Shortest Path config. + +## Help and discussion + [![Buy Me a Coffee](https://img.shields.io/badge/Buy%20Me%20a%20Coffee-donate-yellow)](https://www.paypal.com/paypalme/MicrobotBE?country.x=BE) diff --git a/images/not-enough-runes-01.png b/images/not-enough-runes-01.png new file mode 100644 index 00000000000..a43da8e1fe8 Binary files /dev/null and b/images/not-enough-runes-01.png differ diff --git a/images/shortest-path-01.png b/images/shortest-path-01.png new file mode 100644 index 00000000000..7047a569690 Binary files /dev/null and b/images/shortest-path-01.png differ diff --git a/images/shortest-path-02.png b/images/shortest-path-02.png new file mode 100644 index 00000000000..1be5746c5eb Binary files /dev/null and b/images/shortest-path-02.png differ diff --git a/runelite-client/pom.xml b/runelite-client/pom.xml index 41af5e18231..40acb900bee 100644 --- a/runelite-client/pom.xml +++ b/runelite-client/pom.xml @@ -41,7 +41,7 @@ nogit false false - 1.9.9.1 + 2.0.0 nogit diff --git a/runelite-client/src/main/java/net/runelite/client/RuneLite.java b/runelite-client/src/main/java/net/runelite/client/RuneLite.java index 961e186228b..94fe8f142fc 100644 --- a/runelite-client/src/main/java/net/runelite/client/RuneLite.java +++ b/runelite-client/src/main/java/net/runelite/client/RuneLite.java @@ -43,7 +43,10 @@ import net.runelite.client.eventbus.EventBus; import net.runelite.client.externalplugins.ExternalPluginManager; import net.runelite.client.plugins.PluginManager; +import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.externalplugins.MicrobotPluginManager; +import net.runelite.client.proxy.ProxyChecker; +import net.runelite.client.proxy.ProxyConfiguration; import net.runelite.client.rs.ClientLoader; import net.runelite.client.ui.ClientUI; import net.runelite.client.ui.FatalErrorDialog; @@ -76,8 +79,6 @@ import java.io.IOException; import java.lang.management.ManagementFactory; import java.lang.management.RuntimeMXBean; -import java.net.Authenticator; -import java.net.PasswordAuthentication; import java.net.URI; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -186,9 +187,8 @@ public static void main(String[] args) throws Exception { parser.accepts("noupdate", "Skips the launcher update"); parser.accepts("clean-randomdat", "Clean random dat file"); - final ArgumentAcceptingOptionSpec proxyInfo = parser.accepts("proxy", "Use a proxy server for your runelite session") + final ArgumentAcceptingOptionSpec proxyInfo = parser.accepts("proxy", "Use a specified proxy. Format: scheme://user:pass@host:port") .withRequiredArg().ofType(String.class); - parser.accepts("proxy-type", "The Type of proxy: HTTP or SOCKS").withRequiredArg().ofType(String.class); final ArgumentAcceptingOptionSpec sessionfile = parser.accepts("sessionfile", "Use a specified session file") .withRequiredArg() .withValuesConvertedBy(new ConfigFileConverter()) @@ -249,46 +249,7 @@ public static void main(String[] args) throws Exception { } } - //More information about java proxies can be found here - //https://docs.oracle.com/javase/8/docs/technotes/guides/net/proxies.html - //usage: -proxy=IP:PORT:USER:PASS -proxytype=SOCKS - //OR - //usage: -proxy=IP:PORT:USER:PASS -proxytype=HTTP - if (options.has("proxy")) { - String[] proxy = options.valueOf(proxyInfo).split(":"); - boolean httpProxy = false; - boolean socksProxy = true; //default we take socks proxy - if (options.has("proxy-type")) { - socksProxy = options.valueOf("proxy-type").toString().equalsIgnoreCase("SOCKS"); - httpProxy = options.valueOf("proxy-type").toString().equalsIgnoreCase("HTTP"); - } - - ClientUI.proxyMessage = (socksProxy ? "SOCKS" : "HTTP") + " Proxy with address " + options.valueOf(proxyInfo); - - if (httpProxy && proxy.length >= 2) { - System.setProperty("http.proxyHost", proxy[0]); - System.setProperty("http.proxyPort", proxy[1]); - } else if (socksProxy && proxy.length >= 2) { - System.setProperty("socksProxyHost", proxy[0]); - System.setProperty("socksProxyPort", proxy[1]); - } - - if (socksProxy && proxy.length >= 4) { - System.setProperty("java.net.socks.username", proxy[2]); - System.setProperty("java.net.socks.password", proxy[3]); - - final String user = proxy[2]; - final char[] pass = proxy[3].toCharArray(); - - Authenticator.setDefault(new Authenticator() { - private final PasswordAuthentication auth = new PasswordAuthentication(user, pass); - - protected PasswordAuthentication getPasswordAuthentication() { - return auth; - } - }); - } - } + ProxyConfiguration.setupProxy(options, proxyInfo); Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> { @@ -302,6 +263,19 @@ protected PasswordAuthentication getPasswordAuthentication() { RuneLiteAPI.CLIENT = okHttpClient; SplashScreen.init(); + + SplashScreen.stage(0, "Setting up proxy", "Testing proxy address..."); + + if (options.has(proxyInfo)) { + String ip = ProxyChecker.getDetectedIp(okHttpClient); + if (ip.isEmpty()) { + Microbot.showMessage("Failed to detect external IP address, check your proxy settings. \n\n Make sure to use the format scheme://user:pass@host:port"); + System.exit(1); + } + + ClientUI.proxyMessage = " - Proxy enabled (detected IP " + ip + ")"; + } + SplashScreen.stage(0, "Preparing RuneScape", ""); try @@ -458,6 +432,9 @@ public void start() throws Exception // Load user configuration configManager.load(); + // Initialize MicrobotPluginManager after configManager is loaded + microbotPluginManager.init(); + // Update check requires ConfigManager to be ready before it runs Updater updater = injector.getInstance(Updater.class); updater.update(); // will exit if an update is in progress @@ -492,6 +469,7 @@ public void start() throws Exception eventBus.register(clientUI); eventBus.register(pluginManager); eventBus.register(externalPluginManager); + eventBus.register(microbotPluginManager); eventBus.register(overlayManager); eventBus.register(configManager); eventBus.register(discordService); diff --git a/runelite-client/src/main/java/net/runelite/client/RuneLiteDebug.java b/runelite-client/src/main/java/net/runelite/client/RuneLiteDebug.java index f7a9c221013..8f03f286085 100644 --- a/runelite-client/src/main/java/net/runelite/client/RuneLiteDebug.java +++ b/runelite-client/src/main/java/net/runelite/client/RuneLiteDebug.java @@ -42,8 +42,10 @@ import net.runelite.client.eventbus.EventBus; import net.runelite.client.externalplugins.ExternalPluginManager; import net.runelite.client.plugins.PluginManager; +import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.MicrobotClientLoader; import net.runelite.client.plugins.microbot.externalplugins.MicrobotPluginManager; +import net.runelite.client.proxy.ProxyChecker; import net.runelite.client.ui.ClientUI; import net.runelite.client.ui.FatalErrorDialog; import net.runelite.client.ui.SplashScreen; @@ -73,8 +75,6 @@ import java.io.IOException; import java.lang.management.ManagementFactory; import java.lang.management.RuntimeMXBean; -import java.net.Authenticator; -import java.net.PasswordAuthentication; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -174,7 +174,6 @@ public static void main(String[] args) throws Exception { final ArgumentAcceptingOptionSpec proxyInfo = parser.accepts("proxy", "Use a proxy server for your runelite session") .withRequiredArg().ofType(String.class); - parser.accepts("proxy-type", "The Type of proxy: HTTP or SOCKS").withRequiredArg().ofType(String.class); final ArgumentAcceptingOptionSpec sessionfile = parser.accepts("sessionfile", "Use a specified session file") .withRequiredArg() .withValuesConvertedBy(new ConfigFileConverter()) @@ -208,47 +207,6 @@ public static void main(String[] args) throws Exception { logger.setLevel(Level.DEBUG); } - //More information about java proxies can be found here - //https://docs.oracle.com/javase/8/docs/technotes/guides/net/proxies.html - //usage: -proxy=IP:PORT:USER:PASS -proxytype=SOCKS - //OR - //usage: -proxy=IP:PORT:USER:PASS -proxytype=HTTP - if (options.has("proxy")) { - String[] proxy = options.valueOf(proxyInfo).split(":"); - boolean httpProxy = false; - boolean socksProxy = true; //default we take socks proxy - if (options.has("proxy-type")) { - socksProxy = options.valueOf("proxy-type").toString().equalsIgnoreCase("SOCKS"); - httpProxy = options.valueOf("proxy-type").toString().equalsIgnoreCase("HTTP"); - } - - ClientUI.proxyMessage = (socksProxy ? "SOCKS" : "HTTP") + " Proxy with address " + options.valueOf(proxyInfo); - - if (httpProxy && proxy.length >= 2) { - System.setProperty("http.proxyHost", proxy[0]); - System.setProperty("http.proxyPort", proxy[1]); - } else if (socksProxy && proxy.length >= 2) { - System.setProperty("socksProxyHost", proxy[0]); - System.setProperty("socksProxyPort", proxy[1]); - } - - if (socksProxy && proxy.length >= 4) { - System.setProperty("java.net.socks.username", proxy[2]); - System.setProperty("java.net.socks.password", proxy[3]); - - final String user = proxy[2]; - final char[] pass = proxy[3].toCharArray(); - - Authenticator.setDefault(new Authenticator() { - private final PasswordAuthentication auth = new PasswordAuthentication(user, pass); - - protected PasswordAuthentication getPasswordAuthentication() { - return auth; - } - }); - } - } - Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> { log.error("Uncaught exception:", throwable); @@ -262,6 +220,17 @@ protected PasswordAuthentication getPasswordAuthentication() { RuneLiteAPI.CLIENT = okHttpClient; SplashScreen.init(); + + if (options.has(proxyInfo)) { + String ip = ProxyChecker.getDetectedIp(okHttpClient); + if (ip.isEmpty()) { + Microbot.showMessage("Failed to detect external IP address, check your proxy settings. \n\n Make sure to use the format scheme://user:pass@host:port"); + System.exit(1); + } + + ClientUI.proxyMessage = " - Proxy enabled (detected IP " + ip + ")"; + } + SplashScreen.stage(0, "Retrieving client", ""); try { @@ -364,6 +333,9 @@ public void start() throws Exception { // Load user configuration configManager.load(); + // Initialize MicrobotPluginManager after configManager is loaded + microbotPluginManager.init(); + // Update check requires ConfigManager to be ready before it runs Updater updater = injector.getInstance(Updater.class); updater.update(); // will exit if an update is in progress @@ -390,6 +362,7 @@ public void start() throws Exception { eventBus.register(clientUI); eventBus.register(pluginManager); eventBus.register(externalPluginManager); + eventBus.register(microbotPluginManager); eventBus.register(overlayManager); eventBus.register(configManager); eventBus.register(discordService); @@ -403,7 +376,7 @@ public void start() throws Exception { clientUI.show(); - pluginManager.loadRuneliteCorePlugins(); + pluginManager.loadCoreRunelitePlugins(); microbotPluginManager.loadCorePlugins(pluginsToDebug); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java index 58949957243..3a7c93b62b0 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java @@ -215,58 +215,48 @@ public void startPlugins() { } } - public void loadCorePlugins() throws IOException, PluginInstantiationException { - SplashScreen.stage(.59, null, "Loading plugins"); + /** + * Loads core RuneLite plugins, excluding any Microbot-related plugins. + * This method filters out plugins from the microbot package hierarchy. + */ + public void loadCoreRunelitePlugins() throws IOException, PluginInstantiationException { + SplashScreen.stage(.59, null, "Loading core RuneLite plugins"); ClassPath classPath = ClassPath.from(getClass().getClassLoader()); List> plugins = classPath.getTopLevelClassesRecursive(PLUGIN_PACKAGE).stream() .map(ClassInfo::load) + .filter(clazz -> !isMicrobotRelatedClass(clazz)) .collect(Collectors.toList()); loadPlugins(plugins, (loaded, total) -> - SplashScreen.stage(.60, .70, null, "Loading plugins", loaded, total, false)); + SplashScreen.stage(.60, .70, null, "Loading core RuneLite plugins", loaded, total, false)); } /** - * This excludes any microbot plugin + * Determines if a class is related to Microbot and should be excluded from core RuneLite plugin loading. * - * @throws IOException - * @throws PluginInstantiationException + * @param clazz the class to check + * @return true if the class is Microbot-related and should be filtered out */ - public void loadRuneliteCorePlugins() throws IOException, PluginInstantiationException { + private static boolean isMicrobotRelatedClass(Class clazz) { + if (clazz == null || clazz.getPackage() == null) { + return false; + } + + String packageName = clazz.getPackage().getName(); + + return packageName.startsWith(PLUGIN_PACKAGE + ".microbot"); + } + + public void loadCorePlugins() throws IOException, PluginInstantiationException { SplashScreen.stage(.59, null, "Loading plugins"); ClassPath classPath = ClassPath.from(getClass().getClassLoader()); - List> microbotPlugins = new ArrayList<>(); - List> otherPlugins = new ArrayList<>(); - - for (ClassInfo classInfo : classPath.getTopLevelClassesRecursive(PLUGIN_PACKAGE)) { - - Class clazz = classInfo.load(); - String pkg = clazz.getPackageName().toLowerCase(); - - - /** - * TODO: Over time these should be moved into a core folder within the microbot plugins folder - * This way we can easily detect any core plugins required to run microbot - */ - if (pkg.contains(".microbot") && ( - pkg.contains(".util") - || pkg.contains(".ui") - || pkg.endsWith("microbot") - || pkg.contains(".shortestpath") - || pkg.contains(".rs2cachedebugger") - || pkg.contains("pluginscheduler") - || pkg.contains("inventorysetups"))) { - microbotPlugins.add(clazz); - } else if (!pkg.contains("microbot")) { - otherPlugins.add(clazz); - } - } - - otherPlugins.addAll(microbotPlugins); + List> plugins = classPath.getTopLevelClassesRecursive(PLUGIN_PACKAGE).stream() + .map(ClassInfo::load) + .collect(Collectors.toList()); - loadPlugins(otherPlugins, (loaded, total) -> + loadPlugins(plugins, (loaded, total) -> SplashScreen.stage(.60, .70, null, "Loading plugins", loaded, total, false)); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/BlockingEventManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/BlockingEventManager.java index 795b7a08a58..92f73191ee3 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/BlockingEventManager.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/BlockingEventManager.java @@ -11,6 +11,7 @@ import java.util.Set; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; public class BlockingEventManager { @@ -21,7 +22,6 @@ public class BlockingEventManager // Change the queue to hold just the event references private final BlockingQueue eventQueue = new LinkedBlockingQueue<>(MAX_QUEUE_SIZE); - private final ScheduledExecutorService scheduler; private final ExecutorService blockingExecutor; private final AtomicBoolean isRunning = new AtomicBoolean(false); @@ -32,19 +32,21 @@ public class BlockingEventManager return t; }; + private final ScheduledExecutorService scheduler = + Executors.newSingleThreadScheduledExecutor(threadFactory); + private ScheduledFuture loopFuture; + private static final long INITIAL_DELAY_MS = 300; + private volatile long currentDelay = INITIAL_DELAY_MS; + private static final long MAX_DELAY_MS = 5000; // Maximum delay of 5 seconds + private final AtomicInteger failureCount = new AtomicInteger(0); + public BlockingEventManager() { // single-threaded executor for running event.execute() this.blockingExecutor = Executors.newSingleThreadExecutor(threadFactory); // scheduler for periodic validate() calls - this.scheduler = Executors.newSingleThreadScheduledExecutor(threadFactory); - this.scheduler.scheduleWithFixedDelay( - this::validateAndEnqueue, - 0, - 300, - TimeUnit.MILLISECONDS - ); + startLoop(); // pre-register core events blockingEvents.add(new WelcomeScreenEvent()); @@ -59,8 +61,8 @@ public BlockingEventManager() sortBlockingEvents(); } - public void shutdown() - { + public void shutdown() { + if (loopFuture != null) loopFuture.cancel(true); scheduler.shutdownNow(); blockingExecutor.shutdownNow(); } @@ -89,40 +91,55 @@ private void sortBlockingEvents() ); } + private void startLoop() { + loopFuture = scheduler.schedule(this::loopOnce, 0, TimeUnit.MILLISECONDS); + } + + private void loopOnce() { + try { + validateAndEnqueueWithBackoff(); + } catch (Throwable t) { + Microbot.log(Level.ERROR, "BlockingEvent loop error: %s", t); + } finally { + // re-schedule using the latest currentDelay (volatile) + loopFuture = scheduler.schedule(this::loopOnce, currentDelay, TimeUnit.MILLISECONDS); + } + } + /** * Runs every 300ms on the scheduler thread: tries each event.validate() * and, if true, offers it into the queue (drops if full). */ - private void validateAndEnqueue() - { - if(SplashScreen.isOpen()) - { - return; - } - for (BlockingEvent event : blockingEvents) - { - try - { - if (event.validate()) - { - // only enqueue if it wasn't already pending - if (pendingEvents.add(event)) - { - // offer; if the queue is full, drop and remove from pending - if (!eventQueue.offer(event)) - { - pendingEvents.remove(event); + private void validateAndEnqueueWithBackoff() { + boolean hasValidEvents = false; + if (!SplashScreen.isOpen()) { + for (BlockingEvent event : blockingEvents) { + try { + if (event.validate()) { + hasValidEvents = true; + if (pendingEvents.add(event)) { + if (!eventQueue.offer(event)) { + pendingEvents.remove(event); + } } } + } catch (Exception ex) { + Microbot.log(Level.ERROR, + "Error validating BlockingEvent (%s): %s", + event.getName(), + ex); } } - catch (Exception ex) - { - Microbot.log(Level.ERROR, - "Error validating BlockingEvent (%s): %s", - event.getName(), - ex); - } + } + + if (!hasValidEvents) { + // Increase delay exponentially + int failures = failureCount.incrementAndGet(); + currentDelay = Math.min(INITIAL_DELAY_MS * (1L << Math.min(failures, 4)), MAX_DELAY_MS); + } else { + // Reset on success + failureCount.set(0); + currentDelay = INITIAL_DELAY_MS; } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/GirdyScripts/cannonballsmelter/CannonballSmelterConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/GirdyScripts/cannonballsmelter/CannonballSmelterConfig.java deleted file mode 100644 index f1d1cc0d7d1..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/GirdyScripts/cannonballsmelter/CannonballSmelterConfig.java +++ /dev/null @@ -1,14 +0,0 @@ -package net.runelite.client.plugins.microbot.GirdyScripts.cannonballsmelter; - -import net.runelite.client.config.Config; -import net.runelite.client.config.ConfigGroup; -import net.runelite.client.config.ConfigInformation; - -@ConfigGroup("CannonballSmelter") -@ConfigInformation ("
" + - "

Instructions

" + - "

Start script in Edgeville bank

" + - " MUST have Ammo mould in inventory
" + - " and Steel bars in bank

") -public interface CannonballSmelterConfig extends Config { -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/GirdyScripts/cannonballsmelter/CannonballSmelterOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/GirdyScripts/cannonballsmelter/CannonballSmelterOverlay.java deleted file mode 100644 index d263b3fea4b..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/GirdyScripts/cannonballsmelter/CannonballSmelterOverlay.java +++ /dev/null @@ -1,41 +0,0 @@ -package net.runelite.client.plugins.microbot.GirdyScripts.cannonballsmelter; - -import net.runelite.client.plugins.microbot.Microbot; -import net.runelite.client.ui.overlay.OverlayPanel; -import net.runelite.client.ui.overlay.OverlayPosition; -import net.runelite.client.ui.overlay.components.LineComponent; -import net.runelite.client.ui.overlay.components.TitleComponent; - -import javax.inject.Inject; -import java.awt.*; - -public class CannonballSmelterOverlay extends OverlayPanel { - @Inject - CannonballSmelterOverlay(CannonballSmelterPlugin plugin) { - super(plugin); - setPosition(OverlayPosition.TOP_LEFT); - setNaughty(); - } - - @Override - public Dimension render(Graphics2D graphics) { - try { - panelComponent.setPreferredSize(new Dimension(200, 300)); - panelComponent.getChildren().add(TitleComponent.builder() - .text("Cannonball Smelter V" + CannonballSmelterScript.version) - .color(Color.GREEN) - .build()); - - panelComponent.getChildren().add(LineComponent.builder().build()); - - panelComponent.getChildren().add(LineComponent.builder() - .left(Microbot.status) - .build()); - - - } catch(Exception ex) { - Microbot.logStackTrace(this.getClass().getSimpleName(), ex); - } - return super.render(graphics); - } -} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/GirdyScripts/cannonballsmelter/CannonballSmelterPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/GirdyScripts/cannonballsmelter/CannonballSmelterPlugin.java deleted file mode 100644 index cd3c5b6e514..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/GirdyScripts/cannonballsmelter/CannonballSmelterPlugin.java +++ /dev/null @@ -1,51 +0,0 @@ -package net.runelite.client.plugins.microbot.GirdyScripts.cannonballsmelter; - -import com.google.inject.Provides; -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; - -import javax.inject.Inject; -import java.awt.*; - -@PluginDescriptor( - name = PluginDescriptor.Girdy + "Cannonball Smelter", - description = "Makes cannonballs", - tags = {"smithing", "girdy", "skilling"}, - enabledByDefault = false -) -public class CannonballSmelterPlugin extends Plugin { - - @Inject - private CannonballSmelterConfig config; - - @Provides - CannonballSmelterConfig provideConfig(ConfigManager configManager) { - return configManager.getConfig(CannonballSmelterConfig.class); - } - - @Inject - private OverlayManager overlayManager; - @Inject - private CannonballSmelterOverlay cannonballSmelterOverlay; - - @Inject - CannonballSmelterScript cannonballSmelterScript; - - @Override - protected void startUp() throws AWTException { - Microbot.pauseAllScripts.compareAndSet(true, false); - if (overlayManager != null) { - overlayManager.add(cannonballSmelterOverlay); - } - cannonballSmelterScript.run(config); - } - - protected void shutDown() { - cannonballSmelterScript.shutdown(); - overlayManager.remove(cannonballSmelterOverlay); - } -} - diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/GirdyScripts/cannonballsmelter/CannonballSmelterScript.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/GirdyScripts/cannonballsmelter/CannonballSmelterScript.java deleted file mode 100644 index dc2408b95ab..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/GirdyScripts/cannonballsmelter/CannonballSmelterScript.java +++ /dev/null @@ -1,226 +0,0 @@ -package net.runelite.client.plugins.microbot.GirdyScripts.cannonballsmelter; - - -import net.runelite.api.Client; -import net.runelite.api.GameObject; -import net.runelite.api.TileObject; -import net.runelite.api.gameval.ItemID; -import net.runelite.api.gameval.ObjectID; -import net.runelite.client.plugins.microbot.GirdyScripts.cannonballsmelter.enums.CannonballSmelterStates; -import net.runelite.client.plugins.microbot.Microbot; -import net.runelite.client.plugins.microbot.Script; -import net.runelite.client.plugins.microbot.util.antiban.Rs2Antiban; -import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; -import net.runelite.client.plugins.microbot.util.antiban.enums.Activity; -import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; -import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; -import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; -import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; - -import javax.inject.Inject; -import java.awt.event.KeyEvent; -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.TimeUnit; - - -public class CannonballSmelterScript extends Script { - - public static String version = "1.0.2"; - private final ThreadLocalRandom random = ThreadLocalRandom.current(); - @Inject - private CannonballSmelterConfig config; - @Inject - private Client client; - - long startTime; - long endTime; - - CannonballSmelterStates state = CannonballSmelterStates.IDLING; - - - private boolean hasBalls() { - return Rs2Inventory.hasItem(ItemID.MCANNONBALL); - } - private boolean hasBars() { - return Rs2Inventory.hasItem(ItemID.STEEL_BAR); - } - private boolean required() {return (Rs2Inventory.hasItem(ItemID.AMMO_MOULD) || Rs2Inventory.hasItem(ItemID.DOUBLE_AMMO_MOULD));} - - public boolean run(CannonballSmelterConfig config) { - Rs2Camera.setZoom(260); - Rs2Camera.adjustPitch(383); - Rs2Antiban.resetAntibanSettings(); - cannonballAntiBan(); - Rs2AntibanSettings.actionCooldownChance = 0.1; - Microbot.enableAutoRunOn = true; - Microbot.runEnergyThreshold = 5000; - mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { - try { - if (!super.run() || !Microbot.isLoggedIn()) return; - if (Rs2AntibanSettings.actionCooldownActive) return; - startTime = System.currentTimeMillis(); - - - getState(); - - switch (state) { - case GET_MOULD: - getMould(); - break; - case BANKING: - bank(); - break; - case SMELTING: - smelt(); - break; - } - endTime = System.currentTimeMillis(); - long totalTime = endTime - startTime; - System.out.println("Total time for loop " + totalTime); - } catch (Exception ex) { - Microbot.logStackTrace(this.getClass().getSimpleName(), ex); - } - }, 0, 100, TimeUnit.MILLISECONDS); - return true; - } - - private void getState() { - if (!required()) { - state = CannonballSmelterStates.GET_MOULD; - } - else if(hasBars()) { - state = CannonballSmelterStates.SMELTING; - } else if (!hasBars() || (!hasBars() && hasBalls())){ - state = CannonballSmelterStates.BANKING; - } - } - - public void smelt() { - GameObject furnace = Rs2GameObject.getGameObject(ObjectID.VARROCK_DIARY_FURNACE); - if (furnace != null) { - Rs2GameObject.interact(furnace, "Smelt"); - Microbot.status = "Moving to furnace..."; - sleepUntil(() -> Rs2Widget.getWidget(17694733) != null); - if(Rs2Widget.getWidget(17694733) != null) { - Rs2Widget.clickWidget(17694734); - Microbot.status = "Smelting Cannonballs..."; - sleep(200,600); - mouseOff(); - sleepUntil(() -> !hasBars(), 162000); - Rs2Antiban.actionCooldown(); - Rs2Antiban.takeMicroBreakByChance(); - } - } else { - Microbot.log("Cannot find furnace..."); - sleep(10000); - } - } - - public void bank() { - if (!hasBalls() || hasBars()) return; - - Microbot.status = "Banking..."; - int attempts = 0; - - while (!Rs2Bank.isOpen() && attempts++ < 10) { - if (!isRunning()) break; - Rs2Bank.openBank(); - sleep(300, 600); - } - - if (Rs2Bank.isOpen() && !Rs2Bank.hasItem(ItemID.STEEL_BAR)) { - Microbot.showMessage("No steel bars in bank. Halting."); - sleep(3000, 5000); - shutdown(); - return; - } - - Rs2Bank.withdrawAll(ItemID.STEEL_BAR); - sleepUntil(this::hasBars); - - if (hasBars()) { - Rs2Keyboard.keyPress(KeyEvent.VK_ESCAPE); - } else { - Microbot.showMessage("Failed to withdraw steel bars."); - shutdown(); - } - } - - - public void getMould() { - if(!Rs2Inventory.hasItem("ammo mould")) { - if(!Rs2Bank.isOpen()) { - Rs2Bank.openBank(); - } - sleepUntil(Rs2Bank::isOpen); - if(!Rs2Bank.hasItem("ammo mould")) { - Microbot.showMessage("Could not find ammo mould in bank, exiting..."); - sleep(3000, 5000); - shutdown(); - } - Rs2Bank.withdrawOne("ammo mould"); - sleepUntil(this::required, 3000); - } - if(!Rs2Bank.hasItem(ItemID.STEEL_BAR)) { - Microbot.showMessage("Can't find Steel bars in bank, exiting..."); - sleep(3000,5000); - shutdown(); - } - Rs2Bank.withdrawAll(ItemID.STEEL_BAR); - sleepUntil(() -> Rs2Inventory.hasItem(ItemID.STEEL_BAR)); - if(Rs2Inventory.hasItem(ItemID.STEEL_BAR)) { - Rs2Keyboard.keyPress(KeyEvent.VK_ESCAPE); - } - if (!Rs2Inventory.hasItem(ItemID.STEEL_BAR)) { - Microbot.showMessage("Could not find item in bank."); - shutdown(); - } - } - - public void cannonballAntiBan() { - Rs2AntibanSettings.antibanEnabled = true; - Rs2AntibanSettings.usePlayStyle = false; - Rs2AntibanSettings.randomIntervals = false; - Rs2AntibanSettings.simulateFatigue = true; - Rs2AntibanSettings.simulateAttentionSpan = true; - Rs2AntibanSettings.behavioralVariability = true; - Rs2AntibanSettings.nonLinearIntervals = true; - Rs2AntibanSettings.profileSwitching = true; - Rs2AntibanSettings.timeOfDayAdjust = false; - Rs2AntibanSettings.simulateMistakes = true; - Rs2AntibanSettings.moveMouseRandomly = true; - Rs2AntibanSettings.naturalMouse = true; - Rs2AntibanSettings.contextualVariability = true; - Rs2AntibanSettings.dynamicIntensity = true; - Rs2AntibanSettings.dynamicActivity = false; - Rs2AntibanSettings.devDebug = false; - Rs2AntibanSettings.takeMicroBreaks = true; - Rs2AntibanSettings.playSchedule = false; - Rs2AntibanSettings.universalAntiban = false; - Rs2AntibanSettings.microBreakDurationLow = 2; - Rs2AntibanSettings.microBreakDurationHigh = 10; - Rs2AntibanSettings.actionCooldownChance = 1.00; - Rs2AntibanSettings.microBreakChance = 0.15; - Rs2Antiban.setActivity(Activity.GENERAL_SMITHING); - } - - public void mouseOff() { - int horizontal = random.nextBoolean() ? -1 : client.getCanvasWidth() + 1; - int vertical = random.nextBoolean() ? -1 : client.getCanvasHeight() + 1; - - boolean exitHorizontally = random.nextBoolean(); - if (exitHorizontally) { - Microbot.naturalMouse.moveTo(horizontal, random.nextInt(0, client.getCanvasHeight() + 1)); - } else { - Microbot.naturalMouse.moveTo(random.nextInt(0, client.getCanvasWidth() + 1), vertical); - } - } - - @Override - public void shutdown() { - super.shutdown(); - Rs2Antiban.resetAntibanSettings(); - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/GirdyScripts/cannonballsmelter/enums/CannonballSmelterStates.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/GirdyScripts/cannonballsmelter/enums/CannonballSmelterStates.java deleted file mode 100644 index 4359507cefd..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/GirdyScripts/cannonballsmelter/enums/CannonballSmelterStates.java +++ /dev/null @@ -1,8 +0,0 @@ -package net.runelite.client.plugins.microbot.GirdyScripts.cannonballsmelter.enums; - -public enum CannonballSmelterStates { - GET_MOULD, - BANKING, - SMELTING, - IDLING -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/Microbot.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/Microbot.java index 70b10cec071..e013155457c 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/Microbot.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/Microbot.java @@ -1,37 +1,12 @@ package net.runelite.client.plugins.microbot; import com.google.inject.Injector; -import java.awt.Rectangle; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.lang.reflect.Field; -import java.time.Duration; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Objects; -import java.util.Scanner; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.Collectors; -import javax.inject.Inject; -import javax.swing.JDialog; -import javax.swing.JOptionPane; -import javax.swing.SwingUtilities; -import javax.swing.Timer; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Point; import net.runelite.api.*; import net.runelite.api.annotations.Component; import net.runelite.api.annotations.Varbit; @@ -63,11 +38,8 @@ import net.runelite.client.plugins.microbot.configs.SpecialAttackConfigs; import net.runelite.client.plugins.microbot.dashboard.PluginRequestModel; import net.runelite.client.plugins.microbot.qualityoflife.scripts.pouch.PouchScript; -import static net.runelite.client.plugins.microbot.util.Global.sleep; -import static net.runelite.client.plugins.microbot.util.Global.sleepUntil; -import static net.runelite.client.plugins.microbot.util.Global.sleepUntilNotNull; -import net.runelite.client.plugins.microbot.util.cache.Rs2VarbitCache; import net.runelite.client.plugins.microbot.util.cache.Rs2VarPlayerCache; +import net.runelite.client.plugins.microbot.util.cache.Rs2VarbitCache; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; import net.runelite.client.plugins.microbot.util.item.Rs2ItemManager; import net.runelite.client.plugins.microbot.util.menu.NewMenuEntry; @@ -83,6 +55,29 @@ import net.runelite.client.util.WorldUtil; import net.runelite.http.api.worlds.World; import org.slf4j.event.Level; + +import javax.inject.Inject; +import javax.swing.Timer; +import javax.swing.*; +import java.awt.*; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.lang.reflect.Field; +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.*; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; + +import static net.runelite.client.plugins.microbot.util.Global.*; @Slf4j @NoArgsConstructor public class Microbot { @@ -253,6 +248,21 @@ public static StructComposition getStructComposition(int structId) return getClientThread().runOnClientThreadOptional(() -> getClient().getStructComposition(structId)).orElse(null); } + public static List getDBTableRows(int table) + { + return getClientThread().runOnClientThreadOptional(() -> getClient().getDBTableRows(table)).orElse(new ArrayList<>()); + } + + public static Object[] getDBTableField(int rowID, int column, int tupleIndex) + { + return getClientThread().runOnClientThreadOptional(() -> getClient().getDBTableField(rowID, column, tupleIndex)).orElse(new Object[]{}); + } + + public static List getDBRowsByValue(int table, int column, int tupleIndex, Object value) + { + return getClientThread().runOnClientThreadOptional(() -> getClient().getDBRowsByValue(table, column, tupleIndex, value)).orElse(new ArrayList<>()); + } + public static void setIsGainingExp(boolean value) { isGainingExp = value; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotApi.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotApi.java index 8484b63eda9..64a4d5ea7cb 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotApi.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotApi.java @@ -2,6 +2,7 @@ import com.google.gson.Gson; import com.google.gson.JsonParseException; +import lombok.extern.slf4j.Slf4j; import net.runelite.client.RuneLiteProperties; import okhttp3.OkHttpClient; import okhttp3.Request; @@ -18,6 +19,7 @@ /** * Class that communicates with the microbot api */ +@Slf4j public class MicrobotApi { private final OkHttpClient client; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotConfig.java index 097cd495707..55289d3f008 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotConfig.java @@ -12,6 +12,7 @@ public interface MicrobotConfig extends Config { String configGroup = "microbot"; + String installedPlugins = "installedPlugins"; @ConfigSection( name = "General", diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotVersionChecker.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotVersionChecker.java index 51b18fe2fd6..17d1e4b8818 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotVersionChecker.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotVersionChecker.java @@ -1,5 +1,11 @@ package net.runelite.client.plugins.microbot; +import lombok.extern.slf4j.Slf4j; +import net.runelite.client.RuneLiteProperties; +import net.runelite.client.ui.ClientUI; + +import javax.inject.Singleton; +import javax.swing.*; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.HttpURLConnection; @@ -13,6 +19,7 @@ import javax.swing.SwingUtilities; import lombok.extern.slf4j.Slf4j; import net.runelite.client.RuneLiteProperties; +import net.runelite.client.plugins.microbot.util.misc.Rs2UiHelper; import net.runelite.client.ui.ClientUI; @Slf4j @@ -46,14 +53,14 @@ private void runVersionCheck() String localVersion = RuneLiteProperties.getMicrobotVersion(); String remote = remoteVersion == null ? null : remoteVersion.trim(); String local = localVersion == null ? "" : localVersion.trim(); - if (remote != null && !remote.isEmpty() && !remote.equals(local)) + if (remote != null && !remote.isEmpty() && Rs2UiHelper.compareVersions(local, remote) < 0) { newVersionAvailable.set(true); notifyNewVersionAvailable(remote, local); } else { - log.debug("Microbshot client is up to date: {}", local); + log.debug("Microbot client is up to date: {}", local); } } catch (Exception e) @@ -86,35 +93,86 @@ private String fetchRemoteVersion() throws Exception } } - private void notifyNewVersionAvailable(String remoteVersion, String localVersion) - { - appendToTitle(); + /** + * Notify that a new version is available by appending to the title and logging it. + * @param remoteVersion + * @param localVersion + */ + private void notifyNewVersionAvailable(String remoteVersion, String localVersion) { + appendToTitle(remoteVersion, localVersion); log.info("New Microbot client version available: {} (current: {})", remoteVersion, localVersion); } - private void appendToTitle() - { + /** + * Append the new client marker to the title if not already present and + * if the remote version is newer than the local version. + * @param remoteVersion + * @param localVersion + */ + private void appendToTitle(String remoteVersion, String localVersion) { + if (!isLocalVersionLower(localVersion, remoteVersion)) { + return; + } + SwingUtilities.invokeLater(() -> { - try - { + try { var frame = ClientUI.getFrame(); - if (frame == null) - { + if (frame == null) { return; } String oldTitle = String.valueOf(frame.getTitle()); - if (!oldTitle.contains(NEW_CLIENT_MARKER)) - { + if (!oldTitle.contains(NEW_CLIENT_MARKER)) { frame.setTitle(oldTitle + " " + NEW_CLIENT_MARKER); } } - catch (Exception e) - { + catch (Exception e) { + log.warn("Failed to update client title", e); + } + }); + } + + /** + * Append the new client marker to the title if not already present. + */ + private void appendToTitle() { + SwingUtilities.invokeLater(() -> { + try { + var frame = ClientUI.getFrame(); + if (frame == null) { + return; + } + String oldTitle = String.valueOf(frame.getTitle()); + if (!oldTitle.contains(NEW_CLIENT_MARKER)) { + frame.setTitle(oldTitle + " " + NEW_CLIENT_MARKER); + } + } + catch (Exception e) { log.warn("Failed to update client title", e); } }); } + /** + * Check if the local version is lower than the remote version. + * @param localVersion + * @param remoteVersion + * @return + */ + private boolean isLocalVersionLower(String localVersion, String remoteVersion) { + String[] local = localVersion.split("\\."); + String[] remote = remoteVersion.split("\\."); + + int length = Math.min(local.length, remote.length); + for (int i = 0; i < length; i++) { + int localPart = Integer.parseInt(local[i]); + int remotePart = Integer.parseInt(remote[i]); + if (localPart != remotePart) { + return localPart < remotePart; + } + } + return local.length < remote.length; + } + public void checkForUpdate() { if (scheduled.compareAndSet(false, true)) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/RandomFactClient.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/RandomFactClient.java new file mode 100644 index 00000000000..976b8477321 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/RandomFactClient.java @@ -0,0 +1,65 @@ +package net.runelite.client.plugins.microbot; + +import lombok.extern.slf4j.Slf4j; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +import javax.swing.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +/** + * Client to fetch random facts from the microbot API. + */ +@Slf4j +public class RandomFactClient { + private static final String MICROBOT_API_URL = "https://microbot.cloud/api"; + private static final OkHttpClient client = new OkHttpClient.Builder() + .connectTimeout(10, TimeUnit.SECONDS) + .readTimeout(10, TimeUnit.SECONDS) + .build(); + + /** + * Fetches a random fact from the microbot API. + * @return random fact string or error message + */ + public static String getRandomFact() { + try { + Request request = new Request.Builder() + .url(MICROBOT_API_URL + "/fact/random") + .build(); + + try (Response response = client.newCall(request).execute()) { + if (!response.isSuccessful() || response.body() == null) { + return "Failed to fetch random fact"; + } + return response.body().string(); + } + } catch (Exception e) { + return null; + } + } + + /** + * Fetches a random fact asynchronously and invokes the callback on the Swing EDT. + * @param callback + */ + public static void getRandomFactAsync(Consumer callback) { + CompletableFuture.supplyAsync(() -> { + try { + return getRandomFact(); + } catch (Exception e) { + log.error("Error in async fact fetching", e); + } + return null; + }).thenAccept(fact -> { + // Ensure UI update happens on EDT + SwingUtilities.invokeLater(() -> callback.accept(fact)); + }).exceptionally(throwable -> { + log.error("Async fact fetching failed", throwable); + return null; + }); + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/accountselector/AutoLoginConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/accountselector/AutoLoginConfig.java index 39be1fa0b63..2c09eee9778 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/accountselector/AutoLoginConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/accountselector/AutoLoginConfig.java @@ -27,18 +27,63 @@ public interface AutoLoginConfig extends Config { @ConfigItem( keyName = "Is Member", name = "Is Member", - description = "use Member worlds", - position = 0, + description = "Use member worlds", + position = 1, section = generalSection ) default boolean isMember() { return false; } @ConfigItem( keyName = "RandomWorld", - name = "RandomWorld", - description = "use random worlds", - position = 0, + name = "Use Random World", + description = "Use random worlds", + position = 2, section = generalSection ) default boolean useRandomWorld() { return true; } + + @ConfigSection( + name = "Region Filter", + description = "Filter random world selection by region", + position = 10, + closedByDefault = false + ) + String regionSection = "region"; + + + @ConfigItem( + keyName = "AllowUK", + name = "UK", + description = "Allow UK worlds", + position = 1, + section = regionSection + ) + default boolean allowUK() { return true; } + + @ConfigItem( + keyName = "AllowUS", + name = "US", + description = "Allow US worlds", + position = 2, + section = regionSection + ) + default boolean allowUS() { return true; } + + @ConfigItem( + keyName = "AllowGermany", + name = "Germany", + description = "Allow German worlds", + position = 3, + section = regionSection + ) + default boolean allowGermany() { return true; } + + @ConfigItem( + keyName = "AllowAustralia", + name = "Australia", + description = "Allow Australian worlds", + position = 4, + section = regionSection + ) + default boolean allowAustralia() { return true; } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/accountselector/AutoLoginScript.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/accountselector/AutoLoginScript.java index f9bf10613db..156423ffc83 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/accountselector/AutoLoginScript.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/accountselector/AutoLoginScript.java @@ -3,21 +3,65 @@ import net.runelite.api.GameState; 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.security.Login; +import net.runelite.http.api.worlds.WorldRegion; +import org.slf4j.event.Level; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; import java.util.concurrent.TimeUnit; public class AutoLoginScript extends Script { + private List getAllowedRegions(AutoLoginConfig config) { + List allowedRegions = new ArrayList<>(); + + if (config.allowUK()) { + allowedRegions.add(WorldRegion.UNITED_KINGDOM); + } + if (config.allowUS()) { + allowedRegions.add(WorldRegion.UNITED_STATES_OF_AMERICA); + } + if (config.allowGermany()) { + allowedRegions.add(WorldRegion.GERMANY); + } + if (config.allowAustralia()) { + allowedRegions.add(WorldRegion.AUSTRALIA); + } + + return allowedRegions; + } + + private int getRandomWorldWithRegionFilter(AutoLoginConfig config) { + List allowedRegions = getAllowedRegions(config); + + if (allowedRegions.isEmpty()) { + // If no regions allowed, use default method + return Login.getRandomWorld(config.isMember()); + } + + // Pick a random region from allowed regions + Random random = new Random(); + WorldRegion selectedRegion = allowedRegions.get(random.nextInt(allowedRegions.size())); + + return Login.getRandomWorld(config.isMember(), selectedRegion); + } + public boolean run(AutoLoginConfig autoLoginConfig) { mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { try { if (!super.run()) return; + if (BreakHandlerScript.isBreakActive() || BreakHandlerScript.isMicroBreakActive()) return; if (Microbot.getClient().getGameState() == GameState.LOGIN_SCREEN) { if (autoLoginConfig.useRandomWorld()) { - new Login(Login.getRandomWorld(autoLoginConfig.isMember())); + final int world = getRandomWorldWithRegionFilter(autoLoginConfig); + Microbot.log(Level.INFO, String.format("Auto-logging into random %s world: %d", autoLoginConfig.isMember() ? "member" : "free", world)); + new Login(world); } else { + Microbot.log(Level.INFO, String.format("Auto-logging into world: %d", autoLoginConfig.world())); new Login(autoLoginConfig.world()); } sleep(5000); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/agility/AgilityScript.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/agility/AgilityScript.java index e1f4a572663..a5fe16667cd 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/agility/AgilityScript.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/agility/AgilityScript.java @@ -38,6 +38,8 @@ public class AgilityScript extends Script final MicroAgilityConfig config; WorldPoint startPoint = null; + int lastAgilityXp = 0; + long lastTimeoutWarning = 0; // For throttled timeout warnings @Inject public AgilityScript(MicroAgilityPlugin plugin, MicroAgilityConfig config) @@ -58,6 +60,7 @@ public boolean run() Rs2Antiban.resetAntibanSettings(); Rs2Antiban.antibanSetupTemplates.applyAgilitySetup(); startPoint = plugin.getCourseHandler().getStartPoint(); + lastAgilityXp = Microbot.getClient().getSkillExperience(Skill.AGILITY); mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { try { @@ -87,6 +90,7 @@ public boolean run() } final WorldPoint playerWorldLocation = Microbot.getClient().getLocalPlayer().getWorldLocation(); + final int currentAgilityXp = Microbot.getClient().getSkillExperience(Skill.AGILITY); if (handleFood()) { @@ -97,63 +101,14 @@ public boolean run() return; } - if (plugin.getCourseHandler().getCurrentObstacleIndex() > 0) - { - if (Rs2Player.isMoving() || Rs2Player.isAnimating()) - { - return; - } - } - if (lootMarksOfGrace()) { return; } - if (config.alchemy()) - { - getAlchItem().ifPresent(item -> Rs2Magic.alch(item, 50, 75)); - } - - if (plugin.getCourseHandler() instanceof PrifddinasCourse) - { - PrifddinasCourse course = (PrifddinasCourse) plugin.getCourseHandler(); - if (course.handlePortal()) - { - return; - } - - if (course.handleWalkToStart(playerWorldLocation)) - { - return; - } - } - else if(plugin.getCourseHandler() instanceof WerewolfCourse) - { - WerewolfCourse course = (WerewolfCourse) plugin.getCourseHandler(); - if(course.handleFirstSteppingStone(playerWorldLocation)) - { - return; - } - if(course.handleStickPickup(playerWorldLocation)) - { - return; - } - else if(course.handleSlide()) - { - return; - } - else if(course.handleStickReturn(playerWorldLocation)) - { - return; - } - } - else if (!(plugin.getCourseHandler() instanceof GnomeStrongholdCourse)) + if (handleCourseSpecificActions(playerWorldLocation)) { - if (plugin.getCourseHandler().handleWalkToStart(playerWorldLocation)) - { - return; - } + return; } final int agilityExp = Microbot.getClient().getSkillExperience(Skill.AGILITY); @@ -171,11 +126,86 @@ else if (!(plugin.getCourseHandler() instanceof GnomeStrongholdCourse)) Rs2Walker.walkMiniMap(gameObject.getWorldLocation()); } - if (Rs2GameObject.interact(gameObject)) + // Check if we should click (handles animation/XP logic) + if (!plugin.getCourseHandler().shouldClickObstacle(currentAgilityXp, lastAgilityXp)) + { + return; // Not ready to click yet + } + + // Update XP if we got it while animating + if (currentAgilityXp > lastAgilityXp) { - plugin.getCourseHandler().waitForCompletion(agilityExp, Microbot.getClient().getLocalPlayer().getWorldLocation().getPlane()); - Rs2Antiban.actionCooldown(); - Rs2Antiban.takeMicroBreakByChance(); + lastAgilityXp = currentAgilityXp; + } + + // Handle alchemy if enabled + if (shouldPerformAlch()) + { + Optional alchItem = getAlchItem(); + if (alchItem.isPresent()) + { + // Check if we should skip inefficient alchs + if (config.skipInefficient()) + { + // Only alch if obstacle is far enough for efficient alching + if (gameObject.getWorldLocation().distanceTo(playerWorldLocation) >= 5) + { + if (config.efficientAlching()) + { + if (performEfficientAlch(gameObject, alchItem.get(), agilityExp)) + { + return; + } + } + else + { + // Still do normal alch if far enough but efficient alching is disabled + performNormalAlch(alchItem.get()); + } + } + // Skip alching if obstacle is too close + } + else + { + // Normal behavior when skipInefficient is disabled + if (config.efficientAlching()) + { + if (performEfficientAlch(gameObject, alchItem.get(), agilityExp)) + { + return; + } + } + // Fall back to normal alching + performNormalAlch(alchItem.get()); + } + } + } + + // Normal obstacle interaction + if (Rs2GameObject.interact(gameObject)) { + // Wait for completion - this now returns quickly on XP drop + boolean completed = plugin.getCourseHandler().waitForCompletion(agilityExp, + Microbot.getClient().getLocalPlayer().getWorldLocation().getPlane()); + + if (!completed) { + // Timeout occurred - log warning (throttled to once per 30 seconds) + long now = System.currentTimeMillis(); + if (now - lastTimeoutWarning > 30000) { + Microbot.log("Obstacle completion timed out - retrying on next iteration"); + lastTimeoutWarning = now; + } + return; // Bail early to avoid acting on stale state + } + + // XP tracking is already updated before clicking (line 137) + // Don't update here to avoid losing early action state + + // If we're still animating after XP, don't add delays - proceed immediately + if (!Rs2Player.isAnimating() && !Rs2Player.isMoving()) { + // Only add delays if we're not animating + Rs2Antiban.actionCooldown(); + Rs2Antiban.takeMicroBreakByChance(); + } } } catch (Exception ex) @@ -191,7 +221,7 @@ private Optional getAlchItem() String itemsInput = config.itemsToAlch().trim(); if (itemsInput.isEmpty()) { - Microbot.log("No items specified for alching or none available."); + // Microbot.log("No items specified for alching or none available."); return Optional.empty(); } @@ -203,7 +233,7 @@ private Optional getAlchItem() if (itemsToAlch.isEmpty()) { - Microbot.log("No valid items specified for alching."); + // Microbot.log("No valid items specified for alching."); return Optional.empty(); } @@ -292,4 +322,82 @@ private boolean handleSummerPies() } return true; } + + private boolean shouldPerformAlch() + { + if (!config.alchemy()) + { + return false; + } + + // Check if we should skip alching based on configured chance + if (Math.random() * 100 < config.alchSkipChance()) + { + return false; + } + + return true; + } + + private boolean performEfficientAlch(TileObject gameObject, String alchItem, int agilityExp) + { + WorldPoint playerLocation = Microbot.getClient().getLocalPlayer().getWorldLocation(); + + if (gameObject.getWorldLocation().distanceTo(playerLocation) >= 5) + { + // Efficient alching: click, alch, click + if (Rs2GameObject.interact(gameObject)) + { + sleep(100, 200); + Rs2Magic.alch(alchItem, 50, 75); + Rs2GameObject.interact(gameObject); + boolean completed = plugin.getCourseHandler().waitForCompletion(agilityExp, + Microbot.getClient().getLocalPlayer().getWorldLocation().getPlane()); + + if (!completed) { + // Timeout during efficient alching - log warning + long now = System.currentTimeMillis(); + if (now - lastTimeoutWarning > 30000) { + Microbot.log("Obstacle completion timed out during efficient alching"); + lastTimeoutWarning = now; + } + return false; // Return false to indicate alch sequence failed + } + + Rs2Antiban.actionCooldown(); + Rs2Antiban.takeMicroBreakByChance(); + lastAgilityXp = Microbot.getClient().getSkillExperience(Skill.AGILITY); + return true; + } + } + return false; + } + + private void performNormalAlch(String alchItem) + { + // Simple alch - waitForCompletion handles all timing + Rs2Magic.alch(alchItem, 50, 75); + } + + private boolean handleCourseSpecificActions(WorldPoint playerWorldLocation) + { + if (plugin.getCourseHandler() instanceof PrifddinasCourse) + { + PrifddinasCourse course = (PrifddinasCourse) plugin.getCourseHandler(); + return course.handlePortal() || course.handleWalkToStart(playerWorldLocation); + } + else if (plugin.getCourseHandler() instanceof WerewolfCourse) + { + WerewolfCourse course = (WerewolfCourse) plugin.getCourseHandler(); + return course.handleFirstSteppingStone(playerWorldLocation) + || course.handleStickPickup(playerWorldLocation) + || course.handleSlide() + || course.handleStickReturn(playerWorldLocation); + } + else if (!(plugin.getCourseHandler() instanceof GnomeStrongholdCourse)) + { + return plugin.getCourseHandler().handleWalkToStart(playerWorldLocation); + } + return false; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/agility/MicroAgilityConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/agility/MicroAgilityConfig.java index e6f73392c47..006bb980d43 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/agility/MicroAgilityConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/agility/MicroAgilityConfig.java @@ -5,6 +5,7 @@ import net.runelite.client.config.ConfigInformation; import net.runelite.client.config.ConfigItem; import net.runelite.client.config.ConfigSection; +import net.runelite.client.config.Range; import net.runelite.client.plugins.microbot.agility.enums.AgilityCourse; @ConfigGroup("MicroAgility") @@ -77,4 +78,41 @@ default String itemsToAlch() { return ""; } + + @ConfigItem( + keyName = "efficientAlching", + name = "Efficient Alching", + description = "Click obstacle first, then alch, then click again (for obstacles 5+ tiles away)", + position = 6, + section = generalSection + ) + default boolean efficientAlching() + { + return false; + } + + @ConfigItem( + keyName = "skipInefficient", + name = "Skip Inefficient", + description = "Only alch when obstacle is 5+ tiles away (skip inefficient alchs)", + position = 7, + section = generalSection + ) + default boolean skipInefficient() + { + return false; + } + + @ConfigItem( + keyName = "alchSkipChance", + name = "Alch Skip Chance", + description = "Percentage chance to skip alching on any obstacle (0-100)", + position = 8, + section = generalSection + ) + @Range(min = 0, max = 100) + default int alchSkipChance() + { + return 5; + } } \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/agility/courses/AgilityCourseHandler.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/agility/courses/AgilityCourseHandler.java index d0b8cf435c2..dcc5a4d9628 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/agility/courses/AgilityCourseHandler.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/agility/courses/AgilityCourseHandler.java @@ -77,18 +77,58 @@ default TileObject getCurrentObstacle() return Rs2GameObject.getAll(validObjectPredicate).stream().findFirst().orElse(null); } + // Simple method to check if we should click or wait + default boolean shouldClickObstacle(final int currentXp, final int lastXp) + { + // If animating/moving + if (Rs2Player.isAnimating() || Rs2Player.isMoving()) + { + // Only click if we got XP (signals completion) + return currentXp > lastXp; + } + // Not animating, safe to click + return true; + } + default boolean waitForCompletion(final int agilityExp, final int plane) { double initialHealth = Rs2Player.getHealthPercentage(); int timeoutMs = 15000; - - Global.sleepUntil(() -> Microbot.getClient().getSkillExperience(Skill.AGILITY) != agilityExp || Rs2Player.getHealthPercentage() < initialHealth || Microbot.getClient().getTopLevelWorldView().getPlane() != plane, timeoutMs); - - boolean gainedExp = Microbot.getClient().getSkillExperience(Skill.AGILITY) != agilityExp; - boolean planeChanged = Microbot.getClient().getTopLevelWorldView().getPlane() != plane; - boolean lostHealth = Rs2Player.getHealthPercentage() < initialHealth; - - return gainedExp || planeChanged || lostHealth; + long startTime = System.currentTimeMillis(); + long lastMovingTime = System.currentTimeMillis(); + int waitDelay = 1000; // Default 1 second wait after movement stops + + // Check every 100ms for completion + while (System.currentTimeMillis() - startTime < timeoutMs) + { + // Update last moving time if player is still moving/animating + if (Rs2Player.isMoving() || Rs2Player.isAnimating()) + { + lastMovingTime = System.currentTimeMillis(); + } + + // Get current XP + int currentXp = Microbot.getClient().getSkillExperience(Skill.AGILITY); + + // Use the isObstacleComplete hook for course-specific completion logic + if (isObstacleComplete(currentXp, agilityExp, lastMovingTime, waitDelay)) + { + return true; + } + + // Check other completion conditions (health loss, plane change) + if (Rs2Player.getHealthPercentage() < initialHealth || + Microbot.getClient().getTopLevelWorldView().getPlane() != plane) + { + return true; + } + + // Sleep before next check + Global.sleep(100); + } + + // Timeout reached + return false; } default int getCurrentObstacleIndex() @@ -169,4 +209,19 @@ default boolean handleWalkToStart(WorldPoint playerWorldLocation) default int getLootDistance() { return 1; } + + default boolean isObstacleComplete(int currentXp, int previousXp, long lastMovingTime, int waitDelay) { + // Check if we gained XP (obstacle complete) + if (currentXp > previousXp) { + return true; + } + + // Check if still moving/animating + if (Rs2Player.isMoving() || Rs2Player.isAnimating()) { + return false; + } + + // Check if we've waited long enough after movement stopped + return System.currentTimeMillis() - lastMovingTime >= waitDelay; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/agility/courses/ColossalWyrmAdvancedCourse.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/agility/courses/ColossalWyrmAdvancedCourse.java index 2aca2023fcc..64bfb797fed 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/agility/courses/ColossalWyrmAdvancedCourse.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/agility/courses/ColossalWyrmAdvancedCourse.java @@ -1,10 +1,14 @@ package net.runelite.client.plugins.microbot.agility.courses; import java.util.List; +import net.runelite.api.Skill; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.ObjectID; +import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.agility.models.AgilityObstacleModel; +import net.runelite.client.plugins.microbot.util.Global; import net.runelite.client.plugins.microbot.util.misc.Operation; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; public class ColossalWyrmAdvancedCourse implements AgilityCourseHandler { @@ -32,4 +36,65 @@ public Integer getRequiredLevel() { return 62; } + + @Override + public boolean shouldClickObstacle(int currentXp, int lastXp) { + // Colossal Wyrm courses have multi-XP drop obstacles + // Don't allow early clicking based on XP - wait for animation to finish + return !Rs2Player.isMoving() && !Rs2Player.isAnimating(); + } + + @Override + public boolean waitForCompletion(final int agilityExp, final int plane) + { + double initialHealth = Rs2Player.getHealthPercentage(); + int timeoutMs = 15000; + long startTime = System.currentTimeMillis(); + long lastMovingTime = System.currentTimeMillis(); + int waitDelay = 2000; // Colossal Wyrm needs longer wait after movement stops + + // Check every 100ms for completion + while (System.currentTimeMillis() - startTime < timeoutMs) + { + // Update last moving time if player is still moving/animating + if (Rs2Player.isMoving() || Rs2Player.isAnimating()) + { + lastMovingTime = System.currentTimeMillis(); + } + + // Get current XP + int currentXp = Microbot.getClient().getSkillExperience(Skill.AGILITY); + + // Use our custom completion logic that ignores XP + if (isObstacleComplete(currentXp, agilityExp, lastMovingTime, waitDelay)) + { + return true; + } + + // Check other completion conditions (health loss, plane change) + if (Rs2Player.getHealthPercentage() < initialHealth || + Microbot.getClient().getTopLevelWorldView().getPlane() != plane) + { + return true; + } + + // Sleep before next check + Global.sleep(100); + } + + // Timeout reached + return false; + } + + @Override + public boolean isObstacleComplete(int currentXp, int previousXp, long lastMovingTime, int waitDelay) { + // Colossal Wyrm courses have multi-XP drop obstacles + // We ignore XP checks and only rely on movement/animation + if (Rs2Player.isMoving() || Rs2Player.isAnimating()) { + return false; + } + + // Check if we've waited long enough after movement stopped + return System.currentTimeMillis() - lastMovingTime >= waitDelay; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/agility/courses/ColossalWyrmBasicCourse.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/agility/courses/ColossalWyrmBasicCourse.java index 89bc92ce5a6..ef8cb3439c9 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/agility/courses/ColossalWyrmBasicCourse.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/agility/courses/ColossalWyrmBasicCourse.java @@ -1,10 +1,14 @@ package net.runelite.client.plugins.microbot.agility.courses; import java.util.List; +import net.runelite.api.Skill; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.ObjectID; +import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.agility.models.AgilityObstacleModel; +import net.runelite.client.plugins.microbot.util.Global; import net.runelite.client.plugins.microbot.util.misc.Operation; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; public class ColossalWyrmBasicCourse implements AgilityCourseHandler { @@ -32,4 +36,65 @@ public Integer getRequiredLevel() { return 50; } + + @Override + public boolean shouldClickObstacle(int currentXp, int lastXp) { + // Colossal Wyrm courses have multi-XP drop obstacles + // Don't allow early clicking based on XP - wait for animation to finish + return !Rs2Player.isMoving() && !Rs2Player.isAnimating(); + } + + @Override + public boolean waitForCompletion(final int agilityExp, final int plane) + { + double initialHealth = Rs2Player.getHealthPercentage(); + int timeoutMs = 15000; + long startTime = System.currentTimeMillis(); + long lastMovingTime = System.currentTimeMillis(); + int waitDelay = 2000; // Colossal Wyrm needs longer wait after movement stops + + // Check every 100ms for completion + while (System.currentTimeMillis() - startTime < timeoutMs) + { + // Update last moving time if player is still moving/animating + if (Rs2Player.isMoving() || Rs2Player.isAnimating()) + { + lastMovingTime = System.currentTimeMillis(); + } + + // Get current XP + int currentXp = Microbot.getClient().getSkillExperience(Skill.AGILITY); + + // Use our custom completion logic that ignores XP + if (isObstacleComplete(currentXp, agilityExp, lastMovingTime, waitDelay)) + { + return true; + } + + // Check other completion conditions (health loss, plane change) + if (Rs2Player.getHealthPercentage() < initialHealth || + Microbot.getClient().getTopLevelWorldView().getPlane() != plane) + { + return true; + } + + // Sleep before next check + Global.sleep(100); + } + + // Timeout reached + return false; + } + + @Override + public boolean isObstacleComplete(int currentXp, int previousXp, long lastMovingTime, int waitDelay) { + // Colossal Wyrm courses have multi-XP drop obstacles + // We ignore XP checks and only rely on movement/animation + if (Rs2Player.isMoving() || Rs2Player.isAnimating()) { + return false; + } + + // Check if we've waited long enough after movement stopped + return System.currentTimeMillis() - lastMovingTime >= waitDelay; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/agility/courses/PyramidCourse.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/agility/courses/PyramidCourse.java new file mode 100644 index 00000000000..240fceb426f --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/agility/courses/PyramidCourse.java @@ -0,0 +1,1215 @@ +package net.runelite.client.plugins.microbot.agility.courses; + +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.GameObject; +import net.runelite.api.GroundObject; +import net.runelite.api.ItemID; +import net.runelite.api.NPC; +import net.runelite.api.Skill; +import net.runelite.api.TileObject; +import net.runelite.api.WallObject; +import net.runelite.api.coords.WorldPoint; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.agility.models.AgilityObstacleModel; +import net.runelite.client.plugins.microbot.util.Global; +import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; +import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; +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.walker.Rs2Walker; +import net.runelite.client.plugins.microbot.agility.courses.PyramidObstacleData.ObstacleArea; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +public class PyramidCourse implements AgilityCourseHandler { + + private static final WorldPoint START_POINT = new WorldPoint(3354, 2830, 0); + private static final WorldPoint SIMON_LOCATION = new WorldPoint(3343, 2827, 0); + private static final String SIMON_NAME = "Simon Templeton"; + private static final int PYRAMID_TOP_REGION = 12105; + + // Centralized state tracking + private final PyramidState state = new PyramidState(); + + + // Obstacle areas are now defined in PyramidObstacleData for better maintainability + private static final List OBSTACLE_AREAS = PyramidObstacleData.OBSTACLE_AREAS; + + @Override + public WorldPoint getStartPoint() { + return START_POINT; + } + + @Override + public List getObstacles() { + // Return all unique obstacle IDs for compatibility + return Arrays.asList( + new AgilityObstacleModel(10857), // Stairs + new AgilityObstacleModel(10865), // Low wall + new AgilityObstacleModel(10860), // Ledge + new AgilityObstacleModel(10867), // Plank (main object) + new AgilityObstacleModel(10868), // Plank end (clickable) + new AgilityObstacleModel(10859), // Gap jump + new AgilityObstacleModel(10882), // Gap (floor 1) + new AgilityObstacleModel(10886), // Ledge 3 + new AgilityObstacleModel(10884), // Gap (floor 2) + new AgilityObstacleModel(10861), // Gap + new AgilityObstacleModel(10888), // Ledge 2 + new AgilityObstacleModel(10851), // Climbing rocks + new AgilityObstacleModel(10855) // Doorway + ); + } + + @Override + public TileObject getCurrentObstacle() { + WorldPoint playerPos = Rs2Player.getWorldLocation(); + + // Null check for player position (can happen during logout/disconnect) + if (playerPos == null) { + if (log.isDebugEnabled()) { + log.debug("Player position is null (likely during logout/disconnect) - returning null"); + } + return null; + } + + if (log.isDebugEnabled()) { + log.debug("=== getCurrentObstacle called - Player at {} (plane: {}) ===", playerPos, playerPos.getPlane()); + log.debug("FLAG STATES: CrossGap={}, XpObstacle={}, PyramidTurnIn={}", + state.isDoingCrossGap(), state.isDoingXpObstacle(), state.isHandlingPyramidTurnIn()); + } + + // Check if we should turn in pyramids (either inventory full OR reached random threshold) AND we're on ground level + int pyramidCount = Rs2Inventory.count(ItemID.PYRAMID_TOP); + boolean shouldTurnIn = (Rs2Inventory.isFull() || pyramidCount >= state.getPyramidTurnInThreshold()) && playerPos.getPlane() == 0; + + if (shouldTurnIn) { + if (pyramidCount > 0) { + // We have pyramid tops - handle turn-in + if (!state.isHandlingPyramidTurnIn()) { + if (log.isDebugEnabled()) { + if (Rs2Inventory.isFull()) { + log.debug("Inventory is full with {} pyramid tops - going to Simon Templeton", pyramidCount); + } else { + log.debug("Reached threshold of {} pyramids (have {}) - going to Simon Templeton", + state.getPyramidTurnInThreshold(), pyramidCount); + } + } + state.startPyramidTurnIn(); + } + + // Handle pyramid turn-in + if (handlePyramidTurnIn()) { + return null; // Return null to prevent obstacle interaction + } + } else if (Rs2Inventory.isFull()) { + // Inventory is full but no pyramid tops - stop and warn + Microbot.showMessage("Inventory is full but no pyramid tops found! Clear inventory to continue."); + log.warn("Inventory full without pyramid tops - stopping"); + return null; + } + } else if (!Rs2Inventory.isFull() && pyramidCount < state.getPyramidTurnInThreshold() && state.isHandlingPyramidTurnIn()) { + // Only clear the turn-in flag if we were actively handling turn-in but pyramid count dropped + // This preserves the threshold until we actually complete a pyramid + state.clearPyramidTurnIn(); + } + + // NEVER return an obstacle while moving or animating + if (Rs2Player.isMoving() || Rs2Player.isAnimating()) { + log.debug("Player is moving/animating, returning null to prevent clicking"); + return null; + } + + // Check for empty waterskins and drop them + if (handleEmptyWaterskins()) { + return null; // Return null to prevent obstacle interaction this cycle + } + + // Special blocking for Cross Gap obstacles - don't return any obstacle while doing Cross Gap + if (state.isDoingCrossGap()) { + log.debug("Cross Gap flag is SET - blocking all obstacle selection"); + return null; + } + + // Block all obstacles while doing any XP-granting obstacle (plank, gap, ledge, etc) + if (state.isDoingXpObstacle()) { + log.debug("Currently doing XP-granting obstacle, blocking all other obstacles until XP received"); + return null; + } + + // Double-check movement after a brief moment - animations can have pauses + Global.sleep(35, 65); // Brief jittered delay + + // Recheck after the brief pause + if (Rs2Player.isMoving() || Rs2Player.isAnimating()) { + log.debug("Player started moving/animating after brief pause, returning null"); + return null; + } + + // Prevent getting obstacles too quickly after starting one + if (state.isObstacleCooldownActive()) { + log.debug("Obstacle cooldown active, returning null to prevent spam clicking"); + return null; + } + + // Find the obstacle area containing the player + ObstacleArea currentArea = null; + + // Debug: log areas being checked for current plane + if (log.isDebugEnabled()) { + log.debug("Checking areas for plane {} player position {}:", playerPos.getPlane(), playerPos); + for (ObstacleArea area : OBSTACLE_AREAS) { + if (area.plane == playerPos.getPlane()) { + boolean contains = area.containsPlayer(playerPos); + log.debug(" - Area: {} at ({},{}) to ({},{}) - contains player: {}", + area.name, area.minX, area.minY, area.maxX, area.maxY, contains); + if (contains) { + log.debug(" -> Obstacle ID: {} at location: {}", area.obstacleId, area.obstacleLocation); + } + } + } + } + + for (ObstacleArea area : OBSTACLE_AREAS) { + if (area.containsPlayer(playerPos)) { + // Special check for climbing rocks - skip if we've recently clicked them + if (area.obstacleId == 10851 && area.name.contains("grab pyramid")) { + if (state.isClimbingRocksCooldownActive()) { + log.debug("Recently clicked climbing rocks, skipping to next area"); + continue; + } + } + + currentArea = area; + if (log.isDebugEnabled()) { + log.debug("Found player in area: {} (obstacle ID: {})", area.name, area.obstacleId); + // Debug: log if this is a plank area + if (area.obstacleId == 10868) { + log.debug(" Player in PLANK area - should look for plank end ground object"); + } + } + break; + } + } + + if (currentArea == null) { + if (log.isDebugEnabled()) { + log.debug("Player not in any defined obstacle area at {} (plane: {})", playerPos, playerPos.getPlane()); + } + + // Special check for floor 4 start position + if (playerPos.getPlane() == 2 && playerPos.getX() == 3041 && playerPos.getY() == 4695) { + if (log.isDebugEnabled()) { + log.debug("SPECIAL CASE: Player at floor 4 start position (3041, 4695)"); + } + // Manually find the gap + TileObject gap = findNearestObstacleWithinDistance(playerPos, 10859, 5); + if (gap != null) { + if (log.isDebugEnabled()) { + log.debug("Found Gap manually at {}", gap.getWorldLocation()); + } + return gap; + } + } + + // Log all areas on current plane for debugging + if (log.isDebugEnabled()) { + log.debug("Available areas on plane {}:", playerPos.getPlane()); + int count = 0; + for (ObstacleArea area : OBSTACLE_AREAS) { + if (area.plane == playerPos.getPlane()) { + log.debug(" - {} at ({},{}) to ({},{})", + area.name, area.minX, area.minY, area.maxX, area.maxY); + count++; + if (count > 10) { + log.debug(" ... and more areas"); + break; + } + } + } + } + + // Special case: If player just climbed to floor 1, direct them to low wall + if (playerPos.getPlane() == 1 && playerPos.getX() >= 3354 && playerPos.getX() <= 3355 && playerPos.getY() == 2833) { + log.debug("Player just arrived on floor 1, looking for low wall"); + // Find the low wall obstacle + TileObject lowWall = findNearestObstacle(playerPos, 10865); + if (lowWall != null) { + return lowWall; + } + } + + // Try to find the nearest obstacle on the current plane + log.debug("Looking for nearest pyramid obstacle..."); + return findNearestPyramidObstacle(playerPos); + } + + if (log.isDebugEnabled()) { + log.debug("Player in area for: {} at {} (plane: {})", currentArea.name, playerPos, playerPos.getPlane()); + } + + // Find the specific obstacle instance + TileObject obstacle = null; + + // For gaps and ledges, always find the nearest one since there can be multiple + // Also for floor 4, always use nearest search since obstacles can be multi-tile + if (currentArea.obstacleId == 10859 || currentArea.obstacleId == 10861 || currentArea.obstacleId == 10884 || currentArea.obstacleId == 10860 || playerPos.getPlane() == 2) { + if (log.isDebugEnabled()) { + log.debug("Looking for nearest {}", currentArea.name); + } + + // Use strict sequential checking to prevent skipping ahead + obstacle = findNearestObstacleStrict(playerPos, currentArea.obstacleId, currentArea); + } else { + obstacle = findObstacleAt(currentArea.obstacleLocation, currentArea.obstacleId); + + if (obstacle == null) { + if (log.isDebugEnabled()) { + log.debug("Could not find {} (ID: {}) at expected location {}", + currentArea.name, currentArea.obstacleId, currentArea.obstacleLocation); + } + // Try to find any instance of this obstacle type nearby with strict checking + obstacle = findNearestObstacleStrict(playerPos, currentArea.obstacleId, currentArea); + } + } + + if (obstacle != null) { + if (log.isDebugEnabled()) { + log.debug("Selected obstacle: {} (ID: {}) at {} for player at {}", + currentArea.name, currentArea.obstacleId, obstacle.getWorldLocation(), playerPos); + } + + // Track long-animation gap obstacles specifically + // These gaps have long animations that move the player >3 tiles + if (currentArea.name.contains("Gap") || + currentArea.obstacleId == 10882) { // Gap (floor 1) also has long animation + // Cross gap time is tracked in startCrossGap + state.startCrossGap(); // Set flag that we're doing Cross Gap-type obstacle + if (log.isDebugEnabled()) { + log.debug("Detected long-animation gap obstacle (ID: {}) - setting flag to block all other obstacles", + currentArea.obstacleId); + } + } + + // Track any XP-granting obstacle (gaps, planks, ledges, low walls) + // These give XP: Low wall (8), Ledge (52), Gap/Plank (56.4) + // These don't give XP: Stairs (0), Doorway (0), Climbing rocks (0) + if (currentArea.obstacleId == 10865 || // Low wall + currentArea.obstacleId == 10860 || // Ledge + currentArea.obstacleId == 10868 || // Plank + currentArea.obstacleId == 10859 || // Gap + currentArea.obstacleId == 10861 || // Gap + currentArea.obstacleId == 10882 || // Gap + currentArea.obstacleId == 10884 || // Gap Cross + currentArea.obstacleId == 10886 || // Ledge + currentArea.obstacleId == 10888) { // Ledge + state.startXpObstacle(); + log.debug("Starting XP-granting obstacle - blocking all clicks until XP received"); + } + } else { + log.error("Could not find any obstacle for area: {} (ID: {})", currentArea.name, currentArea.obstacleId); + } + + // Special handling for pyramid top region - if completed, look for stairs down + if (obstacle == null && playerPos.getRegionID() == PYRAMID_TOP_REGION && playerPos.getPlane() == 3) { + TileObject stairs = Rs2GameObject.getTileObject(10857); + if (stairs != null) { + log.debug("No obstacle found on pyramid top, found stairs to go back down"); + return stairs; + } + } + + return obstacle; + } + + private TileObject findObstacleAt(WorldPoint location, int obstacleId) { + if (log.isDebugEnabled()) { + log.debug("findObstacleAt: Looking for obstacle {} at {}", obstacleId, location); + } + + // Special handling for plank end which is a ground object + if (obstacleId == 10868) { + List groundObjects = Rs2GameObject.getGroundObjects(); + if (log.isDebugEnabled()) { + log.debug("Looking for plank end at {}, checking {} ground objects", location, groundObjects.size()); + } + for (GroundObject go : groundObjects) { + if (go.getId() == obstacleId && go.getWorldLocation().equals(location)) { + if (log.isDebugEnabled()) { + log.debug("Found plank end (ground object) at {}", go.getWorldLocation()); + } + return go; + } + } + if (log.isDebugEnabled()) { + log.debug("No plank end found at expected location {}", location); + // List all plank ends found + for (GroundObject go : groundObjects) { + if (go.getId() == obstacleId) { + log.debug(" Found plank end at {} (not at expected location)", go.getWorldLocation()); + } + } + } + return null; + } + + // Normal game objects + List obstacles = Rs2GameObject.getAll(obj -> + obj.getId() == obstacleId && + obj.getWorldLocation().equals(location) + ); + + if (log.isDebugEnabled()) { + log.debug("Found {} obstacles with ID {} at {}", obstacles.size(), obstacleId, location); + } + + if (obstacles.isEmpty()) { + if (log.isDebugEnabled()) { + // Log all obstacles of this type on the current plane + List allObstaclesOfType = Rs2GameObject.getAll(obj -> + obj.getId() == obstacleId && + obj.getPlane() == location.getPlane() + ); + log.debug("No obstacle found at exact location. Found {} obstacles with ID {} on plane {}:", + allObstaclesOfType.size(), obstacleId, location.getPlane()); + for (TileObject obj : allObstaclesOfType) { + log.debug(" - {} at {}", obstacleId, obj.getWorldLocation()); + } + } + return null; + } + + return obstacles.get(0); + } + + private TileObject findNearestObstacleStrict(WorldPoint playerPos, int obstacleId, ObstacleArea currentArea) { + if (log.isDebugEnabled()) { + log.debug("Looking for obstacle {} with strict sequential checking", obstacleId); + } + + // Special handling for floor 4 gaps FIRST - need to select the correct one + // Check if we're on floor 4 (plane 2) and looking for a gap, regardless of exact area name + if (playerPos.getPlane() == 2 && obstacleId == 10859) { + // If player is after low wall at (3043, 4701-4702), we need the second gap + if (playerPos.getX() == 3043 && playerPos.getY() >= 4701) { + log.debug("Player after low wall on floor 4, looking for second gap at (3048, 4695)"); + // Find the gap at (3048, 4695) specifically + List gaps = Rs2GameObject.getAll(obj -> + obj.getId() == obstacleId && + obj.getPlane() == playerPos.getPlane() && + obj.getWorldLocation().getX() >= 3047 && obj.getWorldLocation().getX() <= 3049 && + obj.getWorldLocation().getY() >= 4694 && obj.getWorldLocation().getY() <= 4696 + ); + + if (!gaps.isEmpty()) { + TileObject secondGap = gaps.get(0); + if (log.isDebugEnabled()) { + log.debug("Found second gap at {}", secondGap.getWorldLocation()); + } + return secondGap; + } else { + log.debug("Could not find second gap on floor 4!"); + } + } + // If player is at start of floor 4, we need the first gap + else if (playerPos.getX() >= 3040 && playerPos.getX() <= 3042 && + playerPos.getY() >= 4695 && playerPos.getY() <= 4697) { + log.debug("Player at start of floor 4, looking for first gap"); + // Find the gap at (3040, 4697) specifically + List gaps = Rs2GameObject.getAll(obj -> + obj.getId() == obstacleId && + obj.getPlane() == playerPos.getPlane() && + obj.getWorldLocation().getX() >= 3039 && obj.getWorldLocation().getX() <= 3041 && + obj.getWorldLocation().getY() >= 4696 && obj.getWorldLocation().getY() <= 4698 + ); + + if (!gaps.isEmpty()) { + TileObject firstGap = gaps.get(0); + if (log.isDebugEnabled()) { + log.debug("Found first gap at {}", firstGap.getWorldLocation()); + } + return firstGap; + } + } + } + + // Special handling for floor 2 gaps to prevent skipping ahead + if (playerPos.getPlane() == 2 && (obstacleId == 10859 || obstacleId == 10861 || obstacleId == 10884) && !currentArea.name.contains("floor 4")) { + // Only search in a very limited area based on the current area definition + List obstacles = Rs2GameObject.getAll(obj -> { + if (obj.getId() != obstacleId || obj.getPlane() != playerPos.getPlane()) { + return false; + } + + WorldPoint objLoc = obj.getWorldLocation(); + + // For floor 2 gaps, use very strict position checking + if (currentArea.name.contains("Gap Cross 1")) { + // First gap should be around (3356, 2835) + return objLoc.getX() == 3356 && objLoc.getY() >= 2835 && objLoc.getY() <= 2837; + } else if (currentArea.name.contains("Gap Jump")) { + // Gap jump should be around (3356, 2841) + return objLoc.getX() == 3356 && objLoc.getY() >= 2838 && objLoc.getY() <= 2844; + } else if (currentArea.name.contains("Gap Cross 2")) { + // Gap cross 2 should be around (3356, 2849) + return objLoc.getX() >= 3356 && objLoc.getX() <= 3360 && objLoc.getY() >= 2848 && objLoc.getY() <= 2850; + } else if (currentArea.name.contains("Gap jump") && currentArea.name.contains("end")) { + // End gap jump should be around (3365, 2833) + return objLoc.getX() >= 3363 && objLoc.getX() <= 3367 && objLoc.getY() >= 2833 && objLoc.getY() <= 2834; + } + + // Default: must be within 8 tiles + return objLoc.distanceTo(playerPos) <= 8; + }); + + if (!obstacles.isEmpty()) { + TileObject nearest = obstacles.stream() + .min((a, b) -> Integer.compare( + a.getWorldLocation().distanceTo(playerPos), + b.getWorldLocation().distanceTo(playerPos) + )) + .orElse(null); + + if (nearest != null) { + if (log.isDebugEnabled()) { + log.debug("Found strictly checked obstacle at {}", nearest.getWorldLocation()); + } + return nearest; + } + } + } + + // For floor 3 gaps, use longer distance + if (playerPos.getPlane() == 3 && obstacleId == 10859) { + return findNearestObstacleWithinDistance(playerPos, obstacleId, 20); + } + + // For other obstacles, use normal nearest search but with distance limit + return findNearestObstacleWithinDistance(playerPos, obstacleId, 10); + } + + private TileObject findNearestObstacleWithinDistance(WorldPoint playerPos, int obstacleId, int maxDistance) { + if (log.isDebugEnabled()) { + log.debug("Looking for obstacle {} within {} tiles", obstacleId, maxDistance); + } + + List obstacles = Rs2GameObject.getAll(obj -> + obj.getId() == obstacleId && + obj.getPlane() == playerPos.getPlane() && + obj.getWorldLocation().distanceTo(playerPos) <= maxDistance + ); + + if (obstacles.isEmpty()) { + if (log.isDebugEnabled()) { + log.debug("No obstacles found within {} tiles", maxDistance); + } + return null; + } + + // Log all found obstacles for debugging + if (log.isDebugEnabled()) { + log.debug("Found {} obstacles within {} tiles:", obstacles.size(), maxDistance); + for (TileObject obj : obstacles) { + log.debug(" - {} at {} (distance: {})", + obstacleId, obj.getWorldLocation(), obj.getWorldLocation().distanceTo(playerPos)); + } + } + + return obstacles.stream() + .min((a, b) -> Integer.compare( + a.getWorldLocation().distanceTo(playerPos), + b.getWorldLocation().distanceTo(playerPos) + )) + .orElse(null); + } + + private TileObject findNearestObstacle(WorldPoint playerPos, int obstacleId) { + // Special case for Ledge on floor 2 - different ledges based on position + if (obstacleId == 10860 && playerPos.getPlane() == 2) { + if (log.isDebugEnabled()) { + log.debug("Special handling for floor 2 Ledge at player position {}", playerPos); + } + + // If player is anywhere in the path from Gap 10861 to Ledge, use east ledge + if ((playerPos.getX() >= 3372 && playerPos.getX() <= 3373 && playerPos.getY() >= 2841 && playerPos.getY() <= 2850) || + (playerPos.getX() >= 3364 && playerPos.getX() <= 3373 && playerPos.getY() >= 2849 && playerPos.getY() <= 2850)) { + log.debug("Player in path from Gap 10861 to Ledge, looking for east Ledge at (3372, 2839)"); + + // Find the specific ledge at (3372, 2839) + TileObject eastLedge = findObstacleAt(new WorldPoint(3372, 2839, 2), obstacleId); + if (eastLedge != null) { + if (log.isDebugEnabled()) { + log.debug("Found east Ledge at {}", eastLedge.getWorldLocation()); + } + return eastLedge; + } else { + log.debug("Could not find east Ledge at expected location (3372, 2839)"); + // Try to find any ledge on east side as fallback + List eastLedges = Rs2GameObject.getAll(obj -> + obj.getId() == obstacleId && + obj.getPlane() == playerPos.getPlane() && + obj.getWorldLocation().getX() >= 3372 && obj.getWorldLocation().getX() <= 3373 && + obj.getWorldLocation().getY() >= 2837 && obj.getWorldLocation().getY() <= 2841 + ); + if (!eastLedges.isEmpty()) { + return eastLedges.get(0); + } + } + } + + // Default behavior - look for middle ledge + List obstacles = Rs2GameObject.getAll(obj -> + obj.getId() == obstacleId && + obj.getPlane() == playerPos.getPlane() && + obj.getWorldLocation().getX() < 3370 && // Exclude east side ledges + obj.getWorldLocation().getY() >= 2840 && obj.getWorldLocation().getY() <= 2851 && // Middle Y range + obj.getWorldLocation().distanceTo(playerPos) <= 20 + ); + + // Log all ledges found for debugging + if (log.isDebugEnabled()) { + log.debug("Found {} potential ledges on floor 2:", obstacles.size()); + for (TileObject obj : obstacles) { + log.debug(" - Ledge at {}", obj.getWorldLocation()); + } + } + + // Find the ledge closest to the expected position (3364, 2841) + WorldPoint expectedLedgePos = new WorldPoint(3364, 2841, 2); + TileObject bestLedge = obstacles.stream() + .min((a, b) -> Integer.compare( + a.getWorldLocation().distanceTo(expectedLedgePos), + b.getWorldLocation().distanceTo(expectedLedgePos) + )) + .orElse(null); + + if (bestLedge != null) { + if (log.isDebugEnabled()) { + log.debug("Selected ledge at {} (closest to expected position {})", + bestLedge.getWorldLocation(), expectedLedgePos); + } + return bestLedge; + } else { + log.warn("No suitable ledge found on floor 2!"); + return null; + } + } + // Special handling for plank end which is a ground object + if (obstacleId == 10868) { + List groundObjects = Rs2GameObject.getGroundObjects(); + List nearbyPlanks = new ArrayList<>(); + + for (GroundObject go : groundObjects) { + if (go.getId() == obstacleId && + go.getPlane() == playerPos.getPlane() && + go.getWorldLocation().distanceTo(playerPos) <= 15) { + nearbyPlanks.add(go); + } + } + + if (nearbyPlanks.isEmpty()) { + log.debug("No plank ends (ground objects) found nearby"); + return null; + } + + if (log.isDebugEnabled()) { + log.debug("Found {} plank ends nearby", nearbyPlanks.size()); + for (GroundObject go : nearbyPlanks) { + log.debug(" - Plank end at {} (distance: {})", + go.getWorldLocation(), go.getWorldLocation().distanceTo(playerPos)); + } + } + + // Return closest plank end + return nearbyPlanks.stream() + .min((a, b) -> Integer.compare( + a.getWorldLocation().distanceTo(playerPos), + b.getWorldLocation().distanceTo(playerPos) + )) + .orElse(null); + } + + // Normal game objects + List obstacles = Rs2GameObject.getAll(obj -> + obj.getId() == obstacleId && + obj.getPlane() == playerPos.getPlane() && + obj.getWorldLocation().distanceTo(playerPos) <= 15 + ); + + if (obstacles.isEmpty()) { + return null; + } + + // Log all found obstacles for debugging + if (log.isDebugEnabled()) { + log.debug("Found {} obstacles with ID {} on plane {}:", obstacles.size(), obstacleId, playerPos.getPlane()); + for (TileObject obj : obstacles) { + log.debug(" - {} at {} (distance: {})", + obstacleId, obj.getWorldLocation(), obj.getWorldLocation().distanceTo(playerPos)); + } + } + + // For stairs on floor 1, we need to filter out the wrong stairs + if (obstacleId == 10857 && playerPos.getPlane() == 1) { + // If player just climbed up and is at start position (3354-3355, 2833), we should NOT return any stairs + // The player should go to the low wall instead + if (playerPos.getX() >= 3354 && playerPos.getX() <= 3355 && playerPos.getY() >= 2833 && playerPos.getY() <= 2835) { + log.debug("Player just climbed to floor 1, should not interact with stairs yet"); + return null; + } + + // Filter out stairs that are at the wrong location + // The correct stairs to floor 2 are at (3356, 2831) + obstacles = obstacles.stream() + .filter(obj -> { + WorldPoint loc = obj.getWorldLocation(); + // Only consider stairs in the southwest area of floor 1 + return loc.getX() >= 3356 && loc.getX() <= 3360 && + loc.getY() >= 2831 && loc.getY() <= 2833; + }) + .collect(Collectors.toList()); + + if (obstacles.isEmpty()) { + log.debug("No appropriate stairs found for progression"); + return null; + } + } + + // For low wall on floor 1, make sure we get the north end + if (obstacleId == 10865 && playerPos.getPlane() == 1 && + playerPos.getX() == 3354 && playerPos.getY() <= 2840) { + // Sort by Y coordinate descending to get northernmost wall + obstacles.sort((a, b) -> Integer.compare( + b.getWorldLocation().getY(), + a.getWorldLocation().getY() + )); + + // Return the northernmost low wall + if (!obstacles.isEmpty()) { + TileObject northWall = obstacles.get(0); + if (log.isDebugEnabled()) { + log.debug("Selected northernmost low wall at {}", northWall.getWorldLocation()); + } + return northWall; + } + } + + // Return closest reachable obstacle + return obstacles.stream() + .filter(this::isObstacleReachable) + .min((a, b) -> Integer.compare( + a.getWorldLocation().distanceTo(playerPos), + b.getWorldLocation().distanceTo(playerPos) + )) + .orElse(obstacles.get(0)); + } + + private TileObject findNearestPyramidObstacle(WorldPoint playerPos) { + List pyramidObstacleIds = Arrays.asList( + 10857, 10865, 10860, 10867, 10868, 10859, 10882, 10886, 10884, 10861, 10888, 10851, 10855 + ); + + // Special handling for floor 1 start position + if (playerPos.getPlane() == 1 && playerPos.getX() >= 3354 && playerPos.getX() <= 3355 && playerPos.getY() >= 2833 && playerPos.getY() <= 2835) { + // Player just climbed to floor 1, exclude stairs from search + pyramidObstacleIds = Arrays.asList( + 10865, 10860, 10867, 10868, 10859, 10882, 10886, 10884, 10861, 10888, 10851, 10855 + ); + log.debug("Excluding stairs from search at floor 1 start position"); + } + + List finalObstacleIds = pyramidObstacleIds; + + // First check for ground objects (plank ends) + List groundObjects = Rs2GameObject.getGroundObjects(); + for (GroundObject go : groundObjects) { + if (go.getId() == 10868 && + go.getPlane() == playerPos.getPlane() && + go.getWorldLocation().distanceTo(playerPos) <= 15) { + if (log.isDebugEnabled()) { + log.debug("Found nearby plank end (ground object) at {}", go.getWorldLocation()); + } + return go; + } + } + + // Use longer search distance for floor 3 + int searchDistance = (playerPos.getPlane() == 3) ? 25 : 15; + + // Then check normal game objects + List nearbyObstacles = Rs2GameObject.getAll(obj -> + finalObstacleIds.contains(obj.getId()) && + obj.getPlane() == playerPos.getPlane() && + obj.getWorldLocation().distanceTo(playerPos) <= searchDistance + ); + + if (nearbyObstacles.isEmpty()) { + if (log.isDebugEnabled()) { + log.debug("No pyramid obstacles found within {} tiles on plane {}", searchDistance, playerPos.getPlane()); + } + // Try expanding search radius for floor 4 (pyramid top area) + if (playerPos.getPlane() == 2 && playerPos.getX() >= 3040 && playerPos.getX() <= 3050) { + if (log.isDebugEnabled()) { + log.debug("Expanding search for floor 4 pyramid top area..."); + } + nearbyObstacles = Rs2GameObject.getAll(obj -> + finalObstacleIds.contains(obj.getId()) && + obj.getPlane() == playerPos.getPlane() + ); + } + } + + if (log.isDebugEnabled()) { + log.debug("Found {} pyramid obstacles nearby:", nearbyObstacles.size()); + for (TileObject obj : nearbyObstacles) { + log.debug(" - ID {} at {} (distance: {})", + obj.getId(), obj.getWorldLocation(), obj.getWorldLocation().distanceTo(playerPos)); + } + } + + return nearbyObstacles.stream() + .filter(obj -> isObstacleReachable(obj)) + .min((a, b) -> Integer.compare( + a.getWorldLocation().distanceTo(playerPos), + b.getWorldLocation().distanceTo(playerPos) + )) + .orElse(null); + } + + private boolean isObstacleReachable(TileObject obstacle) { + if (obstacle instanceof GameObject) { + GameObject go = (GameObject) obstacle; + return Rs2GameObject.canReach(go.getWorldLocation(), go.sizeX() + 2, go.sizeY() + 2, 4, 4); + } else if (obstacle instanceof GroundObject) { + return Rs2GameObject.canReach(obstacle.getWorldLocation(), 2, 2); + } else if (obstacle instanceof WallObject) { + return Rs2GameObject.canReach(obstacle.getWorldLocation(), 1, 1); + } else { + return Rs2GameObject.canReach(obstacle.getWorldLocation(), 2, 2); + } + } + + @Override + public boolean handleWalkToStart(WorldPoint playerLocation) { + // Only walk to start if on ground level + if (playerLocation.getPlane() == 0) { + // Check if we should handle pyramid turn-in instead of walking to start + int pyramidCount = Rs2Inventory.count(ItemID.PYRAMID_TOP); + boolean shouldTurnIn = pyramidCount > 0 && (Rs2Inventory.isFull() || pyramidCount >= state.getPyramidTurnInThreshold()); + + if (shouldTurnIn) { + if (!state.isHandlingPyramidTurnIn()) { + if (log.isDebugEnabled()) { + if (Rs2Inventory.isFull()) { + log.debug("Inventory is full with {} pyramid tops - going to Simon instead of pyramid start", pyramidCount); + } else { + log.debug("Reached threshold of {} pyramids (have {}) - going to Simon instead of pyramid start", + state.getPyramidTurnInThreshold(), pyramidCount); + } + } + state.startPyramidTurnIn(); + } + // Handle turn-in instead of walking to start + handlePyramidTurnIn(); + return true; // Return true to prevent other actions + } + + int distanceToStart = playerLocation.distanceTo(START_POINT); + if (distanceToStart > 3) { + // Try to directly click on the pyramid stairs if visible AND reachable + List stairsCandidates = Rs2GameObject.getAll(obj -> + obj.getId() == 10857 && + obj.getPlane() == playerLocation.getPlane() && + obj.getWorldLocation().distanceTo(playerLocation) <= 10 && + obj.getWorldLocation().distanceTo(START_POINT) <= 2 && + Rs2GameObject.canReach(obj.getWorldLocation()) + ); + if (!stairsCandidates.isEmpty()) { + TileObject pyramidStairs = stairsCandidates.stream() + .min(Comparator.comparingInt(obj -> obj.getWorldLocation().distanceTo(playerLocation))) + .orElse(null); + if (pyramidStairs != null) { + log.debug("Clicking directly on pyramid stairs (reachable from current position)"); + if (Rs2GameObject.interact(pyramidStairs)) { + Global.sleep(600, 800); // Small delay after clicking + return true; + } + } + } + + // Can't reach stairs directly (e.g., coming from Simon with climbing rocks in the way) + // Use Rs2Walker to navigate around obstacles + if (log.isDebugEnabled()) { + log.debug("Walking to pyramid start point - stairs not reachable directly (distance: {})", distanceToStart); + } + Rs2Walker.walkTo(START_POINT, 2); + return true; + } + } + return false; + } + + @Override + public boolean waitForCompletion(int agilityExp, int plane) { + // Mark that we've started an obstacle + state.recordObstacleStart(); + + // Note: The flags state.isDoingCrossGap() and state.isDoingXpObstacle() + // are set by getCurrentObstacle() and should remain set during this wait + + // Simplified wait logic using XP drops as primary signal + double initialHealth = Rs2Player.getHealthPercentage(); + int timeoutMs = 8000; // 8 second timeout + final long startTime = System.currentTimeMillis(); + + // Track XP gains + int lastKnownXp = agilityExp; + boolean receivedXp = false; + boolean hitByStoneBlock = false; + + // Track starting position + WorldPoint startPos = Rs2Player.getWorldLocation(); + + // Check if we're at the climbing rocks position (pyramid collection) + boolean isClimbingRocksForPyramid = startPos.getPlane() == 3 && + startPos.getX() >= 3042 && startPos.getX() <= 3043 && + startPos.getY() >= 4697 && startPos.getY() <= 4698; + + if (log.isDebugEnabled()) { + log.debug("Starting obstacle at {}, initial XP: {}", startPos, agilityExp); + log.debug("Flags: CrossGap={}, XpObstacle={}", state.isDoingCrossGap(), state.isDoingXpObstacle()); + } + + while (System.currentTimeMillis() - startTime < timeoutMs) { + int currentXp = Microbot.getClient().getSkillExperience(Skill.AGILITY); + int currentPlane = Microbot.getClient().getTopLevelWorldView() != null + ? Microbot.getClient().getTopLevelWorldView().getPlane() + : Rs2Player.getWorldLocation().getPlane(); + double currentHealth = Rs2Player.getHealthPercentage(); + WorldPoint currentPos = Rs2Player.getWorldLocation(); + + // Special case: Climbing rocks for pyramid collection (no XP) + if (isClimbingRocksForPyramid) { + if (!Rs2Player.isMoving() && !Rs2Player.isAnimating() && System.currentTimeMillis() - startTime > 1500) { + log.debug("Climbing rocks action completed"); + state.recordClimbingRocks(); + // Clear any flags that might have been set + if (state.isDoingXpObstacle()) { + log.debug("WARNING: Clearing XP obstacle flag from climbing rocks path"); + state.clearXpObstacle(); + } + if (state.isDoingCrossGap()) { + state.clearCrossGap(); + } + Global.sleep(300, 400); + return true; + } + Global.sleep(50); + continue; + } + + // Check for XP gain + if (currentXp != lastKnownXp) { + int xpGained = currentXp - lastKnownXp; + + // Check if this is a stone block (12 XP) + if (xpGained == 12) { + log.debug("Hit by stone block (12 XP) - clearing flags to allow immediate retry"); + hitByStoneBlock = true; + lastKnownXp = currentXp; + + // Clear flags to allow immediate retry of the obstacle + if (state.isDoingCrossGap()) { + state.clearCrossGap(); + } + if (state.isDoingXpObstacle()) { + state.clearXpObstacle(); + } + + // Return immediately to retry the obstacle + Global.sleep(300, 400); // Small delay before retry + return true; + } + + // Any other XP gain means obstacle is complete (for XP-granting obstacles) + if (log.isDebugEnabled()) { + log.debug("Received {} XP - obstacle complete!", xpGained); + } + receivedXp = true; + lastKnownXp = currentXp; + + // Check if this was a Cross Gap obstacle + boolean wasCrossGap = state.isDoingCrossGap(); + + // For Cross Gap, ensure minimum time has passed even with XP + if (wasCrossGap && System.currentTimeMillis() - startTime < 3500) { + long waitTime = 3500 - (System.currentTimeMillis() - startTime); + if (log.isDebugEnabled()) { + log.debug("Cross Gap - waiting additional {}ms for minimum duration", waitTime); + } + Global.sleep((int)waitTime); + } + + // Clear flags since we received XP + if (state.isDoingCrossGap()) { + log.debug("Cross Gap completed with XP - clearing flag"); + state.clearCrossGap(); + } + if (state.isDoingXpObstacle()) { + log.debug("XP obstacle completed - clearing flag"); + state.clearXpObstacle(); + } + + // Add delay to ensure animation finishes + // Cross Gap needs longer delay even after XP + if (wasCrossGap) { + log.debug("Cross Gap - waiting longer for animation to fully complete"); + Global.sleep(800, 1000); + } else { + Global.sleep(200, 300); + } + return true; + } + + // Quick checks for other completion conditions + + // Plane change (stairs/doorway) + if (currentPlane != plane) { + log.debug("Plane changed - obstacle complete"); + // Clear flags when plane changes + if (state.isDoingCrossGap()) { + log.debug("Clearing Cross Gap flag due to plane change"); + state.clearCrossGap(); + } + if (state.isDoingXpObstacle()) { + log.debug("Clearing XP obstacle flag due to plane change"); + state.clearXpObstacle(); + } + Global.sleep(200, 300); + return true; + } + + // Health loss (failed obstacle) + if (currentHealth < initialHealth) { + log.debug("Failed obstacle (lost health)"); + // Clear flags if we failed + if (state.isDoingCrossGap()) { + state.clearCrossGap(); + } + if (state.isDoingXpObstacle()) { + state.clearXpObstacle(); + } + return true; + } + + // For non-XP obstacles (stairs, doorway), check if not moving/animating + // Only check after at least 1 second to allow obstacle to start + if (System.currentTimeMillis() - startTime > 1000) { + // If we haven't received XP and are not moving/animating, check if we moved + if (!receivedXp && !Rs2Player.isMoving() && !Rs2Player.isAnimating()) { + int distanceMoved = currentPos.distanceTo(startPos); + + // Special handling for Cross Gap - ALWAYS wait for XP or timeout, never complete on movement + if (state.isDoingCrossGap()) { + // Cross Gap must wait for XP drop or full timeout + // Never complete based on movement or animation state + continue; // Always continue waiting for Cross Gap + } + + // If we're expecting XP (flag is set), don't complete based on movement alone + if (state.isDoingXpObstacle()) { + + // For non-Cross-Gap XP obstacles, use normal logic + // Keep waiting for XP - don't complete based on movement + if (System.currentTimeMillis() - startTime < 4000) { + continue; // Keep waiting for XP + } + // After 4 seconds without XP, check if we at least moved + if (distanceMoved >= 3) { + if (log.isDebugEnabled()) { + log.debug("WARNING: Expected XP but didn't receive it after 4s - completing based on movement"); + log.debug("Cross Gap flag state before returning: {}", state.isDoingCrossGap()); + log.debug("XP obstacle flag state before returning: {}", state.isDoingXpObstacle()); + } + // Clear XP obstacle flag but NOT Cross Gap flag + // Cross Gap needs to wait for XP regardless of movement + state.clearXpObstacle(); + if (log.isDebugEnabled()) { + log.debug("After clearing XP flag - Cross Gap: {}, XP obstacle: {}", + state.isDoingCrossGap(), state.isDoingXpObstacle()); + } + return true; + } + } + + // For non-XP obstacles, movement indicates completion + if (distanceMoved >= 3 && !state.isDoingXpObstacle()) { + if (log.isDebugEnabled()) { + log.debug("Non-XP obstacle complete (moved {} tiles)", distanceMoved); + } + + // Note: We don't clear Cross Gap or XP obstacle flags here + // They should only be cleared by XP receipt or timeout + + Global.sleep(300, 400); + return true; + } + + // If we were hit by stone block and haven't received proper XP, retry + if (hitByStoneBlock && !receivedXp && System.currentTimeMillis() - startTime > 2000) { + log.debug("Stone block interrupted obstacle, no proper XP received - retrying"); + // Clear flags since we're going to retry + if (state.isDoingCrossGap()) { + log.debug("Clearing Cross Gap flag for retry"); + state.clearCrossGap(); + } + if (state.isDoingXpObstacle()) { + log.debug("Clearing XP obstacle flag for retry"); + state.clearXpObstacle(); + } + Global.sleep(800, 1200); + return false; // Retry the obstacle + } + } + } + + Global.sleep(50); + } + + // Timeout reached + if (log.isDebugEnabled()) { + log.debug("Timeout after {}ms - checking if made progress", timeoutMs); + } + int distanceMoved = Rs2Player.getWorldLocation().distanceTo(startPos); + + // Clear flags on timeout + if (state.isDoingCrossGap()) { + log.debug("Clearing Cross Gap flag due to timeout"); + state.clearCrossGap(); + } + if (state.isDoingXpObstacle()) { + log.debug("Clearing XP obstacle flag due to timeout"); + state.clearXpObstacle(); + } + + // If we received XP or moved significantly, consider it successful + if (receivedXp || distanceMoved >= 3) { + if (log.isDebugEnabled()) { + log.debug("Made progress despite timeout (XP: {}, moved: {} tiles)", receivedXp, distanceMoved); + } + return true; + } + + log.debug("No progress made - will retry"); + return false; + } + + @Override + public Integer getRequiredLevel() { + return 30; + } + + @Override + public boolean canBeBoosted() { + return true; + } + + @Override + public int getLootDistance() { + return 5; // Pyramid tops can be a bit further away + } + + private boolean handlePyramidTurnIn() { + try { + // Check if we still have pyramid tops + if (!Rs2Inventory.contains(ItemID.PYRAMID_TOP)) { + log.debug("No pyramid tops found in inventory - returning to course"); + state.clearPyramidTurnIn(); + return false; + } + + // Try to find Simon + NPC simon = Rs2Npc.getNpc(SIMON_NAME); + + // If Simon is found and reachable, use pyramid top on him + if (simon != null && Rs2GameObject.canReach(simon.getWorldLocation())) { + log.debug("Simon found and reachable, using pyramid top"); + + // Handle dialogue first if already in dialogue + if (Rs2Dialogue.isInDialogue()) { + // Continue through dialogue + if (Rs2Dialogue.hasContinue()) { + Rs2Dialogue.clickContinue(); + Global.sleep(600, 1000); + return true; + } + + // Select option to claim reward if available + if (Rs2Dialogue.hasDialogueOption("I've got some pyramid tops for you.")) { + Rs2Dialogue.clickOption("I've got some pyramid tops for you."); + Global.sleep(600, 1000); + return true; + } + } else { + // Not in dialogue, use pyramid top on Simon + boolean used = Rs2Inventory.useItemOnNpc(ItemID.PYRAMID_TOP, simon); + if (used) { + log.debug("Successfully used pyramid top on Simon"); + Global.sleepUntil(() -> Rs2Dialogue.isInDialogue(), 3000); + } else { + log.debug("Failed to use pyramid top on Simon"); + } + } + return true; + } + + // Simon not found or not reachable, walk to him + if (log.isDebugEnabled()) { + log.debug("Simon not found or not reachable, walking to location {}", SIMON_LOCATION); + } + Rs2Walker.walkTo(SIMON_LOCATION, 2); + Rs2Player.waitForWalking(); + + // Check if we've completed the turn-in (no pyramids left and not in dialogue) + if (!Rs2Inventory.contains(ItemID.PYRAMID_TOP) && !Rs2Dialogue.isInDialogue()) { + log.debug("Pyramid tops turned in successfully"); + state.clearPyramidTurnIn(); + + // Walk back towards the pyramid start + WorldPoint currentPos = Rs2Player.getWorldLocation(); + if (currentPos.distanceTo(START_POINT) > 10) { + log.debug("Walking back to pyramid start"); + Rs2Walker.walkTo(START_POINT); + } + return false; // Done with turn-in, can resume obstacles + } + + return true; + + } catch (Exception e) { + log.error("Error in handlePyramidTurnIn", e); + state.clearPyramidTurnIn(); + return false; + } + } + + /** + * Checks for empty waterskins in inventory and drops them + * @return true if waterskins were dropped, false otherwise + */ + private boolean handleEmptyWaterskins() { + if (Rs2Inventory.contains(ItemID.WATERSKIN0)) { + log.debug("Found empty waterskin(s), dropping them"); + Rs2Inventory.drop(ItemID.WATERSKIN0); + Global.sleep(300, 500); + return true; + } + return false; + } + +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/agility/courses/PyramidObstacleData.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/agility/courses/PyramidObstacleData.java new file mode 100644 index 00000000000..280072756cc --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/agility/courses/PyramidObstacleData.java @@ -0,0 +1,119 @@ +package net.runelite.client.plugins.microbot.agility.courses; + +import net.runelite.api.coords.WorldPoint; +import java.util.Arrays; +import java.util.List; + +/** + * Data class containing all obstacle area definitions for the Agility Pyramid course. + * Separates data from logic to improve maintainability. + */ +public class PyramidObstacleData { + + /** + * Represents a rectangular area where a specific obstacle can be interacted with + */ + public static class ObstacleArea { + public final int minX, minY, maxX, maxY, plane; + public final int obstacleId; + public final WorldPoint obstacleLocation; + public final String name; + + public ObstacleArea(int minX, int minY, int maxX, int maxY, int plane, int obstacleId, WorldPoint obstacleLocation, String name) { + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + this.plane = plane; + this.obstacleId = obstacleId; + this.obstacleLocation = obstacleLocation; + this.name = name; + } + + public boolean containsPlayer(WorldPoint playerPos) { + return playerPos.getPlane() == plane && + playerPos.getX() >= minX && playerPos.getX() <= maxX && + playerPos.getY() >= minY && playerPos.getY() <= maxY; + } + } + + // Compact obstacle area definitions using builder pattern for readability + public static final List OBSTACLE_AREAS = Arrays.asList( + // Floor 0 -> 1 + area(3354, 2830, 3354, 2830, 0, 10857, 3354, 2831, "Stairs (up)"), + + // Floor 1 - Clockwise path + area(3354, 2833, 3355, 2833, 1, 10865, 3354, 2849, "Low wall"), + area(3354, 2834, 3354, 2848, 1, 10865, 3354, 2849, "Low wall"), + area(3354, 2850, 3355, 2850, 1, 10860, 3364, 2851, "Ledge (east)"), + area(3354, 2851, 3363, 2852, 1, 10860, 3364, 2851, "Ledge (east)"), + area(3364, 2850, 3375, 2852, 1, 10868, 3368, 2845, "Plank (approach)"), + area(3374, 2845, 3375, 2849, 1, 10868, 3368, 2845, "Plank (east)"), + area(3368, 2834, 3375, 2844, 1, 10882, 3371, 2831, "Gap (floor 1)"), + area(3371, 2832, 3372, 2832, 1, 10886, 3362, 2831, "Ledge 3"), + area(3362, 2832, 3370, 2832, 1, 10886, 3362, 2831, "Ledge 3"), + area(3361, 2832, 3362, 2832, 1, 10857, 3356, 2831, "Stairs (floor 1 up)"), + area(3356, 2831, 3360, 2833, 1, 10857, 3356, 2831, "Stairs (floor 1 up)"), + + // Floor 2 - Three gaps in sequence + area(3356, 2835, 3357, 2837, 2, 10884, 3356, 2835, "Gap Cross 1 (floor 2)"), + area(3356, 2838, 3357, 2847, 2, 10859, 3356, 2841, "Gap Jump (floor 2)"), + area(3356, 2848, 3360, 2850, 2, 10861, 3356, 2849, "Gap Cross 2 (floor 2)"), + // Ledge after gaps + area(3372, 2841, 3373, 2850, 2, 10860, 3372, 2839, "Ledge (floor 2) after gap - east path"), + area(3364, 2849, 3373, 2850, 2, 10860, 3372, 2839, "Ledge (floor 2) after gap - south path"), + area(3367, 2849, 3367, 2850, 2, 10860, 3372, 2839, "Ledge (floor 2) at (3367, 2849-2850)"), + area(3359, 2850, 3360, 2850, 2, 10860, 3364, 2841, "Ledge (floor 2) after gap"), + area(3361, 2849, 3363, 2850, 2, 10860, 3364, 2841, "Ledge (floor 2) south approach"), + // Low wall areas + area(3370, 2834, 3373, 2840, 2, 10865, 3370, 2833, "Low wall (floor 2) after ledge"), + area(3372, 2835, 3373, 2839, 2, 10860, 3364, 2841, "Ledge (floor 2) from wrong position"), + area(3364, 2841, 3373, 2851, 2, 10865, 3370, 2833, "Low wall (floor 2)"), + area(3364, 2851, 3365, 2851, 2, 10865, 3370, 2833, "Low wall (floor 2) from ledge"), + area(3364, 2849, 3365, 2850, 2, 10865, 3370, 2833, "Low wall (floor 2) approach"), + area(3366, 2849, 3373, 2851, 2, 10865, 3370, 2833, "Low wall (floor 2) east"), + // End of floor 2 + area(3369, 2834, 3370, 2834, 2, 10859, 3365, 2833, "Gap jump (floor 2 end)"), + area(3363, 2834, 3365, 2834, 2, 10857, 3358, 2833, "Stairs (floor 2 up)"), + area(3358, 2833, 3362, 2834, 2, 10857, 3358, 2833, "Stairs (floor 2 up)"), + + // Floor 3 - Clockwise path + area(3358, 2837, 3359, 2838, 3, 10865, 3358, 2837, "Low wall (floor 3)"), + area(3358, 2840, 3359, 2842, 3, 10888, 3358, 2840, "Ledge 2"), + // Gap jump areas + area(3358, 2847, 3371, 2848, 3, 10859, 3358, 2843, "Gap jump area (floor 3) after ledge"), + area(3370, 2843, 3371, 2848, 3, 10859, 3358, 2843, "Gap jump area (floor 3) east"), + area(3358, 2843, 3362, 2846, 3, 10859, 3358, 2843, "Gap jump 1 (floor 3)"), + area(3363, 2843, 3367, 2846, 3, 10859, 3363, 2843, "Gap jump 2 (floor 3)"), + area(3368, 2843, 3369, 2846, 3, 10859, 3368, 2843, "Gap jump 3 (floor 3)"), + // Plank and stairs + area(3370, 2835, 3371, 2841, 3, 10868, 3370, 2835, "Plank (floor 3)"), + area(3369, 2840, 3371, 2842, 3, 10868, 3370, 2835, "Plank (floor 3) - gap landing"), + area(3360, 2835, 3369, 2836, 3, 10857, 3360, 2835, "Stairs (floor 3 up)"), + + // Floor 4 (uses special coordinate system, plane=2) + area(3040, 4695, 3041, 4696, 2, 10859, 3040, 4697, "Gap jump (floor 4 start)"), + area(3042, 4695, 3042, 4697, 2, 10859, 3040, 4695, "Gap jump (floor 4 start alt)"), + area(3040, 4698, 3042, 4702, 2, 10865, 3040, 4699, "Low wall (floor 4)"), + area(3041, 4697, 3042, 4697, 2, 10865, 3040, 4699, "Low wall (floor 4 alt)"), + area(3043, 4701, 3043, 4702, 2, 10859, 3048, 4695, "Gap jump (floor 4 second)"), + area(3043, 4695, 3049, 4700, 2, 10859, 3048, 4695, "Gap jump (floor 4 mid)"), + area(3047, 4693, 3049, 4696, 2, 10865, 3047, 4693, "Low wall (floor 4 end)"), + area(3048, 4695, 3049, 4696, 2, 10865, 3047, 4693, "Low wall (floor 4 end alt)"), + area(3042, 4693, 3047, 4695, 2, 10857, 3042, 4693, "Stairs (floor 4 up)"), + + // Floor 5 (pyramid top, plane=3) + area(3042, 4697, 3043, 4698, 3, 10851, 3042, 4697, "Climbing rocks (grab pyramid)"), + area(3042, 4697, 3043, 4698, 3, 10859, 3046, 4698, "Gap jump (floor 5) from pyramid spot"), + area(3044, 4697, 3047, 4700, 3, 10859, 3046, 4698, "Gap jump (floor 5)"), + area(3047, 4696, 3047, 4700, 3, 10855, 3044, 4695, "Doorway (floor 5)"), + area(3044, 4695, 3046, 4696, 3, 10855, 3044, 4695, "Doorway (floor 5 approach)") + ); + + // Helper method to create ObstacleArea with less verbosity + private static ObstacleArea area(int minX, int minY, int maxX, int maxY, int plane, + int obstacleId, int locX, int locY, String name) { + return new ObstacleArea(minX, minY, maxX, maxY, plane, obstacleId, + new WorldPoint(locX, locY, plane), name); + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/agility/courses/PyramidState.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/agility/courses/PyramidState.java new file mode 100644 index 00000000000..4a8710fa6c8 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/agility/courses/PyramidState.java @@ -0,0 +1,155 @@ +package net.runelite.client.plugins.microbot.agility.courses; + +import net.runelite.client.plugins.microbot.util.math.Rs2Random; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Encapsulates state tracking for the Agility Pyramid course. + * Centralizes all state management to avoid scattered static variables. + */ +public class PyramidState { + + // Timing and cooldown tracking + private volatile long lastObstacleStartTime = 0; + private volatile long lastClimbingRocksTime = 0; + + // State flags - using AtomicBoolean for thread safety + private final AtomicBoolean currentlyDoingCrossGap = new AtomicBoolean(false); + private final AtomicBoolean currentlyDoingXpObstacle = new AtomicBoolean(false); + private final AtomicBoolean handlingPyramidTurnIn = new AtomicBoolean(false); + + // Random turn-in threshold (4-6 pyramids) + private volatile int pyramidTurnInThreshold = generateNewThreshold(); + + // Cooldown constants (in nanoseconds for precise timing) + private static final long OBSTACLE_COOLDOWN = TimeUnit.MILLISECONDS.toNanos(1500); // 1.5 seconds between obstacles + private static final long CLIMBING_ROCKS_COOLDOWN = TimeUnit.MILLISECONDS.toNanos(30000); // 30 seconds - pyramid respawn time + + /** + * Records that an obstacle was just started + */ + public void recordObstacleStart() { + lastObstacleStartTime = System.nanoTime(); + } + + /** + * Checks if enough time has passed since last obstacle + */ + public boolean isObstacleCooldownActive() { + return System.nanoTime() - lastObstacleStartTime < OBSTACLE_COOLDOWN; + } + + /** + * Records that climbing rocks were clicked and generates new random threshold + */ + public void recordClimbingRocks() { + lastClimbingRocksTime = System.nanoTime(); + // Generate a new random threshold for the next pyramid run + pyramidTurnInThreshold = generateNewThreshold(); + } + + /** + * Checks if climbing rocks are on cooldown + */ + public boolean isClimbingRocksCooldownActive() { + return System.nanoTime() - lastClimbingRocksTime < CLIMBING_ROCKS_COOLDOWN; + } + + /** + * Sets the Cross Gap flag (for long-animation gap obstacles) + */ + public void startCrossGap() { + currentlyDoingCrossGap.set(true); + } + + /** + * Clears the Cross Gap flag + */ + public void clearCrossGap() { + currentlyDoingCrossGap.set(false); + } + + /** + * Checks if currently doing a Cross Gap obstacle + */ + public boolean isDoingCrossGap() { + return currentlyDoingCrossGap.get(); + } + + /** + * Sets the XP obstacle flag + */ + public void startXpObstacle() { + currentlyDoingXpObstacle.set(true); + } + + /** + * Clears the XP obstacle flag + */ + public void clearXpObstacle() { + currentlyDoingXpObstacle.set(false); + } + + /** + * Checks if currently doing an XP-granting obstacle + */ + public boolean isDoingXpObstacle() { + return currentlyDoingXpObstacle.get(); + } + + /** + * Sets the pyramid turn-in flag + */ + public void startPyramidTurnIn() { + handlingPyramidTurnIn.set(true); + } + + /** + * Clears the pyramid turn-in flag + */ + public void clearPyramidTurnIn() { + handlingPyramidTurnIn.set(false); + // Threshold is regenerated when grabbing the pyramid top (recordClimbingRocks), not after turn-in + } + + /** + * Checks if currently handling pyramid turn-in + */ + public boolean isHandlingPyramidTurnIn() { + return handlingPyramidTurnIn.get(); + } + + /** + * Gets the current pyramid turn-in threshold + */ + public int getPyramidTurnInThreshold() { + return pyramidTurnInThreshold; + } + + /** + * Package-private setter for unit testing purposes to avoid randomness in tests + */ + void setPyramidTurnInThresholdForTesting(int value) { + this.pyramidTurnInThreshold = value; + } + + /** + * Generates a new random threshold between 4 and 6 (inclusive) + */ + private int generateNewThreshold() { + return Rs2Random.betweenInclusive(4, 6); + } + + /** + * Resets all state flags (useful for plugin restart) + */ + public void reset() { + lastObstacleStartTime = 0; + lastClimbingRocksTime = 0; + currentlyDoingCrossGap.set(false); + currentlyDoingXpObstacle.set(false); + handlingPyramidTurnIn.set(false); + pyramidTurnInThreshold = generateNewThreshold(); + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/agility/enums/AgilityCourse.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/agility/enums/AgilityCourse.java index 2f565c041b0..abbaa8bb717 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/agility/enums/AgilityCourse.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/agility/enums/AgilityCourse.java @@ -13,6 +13,7 @@ import net.runelite.client.plugins.microbot.agility.courses.GnomeStrongholdCourse; import net.runelite.client.plugins.microbot.agility.courses.PollnivneachCourse; import net.runelite.client.plugins.microbot.agility.courses.PrifddinasCourse; +import net.runelite.client.plugins.microbot.agility.courses.PyramidCourse; import net.runelite.client.plugins.microbot.agility.courses.RellekkaCourse; import net.runelite.client.plugins.microbot.agility.courses.SeersCourse; import net.runelite.client.plugins.microbot.agility.courses.ShayzienAdvancedCourse; @@ -23,12 +24,13 @@ @Getter public enum AgilityCourse { + AGILITY_PYRAMID("Agility Pyramid", new PyramidCourse()), AL_KHARID_ROOFTOP_COURSE("Al Kharid Rooftop Course", new AlKharidCourse()), APE_ATOLL_AGILITY_COURSE("Ape Atoll Agility Course", new ApeAtollCourse()), ARDOUGNE_ROOFTOP_COURSE("Ardougne Rooftop Course", new ArdougneCourse()), + CANIFIS_ROOFTOP_COURSE("Canifis Rooftop Course", new CanafisCourse()), COLOSSAL_WYRM_ADVANCED_COURSE("Colossal Wyrm Advanced Course", new ColossalWyrmAdvancedCourse()), COLOSSAL_WYRM_BASIC_COURSE("Colossal Wyrm Basic Course", new ColossalWyrmBasicCourse()), - CANIFIS_ROOFTOP_COURSE("Canifis Rooftop Course", new CanafisCourse()), DRAYNOR_VILLAGE_ROOFTOP_COURSE("Draynor Village Rooftop Course", new DraynorCourse()), FALADOR_ROOFTOP_COURSE("Falador Rooftop Course", new FaladorCourse()), GNOME_STRONGHOLD_AGILITY_COURSE("Gnome Stronghold Agility Course", new GnomeStrongholdCourse()), @@ -36,8 +38,8 @@ public enum AgilityCourse PRIFDDINAS_AGILITY_COURSE("Prifddinas Agility Course", new PrifddinasCourse()), RELLEKKA_ROOFTOP_COURSE("Rellekka Rooftop Course", new RellekkaCourse()), SEERS_VILLAGE_ROOFTOP_COURSE("Seers' Village Rooftop Course", new SeersCourse()), - SHAYZIEN_BASIC_COURSE("Shayzien Basic Agility Course", new ShayzienBasicCourse()), SHAYZIEN_ADVANCED_COURSE("Shayzien Advanced Agility Course", new ShayzienAdvancedCourse()), + SHAYZIEN_BASIC_COURSE("Shayzien Basic Agility Course", new ShayzienBasicCourse()), VARROCK_ROOFTOP_COURSE("Varrock Rooftop Course", new VarrockCourse()), WEREWOLF_COURSE("Werewolf Agility Course", new WerewolfCourse()) ; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterConfig.java index 0d6a7adea61..99e5dc3bfff 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterConfig.java @@ -246,7 +246,7 @@ default DefaultLooterStyle looterStyle() { section = lootSection ) default String listOfItemsToLoot() { - return "bones,ashes"; + return "brimstone key"; } @ConfigItem( @@ -322,7 +322,7 @@ default boolean toggleLootUntradables() { @ConfigItem( keyName = "Bury Bones", name = "Bury Bones", - description = "Picks up and Bury Bones", + description = "Picks up and Bury Bones. Casts Sinister Offering if possible.", position = 96, section = lootSection ) @@ -332,8 +332,8 @@ default boolean toggleBuryBones() { @ConfigItem( keyName = "Scatter", - name = "Scatter", - description = "Picks up and Scatter ashes", + name = "Scatter Ashes", + description = "Picks up and Scatter ashes. Casts Demonic Offering if possible.", position = 97, section = lootSection ) @@ -398,6 +398,31 @@ default boolean toggleHighAlchProfitable() { ) default boolean eatFoodForSpace() { return false; } + @ConfigItem( + keyName = "waitForLoot", + name = "Wait for Loot", + description = "Wait for loot to appear before attacking next NPC", + position = 103, + section = lootSection, + hidden = true + ) + default boolean toggleWaitForLoot() { + return false; + } + + @Range(min = 1, max = 10) + @ConfigItem( + keyName = "lootWaitTimeout", + name = "Loot Wait Timeout (s)", + description = "Seconds to wait for loot before resuming combat (1-10)", + position = 104, + section = lootSection, + hidden = true + ) + default int lootWaitTimeout() { + return 6; + } + //set center tile manually @ConfigItem( keyName = "Center Tile", diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java index 333e7b008a5..e8dd33c366c 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java @@ -55,7 +55,7 @@ ) @Slf4j public class AIOFighterPlugin extends Plugin { - public static final String version = "2.0.2 BETA"; + public static final String version = "2.0.3 BETA"; public static boolean needShopping = false; private static final String SET = "Set"; private static final String CENTER_TILE = ColorUtil.wrapWithColorTag("Center Tile", JagexColors.MENU_TARGET); @@ -68,6 +68,26 @@ public class AIOFighterPlugin extends Plugin { @Getter @Setter public static int cooldown = 0; + + @Getter @Setter + private static volatile long lastNpcKilledTime = 0; + + @Getter @Setter + private static volatile boolean waitingForLoot = false; + + /** + * Centralized method to clear wait-for-loot state + * @param reason Optional reason for clearing the state (for logging) + */ + public static void clearWaitForLoot(String reason) { + setWaitingForLoot(false); + setLastNpcKilledTime(0L); + AttackNpcScript.cachedTargetNpcIndex = -1; + if (reason != null) { + Microbot.log("Clearing wait-for-loot state: " + reason); + } + } + private final CannonScript cannonScript = new CannonScript(); private final AttackNpcScript attackNpc = new AttackNpcScript(); @@ -117,6 +137,9 @@ protected void startUp() throws AWTException { return; } setState(State.IDLE); + // Reset wait for loot state on startup + setWaitingForLoot(false); + setLastNpcKilledTime(0L); // Get the future from the reference and cancel it ScheduledFuture scheduledFuture = futureRef.get(); if (scheduledFuture != null) { @@ -167,6 +190,10 @@ protected void startUp() throws AWTException { } protected void shutDown() { + // Reset wait for loot state on shutdown + setWaitingForLoot(false); + setLastNpcKilledTime(0L); + highAlchScript.shutdown(); lootScript.shutdown(); cannonScript.shutdown(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/AttackNpcScript.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/AttackNpcScript.java index 85e8f4e964a..79a03daaa74 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/AttackNpcScript.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/AttackNpcScript.java @@ -4,7 +4,6 @@ import net.runelite.api.Actor; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.ItemID; -import net.runelite.api.gameval.VarPlayerID; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.aiofighter.AIOFighterConfig; @@ -18,6 +17,7 @@ import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; import net.runelite.client.plugins.microbot.util.antiban.enums.ActivityIntensity; import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; +import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; import net.runelite.client.plugins.microbot.util.coords.Rs2WorldArea; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; @@ -45,6 +45,7 @@ public class AttackNpcScript extends Script { public static Actor currentNpc = null; public static AtomicReference> filteredAttackableNpcs = new AtomicReference<>(new ArrayList<>()); public static Rs2WorldArea attackableArea = null; + public static volatile int cachedTargetNpcIndex = -1; private boolean messageShown = false; private int noNpcCount = 0; @@ -103,6 +104,53 @@ public void run(AIOFighterConfig config) { if (config.state().equals(State.BANKING) || config.state().equals(State.WALKING)) return; + // Check if we should pause while looting is happening + if (Microbot.pauseAllScripts.get()) { + return; // Don't attack while looting + } + + // Check if we need to update our cached target (but not while waiting for loot) + if (!AIOFighterPlugin.isWaitingForLoot()) { + Actor currentInteracting = Rs2Player.getInteracting(); + if (currentInteracting instanceof Rs2NpcModel) { + Rs2NpcModel npc = (Rs2NpcModel) currentInteracting; + // Update our cached target to who we're fighting + if (npc.getHealthRatio() > 0 && !npc.isDead()) { + cachedTargetNpcIndex = npc.getIndex(); + } + } + } + + // Check if our cached target died + if (config.toggleWaitForLoot() && !AIOFighterPlugin.isWaitingForLoot() && cachedTargetNpcIndex != -1) { + // Find the NPC by index using Rs2 API + Rs2NpcModel cachedNpcModel = Rs2Npc.getNpcByIndex(cachedTargetNpcIndex); + + if (cachedNpcModel != null && (cachedNpcModel.isDead() || (cachedNpcModel.getHealthRatio() == 0 && cachedNpcModel.getHealthScale() > 0))) { + AIOFighterPlugin.setWaitingForLoot(true); + AIOFighterPlugin.setLastNpcKilledTime(System.currentTimeMillis()); + Microbot.status = "Waiting for loot..."; + Microbot.log("NPC died, waiting for loot..."); + cachedTargetNpcIndex = -1; + return; + } + } + + // Check if we're waiting for loot + if (config.toggleWaitForLoot() && AIOFighterPlugin.isWaitingForLoot()) { + long timeSinceKill = System.currentTimeMillis() - AIOFighterPlugin.getLastNpcKilledTime(); + int timeoutMs = config.lootWaitTimeout() * 1000; + if (timeSinceKill >= timeoutMs) { + // Timeout reached, resume combat + AIOFighterPlugin.clearWaitForLoot("Loot wait timeout reached, resuming combat"); + cachedTargetNpcIndex = -1; // Clear cached NPC on timeout + } else { + // Still waiting for loot, don't attack + int secondsLeft = (int) Math.max(1, TimeUnit.MILLISECONDS.toSeconds(timeoutMs - timeSinceKill)); + Microbot.status = "Waiting for loot... " + secondsLeft + "s"; + return; + } + } if (config.toggleCenterTile() && config.centerLocation().getX() == 0 && config.centerLocation().getY() == 0) { @@ -114,15 +162,24 @@ public void run(AIOFighterConfig config) { } messageShown = false; - - if (Rs2AntibanSettings.actionCooldownActive) { - AIOFighterPlugin.setState(State.COMBAT); - handleItemOnNpcToKill(config); - return; + if(Rs2AntibanSettings.antibanEnabled && Rs2AntibanSettings.actionCooldownChance > 0){ + if (Rs2AntibanSettings.actionCooldownActive) { + AIOFighterPlugin.setState(State.COMBAT); + handleItemOnNpcToKill(config); + return; + } + } + else { + if (Rs2Combat.inCombat()) { + AIOFighterPlugin.setState(State.COMBAT); + handleItemOnNpcToKill(config); + return; + } } if (!attackableNpcs.isEmpty()) { noNpcCount = 0; + Rs2NpcModel npc = attackableNpcs.stream().findFirst().orElse(null); if (!Rs2Camera.isTileOnScreen(npc.getLocalLocation())) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/BuryScatterScript.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/BuryScatterScript.java index 30683417cf0..262300cf8d1 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/BuryScatterScript.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/BuryScatterScript.java @@ -1,50 +1,58 @@ package net.runelite.client.plugins.microbot.aiofighter.combat; -import java.util.List; -import java.util.concurrent.TimeUnit; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.aiofighter.AIOFighterConfig; 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.magic.Rs2Magic; +import net.runelite.client.plugins.microbot.util.magic.Rs2Spells; import net.runelite.client.plugins.microbot.util.player.Rs2Player; -public class BuryScatterScript extends Script -{ - public boolean run(AIOFighterConfig config) - { - mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { - try - { - if (!Microbot.isLoggedIn() || !super.run() || (!config.toggleBuryBones() && !config.toggleScatter())) - { - return; - } +import java.util.List; +import java.util.concurrent.TimeUnit; + +public class BuryScatterScript extends Script { + public boolean run(AIOFighterConfig config) { + mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { + try { + if (!Microbot.isLoggedIn() || !super.run()) { + return; + } - processItems(config.toggleBuryBones(), Rs2Inventory.getBones(), "bury"); - processItems(config.toggleScatter(), Rs2Inventory.getAshes(), "scatter"); + if (config.toggleBuryBones()) { + List bones = Rs2Inventory.getBones(); + processItems(bones, Rs2Spells.SINISTER_OFFERING, "bury"); + } + if (config.toggleScatter()) { + List ashes = Rs2Inventory.getAshes(); + processItems(ashes, Rs2Spells.DEMONIC_OFFERING, "scatter"); + } + } catch (Exception ex) { + Microbot.logStackTrace(this.getClass().getSimpleName(), ex); + } + }, 0, 600, TimeUnit.MILLISECONDS); + return true; + } - } - catch (Exception ex) - { - Microbot.logStackTrace(this.getClass().getSimpleName(), ex); - } - }, 0, 600, TimeUnit.MILLISECONDS); - return true; - } - private void processItems(boolean toggle, List items, String action) - { - if (!toggle || items == null || items.isEmpty()) - { - return; - } - Rs2Inventory.interact(items.get(0), action); - Rs2Player.waitForAnimation(); - } + private void processItems(List items, Rs2Spells spell, String action) { + if (items == null || items.isEmpty()) { + return; + } + if (Rs2Magic.canCast(spell)) { + if (items.size() >= 3) { + if (Rs2Magic.cast(spell)) { + sleepUntil(() -> Rs2Inventory.getList(item -> item.getName().equals(items.get(0).getName())).isEmpty()); + } + } + } else { + Rs2Inventory.interact(items.get(0), action); + Rs2Player.waitForAnimation(); + } + } - public void shutdown() - { - super.shutdown(); - } + public void shutdown() { + super.shutdown(); + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/FlickerScript.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/FlickerScript.java index 21310b3ba70..f468f0e2501 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/FlickerScript.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/FlickerScript.java @@ -173,7 +173,7 @@ public void resetLastAttack(boolean forceReset) { AttackStyle attackStyle = AttackStyleMapper.mapToAttackStyle(style); if (m != null) { - if (forceReset && m.lastAttack <= 0) { + if (forceReset && (m.lastAttack <= 0 || npc.getAnimation() != -1)) { m.lastAttack = m.rs2NpcStats.getAttackSpeed(); } if ((!npc.isDead() && m.lastAttack <= 0) || diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/HighAlchScript.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/HighAlchScript.java index bb74a68b9d9..3300936c6aa 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/HighAlchScript.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/HighAlchScript.java @@ -1,8 +1,5 @@ package net.runelite.client.plugins.microbot.aiofighter.combat; -import java.util.List; -import java.util.concurrent.TimeUnit; -import net.runelite.api.Skill; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.aiofighter.AIOFighterConfig; @@ -19,6 +16,9 @@ import net.runelite.client.plugins.microbot.util.tabs.Rs2Tab; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; +import java.util.List; +import java.util.concurrent.TimeUnit; + public class HighAlchScript extends Script { @@ -70,7 +70,7 @@ public boolean run(AIOFighterConfig config) } Rs2ExplorersRing.closeInterface(); } - else if (Rs2Player.getSkillRequirement(Skill.MAGIC, Rs2Spells.HIGH_LEVEL_ALCHEMY.getRequiredLevel()) && Rs2Magic.hasRequiredRunes(Rs2Spells.HIGH_LEVEL_ALCHEMY)) + else if (Rs2Magic.canCast(Rs2Spells.HIGH_LEVEL_ALCHEMY)) { for (Rs2ItemModel item : items) { @@ -86,9 +86,9 @@ else if (Rs2Player.getSkillRequirement(Skill.MAGIC, Rs2Spells.HIGH_LEVEL_ALCHEMY if (Rs2Widget.hasWidget("Proceed to cast High Alchemy on it")) { Rs2Keyboard.keyPress('1'); - Rs2Player.waitForAnimation(); } } + Rs2Player.waitForAnimation(); } } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/loot/LootScript.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/loot/LootScript.java index f3e4a0ce511..0791d45db93 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/loot/LootScript.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/loot/LootScript.java @@ -1,84 +1,63 @@ package net.runelite.client.plugins.microbot.aiofighter.loot; import lombok.extern.slf4j.Slf4j; -import net.runelite.client.plugins.grounditems.GroundItem; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.aiofighter.AIOFighterConfig; import net.runelite.client.plugins.microbot.aiofighter.AIOFighterPlugin; import net.runelite.client.plugins.microbot.aiofighter.enums.DefaultLooterStyle; import net.runelite.client.plugins.microbot.aiofighter.enums.State; -import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; +import net.runelite.client.plugins.microbot.util.grounditem.LootingParameters; import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.inventory.Rs2RunePouch; -import net.runelite.client.plugins.microbot.util.magic.Runes; import net.runelite.client.plugins.microbot.util.player.Rs2Player; -import java.util.*; import java.util.concurrent.TimeUnit; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -import static net.runelite.api.TileItem.OWNERSHIP_SELF; -import static net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem.*; @Slf4j public class LootScript extends Script { int minFreeSlots = 0; + public LootScript() { + + } + + public boolean run(AIOFighterConfig config) { + mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { try { minFreeSlots = config.bank() ? config.minFreeSlots() : 0; if (!super.run()) return; if (!Microbot.isLoggedIn()) return; if (!config.toggleLootItems()) return; - if (Rs2AntibanSettings.actionCooldownActive) return; - if (AIOFighterPlugin.getState().equals(State.BANKING) || AIOFighterPlugin.getState().equals(State.WALKING)) { - return; - } - if (Rs2Player.isInCombat() && !config.toggleForceLoot()) { + if (AIOFighterPlugin.getState().equals(State.BANKING) || AIOFighterPlugin.getState().equals(State.WALKING)) return; + if (((Rs2Inventory.isFull() || Rs2Inventory.getEmptySlots() <= minFreeSlots) && !config.eatFoodForSpace()) || (Rs2Player.isInCombat() && !config.toggleForceLoot() && !AIOFighterPlugin.isWaitingForLoot())) { return; } + if (config.toggleWaitForLoot()) { + //TODO: currently disabled as it had merge conflicts with ducks changes. + } - String[] itemNamesToLoot = lootItemNames(config); - final Predicate filter = groundItem -> - groundItem.getLocation().distanceTo(Microbot.getClient().getLocalPlayer().getWorldLocation()) < config.attackRadius() && - (!config.toggleOnlyLootMyItems() || groundItem.getOwnership() == OWNERSHIP_SELF) && - (shouldLootBasedOnName(groundItem, itemNamesToLoot) || shouldLootBasedOnValue(groundItem, config)); - List groundItems = getGroundItems().values().stream() - .filter(filter) - .collect(Collectors.toList()); - if (groundItems.isEmpty()) { - return; - } - if (config.toggleDelayedLooting()) { - groundItems.sort(Comparator.comparingInt(Rs2GroundItem::calculateDespawnTime)); + if (config.looterStyle().equals(DefaultLooterStyle.MIXED) || config.looterStyle().equals(DefaultLooterStyle.ITEM_LIST)) { + lootItemsOnName(config); } - //Pause other scripts before looting - Microbot.pauseAllScripts.getAndSet(true); - for (GroundItem groundItem : groundItems) { - if (Rs2Inventory.emptySlotCount() <= minFreeSlots && !canStackItem(groundItem)) { - Microbot.log("Unable to pick loot: " + groundItem.getName() + " making space"); - if (!config.eatFoodForSpace()) { - continue; - } - int emptySlots = Rs2Inventory.emptySlotCount(); - if (Rs2Player.eatAt(100)) { - sleepUntil(() -> emptySlots < Rs2Inventory.emptySlotCount(), 1200); - } - } - Microbot.log("Picking up loot: " + groundItem.getName()); - if (!waitForGroundItemDespawn(() -> interact(groundItem), groundItem)) { - return; - } + + + if (config.looterStyle().equals(DefaultLooterStyle.GE_PRICE_RANGE) || config.looterStyle().equals(DefaultLooterStyle.MIXED)) { + lootItemsByValue(config); } - Microbot.log("Looting complete"); - Microbot.pauseAllScripts.compareAndSet(true, false); - } catch (Exception ex) { + + lootBones(config); + lootAshes(config); + lootRunes(config); + lootCoins(config); + lootUntradeableItems(config); + lootArrows(config); + + } catch(Exception ex) { Microbot.log("Looterscript: " + ex.getMessage()); } @@ -86,54 +65,147 @@ public boolean run(AIOFighterConfig config) { return true; } - private boolean canStackItem(GroundItem groundItem) { - if (!groundItem.isStackable()) { - return false; - } - int runePouchRunes = Rs2RunePouch.getQuantity(groundItem.getItemId()); - if (runePouchRunes > 0 && runePouchRunes <= 16000 - groundItem.getQuantity()) { - return true; + private void lootArrows(AIOFighterConfig config) { + if (config.toggleLootArrows()) { + LootingParameters arrowParams = new LootingParameters( + config.attackRadius(), + 1, + 10, + minFreeSlots, + config.toggleDelayedLooting(), + config.toggleOnlyLootMyItems(), + "arrow" + ); + arrowParams.setEatFoodForSpace(config.eatFoodForSpace()); + if (Rs2GroundItem.lootItemsBasedOnNames(arrowParams)) { + Microbot.pauseAllScripts.compareAndSet(true, false); + } } - //TODO("Coal bag, Herb Sack, Seed pack") - return Rs2Inventory.contains(groundItem.getItemId()); - } - - private boolean shouldLootBasedOnValue(GroundItem groundItem, AIOFighterConfig config) { - if (config.looterStyle() != DefaultLooterStyle.GE_PRICE_RANGE && config.looterStyle() != DefaultLooterStyle.MIXED) - return false; - int price = groundItem.getGePrice(); - return config.minPriceOfItemsToLoot() <= price && price / groundItem.getQuantity() <= config.maxPriceOfItemsToLoot(); } - private boolean shouldLootBasedOnName(GroundItem groundItem, String[] itemNamesToLoot) { - return Arrays.stream(itemNamesToLoot).anyMatch(name -> groundItem.getName().trim().toLowerCase().contains(name.trim().toLowerCase())); - } - - private String[] lootItemNames(AIOFighterConfig config) { - ArrayList itemNamesToLoot = new ArrayList<>(); - if (config.toggleLootArrows()) { - itemNamesToLoot.add("arrow"); - } + private void lootBones(AIOFighterConfig config) { if (config.toggleBuryBones()) { - itemNamesToLoot.add("bones"); + LootingParameters bonesParams = new LootingParameters( + config.attackRadius(), + 1, + 1, + minFreeSlots, + config.toggleDelayedLooting(), + config.toggleOnlyLootMyItems(), + "bones" + ); + bonesParams.setEatFoodForSpace(config.eatFoodForSpace()); + if (Rs2GroundItem.lootItemsBasedOnNames(bonesParams)) { + Microbot.pauseAllScripts.compareAndSet(true, false); + } } + } + + private void lootAshes(AIOFighterConfig config) { if (config.toggleScatter()) { - itemNamesToLoot.add(" ashes"); + LootingParameters ashesParams = new LootingParameters( + config.attackRadius(), + 1, + 1, + minFreeSlots, + config.toggleDelayedLooting(), + config.toggleOnlyLootMyItems(), + " ashes" + ); + ashesParams.setEatFoodForSpace(config.eatFoodForSpace()); + if (Rs2GroundItem.lootItemsBasedOnNames(ashesParams)) { + Microbot.pauseAllScripts.compareAndSet(true, false); + } } + } + + // loot runes + private void lootRunes(AIOFighterConfig config) { if (config.toggleLootRunes()) { - itemNamesToLoot.add(" rune"); + LootingParameters runesParams = new LootingParameters( + config.attackRadius(), + 1, + 1, + minFreeSlots, + config.toggleDelayedLooting(), + config.toggleOnlyLootMyItems(), + " rune" + ); + runesParams.setEatFoodForSpace(config.eatFoodForSpace()); + if (Rs2GroundItem.lootItemsBasedOnNames(runesParams)) { + Microbot.pauseAllScripts.compareAndSet(true, false); + } } + } + + // loot coins + private void lootCoins(AIOFighterConfig config) { if (config.toggleLootCoins()) { - itemNamesToLoot.add("coins"); + LootingParameters coinsParams = new LootingParameters( + config.attackRadius(), + 1, + 1, + minFreeSlots, + config.toggleDelayedLooting(), + config.toggleOnlyLootMyItems(), + "coins" + ); + coinsParams.setEatFoodForSpace(config.eatFoodForSpace()); + if (Rs2GroundItem.lootCoins(coinsParams)) { + Microbot.pauseAllScripts.compareAndSet(true, false); + } } + } + + // loot untradeable items + private void lootUntradeableItems(AIOFighterConfig config) { if (config.toggleLootUntradables()) { - itemNamesToLoot.add("untradeable"); - itemNamesToLoot.add("scroll box"); + LootingParameters untradeableItemsParams = new LootingParameters( + config.attackRadius(), + 1, + 1, + minFreeSlots, + config.toggleDelayedLooting(), + config.toggleOnlyLootMyItems(), + "untradeable" + ); + untradeableItemsParams.setEatFoodForSpace(config.eatFoodForSpace()); + if (Rs2GroundItem.lootUntradables(untradeableItemsParams)) { + Microbot.pauseAllScripts.compareAndSet(true, false); + } + } + } + + private void lootItemsByValue(AIOFighterConfig config) { + LootingParameters valueParams = new LootingParameters( + config.minPriceOfItemsToLoot(), + config.maxPriceOfItemsToLoot(), + config.attackRadius(), + 1, + minFreeSlots, + config.toggleDelayedLooting(), + config.toggleOnlyLootMyItems() + ); + valueParams.setEatFoodForSpace(config.eatFoodForSpace()); + if (Rs2GroundItem.lootItemBasedOnValue(valueParams)) { + Microbot.pauseAllScripts.compareAndSet(true, false); } - if (config.looterStyle().equals(DefaultLooterStyle.MIXED) || config.looterStyle().equals(DefaultLooterStyle.ITEM_LIST)) { - itemNamesToLoot.addAll(Arrays.asList(config.listOfItemsToLoot().trim().split(","))); + } + + private void lootItemsOnName(AIOFighterConfig config) { + LootingParameters valueParams = new LootingParameters( + config.attackRadius(), + 1, + 1, + minFreeSlots, + config.toggleDelayedLooting(), + config.toggleOnlyLootMyItems(), + config.listOfItemsToLoot().trim().split(",") + ); + valueParams.setEatFoodForSpace(config.eatFoodForSpace()); + if (Rs2GroundItem.lootItemsBasedOnNames(valueParams)) { + Microbot.pauseAllScripts.compareAndSet(true, false); } - return itemNamesToLoot.toArray(new String[0]); } public void shutdown() { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/bga/autoherblore/AutoHerbloreConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/bga/autoherblore/AutoHerbloreConfig.java deleted file mode 100644 index 16b77aca0b1..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/bga/autoherblore/AutoHerbloreConfig.java +++ /dev/null @@ -1,42 +0,0 @@ -package net.runelite.client.plugins.microbot.bga.autoherblore; - -import net.runelite.client.config.Config; -import net.runelite.client.config.ConfigGroup; -import net.runelite.client.config.ConfigItem; -import net.runelite.client.config.ConfigSection; -import net.runelite.client.plugins.microbot.bga.autoherblore.enums.HerblorePotion; -import net.runelite.client.plugins.microbot.bga.autoherblore.enums.Mode; - -@ConfigGroup("AutoHerblore") -public interface AutoHerbloreConfig extends Config { - @ConfigSection( - name = "Mode", - description = "Select the herblore mode", - position = 0 - ) - String MODE_SECTION = "mode"; - - @ConfigItem( - keyName = "mode", - name = "Mode", - description = "Select mode", - section = MODE_SECTION - ) - default Mode mode() { return Mode.CLEAN_HERBS; } - - @ConfigSection( - name = "Finished Potion Type", - description = "Select which finished potion to create", - position = 1 - ) - String POTION_SECTION = "potion"; - - @ConfigItem( - keyName = "potion", - name = "Potion", - description = "Select potion", - section = POTION_SECTION - ) - default HerblorePotion potion() { return HerblorePotion.ATTACK; } -} - diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/bga/autoherblore/AutoHerblorePlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/bga/autoherblore/AutoHerblorePlugin.java deleted file mode 100644 index 25fde6cfa44..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/bga/autoherblore/AutoHerblorePlugin.java +++ /dev/null @@ -1,27 +0,0 @@ -package net.runelite.client.plugins.microbot.bga.autoherblore; - -import com.google.inject.Provides; -import net.runelite.client.config.ConfigManager; -import net.runelite.client.plugins.Plugin; -import net.runelite.client.plugins.PluginDescriptor; -import javax.inject.Inject; -import java.awt.AWTException; - -@PluginDescriptor( - name = "[bga] Auto Herblore", - description = "Performs various herblore tasks...", - tags = {"herblore","skilling"}, - enabledByDefault = false -) -public class AutoHerblorePlugin extends Plugin { - @Inject - private AutoHerbloreConfig config; - @Provides - AutoHerbloreConfig provideConfig(ConfigManager configManager) { return configManager.getConfig(AutoHerbloreConfig.class); } - @Inject - private AutoHerbloreScript script; - @Override - protected void startUp() throws AWTException { script.run(config); } - @Override - protected void shutDown() { script.shutdown(); } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/bga/autoherblore/AutoHerbloreScript.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/bga/autoherblore/AutoHerbloreScript.java deleted file mode 100644 index eea156ab10e..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/bga/autoherblore/AutoHerbloreScript.java +++ /dev/null @@ -1,193 +0,0 @@ -package net.runelite.client.plugins.microbot.bga.autoherblore; - -import net.runelite.api.Skill; -import net.runelite.api.gameval.ItemID; -import net.runelite.client.plugins.microbot.Microbot; -import net.runelite.client.plugins.microbot.Script; -import net.runelite.client.plugins.microbot.util.antiban.Rs2Antiban; -import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; -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.player.Rs2Player; -import net.runelite.client.plugins.microbot.bga.autoherblore.enums.Herb; -import net.runelite.client.plugins.microbot.bga.autoherblore.enums.Mode; -import net.runelite.client.plugins.microbot.bga.autoherblore.enums.HerblorePotion; -import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; -import net.runelite.client.plugins.microbot.util.inventory.InteractOrder; -import java.util.concurrent.TimeUnit; - -public class AutoHerbloreScript extends Script { - - private enum State { BANK, CLEAN, MAKE_UNFINISHED, MAKE_FINISHED } - private State state; - private Herb current; - private Herb currentHerbForUnfinished; - private HerblorePotion currentPotion; - private boolean currentlyMakingPotions; - private int withdrawnAmount; - private AutoHerbloreConfig config; - public boolean run(AutoHerbloreConfig config) { - this.config = config; - Rs2Antiban.resetAntibanSettings(); - Rs2Antiban.antibanSetupTemplates.applyHerbloreSetup(); - state = State.BANK; - current = null; - currentHerbForUnfinished = null; - currentPotion = null; - currentlyMakingPotions = false; - withdrawnAmount = 0; - mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { - try { - if (!Microbot.isLoggedIn()) return; - if (!super.run()) return; - if (Rs2AntibanSettings.actionCooldownActive) return; - if (state == State.BANK) { - if (!Rs2Bank.isNearBank(10)) { - Rs2Bank.walkToBank(); - return; - } - if (!Rs2Bank.openBank()) return; - Rs2Bank.depositAll(); - Rs2Inventory.waitForInventoryChanges(1800); - if (config.mode() == Mode.CLEAN_HERBS) { - if (current == null || !Rs2Bank.hasItem(current.grimy)) current = findHerb(); - if (current == null) { - Microbot.showMessage("No more herbs"); - shutdown(); - return; - } - Rs2Bank.withdrawX(current.grimy, 28); - Rs2Inventory.waitForInventoryChanges(1800); - Rs2Bank.closeBank(); - state = State.CLEAN; - return; - } - if (config.mode() == Mode.UNFINISHED_POTIONS) { - if (currentHerbForUnfinished == null || (!Rs2Bank.hasItem(currentHerbForUnfinished.clean) || !Rs2Bank.hasItem(ItemID.VIAL_WATER))) { - currentHerbForUnfinished = findHerbForUnfinished(); - if (currentHerbForUnfinished == null) { - Microbot.showMessage("No more herbs or vials of water"); - shutdown(); - return; - } - } - int herbCount = Rs2Bank.count(currentHerbForUnfinished.clean); - int vialCount = Rs2Bank.count(ItemID.VIAL_WATER); - withdrawnAmount = Math.min(Math.min(herbCount, vialCount), 14); - - Rs2Bank.withdrawX(currentHerbForUnfinished.clean, withdrawnAmount); - Rs2Bank.withdrawX(ItemID.VIAL_WATER, withdrawnAmount); - Rs2Inventory.waitForInventoryChanges(1800); - Rs2Bank.closeBank(); - state = State.MAKE_UNFINISHED; - return; - } - if (config.mode() == Mode.FINISHED_POTIONS) { - if (currentPotion == null || !Rs2Bank.hasItem(currentPotion.unfinished) || !Rs2Bank.hasItem(currentPotion.secondary)) { - currentPotion = findPotion(); - if (currentPotion == null) { - Microbot.showMessage("No more ingredients for selected potion"); - shutdown(); - return; - } - } - int unfinishedCount = Rs2Bank.count(currentPotion.unfinished); - int secondaryCount = Rs2Bank.count(currentPotion.secondary); - withdrawnAmount = Math.min(Math.min(unfinishedCount, secondaryCount), 14); - - Rs2Bank.withdrawX(currentPotion.unfinished, withdrawnAmount); - Rs2Bank.withdrawX(currentPotion.secondary, withdrawnAmount); - Rs2Inventory.waitForInventoryChanges(1800); - Rs2Bank.closeBank(); - state = State.MAKE_FINISHED; - return; - } - } - if (config.mode() == Mode.CLEAN_HERBS && state == State.CLEAN) { - if (Rs2Inventory.hasItem("grimy")) { - Rs2Inventory.cleanHerbs(InteractOrder.ZIGZAG); - Rs2Inventory.waitForInventoryChanges(1800); - return; - } - state = State.BANK; - } - if (config.mode() == Mode.UNFINISHED_POTIONS && state == State.MAKE_UNFINISHED) { - if (currentlyMakingPotions) { - if (!Rs2Inventory.hasItem(currentHerbForUnfinished.clean) && !Rs2Inventory.hasItem(ItemID.VIAL_WATER)) { - currentlyMakingPotions = false; - state = State.BANK; - return; - } - return; - } - - if (Rs2Inventory.hasItem(currentHerbForUnfinished.clean) && Rs2Inventory.hasItem(ItemID.VIAL_WATER)) { - if (Rs2Inventory.combine(currentHerbForUnfinished.clean, ItemID.VIAL_WATER)) { - sleep(600, 800); - // Only press 1 if we're making more than 1 potion - if (withdrawnAmount > 1) { - Rs2Keyboard.keyPress('1'); - } - currentlyMakingPotions = true; - return; - } - } - state = State.BANK; - } - if (config.mode() == Mode.FINISHED_POTIONS && state == State.MAKE_FINISHED) { - if (currentlyMakingPotions) { - if (!Rs2Inventory.hasItem(currentPotion.unfinished) && !Rs2Inventory.hasItem(currentPotion.secondary)) { - currentlyMakingPotions = false; - state = State.BANK; - return; - } - return; - } - - if (Rs2Inventory.hasItem(currentPotion.unfinished) && Rs2Inventory.hasItem(currentPotion.secondary)) { - if (Rs2Inventory.combine(currentPotion.unfinished, currentPotion.secondary)) { - sleep(600, 800); - // Only press 1 if we're making more than 1 potion - if (withdrawnAmount > 1) { - Rs2Keyboard.keyPress('1'); - } - currentlyMakingPotions = true; - return; - } - } - state = State.BANK; - } - } catch (Exception e) { - Microbot.log(e.getMessage()); - } - }, 0, 600, TimeUnit.MILLISECONDS); - return true; - } - private Herb findHerb() { - int level = Rs2Player.getRealSkillLevel(Skill.HERBLORE); - for (Herb h : Herb.values()) if (level >= h.level && Rs2Bank.hasItem(h.grimy)) return h; - return null; - } - private Herb findHerbForUnfinished() { - int level = Rs2Player.getRealSkillLevel(Skill.HERBLORE); - // Find any herb we can make unfinished potions with, starting from the lowest level herb - for (Herb h : Herb.values()) { - if (level >= h.level && Rs2Bank.hasItem(h.clean) && Rs2Bank.hasItem(ItemID.VIAL_WATER)) { - return h; - } - } - return null; - } - private HerblorePotion findPotion() { - int level = Rs2Player.getRealSkillLevel(Skill.HERBLORE); - HerblorePotion selectedPotion = config.potion(); - if (selectedPotion != null && level >= selectedPotion.level && Rs2Bank.hasItem(selectedPotion.unfinished) && Rs2Bank.hasItem(selectedPotion.secondary)) { - return selectedPotion; - } - return null; - } - public void shutdown() { - super.shutdown(); - Rs2Antiban.resetAntibanSettings(); - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/bga/autoherblore/enums/Herb.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/bga/autoherblore/enums/Herb.java deleted file mode 100644 index a6831c1f617..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/bga/autoherblore/enums/Herb.java +++ /dev/null @@ -1,30 +0,0 @@ -package net.runelite.client.plugins.microbot.bga.autoherblore.enums; - -import net.runelite.api.gameval.ItemID; - -public enum Herb { - GUAM(1, ItemID.UNIDENTIFIED_GUAM, ItemID.GUAM_LEAF, ItemID.GUAMVIAL), - MARRENTILL(5, ItemID.UNIDENTIFIED_MARENTILL, ItemID.MARENTILL, ItemID.MARRENTILLVIAL), - TARROMIN(11, ItemID.UNIDENTIFIED_TARROMIN, ItemID.TARROMIN, ItemID.TARROMINVIAL), - HARRALANDER(20, ItemID.UNIDENTIFIED_HARRALANDER, ItemID.HARRALANDER, ItemID.HARRALANDERVIAL), - RANARR(25, ItemID.UNIDENTIFIED_RANARR, ItemID.RANARR_WEED, ItemID.RANARRVIAL), - TOADFLAX(30, ItemID.UNIDENTIFIED_TOADFLAX, ItemID.TOADFLAX, ItemID.TOADFLAXVIAL), - IRIT(40, ItemID.UNIDENTIFIED_IRIT, ItemID.IRIT_LEAF, ItemID.IRITVIAL), - AVANTOE(48, ItemID.UNIDENTIFIED_AVANTOE, ItemID.AVANTOE, ItemID.AVANTOEVIAL), - KWUARM(54, ItemID.UNIDENTIFIED_KWUARM, ItemID.KWUARM, ItemID.KWUARMVIAL), - SNAPDRAGON(59, ItemID.UNIDENTIFIED_SNAPDRAGON, ItemID.SNAPDRAGON, ItemID.SNAPDRAGONVIAL), - CADANTINE(65, ItemID.UNIDENTIFIED_CADANTINE, ItemID.CADANTINE, ItemID.CADANTINEVIAL), - LANTADYME(67, ItemID.UNIDENTIFIED_LANTADYME, ItemID.LANTADYME, ItemID.LANTADYMEVIAL), - DWARF(70, ItemID.UNIDENTIFIED_DWARF_WEED, ItemID.DWARF_WEED, ItemID.DWARFWEEDVIAL), - TORSTOL(75, ItemID.UNIDENTIFIED_TORSTOL, ItemID.TORSTOL, ItemID.TORSTOLVIAL); - public final int level; - public final int grimy; - public final int clean; - public final int unfinished; - Herb(int level, int grimy, int clean, int unfinished) { - this.level = level; - this.grimy = grimy; - this.clean = clean; - this.unfinished = unfinished; - } -} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/bga/autoherblore/enums/HerblorePotion.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/bga/autoherblore/enums/HerblorePotion.java deleted file mode 100644 index 78a10031e20..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/bga/autoherblore/enums/HerblorePotion.java +++ /dev/null @@ -1,26 +0,0 @@ -package net.runelite.client.plugins.microbot.bga.autoherblore.enums; - -import net.runelite.api.gameval.ItemID; - -public enum HerblorePotion { - ATTACK(1, ItemID.GUAMVIAL, ItemID.EYE_OF_NEWT), - ANTIPOISON(5, ItemID.MARRENTILLVIAL, ItemID.UNICORN_HORN_DUST), - STRENGTH(12, ItemID.TARROMINVIAL, ItemID.LIMPWURT_ROOT), - ENERGY(26, ItemID.HARRALANDERVIAL, ItemID.CHOCOLATE_DUST), - DEFENCE(30, ItemID.RANARRVIAL, ItemID.WHITE_BERRIES), - PRAYER(38, ItemID.RANARRVIAL, ItemID.SNAPE_GRASS), - SUPER_ATTACK(45, ItemID.IRITVIAL, ItemID.EYE_OF_NEWT), - SUPER_STRENGTH(55, ItemID.KWUARMVIAL, ItemID.LIMPWURT_ROOT), - SUPER_RESTORE(63, ItemID.SNAPDRAGONVIAL, ItemID.RED_SPIDERS_EGGS), - SUPER_DEFENCE(66, ItemID.CADANTINEVIAL, ItemID.WHITE_BERRIES), - RANGING(72, ItemID.DWARFWEEDVIAL, ItemID.WINE_OF_ZAMORAK), - MAGIC(76, ItemID.LANTADYMEVIAL, ItemID.CACTUS_POTATO); - public final int level; - public final int unfinished; - public final int secondary; - HerblorePotion(int level, int unfinished, int secondary) { - this.level = level; - this.unfinished = unfinished; - this.secondary = secondary; - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/bga/autoherblore/enums/Mode.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/bga/autoherblore/enums/Mode.java deleted file mode 100644 index 85faf646526..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/bga/autoherblore/enums/Mode.java +++ /dev/null @@ -1,7 +0,0 @@ -package net.runelite.client.plugins.microbot.bga.autoherblore.enums; - -public enum Mode { - CLEAN_HERBS, - UNFINISHED_POTIONS, - FINISHED_POTIONS -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnaceScript.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnaceScript.java index dac00553711..48dd6a8bc3c 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnaceScript.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnaceScript.java @@ -415,13 +415,16 @@ private void useStaminaPotions() { boolean hasStaminaPotion = Rs2Bank.hasItem(Rs2Potion.getStaminaPotion()); boolean hasEnergyPotion = Rs2Bank.hasItem(Rs2Potion.getRestoreEnergyPotionsVariants()); - if ((Rs2Player.hasStaminaBuffActive() && hasEnergyPotion) || (!hasStaminaPotion && hasEnergyPotion)) { - String potionName = getLowestDosePotionName(Rs2Potion.getRestoreEnergyPotionsVariants()); + + if (!Rs2Player.hasStaminaBuffActive() && hasStaminaPotion) { + String potionName = getLowestDosePotionName(List.of(Rs2Potion.getStaminaPotion())); if (potionName != null) { withdrawAndDrink(potionName); + return; } - } else if (hasStaminaPotion) { - String potionName = getLowestDosePotionName(List.of(Rs2Potion.getStaminaPotion())); + } + if (hasEnergyPotion) { + String potionName = getLowestDosePotionName(Rs2Potion.getRestoreEnergyPotionsVariants()); if (potionName != null) { withdrawAndDrink(potionName); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/BreakHandlerConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/BreakHandlerConfig.java index 4cf294a1c2c..53f089e6b1a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/BreakHandlerConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/BreakHandlerConfig.java @@ -123,7 +123,7 @@ default boolean logoutAfterBreak() { @ConfigItem( keyName = "useRandomWorld", - name = "Use RandomWorld", + name = "Use Random World", description = "Change to a random world once break is finished", position = 2, section = breakBehaviorOptions @@ -132,11 +132,22 @@ default boolean useRandomWorld() { return false; } + @ConfigItem( + keyName = "regionFilter", + name = "Region Filter", + description = "Restrict random worlds to a specific region", + position = 3, + section = breakBehaviorOptions + ) + default RegionFilter regionFilter() { + return RegionFilter.ANY; + } + @ConfigItem( keyName = "shutdownClient", name = "Shutdown Client", description = "WARNING: This will completely shutdown the entire RuneLite client during breaks.
Use with caution - you will need to manually restart the client after breaks.", - position = 3, + position = 4, section = breakBehaviorOptions ) default boolean shutdownClient() { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/BreakHandlerScript.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/BreakHandlerScript.java index 65194d235cc..b7707ababd5 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/BreakHandlerScript.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/BreakHandlerScript.java @@ -43,7 +43,7 @@ */ @Slf4j public class BreakHandlerScript extends Script { - public static String version = "2.0.0"; + public static String version = "2.1.0"; // Constants for configuration and timing private static final int SCHEDULER_INTERVAL_MS = 1000; @@ -106,6 +106,10 @@ public static void setLockState(boolean state) { private String originalWindowTitle = ""; private BreakHandlerConfig config; + // Track world state across breaks + private int preBreakWorld = -1; + private boolean loggedOutDuringBreak = false; + /** * Checks if a break is currently active (any break state except waiting). */ @@ -323,8 +327,14 @@ private void handleInitiatingBreakState() { Microbot.pauseAllScripts.compareAndSet(false, true); PluginPauseEvent.setPaused(true); Rs2Walker.setTarget(null); + + // Remember the world we were in before the break + preBreakWorld = Microbot.getClient().getWorld(); + // Determine next state based on break type - if (Rs2AntibanSettings.microBreakActive && (config.onlyMicroBreaks() || !shouldLogout())) { + boolean logout = shouldLogout(); + loggedOutDuringBreak = logout && !(Rs2AntibanSettings.microBreakActive && config.onlyMicroBreaks()); + if (!logout || (Rs2AntibanSettings.microBreakActive && config.onlyMicroBreaks())) { setBreakDuration(); transitionToState(BreakHandlerState.MICRO_BREAK_ACTIVE); } else { @@ -447,9 +457,12 @@ private void handleLoginRequestedState() { try { // Use the Login utility class to handle login if (Login.activeProfile != null) { - new Login(); + int world = config.useRandomWorld() + ? Login.getRandomWorld(Rs2Player.isMember(), config.regionFilter().getRegion()) + : preBreakWorld; + new Login(world); } else { - // If no active profile, use default login + // If no active profile, fall back to default login new Login(); } transitionToState(BreakHandlerState.LOGGING_IN); @@ -551,7 +564,10 @@ private boolean isSafeToBreak() { * Determines if logout should occur based on configuration and conditions. */ private boolean shouldLogout() { - return isOutsidePlaySchedule() || config.logoutAfterBreak(); + // Only attempt to logout during a normal break. When a micro break is + // active we should remain logged in regardless of the logout setting. + return !Rs2AntibanSettings.microBreakActive && + (isOutsidePlaySchedule() || config.logoutAfterBreak()); } /** @@ -608,9 +624,9 @@ private void resumeFromBreak() { * Handles world switching based on configuration. */ private void handleWorldSwitching() { - if (config.useRandomWorld()) { + if (config.useRandomWorld() && !loggedOutDuringBreak) { try { - int randomWorld = Login.getRandomWorld(Rs2Player.isMember()); + int randomWorld = Login.getRandomWorld(Rs2Player.isMember(), config.regionFilter().getRegion()); Microbot.hopToWorld(randomWorld); log.info("Switched to world {}", randomWorld); } catch (Exception ex) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/RegionFilter.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/RegionFilter.java new file mode 100644 index 00000000000..161566b395f --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/RegionFilter.java @@ -0,0 +1,27 @@ +package net.runelite.client.plugins.microbot.breakhandler; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.runelite.http.api.worlds.WorldRegion; + +/** + * Region options for selecting random worlds. + */ +@RequiredArgsConstructor +public enum RegionFilter { + ANY(null, "Any"), + US(WorldRegion.UNITED_STATES_OF_AMERICA, "US"), + UK(WorldRegion.UNITED_KINGDOM, "UK"), + GERMANY(WorldRegion.GERMANY, "Germany"), + AUSTRALIA(WorldRegion.AUSTRALIA, "Australia"); + + @Getter + private final WorldRegion region; + + private final String name; + + @Override + public String toString() { + return name; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/chompy/ChompyConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/chompy/ChompyConfig.java deleted file mode 100644 index b2380857fe5..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/chompy/ChompyConfig.java +++ /dev/null @@ -1,20 +0,0 @@ -package net.runelite.client.plugins.microbot.chompy; - -import net.runelite.client.config.Config; -import net.runelite.client.config.ConfigGroup; -import net.runelite.client.config.ConfigItem; -import net.runelite.client.plugins.microbot.fletching.FletchingConfig; - - -@ConfigGroup("chompy") -public interface ChompyConfig extends Config { - @ConfigItem( - keyName = "guide", - name = "How to use", - description = "How to use this plugin", - position = 1 - ) - default String GUIDE() { - return "Chompy Hunt plugin - start near some toads with bow and arrows equipped. You might want to babysit this one."; - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/chompy/ChompyOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/chompy/ChompyOverlay.java deleted file mode 100644 index d91a91cc2bd..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/chompy/ChompyOverlay.java +++ /dev/null @@ -1,51 +0,0 @@ -package net.runelite.client.plugins.microbot.chompy; - -import net.runelite.client.plugins.microbot.Microbot; -import net.runelite.client.ui.overlay.OverlayPanel; -import net.runelite.client.ui.overlay.OverlayPosition; -import net.runelite.client.ui.overlay.components.LineComponent; -import net.runelite.client.ui.overlay.components.TitleComponent; - -import javax.inject.Inject; -import java.awt.*; - -public class ChompyOverlay extends OverlayPanel { - @Inject - ChompyOverlay(ChompyPlugin plugin) - { - super(plugin); - setPosition(OverlayPosition.TOP_LEFT); - setNaughty(); - } - @Override - public Dimension render(Graphics2D graphics) { - try { - panelComponent.setPreferredSize(new Dimension(200, 300)); - panelComponent.getChildren().add(TitleComponent.builder() - .text("Chompy Hunter " + ChompyScript.version) - .color(Color.GREEN) - .build()); - - // Kills per hour: - long elapsed= System.currentTimeMillis() - ChompyScript.start_time; - double hours = elapsed / 3600000.0; - double killsPerHour = ChompyScript.chompy_kills / hours; - - panelComponent.getChildren().add(TitleComponent.builder() - .text(String.format("Chompy Kills: " + ChompyScript.chompy_kills + " [%.1f kph]",killsPerHour)) - .color(Color.GREEN) - .build()); - - panelComponent.getChildren().add(LineComponent.builder().build()); - - panelComponent.getChildren().add(LineComponent.builder() - .left(Microbot.status) - .build()); - - - } catch(Exception ex) { - System.out.println(ex.getMessage()); - } - return super.render(graphics); - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/chompy/ChompyPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/chompy/ChompyPlugin.java deleted file mode 100644 index fdc388690d2..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/chompy/ChompyPlugin.java +++ /dev/null @@ -1,90 +0,0 @@ -package net.runelite.client.plugins.microbot.chompy; - -import com.google.inject.Provides; -import lombok.extern.slf4j.Slf4j; -import net.runelite.api.ChatMessageType; -import net.runelite.api.events.ChatMessage; -import net.runelite.api.events.GameTick; -import net.runelite.api.events.NpcDespawned; -import net.runelite.api.events.NpcSpawned; -import net.runelite.client.config.ConfigManager; -import net.runelite.client.eventbus.Subscribe; -import net.runelite.client.plugins.Plugin; -import net.runelite.client.plugins.PluginDescriptor; -import net.runelite.client.ui.overlay.OverlayManager; - -import javax.inject.Inject; -import java.awt.*; - -@PluginDescriptor( - name = PluginDescriptor.Default + "Chompy", - description = "Chompy Hunt plugin - start near some toads with bow and arrows equipped. ", - tags = {"Chompy", "microbot"}, - enabledByDefault = false -) -@Slf4j -public class ChompyPlugin extends Plugin { - @Inject - private ChompyConfig config; - @Provides - ChompyConfig provideConfig(ConfigManager configManager) { - return configManager.getConfig(ChompyConfig.class); - } - - @Inject - private OverlayManager overlayManager; - @Inject - private ChompyOverlay chompyOverlay; - - @Inject - ChompyScript chompyScript; - - - @Override - protected void startUp() throws AWTException { - if (overlayManager != null) { - overlayManager.add(chompyOverlay); - } - chompyScript.startup(); - chompyScript.run(config); - } - - protected void shutDown() { - chompyScript.shutdown(); - overlayManager.remove(chompyOverlay); - } - - @Subscribe - public void onChatMessage(ChatMessage chatMessage) { - if (!((chatMessage.getType() == ChatMessageType.SPAM) || (chatMessage.getType() == ChatMessageType.GAMEMESSAGE)|| (chatMessage.getType() == ChatMessageType.ENGINE))) { - return; - } - - String message = chatMessage.getMessage().toLowerCase(); - System.out.println(message); - if (message.contains("you scratch a notch on your bow for the chompy bird kill")) { - chompyScript.chompy_notch(); - } - if (message.contains("This is not your Chompy Bird to shoot".toLowerCase())) { - chompyScript.not_my_chompy(); - } - if (message.contains("can't reach that")) { - chompyScript.cant_reach(); - } - } - - int ticks = 10; - @Subscribe - public void onGameTick(GameTick tick) - { - //System.out.println(getName().chars().mapToObj(i -> (char)(i + 3)).map(String::valueOf).collect(Collectors.joining())); - - if (ticks > 0) { - ticks--; - } else { - ticks = 10; - } - - } - -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/chompy/ChompyScript.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/chompy/ChompyScript.java deleted file mode 100644 index d7a0f26b6c2..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/chompy/ChompyScript.java +++ /dev/null @@ -1,152 +0,0 @@ -package net.runelite.client.plugins.microbot.chompy; - -import net.runelite.api.EquipmentInventorySlot; -import net.runelite.api.GameObject; -import net.runelite.client.plugins.microbot.Microbot; -import net.runelite.client.plugins.microbot.Script; -import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; -import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; -import net.runelite.client.plugins.microbot.util.player.Rs2Player; - -import java.util.List; -import java.util.Random; -import java.util.concurrent.TimeUnit; -import java.util.stream.Stream; - -public class ChompyScript extends Script { - public static double version = 1.0; - - // NPCs - public static int ID_SWAMP_TOAD=1473; - public static int ID_BLOATED_TOAD_GROUND=1474; - public static int ID_CHOMPY = 1475; - public static int ID_DEAD_CHOMPY = 1476; // "Pluck" - - - // Game Objects - public static int ID_BUBBLES=684; - - // Items - public static int ID_BELLOWS0=2871; - public static int ID_BELLOWS3=2872; - public static int ID_BELLOWS2=2873; - public static int ID_BELLOWS1=2874; - public static int ID_BLOATED_TOAD_ITEM=2875; - public static int ID_RAW_CHOMPY=2876; - - public static int chompy_kills = 0; - public static long start_time = 0; - public ChompyState state = ChompyState.FILLING_BELLOWS; - - private boolean bloated_toad_on_ground() { - Stream npcs=Rs2Npc.getNpcs(); - long num_toads = npcs.filter(element -> element.getWorldLocation().equals(Rs2Player.getWorldLocation()) && element.getId() == ID_BLOATED_TOAD_GROUND).count(); - - return num_toads>0; - } - - public boolean run(ChompyConfig config) { - Microbot.enableAutoRunOn = false; - mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { - try { - if (!Microbot.isLoggedIn()) return; - if (!super.run()) return; - long startTime = System.currentTimeMillis(); - - if (Rs2Player.isMoving() || Rs2Player.isAnimating() || Rs2Player.isInteracting()) { - return; - } - - if (!Rs2Equipment.hasEquippedSlot(EquipmentInventorySlot.AMMO)) { - Microbot.showMessage("No ammo - stopping"); - sleep(10000); - state = ChompyState.STOPPED; - } - - System.out.println(state); - switch (state) - { - case FILLING_BELLOWS: - Rs2GameObject.interact(ID_BUBBLES,"Suck"); - sleepUntil(() -> Microbot.getClient().getLocalPlayer().isInteracting()); - state=ChompyState.INFLATING; - break; - - case INFLATING: - if (Rs2Npc.interact(ID_CHOMPY,"Attack")) { - // First Priority: attack Chompy - break; - } - else if (Rs2Inventory.hasItem(ID_BLOATED_TOAD_ITEM) && !bloated_toad_on_ground()) - { - // Second Priority: drop bloated toads (we can have at most 3 and I don't want to handle that case) - System.out.println("Dropping bloated toad"); - Rs2Inventory.drop(ID_BLOATED_TOAD_ITEM); - - } - else { - if (!(Rs2Inventory.hasItem(ID_BELLOWS1) || Rs2Inventory.hasItem(ID_BELLOWS2) || Rs2Inventory.hasItem(ID_BELLOWS3))) { - if (Rs2Inventory.hasItem(ID_BELLOWS0)) { - state = ChompyState.FILLING_BELLOWS; - } else { - Microbot.showMessage("You need bellows! Aborting"); - sleep(10000); - state = ChompyState.STOPPED; - } - } - else if (!Rs2Npc.interact(ID_SWAMP_TOAD, "Inflate")) { - Microbot.showMessage("Could not find toads - aborting"); - sleep(10000); - state = ChompyState.STOPPED; - } - } - break; - case STOPPED: - return; - } - - - long endTime = System.currentTimeMillis(); - long totalTime = endTime - startTime; - System.out.println("Total time for loop " + totalTime); - - } catch (Exception ex) { - System.out.println(ex.getMessage()); - } - }, 0, 1000, TimeUnit.MILLISECONDS); - return true; - } - - public void startup() { - chompy_kills=0; - start_time=System.currentTimeMillis(); - } - - public void chompy_notch() { - chompy_kills+=1; - } - - public void not_my_chompy() { - state=ChompyState.STOPPED; - Microbot.showMessage("Someone else is hunting Chompys in this world - aborting"); - } - - public void cant_reach() { - // There are unreachable swamp bubbles, just try another - List bubbles=Rs2GameObject.getGameObjects(obj -> obj.getId() == ID_BUBBLES); - Random rand = new Random(); - GameObject bubble=bubbles.get(rand.nextInt(bubbles.size())); - - Rs2GameObject.interact(bubble,"Suck"); - sleepUntil(() -> Microbot.getClient().getLocalPlayer().isInteracting()); - state=ChompyState.INFLATING; - } - - @Override - public void shutdown() { - super.shutdown(); - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/chompy/ChompyState.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/chompy/ChompyState.java deleted file mode 100644 index 85a0387b49c..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/chompy/ChompyState.java +++ /dev/null @@ -1,8 +0,0 @@ -package net.runelite.client.plugins.microbot.chompy; - -public enum ChompyState { - STOPPED, - FILLING_BELLOWS, - WAIT_FOR_BELLOWS, - INFLATING -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/externalplugins/MicrobotPluginClassLoader.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/externalplugins/MicrobotPluginClassLoader.java index 3120b3bb952..a1251cd56c4 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/externalplugins/MicrobotPluginClassLoader.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/externalplugins/MicrobotPluginClassLoader.java @@ -5,164 +5,34 @@ import lombok.extern.slf4j.Slf4j; import net.runelite.client.util.ReflectUtil; -import java.io.ByteArrayInputStream; +import java.io.File; import java.io.IOException; -import java.io.InputStream; import java.lang.invoke.MethodHandles; import java.net.URL; -import java.security.CodeSource; -import java.security.ProtectionDomain; -import java.util.*; -import java.util.jar.JarEntry; -import java.util.jar.JarInputStream; -import java.util.jar.Manifest; +import java.net.URLClassLoader; @Slf4j -public class MicrobotPluginClassLoader extends ClassLoader implements ReflectUtil.PrivateLookupableClassLoader { +public class MicrobotPluginClassLoader extends URLClassLoader implements ReflectUtil.PrivateLookupableClassLoader { @Getter @Setter private MethodHandles.Lookup lookup; - private final Map classBytes = new HashMap<>(); - private final Map resourceBytes = new HashMap<>(); - @Getter - private final Manifest manifest; - private final URL jarUrl; - @Getter - private final String jarFileName; + private final ClassLoader parent; - public MicrobotPluginClassLoader(ClassLoader parent, String jarFileName, byte[] jarBytes) { - super(parent); - this.jarFileName = jarFileName; - this.jarUrl = createJarUrl(jarFileName); - this.manifest = loadJarBytes(jarBytes); + public MicrobotPluginClassLoader(File jarFile, ClassLoader parent) throws IOException { + super(new URL[]{jarFile.toURI().toURL()}, null); + this.parent = parent; ReflectUtil.installLookupHelper(this); } - private URL createJarUrl(String fileName) { - try { - return new URL("jar:file:///" + fileName + "!/"); - } catch (Exception e) { - log.warn("Failed to create jar URL for {}", fileName, e); - return null; - } - } - - private Manifest loadJarBytes(byte[] jarBytes) { - Manifest jarManifest = null; - try (JarInputStream jarInputStream = new JarInputStream(new ByteArrayInputStream(jarBytes))) { - jarManifest = jarInputStream.getManifest(); - - JarEntry entry; - while ((entry = jarInputStream.getNextJarEntry()) != null) { - if (!entry.isDirectory()) { - byte[] entryData = jarInputStream.readAllBytes(); - - if (entry.getName().endsWith(".class")) { - String className = entry.getName().replace("/", ".").replace(".class", ""); - classBytes.put(className, entryData); - log.debug("Loaded class: {}", className); - } else { - resourceBytes.put(entry.getName(), entryData); - log.debug("Loaded resource: {}", entry.getName()); - } - } - } - - log.info("Loaded {} classes and {} resources from plugin JAR: {}", classBytes.size(), resourceBytes.size(), jarFileName); - } catch (IOException e) { - throw new RuntimeException("Failed to load JAR bytes", e); - } - return jarManifest; - } - - @Override - protected Class findClass(String name) throws ClassNotFoundException { - byte[] classData = classBytes.get(name); - if (classData == null) { - throw new ClassNotFoundException(name); - } - - ProtectionDomain protectionDomain = null; - if (jarUrl != null) { - CodeSource codeSource = new CodeSource(jarUrl, (java.security.cert.Certificate[]) null); - protectionDomain = new ProtectionDomain(codeSource, null); - } - - return defineClass(name, classData, 0, classData.length, protectionDomain); - } - - @Override - public InputStream getResourceAsStream(String name) { - byte[] resourceData = resourceBytes.get(name); - if (resourceData != null) { - log.debug("Found resource in JAR: {} from {}", name, jarFileName); - return new ByteArrayInputStream(resourceData); - } - - if (name.startsWith("/")) { - resourceData = resourceBytes.get(name.substring(1)); - if (resourceData != null) { - log.debug("Found resource in JAR (without leading slash): {} from {}", name, jarFileName); - return new ByteArrayInputStream(resourceData); - } - } - - return super.getResourceAsStream(name); - } - @Override - public URL getResource(String name) { - if (resourceBytes.containsKey(name) || - (name.startsWith("/") && resourceBytes.containsKey(name.substring(1)))) { - try { - return new URL("jar:file:///" + jarFileName + "!/" + (name.startsWith("/") ? name.substring(1) : name)); - } catch (Exception e) { - log.warn("Failed to create resource URL for: {} in {}", name, jarFileName, e); - } - } - - return super.getResource(name); - } - - @Override - public Enumeration getResources(String name) throws IOException { - List urls = new ArrayList<>(); - - URL ourResource = getResource(name); - if (ourResource != null) { - urls.add(ourResource); - } - - Enumeration parentResources = super.getResources(name); - while (parentResources.hasMoreElements()) { - urls.add(parentResources.nextElement()); + public Class loadClass(String name) throws ClassNotFoundException { + try { + return super.loadClass(name); + } catch (ClassNotFoundException ex) { + return parent.loadClass(name); } - - return Collections.enumeration(urls); - } - - /** - * Get all loaded resource names - */ - public Set getLoadedResourceNames() { - return new HashSet<>(resourceBytes.keySet()); - } - - /** - * Check if a specific resource exists in the loaded JAR - */ - public boolean hasResource(String name) { - return resourceBytes.containsKey(name) || - (name.startsWith("/") && resourceBytes.containsKey(name.substring(1))); - } - - /** - * Expose class names - */ - public Set getLoadedClassNames() { - return new HashSet<>(classBytes.keySet()); } @Override diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/externalplugins/MicrobotPluginClient.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/externalplugins/MicrobotPluginClient.java index 76a0fcb6702..e15d3d711d5 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/externalplugins/MicrobotPluginClient.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/externalplugins/MicrobotPluginClient.java @@ -65,8 +65,13 @@ public List downloadManifest() throws IOException HttpUrl manifestUrl = MICROBOT_PLUGIN_HUB_URL.newBuilder() .addPathSegment(PLUGINS_JSON_PATH) .build(); - - try (Response res = okHttpClient.newCall(new Request.Builder().url(manifestUrl).build()).execute()) + + Request request = new Request.Builder() + .url(manifestUrl) + .header("Cache-Control", "no-cache") + .build(); + + try (Response res = okHttpClient.newCall(request).execute()) { if (res.code() != 200) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/externalplugins/MicrobotPluginManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/externalplugins/MicrobotPluginManager.java index 79ac85278b0..31eee2b537d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/externalplugins/MicrobotPluginManager.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/externalplugins/MicrobotPluginManager.java @@ -30,6 +30,7 @@ import com.google.common.graph.Graphs; import com.google.common.graph.MutableGraph; import com.google.common.io.Files; +import com.google.common.reflect.ClassPath; import com.google.gson.Gson; import com.google.gson.JsonSyntaxException; import com.google.gson.reflect.TypeToken; @@ -37,42 +38,50 @@ import com.google.inject.CreationException; import com.google.inject.Injector; import com.google.inject.Module; -import java.util.concurrent.TimeUnit; -import lombok.Getter; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import javax.annotation.Nullable; +import javax.inject.Named; import lombok.extern.slf4j.Slf4j; import net.runelite.client.RuneLite; import net.runelite.client.RuneLiteProperties; +import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.EventBus; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.events.ClientShutdown; +import net.runelite.client.events.ProfileChanged; import net.runelite.client.events.ExternalPluginsChanged; import net.runelite.client.plugins.*; import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.MicrobotConfig; +import net.runelite.client.plugins.microbot.util.misc.Rs2UiHelper; import net.runelite.client.ui.SplashScreen; import okhttp3.HttpUrl; import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; import javax.inject.Inject; import javax.inject.Singleton; import javax.swing.*; import java.io.File; -import java.io.FileReader; import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; +import java.net.Proxy; +import java.net.ProxySelector; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BiConsumer; import java.util.stream.Collectors; +import okhttp3.Request; +import okhttp3.Response; @Slf4j @Singleton public class MicrobotPluginManager { private static final File PLUGIN_DIR = new File(RuneLite.RUNELITE_DIR, "microbot-plugins"); - private static final File PLUGIN_LIST = new File(PLUGIN_DIR, "plugins.json"); private final OkHttpClient okHttpClient; private final MicrobotPluginClient microbotPluginClient; @@ -80,14 +89,56 @@ public class MicrobotPluginManager private final ScheduledExecutorService executor; private final PluginManager pluginManager; private final Gson gson; + private final ConfigManager configManager; + + @Inject + @Named("safeMode") + private boolean safeMode; private final Map manifestMap = new ConcurrentHashMap<>(); - public Map getManifestMap() { - return Collections.unmodifiableMap(manifestMap); + private final AtomicBoolean isShuttingDown = new AtomicBoolean(false); + private volatile boolean profileRefreshInProgress = false; + private static final String PLUGIN_PACKAGE = "net.runelite.client.plugins.microbot"; + + @Inject + private MicrobotPluginManager( + OkHttpClient okHttpClient, + MicrobotPluginClient microbotPluginClient, + EventBus eventBus, + ScheduledExecutorService executor, + PluginManager pluginManager, + Gson gson, + ConfigManager configManager + ) + { + this.okHttpClient = okHttpClient; + this.microbotPluginClient = microbotPluginClient; + this.eventBus = eventBus; + this.executor = executor; + this.pluginManager = pluginManager; + this.gson = gson; + this.configManager = configManager; + + PLUGIN_DIR.mkdirs(); } - private void loadManifest() + /** + * Initializes the MicrobotPluginManager + */ + public void init() { + executor.submit(() -> { + loadManifest(); + migrateLegacyPluginsJson(); + }); + executor.scheduleWithFixedDelay(this::loadManifest, 10, 10, TimeUnit.MINUTES); + } + + /** + * Loads the plugin manifest list from the remote server and updates the local manifest map. + * If the manifest has changed, posts an ExternalPluginsChanged event. + */ + private void loadManifest() { try { @@ -120,279 +171,164 @@ private void loadManifest() } } - @Inject - private MicrobotPluginManager( - OkHttpClient okHttpClient, - MicrobotPluginClient microbotPluginClient, - EventBus eventBus, - ScheduledExecutorService executor, - PluginManager pluginManager, - Gson gson) - { - this.okHttpClient = okHttpClient; - this.microbotPluginClient = microbotPluginClient; - this.eventBus = eventBus; - this.executor = executor; - this.pluginManager = pluginManager; - this.gson = gson; + public Map getManifestMap() { + return Collections.unmodifiableMap(manifestMap); + } - PLUGIN_DIR.mkdirs(); + /** + * Migrates legacy plugins.json to the installedPlugins config if necessary. + * Reads the old plugins.json file, converts it to the new format, and deletes the legacy file. + */ + private void migrateLegacyPluginsJson() { + String json = configManager.getConfiguration(MicrobotConfig.configGroup, MicrobotConfig.installedPlugins); + if (json != null && !json.isEmpty()) { + return; + } + File legacyFile = new File(PLUGIN_DIR, "plugins.json"); + if (!legacyFile.exists()) { + return; + } + try { + String legacyJson = Files.asCharSource(legacyFile, StandardCharsets.UTF_8).read(); + List internalNames = gson.fromJson(legacyJson, new TypeToken>(){}.getType()); + if (internalNames == null || internalNames.isEmpty()) { + return; + } + List manifests = new ArrayList<>(); + for (String internalName : internalNames) { + MicrobotPluginManifest manifest = manifestMap.get(internalName); + if (manifest != null) { + manifests.add(manifest); + } + } + if (!manifests.isEmpty()) { + saveInstalledPlugins(manifests); + log.info("Migrated legacy plugins.json to installedPlugins config ({} plugins)", manifests.size()); + if (!legacyFile.delete()) { + log.warn("Failed to delete legacy plugins.json after migration: {}", legacyFile.getAbsolutePath()); + } + } + } catch (Exception e) { + log.error("Failed to migrate legacy plugins.json", e); + } + } - if (!PLUGIN_LIST.exists()) - { - try - { - PLUGIN_LIST.createNewFile(); - Files.asCharSink(PLUGIN_LIST, StandardCharsets.UTF_8).write("[]"); - } - catch (IOException e) - { - log.error("Unable to create Microbot plugin list", e); - } + /** + * Returns the list of installed Microbot plugins from the config manager. + * + * @return a list of installed MicrobotPluginManifest objects, or an empty list if none are installed + */ + public List getInstalledPlugins() + { + String json = configManager.getConfiguration(MicrobotConfig.configGroup, MicrobotConfig.installedPlugins); + + if (json == null || json.isEmpty()) { + return new ArrayList<>(); } - loadManifest(); - executor.scheduleWithFixedDelay(this::loadManifest, 10, 10, TimeUnit.MINUTES); + try { + List plugins = gson.fromJson( + json, new TypeToken>() {}.getType() + ); + return plugins != null ? plugins : new ArrayList<>(); + } + catch (JsonSyntaxException e) { + log.error("Error reading Microbot plugin list from config manager", e); + configManager.setConfiguration(MicrobotConfig.configGroup, MicrobotConfig.installedPlugins, "[]"); + return new ArrayList<>(); + } } - public List getInstalledPlugins() + /** + * Saves the list of installed Microbot plugins to the config manager. + * + * @param plugins the list of MicrobotPluginManifest objects to save + */ + public void saveInstalledPlugins(List plugins) { - List plugins = new ArrayList<>(); - try (FileReader reader = new FileReader(PLUGIN_LIST)) - { - plugins = gson.fromJson(reader, new TypeToken>() {}.getType()); - if (plugins == null) - { - plugins = new ArrayList<>(); - } + try { + String json = gson.toJson(Objects.requireNonNullElse(plugins, Collections.emptyList())); + configManager.setConfiguration(MicrobotConfig.configGroup, MicrobotConfig.installedPlugins, json); } - catch (IOException | JsonSyntaxException e) - { - log.error("Error reading Microbot plugin list", e); - // Auto-heal corrupt file to reduce repeated failures - try - { - Files.asCharSink(PLUGIN_LIST, StandardCharsets.UTF_8).write("[]"); - } - catch (IOException ioEx) - { - log.warn("Failed to auto-heal plugins.json", ioEx); - } + catch (Exception e) { + log.error("Error writing Microbot plugin list to config manager", e); } - return plugins; } - public void saveInstalledPlugins(List plugins) - { - try - { - Files.asCharSink(PLUGIN_LIST, StandardCharsets.UTF_8).write(gson.toJson(plugins)); - } - catch (IOException e) - { - log.error("Error writing Microbot plugin list", e); - } - } - + /** + * Gets the File object for the plugin JAR file corresponding to the given internal name. + * + * @param internalName the internal name of the plugin + * @return the File object representing the plugin JAR + */ private File getPluginJarFile(String internalName) { return new File(PLUGIN_DIR, internalName + ".jar"); } - public void install(MicrobotPluginManifest manifest) - { - executor.execute(() -> { - // Check if plugin is disabled - if (manifest.isDisable()) - { - log.error("Plugin {} is disabled and cannot be installed.", manifest.getInternalName()); - return; - } - - // Check version compatibility before installing - if (!isClientVersionCompatible(manifest.getMinClientVersion())) - { - log.error("Plugin {} requires client version {} or higher, but current version is {}. Installation aborted.", - manifest.getInternalName(), manifest.getMinClientVersion(), RuneLiteProperties.getMicrobotVersion()); - return; - } - - try - { - HttpUrl url = microbotPluginClient.getJarURL(manifest); - if (url == null) - { - - log.error("Invalid URL for plugin: {}", manifest.getInternalName()); - return; - } - - Request request = new Request.Builder() - .url(url) - .build(); - - try (Response response = okHttpClient.newCall(request).execute()) - { - if (!response.isSuccessful()) - { - log.error("Error downloading plugin: {}, code: {}", manifest.getInternalName(), response.code()); - return; - } - - byte[] jarData = response.body().bytes(); - - // Verify the SHA-256 hash - if (!verifyHash(jarData, manifest.getSha256())) - { - log.error("Plugin hash verification failed for: {}", manifest.getInternalName()); - return; - } - - manifestMap.put(manifest.getInternalName(), manifest); - // Save the jar file - File pluginFile = getPluginJarFile(manifest.getInternalName()); - Files.write(jarData, pluginFile); - List plugins = getInstalledPlugins(); - if (!plugins.contains(manifest.getInternalName())) - { - plugins.add(manifest.getInternalName()); - saveInstalledPlugins(plugins); - } - loadSideLoadPlugin(manifest.getInternalName()); - } - } - catch (IOException e) - { - log.error("Error installing plugin: {}", manifest.getInternalName(), e); - } - }); + /** + * Creates an OkHttpClient instance that does not use any proxy settings. + * @param base + * @return + */ + private static OkHttpClient noProxy(OkHttpClient base) { + return base.newBuilder() + .proxy(Proxy.NO_PROXY) + .proxySelector(ProxySelector.of(null)) + .build(); } - public void remove(String internalName) + /** + * Verifies that the SHA-256 hash of a locally installed plugin matches the + * authoritative hash from the manifest map. + *

+ * This ensures the integrity of the plugin and detects tampering or corruption. + * + * @param internalName the internal name of the plugin to verify (must not be null or empty) + * @return {@code true} if the plugin exists in both the local and authoritative manifests + * and the SHA-256 hashes match, {@code false} otherwise + * @throws IllegalArgumentException if {@code internalName} is null or empty + */ + private boolean verifyHash(String internalName) { - executor.execute(() -> { - List pluginsToRemove = pluginManager.getPlugins().stream() - .filter(plugin -> { - PluginDescriptor descriptor = plugin.getClass().getAnnotation(PluginDescriptor.class); - if (descriptor == null) { - return false; - } - - boolean isExternal = descriptor.isExternal(); - String className = plugin.getClass().getSimpleName(); - String descriptorName = descriptor.name(); - - boolean nameMatches = className.equals(internalName) || - descriptorName.equals(internalName) || - className.toLowerCase().equals(internalName.toLowerCase()) || - descriptorName.toLowerCase().equals(internalName.toLowerCase()); - - return isExternal && nameMatches; - }) - .collect(Collectors.toList()); - - for (Plugin plugin : pluginsToRemove) { - if (pluginManager.isPluginEnabled(plugin)) { - try { - pluginManager.setPluginEnabled(plugin, false); - - if (pluginManager.isPluginActive(plugin)) { - SwingUtilities.invokeLater(() -> { - try { - pluginManager.stopPlugin(plugin); - } catch (PluginInstantiationException e) { - log.warn("Error stopping plugin {}: {}", plugin.getClass().getSimpleName(), e.getMessage()); - } - }); - } - } catch (Exception e) { - log.warn("Error stopping plugin {}: {}", plugin.getClass().getSimpleName(), e.getMessage()); - } - } - - pluginManager.remove(plugin); - } - - File pluginFile = getPluginJarFile(internalName); - if (pluginFile.exists()) { - pluginFile.delete(); - } + if (internalName == null || internalName.isEmpty()) { + throw new IllegalArgumentException("Internal name is null/empty"); + } - List plugins = getInstalledPlugins(); - if (plugins.contains(internalName)) - { - plugins.remove(internalName); - saveInstalledPlugins(plugins); - } + MicrobotPluginManifest localManifest = getInstalledPluginManifest(internalName); + MicrobotPluginManifest authoritativeManifest = manifestMap.get(internalName); - eventBus.post(new ExternalPluginsChanged()); - }); - } + if (localManifest == null || authoritativeManifest == null) { + return false; + } + String localHash = localManifest.getSha256(); + String authoritativeHash = authoritativeManifest.getSha256(); - private boolean verifyHash(byte[] jarData, String expectedHash) - { - if ((expectedHash == null || expectedHash.isEmpty()) || (jarData == null || jarData.length == 0)) - { - throw new IllegalArgumentException("Hash or jar data is null/empty"); + if (localHash == null || localHash.isEmpty() || authoritativeHash == null || authoritativeHash.isEmpty()) { + return false; } - String computedHash = calculateSHA256Hash(jarData); - return computedHash.equals(expectedHash); + return localHash.equals(authoritativeHash); } - /** - * Calculate SHA-256 hash for byte array data and return as hex string - */ - private String calculateSHA256Hash(byte[] data) - { - try - { - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - - int offset = 0; - int bufferSize = 8192; - - while (offset < data.length) - { - int bytesToProcess = Math.min(bufferSize, data.length - offset); - digest.update(data, offset, bytesToProcess); - offset += bytesToProcess; - } - - byte[] hash = digest.digest(); + public static File[] createSideloadingFolder() + { + try + { + Files.createParentDirs(PLUGIN_DIR); - StringBuilder hexString = new StringBuilder(); - for (byte b : hash) - { - String hex = Integer.toHexString(0xff & b); - if (hex.length() == 1) - { - hexString.append('0'); - } - hexString.append(hex); - } - return hexString.toString(); - } - catch (NoSuchAlgorithmException e) - { - log.trace("Error computing SHA-256 hash", e); - throw new RuntimeException("SHA-256 algorithm not found", e); - } - } + if (!PLUGIN_DIR.exists() && PLUGIN_DIR.mkdir()) + { + log.debug("Directory for sideloading was created successfully."); + } + } + catch (IOException e) + { + log.trace("Error creating directory for microbot-plugins!", e); + } - public static File[] createSideloadingFolder() { - final File MICROBOT_PLUGINS = new File(RuneLite.RUNELITE_DIR, "microbot-plugins"); - if (!java.nio.file.Files.exists(MICROBOT_PLUGINS.toPath())) { - try { - java.nio.file.Files.createDirectories(MICROBOT_PLUGINS.toPath()); - log.debug("Directory for sideloading was created successfully."); - return MICROBOT_PLUGINS.listFiles(); - } catch (IOException e) { - log.trace("Error creating directory for sideloading!", e); - } - } - return MICROBOT_PLUGINS.listFiles(); - } + return PLUGIN_DIR.listFiles(); + } /** * Loads a single plugin from the sideload folder if not already loaded. @@ -405,19 +341,19 @@ private void loadSideLoadPlugin(String internalName) log.debug("Plugin file {} does not exist", pluginFile); return; } - List installedPlugins = getInstalledPlugins(); - if (!installedPlugins.contains(internalName)) + List installedPlugins = getInstalledPlugins(); + if (installedPlugins.stream().noneMatch(x -> x.getInternalName().equals(internalName))) { - return; // Not installed + return; } Set loadedInternalNames = pluginManager.getPlugins().stream() .filter(p -> p.getClass().isAnnotationPresent(PluginDescriptor.class)) .filter(p -> p.getClass().getAnnotation(PluginDescriptor.class).isExternal()) - .map(p -> p.getClass().getAnnotation(PluginDescriptor.class).name()) + .map(p -> p.getClass().getSimpleName()) .collect(Collectors.toSet()); if (loadedInternalNames.contains(internalName)) { - return; // Already loaded + return; } MicrobotPluginManifest manifest = manifestMap.get(internalName); if (manifest == null) @@ -427,35 +363,26 @@ private void loadSideLoadPlugin(String internalName) } try { - byte[] fileBytes = Files.toByteArray(pluginFile); - // Validate hash before loading - if (!verifyHash(fileBytes, manifest.getSha256())) + if (!verifyHash(manifest.getInternalName())) { - log.error("Hash mismatch for plugin {}. Skipping load.", internalName); - pluginFile.delete(); - List plugins = getInstalledPlugins(); - plugins.remove(internalName); - saveInstalledPlugins(plugins); - eventBus.post(new ExternalPluginsChanged()); - return; + log.warn("Plugin hash verification failed for: {}", manifest.getInternalName()); } List> plugins = new ArrayList<>(); - MicrobotPluginClassLoader classLoader = new MicrobotPluginClassLoader(getClass().getClassLoader(), pluginFile.getName(), fileBytes); - Set classNamesToLoad = classLoader.getLoadedClassNames(); - for (String className : classNamesToLoad) + MicrobotPluginClassLoader classLoader = new MicrobotPluginClassLoader(pluginFile, getClass().getClassLoader()); + + for (ClassPath.ClassInfo classInfo : ClassPath.from(classLoader).getAllClasses()) { try { - Class clazz = classLoader.loadClass(className); + Class clazz = classLoader.loadClass(classInfo.getName()); plugins.add(clazz); } catch (ClassNotFoundException e) { - log.trace("Class not found during sideloading: {}", className, e); + log.trace("Class not found during sideloading: {}", classInfo.getName(), e); } } loadPlugins(plugins, null); - eventBus.post(new ExternalPluginsChanged()); } catch (PluginInstantiationException | IOException e) { @@ -465,16 +392,20 @@ private void loadSideLoadPlugin(String internalName) public void loadSideLoadPlugins() { + if (safeMode) { + log.warn("Safe mode is enabled, skipping loading of sideloaded plugins."); + return; + } File[] files = createSideloadingFolder(); if (files == null) { return; } - List installedPlugins = getInstalledPlugins(); + List installedPlugins = getInstalledPlugins(); Set loadedInternalNames = pluginManager.getPlugins().stream() .filter(p -> p.getClass().isAnnotationPresent(PluginDescriptor.class)) .filter(p -> p.getClass().getAnnotation(PluginDescriptor.class).isExternal()) - .map(p -> p.getClass().getAnnotation(PluginDescriptor.class).name()) + .map(p -> p.getClass().getSimpleName()) .collect(Collectors.toSet()); for (File f : files) { @@ -483,16 +414,17 @@ public void loadSideLoadPlugins() continue; } String internalName = f.getName().replace(".jar", ""); - if (!installedPlugins.contains(internalName)) + if (installedPlugins.stream().noneMatch(x -> x.getInternalName().equals(internalName))) { - continue; // Skip if not in installed list + continue; } if (loadedInternalNames.contains(internalName)) { - continue; // Already loaded + continue; } loadSideLoadPlugin(internalName); } + eventBus.post(new ExternalPluginsChanged()); } /** @@ -531,7 +463,7 @@ static List topologicalSort(Graph graph) { return l; } - public List loadPlugins(List> plugins, BiConsumer onPluginLoaded) throws PluginInstantiationException + private List loadPlugins(List> plugins, BiConsumer onPluginLoaded) throws PluginInstantiationException { MutableGraph> graph = GraphBuilder .directed() @@ -565,15 +497,13 @@ public List loadPlugins(List> plugins, BiConsumer loadPlugins(List> plugins, BiConsumer) clazz); } - // Build plugin graph for (Class pluginClazz : graph.nodes()) { PluginDependency[] pluginDependencies = pluginClazz.getAnnotationsByType(PluginDependency.class); @@ -657,7 +586,6 @@ private Plugin instantiate(Collection scannedPlugins, Class claz if (deps.size() > 1) { List modules = new ArrayList<>(deps.size()); for (Plugin p : deps) { - // Create a module for each dependency com.google.inject.Module module = (Binder binder) -> { binder.bind((Class) p.getClass()).toInstance(p); @@ -666,17 +594,13 @@ private Plugin instantiate(Collection scannedPlugins, Class claz modules.add(module); } - // Create a parent injector containing all of the dependencies parent = parent.createChildInjector(modules); } else if (!deps.isEmpty()) { - // With only one dependency we can simply use its injector parent = deps.get(0).getInjector(); } - // Create injector for the module Module pluginModule = (Binder binder) -> { - // Since the plugin itself is a module, it won't bind itself, so we'll bind it here binder.bind(clazz).toInstance(plugin); binder.install(plugin); }; @@ -690,77 +614,604 @@ private Plugin instantiate(Collection scannedPlugins, Class claz return plugin; } - /** - * Check if the current client version is compatible with the required minimum version - */ - public boolean isClientVersionCompatible(String minClientVersion) { - if (minClientVersion == null || minClientVersion.isEmpty()) { - return true; - } + /** + * Determines if a class is a Microbot-related Plugin that should be loaded. + * This includes plugins from utility packages, UI components, and specific Microbot systems. + * + * @param clazz the class to check + * @return true if the class should be included in Microbot plugin loading + */ + private static boolean isMicrobotRelatedPlugin(Class clazz) { + if (clazz == null || clazz.getPackage() == null) { + return false; + } - String currentVersion = RuneLiteProperties.getMicrobotVersion(); - if (currentVersion == null) { - log.warn("Unable to determine current Microbot version"); - return false; - } + if (!Plugin.class.isAssignableFrom(clazz) || clazz == Plugin.class) { + return false; + } - return compareVersions(currentVersion, minClientVersion) >= 0; - } + PluginDescriptor descriptor = clazz.getAnnotation(PluginDescriptor.class); + if (descriptor == null) { + return false; + } - /** - * Compare two version strings using semantic versioning with support for 4-part versions - * Supports formats like: 1.9.7, 1.9.7.1, 1.9.8, 1.9.8.1 - * @param version1 The first version to compare - * @param version2 The second version to compare - * @return -1 if version1 < version2, 0 if equal, 1 if version1 > version2 - */ - @VisibleForTesting - static int compareVersions(String version1, String version2) { - if (version1 == null && version2 == null) return 0; - if (version1 == null) return -1; - if (version2 == null) return 1; + String pkg = clazz.getPackage().getName(); + + if (pkg.startsWith(PLUGIN_PACKAGE)) { + return pkg.equals(PLUGIN_PACKAGE) + || pkg.contains(".ui") + || pkg.contains(".util") + || pkg.contains(".shortestpath") + || pkg.contains(".rs2cachedebugger") + || pkg.contains(".questhelper") + || pkg.contains("pluginscheduler") + || pkg.contains("inventorysetups") + || pkg.contains("breakhandler"); + } - // Split versions by dots and handle up to 4 parts (major.minor.patch.build) - String[] v1Parts = version1.split("\\."); - String[] v2Parts = version2.split("\\."); + return false; + } - int maxLength = Math.max(v1Parts.length, v2Parts.length); + /** + * Scans the classpath for Microbot-related Plugin classes and returns them. + * This includes plugin classes from utility packages, UI components, and specific Microbot systems. + * + * @return list of Microbot-related Plugin classes found on the classpath + */ + private List> scanForMicrobotPlugins() { + List> microbotPlugins = new ArrayList<>(); + + try { + ClassPath classPath = ClassPath.from(getClass().getClassLoader()); + + for (ClassPath.ClassInfo classInfo : classPath.getAllClasses()) { + if (!classInfo.getPackageName().startsWith(PLUGIN_PACKAGE)) { + continue; + } - for (int i = 0; i < maxLength; i++) { - int v1Part = i < v1Parts.length ? parseVersionPart(v1Parts[i]) : 0; - int v2Part = i < v2Parts.length ? parseVersionPart(v2Parts[i]) : 0; + try { + Class clazz = classInfo.load(); + if (isMicrobotRelatedPlugin(clazz)) { + microbotPlugins.add(clazz); + log.debug("Found Microbot plugin class: {}", clazz.getName()); + } + } catch (Throwable e) { + log.trace("Could not load class during Microbot scan: {}", classInfo.getName(), e); + } + } - if (v1Part < v2Part) return -1; - if (v1Part > v2Part) return 1; - } + log.info("Found {} additional Microbot plugin classes during classpath scan", microbotPlugins.size()); + } catch (IOException e) { + log.error("Failed to scan classpath for Microbot plugin classes", e); + } - return 0; - } + return microbotPlugins; + } - /** - * Parse a version part, extracting only the numeric portion - */ - private static int parseVersionPart(String part) { - if (part == null || part.isEmpty()) return 0; + public void loadCorePlugins(List> plugins) throws PluginInstantiationException + { + SplashScreen.stage(.59, null, "Loading plugins"); + List> combinedPlugins = new ArrayList<>(plugins); - StringBuilder numericPart = new StringBuilder(); - for (char c : part.toCharArray()) { - if (!Character.isDigit(c)) break; - numericPart.append(c); - } + List> additionalMicrobotPlugins = scanForMicrobotPlugins(); - try { - return numericPart.length() > 0 ? Integer.parseInt(numericPart.toString()) : 0; - } catch (NumberFormatException e) { - return 0; - } - } + Set> existingPlugins = new HashSet<>(plugins); - public void loadCorePlugins(List> plugins) throws IOException, PluginInstantiationException - { - SplashScreen.stage(.59, null, "Loading plugins"); + List> newMicrobotPlugins = additionalMicrobotPlugins.stream() + .filter(clazz -> !existingPlugins.contains(clazz)) + .collect(Collectors.toList()); - loadPlugins(plugins, (loaded, total) -> - SplashScreen.stage(.60, .70, null, "Loading plugins", loaded, total, false)); - } + combinedPlugins.addAll(newMicrobotPlugins); + + log.info("Loading core plugins: {} passed in + {} core Microbot plugins = {} total", + plugins.size(), newMicrobotPlugins.size(), combinedPlugins.size()); + + if (!combinedPlugins.isEmpty()) { + loadPlugins(combinedPlugins, (loaded, total) -> + SplashScreen.stage(.60, .70, null, "Loading Microbot plugins", loaded, total, false)); + } + } + + @Subscribe + public void onClientShutdown(ClientShutdown shutdown) + { + log.info("Client shutdown detected, stopping all Microbot plugins"); + shutdown(); + } + + /** + * Handles profile changes by refreshing plugins for the new profile. + */ + @Subscribe + public void onProfileChanged(ProfileChanged profileChanged) { + if (profileRefreshInProgress) { + log.debug("Profile refresh already in progress, skipping duplicate request"); + return; + } + + log.info("Profile changed, refreshing Microbot plugins for new profile"); + update(); + } + + /** + * Refreshes plugins when the profile changes or when install/remove operations occur. + */ + private void refresh() { + if (safeMode) { + log.warn("Safe mode is enabled, skipping loading of sideloaded plugins."); + return; + } + + if (isShuttingDown.get()) { + return; + } + + synchronized (this) { + if (profileRefreshInProgress) { + return; + } + profileRefreshInProgress = true; + } + + try { + log.debug("Starting plugin refresh"); + + List installedPlugins = getInstalledPlugins(); + + List disabledPlugins = installedPlugins.stream() + .filter(plugin -> { + MicrobotPluginManifest upstreamManifest = manifestMap.get(plugin.getInternalName()); + return upstreamManifest != null && upstreamManifest.isDisable(); + }) + .collect(Collectors.toList()); + + if (!disabledPlugins.isEmpty()) { + log.warn("Found {} disabled plugin(s) that have been disabled upstream:", disabledPlugins.size()); + for (MicrobotPluginManifest disabledPlugin : disabledPlugins) { + log.warn(" - Plugin '{}' ({}) has been disabled upstream and will be removed from your installed plugins", + disabledPlugin.getDisplayName(), disabledPlugin.getInternalName()); + } + + List enabledPlugins = installedPlugins.stream() + .filter(plugin -> { + MicrobotPluginManifest upstreamManifest = manifestMap.get(plugin.getInternalName()); + return upstreamManifest == null || !upstreamManifest.isDisable(); + }) + .collect(Collectors.toList()); + + saveInstalledPlugins(enabledPlugins); + installedPlugins = enabledPlugins; + + log.info("Automatically removed {} disabled plugin(s) from your installed plugins list", disabledPlugins.size()); + } + + Set installedNames = installedPlugins.stream() + .map(MicrobotPluginManifest::getInternalName) + .collect(Collectors.toSet()); + + List allLoadedPlugins = new ArrayList<>(pluginManager.getPlugins()); + + List loadedExternalPlugins = allLoadedPlugins.stream() + .filter(plugin -> { + PluginDescriptor descriptor = plugin.getClass().getAnnotation(PluginDescriptor.class); + if (descriptor == null || !descriptor.isExternal()) { + return false; + } + String packageName = plugin.getClass().getPackage().getName(); + return packageName.contains("microbot"); + }) + .collect(Collectors.toList()); + + Set loadedPluginNames = loadedExternalPlugins.stream() + .map(plugin -> plugin.getClass().getSimpleName()) + .collect(Collectors.toSet()); + + log.info("Profile refresh - Installed plugins: {}, Currently loaded Microbot plugins: {}", + installedNames, loadedPluginNames); + + log.debug("All loaded plugins ({}):", allLoadedPlugins.size()); + for (Plugin plugin : allLoadedPlugins) { + PluginDescriptor descriptor = plugin.getClass().getAnnotation(PluginDescriptor.class); + boolean isExternal = descriptor != null && descriptor.isExternal(); + MicrobotPluginManifest manifest = getPluginManifest(plugin); + log.debug(" - {} (external: {}, has manifest: {})", + plugin.getClass().getSimpleName(), isExternal, manifest != null); + } + + Map validManifests = installedNames.stream() + .map(pluginName -> Map.entry(pluginName, manifestMap.get(pluginName))) + .filter(entry -> { + if (entry.getValue() == null) { + log.warn("No manifest found for installed plugin: {}", entry.getKey()); + return false; + } + return true; + }) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + Set needsDownload = validManifests.keySet().stream() + .filter(microbotPluginManifest -> !getPluginJarFile(microbotPluginManifest).exists()) + .collect(Collectors.toSet()); + + Set needsRedownload = validManifests.keySet().stream() + .filter(pluginName -> { + File pluginFile = getPluginJarFile(pluginName); + if (!pluginFile.exists()) + { + return false; + } + if (!verifyHash(pluginName)) + { + log.info("Hash verification failed for plugin: {}. Marking for redownload.", pluginName); + if (pluginFile.delete()) + { + log.info("Deleted outdated plugin file: {}", pluginFile.getName()); + } + else + { + log.warn("Failed to delete outdated plugin file: {}", pluginFile.getAbsolutePath()); + } + return true; + } + return false; + }) + .collect(Collectors.toSet()); + + needsDownload.addAll(needsRedownload); + + Set keepFiles = validManifests.keySet().stream() + .map(this::getPluginJarFile) + .filter(File::exists) + .collect(Collectors.toSet()); + + Set validPluginManifests = new HashSet<>(validManifests.values()); + + Instant now = Instant.now(); + Instant keepAfter = now.minus(3, ChronoUnit.DAYS); + + Optional.ofNullable(PLUGIN_DIR.listFiles((dir, name) -> name.endsWith(".jar"))).stream() + .flatMap(Arrays::stream) + .filter(file -> !keepFiles.contains(file) && file.lastModified() < keepAfter.toEpochMilli()) + .forEach(file -> { + log.info("Cleaning up old plugin file (>3 days): {}", file.getName()); + if (!file.delete()) { + log.warn("Failed to delete old plugin file: {}", file.getAbsolutePath()); + } + }); + + for (String pluginName : needsDownload) { + log.info("Downloading missing plugin: {}", pluginName); + if (!downloadPlugin(pluginName)) { + MicrobotPluginManifest failedManifest = manifestMap.get(pluginName); + if (failedManifest != null) { + validPluginManifests.remove(failedManifest); + } + } + } + + Set installedPluginNames = validPluginManifests.stream() + .map(MicrobotPluginManifest::getInternalName) + .collect(Collectors.toSet()); + + Set toAdd = validPluginManifests.stream() + .filter(manifest -> !loadedPluginNames.contains(manifest.getInternalName())) + .collect(Collectors.toSet()); + + List toRemove = loadedExternalPlugins.stream() + .filter(plugin -> !installedPluginNames.contains(plugin.getClass().getSimpleName())) + .collect(Collectors.toList()); + + log.info("Plugin refresh - Will add: {} plugins, Will remove: {} plugins", + toAdd.stream().map(MicrobotPluginManifest::getInternalName).collect(Collectors.toSet()), + toRemove.stream().map(p -> p.getClass().getSimpleName()).collect(Collectors.toSet())); + + toRemove.forEach(plugin -> { + log.info("Stopping plugin \"{}\" (no longer installed for this profile)", plugin.getClass().getSimpleName()); + stopPlugin(plugin); + }); + + for (MicrobotPluginManifest manifest : toAdd) { + String pluginName = manifest.getInternalName(); + File pluginFile = getPluginJarFile(pluginName); + if (!pluginFile.exists()) { + log.warn("Plugin file missing for {}, skipping load", pluginName); + continue; + } + + log.info("Loading plugin \"{}\"", pluginName); + List newPlugins = null; + MicrobotPluginClassLoader classLoader = null; + try { + if (!verifyHash(pluginName)) { + log.warn("Plugin hash verification failed for: {}. The installed version may be outdated or from a different source.", pluginName); + continue; + } + + List> pluginClasses = new ArrayList<>(); + classLoader = new MicrobotPluginClassLoader(pluginFile, getClass().getClassLoader()); + + for (ClassPath.ClassInfo classInfo : ClassPath.from(classLoader).getAllClasses()) { + try + { + Class clazz = classLoader.loadClass(classInfo.getName()); + pluginClasses.add(clazz); + } + catch (ClassNotFoundException e) + { + log.trace("Class not found during plugin loading: {}", classInfo.getName(), e); + } + } + + newPlugins = loadPlugins(pluginClasses, null); + + boolean startup = SplashScreen.isOpen(); + if (!startup && !newPlugins.isEmpty()) { + pluginManager.loadDefaultPluginConfiguration(newPlugins); + final List pluginsToStart = newPlugins; + SwingUtilities.invokeAndWait(() -> { + try { + for (Plugin p : pluginsToStart) { + pluginManager.startPlugin(p); + } + } catch (PluginInstantiationException e) { + throw new RuntimeException(e); + } + }); + } + log.info("Successfully loaded plugin: {}", pluginName); + } catch (ThreadDeath e) { + throw e; + } catch (Throwable e) { + log.warn("Unable to load or start plugin \"{}\"", pluginName, e); + } + } + + if (!toAdd.isEmpty() || !toRemove.isEmpty()) { + eventBus.post(new ExternalPluginsChanged()); + } + + log.info("Completed plugin refresh - Added: {}, Removed: {}", toAdd.size(), toRemove.size()); + } catch (Exception e) { + log.error("Error during plugin refresh", e); + } finally { + profileRefreshInProgress = false; + } + } + + /** + * Downloads a plugin JAR file from the remote server. + * + * @param internalName the internal name of the plugin to download + * @return true if the plugin was successfully downloaded, false otherwise + */ + private boolean downloadPlugin(String internalName) { + MicrobotPluginManifest manifest = manifestMap.get(internalName); + if (manifest == null) { + log.error("Cannot download plugin {}: manifest not found", internalName); + return false; + } + + try { + File pluginFile = getPluginJarFile(internalName); + + HttpUrl jarUrl = microbotPluginClient.getJarURL(manifest); + if (jarUrl == null || !jarUrl.isHttps()) { + log.error("Invalid JAR URL for plugin {}", internalName); + return false; + } + + OkHttpClient clientWithoutProxy = noProxy(okHttpClient); + Request request = new Request.Builder() + .url(jarUrl) + .build(); + + try (Response response = clientWithoutProxy.newCall(request).execute()) { + if (!response.isSuccessful()) { + log.error("Failed to download plugin {}: HTTP {}", internalName, response.code()); + return false; + } + + byte[] jarData = response.body().bytes(); + + Files.write(jarData, pluginFile); + log.info("Plugin {} downloaded to {}", internalName, pluginFile.getAbsolutePath()); + return true; + } + + } catch (Exception e) { + log.error("Failed to download plugin {}", internalName, e); + + File pluginFile = getPluginJarFile(internalName); + if (pluginFile.exists() && !pluginFile.delete()) { + log.warn("Failed to delete corrupted plugin file: {}", pluginFile.getAbsolutePath()); + } + return false; + } + } + + /** + * Installs a plugin and triggers UI refresh. + * + * @param manifest the manifest of the plugin to install + */ + public void installPlugin(MicrobotPluginManifest manifest) { + executor.submit(() -> install(manifest)); + } + + /** + * Removes a plugin and triggers UI refresh. + * + * @param manifest the manifest of the plugin to remove + */ + public void removePlugin(MicrobotPluginManifest manifest) { + executor.submit(() -> remove(manifest)); + } + + /** + * Installs a plugin by adding it to the installed plugins list in config. + * + * @param manifest the manifest of the plugin to install + */ + public void install(MicrobotPluginManifest manifest) { + if (manifest == null || !manifestMap.containsValue(manifest)) { + log.error("Can't install plugin: unable to identify manifest"); + return; + } + + final String internalName = manifest.getInternalName(); + if (internalName == null || internalName.isEmpty()) { + log.error("Cannot install plugin: internal name is null or empty"); + return; + } + + if (manifest.isDisable()) { + log.warn("Cannot install plugin '{}' ({}): This plugin has been disabled upstream by the developers. " + + "This usually means the plugin is no longer functional, has security issues, or has been deprecated.", + manifest.getDisplayName(), internalName); + return; + } + + List installedPlugins = getInstalledPlugins(); + + if (installedPlugins.stream().anyMatch(p -> internalName.equals(p.getInternalName()))) { + log.info("Plugin {} is already installed", internalName); + return; + } + + installedPlugins.add(manifest); + saveInstalledPlugins(installedPlugins); + + log.info("Added plugin {} to installed list", manifest.getDisplayName()); + + update(); + } + + /** + * Removes a plugin by removing it from the installed plugins list in config. + * + * @param manifest the manifest of the plugin to remove + */ + public void remove(MicrobotPluginManifest manifest) { + if (manifest == null) { + log.error("Can't remove plugin: unable to identify manifest"); + return; + } + + final String internalName = manifest.getInternalName(); + + if (internalName == null || internalName.isEmpty()) { + log.error("Cannot remove plugin: internal name is null or empty"); + return; + } + + List installedPlugins = getInstalledPlugins(); + + boolean wasInstalled = installedPlugins.removeIf(p -> internalName.equals(p.getInternalName())); + + if (!wasInstalled) { + log.info("Plugin {} was not in installed list", internalName); + return; + } + + saveInstalledPlugins(installedPlugins); + + log.info("Removed plugin {} from installed list", internalName); + + update(); + } + + /** + * Submits a plugin refresh task to the executor. + * This will reload plugins based on the current profile's installed plugins list. + */ + public void update() { + executor.submit(this::refresh); + } + + /** + * Gets the manifest for a given plugin, this pulls from the global manifest map. + * + * @param plugin the plugin to get the manifest for + * @return the manifest for the plugin, or null if not found or not an external plugin + */ + @Nullable + private MicrobotPluginManifest getPluginManifest(Plugin plugin) { + PluginDescriptor descriptor = plugin.getClass().getAnnotation(PluginDescriptor.class); + if (descriptor == null || !descriptor.isExternal()) { + return null; + } + + String internalName = plugin.getClass().getSimpleName(); + + return manifestMap.get(internalName); + } + + /** + * Gets the manifest for a plugin from the current profile's installed plugins list only. + * + * @param internalName the internal name of the plugin + * @return the manifest for the plugin from the current profile, or null if not found + */ + @Nullable + private MicrobotPluginManifest getInstalledPluginManifest(String internalName) { + List installedPlugins = getInstalledPlugins(); + return installedPlugins.stream() + .filter(manifest -> internalName.equals(manifest.getInternalName())) + .findFirst() + .orElse(null); + } + + /** + * Gracefully stops a plugin + */ + private void stopPlugin(Plugin plugin) { + String pluginName = plugin.getClass().getSimpleName(); + + try { + if (pluginManager.isPluginEnabled(plugin)) { + pluginManager.setPluginEnabled(plugin, false); + } + + if (pluginManager.isPluginActive(plugin)) { + SwingUtilities.invokeAndWait(() -> { + try { + pluginManager.stopPlugin(plugin); + } catch (PluginInstantiationException e) { + log.warn("Error stopping plugin {}: {}", pluginName, e.getMessage()); + } + }); + } + pluginManager.remove(plugin); + } catch (Exception e) { + log.warn("Error during plugin stop for {}: {}", pluginName, e.getMessage()); + } + } + + /** + * Gracefully shuts down the plugin manager and performs final cleanup. + */ + private void shutdown() { + if (!isShuttingDown.compareAndSet(false, true)) { + return; + } + + log.info("Shutting down MicrobotPluginManager"); + + try { + List externalPlugins = pluginManager.getPlugins().stream() + .filter(plugin -> { + PluginDescriptor descriptor = plugin.getClass().getAnnotation(PluginDescriptor.class); + return descriptor != null && descriptor.isExternal(); + }) + .collect(Collectors.toList()); + + for (Plugin plugin : externalPlugins) { + stopPlugin(plugin); + } + + log.info("MicrobotPluginManager shutdown complete"); + } catch (Exception e) { + log.error("Error during MicrobotPluginManager shutdown", e); + } + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/farmTreeRun/FarmTreeRunConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/farmTreeRun/FarmTreeRunConfig.java index 5599dba18f9..939ddd2bf1d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/farmTreeRun/FarmTreeRunConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/farmTreeRun/FarmTreeRunConfig.java @@ -33,290 +33,282 @@ "

  • Filled Bottomless compost bucket
  • \n" + "" + "
    Extra information:\n" + - "
    If you want to stop the script during your farm run (maybe it gets stuck or whatever reason), make sure to disable 'Banking' and disable patches you previously ran.
    Happy botting\n" + "
    If you want to stop the script during your farm run (maybe it gets stuck or whatever reason), make sure to disable 'Banking' and disable patches you previously ran." + + "Happy botting\n" + + "

    UI and new trees added thanks to Diogenes and T\n" + + "

    The tree order is as follows: GS Fruit → GS Tree → TGV Fruit → Farming Guild Tree → Farming Guild Fruit → Taverley → Falador → Lumbridge → Varrock → Brimhaven Fruit → Catherby Fruit → Fossil A/B/C → Lletya Fruit → Auburnvale Tree → Kastori Fruit → Avium Savannah Hardwood.

    Patches are listed in the order they will be attended filtered by type\n" ) public interface FarmTreeRunConfig extends Config { - public static final boolean DEBUG_MODE = System.getProperty("java.vm.info", "").contains("sharing"); + /* ========================= + * Sections (as requested) + * ========================= */ @ConfigSection( - name = "General", - description = "General", + name = "Sapling selection", + description = "Choose which saplings to plant", position = 1 ) - String generalSection = "general"; + String saplingSection = "saplingSection"; - // Tree patches section @ConfigSection( - name = "Tree patches", - description = "Select which tree patches to use", + name = "Protection", + description = "Configure payment (protection) per tree type", position = 2 ) + String protectionSection = "protectionSection"; + + @ConfigSection( + name = "Gear", + description = "General gear and run settings", + position = 3 + ) + String gearSection = "gearSection"; + + @ConfigSection( + name = "Tree patches", + description = "Select which regular tree patches to use", + position = 4 + ) String treePatchesSection = "treePatchesSection"; - // Fruit tree patches section @ConfigSection( name = "Fruit tree patches", description = "Select which fruit tree patches to use", - position = 3 + position = 5 ) String fruitTreePatchesSection = "fruitTreePatchesSection"; -// TODO: Not implemented yet -// @ConfigItem( -// keyName = "trackRuneLite", -// name = "Use RuneLite time tracking plugin", -// description = "When enabled it tracks RuneLite farm patch times. Only when select farm patches below are fully grown, it will start the farm run.", -// position = 1, -// section = generalSection -// ) -// default boolean trackRuneLiteTimeTracking() -// { -// return false; -// } - - @ConfigItem( - keyName = "banking", - name = "Banking", - description = "Enabling this will run to bank and reset inventory with required items.", - position = 1, - section = generalSection + @ConfigSection( + name = "Hardwood patches", + description = "Select which hardwood patches to use", + position = 6 ) - default boolean banking() { - return true; - } + String hardTreePatchesSection = "hardTreePatchesSection"; + /* ========================= + * Sapling selection + * ========================= */ @ConfigItem( keyName = "treeSapling", name = "Tree sapling", description = "Select tree sapling to use", + position = 0, + section = saplingSection + ) + default TreeEnums selectedTree() { return TreeEnums.MAPLE; } + + @ConfigItem( + keyName = "fruitTreeSapling", + name = "Fruit tree sapling", + description = "Select fruit tree sapling to use", + position = 1, + section = saplingSection + ) + default FruitTreeEnum selectedFruitTree() { return FruitTreeEnum.PAPAYA; } + + @ConfigItem( + keyName = "Fossil Island Tree", + name = "Hard sapling", + description = "Select Hard tree sapling to use", position = 2, - section = generalSection + section = saplingSection ) - default TreeEnums selectedTree() { - return TreeEnums.MAPLE; - } + default HardTreeEnums selectedHardTree() { return HardTreeEnums.MAHOGANY; } + /* ========================= + * Protection + * ========================= */ @ConfigItem( keyName = "protectTree", name = "Protect trees", description = "Do you want to protect your trees?", - position = 3, - section = generalSection - ) - default boolean protectTrees() { - return true; - } - - @ConfigItem( - keyName = "fruitTreeSapling", - name = "Fruit tree sapling", - description = "Select fruit tree sapling to use", - position = 4, - section = generalSection + position = 0, + section = protectionSection ) - default FruitTreeEnum selectedFruitTree() { - return FruitTreeEnum.PAPAYA; - } + default boolean protectTrees() { return true; } @ConfigItem( keyName = "protectFruitTree", name = "Protect fruit trees", description = "Do you want to protect your fruit trees?", - position = 5, - section = generalSection + position = 1, + section = protectionSection ) - default boolean protectFruitTrees() { - return false; - } + default boolean protectFruitTrees() { return false; } @ConfigItem( - keyName = "Fossil Island Tree", - name = "Hard sapling", - description = "Select Hard tree sapling to use", - position = 6, - section = generalSection + keyName = "protectHardTree", + name = "Protect Hard trees", + description = "Do you want to protect your hard wood ;)?", + position = 2, + section = protectionSection ) - default HardTreeEnums selectedHardTree() { - return HardTreeEnums.MAHOGANY; - } + default boolean protectHardTrees() { return false; } + /* ========================= + * Gear + * ========================= */ @ConfigItem( - keyName = "protectHardTree", - name = "Protect Hard trees", - description = "Do you want to protect your hard wood ;) ?", - position = 7, - section = generalSection + keyName = "banking", + name = "Banking", + description = "Enabling this will run to bank and reset inventory with required items.", + position = 0, + section = gearSection ) - default boolean protectHardTrees() { - return false; - } + default boolean banking() { return true; } @ConfigItem( keyName = "useCompost", name = "Use compost", description = "Only bottomless compost bucket is supported", - position = 8, - section = generalSection + position = 1, + section = gearSection ) - default boolean useCompost() { - return true; - } + default boolean useCompost() { return true; } @ConfigItem( keyName = "useGraceful", name = "Use graceful", description = "Enable if you want to wear graceful outfit", - position = 9, - section = generalSection + position = 2, + section = gearSection ) - default boolean useGraceful() { - return true; - } + default boolean useGraceful() { return true; } @ConfigItem( keyName = "useSkillsNecklace", name = "Use Skills Necklace", description = "Useful if you don't have Spirit tree or Farming cape", - position = 10, - section = generalSection + position = 3, + section = gearSection ) - default boolean useSkillsNecklace() { - return true; - } + default boolean useSkillsNecklace() { return true; } @ConfigItem( keyName = "useEnergyPotion", name = "Use Energy Potion", description = "Useful if you want to have a faster run", - position = 11, - section = generalSection + position = 4, + section = gearSection ) - default boolean useEnergyPotion() { - return true; - } + default boolean useEnergyPotion() { return true; } + /* ========================= + * Tree patches (regular) — ordered to match run: + * GS Tree → Farming Guild Tree → Taverley → Falador → Lumbridge → Varrock → Auburnvale + * ========================= */ @ConfigItem( - keyName = "falador", - name = "Falador", - description = "Falador tree patch", + keyName = "gnomeStrongholdTree", + name = "Gnome Stronghold", + description = "Gnome Stronghold tree patch", position = 0, section = treePatchesSection ) - default boolean faladorTreePatch() { - return true; - } + default boolean gnomeStrongholdTreePatch() { return true; } @ConfigItem( - keyName = "gnomeStrongholdTree", - name = "Gnome Stronghold", - description = "Gnome Stronghold tree patch", + keyName = "farmingGuildTree", + name = "Farming Guild", + description = "FarmingGuild tree patch", position = 1, section = treePatchesSection ) - default boolean gnomeStrongholdTreePatch() { - return true; - } + default boolean farmingGuildTreePatch() { return true; } @ConfigItem( - keyName = "lumbridge", - name = "Lumbridge", - description = "Lumbridge tree patch", + keyName = "taverley", + name = "Taverley", + description = "Taverley tree patch", position = 2, section = treePatchesSection ) - default boolean lumbridgeTreePatch() { - return true; - } + default boolean taverleyTreePatch() { return true; } @ConfigItem( - keyName = "taverley", - name = "Taverley", - description = "Taverley tree patch", + keyName = "falador", + name = "Falador", + description = "Falador tree patch", position = 3, section = treePatchesSection ) - default boolean taverleyTreePatch() { - return true; - } + default boolean faladorTreePatch() { return true; } @ConfigItem( - keyName = "varrock", - name = "Varrock", - description = "Varrock tree patch", + keyName = "lumbridge", + name = "Lumbridge", + description = "Lumbridge tree patch", position = 4, section = treePatchesSection ) - default boolean varrockTreePatch() { - return true; - } + default boolean lumbridgeTreePatch() { return true; } @ConfigItem( - keyName = "fossil", - name = "Fossil Island", - description = "Fossil Island tree patch x3", - position = 4, + keyName = "varrock", + name = "Varrock", + description = "Varrock tree patch", + position = 5, section = treePatchesSection ) - default boolean fossilTreePatch() { - return true; - } + default boolean varrockTreePatch() { return true; } @ConfigItem( - keyName = "farmingGuildTree", - name = "Farming Guild", - description = "FarmingGuild tree patch", - position = 5, + keyName = "AuburnvaleTree", + name = "Auburnvale", + description = "Auburnvale tree patch", + position = 6, section = treePatchesSection ) - default boolean farmingGuildTreePatch() { - return true; - } + default boolean auburnTreePatch() { return true; } + /* ========================= + * Fruit tree patches — ordered to match run: + * GS Fruit → TGV Fruit → Farming Guild Fruit → Brimhaven → Catherby → Lletya → Kastori + * ========================= */ + @ConfigItem( + keyName = "gnomeStrongholdFruitTree", + name = "Gnome Stronghold", + description = "Gnome Stronghold fruit tree patch", + position = 0, + section = fruitTreePatchesSection + ) + default boolean gnomeStrongholdFruitTreePatch() { return true; } @ConfigItem( - keyName = "brimhaven", - name = "Brimhaven", - description = "Brimhaven fruit tree patch", + keyName = "treeGnomeVillage", + name = "Tree gnome village", + description = "Tree gnome village fruit tree patch", position = 1, section = fruitTreePatchesSection ) - default boolean brimhavenFruitTreePatch() { - return true; - } + default boolean treeGnomeVillageFruitTreePatch() { return true; } @ConfigItem( - keyName = "catherby", - name = "Catherby", - description = "Catherby fruit tree patch", + keyName = "farmingGuildFruitTree", + name = "Farming Guild", + description = "Farming guild fruit tree patch", position = 2, section = fruitTreePatchesSection ) - default boolean catherbyFruitTreePatch() { - return true; - } + default boolean farmingGuildFruitTreePatch() { return false; } @ConfigItem( - keyName = "gnomeStrongholdFruitTree", - name = "Gnome Stronghold", - description = "Gnome Stronghold fruit tree patch", + keyName = "brimhaven", + name = "Brimhaven", + description = "Brimhaven fruit tree patch", position = 3, section = fruitTreePatchesSection ) - default boolean gnomeStrongholdFruitTreePatch() { - return true; - } + default boolean brimhavenFruitTreePatch() { return true; } @ConfigItem( - keyName = "treeGnomeVillage", - name = "Tree gnome village", - description = "Tree gnome village tree patch", + keyName = "catherby", + name = "Catherby", + description = "Catherby fruit tree patch", position = 4, section = fruitTreePatchesSection ) - default boolean treeGnomeVillageFruitTreePatch() { - return true; - } + default boolean catherbyFruitTreePatch() { return true; } @ConfigItem( keyName = "lletya", @@ -325,18 +317,35 @@ default boolean treeGnomeVillageFruitTreePatch() { position = 5, section = fruitTreePatchesSection ) - default boolean lletyaFruitTreePatch() { - return false; - } + default boolean lletyaFruitTreePatch() { return false; } @ConfigItem( - keyName = "farmingGuildFruitTree", - name = "Farming Guild", - description = "Farming guild fruit tree patch", + keyName = "kastoriFruitTreePatch", + name = "Kastori", + description = "Enable Kastori fruit tree patch", position = 6, section = fruitTreePatchesSection ) - default boolean farmingGuildFruitTreePatch() { - return false; - } + default boolean kastoriFruitTreePatch() { return true; } + + /* ========================= + * Hardwood patches — run uses Fossil Island (x3) then Avium Savannah + * ========================= */ + @ConfigItem( + keyName = "fossil", + name = "Fossil Island", + description = "Fossil Island tree patch x3", + position = 0, + section = hardTreePatchesSection + ) + default boolean fossilTreePatch() { return true; } + + @ConfigItem( + keyName = "aviumSavannahHardwood", + name = "Avium Savannah", + description = "Enable this hardwood tree patch", + position = 1, + section = hardTreePatchesSection + ) + default boolean aviumSavannahHardwoodPatch() { return false; } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/farmTreeRun/FarmTreeRunOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/farmTreeRun/FarmTreeRunOverlay.java index 1162ce386a0..8d1433ce59c 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/farmTreeRun/FarmTreeRunOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/farmTreeRun/FarmTreeRunOverlay.java @@ -23,7 +23,7 @@ public Dimension render(Graphics2D graphics) { try { panelComponent.setPreferredSize(new Dimension(200, 300)); panelComponent.getChildren().add(TitleComponent.builder() - .text("Acun's farm tree runner (v1.3.0)") + .text("Acun's farm tree runner (v1.3.1)") .color(Color.GREEN) .build()); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/farmTreeRun/FarmTreeRunPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/farmTreeRun/FarmTreeRunPlugin.java index a6f2fa3f04e..c5d7c77171d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/farmTreeRun/FarmTreeRunPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/farmTreeRun/FarmTreeRunPlugin.java @@ -24,7 +24,7 @@ */ @PluginDescriptor( name = PluginDescriptor.Default + "Farm tree runner", - description = "Acun's farm tree runner. Supports regular and fruit trees", + description = "Acun's farm tree runner. Supports regular, fruit and hardwood trees", tags = {"Farming", "Tree run"}, enabledByDefault = false ) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/farmTreeRun/FarmTreeRunScript.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/farmTreeRun/FarmTreeRunScript.java index 9d0ba9e26a4..f8e524e8585 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/farmTreeRun/FarmTreeRunScript.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/farmTreeRun/FarmTreeRunScript.java @@ -4,8 +4,6 @@ import lombok.RequiredArgsConstructor; import net.runelite.api.*; import net.runelite.api.coords.WorldPoint; -import net.runelite.client.Notifier; -import net.runelite.client.config.Notification; import net.runelite.client.plugins.microbot.farmTreeRun.enums.FarmTreeRunState; import net.runelite.client.plugins.microbot.farmTreeRun.enums.FruitTreeEnum; import net.runelite.client.plugins.microbot.farmTreeRun.enums.HardTreeEnums; @@ -16,7 +14,6 @@ import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; import net.runelite.client.plugins.microbot.util.antiban.enums.ActivityIntensity; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; -import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; @@ -87,7 +84,10 @@ public enum Patch { LLETYA_FRUIT_TREE_PATCH(26579, new WorldPoint(2345, 3163, 0), TreeKind.FRUIT_TREE, 1, 0), FOSSIL_TREE_PATCH_A(30482, new WorldPoint(3718, 3835, 0), TreeKind.HARD_TREE, 1, 0), FOSSIL_TREE_PATCH_B(30480, new WorldPoint(3709, 3836, 0), TreeKind.HARD_TREE, 1, 0), - FOSSIL_TREE_PATCH_C(30481, new WorldPoint(3701, 3840, 0), TreeKind.HARD_TREE, 1, 0); + FOSSIL_TREE_PATCH_C(30481, new WorldPoint(3701, 3840, 0), TreeKind.HARD_TREE, 1, 0), + AUBURNVALE_TREE_PATCH(56953, new WorldPoint(1365, 3320, 0), TreeKind.TREE, 1, 0), + KASTORI_FRUIT_TREE_PATCH(56955, new WorldPoint(1349, 3058, 0), TreeKind.FRUIT_TREE, 1, 12765), + AVIUM_SAVANNAH_HARDWOOD_PATCH(50692, new WorldPoint(1684, 2974, 0), TreeKind.HARD_TREE,1,0); private final int id; private final WorldPoint location; @@ -301,9 +301,46 @@ public boolean run(FarmTreeRunConfig config) { if (!handledPatch) return; } - botStatus = FINISHED; + botStatus = HANDLE_AUBURNVALE_TREE_PATCH; break; - case FINISHED: + + case HANDLE_AUBURNVALE_TREE_PATCH: { + patch = Patch.AUBURNVALE_TREE_PATCH; + if (config.auburnTreePatch()) { + if (walkToLocation(patch.getLocation())) { + handledPatch = handlePatch(config, patch); + } + if (!handledPatch) return; // stay in this state until done + } + botStatus = HANDLE_KASTORI_FRUIT_TREE_PATCH; + break; + } + case HANDLE_KASTORI_FRUIT_TREE_PATCH: { + patch = Patch.KASTORI_FRUIT_TREE_PATCH; + if (config.kastoriFruitTreePatch()) { + if (walkToLocation(patch.getLocation())) { + handledPatch = handlePatch(config, patch); + } + if (!handledPatch) return; + } + botStatus = HANDLE_AVIUM_SAVANNAH_HARDWOOD_PATCH; + break; + } + + case HANDLE_AVIUM_SAVANNAH_HARDWOOD_PATCH: { + patch = Patch.AVIUM_SAVANNAH_HARDWOOD_PATCH; + if (config.aviumSavannahHardwoodPatch()) { + if (walkToLocation(patch.getLocation())) { + handledPatch = handlePatch(config, patch); + } + if (!handledPatch) return; + } + botStatus = FINISHED; + break; + } + + + case FINISHED: Microbot.getClientThread().runOnClientThreadOptional(() -> { Microbot.getClient().addChatMessage(ChatMessageType.ENGINE, "", "Tree run completed.", "Acun", false); Microbot.getClient().addChatMessage(ChatMessageType.ENGINE, "", "Made with love by Acun.", "Acun", false); @@ -839,7 +876,8 @@ private List getSelectedTreePatches(FarmTreeRunConfig config) { config::lumbridgeTreePatch, config::taverleyTreePatch, config::varrockTreePatch, - config::farmingGuildTreePatch + config::farmingGuildTreePatch, + config::auburnTreePatch ); // Filter the patches to include only those that return true @@ -853,7 +891,8 @@ private List getSelectedHardTreePatches(FarmTreeRunConfig confi List allHardTreePatches = List.of( config::fossilTreePatch, config::fossilTreePatch, - config::fossilTreePatch + config::fossilTreePatch, + config::aviumSavannahHardwoodPatch ); // Filter the patches to include only those that return true @@ -870,7 +909,8 @@ private List getSelectedFruitTreePatches(FarmTreeRunConfig conf config::farmingGuildFruitTreePatch, config::lletyaFruitTreePatch, config::gnomeStrongholdFruitTreePatch, - config::treeGnomeVillageFruitTreePatch + config::treeGnomeVillageFruitTreePatch, + config::kastoriFruitTreePatch ); // Filter the patches to include only those that return true diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/farmTreeRun/enums/FarmTreeRunState.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/farmTreeRun/enums/FarmTreeRunState.java index 9c024372da6..4febb5d3e92 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/farmTreeRun/enums/FarmTreeRunState.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/farmTreeRun/enums/FarmTreeRunState.java @@ -45,5 +45,11 @@ public enum FarmTreeRunState { HANDLE_FOSSIL_TREE_PATCH_C, + HANDLE_AUBURNVALE_TREE_PATCH, + + HANDLE_KASTORI_FRUIT_TREE_PATCH, + + HANDLE_AVIUM_SAVANNAH_HARDWOOD_PATCH, + FINISHED } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/fletching/FletchingConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/fletching/FletchingConfig.java deleted file mode 100644 index c0b5b11c48e..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/fletching/FletchingConfig.java +++ /dev/null @@ -1,86 +0,0 @@ -package net.runelite.client.plugins.microbot.fletching; - -import net.runelite.client.config.Config; -import net.runelite.client.config.ConfigGroup; -import net.runelite.client.config.ConfigItem; -import net.runelite.client.config.ConfigSection; -import net.runelite.client.plugins.microbot.fletching.enums.FletchingItem; -import net.runelite.client.plugins.microbot.fletching.enums.FletchingMaterial; -import net.runelite.client.plugins.microbot.fletching.enums.FletchingMode; - -@ConfigGroup(FletchingConfig.GROUP) -public interface FletchingConfig extends Config { - - String GROUP = "Fletching"; - - @ConfigItem( - keyName = "guide", - name = "How to use", - description = "How to use this plugin", - position = 1 - ) - default String GUIDE() { - return "Start the script at any bank (grand exchange preferably)\n" + - "Make sure to have a bank and all the logs in your bank"; - } - - @ConfigSection( - name = "General", - description = "General", - position = 0, - closedByDefault = false - ) - String generalSection = "general"; - - @ConfigItem( - keyName = "Mode", - name = "Mode", - description = "Choose your mode of fletching", - position = 0, - section = generalSection - ) - default FletchingMode fletchingMode() - { - return FletchingMode.UNSTRUNG; - } - @ConfigItem( - keyName = "Material", - name = "Material", - description = "Choose your material", - position = 1, - section = generalSection - ) - default FletchingMaterial fletchingMaterial() - { - return FletchingMaterial.LOG; - } - @ConfigItem( - keyName = "Item", - name = "Item", - description = "Choose your item", - position = 2, - section = generalSection - ) - default FletchingItem fletchingItem() - { - return FletchingItem.SHORT; - } - @ConfigSection( - name = "Antiban", - description = "Configure antiban measures", - position = 1, - closedByDefault = false - ) - String antibanSection = "antiban"; - @ConfigItem( - keyName = "Afk", - name = "Afk randomly", - description = "Randomy afks between 3 and 60 seconds", - position = 0, - section = antibanSection - ) - default boolean Afk() - { - return false; - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/fletching/FletchingOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/fletching/FletchingOverlay.java deleted file mode 100644 index 098479bed7e..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/fletching/FletchingOverlay.java +++ /dev/null @@ -1,39 +0,0 @@ -package net.runelite.client.plugins.microbot.fletching; - -import net.runelite.client.plugins.microbot.Microbot; -import net.runelite.client.ui.overlay.OverlayPanel; -import net.runelite.client.ui.overlay.OverlayPosition; -import net.runelite.client.ui.overlay.components.LineComponent; -import net.runelite.client.ui.overlay.components.TitleComponent; - -import javax.inject.Inject; -import java.awt.*; - - -public class FletchingOverlay extends OverlayPanel { - @Inject - FletchingOverlay(FletchingPlugin plugin) - { - super(plugin); - setPosition(OverlayPosition.TOP_LEFT); - setNaughty(); - } - @Override - public Dimension render(Graphics2D graphics) { - try { - panelComponent.setPreferredSize(new Dimension(200, 300)); - panelComponent.getChildren().add(TitleComponent.builder() - .text("Micro Fletcher") - .color(Color.GREEN) - .build()); - - panelComponent.getChildren().add(LineComponent.builder() - .left(Microbot.status) - .right("Version: " + FletchingScript.version) - .build()); - } catch(Exception ex) { - System.out.println(ex.getMessage()); - } - return super.render(graphics); - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/fletching/FletchingPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/fletching/FletchingPlugin.java deleted file mode 100644 index 23484780bc2..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/fletching/FletchingPlugin.java +++ /dev/null @@ -1,57 +0,0 @@ -package net.runelite.client.plugins.microbot.fletching; - -import com.google.inject.Provides; -import lombok.extern.slf4j.Slf4j; -import net.runelite.api.Client; -import net.runelite.api.events.WidgetLoaded; -import net.runelite.client.Notifier; -import net.runelite.client.callback.ClientThread; -import net.runelite.client.config.ConfigManager; -import net.runelite.client.eventbus.Subscribe; -import net.runelite.client.plugins.Plugin; -import net.runelite.client.plugins.PluginDescriptor; -import net.runelite.client.plugins.microbot.Microbot; -import net.runelite.client.plugins.microbot.util.mouse.VirtualMouse; -import net.runelite.client.ui.overlay.OverlayManager; - -import javax.inject.Inject; -import java.awt.*; - -@PluginDescriptor( - name = PluginDescriptor.Mocrosoft + "Fletcher", - description = "Microbot fletching plugin", - tags = {"fletching", "microbot", "skills"}, - enabledByDefault = false -) -@Slf4j -public class FletchingPlugin extends Plugin { - @Inject - private FletchingConfig config; - - @Provides - FletchingConfig provideConfig(ConfigManager configManager) { - return configManager.getConfig(FletchingConfig.class); - } - @Inject - private OverlayManager overlayManager; - @Inject - private FletchingOverlay fletchingOverlay; - - FletchingScript fletchingScript; - - - @Override - protected void startUp() throws AWTException { - Microbot.pauseAllScripts.compareAndSet(true, false); - if (overlayManager != null) { - overlayManager.add(fletchingOverlay); - } - fletchingScript = new FletchingScript(); - fletchingScript.run(config); - } - - protected void shutDown() { - fletchingScript.shutdown(); - overlayManager.remove(fletchingOverlay); - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/fletching/FletchingScript.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/fletching/FletchingScript.java deleted file mode 100644 index 97dcd04627c..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/fletching/FletchingScript.java +++ /dev/null @@ -1,288 +0,0 @@ -package net.runelite.client.plugins.microbot.fletching; - - -import lombok.Getter; -import lombok.Setter; -import net.runelite.api.*; -import net.runelite.api.events.WidgetLoaded; -import net.runelite.api.widgets.Widget; -import net.runelite.client.plugins.microbot.Microbot; -import net.runelite.client.plugins.microbot.Script; -import net.runelite.client.plugins.microbot.fletching.enums.FletchingItem; -import net.runelite.client.plugins.microbot.fletching.enums.FletchingMaterial; -import net.runelite.client.plugins.microbot.fletching.enums.FletchingMode; -import net.runelite.client.plugins.microbot.util.antiban.Rs2Antiban; -import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; -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.keyboard.Rs2Keyboard; -import net.runelite.client.plugins.microbot.util.math.Rs2Random; -import net.runelite.client.plugins.microbot.util.misc.Rs2UiHelper; -import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; - -import java.util.concurrent.TimeUnit; - -@Getter -class ProgressiveFletchingModel { - @Setter - private FletchingItem fletchingItem; - @Setter - private FletchingMaterial fletchingMaterial; -} - -public class FletchingScript extends Script { - - public static String version = "1.6.2"; - - // The fletching interface widget group ID - private static final int FLETCHING_WIDGET_GROUP_ID = 17694736; - - ProgressiveFletchingModel model = new ProgressiveFletchingModel(); - - String primaryItemToFletch = ""; - String secondaryItemToFletch = ""; - - FletchingMode fletchingMode; - - public void run(FletchingConfig config) { - fletchingMode = config.fletchingMode(); - Rs2Antiban.resetAntibanSettings(); - Rs2Antiban.antibanSetupTemplates.applyFletchingSetup(); - mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { - try { - if (!Microbot.isLoggedIn()) - return; - if (!super.run()) return; - - if ((fletchingMode == FletchingMode.PROGRESSIVE || fletchingMode == FletchingMode.PROGRESSIVE_STRUNG) - && model.getFletchingItem() == null) { - calculateItemToFletch(); - } - - - if (!configChecks(config)) return; - - if (Rs2AntibanSettings.actionCooldownActive) - return; - -// if (config.Afk() && Random.random(1, 100) == 2) -// sleep(1000, 60000); - - boolean hasRequirementsToFletch; - boolean hasRequirementsToBank; - primaryItemToFletch = fletchingMode.getItemName(); - - if (fletchingMode == FletchingMode.PROGRESSIVE) { - secondaryItemToFletch = (model.getFletchingMaterial().getName() + " logs").trim(); - hasRequirementsToFletch = Rs2Inventory.hasItem(primaryItemToFletch) - && Rs2Inventory.hasItemAmount(secondaryItemToFletch, model.getFletchingItem().getAmountRequired()); - hasRequirementsToBank = !Rs2Inventory.hasItem(primaryItemToFletch) - || !Rs2Inventory.hasItemAmount(secondaryItemToFletch, model.getFletchingItem().getAmountRequired()); - } else if (fletchingMode == FletchingMode.PROGRESSIVE_STRUNG) { - secondaryItemToFletch = model.getFletchingMaterial().getName() + " " - + model.getFletchingItem().getContainsInventoryName() + " (u)"; - hasRequirementsToFletch = Rs2Inventory.hasItem(primaryItemToFletch) && Rs2Inventory.hasItem(secondaryItemToFletch); - hasRequirementsToBank = !Rs2Inventory.hasItem(primaryItemToFletch) || !Rs2Inventory.hasItem(secondaryItemToFletch); - } else { - secondaryItemToFletch = fletchingMode == FletchingMode.STRUNG - ? config.fletchingMaterial().getName() + " " + config.fletchingItem().getContainsInventoryName() + " (u)" - : (config.fletchingMaterial().getName() + " logs").trim(); - hasRequirementsToFletch = Rs2Inventory.hasItem(primaryItemToFletch) - && Rs2Inventory.hasItemAmount(secondaryItemToFletch, config.fletchingItem().getAmountRequired()); - hasRequirementsToBank = !Rs2Inventory.hasItem(primaryItemToFletch) - || !Rs2Inventory.hasItemAmount(secondaryItemToFletch, config.fletchingItem().getAmountRequired()); - } - - if (hasRequirementsToFletch) { - fletch(config); - } - if (hasRequirementsToBank) { - bankItems(config); - } - - } catch (Exception ex) { - System.out.println(ex.getMessage()); - } - }, 0, 600, TimeUnit.MILLISECONDS); - } - - private void bankItems(FletchingConfig config) { - Rs2Bank.openBank(); - - // Deposit items based on the fletching mode - switch (fletchingMode) { - case STRUNG: - Rs2Bank.depositAll(); - break; - case PROGRESSIVE: - Rs2Bank.depositAll(model.getFletchingItem().getContainsInventoryName()); - calculateItemToFletch(); - secondaryItemToFletch = (model.getFletchingMaterial().getName() + " logs").trim(); - break; - case PROGRESSIVE_STRUNG: - Rs2Bank.depositAll(); - calculateItemToFletch(); - secondaryItemToFletch = model.getFletchingMaterial().getName() + " " - + model.getFletchingItem().getContainsInventoryName() + " (u)"; - break; - default: - Rs2Bank.depositAll(config.fletchingItem().getContainsInventoryName()); - Rs2Inventory.waitForInventoryChanges(5000); - break; - } - - // Check if the primary item is available - if (!Rs2Bank.hasItem(primaryItemToFletch) && !Rs2Inventory.hasItem(primaryItemToFletch)) { - Rs2Bank.closeBank(); - Microbot.status = "[Shutting down] - Reason: " + primaryItemToFletch + " not found in the bank."; - Microbot.showMessage(Microbot.status); - shutdown(); - return; - } - - // Ensure the inventory isn't full without the primary item - if (!Rs2Inventory.hasItem(primaryItemToFletch)) { - Rs2Bank.depositAll(); - } - - // Withdraw the primary item if not already in the inventory - if (!Rs2Inventory.hasItem(primaryItemToFletch)) { - Rs2Bank.withdrawX(primaryItemToFletch, fletchingMode.getAmount(), true); - } - - // Check if the secondary item is available - if (!Rs2Bank.hasItem(secondaryItemToFletch)) { - if (fletchingMode == FletchingMode.UNSTRUNG_STRUNG && Rs2Bank.hasBankItem("bow string")) { - Rs2Bank.depositAll(); - fletchingMode = FletchingMode.STRUNG; - return; - } - Rs2Bank.closeBank(); - Microbot.status = "[Shutting down] - Reason: " + secondaryItemToFletch + " not found in the bank."; - Microbot.showMessage(Microbot.status); - shutdown(); - return; - } - - // Withdraw the secondary item if not already in the inventory - if (!Rs2Inventory.hasItem(secondaryItemToFletch)) { - if (fletchingMode == FletchingMode.STRUNG) { - Rs2Bank.withdrawDeficit(secondaryItemToFletch, fletchingMode.getAmount()); - } else { - Rs2Bank.withdrawAll(secondaryItemToFletch); - } - } - if (Rs2AntibanSettings.naturalMouse) { - // Testing if completing the mouse movement before the final item check improves the overall flow. - // This should allow time for the inventory to update while the mouse is moving. - // Enhances the bot's behavior to appear more natural and less automated. - Widget closeButton = Rs2Widget.getWidget(786434).getChild(11); - Point closePoint = Rs2UiHelper.getClickingPoint(closeButton != null ? closeButton.getBounds() : null, true); - Rs2Random.waitEx(200, 100); - Microbot.naturalMouse.moveTo(closePoint.getX(), closePoint.getY()); - } - - // Final check to ensure both items are in the inventory - if (!Rs2Inventory.hasItem(primaryItemToFletch) || !Rs2Inventory.hasItem(secondaryItemToFletch)) { - Microbot.log("waiting for inventory changes."); - Rs2Inventory.waitForInventoryChanges(5000); - } - - Rs2Random.waitEx(200, 100); - Rs2Bank.closeBank(); - } - - - private void fletch(FletchingConfig config) { - Rs2Inventory.combineClosest(primaryItemToFletch, secondaryItemToFletch); - sleepUntil(() -> Rs2Widget.getWidget(FLETCHING_WIDGET_GROUP_ID) != null, 5000); - char option; - if (fletchingMode == FletchingMode.PROGRESSIVE || fletchingMode == FletchingMode.PROGRESSIVE_STRUNG) { - - option = model.getFletchingItem().getOption(model.getFletchingMaterial(), fletchingMode); - Rs2Keyboard.keyPress(option); - } else { - option = config.fletchingItem().getOption(config.fletchingMaterial(), fletchingMode); - Rs2Keyboard.keyPress(option); - } - - sleepUntil(() -> !Rs2Inventory.hasItem(secondaryItemToFletch), 60000); - Rs2Antiban.actionCooldown(); - Rs2Antiban.takeMicroBreakByChance(); - Rs2Bank.preHover(); - } - - private boolean configChecks(FletchingConfig config) { - if (config.fletchingMaterial() == FletchingMaterial.REDWOOD && config.fletchingItem() != FletchingItem.SHIELD) { - Microbot.getNotifier().notify("[Wrong Configuration] You can only make shields with redwood logs."); - shutdown(); - return false; - } - return true; - } - - public void calculateItemToFletch() { - int level = Microbot.getClient().getRealSkillLevel(Skill.FLETCHING); - FletchingItem item = null; - FletchingMaterial material = null; - - - - if (fletchingMode == FletchingMode.PROGRESSIVE_STRUNG && level < 5) { - Microbot.showMessage("Can't String Bows Below Level 5"); - shutdown(); - return; - } - if (level < 5) { - item = FletchingItem.ARROW_SHAFT; - material = FletchingMaterial.LOG; - } else if (level < 10) { - item = FletchingItem.SHORT; - material = (fletchingMode == FletchingMode.PROGRESSIVE) ? FletchingMaterial.LOG : FletchingMaterial.WOOD; - } else if (level < 20) { - item = FletchingItem.LONG; - material = (fletchingMode == FletchingMode.PROGRESSIVE) ? FletchingMaterial.LOG : FletchingMaterial.WOOD; - } else if (level < 25) { - item = FletchingItem.SHORT; - material = FletchingMaterial.OAK; - } else if (level < 35) { - item = FletchingItem.LONG; - material = FletchingMaterial.OAK; - } else if (level < 40) { - item = FletchingItem.SHORT; - material = FletchingMaterial.WILLOW; - } else if (level < 50) { - item = FletchingItem.LONG; - material = FletchingMaterial.WILLOW; - } else if (level < 55) { - item = FletchingItem.SHORT; - material = FletchingMaterial.MAPLE; - } else if (level < 65) { - item = FletchingItem.LONG; - material = FletchingMaterial.MAPLE; - } else if (level < 70) { - item = FletchingItem.SHORT; - material = FletchingMaterial.YEW; - } else if (level < 80) { - item = FletchingItem.LONG; - material = FletchingMaterial.YEW; - } else if (level < 85) { - item = FletchingItem.SHORT; - material = FletchingMaterial.MAGIC; - } else { - item = FletchingItem.LONG; - material = FletchingMaterial.MAGIC; - } - - model.setFletchingItem(item); - model.setFletchingMaterial(material); - } - - - @Override - public void shutdown() { - - Rs2Antiban.resetAntibanSettings(); - super.shutdown(); - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/fletching/enums/FletchingItem.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/fletching/enums/FletchingItem.java deleted file mode 100644 index d8e0aa125ec..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/fletching/enums/FletchingItem.java +++ /dev/null @@ -1,39 +0,0 @@ -package net.runelite.client.plugins.microbot.fletching.enums; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor -public enum FletchingItem -{ - ARROW_SHAFT("Arrow shaft", '1', "arrow shaft", 1), - SHORT("Short bows", '2', "shortbow", 1), - LONG("Long bows", '3', "longbow", 1), - STOCK("Crossbow stock", '4', "stock", 1), - SHIELD("Shield", '5', "shield", 2); - - private final String name; - private final char option; - private final String containsInventoryName; - private final int amountRequired; - - @Override - public String toString() - { - return name; - } - - public char getOption(FletchingMaterial material, FletchingMode fletchingMode) { - if (fletchingMode == FletchingMode.STRUNG - || fletchingMode == FletchingMode.PROGRESSIVE_STRUNG) { - return '1'; - } - if (material == FletchingMaterial.LOG && option == '2') return '3'; - if (material == FletchingMaterial.LOG && option == '3') return '4'; - //redwood is an exception - if (material == FletchingMaterial.REDWOOD) - return '2'; - return option; - } -} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/fletching/enums/FletchingMaterial.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/fletching/enums/FletchingMaterial.java deleted file mode 100644 index 0bdc7c49a37..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/fletching/enums/FletchingMaterial.java +++ /dev/null @@ -1,27 +0,0 @@ -package net.runelite.client.plugins.microbot.fletching.enums; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor -public enum FletchingMaterial -{ - LOG(""), - WOOD("Wood"), - OAK("Oak"), - WILLOW("Willow"), - MAPLE("Maple"), - YEW("Yew"), - MAGIC("Magic"), - REDWOOD("Redwood"); - - private final String name; - - - @Override - public String toString() - { - return name; - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/fletching/enums/FletchingMode.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/fletching/enums/FletchingMode.java deleted file mode 100644 index 39ba42e828f..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/fletching/enums/FletchingMode.java +++ /dev/null @@ -1,24 +0,0 @@ -package net.runelite.client.plugins.microbot.fletching.enums; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor -public enum FletchingMode { - UNSTRUNG("Cutting", "knife", 1), - STRUNG("Stringing", "bow string", 14), - PROGRESSIVE_STRUNG("Progressive Bow Stringing", "bow string", 14), - UNSTRUNG_STRUNG("Cutting & Stringing", "knife", 1), - PROGRESSIVE("Progressive Logs Cutting", "knife", 1); - - - private final String name; - private final String itemName; - private final int amount; - - @Override - public String toString() { - return name; - } -} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/frosty/frostyrc/RcPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/frosty/frostyrc/RcPlugin.java index 8453221403b..fadfa51a1aa 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/frosty/frostyrc/RcPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/frosty/frostyrc/RcPlugin.java @@ -57,7 +57,7 @@ RcConfig provideConfig(ConfigManager configManager) { @Getter private WorldPoint myWorldPoint; @Getter - public static String version = "v1.1.0"; + public static String version = "v1.1.2"; @Subscribe public void onGameObjectSpawned(GameObjectSpawned event) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/frosty/frostyrc/RcScript.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/frosty/frostyrc/RcScript.java index f4fe9510d86..4e39cd052bf 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/frosty/frostyrc/RcScript.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/frosty/frostyrc/RcScript.java @@ -48,6 +48,8 @@ public class RcScript extends Script { private final WorldPoint outsideWrathRuins = new WorldPoint(2445, 2818, 0); private final WorldPoint wrathRuinsLoc = new WorldPoint(2445, 2824, 0); + private volatile boolean forceDrinkAtFerox = false; + public static final int pureEss = 7936; public static final int feroxPool = 39651; public static final int monasteryRegion = 10290; @@ -99,6 +101,7 @@ public boolean run() { try { if (!Microbot.isLoggedIn()) return; if (!super.run()) return; + if (shouldPauseForBreak()) return; long startTime = System.currentTimeMillis(); if (lumbyElite == -1) { @@ -158,6 +161,23 @@ public void shutdown() { //Rs2Player.logout(); } + private boolean shouldPauseForBreak() { + if (!plugin.isBreakHandlerEnabled()) { + return false; + } + + if (BreakHandlerScript.isBreakActive()) { + return true; + } + + if (BreakHandlerScript.breakIn <= 0) { + BreakHandlerScript.setLockState(false); + return true; + } + + return false; + } + private void checkPouches() { Rs2Inventory.interact(colossalPouch, "Check"); sleepGaussian(900, 200); @@ -174,18 +194,22 @@ private void handleBanking() { } } + if (plugin.isBreakHandlerEnabled()) { + BreakHandlerScript.setLockState(true); + } + Rs2Tab.switchToInventoryTab(); + if (Rs2Inventory.hasDegradedPouch()) { + Rs2Magic.repairPouchesWithLunar(); + sleepGaussian(900, 200); + return; + } + if (Rs2Inventory.anyPouchUnknown()) { checkPouches(); } - if (Rs2Inventory.hasDegradedPouch()) { - Rs2Magic.repairPouchesWithLunar(); - sleepGaussian(900, 200); - return; - } - if (Rs2Inventory.isFull() && Rs2Inventory.allPouchesFull() && Rs2Inventory.contains(pureEss)) { Microbot.log("We are full, skipping bank"); state = State.GOING_HOME; @@ -195,10 +219,6 @@ private void handleBanking() { handleFeroxRunEnergy(); } - if (plugin.isBreakHandlerEnabled()) { - BreakHandlerScript.setLockState(false); - } - while (!Rs2Bank.isOpen() && isRunning() && (!Rs2Inventory.allPouchesFull() || !Rs2Inventory.contains(colossalPouch) @@ -330,8 +350,9 @@ private void handleFillPouch() { } private void handleFeroxRunEnergy() { - if (Rs2Player.getRunEnergy() < 45) { - Microbot.log("We are thirsty...let us Drink"); + if (forceDrinkAtFerox || Rs2Player.getRunEnergy() <= 15 || Rs2Player.getHealthPercentage() <= 20) { + Microbot.log("We are thirsty...let us Drink"); + forceDrinkAtFerox = true; if (plugin.getMyWorldPoint().distanceTo(feroxPoolWp) > 5) { Microbot.log("Walking to Ferox pool"); Rs2Walker.walkTo(feroxPoolWp); @@ -344,6 +365,7 @@ private void handleFeroxRunEnergy() { } sleepUntil(() -> (!Rs2Player.isInteracting()) && !Rs2Player.isAnimating() && Rs2Player.getRunEnergy() > 90); sleepGaussian(1100, 200); + forceDrinkAtFerox = false; } } @@ -452,11 +474,18 @@ private void handleWrathWalking() { BreakHandlerScript.setLockState(true); } + if (Rs2Bank.isOpen()) { Rs2Bank.closeBank(); } + if (Rs2Inventory.contains(mythCape)) { Microbot.log("Interacting with myth cape"); Rs2Inventory.interact(mythCape, "Teleport"); sleepUntil(() -> plugin.getMyWorldPoint().getRegionID() == mythicStatueRegion); - sleepGaussian(1100, 200); + sleepGaussian(600, 200); + + GameObject statue = Rs2GameObject.get("Mythic Statue"); + if (statue != null && !Rs2Player.isAnimating()) { + Rs2GameObject.interact(statue, "Teleport"); + } if (plugin.getMyWorldPoint().getRegionID() == mythicStatueRegion) { Microbot.log("Walking to Wrath ruins"); @@ -707,6 +736,16 @@ private void handleCrafting() { Microbot.log("Crafting runes"); handleEmptyPouch(); } + + handleFeroxRunEnergy(); + + if (plugin.isBreakHandlerEnabled()) { + BreakHandlerScript.setLockState(false); + if (BreakHandlerScript.isBreakActive() || BreakHandlerScript.breakIn <= 0) { + return; + } + } + state = State.BANKING; } @@ -731,7 +770,13 @@ private void handleBankTeleport() { Rs2Tab.switchToEquipmentTab(); sleepGaussian(1300, 200); - List bankTeleport = Arrays.asList( + boolean needRefill = (forceDrinkAtFerox || Rs2Player.getRunEnergy() <= 15 || Rs2Player.getHealthPercentage() <= 20); + List bankTeleport = needRefill + ? Arrays.asList( + Teleports.FEROX_ENCLAVE, + Teleports.CRAFTING_CAPE, + Teleports.FARMING_CAPE) + : Arrays.asList( Teleports.CRAFTING_CAPE, Teleports.FARMING_CAPE, Teleports.FEROX_ENCLAVE @@ -745,6 +790,10 @@ private void handleBankTeleport() { Rs2Equipment.interact(bankTeleportsId, teleport.getInteraction()); sleepUntil(() -> teleport.matchesRegion(plugin.getMyWorldPoint().getRegionID())); sleepGaussian(1100, 200); + if (teleport == Teleports.FEROX_ENCLAVE) { + forceDrinkAtFerox = true; + handleFeroxRunEnergy(); + } teleportUsed = true; break; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/gabplugs/sandminer/GabulhasSandMinerScript.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/gabplugs/sandminer/GabulhasSandMinerScript.java index 48eb030a10b..2fde9ed3f09 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/gabplugs/sandminer/GabulhasSandMinerScript.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/gabplugs/sandminer/GabulhasSandMinerScript.java @@ -83,6 +83,11 @@ private void miningLoop(GabulhasSandMinerConfig config) { sleep(100, 4000); } while (!Rs2Inventory.isFull() && super.isRunning()) { + // Drop empty waterskins if not using humidify (only when idle) + if (!config.useHumidify() && !Rs2Player.isAnimating()) { + dropEmptyWaterskins(); + } + while (Rs2Player.hopIfPlayerDetected(1, 3000, 100) && super.isRunning()) { sleepUntil(() -> Microbot.getClient().getGameState() == GameState.HOPPING); sleepUntil(() -> Microbot.getClient().getGameState() == GameState.LOGGED_IN); @@ -131,6 +136,12 @@ private void humidifyIfNeeded() { } } + private void dropEmptyWaterskins() { + while (Rs2Inventory.hasItem(ItemID.WATER_SKIN0)) { + Rs2Inventory.drop(ItemID.WATER_SKIN0); + } + } + private void deposit(GabulhasSandMinerConfig config) { if (!config.turboMode()) Rs2Walker.walkTo(grinder); GameObject sandstoneRock = Rs2GameObject.findObject(26199, grinder); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/github/GithubConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/github/GithubConfig.java deleted file mode 100644 index 07f51a1616d..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/github/GithubConfig.java +++ /dev/null @@ -1,26 +0,0 @@ -package net.runelite.client.plugins.microbot.github; - -import net.runelite.client.config.Config; -import net.runelite.client.config.ConfigGroup; -import net.runelite.client.config.ConfigInformation; -import net.runelite.client.config.ConfigItem; - -@ConfigGroup(GithubConfig.GROUP) -@ConfigInformation( - "• Loads jar files from github repository." -) -public interface GithubConfig extends Config { - String GROUP = "GithubPlugin"; - - - @ConfigItem( - keyName = "repoUrls", - name = "My repoUrls", - description = "Comma-separated list of options" - ) - default String repoUrls() - { - return "https://github.com/chsami/microbot"; - } - -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/github/GithubDownloader.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/github/GithubDownloader.java deleted file mode 100644 index 475f5bac96f..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/github/GithubDownloader.java +++ /dev/null @@ -1,227 +0,0 @@ -package net.runelite.client.plugins.microbot.github; - -import lombok.extern.slf4j.Slf4j; -import net.runelite.client.RuneLite; -import net.runelite.client.plugins.microbot.github.models.FileInfo; -import net.runelite.client.plugins.microbot.github.models.GithubRepoInfo; -import org.jetbrains.annotations.Nullable; -import org.json.JSONArray; -import org.json.JSONObject; - -import javax.swing.*; -import java.io.*; -import java.net.HttpURLConnection; -import java.net.URL; -import java.net.URLEncoder; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; - -/** - * Downloads files from a GitHub repository using the GitHub API. - */ -@Slf4j -public class GithubDownloader { - // GitHub API URL format: replace {owner}, {repo}, and {path} - private static final String GITHUB_API_URL = "https://api.github.com/repos/%s/%s/contents/%s"; - - - /** - * Makes an HTTP GET request and returns the response as a String. - */ - private static String get(String urlStr, String token) throws Exception { - URL url = new URL(urlStr); - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - // Set headers to ensure GitHub returns JSON in the v3 format - connection.setRequestProperty("Accept", "application/vnd.github.v3+json"); - connection.setRequestProperty("User-Agent", "Java-GitHubDownloader"); - if (!token.isEmpty()) { - connection.setRequestProperty("Authorization", "Bearer " + token); - } - - BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); - StringBuilder response = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) { - response.append(line); - } - reader.close(); - return response.toString(); - } - - /** - * Downloads a file from the given URL and saves it to the specified destination. - */ - public static void downloadFile(String downloadUrl) throws Exception { - URL url = new URL(downloadUrl); - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - connection.setRequestProperty("User-Agent", "Java-GitHubDownloader"); - String filename = url.toString().substring(url.toString().lastIndexOf('/') + 1); - InputStream in = connection.getInputStream(); - OutputStream out = new FileOutputStream(Paths.get(RuneLite.RUNELITE_DIR + "/microbot-plugins/" + filename).toString()); - - byte[] buffer = new byte[4096]; - int bytesRead; - while ((bytesRead = in.read(buffer)) != -1) { - out.write(buffer, 0, bytesRead); - } - - in.close(); - out.close(); - } - - /** - * Fetches the files in a GitHub repository folder. - * - * @param url - * @param folder - * @return - */ - public static String fetchFiles(String url, String folder, String token) { - try { - GithubRepoInfo repoInfo = new GithubRepoInfo(url); - String apiUrl = String.format(GITHUB_API_URL, repoInfo.getOwner(), repoInfo.getRepo(), folder); - - HttpURLConnection conn = (HttpURLConnection) new URL(apiUrl).openConnection(); - conn.setRequestProperty("Accept", "application/vnd.github.v3+json"); - if (!token.isEmpty()) { - conn.setRequestProperty("Authorization", "Bearer " + token); - } - - checkRateLimit(conn); - String json = new String(conn.getInputStream().readAllBytes()); - - return json; - } catch (Exception ex) { - log.error(ex.getMessage()); - } - return ""; - } - - @Nullable - private static String checkRateLimit(HttpURLConnection conn) throws IOException { - int responseCode = conn.getResponseCode(); - if (responseCode == 403) { - // read the error stream and show it - String errorMsg = readStream(conn.getErrorStream()); - JOptionPane.showConfirmDialog(null, "GitHub API error (403):\n" + errorMsg, "Error", - JOptionPane.DEFAULT_OPTION); - } - return ""; - } - - private static String readStream(InputStream stream) { - if (stream == null) return "No error message received."; - try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) { - StringBuilder sb = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) { - sb.append(line).append("\n"); - } - return sb.toString().trim(); - } catch (IOException e) { - return "Failed to read error message: " + e.getMessage(); - } - } - - /** - * Checks if the repository is too large to download. - * - * @param url - * @return - */ - public static boolean isLargeRepo(String url, String token) { - try { - var githubRepoInfo = new GithubRepoInfo(url); - - String apiUrl = String.format("https://api.github.com/repos/%s/%s", - URLEncoder.encode(githubRepoInfo.getOwner(), "UTF-8"), URLEncoder.encode(githubRepoInfo.getRepo(), "UTF-8")); - - HttpURLConnection conn = (HttpURLConnection) new URL(apiUrl).openConnection(); - conn.setRequestProperty("Accept", "application/vnd.github.v3+json"); - if (!token.isEmpty()) { - conn.setRequestProperty("Authorization", "Bearer " + token); - } - - if (conn.getResponseCode() != 200) { - return false; // can't determine size, assume it's fine - } - - checkRateLimit(conn); - String json = new String(conn.getInputStream().readAllBytes()); - JSONObject obj = new JSONObject(json); - int sizeKB = obj.getInt("size"); // GitHub gives size in kilobytes - double sizeMB = sizeKB / 1024.0; - - if (sizeMB > 50) { - return true; - } - } catch (Exception e) { - log.error("Failed to check repo size: " + e.getMessage()); - } - return false; - } - - /** - * Fetches all files in a GitHub repository folder recursively. - * - * @param url - * @param path - * @return - */ - public static List getAllFilesRecursively(String url, String path, String token) { - List files = new ArrayList<>(); - fetchRecursive(url, path, token, files); - return files; - } - - /** - * Fetches all files in a GitHub repository folder recursively. - * - * @param url - * @param path - * @param files - */ - public static void fetchRecursive(String url, String path, String token, List files) { - try { - GithubRepoInfo repoInfo = new GithubRepoInfo(url); - String apiUrl = String.format("https://api.github.com/repos/%s/%s/contents/%s", - URLEncoder.encode(repoInfo.getOwner(), "UTF-8"), - URLEncoder.encode(repoInfo.getRepo(), "UTF-8"), - URLEncoder.encode(path, "UTF-8")); - - HttpURLConnection conn = (HttpURLConnection) new URL(apiUrl).openConnection(); - conn.setRequestProperty("Accept", "application/vnd.github.v3+json"); - if (!token.isEmpty()) { - conn.setRequestProperty("Authorization", "Bearer " + token); - } - - if (conn.getResponseCode() != 200) { - System.err.println("Failed to fetch: " + apiUrl + " (HTTP " + conn.getResponseCode() + ")"); - return; - } - - checkRateLimit(conn); - String json = new String(conn.getInputStream().readAllBytes()); - JSONArray items = new JSONArray(json); - - for (int i = 0; i < items.length(); i++) { - JSONObject obj = items.getJSONObject(i); - String type = obj.getString("type"); - - if (type.equals("file")) { - String name = obj.getString("name"); - String downloadUrl = obj.getString("download_url"); - - files.add(new FileInfo(name, downloadUrl)); - } else if (type.equals("dir")) { - String subPath = obj.getString("path"); // full subfolder path - fetchRecursive(url, subPath, token, files); // recurse - } - } - } catch (Exception e) { - log.error("Error in fetchRecursive: " + e.getMessage()); - } - } - -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/github/GithubPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/github/GithubPanel.java deleted file mode 100644 index 9f4ae943782..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/github/GithubPanel.java +++ /dev/null @@ -1,378 +0,0 @@ -package net.runelite.client.plugins.microbot.github; - -import lombok.SneakyThrows; -import net.runelite.client.RuneLite; -import net.runelite.client.config.ConfigManager; -import net.runelite.client.plugins.microbot.externalplugins.MicrobotPluginManager; -import net.runelite.client.plugins.microbot.github.models.FileInfo; -import net.runelite.client.ui.ColorScheme; -import net.runelite.client.ui.PluginPanel; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import javax.inject.Inject; -import javax.swing.*; -import java.awt.*; -import java.io.File; -import java.io.IOException; -import java.util.*; -import java.util.List; -import java.util.stream.Collectors; - -import static javax.swing.JOptionPane.showMessageDialog; - -public class GithubPanel extends PluginPanel { - - private final JComboBox repoDropdown = new JComboBox(); - private final JTextField folderField = new JTextField("", 10); - private final JTextField tokenField = new JTextField("", 10); - - private final DefaultListModel listModel = new DefaultListModel(); - private final JList fileList = new JList<>(listModel); - - @Inject - MicrobotPluginManager microbotPluginManager; - - @Inject - ConfigManager configManager; - - GithubPlugin plugin; - - @Inject - public GithubPanel(GithubPlugin plugin) { - this.plugin = plugin; - - // Top panel for inputs - // Keep BoxLayout - JPanel inputPanel = new JPanel(new GridBagLayout()); - - GridBagConstraints gbc = new GridBagConstraints(); - - - gbc.insets = new Insets(2, 2, 2, 2); // Add some padding - gbc.fill = GridBagConstraints.HORIZONTAL; - gbc.weightx = 1.0; - gbc.gridwidth = GridBagConstraints.REMAINDER; - gbc.anchor = GridBagConstraints.WEST; - - GridBagConstraints gbci = new GridBagConstraints(); - - - gbci.insets = new Insets(10, 2, 10, 2); // Add some padding - gbci.fill = GridBagConstraints.HORIZONTAL; - gbci.weightx = 1.0; - gbci.gridwidth = GridBagConstraints.REMAINDER; - gbci.anchor = GridBagConstraints.WEST; - - inputPanel.add(new JLabel("Repo Url:*"), gbc); - repoDropdown.setBorder(BorderFactory.createLineBorder(ColorScheme.BRAND_ORANGE)); - inputPanel.add(repoDropdown, gbci); - for (String option : getOptionsList()) { - repoDropdown.addItem(option); - } - - - inputPanel.add(new JLabel("Folder: (empty = root folder)"), gbc); - folderField.setBorder(BorderFactory.createLineBorder(ColorScheme.BRAND_ORANGE)); - - inputPanel.add(folderField, gbci); - - inputPanel.add(new JLabel("Token:"), gbc); - tokenField.setBorder(BorderFactory.createLineBorder(ColorScheme.BRAND_ORANGE)); - inputPanel.add(tokenField, gbci); - inputPanel.add(new JLabel(""), gbci); - - - // Button panel - JPanel buttonPanel = new JPanel(new GridLayout(5, 1, 10, 10)); - JButton addRepoButton = new JButton("Add Repo Url"); - addRepoButton.setBorder(BorderFactory.createLineBorder(ColorScheme.BRAND_ORANGE)); - JButton deleteRepoButton = new JButton("Delete Repo Url"); - deleteRepoButton.setBorder(BorderFactory.createLineBorder(ColorScheme.PROGRESS_ERROR_COLOR)); - JButton fetchButton = new JButton("Fetch from GitHub"); - fetchButton.setBorder(BorderFactory.createLineBorder(ColorScheme.BRAND_ORANGE)); - JButton downloadButton = new JButton("Download Selected"); - downloadButton.setBorder(BorderFactory.createLineBorder(ColorScheme.BRAND_ORANGE)); - JButton downloadAllButton = new JButton("Download All"); - downloadAllButton.setBorder(BorderFactory.createLineBorder(ColorScheme.BRAND_ORANGE)); - JButton openMicrobotSideLoadPluginFolder = new JButton("Open folder"); - openMicrobotSideLoadPluginFolder.setBorder(BorderFactory.createLineBorder(ColorScheme.BRAND_ORANGE)); - buttonPanel.add(addRepoButton); - buttonPanel.add(deleteRepoButton); - buttonPanel.add(fetchButton); - buttonPanel.add(downloadButton); - buttonPanel.add(downloadAllButton); - buttonPanel.add(openMicrobotSideLoadPluginFolder); - buttonPanel.add(new JLabel("")); - - // Main layout - setLayout(new BorderLayout()); - add(inputPanel, BorderLayout.NORTH); - add(buttonPanel, BorderLayout.CENTER); - add(new JScrollPane(fileList), BorderLayout.SOUTH); - - - fileList.setCellRenderer(new DefaultListCellRenderer() { - @Override - public Component getListCellRendererComponent(JList list, Object value, int index, - boolean isSelected, boolean cellHasFocus) { - JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); - if (value instanceof FileInfo) { - FileInfo fileInfo = (FileInfo) value; - File localFile = new File(RuneLite.RUNELITE_DIR, "microbot-plugins/" + fileInfo.getName()); - boolean exists = localFile.exists(); - - if (exists) { - label.setText("✔ " + fileInfo.getName()); - label.setForeground(Color.GREEN.darker()); - } else { - label.setText(fileInfo.getName()); - label.setForeground(isSelected ? list.getSelectionForeground() : list.getForeground()); - } - } - return label; - } - }); - - // Button actions - addRepoButton.addActionListener(e -> addRepoUrl()); - deleteRepoButton.addActionListener(e -> deleteRepoUrl()); - fetchButton.addActionListener(e -> fetchFiles()); - downloadButton.addActionListener(e -> downloadSelected()); - downloadAllButton.addActionListener(e -> downloadAll()); - openMicrobotSideLoadPluginFolder.addActionListener(e -> openMicrobotSideLoadingFolder()); - - } - - /** - * Deletes a repository URL from the dropdown and saves the updated list to the configuration. - */ - private void deleteRepoUrl() { - String selected = (String) repoDropdown.getSelectedItem(); - if (selected != null) { - int confirm = JOptionPane.showConfirmDialog(this, - "Are you sure you want to delete the selected repository URL?", - "Confirm Deletion", - JOptionPane.YES_NO_OPTION); - - if (confirm == JOptionPane.YES_OPTION) { - List currentItems = getOptionsList(); - currentItems.remove(selected); - String updatedConfig = String.join(",", currentItems); - - configManager.setConfiguration("GithubPlugin", "repoUrls", updatedConfig); - repoDropdown.removeItem(selected); - } - } - } - - /** - * Adds a repository URL to the dropdown and saves it to the configuration. - */ - private void addRepoUrl() { - String url = JOptionPane.showInputDialog(this, "Enter the repository URL:"); - if (url != null && !url.isEmpty()) { - repoDropdown.addItem(url); - repoDropdown.setSelectedItem(url); - List currentItems = getOptionsList(); - currentItems.add(url); - String updatedConfig = String.join(",", currentItems); - - configManager.setConfiguration("GithubPlugin", "repoUrls", updatedConfig); - } - } - - /** - * Deletes all files in the downloads directory. - */ - private void openMicrobotSideLoadingFolder() { - String userHome = System.getProperty("user.home"); - File folder = new File(userHome, ".runelite/microbot-plugins"); - - if (folder.exists()) { - try { - Desktop.getDesktop().open(folder); - } catch (IOException e) { - System.err.println("Failed to open folder: " + e.getMessage()); - } - } else { - System.err.println("Folder does not exist: " + folder.getAbsolutePath()); - } - } - - /** - * Downloads all files in the specified GitHub repository folder. - */ - @SneakyThrows - private void downloadAll() { - if (!isRepoSelected()) return; - - if (listModel.isEmpty()) { - showMessageDialog(this, "No files to download.", "Error", JOptionPane.ERROR_MESSAGE); - return; - } - - if (folderField.getText().isEmpty() && GithubDownloader.isLargeRepo(Objects.requireNonNull(repoDropdown.getSelectedItem()).toString(), tokenField.getText())) { - int choice = JOptionPane.showConfirmDialog(this, - "⚠ The repository is over 50MB.\nAre you sure you want to continue?", - "Large Repository", - JOptionPane.YES_NO_OPTION); - if (choice != JOptionPane.YES_OPTION) { - return; - } - } - Window parentWindow = SwingUtilities.getWindowAncestor(this); - JDialog dialog = createLoadingDialog(parentWindow); - - List allFiles = GithubDownloader.getAllFilesRecursively(Objects.requireNonNull(repoDropdown.getSelectedItem()).toString(), folderField.getText(), tokenField.getText()); - - dialog.setVisible(false); - parentWindow.remove(dialog); - // Create progress dialog - JDialog progressDialog = new JDialog(parentWindow, "loader message...", Dialog.ModalityType.APPLICATION_MODAL); - JProgressBar progressBar = new JProgressBar(0, allFiles.size()); - progressBar.setStringPainted(true); - progressDialog.add(progressBar); - progressDialog.setSize(300, 75); - progressDialog.setLocationRelativeTo(this); - - List downloadedPlugins = new ArrayList<>(); - - // Background task - SwingWorker worker = new SwingWorker<>() { - @SneakyThrows - @Override - protected Void doInBackground() { - for (int i = 0; i < allFiles.size(); i++) { - FileInfo fileInfo = allFiles.get(i); - String downloadUrl = fileInfo.getUrl(); - String fileName = fileInfo.getName(); - System.out.println("Downloading file: " + fileName); - GithubDownloader.downloadFile(downloadUrl); - publish(i + 1); - downloadedPlugins.add(fileName); - } - return null; - } - - @Override - protected void process(List chunks) { - int latest = chunks.get(chunks.size() - 1); - progressBar.setValue(latest); - } - - @SneakyThrows - @Override - protected void done() { - progressDialog.dispose(); - fileList.repaint(); // update any downloaded indicators - JOptionPane.showMessageDialog(parentWindow, "All files downloaded.", "Download Successful!", - JOptionPane.INFORMATION_MESSAGE); - } - }; - - worker.execute(); - progressDialog.setVisible(true); // blocks until worker finishes - microbotPluginManager.saveInstalledPlugins(downloadedPlugins); - microbotPluginManager.loadSideLoadPlugins(); - - } - - /** - * Checks if a repository URL is selected. - * - * @return - */ - private boolean isRepoSelected() { - if (repoDropdown.getSelectedItem() == null) { - showMessageDialog(this, "Please select a repository URL.", "Error", JOptionPane.ERROR_MESSAGE); - return false; - } - return true; - } - - /** - * Downloads the selected files in the list. - */ - @SneakyThrows - private void downloadSelected() { - if (!isRepoSelected()) return; - - if (listModel.isEmpty()) { - showMessageDialog(this, "No files to download.", "Error", JOptionPane.ERROR_MESSAGE); - return; - } - - List selectedFileInfoList = fileList.getSelectedValuesList(); - for (FileInfo fileInfo : selectedFileInfoList) { - GithubDownloader.downloadFile(fileInfo.getUrl()); - } - showMessageDialog(this, "Restart the client so the plugin(s) get shown", "Information", JOptionPane.INFORMATION_MESSAGE); - fileList.repaint(); - } - - /** - * Fetches the files in the specified GitHub repository folder and adds them to the list. - */ - private void fetchFiles() { - if (!isRepoSelected()) return; - try { - String json = GithubDownloader.fetchFiles(Objects.requireNonNull(repoDropdown.getSelectedItem()).toString(), folderField.getText(), tokenField.getText()); - JSONArray arr = new JSONArray(json); - - listModel.clear(); - for (int i = 0; i < arr.length(); i++) { - JSONObject obj = arr.getJSONObject(i); - if (obj.getString("type").equals("file") && obj.getString("name").endsWith(".jar")) { - String fileName = obj.getString("name"); - String downloadUrl = obj.getString("download_url"); - listModel.addElement(new FileInfo(fileName, downloadUrl)); - } - } - if (listModel.isEmpty()) { - showMessageDialog(this, "No jar files found in repository.", "Error", JOptionPane.ERROR_MESSAGE); - } - } catch (JSONException ex) { - // show dialog box with message failed - showMessageDialog(this, "Failed to fetch files from repository.", "Error", JOptionPane.ERROR_MESSAGE); - } - } - - /** - * Creates a loading dialog with a progress bar. - * - * @param parent - * @return - */ - private JDialog createLoadingDialog(Window parent) { - JDialog dialog = new JDialog(parent, "Please wait...", Dialog.ModalityType.APPLICATION_MODAL); - dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); - dialog.setSize(300, 100); - dialog.setLayout(new BorderLayout()); - - JLabel label = new JLabel("Scanning Repo...", SwingConstants.CENTER); - JProgressBar progressBar = new JProgressBar(); - progressBar.setIndeterminate(true); - - dialog.add(label, BorderLayout.NORTH); - dialog.add(progressBar, BorderLayout.CENTER); - dialog.setLocationRelativeTo(parent); - return dialog; - } - - /** - * Gets the list of options from the configuration. - * - * @return - */ - private List getOptionsList() { - String raw = plugin.config.repoUrls(); - if (raw == null || raw.isEmpty()) { - return Collections.emptyList(); - } - return Arrays.stream(raw.split(",")) - .map(String::trim) - .collect(Collectors.toList()); - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/github/GithubPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/github/GithubPlugin.java deleted file mode 100644 index f2999b28211..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/github/GithubPlugin.java +++ /dev/null @@ -1,55 +0,0 @@ -package net.runelite.client.plugins.microbot.github; - -import com.google.inject.Provides; -import lombok.extern.slf4j.Slf4j; -import net.runelite.client.config.ConfigManager; -import net.runelite.client.plugins.Plugin; -import net.runelite.client.plugins.PluginDescriptor; -import net.runelite.client.ui.ClientToolbar; -import net.runelite.client.ui.NavigationButton; -import net.runelite.client.util.ImageUtil; - -import javax.inject.Inject; -import java.awt.image.BufferedImage; - -@PluginDescriptor( - name = "Github plugin", - description = "Allows to download plugins from a github and sideload them", - tags = {"github", "microbot"}, - enabledByDefault = false, - hidden = true -) -@Slf4j -public class GithubPlugin extends Plugin { - - GithubPanel panel; - NavigationButton navButton; - @Inject - ClientToolbar clientToolbar; - - @Inject - public GithubConfig config; - - @Provides - GithubConfig provideConfig(ConfigManager configManager) { - return configManager.getConfig(GithubConfig.class); - } - - @Override - protected void startUp() { - panel = injector.getInstance(GithubPanel.class); - final BufferedImage icon = ImageUtil.loadImageResource(GithubPlugin.class, "github_icon.png"); - navButton = NavigationButton.builder() - .tooltip("Github Repository") - .icon(icon) - .priority(8) - .panel(panel) - .build(); - clientToolbar.addNavigation(navButton); - } - - @Override - protected void shutDown() { - System.out.println("Github plugin stopped"); - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/github/models/FileInfo.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/github/models/FileInfo.java deleted file mode 100644 index 9f63da55e12..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/github/models/FileInfo.java +++ /dev/null @@ -1,24 +0,0 @@ -package net.runelite.client.plugins.microbot.github.models; - -public class FileInfo { - private final String name; - private final String url; - - public FileInfo(String name, String url) { - this.name = name; - this.url = url; - } - - public String getName() { - return name; - } - - public String getUrl() { - return url; - } - - @Override - public String toString() { - return name; // this is what gets shown in the JList - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/github/models/GithubRepoInfo.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/github/models/GithubRepoInfo.java deleted file mode 100644 index c8c148e1b8d..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/github/models/GithubRepoInfo.java +++ /dev/null @@ -1,27 +0,0 @@ -package net.runelite.client.plugins.microbot.github.models; - -import lombok.Getter; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class GithubRepoInfo { - @Getter - private final String owner; - @Getter - private final String repo; - - public GithubRepoInfo(String url) { - Pattern pattern = Pattern.compile("github\\.com/([^/]+)/([^/]+)"); - Matcher matcher = pattern.matcher(url); - if (matcher.find()) { - String owner = matcher.group(1); - String repo = matcher.group(2); - this.owner = owner; - this.repo = repo; - } else { - this.owner = ""; - this.repo = ""; - } - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaConfig.java deleted file mode 100644 index 6605ec2ea6b..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaConfig.java +++ /dev/null @@ -1,96 +0,0 @@ -package net.runelite.client.plugins.microbot.magetrainingarena; - -import net.runelite.client.config.*; -import net.runelite.client.plugins.microbot.magetrainingarena.enums.*; - -@ConfigGroup("mta") -@ConfigInformation("- Enable the official RuneLite plugin 'Mage Training Arena'
    " + - "
    " + - "- Configure staves and tomes, make sure you can equip them.
    " + - "- Staves, Tomes, Laws, Cosmic and Nature runes in inventory only, No rune pouch!
    " + - "
    " + - "- T6 enchant requires Lava staff OR Tome of Fire and any Earth staff.
    " + - "- T5 enchant requires Tome of Water and any Earth staff, OR either water/earth runes.
    "+ - "
    " + - "- When set to buy rewards, the rooms are cycled until the points are met, the reward will be stored in your bank.
    " + - "- If not set to buy rewards, the rooms are cycled as if it would buy the rewards then continues cycling the rooms afterwards.
    " + - "
    " + - "- 'All items' will get enough points for you to finish Collection Log'" + - "
    " + - " For repeat rooms functionality to work the requirements must be met and you must be in the room you wish to repeat.") -public interface MageTrainingArenaConfig extends Config { - @ConfigSection( - name = "Rooms", - description = "Magic Training Arena Rooms", - position = 1 - ) - String roomSection = "rooms"; - - @ConfigItem( - keyName = "repeatRoom", - name = "Repeat Room", - description = "Determines whether the bot should repeat the current room.", - position = 0, - section = roomSection - ) - default boolean repeatRoom() { return false; } - - @ConfigSection( - name = "Rewards", - description = "Rewards", - position = 2 - ) - String rewardsSection = "rewards"; - - @ConfigSection( - name = "Graveyard", - description = "Graveyard", - position = 3, - closedByDefault = true - ) - String graveyardSection = "graveyard"; - - @ConfigItem( - keyName = "Buy rewards", - name = "Buy rewards", - description = "Determines whether the bot should buy the selected reward.", - position = 1, - section = rewardsSection - ) - default boolean buyRewards() { - return true; - } - - @ConfigItem( - keyName = "Reward", - name = "Reward", - description = "The reward to aim for.", - position = 2, - section = rewardsSection - ) - default Rewards reward() { - return Rewards.BONES_TO_PEACHES; - } - - @ConfigItem( - keyName = "Healing threshold (min)", - name = "Healing threshold (min)", - description = "Each time the bot eats it chooses a random threshold (between min and max value) to eat at next time.", - position = 3, - section = graveyardSection - ) - default int healingThresholdMin() { - return 40; - } - - @ConfigItem( - keyName = "Healing threshold (max)", - name = "Healing threshold (max)", - description = "Each time the bot eats it chooses a random threshold (between min and max value) to eat at next time.", - position = 4, - section = graveyardSection - ) - default int healingThresholdMax() { - return 70; - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaOverlay.java deleted file mode 100644 index 68ab44ac6e9..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaOverlay.java +++ /dev/null @@ -1,79 +0,0 @@ -package net.runelite.client.plugins.microbot.magetrainingarena; - -import net.runelite.client.plugins.microbot.Microbot; -import net.runelite.client.ui.overlay.OverlayPanel; -import net.runelite.client.ui.overlay.OverlayPosition; -import net.runelite.client.ui.overlay.components.*; - -import javax.inject.Inject; -import java.awt.*; - -public class MageTrainingArenaOverlay extends OverlayPanel { - MageTrainingArenaConfig config; - - @Inject - MageTrainingArenaOverlay(MageTrainingArenaPlugin plugin, MageTrainingArenaConfig config) - { - super(plugin); - setPosition(OverlayPosition.TOP_LEFT); - setPriority(PRIORITY_HIGHEST); - setNaughty(); - - this.config = config; - } - @Override - public Dimension render(Graphics2D graphics) { - try { - panelComponent.setPreferredSize(new Dimension(200, 300)); - panelComponent.getChildren().add(TitleComponent.builder() - .text("Basche's Mage Training Arena " + MageTrainingArenaScript.version) - .color(Color.GREEN) - .build()); - - panelComponent.getChildren().add(LineComponent.builder().build()); - - if (!Microbot.getPluginManager().isActive(MageTrainingArenaScript.getMtaPlugin())){ - panelComponent.getChildren().add(LineComponent.builder() - .left("Make sure to enable the 'Mage Training Arena' plugin!") - .leftColor(Color.RED) - .build()); - } else { - panelComponent.getChildren().add(LineComponent.builder() - .left("Room: " + (MageTrainingArenaScript.getCurrentRoom() != null ? MageTrainingArenaScript.getCurrentRoom() : "-")) - .build()); - panelComponent.getChildren().add(LineComponent.builder() - .left("Reward: " + config.reward()) - .build()); - - panelComponent.getChildren().add(LineComponent.builder().build()); - for (var points : MageTrainingArenaScript.getCurrentPoints().entrySet()){ - var rewardPoints = MageTrainingArenaScript.getRequiredPoints(config).get(points.getKey()); - panelComponent.getChildren().add(LineComponent.builder() - .left(String.format("%s: %d / %d", points.getKey(), points.getValue(), rewardPoints)) - .build()); - } - - panelComponent.getChildren().add(LineComponent.builder().build()); - - double progress = 0; - for (var points : MageTrainingArenaScript.getCurrentPoints().entrySet()){ - var rewardPoints = MageTrainingArenaScript.getRequiredPoints(config).get(points.getKey()); - progress += Math.min((double) (points.getValue() - (config.buyRewards() ? 0 : MageTrainingArenaScript.getBuyable()) * rewardPoints) / rewardPoints, 1) * 25; - } - - - if (config.buyRewards() && MageTrainingArenaScript.getBought() > 0) - panelComponent.getChildren().add(LineComponent.builder().left("Bought: " + MageTrainingArenaScript.getBought()).build()); - else if (!config.buyRewards() && MageTrainingArenaScript.getBuyable() > 0) - panelComponent.getChildren().add(LineComponent.builder().left("Buyable: " + MageTrainingArenaScript.getBuyable()).build()); - - var progressBar = new ProgressBarComponent(); - progressBar.setValue(progress); - panelComponent.getChildren().add(progressBar); - } - } catch(Exception ex) { - System.out.println(ex.getMessage()); - } - return super.render(graphics); - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaPlugin.java deleted file mode 100644 index 091c6aeb017..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaPlugin.java +++ /dev/null @@ -1,50 +0,0 @@ -package net.runelite.client.plugins.microbot.magetrainingarena; - -import com.google.inject.Provides; -import lombok.extern.slf4j.Slf4j; -import net.runelite.client.config.ConfigManager; -import net.runelite.client.plugins.Plugin; -import net.runelite.client.plugins.PluginDescriptor; -import net.runelite.client.ui.overlay.OverlayManager; - -import javax.inject.Inject; -import java.awt.*; - -@PluginDescriptor( - name = PluginDescriptor.Basche + "Mage Training Arena", - description = "Basche's Mage Training Arena plugin", - tags = {"basche", "mta", "moneymaking"}, - enabledByDefault = false -) -@Slf4j -public class MageTrainingArenaPlugin extends Plugin { - @Inject - private MageTrainingArenaConfig config; - @Provides - MageTrainingArenaConfig provideConfig(ConfigManager configManager) { - return configManager.getConfig(MageTrainingArenaConfig.class); - } - - @Inject - private OverlayManager overlayManager; - @Inject - private MageTrainingArenaOverlay overlay; - - @Inject - MageTrainingArenaScript script; - - - @Override - protected void startUp() throws AWTException { - if (overlayManager != null) { - overlayManager.add(overlay); - } - - script.run(config); - } - - protected void shutDown() { - script.shutdown(); - overlayManager.remove(overlay); - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaScript.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaScript.java deleted file mode 100644 index d84432d0917..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaScript.java +++ /dev/null @@ -1,741 +0,0 @@ -package net.runelite.client.plugins.microbot.magetrainingarena; - -import lombok.Getter; -import net.runelite.api.EquipmentInventorySlot; -import net.runelite.api.Skill; -import net.runelite.api.gameval.*; -import net.runelite.api.coords.LocalPoint; -import net.runelite.api.coords.WorldPoint; -import net.runelite.client.plugins.microbot.Microbot; -import net.runelite.client.plugins.microbot.Script; -import net.runelite.client.plugins.microbot.globval.enums.InterfaceTab; -import net.runelite.client.plugins.microbot.magetrainingarena.enums.*; -import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; -import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; -import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; -import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; -import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; -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.inventory.Rs2RunePouch; -import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; -import net.runelite.client.plugins.microbot.util.magic.*; -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.npc.Rs2NpcModel; -import net.runelite.client.plugins.microbot.util.player.Rs2Player; -import net.runelite.client.plugins.microbot.util.tabs.Rs2Tab; -import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; -import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; -import net.runelite.client.plugins.mta.MTAPlugin; -import net.runelite.client.plugins.mta.alchemy.AlchemyRoomTimer; -import net.runelite.client.plugins.mta.telekinetic.TelekineticRoom; -import net.runelite.client.plugins.skillcalculator.skills.MagicAction; - -import java.awt.event.KeyEvent; -import java.util.*; -import java.util.concurrent.TimeUnit; -import java.util.function.Predicate; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; - -import static net.runelite.client.plugins.microbot.util.magic.Rs2Magic.getRs2Staff; -import static net.runelite.client.plugins.microbot.util.magic.Rs2Magic.getRs2Tome; - -public class MageTrainingArenaScript extends Script { - public static String version = "1.1.4"; - - private static boolean firstTime = false; - - private static final WorldPoint portalPoint = new WorldPoint(3363, 3318, 0); - private static final WorldPoint bankPoint = new WorldPoint(3365, 3318, 1); - - private MageTrainingArenaConfig config; - private Rooms currentRoom; - private int nextHpThreshold = 50; - private Boolean btp = null; - private int shapesToPick = 3; - - @Getter - private static MTAPlugin mtaPlugin; - @Getter - private static final Map currentPoints = Arrays.stream(Points.values()).collect(Collectors.toMap(x -> x, x -> -1)); - @Getter - private static int bought; - @Getter - private static int buyable; - - public boolean run(MageTrainingArenaConfig config) { - this.config = config; - Microbot.log(String.format("repeatRoom: %s", config.repeatRoom())); - Microbot.enableAutoRunOn = true; - bought = 0; - buyable = 0; - Rs2Walker.disableTeleports = true; - mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { - try { - if (!Microbot.isLoggedIn()) return; - if (!super.run()) return; - if (mtaPlugin != null && !Microbot.getPluginManager().isActive(mtaPlugin)) return; - - if (!Rs2Magic.isSpellbook(Rs2Spellbook.MODERN)) { - Microbot.log("Wrong spellbook found...please use the modern spellbook for this script."); - sleep(5000); - return; - } - - if (mtaPlugin == null) { - if (Microbot.getPluginManager() == null) return; - - mtaPlugin = (MTAPlugin) Microbot.getPluginManager().getPlugins() - .stream().filter(x -> x instanceof net.runelite.client.plugins.mta.MTAPlugin) - .findFirst().orElse(null); - - return; - } - - if (handleFirstTime()) - return; - - currentRoom = getCurrentRoom(); - updatePoints(); - if (initPoints()) - return; - - if (currentRoom == null) { - if (ensureInventory()) - return; - - if (currentPoints.entrySet().stream().allMatch(x -> getRequiredPoints().get(x.getKey()) * (config.buyRewards() ? 1 : (buyable + 1)) <= x.getValue())) { - if (config.buyRewards()) { - var rewardToBuy = config.reward(); - while (rewardToBuy.getPreviousReward() != null && !Rs2Inventory.contains(rewardToBuy.getPreviousReward().getItemId())) - rewardToBuy = rewardToBuy.getPreviousReward(); - - buyReward(rewardToBuy); - } else { - buyable = getRequiredPoints().entrySet().stream() - .mapToInt(x -> currentPoints.get(x.getKey()) / x.getValue()) - .min().orElseThrow(); - } - return; - } - - var missingPoints = currentPoints.entrySet().stream() - .filter(entry -> { - var required = getRequiredPoints().get(entry.getKey()) * (config.buyRewards() ? 1 : (buyable + 1)); - return required > entry.getValue() - && Arrays.stream(Rooms.values()) - .anyMatch(room -> room.getPoints() == entry.getKey() - && room.getRequirements().getAsBoolean()); - }) - .map(Map.Entry::getKey) - .collect(Collectors.toList()); - - if (!missingPoints.isEmpty()) { - var index = Rs2Random.between(0, missingPoints.size()); - var nextRooms = Arrays.stream(Rooms.values()) - .filter(room -> room.getPoints() == missingPoints.get(index)) - .collect(Collectors.toList()); - - enterRoom(nextRooms.get(0)); - } else { - Microbot.showMessage("MTA: Out of runes! Please restart the plugin after you restocked on runes."); - sleep(500); - shutdown(); - } - } else if (config.repeatRoom()) { - if (currentRoom != null) { - Microbot.log("Repeating room: " + currentRoom.name()); - switch (currentRoom) { - case ALCHEMIST: - handleAlchemistRoom(); - break; - case GRAVEYARD: - handleGraveyardRoom(); - break; - case ENCHANTMENT: - handleEnchantmentRoom(); - break; - case TELEKINETIC: - handleTelekineticRoom(); - break; - } - } - } else if (!currentRoom.getRequirements().getAsBoolean() - || currentPoints.get(currentRoom.getPoints()) >= getRequiredPoints().get(currentRoom.getPoints()) * (config.buyRewards() ? 1 : (buyable + 1))) { - leaveRoom(); - } else { - switch (currentRoom) { - case ALCHEMIST: - handleAlchemistRoom(); - break; - case GRAVEYARD: - handleGraveyardRoom(); - break; - case ENCHANTMENT: - handleEnchantmentRoom(); - break; - case TELEKINETIC: - handleTelekineticRoom(); - break; - } - } - - sleepGaussian(600, 150); - } catch (Exception ex) { - if (ex instanceof InterruptedException) - return; - - System.out.println(ex.getMessage()); - ex.printStackTrace(System.out); - Microbot.log("MTA Exception: " + ex.getMessage()); - } - }, 0, 10, TimeUnit.MILLISECONDS); - return true; - } - - private boolean ensureInventory() { - var reward = config.reward(); - var previousRewards = new ArrayList(); - while (reward.getPreviousReward() != null) { - reward = reward.getPreviousReward(); - previousRewards.add(reward.getItemId()); - } - - Predicate additionalItemPredicate = x -> !x.getName().toLowerCase().contains("rune") - && !x.getName().toLowerCase().contains("staff") - && !x.getName().toLowerCase().contains("tome") - && !previousRewards.contains(x.getId()) - && !x.getName().toLowerCase().contains("bass"); - - if (Rs2Inventory.contains(additionalItemPredicate)) { - if (!Rs2Bank.walkToBankAndUseBank()) - return true; - - Rs2Bank.depositAll(additionalItemPredicate); - return true; - } - - return false; - } - - private boolean initPoints() { - if (currentPoints.values().stream().anyMatch(x -> x == -1)) { - if (currentRoom != null) - leaveRoom(); - else - Rs2Walker.walkTo(portalPoint); - - return true; - } - - return false; - } - - private void updatePoints() { - for (var points : currentPoints.entrySet()) { - int gain = 0; - if (points.getKey() == Points.ALCHEMIST && currentRoom == Rooms.ALCHEMIST && Rs2Inventory.hasItem("Coins")) - gain = Rs2Inventory.get("Coins").getQuantity() / 100; - - var widget = Rs2Widget.getWidget(points.getKey().getWidgetId(), points.getKey().getChildId()); - if (widget != null && !Microbot.getClientThread().runOnClientThreadOptional(widget::isHidden).orElse(false)) - currentPoints.put(points.getKey(), Integer.parseInt(widget.getText().replace(",", ""))); - else { - var roomWidget = Rs2Widget.getWidget(points.getKey().getRoomWidgetId(), points.getKey().getRoomChildId()); - if (roomWidget != null) - currentPoints.put(points.getKey(), Integer.parseInt(roomWidget.getText().replace(",", "")) + gain); - } - } - } - - private Map getRequiredPoints() { - return getRequiredPoints(config); - } - - public static Map getRequiredPoints(MageTrainingArenaConfig config) { - var currentReward = config.reward(); - var requiredPoints = currentReward.getPoints().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - - while (currentReward.getPreviousReward() != null) { - currentReward = currentReward.getPreviousReward(); - if (Rs2Inventory.contains(currentReward.getItemId())) - break; - - for (var points : requiredPoints.entrySet()) - points.setValue(points.getValue() + currentReward.getPoints().get(points.getKey())); - } - - return requiredPoints; - } - - private void handleEnchantmentRoom() { - MagicAction enchant; - var magicLevel = Microbot.getClient().getBoostedSkillLevel(Skill.MAGIC); - - if (magicLevel >= 87 && currentRoom.getRequirements().getAsBoolean()) { - enchant = MagicAction.ENCHANT_ONYX_JEWELLERY; - } else if (magicLevel >= 68 && currentRoom.getRequirements().getAsBoolean()) { - enchant = MagicAction.ENCHANT_DRAGONSTONE_JEWELLERY; - } else if (magicLevel >= 57 && currentRoom.getRequirements().getAsBoolean()) { - enchant = MagicAction.ENCHANT_DIAMOND_JEWELLERY; - } else if (magicLevel >= 49 && currentRoom.getRequirements().getAsBoolean()) { - enchant = MagicAction.ENCHANT_RUBY_JEWELLERY; - } else if (magicLevel >= 27 && currentRoom.getRequirements().getAsBoolean()) { - enchant = MagicAction.ENCHANT_EMERALD_JEWELLERY; - } else { - enchant = MagicAction.ENCHANT_SAPPHIRE_JEWELLERY; - } - - if (areRoomRequirementsInvalid()) { - if (!config.repeatRoom()) { - leaveRoom(); - } - return; - } - - if (Rs2Inventory.isFull()) { - if (!Rs2Walker.walkTo(new WorldPoint(3363, 9640, 0))) - return; - - Rs2GameObject.interact(ObjectID.MAGICTRAINING_ENCHA_HOLE, "Deposit"); - Rs2Player.waitForWalking(); - return; - } - - boolean successFullLoot = Rs2GroundItem.loot(ItemID.MAGICTRAINING_DRAGONSTONE, 12) && Rs2Inventory.waitForInventoryChanges(5000); - - if (successFullLoot && Rs2Inventory.emptySlotCount() > 0) - return; - - var bonusShape = getBonusShape(); - if (bonusShape == null) return; - var object = Rs2GameObject.getGameObject(obj -> (obj.getId() == bonusShape.getObjectId()) && Rs2Camera.isTileOnScreen(obj)); - if (object == null) { - var index = Rs2Random.between(0, 4); - Rs2Walker.walkTo(new WorldPoint[]{ - new WorldPoint(3347, 9655, 0), - new WorldPoint(3378, 9655, 0), - new WorldPoint(3379, 9624, 0), - new WorldPoint(3346, 9624, 0) - }[index]); - Rs2Player.waitForWalking(); - return; - } - int itemId; - if (Rs2Inventory.contains(ItemID.MAGICTRAINING_DRAGONSTONE)) - itemId = ItemID.MAGICTRAINING_DRAGONSTONE; - else - itemId = bonusShape.getItemId(); - if (Rs2Inventory.contains(ItemID.MAGICTRAINING_DRAGONSTONE) || Rs2Inventory.count(itemId) >= shapesToPick) { - shapesToPick = Rs2Random.between(2, 4); - - Rs2Magic.cast(enchant); - sleepUntil(() -> Rs2Tab.getCurrentTab() == InterfaceTab.INVENTORY); - sleepGaussian(600, 150); - Rs2Inventory.interact(itemId); - - sleepUntil(() -> !Rs2Inventory.contains(itemId) || itemId != ItemID.MAGICTRAINING_DRAGONSTONE && bonusShape != getBonusShape(), 20000); - } else if (Rs2GameObject.interact(object, "Take-from")) { - Rs2Inventory.waitForInventoryChanges(1000); - } else if (Rs2Player.getWorldLocation().distanceTo(object.getWorldLocation()) > 10){ - Rs2Walker.walkFastCanvas(object.getWorldLocation()); - } - } - - private EnchantmentShapes getBonusShape() { - for (var shape : EnchantmentShapes.values()) - if (Rs2Widget.isWidgetVisible(shape.getWidgetId(), shape.getWidgetChildId())) - return shape; - - return null; - } - - private void handleTelekineticRoom() { - if (areRoomRequirementsInvalid()) { - if (!config.repeatRoom()) { - leaveRoom(); - } - return; - } - - var room = mtaPlugin.getTelekineticRoom(); - var teleRoom = Arrays.stream(TelekineticRooms.values()) - .filter(x -> Rs2Player.getWorldLocation().distanceTo(x.getArea()) == 0) - .findFirst().orElseThrow(); - - // Walk to maze if guardian is not visible - WorldPoint target; - if (room.getTarget() != null) - target = room.getTarget(); - else { - Rs2Walker.walkTo(teleRoom.getMaze(), 4); - sleepUntil(() -> room.getTarget() != null, 10_000); - // MageTrainingArenaScript is dependent on the official mage arena plugin of runelite - // In some cases it glitches out and target is not defined by an arrow, in this case we will reset them room - if (room.getTarget() == null) { - Microbot.log("Something seems wrong, room target was still not found...leaving room to reset."); - leaveRoom(); - return; - } - target = room.getTarget(); - sleep(400, 600); - } - - var localTarget = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), target); - var targetConverted = WorldPoint.fromLocalInstance(Microbot.getClient(), Objects.requireNonNull(localTarget)); - - if (Rs2Camera.getZoom() < 40 || Rs2Camera.getZoom() > 60) { - Rs2Camera.setZoom(Rs2Random.betweenInclusive(40,60)); - } - - if (room.getGuardian().getWorldLocation().equals(room.getFinishLocation())) { - sleepUntil(() -> room.getGuardian().getId() == NpcID.MAGICTRAINING_GUARD_MAZE_COMPLETE); - sleep(200, 400); - Rs2Npc.interact(new Rs2NpcModel(room.getGuardian()), "New-maze"); - sleepUntil(() -> Rs2Player.getWorldLocation().distanceTo(teleRoom.getArea()) != 0); - } else { - while (!Rs2Player.getWorldLocation().equals(targetConverted) - && (Microbot.getClient().getLocalDestinationLocation() == null - || !Microbot.getClient().getLocalDestinationLocation().equals(localTarget))) { - if (Rs2Camera.isTileOnScreen(localTarget) && Rs2Player.getWorldLocation().distanceTo(targetConverted) < 10) { - Rs2Walker.walkFastCanvas(targetConverted); - sleepGaussian(600, 150); - } else { - Rs2Walker.walkTo(targetConverted); - } - sleepUntil(() -> !Rs2Player.isMoving()); - } - - if (!Rs2Player.isAnimating() - && !Rs2Player.isMoving() - && StreamSupport.stream(Microbot.getClient().getProjectiles().spliterator(), false).noneMatch(x -> x.getId() == SpotanimID.TELEGRAB_TRAVEL) - && !TelekineticRoom.getMoves().isEmpty() - && TelekineticRoom.getMoves().peek() == room.getPosition() - && room.getGuardian().getId() != NpcID.MAGICTRAINING_GUARD_MAZE_MOVING - && !room.getGuardian().getLocalLocation().equals(room.getDestination())) { - Rs2Magic.cast(MagicAction.TELEKINETIC_GRAB); - sleepGaussian(600, 150); - if (Rs2Random.dicePercentage(50)) { - Rs2Camera.turnTo(room.getGuardian()); - } - Rs2Npc.interact(new Rs2NpcModel(room.getGuardian())); - sleepUntil(()->room.getGuardian().getId() != NpcID.MAGICTRAINING_GUARD_MAZE_MOVING); - } - } - } - - private void handleGraveyardRoom() { - if (areRoomRequirementsInvalid()) { - if (!config.repeatRoom()) { - leaveRoom(); - } - return; - } - - if (btp == null) - btp = Rs2Magic.canCast(MagicAction.BONES_TO_PEACHES); - - var boneGoal = 28 - Rs2Inventory.items().filter(x -> x.getName().equalsIgnoreCase("Animals' bones")).count(); - if (mtaPlugin.getGraveyardRoom().getCounter() != null && mtaPlugin.getGraveyardRoom().getCounter().getCount() >= boneGoal) { - Rs2Magic.cast(btp ? MagicAction.BONES_TO_PEACHES : MagicAction.BONES_TO_BANANAS); - Rs2Player.waitForAnimation(); - if(!Rs2Tab.isCurrentTab(InterfaceTab.INVENTORY)){ - sleep(500,1000); - Rs2Tab.switchTo(InterfaceTab.INVENTORY); - sleep(500,1000); - } - return; - } - - if (Rs2Inventory.contains(ItemID.BANANA, ItemID.PEACH)) { - var currentHp = Microbot.getClient().getBoostedSkillLevel(Skill.HITPOINTS); - var maxHp = Microbot.getClient().getRealSkillLevel(Skill.HITPOINTS); - if ((currentHp * 100) / maxHp < nextHpThreshold) { - var maxAmountToEat = (maxHp - currentHp) / (btp ? 8 : 2); - var amountToEat = Rs2Random.between(Math.min(2, maxAmountToEat), Math.min(6, maxAmountToEat)); - nextHpThreshold = Rs2Random.between(config.healingThresholdMin(), config.healingThresholdMax()); - for (int i = 0; i < amountToEat; i++) { - Rs2Inventory.interact(btp ? ItemID.PEACH : ItemID.BANANA, "eat"); - - if (i < amountToEat - 1) - sleepGaussian(1400, 350); - } - } - if (Rs2Inventory.contains(ItemID.BANANA, ItemID.PEACH)) { - Rs2GameObject.interact(new WorldPoint(3354, 9639, 1), "Deposit"); - Rs2Inventory.waitForInventoryChanges(5000); - } - return; - } - if (mtaPlugin.getGraveyardRoom().getCounter() == null){ - Rs2GameObject.interact(new WorldPoint(3352, 9637, 1), "Grab"); - sleepUntil(()->!Rs2Player.isMoving() && Rs2Inventory.waitForInventoryChanges(2000)); - } - while (mtaPlugin.getGraveyardRoom().getCounter() != null && mtaPlugin.getGraveyardRoom().getCounter().getCount() < boneGoal && isRunning()){ - System.out.println("Plugin Counter: " + mtaPlugin.getGraveyardRoom().getCounter().getCount() + " boneGoal: " + boneGoal); - Rs2GameObject.interact(new WorldPoint(3352, 9637, 1), "Grab"); - Rs2Inventory.waitForInventoryChanges(5000); - boneGoal = (Rs2Inventory.items().filter(x -> x.getName().equalsIgnoreCase("Animals' bones")).count()+Rs2Inventory.emptySlotCount()); - sleepGaussian(400, 150); - } - } - - private void handleAlchemistRoom() { - if (areRoomRequirementsInvalid()) { - if (!config.repeatRoom()) { - leaveRoom(); - } - return; - } - - var room = mtaPlugin.getAlchemyRoom(); - var best = room.getBest(); - var item = Rs2Inventory.getLast(best.getId()); - if (item != null) { - sleep(50,150); - Rs2Magic.alch(item); - return; - }else { - Rs2Inventory.dropAll(6897,6896,6895,6894,6893); - } - - var timer = (AlchemyRoomTimer) Microbot.getInfoBoxManager().getInfoBoxes().stream() - .filter(x -> x instanceof AlchemyRoomTimer) - .findFirst().orElse(null); - if (timer == null || Integer.parseInt(timer.getText().split(":")[1]) < 2) { - Rs2Walker.walkTo(3364, 9636, 2, 2); - return; - } - - if (room.getSuggestion() == null) { - Rs2GameObject.interact("Cupboard", "Search"); - sleep(300,600); - - if (sleepUntilTrue(Rs2Player::isMoving, 100, 1000)) - sleepUntil(() -> !Rs2Player.isMoving()); - } else { - Rs2GameObject.interact(room.getSuggestion().getGameObject(), "Take-5"); - Rs2Inventory.waitForInventoryChanges(3000); - sleep(300,600); - } - } - - private boolean areRoomRequirementsInvalid() { - if (!currentRoom.getRequirements().getAsBoolean()) { - Microbot.log("You're missing room requirements. Please restock or fix your staves settings."); - if (!config.repeatRoom()) { - sleep(5000); - } - return true; - } - return false; - } - - private void buyReward(Rewards reward) { - if (!Rs2Walker.walkTo(bankPoint)) - return; - - if (!Rs2Widget.isWidgetVisible(197, 0)) { - Rs2Npc.interact(NpcID.MAGICTRAINING_GUARD_REWARDS, "Trade-with"); - sleepUntil(() -> Rs2Widget.isWidgetVisible(197, 0)); - sleepGaussian(600, 150); - return; - } - - Rs2Inventory.waitForInventoryChanges(() -> { - var rewardWidgets = Rs2Widget.getWidget(197, 11).getDynamicChildren(); - if (rewardWidgets == null) return; - var widget = Arrays.stream(rewardWidgets).filter(x -> x.getItemId() == reward.getItemId()).findFirst().orElse(null); - Rs2Widget.clickWidgetFast(widget, Arrays.asList(rewardWidgets).indexOf(widget)); - sleepGaussian(600, 150); - Rs2Widget.clickWidget(197, 9); - sleepGaussian(600, 150); - Rs2Keyboard.keyPress(KeyEvent.VK_ESCAPE); - }); - - if (reward == config.reward()) - bought++; - } - - public static Rooms getCurrentRoom() { - for (var room : Rooms.values()) { - if (room == Rooms.TELEKINETIC && Arrays.stream(TelekineticRooms.values()).anyMatch(x -> Rs2Player.getWorldLocation().distanceTo(x.getArea()) == 0) - || room.getArea() != null && Rs2Player.getWorldLocation().distanceTo(room.getArea()) == 0) - return room; - } - - return null; - } - - public static void enterRoom(Rooms room) { - if (!Rs2Walker.walkTo(portalPoint)) - return; - - Rs2GameObject.interact(room.getTeleporter(), "Enter"); - Rs2Player.waitForAnimation(); - if (Rs2Widget.hasWidget("You must talk to the Entrance Guardian")) - firstTime = true; - } - - public static void leaveRoom() { - var room = getCurrentRoom(); - - if (room == null) - return; - - WorldPoint exit = null; - if (room != Rooms.TELEKINETIC) - exit = room.getExit(); - else { - for (var teleRoom : TelekineticRooms.values()) { - if (Rs2Player.getWorldLocation().distanceTo(teleRoom.getArea()) == 0) { - exit = teleRoom.getExit(); - break; - } - } - } - - if (!Rs2Walker.walkTo(exit)) - return; - - Rs2GameObject.interact(ObjectID.MAGICTRAINING_RETURNDOOR, "Enter"); - Rs2Player.waitForWalking(); - } - - private boolean handleFirstTime() { - if (firstTime) { - if (!Rs2Walker.walkTo(new WorldPoint(3363, 3304, 0))) - return true; - - if (!Rs2Dialogue.isInDialogue()) - Rs2Npc.interact(NpcID.MAGICTRAINING_GUARD_ENTRANCE, "Talk-to"); - else if (Rs2Dialogue.hasSelectAnOption() && Rs2Widget.hasWidget("I'm new to this place")) - Rs2Widget.clickWidget("I'm new to this place"); - else if (Rs2Dialogue.hasSelectAnOption() && Rs2Widget.hasWidget("Thanks, bye!")) { - Rs2Widget.clickWidget("Thanks, bye!"); - firstTime = false; - } else - Rs2Dialogue.clickContinue(); - - return true; - } - - return false; - } - - @Override - public void shutdown() { - super.shutdown(); - } - - /** - * Attempts to equip the best available {@code staff} from inventory that reduces rune cost for the specified {@link Rs2Spells} spell. - * This method also accounts for any equipped or equippable {@code tome} in the shield slot which may provide passive rune substitution. - * - *

    The method evaluates all available staff+tome combinations, selects the one with the highest effective rune savings, - * equips the staff if not already equipped, and verifies that the spell is now castable with the resulting equipment and runes. - * - *

    Logic Summary: - *

      - *
    • Scans equipped weapon/shield and inventory for candidate staff and tome combinations
    • - *
    • Simulates rune savings provided by each staff+tome combo
    • - *
    • Verifies castability after accounting for free runes and available inventory/pouch supply
    • - *
    • Equips the best staff (if not already equipped) and confirms the spell is ready for casting
    • - *
    - * - *

    This method does not return {@code true} unless a valid staff was equipped (or already equipped) - * and the spell is verifiably castable after staff+tome rune substitution. - * - * @param spell The {@link Rs2Spells} spell to evaluate for castability. - * @param hasRunePouch {@code true} if the player's rune pouch is available and should be included in rune calculations. - * @return {@code true} if the spell is castable after equipping the best available staff (and considering any tome); {@code false} otherwise. - * - *

    Side effects: May trigger staff equipping from inventory. No tome swapping is performed — only passively recognized if equipped.

    - */ - public static boolean tryEquipBestStaffAndCast(Rs2Spells spell, boolean hasRunePouch) { - Map requiredRunes = spell.getRequiredRunes(); - - List candidates = new ArrayList<>(); - Rs2ItemModel equipped = Rs2Equipment.get(EquipmentInventorySlot.WEAPON); - if (equipped != null) candidates.add(equipped); - candidates.addAll(Rs2Inventory.items().collect(Collectors.toList())); - - Rs2Tome equippedTome = Rs2Tome.NONE; - Rs2ItemModel shield = Rs2Equipment.get(EquipmentInventorySlot.SHIELD); - if (shield != null) equippedTome = getRs2Tome(shield.getId()); - - Map inventoryRunes = new EnumMap<>(Runes.class); - Rs2Inventory.items().forEach(item -> { - Runes rune = Runes.byItemId(item.getId()); - if (rune != null) { - inventoryRunes.merge(rune, item.getQuantity(), Integer::sum); - } - }); - - if (hasRunePouch) { - Rs2RunePouch.getRunes().forEach((id, qty) -> { - Runes rune = Runes.byItemId(id.getItemId()); - if (rune != null) { - inventoryRunes.merge(rune, qty, Integer::sum); - } - }); - } - - int maxSavings = -1; - Integer bestStaffId = null; - Set bestProvidedRunes = null; - - for (Rs2ItemModel staffItem : candidates) { - Rs2Staff staff = getRs2Staff(staffItem.getId()); - if (staff == Rs2Staff.NONE) continue; - - Set providedRunes = new HashSet<>(staff.getRunes()); - if (equippedTome != Rs2Tome.NONE) providedRunes.addAll(equippedTome.getRunes()); - - boolean castable = true; - int savings = 0; - - for (Map.Entry entry : requiredRunes.entrySet()) { - if (providedRunes.contains(entry.getKey())) { - savings += entry.getValue(); - continue; - } - if (inventoryRunes.getOrDefault(entry.getKey(), 0) < entry.getValue()) { - castable = false; - break; - } - } - - if (castable && savings > maxSavings) { - maxSavings = savings; - bestStaffId = staff.getItemID(); - bestProvidedRunes = providedRunes; - } - } - - if (bestStaffId != null) { - if (!Rs2Equipment.isWearing(bestStaffId)) { - Rs2Inventory.wear(bestStaffId); - } - - Set activeRunes = new HashSet<>(bestProvidedRunes); - Rs2ItemModel activeShield = Rs2Equipment.get(EquipmentInventorySlot.SHIELD); - if (activeShield != null) { - Rs2Tome newTome = getRs2Tome(activeShield.getId()); - if (newTome != Rs2Tome.NONE) activeRunes.addAll(newTome.getRunes()); - } - - for (Map.Entry entry : requiredRunes.entrySet()) { - if (activeRunes.contains(entry.getKey())) continue; - if (inventoryRunes.getOrDefault(entry.getKey(), 0) < entry.getValue()) return false; - } - return true; - } - - return false; - } - -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/enums/EnchantmentShapes.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/enums/EnchantmentShapes.java deleted file mode 100644 index 8ccfbb2d45e..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/enums/EnchantmentShapes.java +++ /dev/null @@ -1,20 +0,0 @@ -package net.runelite.client.plugins.microbot.magetrainingarena.enums; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import net.runelite.api.gameval.ItemID; -import net.runelite.api.gameval.ObjectID; - -@Getter -@AllArgsConstructor -public enum EnchantmentShapes { - PENTAMID(ObjectID.MAGICTRAINING_ENCHAN_SHAPEPILE4, ItemID.MAGICTRAINING_ENCHAN_PENTAMID, 195, 14), - ICOSAHEDRON(ObjectID.MAGICTRAINING_ENCHAN_SHAPEPILE3, ItemID.MAGICTRAINING_ENCHAN_ICOSAHENDRON, 195, 16), - CUBE(ObjectID.MAGICTRAINING_ENCHAN_SHAPEPILE1, ItemID.MAGICTRAINING_ENCHAN_CUBE, 195, 10), - CYLINDER(ObjectID.MAGICTRAINING_ENCHAN_SHAPEPILE2, ItemID.MAGICTRAINING_ENCHAN_CYLINDER, 195, 12); - - private final int objectId; - private final int itemId; - private final int widgetId; - private final int widgetChildId; -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/enums/Points.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/enums/Points.java deleted file mode 100644 index 3530e170103..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/enums/Points.java +++ /dev/null @@ -1,24 +0,0 @@ -package net.runelite.client.plugins.microbot.magetrainingarena.enums; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -@Getter -@AllArgsConstructor -public enum Points { - TELEKINETIC(553, 10, 198, 6), - ALCHEMIST(553, 11, 194, 6), - ENCHANTMENT(553, 12, 195, 6), - GRAVEYARD(553, 13, 196, 6); - - private int widgetId; - private int childId; - private int roomWidgetId; - private int roomChildId; - - @Override - public String toString() { - String name = name(); - return name.charAt(0) + name.substring(1).toLowerCase(); - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/enums/Rewards.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/enums/Rewards.java deleted file mode 100644 index bfccbacb287..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/enums/Rewards.java +++ /dev/null @@ -1,93 +0,0 @@ -package net.runelite.client.plugins.microbot.magetrainingarena.enums; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import net.runelite.api.gameval.ItemID; - -import java.util.Map; - -@Getter -@AllArgsConstructor -public enum Rewards { - ALL_ITEMS(ItemID.SKILLCAPE_MAX, Map.of( - Points.TELEKINETIC, 2675, - Points.GRAVEYARD, 2675, - Points.ENCHANTMENT, 27500, - Points.ALCHEMIST, 3075), null), - - INFINITY_HAT(ItemID.MAGICTRAINING_INFINITYHAT, Map.of( - Points.TELEKINETIC, 350, - Points.GRAVEYARD, 350, - Points.ENCHANTMENT, 3000, - Points.ALCHEMIST, 400), null), - - INFINITY_TOP(ItemID.MAGICTRAINING_INFINITYTOP, Map.of( - Points.TELEKINETIC, 400, - Points.GRAVEYARD, 400, - Points.ENCHANTMENT, 4000, - Points.ALCHEMIST, 450), null), - - INFINITY_BOTTOMS(ItemID.MAGICTRAINING_INFINITYBOTTOM, Map.of( - Points.TELEKINETIC, 450, - Points.GRAVEYARD, 450, - Points.ENCHANTMENT, 5000, - Points.ALCHEMIST, 500), null), - - INFINITY_GLOVES(ItemID.MAGICTRAINING_INFINITYGLOVES, Map.of( - Points.TELEKINETIC, 175, - Points.GRAVEYARD, 175, - Points.ENCHANTMENT, 1500, - Points.ALCHEMIST, 225), null), - - INFINITY_BOOTS(ItemID.MAGICTRAINING_INFINITYBOOTS, Map.of( - Points.TELEKINETIC, 120, - Points.GRAVEYARD, 120, - Points.ENCHANTMENT, 1200, - Points.ALCHEMIST, 120), null), - - BEGINNER_WAND(ItemID.MAGICTRAINING_WAND_BEG, Map.of( - Points.TELEKINETIC, 30, - Points.GRAVEYARD, 30, - Points.ENCHANTMENT, 300, - Points.ALCHEMIST, 30), null), - - APPRENTICE_WAND(ItemID.MAGICTRAINING_WAND_APPR, Map.of( - Points.TELEKINETIC, 60, - Points.GRAVEYARD, 60, - Points.ENCHANTMENT, 600, - Points.ALCHEMIST, 60), BEGINNER_WAND), - - TEACHER_WAND(ItemID.MAGICTRAINING_WAND_TEACH, Map.of( - Points.TELEKINETIC, 150, - Points.GRAVEYARD, 150, - Points.ENCHANTMENT, 1500, - Points.ALCHEMIST, 200), APPRENTICE_WAND), - - MASTER_WAND(ItemID.MAGICTRAINING_WAND_MASTER, Map.of( - Points.TELEKINETIC, 240, - Points.GRAVEYARD, 240, - Points.ENCHANTMENT, 2400, - Points.ALCHEMIST, 240), TEACHER_WAND), - - MAGES_BOOK(ItemID.MAGICTRAINING_BOOKOFMAGIC, Map.of( - Points.TELEKINETIC, 500, - Points.GRAVEYARD, 500, - Points.ENCHANTMENT, 6000, - Points.ALCHEMIST, 550), null), - - BONES_TO_PEACHES(ItemID.MAGICTRAINING_PEACHSPELL, Map.of( - Points.TELEKINETIC, 200, - Points.GRAVEYARD, 200, - Points.ENCHANTMENT, 2000, - Points.ALCHEMIST, 300), null); - - private final int itemId; - private final Map points; - private final Rewards previousReward; - - @Override - public String toString() { - String name = name(); - return name.charAt(0) + name.substring(1).toLowerCase().replace("_", " "); - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/enums/Rooms.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/enums/Rooms.java deleted file mode 100644 index d787d2187f1..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/enums/Rooms.java +++ /dev/null @@ -1,92 +0,0 @@ -package net.runelite.client.plugins.microbot.magetrainingarena.enums; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import net.runelite.api.Skill; -import net.runelite.api.coords.WorldArea; -import net.runelite.api.coords.WorldPoint; -import net.runelite.client.plugins.microbot.Microbot; -import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.magic.Rs2Spells; -import org.slf4j.event.Level; - -import java.util.function.BooleanSupplier; - -import static net.runelite.client.plugins.microbot.magetrainingarena.MageTrainingArenaScript.tryEquipBestStaffAndCast; - -@Getter -@AllArgsConstructor -public enum Rooms { - TELEKINETIC("Telekinetic", 23673, null, null, Points.TELEKINETIC, - () -> { - boolean result = tryEquipBestStaffAndCast(Rs2Spells.TELEKINETIC_GRAB, Rs2Inventory.hasRunePouch()); - Microbot.log("TELEKINETIC req met: " + result, Level.DEBUG); - return result; - }), - - ALCHEMIST("Alchemist", 23675, - new WorldArea(3345, 9616, 38, 38, 2), - new WorldPoint(3364, 9623, 2), - Points.ALCHEMIST, - () -> { - boolean high = tryEquipBestStaffAndCast(Rs2Spells.HIGH_LEVEL_ALCHEMY, Rs2Inventory.hasRunePouch()); - boolean low = tryEquipBestStaffAndCast(Rs2Spells.LOW_LEVEL_ALCHEMY, Rs2Inventory.hasRunePouch()); - Microbot.log("ALCHEMIST req met: HIGH=" + high + " LOW=" + low, Level.DEBUG); - return high || low; - }), - - ENCHANTMENT("Enchantment", 23674, - new WorldArea(3339, 9617, 50, 46, 0), - new WorldPoint(3363, 9640, 0), - Points.ENCHANTMENT, - () -> { - int level = Microbot.getClient().getBoostedSkillLevel(Skill.MAGIC); - boolean result; - - if (level >= 87) { - result = tryEquipBestStaffAndCast(Rs2Spells.ENCHANT_ONYX_JEWELLERY, Rs2Inventory.hasRunePouch()); - } else if (level >= 68) { - result = tryEquipBestStaffAndCast(Rs2Spells.ENCHANT_DRAGONSTONE_JEWELLERY, Rs2Inventory.hasRunePouch()); - } else if (level >= 57) { - result = tryEquipBestStaffAndCast(Rs2Spells.ENCHANT_DIAMOND_JEWELLERY, Rs2Inventory.hasRunePouch()); - } else if (level >= 49) { - result = tryEquipBestStaffAndCast(Rs2Spells.ENCHANT_RUBY_JEWELLERY, Rs2Inventory.hasRunePouch()); - } else if (level >= 27) { - result = tryEquipBestStaffAndCast(Rs2Spells.ENCHANT_EMERALD_JEWELLERY, Rs2Inventory.hasRunePouch()); - } else { - result = tryEquipBestStaffAndCast(Rs2Spells.ENCHANT_SAPPHIRE_JEWELLERY, Rs2Inventory.hasRunePouch()); - } - - Microbot.log("ENCHANTMENT req met (level=" + level + "): " + result, Level.DEBUG); - return result; - }), - - GRAVEYARD("Graveyard", 23676, - new WorldArea(3336, 9614, 54, 51, 1), - new WorldPoint(3363, 9640, 1), - Points.GRAVEYARD, - () -> { - boolean bananas = tryEquipBestStaffAndCast(Rs2Spells.BONES_TO_BANANAS, Rs2Inventory.hasRunePouch()); - boolean peaches = tryEquipBestStaffAndCast(Rs2Spells.BONES_TO_PEACHES, Rs2Inventory.hasRunePouch()); - - Microbot.log("GRAVEYARD req met: BANANAS=" + bananas + " PEACHES=" + peaches, Level.DEBUG); - if (!bananas && !peaches) { - Microbot.log("Missing requirement to cast Bones to Bananas or Peaches.", Level.DEBUG); - return false; - } - return true; - }); - - private final String name; - private final int teleporter; - private final WorldArea area; - private final WorldPoint exit; - private final Points points; - private final BooleanSupplier requirements; - - @Override - public String toString() { - String n = name(); - return n.charAt(0) + n.substring(1).toLowerCase(); - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/enums/TelekineticRooms.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/enums/TelekineticRooms.java deleted file mode 100644 index 2f2c359039b..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/enums/TelekineticRooms.java +++ /dev/null @@ -1,25 +0,0 @@ -package net.runelite.client.plugins.microbot.magetrainingarena.enums; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import net.runelite.api.coords.WorldArea; -import net.runelite.api.coords.WorldPoint; - -@Getter -@AllArgsConstructor -public enum TelekineticRooms { - A(new WorldArea(3324, 9697, 34, 30, 0), new WorldPoint(3334, 9718, 0), new WorldPoint(3339, 9713, 0)), - B(new WorldArea(3358, 9704, 34, 23, 0), new WorldPoint(3379, 9716, 0), new WorldPoint(3373, 9716, 0)), - C(new WorldArea(3327, 9669, 31, 27, 0), new WorldPoint(3352, 9690, 0), new WorldPoint(3346, 9683, 0)), - D(new WorldArea(3361, 9664, 28, 39, 0), new WorldPoint(3373, 9696, 0), new WorldPoint(3373, 9681, 0)), - E(new WorldArea(3331, 9702, 37, 24, 1), new WorldPoint(3362, 9713, 1), new WorldPoint(3349, 9713, 1)), - F(new WorldArea(3368, 9704, 22, 22, 1), new WorldPoint(3377, 9706, 1), new WorldPoint(3382, 9714, 1)), - G(new WorldArea(3332, 9671, 35, 29, 1), new WorldPoint(3355, 9693, 1), new WorldPoint(3354, 9688, 1)), - H(new WorldArea(3367, 9669, 25, 34, 1), new WorldPoint(3382, 9698, 1), new WorldPoint(3381, 9685, 1)), - I(new WorldArea(3332, 9696, 42, 31, 2), new WorldPoint(3359, 9701, 2), new WorldPoint(3351, 9710, 2)), - J(new WorldArea(3331, 9667, 45, 27, 2), new WorldPoint(3368, 9680, 2), new WorldPoint(3347, 9679, 2)); - - private final WorldArea area; - private final WorldPoint exit; - private final WorldPoint maze; -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/ContractLocation.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/ContractLocation.java index 5b61781f159..2ca2d34a70a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/ContractLocation.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/ContractLocation.java @@ -7,7 +7,7 @@ @Getter public enum ContractLocation { MAHOGANY_HOMES_ARDOUGNE("Mahogany Homes", new WorldPoint(2635, 3294, 0)), - MAHOGANY_HOMES_FALADOR("Mahogany Homes", new WorldPoint(2989, 3363, 0)), + MAHOGANY_HOMES_FALADOR("Mahogany Homes", new WorldPoint(2990, 3365, 0)), MAHOGANY_HOMES_HOSIDIUS("Mahogany Homes", new WorldPoint(1781, 3626, 0)), MAHOGANY_HOMES_VARROCK("Mahogany Homes", new WorldPoint(3240, 3471, 0)); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesScript.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesScript.java index 262ebd12ea3..a5df49d1cd3 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesScript.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesScript.java @@ -262,7 +262,7 @@ private void finish() { if (plugin.getCurrentHome() != null && plugin.getCurrentHome().isInside(Rs2Player.getWorldLocation()) && Hotspot.isEverythingFixed()) { - if(plugin.getConfig().usePlankSack() && planksInPlankSack() > 0){ + if(plugin.getConfig().usePlankSack() && planksInPlankSack() > 0 && !Rs2Inventory.isFull()){ if (Rs2Inventory.contains(ItemID.PLANK_SACK) && Rs2Inventory.contains(ItemID.STEEL_BAR)) { Rs2ItemModel plankSack = Rs2Inventory.get(ItemID.PLANK_SACK); if (plankSack != null) { @@ -322,11 +322,22 @@ private void getNewContract() { log("Getting new contract"); - var npc = Rs2Npc.getNpcWithAction("Contract"); + // Search for Mahogany Homes contract NPCs directly by name + var npc = Rs2Npc.getNpcs() + .filter(n -> n.getName() != null && + (n.getName().equals("Amy") || + n.getName().equals("Marlo") || + n.getName().equals("Ellie") || + n.getName().equals("Angelo"))) + .findFirst() + .orElse(null); + if (npc == null) { + log("No contract NPC found, waiting before retry"); + sleep(2000, 3000); // Wait 2-3 seconds to prevent spam return; } - log("NPC found: " + npc.getComposition().transform().getName()); + log("NPC found: " + npc.getName()); if (Rs2Npc.interact(npc, "Contract")) { handleContractDialogue(); } @@ -338,9 +349,13 @@ private void getNewContract() { } public void handleContractDialogue() { - sleepUntil(Rs2Dialogue::hasSelectAnOption, Rs2Dialogue::clickContinue, 10000, 300); + // Reduced timeout and early return if dialogue not available + if (!sleepUntil(Rs2Dialogue::hasSelectAnOption, Rs2Dialogue::clickContinue, 5000, 300)) { + log("No dialogue options available, returning early"); + return; + } Rs2Dialogue.keyPressForDialogueOption(plugin.getConfig().currentTier().getPlankSelection().getChatOption()); - sleepUntil(Rs2Dialogue::hasContinue, 10000); + sleepUntil(Rs2Dialogue::hasContinue, 5000); sleep(400, 800); sleepUntil(() -> !Rs2Dialogue.isInDialogue(), Rs2Dialogue::clickContinue, 6000, 300); sleep(1200, 2200); @@ -394,12 +409,30 @@ && isMissingItems()) { Rs2Bank.withdrawAll(plugin.getConfig().currentTier().getPlankSelection().getPlankId()); Rs2Bank.closeBank(); } else { - if (Rs2Inventory.getEmptySlots() - steelBarsNeeded() > 0) - Rs2Bank.withdrawX(plugin.getConfig().currentTier().getPlankSelection().getPlankId(), Rs2Inventory.getEmptySlots() - steelBarsNeeded()); - sleep(600, 1200); + // Withdraw steel bars first if needed if (steelBarsNeeded() > steelBarsInInventory()) { - Rs2Bank.withdrawX(ItemID.STEEL_BAR, steelBarsNeeded()); - sleep(600, 1200); + Rs2Bank.withdrawX(ItemID.STEEL_BAR, steelBarsNeeded() - steelBarsInInventory()); + Rs2Inventory.waitForInventoryChanges(5000); + } + + // Calculate if we'll have enough space for planks after steel bars + int freeSlots = Rs2Inventory.getEmptySlots(); + int currentPlanks = planksInInventory() + planksInPlankSack(); + int additionalPlanksNeeded = planksNeeded() - currentPlanks; + + if (additionalPlanksNeeded <= 0) { + // We already have enough planks + log("Already have sufficient planks: %d/%d", currentPlanks, planksNeeded()); + } else if (freeSlots >= additionalPlanksNeeded) { + // Withdraw all planks to fill inventory + Rs2Bank.withdrawAll(plugin.getConfig().currentTier().getPlankSelection().getPlankId()); + Rs2Inventory.waitForInventoryChanges(5000); + } else { + // This should never happen - inventory can't fit required materials + log("CRITICAL ERROR: Need %d more planks but only %d slots available!", additionalPlanksNeeded, freeSlots); + Microbot.showMessage("Please free up inventory space! Need " + additionalPlanksNeeded + " more planks but only " + freeSlots + " slots available. Stopping script."); + shutdown(); + return; } } Rs2Bank.closeBank(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/AgaHerbs.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/AgaHerbs.java deleted file mode 100644 index 58b55c8cfff..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/AgaHerbs.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.runelite.client.plugins.microbot.mixology; - -import lombok.RequiredArgsConstructor; -@RequiredArgsConstructor -public enum AgaHerbs { - Irit("irit leaf"), - Cadantine("cadantine"), - Lantadyme("Lantadyme"), - Dwarf_Weed("dwarf weed"), - Torstol("torstol"); - - private final String itemName; - - @Override - public String toString() { - return itemName; - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/AlchemyObject.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/AlchemyObject.java deleted file mode 100644 index 39d469a9a20..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/AlchemyObject.java +++ /dev/null @@ -1,35 +0,0 @@ -package net.runelite.client.plugins.microbot.mixology; - -import net.runelite.api.coords.WorldPoint; - -public enum AlchemyObject { - MOX_LEVER(54868, new WorldPoint(1395, 9324, 0)), - AGA_LEVER(54867, new WorldPoint(1394, 9324, 0)), - LYE_LEVER(54869, new WorldPoint(1393, 9324, 0)), - MIXING_VESSEL(55395, new WorldPoint(1394, 9326, 0)), - ALEMBIC(55391, new WorldPoint(1391, 9325, 0)), - AGITATOR(55390, new WorldPoint(1393, 9329, 0)), - RETORT(55389, new WorldPoint(1397, 9325, 0)), - CONVEYOR_BELT(54917, new WorldPoint(1394, 9331, 0)), - HOPPER(54903, new WorldPoint(1394, 9322, 0)), - DIGWEED_NORTH_EAST(55396, new WorldPoint(1399, 9331, 0)), - DIGWEED_SOUTH_EAST(55397, new WorldPoint(1399, 9322, 0)), - DIGWEED_SOUTH_WEST(55398, new WorldPoint(1389, 9322, 0)), - DIGWEED_NORTH_WEST(55399, new WorldPoint(1389, 9331, 0)); - - private final int objectId; - private final WorldPoint coordinate; - - AlchemyObject(int objectId, WorldPoint coordinate) { - this.objectId = objectId; - this.coordinate = coordinate; - } - - public int objectId() { - return this.objectId; - } - - public WorldPoint coordinate() { - return this.coordinate; - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/InventoryPotionOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/InventoryPotionOverlay.java deleted file mode 100644 index 2fa33ef5720..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/InventoryPotionOverlay.java +++ /dev/null @@ -1,26 +0,0 @@ -package net.runelite.client.plugins.microbot.mixology; - -import com.google.inject.Inject; -import net.runelite.api.widgets.WidgetItem; -import net.runelite.client.ui.FontManager; -import net.runelite.client.ui.overlay.WidgetItemOverlay; - -import java.awt.*; - -public class InventoryPotionOverlay extends WidgetItemOverlay { - - @Inject - InventoryPotionOverlay() { - this.showOnInventory(); - } - - public void renderItemOverlay(Graphics2D graphics2D, int itemId, WidgetItem widgetItem) { - PotionType potion = PotionType.fromItemId(itemId); - if (potion != null) { - Rectangle bounds = widgetItem.getCanvasBounds(); - graphics2D.setFont(FontManager.getRunescapeSmallFont()); - graphics2D.setColor(Color.WHITE); - graphics2D.drawString(potion.abbreviation(), bounds.x - 1, bounds.y + 15); - } - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/LyeHerbs.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/LyeHerbs.java deleted file mode 100644 index 32674c1751c..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/LyeHerbs.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.runelite.client.plugins.microbot.mixology; - -import lombok.RequiredArgsConstructor; -@RequiredArgsConstructor -public enum LyeHerbs { - Ranarr("ranarr weed"), - Toadflax("toadflax"), - Avantoe("avantoe"), - Kwuarm("kwuarm"), - Snapdragon("snapdragon"); - - private final String itemName; - - @Override - public String toString() { - return itemName; - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/MixologyConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/MixologyConfig.java deleted file mode 100644 index 48b3d64f94e..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/MixologyConfig.java +++ /dev/null @@ -1,160 +0,0 @@ -package net.runelite.client.plugins.microbot.mixology; - -import net.runelite.client.config.*; -import org.jetbrains.annotations.Range; - -@ConfigGroup("mixology") -@ConfigInformation("

    Start the script at the bankchest of the mixology minigame room with an empty inventory.


    " + - "
    Requirements: " + - "
      " + - "
    1. 60 Herblore
    2. " + - "
    3. Herbs for making mox/aga/lye paste
    4. " + - "
    ") -public interface MixologyConfig extends Config { - @ConfigSection( - name = "Refiner", - description = "Refiner configuration", - position = 0 - ) - String refiner = "Refiner"; - - @ConfigSection( - name = "Minigame", - description = "General minigame configuration", - position = 1 - ) - String minigame = "Minigame"; - - @ConfigItem( - keyName = "RefinerHerbMox", - name = "Refining Mox Herb", - description = "Refine herbs into mox paste", - position = 0, - section = refiner - ) - default MoxHerbs moxHerb() { - return MoxHerbs.GUAM; - } - @Range(from = 100, to = 3000) - @ConfigItem( - keyName = "RefinerHerbMoxAmt", - name = "Refining Mox Herb Amount", - description = "Amount of herbs to refine into mox paste", - position = 1, - section = refiner - ) - default int amtMoxHerb() { - return 1000; - } - - @ConfigItem( - keyName = "RefinerHerbLye", - name = "Refining Lye Herb", - description = "Refine herbs into lye paste", - position = 2, - section = refiner - ) - default LyeHerbs lyeHerb() { - return LyeHerbs.Ranarr; - } - @Range(from = 100, to = 3000) - @ConfigItem( - keyName = "RefinerHerbLyeAmt", - name = "Refining lye Herb Amount", - description = "Amount of herbs to refine into lye paste", - position = 3, - section = refiner - ) - default int amtLyeHerb() { - return 1000; - } - - @ConfigItem( - keyName = "RefinerHerbAga", - name = "Refining Aga Herb", - description = "Refine herbs into aga paste", - position = 4, - section = refiner - ) - default AgaHerbs agaHerb() { - return AgaHerbs.Irit; - } - @Range(from = 100, to = 3000) - @ConfigItem( - keyName = "RefinerHerbAgaAmt", - name = "Refining Aga Herb Amount", - description = "Amount of herbs to refine into aga paste", - position = 5, - section = refiner - ) - default int amtAgaHerb() { - return 1000; - } - - @ConfigItem( - keyName = "useQuickActionRefiner", - name = "Use Quick Action on Refiner", - description = "Will click while paste to allow for faster completion of the task", - position = 5, - section = refiner - ) - default boolean useQuickActionRefiner() { - return true; - } - - // -- MINIGAME SECTION -- // - - @ConfigItem( - keyName = "useQuickActionAlembic", - name = "Use Quick Action on Alembic", - description = "Will click once there is a quick action available on the alembic", - position = 0, - section = minigame - ) - default boolean useQuickActionOnAlembic() { - return true; - } - @ConfigItem( - keyName = "useQuickActionAgitator", - name = "Use Quick Action on Agitator", - description = "Will click once there is a quick action available on the agitator", - position = 1, - section = minigame - ) - default boolean useQuickActionOnAgitator() { - return true; - } - - @ConfigItem( - keyName = "useQuickActionRetort", - name = "Use Quick Action on Retort", - description = "Will click once there is a quick action available on the retort", - position = 2, - section = minigame - ) - default boolean useQuickActionOnRetort() { - return true; - } - - @ConfigItem( - keyName = "pickDigWeed", - name = "Pick DigWeed", - description = "Will pick digweed if available to increase points", - position = 3, - section = minigame - ) - default boolean pickDigWeed() { - return true; - } - - @ConfigItem( - keyName = "useQuickActionLever", - name = "Use Quick Action on Lever", - description = "Will click fast when interacting with the lever for mixing potions", - position = 4, - section = minigame - ) - default boolean useQuickActionLever() { - return true; - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/MixologyOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/MixologyOverlay.java deleted file mode 100644 index d9ecaeb05f2..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/MixologyOverlay.java +++ /dev/null @@ -1,92 +0,0 @@ -package net.runelite.client.plugins.microbot.mixology; - -import net.runelite.client.ui.overlay.OverlayLayer; -import net.runelite.client.ui.overlay.OverlayPanel; -import net.runelite.client.ui.overlay.OverlayPosition; -import net.runelite.client.ui.overlay.components.LineComponent; -import net.runelite.client.ui.overlay.components.TitleComponent; -import net.runelite.client.ui.overlay.outline.ModelOutlineRenderer; - -import javax.inject.Inject; -import java.awt.*; -import java.time.Duration; - -public class MixologyOverlay extends OverlayPanel { - private final MixologyPlugin plugin; - private final ModelOutlineRenderer modelOutlineRenderer; - - @Inject - MixologyOverlay(MixologyPlugin plugin, ModelOutlineRenderer modelOutlineRenderer) { - this.plugin = plugin; - this.modelOutlineRenderer = modelOutlineRenderer; - this.setPosition(OverlayPosition.DYNAMIC); - this.setLayer(OverlayLayer.ABOVE_SCENE); - } - - public Dimension render(Graphics2D graphics) { - - panelComponent.setPreferredLocation(new Point(200, 20)); - panelComponent.setPreferredSize(new Dimension(300, 300)); - panelComponent.getChildren().add(TitleComponent.builder() - .text("Micro Mixology V" + MixologyScript.version) - .color(Color.GREEN) - .build()); - - panelComponent.getChildren().add(LineComponent.builder().build()); - - panelComponent.getChildren().add(LineComponent.builder() - .left("Mixology state") - .right(MixologyScript.mixologyState.toString()) - .build()); - - panelComponent.getChildren().add(LineComponent.builder() - .left("Mox/Aga/Lye paste") - .right(String.valueOf(MixologyScript.moxPasteAmount) + "/" + String.valueOf(MixologyScript.agaPasteAmount) + "/" + String.valueOf(MixologyScript.lyePasteAmount)) - .build()); - - Duration runtime = plugin.mixologyScript.getRunTime(); - - panelComponent.getChildren().add(LineComponent.builder() - .left("Mox/Aga/Lye points per hour") - .rightColor(Color.YELLOW) - .right(calculatePointsPerHour(runtime.getSeconds())) - .build()); - - panelComponent.getChildren().add(LineComponent.builder() - .left("Runtime") - .rightColor(Color.GREEN) - .right(String.format("%02d:%02d:%02d", runtime.toHours(), runtime.toMinutesPart(), runtime.toSecondsPart())) - .build()); - - for (MixologyPlugin.HighlightedObject highlightedObject : this.plugin.highlightedObjects().values()) { - this.modelOutlineRenderer.drawOutline(highlightedObject.object(), highlightedObject.outlineWidth(), highlightedObject.color(), highlightedObject.feather()); - } - - return super.render(graphics); - } - - private String calculatePointsPerHour(long seconds) { - // int elapsedTimeInSeconds = 3600; // Time elapsed (e.g., 1 hour = 3600 seconds) - - // Convert time to hours - double elapsedTimeInHours = seconds / 3600.0; - - int gainedMoxPoints = MixologyScript.currentMoxPoints - MixologyScript.startMoxPoints; - int gainedLyePoints = MixologyScript.currentLyePoints - MixologyScript.startLyePoints; - int gainedAgaPoints = MixologyScript.currentAgaPoints - MixologyScript.startAgaPoints; - - // Calculate experience per hour - int moxPointsPerHour = (int) (gainedMoxPoints / elapsedTimeInHours); - int lyePointsPerHour = (int) (gainedLyePoints / elapsedTimeInHours); - int agaPointsPerHour = (int) (gainedAgaPoints / elapsedTimeInHours); - - if (moxPointsPerHour < 0) - moxPointsPerHour = 0; - if (lyePointsPerHour < 0) - lyePointsPerHour = 0; - if (agaPointsPerHour < 0) - agaPointsPerHour = 0; - - return "" + moxPointsPerHour + "/" + agaPointsPerHour + "/" + lyePointsPerHour; - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/MixologyPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/MixologyPlugin.java deleted file mode 100644 index c76f77912cc..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/MixologyPlugin.java +++ /dev/null @@ -1,344 +0,0 @@ -package net.runelite.client.plugins.microbot.mixology; - -import com.google.inject.Provides; -import lombok.extern.slf4j.Slf4j; -import net.runelite.api.Client; -import net.runelite.api.GameState; -import net.runelite.api.TileObject; -import net.runelite.api.VarbitComposition; -import net.runelite.api.events.*; -import net.runelite.api.widgets.Widget; -import net.runelite.client.callback.ClientThread; -import net.runelite.client.config.ConfigManager; -import net.runelite.client.eventbus.Subscribe; -import net.runelite.client.plugins.Plugin; -import net.runelite.client.plugins.PluginDescriptor; -import net.runelite.client.ui.overlay.OverlayManager; -import net.runelite.client.util.ColorUtil; - -import javax.inject.Inject; -import java.awt.*; -import java.util.List; -import java.util.*; - -@PluginDescriptor( - name = PluginDescriptor.Mocrosoft + "AutoMixology", - description = "Mixology plugin", - tags = {"herblore", "microbot", "mixology"}, - enabledByDefault = false -) -@Slf4j -public class MixologyPlugin extends Plugin { - @Inject - private Client client; - @Inject - private MixologyConfig config; - @Inject - private OverlayManager overlayManager; - @Inject - private ClientThread clientThread; - @Inject - private MixologyOverlay overlay; - @Inject - private InventoryPotionOverlay potionOverlay; - private final Map highlightedObjects = new LinkedHashMap(); - private boolean inLab = false; - private PotionType alembicPotionType; - private PotionType agitatorPotionType; - private PotionType retortPotionType; - - @Inject - MixologyScript mixologyScript; - - public MixologyPlugin() { - } - - public Map highlightedObjects() { - return this.highlightedObjects; - } - - @Provides - MixologyConfig provideConfig(ConfigManager configManager) { - return (MixologyConfig) configManager.getConfig(MixologyConfig.class); - } - - protected void startUp() { - mixologyScript.run(config); - this.overlayManager.add(this.overlay); - this.overlayManager.add(this.potionOverlay); - if (this.client.getGameState() == GameState.LOGGED_IN) { - this.clientThread.invokeLater(this::initialize); - } - - } - - protected void shutDown() { - mixologyScript.shutdown(); - this.overlayManager.remove(this.overlay); - this.overlayManager.remove(this.potionOverlay); - this.inLab = false; - } - - @Subscribe - public void onGameStateChanged(GameStateChanged event) { - if (event.getGameState() == GameState.LOGIN_SCREEN || event.getGameState() == GameState.HOPPING) { - this.highlightedObjects.clear(); - } - - } - - @Subscribe - public void onWidgetLoaded(WidgetLoaded event) { - if (event.getGroupId() == 882) { - this.initialize(); - } - } - - @Subscribe - public void onWidgetClosed(WidgetClosed event) { - if (event.getGroupId() == 882) { - this.highlightedObjects.clear(); - this.inLab = false; - } - } - - @Subscribe - public void onVarbitChanged(VarbitChanged event) { - int varbitId = event.getVarbitId(); - int value = event.getValue(); - if (varbitId == 11315) { - if (value == 0) { - // this.unHighlightAllStations(); - } else { - this.clientThread.invokeAtTickEnd(this::updatePotionOrders); - } - } else if (varbitId == 11342) { - if (value == 0) { - this.tryFulfillOrder(this.alembicPotionType, PotionModifier.CRYSTALISED); - this.alembicPotionType = null; - } else { - this.alembicPotionType = PotionType.fromIdx(value - 1); - } - } else if (varbitId == 11340) { - if (value == 0) { - this.tryFulfillOrder(this.agitatorPotionType, PotionModifier.HOMOGENOUS); - this.agitatorPotionType = null; - } else { - this.agitatorPotionType = PotionType.fromIdx(value - 1); - } - } else if (varbitId == 11341) { - if (value == 0) { - this.tryFulfillOrder(this.retortPotionType, PotionModifier.CONCENTRATED); - this.retortPotionType = null; - } else { - this.retortPotionType = PotionType.fromIdx(value - 1); - } - } else if (varbitId == 11330) { - if (value == 1) { - mixologyScript.digweed = AlchemyObject.DIGWEED_NORTH_EAST; - } else { - mixologyScript.digweed = null; - } - } else if (varbitId == 11331) { - if (value == 1) { - mixologyScript.digweed = AlchemyObject.DIGWEED_SOUTH_EAST; - } else { - mixologyScript.digweed = null; - } - } else if (varbitId == 11332) { - if (value == 1) { - mixologyScript.digweed = AlchemyObject.DIGWEED_SOUTH_WEST; - } else { - mixologyScript.digweed = null; - } - } else if (varbitId == 11333) { - if (value == 1) { - mixologyScript.digweed = AlchemyObject.DIGWEED_NORTH_WEST; - } else { - mixologyScript.digweed = null; - } - } else if (varbitId == 11329) { - if (mixologyScript.agitatorQuickActionTicks == 2) { - mixologyScript.agitatorQuickActionTicks = 0; - } - - if (mixologyScript.agitatorQuickActionTicks == 1) { - mixologyScript.agitatorQuickActionTicks = 2; - } - } else if (varbitId == 11328) { - if (mixologyScript.alembicQuickActionTicks == 1) { - mixologyScript.alembicQuickActionTicks = 0; - } - } - } - - @Subscribe - public void onGraphicsObjectCreated(GraphicsObjectCreated event) { - int spotAnimId = event.getGraphicsObject().getId(); - if (spotAnimId == 2955 && this.alembicPotionType != null) { - mixologyScript.alembicQuickActionTicks = 1; - } - - if (spotAnimId == 2954 && this.agitatorPotionType != null) { - mixologyScript.agitatorQuickActionTicks = 1; - } - } - - @Subscribe - public void onScriptPostFired(ScriptPostFired event) { - int scriptId = event.getScriptId(); - if (scriptId == 7063 || scriptId == 7064) { - Widget baseWidget = this.client.getWidget(57802754); - if (baseWidget != null) { - if (scriptId == 7063) { - this.updatePotionOrdersComponent(baseWidget); - } else { - this.appendResins(baseWidget); - } - - } - } - } - - private void updatePotionOrdersComponent(Widget baseWidget) { - Widget[] children = baseWidget.getChildren(); - if (children != null) { - for (int i = 0; i < mixologyScript.potionOrders.size(); ++i) { - PotionOrder order = mixologyScript.potionOrders.get(i); - Widget orderGraphic = children[order.idx() * 2 + 1]; - Widget orderText = children[order.idx() * 2 + 2]; - if (orderGraphic.getType() == 5 && orderText.getType() == 4) { - StringBuilder builder = new StringBuilder(orderText.getText()); - if (order.fulfilled()) { - builder.append(" (done!)"); - } else { - builder.append(" (").append(order.potionType().recipe()).append(")"); - } - - orderText.setText(builder.toString()); - if (i != order.idx()) { - int y = 20 + i * 26 + 3; - orderGraphic.setOriginalY(y); - orderText.setOriginalY(y); - orderGraphic.revalidate(); - orderText.revalidate(); - } - } - } - - } - } - - private void appendResins(Widget baseWidget) { - int parentWidth = baseWidget.getWidth(); - int dx = parentWidth / 3; - int x = dx / 2; - this.addResinText(baseWidget.createChild(-1, 4), x, 4416, PotionComponent.MOX); - this.addResinText(baseWidget.createChild(-1, 4), x + dx, 4415, PotionComponent.AGA); - this.addResinText(baseWidget.createChild(-1, 4), x + dx * 2, 4414, PotionComponent.LYE); - } - - private void initialize() { - Widget ordersLayer = this.client.getWidget(882, 0); - if (ordersLayer != null && !ordersLayer.isSelfHidden()) { - this.inLab = true; - this.updatePotionOrders(); - } - } - - private void updatePotionOrders() { - System.out.println("Updating potion orders"); - mixologyScript.potionOrders = this.getPotionOrders(); - // Desired order: CRYSTALISED > CONCENTRATED > HOMOGENOUS - - mixologyScript.potionOrders.sort(Comparator.comparingInt(mixologyScript.customOrder::indexOf)); - - VarbitComposition varbitType = this.client.getVarbit(11315); - if (varbitType != null) { - this.client.queueChangedVarp(varbitType.getIndex()); - } - - } - - private void addResinText(Widget widget, int x, int varp, PotionComponent component) { - int amount = this.client.getVarpValue(varp); - int color = ColorUtil.fromHex(component.color()).getRGB(); - widget.setText("" + amount).setTextShadowed(true).setTextColor(color).setOriginalWidth(20).setOriginalHeight(15).setFontId(497).setOriginalY(0).setOriginalX(x).setYPositionMode(2).setXTextAlignment(1).setYTextAlignment(1); - widget.revalidate(); - } - - private void tryFulfillOrder(PotionType potionType, PotionModifier modifier) { - for (PotionOrder order : mixologyScript.potionOrders) { - if (order.potionType() == potionType && order.potionModifier() == modifier && !order.fulfilled()) { - order.setFulfilled(true); - break; - } - } - - } - - private List getPotionOrders() { - ArrayList potionOrders = new ArrayList(3); - - for (int orderIdx = 0; orderIdx < 3; ++orderIdx) { - PotionType potionType = this.getPotionType(orderIdx); - PotionModifier potionModifier = this.getPotionModifier(orderIdx); - if (potionType != null && potionModifier != null) { - potionOrders.add(new PotionOrder(orderIdx, potionType, potionModifier)); - } - } - - return potionOrders; - } - - private PotionType getPotionType(int orderIdx) { - if (orderIdx == 0) { - return PotionType.fromIdx(this.client.getVarbitValue(11315) - 1); - } else if (orderIdx == 1) { - return PotionType.fromIdx(this.client.getVarbitValue(11317) - 1); - } else { - return orderIdx == 2 ? PotionType.fromIdx(this.client.getVarbitValue(11319) - 1) : null; - } - } - - private PotionModifier getPotionModifier(int orderIdx) { - if (orderIdx == 0) { - return PotionModifier.from(this.client.getVarbitValue(11316) - 1); - } else if (orderIdx == 1) { - return PotionModifier.from(this.client.getVarbitValue(11318) - 1); - } else { - return orderIdx == 2 ? PotionModifier.from(this.client.getVarbitValue(11320) - 1) : null; - } - } - - public static class HighlightedObject { - private final TileObject object; - private final Color color; - private final int outlineWidth; - private final int feather; - - private HighlightedObject(TileObject object, Color color, int outlineWidth, int feather) { - this.object = object; - this.color = color; - this.outlineWidth = outlineWidth; - this.feather = feather; - } - - public TileObject object() { - return this.object; - } - - public Color color() { - return this.color; - } - - public int outlineWidth() { - return this.outlineWidth; - } - - public int feather() { - return this.feather; - } - } - -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/MixologyScript.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/MixologyScript.java deleted file mode 100644 index 54a32b532ec..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/MixologyScript.java +++ /dev/null @@ -1,466 +0,0 @@ -package net.runelite.client.plugins.microbot.mixology; - -import net.runelite.api.DynamicObject; -import net.runelite.api.GameObject; -import net.runelite.api.gameval.ItemID; -import net.runelite.api.gameval.ObjectID; - - -import net.runelite.api.coords.WorldPoint; -import net.runelite.client.plugins.microbot.Microbot; -import net.runelite.client.plugins.microbot.Script; -import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; -import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; -import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.math.Rs2Random; -import net.runelite.client.plugins.microbot.util.player.Rs2Player; -import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; -import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; - -import java.util.*; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - -import static net.runelite.client.plugins.microbot.mixology.AlchemyObject.MIXING_VESSEL; - - -public class MixologyScript extends Script { - - public final static String version = "1.0.2-beta"; - private static final Integer DIGWEED = ItemID.MM_LAB_SPECIAL_HERB; - - public java.util.List potionOrders = Collections.emptyList(); - - public static MixologyState mixologyState = MixologyState.IDLE; - public static int lyePasteAmount, agaPasteAmount, moxPasteAmount = 0; - public static int startLyePoints, startAgaPoints, startMoxPoints = 0; - public static int currentLyePoints, currentAgaPoints, currentMoxPoints = 0; - public int agitatorQuickActionTicks = 0; - public int alembicQuickActionTicks = 0; - public AlchemyObject digweed; - public int leverRetries = 0; - public List customOrder = Arrays.asList( - PotionModifier.CRYSTALISED, - PotionModifier.CONCENTRATED, - PotionModifier.HOMOGENOUS - ); - - public boolean run(MixologyConfig config) { - Microbot.enableAutoRunOn = false; - currentMoxPoints = 0; - currentAgaPoints = 0; - currentLyePoints = 0; - leverRetries = 0; - if (!Rs2AntibanSettings.naturalMouse) { - Microbot.log("Hey! Did you know this script works really well with natural mouse? Feel free to enable it in the antiban settings."); - } - mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { - try { - if (!Microbot.isLoggedIn()) return; - if (!super.run()) return; - long startTime = System.currentTimeMillis(); - - if (leverRetries >= 20) { - Microbot.log("Failed to create a potion. Please do this step manually and restart the script."); - return; - } - - - boolean isInMinigame = Rs2Widget.getWidget(882, 2) != null; - - - if (!isInMinigame && mixologyState != MixologyState.REFINER) { - Rs2Walker.walkTo(1395, 9322, 0, 2); - return; - } - - if (isInMinigame) { - - if (startLyePoints == 0 && startAgaPoints == 0 && startMoxPoints == 0) { - startMoxPoints = getMoxPoints(); - startAgaPoints = getAgaPoints(); - startLyePoints = getLyePoints(); - } - - if (digweed != null && !Rs2Player.isAnimating() && !Rs2Inventory.hasItem(DIGWEED) - && config.pickDigWeed()) { - Rs2GameObject.interact(digweed.coordinate()); - Rs2Player.waitForWalking(); - Rs2Player.waitForAnimation(); - return; - } - - if (Rs2Inventory.hasItem(DIGWEED) && !Rs2Player.isAnimating()) { - Optional potionItemId = potionOrders - .stream() - .filter(x -> !x.fulfilled() && Rs2Inventory.hasItem(x.potionType().itemId())) - .map(x -> x.potionType().itemId()) - .findFirst(); - if (potionItemId.isPresent()) { - Rs2Inventory.interact(DIGWEED, "use"); - Rs2Inventory.interact(potionItemId.get(), "use"); - Rs2Player.waitForAnimation(); - return; - } - } - - moxPasteAmount = Integer.parseInt(Rs2Widget.getWidget(882, 2).getDynamicChildren()[8].getText()) + Rs2Inventory.itemQuantity(ItemID.MM_MOX_PASTE); - agaPasteAmount = Integer.parseInt(Rs2Widget.getWidget(882, 2).getDynamicChildren()[11].getText()) + Rs2Inventory.itemQuantity(ItemID.MM_AGA_PASTE); - lyePasteAmount = Integer.parseInt(Rs2Widget.getWidget(882, 2).getDynamicChildren()[14].getText()) + Rs2Inventory.itemQuantity(ItemID.MM_LYE_PASTE); - - if (mixologyState != MixologyState.REFINER && (moxPasteAmount < 100 || agaPasteAmount < 100 || lyePasteAmount < 100)) { - mixologyState = MixologyState.REFINER; - } else if (Rs2Inventory.hasItem(ItemID.MM_MOX_PASTE) || Rs2Inventory.hasItem(ItemID.MM_LYE_PASTE) || Rs2Inventory.hasItem(ItemID.MM_AGA_PASTE)) { - if (Integer.parseInt(Rs2Widget.getWidget(882, 2).getDynamicChildren()[8].getText()) >= 3000 && Rs2Inventory.hasItem(ItemID.MM_MOX_PASTE)) { - mixologyState = MixologyState.BANK; - } else if (Integer.parseInt(Rs2Widget.getWidget(882, 2).getDynamicChildren()[11].getText()) >= 3000 && Rs2Inventory.hasItem(ItemID.MM_AGA_PASTE)) { - mixologyState = MixologyState.BANK; - - } else if (Integer.parseInt(Rs2Widget.getWidget(882, 2).getDynamicChildren()[14].getText()) >= 3000 && Rs2Inventory.hasItem(ItemID.MM_LYE_PASTE)) { - mixologyState = MixologyState.BANK; - } else { - mixologyState = MixologyState.DEPOSIT_HOPPER; - } - } - } - - if (mixologyState == MixologyState.IDLE) { - mixologyState = MixologyState.MIX_POTION_STAGE_1; - } - - if (hasAllFulFilledItems()) { - mixologyState = MixologyState.CONVEYER_BELT; - } - - switch (mixologyState) { - case BANK: - if (Rs2Inventory.hasItem("paste")) { - if (Rs2Bank.openBank()) { - Rs2Bank.depositAll(); - } - return; - } - mixologyState = MixologyState.MIX_POTION_STAGE_1; - break; - case REFINER: - String herb = ""; - WorldPoint bankLocation = new WorldPoint(1398, 9313, 0); - if (Rs2Player.getWorldLocation().distanceTo(bankLocation) > 10) { - Rs2Walker.walkTo(bankLocation); - return; - } - - if (Rs2Inventory.hasItem(config.agaHerb().toString()) || Rs2Inventory.hasItem(config.lyeHerb().toString()) || Rs2Inventory.hasItem(config.moxHerb().toString())) { - Rs2GameObject.interact(ObjectID.MM_LAB_MILL); - Rs2Player.waitForAnimation(); - sleepGaussian(450, 150); - if (!config.useQuickActionRefiner()) { - sleepUntil(() -> !Microbot.isGainingExp, 30000); - } - return; - } - if (Rs2Bank.openBank()) { - sleepUntil(Rs2Bank::isOpen); - moxPasteAmount = Rs2Bank.count(ItemID.MM_MOX_PASTE); - lyePasteAmount = Rs2Bank.count(ItemID.MM_LYE_PASTE); - agaPasteAmount = Rs2Bank.count(ItemID.MM_AGA_PASTE); - if (moxPasteAmount < config.amtMoxHerb()) { - herb = config.moxHerb().toString(); - } else if (lyePasteAmount < config.amtLyeHerb()) { - herb = config.lyeHerb().toString(); - } else if (agaPasteAmount < config.amtAgaHerb()) { - herb = config.agaHerb().toString(); - } else { - if (Rs2Bank.openBank()) { - Rs2Bank.depositAll(); - Rs2Bank.withdrawAll(ItemID.MM_MOX_PASTE); - Rs2Bank.withdrawAll(ItemID.MM_LYE_PASTE); - Rs2Bank.withdrawAll(ItemID.MM_AGA_PASTE); - mixologyState = MixologyState.DEPOSIT_HOPPER; - return; - } - } - Rs2Bank.depositAll(); - if (!Rs2Bank.hasItem(herb, true)) { - Microbot.showMessage("Failed to find " + herb + " in your bank. Shutting down script..."); - shutdown(); - return; - } - Rs2Bank.withdrawAll(herb, true); - Rs2Bank.closeBank(); - sleepGaussian(600, 150); - } - break; - case DEPOSIT_HOPPER: - if (Rs2GameObject.interact(ObjectID.MM_LAB_HOPPER)) { - Rs2Player.waitForWalking(); - Rs2Inventory.waitForInventoryChanges(10000); - mixologyState = MixologyState.MIX_POTION_STAGE_1; - } - break; - case MIX_POTION_STAGE_1: - - Map itemsToCheck = new HashMap<>(); - PotionOrder potionToMake = null; - - for (PotionOrder _potionOrder : potionOrders) { - int key = _potionOrder.potionType().itemId(); - int value = itemsToCheck.getOrDefault(key, 0); - itemsToCheck.put(key, value + 1); - } - - for (int itemId : itemsToCheck.keySet()) { - PotionOrder _potionOrder = potionOrders - .stream() - .filter(x -> x.potionType().itemId() == itemId) - .findFirst() - .orElse(null); - - if (_potionOrder == null) continue; - - int itemAmount = itemsToCheck.getOrDefault(itemId, 1); - - if (!Rs2Inventory.hasItemAmount(itemId, itemAmount)) { - potionToMake = _potionOrder; - } - } - - if (potionToMake == null) { - mixologyState = MixologyState.MIX_POTION_STAGE_2; - return; - } - - if (canCreatePotion(potionToMake)) { - mixologyState = MixologyState.TAKE_FROM_MIXIN_VESSEL; - leverRetries = 0; - } else { - createPotion(potionToMake, config); - } - break; - case TAKE_FROM_MIXIN_VESSEL: - Rs2GameObject.interact(MIXING_VESSEL.objectId()); - boolean result = Rs2Inventory.waitForInventoryChanges(5000); - if (result) { - mixologyState = MixologyState.MIX_POTION_STAGE_1; - } - break; - case MIX_POTION_STAGE_2: - - // Sort using a custom comparator - List nonFulfilledPotions = potionOrders - .stream() - .filter(x -> !x.fulfilled()) - .sorted(Comparator.comparingInt(customOrder::indexOf)) - .collect(Collectors.toList()); - - if (nonFulfilledPotions.isEmpty()) { - mixologyState = MixologyState.CONVEYER_BELT; - return; - } - - PotionOrder nonFulfilledPotion = nonFulfilledPotions.get(0); - - if (Rs2Player.isAnimating()) { - if (agitatorQuickActionTicks > 0 && config.useQuickActionOnAgitator()) { - int clicks = Rs2AntibanSettings.naturalMouse ? Rs2Random.between(4, 6) : Rs2Random.between(6, 10); - for (int i = 0; i < clicks; i++) { - quickActionProcessPotion(nonFulfilledPotion); - } - agitatorQuickActionTicks = 0; - } else if (alembicQuickActionTicks > 0 && config.useQuickActionOnAlembic()) { - quickActionProcessPotion(nonFulfilledPotion); - alembicQuickActionTicks = 0; - } - if (nonFulfilledPotion.potionModifier().alchemyObject() == AlchemyObject.RETORT && config.useQuickActionOnRetort()&&Microbot.getVarbitValue(11327)<15&&Microbot.getVarbitValue(11327)!=0) { - quickActionProcessPotion(nonFulfilledPotion); - sleep(350, 400); - } - return; - } - - if (nonFulfilledPotion == null || !Rs2Inventory.hasItem(nonFulfilledPotion.potionType().itemId())) { - mixologyState = MixologyState.MIX_POTION_STAGE_1; - return; - } - - processPotion(nonFulfilledPotion); - sleepUntil(Rs2Player::isAnimating); - break; - case CONVEYER_BELT: - if (potionOrders.stream().noneMatch(x -> Rs2Inventory.hasItem(x.potionType().getFulfilledItemId()))) { - mixologyState = MixologyState.MIX_POTION_STAGE_1; - return; - } - if (Rs2GameObject.interact(AlchemyObject.CONVEYOR_BELT.objectId())) { - Rs2Inventory.waitForInventoryChanges(5000); - currentAgaPoints = getAgaPoints(); - currentLyePoints = getLyePoints(); - currentMoxPoints = getMoxPoints(); - } - break; - } - - - long endTime = System.currentTimeMillis(); - long totalTime = endTime - startTime; - System.out.println("Total time for loop " + totalTime); - - } catch (Exception ex) { - System.out.println(ex.getMessage()); - } - }, 0, 100, TimeUnit.MILLISECONDS); - return true; - } - - private boolean hasAllFulFilledItems() { - Map itemsToCheck = new HashMap<>(); - boolean hasAllFulFilledItems = true; - - for (PotionOrder _potionOrder : potionOrders) { - int key = _potionOrder.potionType().getFulfilledItemId(); - int value = itemsToCheck.getOrDefault(key, 0); - itemsToCheck.put(key, value + 1); - } - - for (int itemId : itemsToCheck.keySet()) { - PotionOrder _potionOrder = potionOrders - .stream() - .filter(x -> x.potionType().getFulfilledItemId() == itemId) - .findFirst() - .orElse(null); - - if (_potionOrder == null) continue; - - int itemAmount = itemsToCheck.getOrDefault(itemId, 1); - - if (!Rs2Inventory.hasItemAmount(itemId, itemAmount)) { - hasAllFulFilledItems = false; - } - } - return hasAllFulFilledItems; - } - - private static void processPotion(PotionOrder nonFulfilledPotion) { - switch (nonFulfilledPotion.potionModifier()) { - case HOMOGENOUS: - GameObject agitator = (GameObject) Rs2GameObject.findObjectById(AlchemyObject.AGITATOR.objectId()); - if (agitator != null && (((DynamicObject) agitator.getRenderable()).getAnimation().getId() == 11633 || ((DynamicObject) agitator.getRenderable()).getAnimation().getId() == 11632)) { - Rs2GameObject.interact(AlchemyObject.AGITATOR.objectId()); - } else { - Rs2Inventory.useItemOnObject(nonFulfilledPotion.potionType().itemId(), AlchemyObject.AGITATOR.objectId()); - } - break; - case CONCENTRATED: - GameObject retort = (GameObject) Rs2GameObject.findObjectById(AlchemyObject.RETORT.objectId()); - if (retort != null && (((DynamicObject) retort.getRenderable()).getAnimation().getId() == 11643 || ((DynamicObject) retort.getRenderable()).getAnimation().getId() == 11642)) { - Rs2GameObject.interact(AlchemyObject.RETORT.objectId()); - } else { - Rs2Inventory.useItemOnObject(nonFulfilledPotion.potionType().itemId(), AlchemyObject.RETORT.objectId()); - } - break; - case CRYSTALISED: - GameObject alembic = (GameObject) Rs2GameObject.findObjectById(AlchemyObject.ALEMBIC.objectId()); - if (alembic != null && (((DynamicObject) alembic.getRenderable()).getAnimation().getId() == 11638 || ((DynamicObject) alembic.getRenderable()).getAnimation().getId() == 11637)) { - Rs2GameObject.interact(AlchemyObject.ALEMBIC.objectId()); - } else { - Rs2Inventory.useItemOnObject(nonFulfilledPotion.potionType().itemId(), AlchemyObject.ALEMBIC.objectId()); - } - break; - } - } - - private static void quickActionProcessPotion(PotionOrder nonFulfilledPotion) { - switch (nonFulfilledPotion.potionModifier()) { - case HOMOGENOUS: - Rs2GameObject.interact(AlchemyObject.AGITATOR.objectId()); - break; - case CONCENTRATED: - Rs2GameObject.interact(AlchemyObject.RETORT.objectId()); - break; - case CRYSTALISED: - Rs2GameObject.interact(AlchemyObject.ALEMBIC.objectId()); - break; - } - } - - private void createPotion(PotionOrder potionOrder, MixologyConfig config) { - for (PotionComponent component : potionOrder.potionType().components()) { - if (canCreatePotion(potionOrder)) break; - if (component.character() == 'A') { - Rs2GameObject.interact(AlchemyObject.AGA_LEVER.objectId()); - } else if (component.character() == 'L') { - Rs2GameObject.interact(AlchemyObject.LYE_LEVER.objectId()); - } else if (component.character() == 'M') { - Rs2GameObject.interact(AlchemyObject.MOX_LEVER.objectId()); - } - if (config.useQuickActionLever()) { - Rs2Player.waitForAnimation(); - } else { - sleepUntil(Rs2Player::isAnimating); - final int sleep = Rs2Random.between(300, 600); - sleepGaussian(sleep, sleep / 4); - } - leverRetries++; - } - } - - private boolean canCreatePotion(PotionOrder potionOrder) { - // Get the mixer game objects - GameObject[] mixers = { - (GameObject) Rs2GameObject.findObjectById(ObjectID.MM_LAB_MIXER_03), // mixer3 - (GameObject) Rs2GameObject.findObjectById(ObjectID.MM_LAB_MIXER_02), // mixer2 - (GameObject) Rs2GameObject.findObjectById(ObjectID.MM_LAB_MIXER_01) // mixer1 - }; - - // Check if any mixers are missing - if (Arrays.stream(mixers).anyMatch(Objects::isNull)) { - return false; - } - - // Get animations in correct order - int[] currentAnimations = Arrays.stream(mixers) - .map(mixer -> ((DynamicObject) mixer.getRenderable()).getAnimation().getId()) - .mapToInt(Integer::intValue) - .toArray(); - - // Map components to their valid animations - Map componentAnimations = Map.of( - 'A', new int[]{11615, 11609, 11612}, // AGA animations - 'M', new int[]{11617, 11614, 11607}, // MOX animations - 'L', new int[]{11608, 11611, 11618} // LYE animations - ); - - // Check each position - for (int i = 0; i < potionOrder.potionType().components().length; i++) { - char expectedComponent = potionOrder.potionType().components()[i].character(); - int currentAnimation = currentAnimations[i]; - - boolean isValid = Arrays.stream(componentAnimations.get(expectedComponent)) - .anyMatch(validAnim -> validAnim == currentAnimation); - - if (!isValid) { - return false; - } - } - - return true; - } - - private int getMoxPoints() { - return Integer.parseInt(Rs2Widget.getWidget(882, 2).getDynamicChildren()[16].getText()); - } - - private int getAgaPoints() { - return Integer.parseInt(Rs2Widget.getWidget(882, 2).getDynamicChildren()[17].getText()); - } - - private int getLyePoints() { - return Integer.parseInt(Rs2Widget.getWidget(882, 2).getDynamicChildren()[18].getText()); - } - - @Override - public void shutdown() { - super.shutdown(); - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/MixologyState.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/MixologyState.java deleted file mode 100644 index 44c7dcd7c74..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/MixologyState.java +++ /dev/null @@ -1,12 +0,0 @@ -package net.runelite.client.plugins.microbot.mixology; - -public enum MixologyState { - IDLE, - BANK, - REFINER, - DEPOSIT_HOPPER, - MIX_POTION_STAGE_1, - TAKE_FROM_MIXIN_VESSEL, - MIX_POTION_STAGE_2, - CONVEYER_BELT -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/MoxHerbs.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/MoxHerbs.java deleted file mode 100644 index cad04822f89..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/MoxHerbs.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.runelite.client.plugins.microbot.mixology; - -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor -public enum MoxHerbs { - GUAM("guam leaf"), - Marrentill("marrentill"), - Tarromin("tarromin"), - Harralander("harralander"); - - private final String itemName; - - @Override - public String toString() { - return itemName; - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/PotionComponent.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/PotionComponent.java deleted file mode 100644 index 22fcc672fa5..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/PotionComponent.java +++ /dev/null @@ -1,25 +0,0 @@ -package net.runelite.client.plugins.microbot.mixology; - - -public enum PotionComponent { - AGA('A', "00e676"), - LYE('L', "e91e63"), - MOX('M', "03a9f4"); - - private final char character; - private final String color; - - private PotionComponent(char character, String color) { - this.character = character; - this.color = color; - } - - public char character() { - return this.character; - } - - public String color() { - return this.color; - } -} - diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/PotionModifier.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/PotionModifier.java deleted file mode 100644 index 4b8c93d149a..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/PotionModifier.java +++ /dev/null @@ -1,28 +0,0 @@ -package net.runelite.client.plugins.microbot.mixology; - -public enum PotionModifier { - HOMOGENOUS(AlchemyObject.AGITATOR, 21), - CONCENTRATED(AlchemyObject.RETORT, 20), - CRYSTALISED(AlchemyObject.ALEMBIC, 14); - - private static final PotionModifier[] TYPES = values(); - private final AlchemyObject alchemyObject; - private final int quickActionExperience; - - private PotionModifier(AlchemyObject alchemyObject, int quickActionExperience) { - this.alchemyObject = alchemyObject; - this.quickActionExperience = quickActionExperience; - } - - public static PotionModifier from(int potionModifierId) { - return potionModifierId >= 0 && potionModifierId < TYPES.length ? TYPES[potionModifierId] : null; - } - - public AlchemyObject alchemyObject() { - return this.alchemyObject; - } - - public int quickActionExperience() { - return this.quickActionExperience; - } -} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/PotionOrder.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/PotionOrder.java deleted file mode 100644 index 4f0fa581c64..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/PotionOrder.java +++ /dev/null @@ -1,38 +0,0 @@ -package net.runelite.client.plugins.microbot.mixology; - -public class PotionOrder { - private final int idx; - private final PotionType potionType; - private final PotionModifier potionModifier; - private boolean fulfilled; - - public PotionOrder(int idx, PotionType potionType, PotionModifier potionModifier) { - this.idx = idx; - this.potionType = potionType; - this.potionModifier = potionModifier; - } - - public int idx() { - return this.idx; - } - - public PotionType potionType() { - return this.potionType; - } - - public PotionModifier potionModifier() { - return this.potionModifier; - } - - public void setFulfilled(boolean fulfilled) { - this.fulfilled = fulfilled; - } - - public boolean fulfilled() { - return this.fulfilled; - } - - public String toString() { - return "PotionOrder{idx=" + this.idx + ", potionType=" + this.potionType + ", potionModifier=" + this.potionModifier + "}"; - } -} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/PotionType.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/PotionType.java deleted file mode 100644 index e4234fb2ddc..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/mixology/PotionType.java +++ /dev/null @@ -1,90 +0,0 @@ -package net.runelite.client.plugins.microbot.mixology; - -import com.google.common.collect.ImmutableMap; -import lombok.Getter; -import net.runelite.api.gameval.ItemID; - -import java.util.Arrays; -import java.util.Map; - -public enum PotionType { - MAMMOTH_MIGHT_MIX(ItemID.MM_POTION_MMM_UNFINISHED, ItemID.MM_POTION_MMM_FINISHED, 1900, new PotionComponent[]{PotionComponent.MOX, PotionComponent.MOX, PotionComponent.MOX}), - MYSTIC_MANA_AMALGAM(ItemID.MM_POTION_MMA_UNFINISHED, ItemID.MM_POTION_MMA_FINISHED, 2150, new PotionComponent[]{PotionComponent.MOX, PotionComponent.MOX, PotionComponent.AGA}), - MARLEYS_MOONLIGHT(ItemID.MM_POTION_MML_UNFINISHED, ItemID.MM_POTION_MML_FINISHED, 2400, new PotionComponent[]{PotionComponent.MOX, PotionComponent.MOX, PotionComponent.LYE}), - ALCO_AUGMENTATOR(ItemID.MM_POTION_AAA_UNFINISHED, ItemID.MM_POTION_AAA_FINISHED, 1900, new PotionComponent[]{PotionComponent.AGA, PotionComponent.AGA, PotionComponent.AGA}), - AZURE_AURA_MIX(ItemID.MM_POTION_AAM_UNFINISHED, ItemID.MM_POTION_AAM_FINISHED, 2650, new PotionComponent[]{PotionComponent.AGA, PotionComponent.AGA, PotionComponent.MOX}), - AQUALUX_AMALGAM(ItemID.MM_POTION_AAL_UNFINISHED, ItemID.MM_POTION_AAL_FINISHED, 2900, new PotionComponent[]{PotionComponent.AGA, PotionComponent.LYE, PotionComponent.AGA}), - LIPLACK_LIQUOR(ItemID.MM_POTION_LLL_UNFINISHED, ItemID.MM_POTION_LLL_FINISHED, 1900, new PotionComponent[]{PotionComponent.LYE, PotionComponent.LYE, PotionComponent.LYE}), - MEGALITE_LIQUID(ItemID.MM_POTION_LLM_UNFINISHED, ItemID.MM_POTION_LLM_FINISHED, 3150, new PotionComponent[]{PotionComponent.MOX, PotionComponent.LYE, PotionComponent.LYE}), - ANTI_LEECH_LOTION(ItemID.MM_POTION_LLA_UNFINISHED, ItemID.MM_POTION_LLA_FINISHED, 3400, new PotionComponent[]{PotionComponent.AGA, PotionComponent.LYE, PotionComponent.LYE}), - MIXALOT(ItemID.MM_POTION_MAL_UNFINISHED, ItemID.MM_POTION_MAL_FINISHED, 3650, new PotionComponent[]{PotionComponent.MOX, PotionComponent.AGA, PotionComponent.LYE}); - - public static final PotionType[] TYPES = values(); - private static final Map ITEM_MAP; - private final int itemId; - @Getter - private final int fulfilledItemId; - private final String recipe; - private final String abbreviation; - private final int experience; - private final PotionComponent[] components; - - PotionType(int itemId, int fulfilledItemId, int experience, PotionComponent... components) { - this.itemId = itemId; - this.fulfilledItemId = fulfilledItemId; - this.recipe = colorizeRecipe(components); - this.experience = experience; - this.components = components; - this.abbreviation = "" + components[0].character() + components[1].character() + components[2].character(); - } - - public static PotionType fromItemId(int itemId) { - return (PotionType)ITEM_MAP.get(itemId); - } - - public static PotionType fromIdx(int potionTypeId) { - return potionTypeId >= 0 && potionTypeId < TYPES.length ? TYPES[potionTypeId] : null; - } - - private static String colorizeRecipe(PotionComponent[] components) { - if (components.length != 3) { - throw new IllegalArgumentException("Invalid potion components: " + Arrays.toString(components)); - } else { - String var10000 = colorizeRecipeComponent(components[0]); - return var10000 + colorizeRecipeComponent(components[1]) + colorizeRecipeComponent(components[2]); - } - } - - private static String colorizeRecipeComponent(PotionComponent component) { - return "" + component.character() + ""; - } - - public int itemId() { - return this.itemId; - } - - public String recipe() { - return this.recipe; - } - public int experience() { - return this.experience; - } - - public PotionComponent[] components() { - return this.components; - } - - public String abbreviation() { - return this.abbreviation; - } - - static { - ImmutableMap.Builder builder = new ImmutableMap.Builder(); - for (PotionType potionType: values()) { - builder.put(potionType.itemId(), potionType); - } - - ITEM_MAP = builder.build(); - } -} - diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/moonsOfPeril/handlers/BossHandler.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/moonsOfPeril/handlers/BossHandler.java index fb4dddd3816..4d14275f5a1 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/moonsOfPeril/handlers/BossHandler.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/moonsOfPeril/handlers/BossHandler.java @@ -113,6 +113,9 @@ public void eatIfNeeded() { public void drinkIfNeeded() { int maxPrayer = Rs2Player.getRealSkillLevel(Skill.PRAYER); int minimumPrayerPoint = (maxPrayer * prayerPercentage) / 100; + if (!Rs2Player.hasMoonlightActive()) { + Rs2Player.drinkPrayerPotion(); + } Rs2Player.drinkPrayerPotionAt(minimumPrayerPoint); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/moonsOfPeril/handlers/ResupplyHandler.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/moonsOfPeril/handlers/ResupplyHandler.java index cb28b54498f..d6892ad3b89 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/moonsOfPeril/handlers/ResupplyHandler.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/moonsOfPeril/handlers/ResupplyHandler.java @@ -177,7 +177,8 @@ private int checkPotionQuantum(int target) int desired = target - countMoonlightPotions(); if (desired <= 0) return 0; - int requiredSlots = desired * 2 + 1; // 2 per potion + 1 mortar + final int overheadSlots = 2; // 1 mortar + 1 extra slot for double pulls from crate + int requiredSlots = desired * 2 + overheadSlots; // 2 per potion + 1 mortar + 1 space for double potion pulls from crate if (debugLogging) {Microbot.log("Required free inventory slots: " + requiredSlots);} int freeSlots = Rs2Inventory.emptySlotCount(); if (debugLogging) {Microbot.log("Current free inventory slots: " + freeSlots);} @@ -196,7 +197,7 @@ private int checkPotionQuantum(int target) freeSlots = Rs2Inventory.emptySlotCount(); } if (freeSlots < requiredSlots) { - desired = Math.max((freeSlots - 1) / 2, 0); + desired = Math.max((freeSlots - overheadSlots) / 2, 0); } return desired; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLConfig.java index 878c102ec90..7a3d0c5eb33 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLConfig.java @@ -1,9 +1,9 @@ package net.runelite.client.plugins.microbot.qualityoflife; import net.runelite.client.config.*; -import net.runelite.client.plugins.microbot.fletching.enums.FletchingItem; import net.runelite.client.plugins.microbot.inventorysetups.InventorySetup; import net.runelite.client.plugins.microbot.qualityoflife.enums.CraftingItem; +import net.runelite.client.plugins.microbot.qualityoflife.enums.FletchingItem; import net.runelite.client.plugins.microbot.qualityoflife.enums.WintertodtActions; import net.runelite.client.plugins.microbot.util.misc.SpecialAttackWeaponEnum; @@ -133,6 +133,13 @@ public interface QoLConfig extends Config { ) String autoPrayerSection = "autoPrayerSection"; + @ConfigSection( + name = "Grand Exchange", + description = "Grand Exchange settings", + position = 91 + ) + String grandExchangeSection = "grandExchangeSection"; + // boolean to render Max Hit Overlay @ConfigItem( keyName = "renderMaxHitOverlay", @@ -934,4 +941,14 @@ default boolean aggressiveAntiPkMode() { return false; } + @ConfigItem( + keyName = "grandExchangeHotkey", + name = "Paste and Search GE Hotkey", + description = "Pastes clipboard text into the GE search box.", + position = 0, + section = grandExchangeSection + ) + default Keybind grandExchangeHotkey() { + return Keybind.NOT_SET; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLPlugin.java index 3b3f0109f48..8b636f3990c 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLPlugin.java @@ -10,8 +10,16 @@ import net.runelite.client.eventbus.Subscribe; import net.runelite.client.events.ConfigChanged; import net.runelite.client.events.ProfileChanged; +import net.runelite.client.input.KeyManager; +import net.runelite.api.gameval.InterfaceID; +import net.runelite.api.gameval.VarClientID; +import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; +import java.awt.Toolkit; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.DataFlavor; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.api.widgets.Widget; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.MicrobotPlugin; import net.runelite.client.plugins.microbot.inventorysetups.InventorySetup; @@ -47,6 +55,13 @@ import net.runelite.client.ui.SplashScreen; import net.runelite.client.ui.overlay.OverlayManager; import net.runelite.client.util.ImageUtil; +import net.runelite.client.input.KeyListener; +import net.runelite.api.gameval.InterfaceID; +import net.runelite.api.gameval.VarClientID; +import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; +import java.awt.Toolkit; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.DataFlavor; import org.apache.commons.lang3.reflect.FieldUtils; @@ -73,7 +88,7 @@ enabledByDefault = false ) @Slf4j -public class QoLPlugin extends Plugin { +public class QoLPlugin extends Plugin implements KeyListener { public static final List bankMenuEntries = new LinkedList<>(); public static final List furnaceMenuEntries = new LinkedList<>(); public static final List anvilMenuEntries = new LinkedList<>(); @@ -142,6 +157,9 @@ public class QoLPlugin extends Plugin { @Inject private AutoPrayer autoPrayer; + @Inject + private KeyManager keyManager; + @Provides QoLConfig provideConfig(ConfigManager configManager) { return configManager.getConfig(QoLConfig.class); @@ -203,6 +221,7 @@ protected void startUp() throws AWTException { bankpinScript.run(config); potionManagerScript.run(config); autoPrayer.run(config); + keyManager.registerKeyListener(this); // pvpScript.run(config); awaitExecutionUntil(() ->Microbot.getClientThread().invokeLater(this::updateUiElements), () -> !SplashScreen.isOpen(), 600); } @@ -222,6 +241,7 @@ protected void shutDown() { eventBus.unregister(craftingManager); potionManagerScript.shutdown(); autoPrayer.shutdown(); + keyManager.unregisterKeyListener(this); } @Subscribe( @@ -824,4 +844,36 @@ public void onPlayerChanged(PlayerChanged event) { autoPrayer.handleAggressivePrayerOnGearChange(event.getPlayer(), config); } } + + @Override + public void keyTyped(KeyEvent e) { + if (config.grandExchangeHotkey().matches(e)) { + e.consume(); + } + } + + @Override + public void keyPressed(KeyEvent e) { + if (config.grandExchangeHotkey().matches(e)) { + e.consume(); + try { + String clipboardText = (String) Toolkit.getDefaultToolkit().getSystemClipboard().getData(DataFlavor.stringFlavor); + if (clipboardText != null && !clipboardText.isEmpty()) { + Microbot.getClientThread().invoke(() -> { + Microbot.getClient().setVarcStrValue(VarClientID.MESLAYERINPUT, clipboardText); + Microbot.getClient().runScript(ScriptID.GE_ITEM_SEARCH); + }); + } + } catch (Exception ex) { + Microbot.log("Failed to paste from clipboard: " + ex.getMessage()); + } + } + } + + @Override + public void keyReleased(KeyEvent e) { + if (config.grandExchangeHotkey().matches(e)) { + e.consume(); + } + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/enums/FletchingItem.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/enums/FletchingItem.java new file mode 100644 index 00000000000..f8329ccac13 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/enums/FletchingItem.java @@ -0,0 +1,39 @@ +package net.runelite.client.plugins.microbot.qualityoflife.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum FletchingItem +{ + ARROW_SHAFT("Arrow shaft", '1', "arrow shaft", 1), + SHORT("Short bows", '2', "shortbow", 1), + LONG("Long bows", '3', "longbow", 1), + STOCK("Crossbow stock", '4', "stock", 1), + SHIELD("Shield", '5', "shield", 2); + + private final String name; + private final char option; + private final String containsInventoryName; + private final int amountRequired; + + @Override + public String toString() + { + return name; + } + + public char getOption(FletchingMaterial material, FletchingMode fletchingMode) { + if (fletchingMode == FletchingMode.STRUNG + || fletchingMode == FletchingMode.PROGRESSIVE_STRUNG) { + return '1'; + } + if (material == FletchingMaterial.LOG && option == '2') return '3'; + if (material == FletchingMaterial.LOG && option == '3') return '4'; + //redwood is an exception + if (material == FletchingMaterial.REDWOOD) + return '2'; + return option; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/enums/FletchingLogs.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/enums/FletchingLogs.java index 99ef95ec253..dc8f3f4708e 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/enums/FletchingLogs.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/enums/FletchingLogs.java @@ -2,7 +2,6 @@ import net.runelite.api.gameval.ItemID; import net.runelite.api.Skill; -import net.runelite.client.plugins.microbot.fletching.enums.FletchingItem; 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.player.Rs2Player; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/enums/FletchingMaterial.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/enums/FletchingMaterial.java new file mode 100644 index 00000000000..8bfc756af22 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/enums/FletchingMaterial.java @@ -0,0 +1,27 @@ +package net.runelite.client.plugins.microbot.qualityoflife.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum FletchingMaterial +{ + LOG(""), + WOOD("Wood"), + OAK("Oak"), + WILLOW("Willow"), + MAPLE("Maple"), + YEW("Yew"), + MAGIC("Magic"), + REDWOOD("Redwood"); + + private final String name; + + + @Override + public String toString() + { + return name; + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/enums/FletchingMode.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/enums/FletchingMode.java new file mode 100644 index 00000000000..85f8223f98f --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/enums/FletchingMode.java @@ -0,0 +1,24 @@ +package net.runelite.client.plugins.microbot.qualityoflife.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum FletchingMode { + UNSTRUNG("Cutting", "knife", 1), + STRUNG("Stringing", "bow string", 14), + PROGRESSIVE_STRUNG("Progressive Bow Stringing", "bow string", 14), + UNSTRUNG_STRUNG("Cutting & Stringing", "knife", 1), + PROGRESSIVE("Progressive Logs Cutting", "knife", 1); + + + private final String name; + private final String itemName; + private final int amount; + + @Override + public String toString() { + return name; + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/.quest-helper-sync b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/.quest-helper-sync new file mode 100644 index 00000000000..98091843fe1 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/.quest-helper-sync @@ -0,0 +1,51 @@ +# Quest Helper Sync Tracking File +# This file tracks the last synchronized commit from the external quest-helper repository +# +# External Repository: https://github.com/Zoinkwiz/quest-helper +# Local Integration: runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/ +# +# IMPORTANT: Do not manually edit this file unless you know what you're doing. +# Use the quest-helper update scripts to manage synchronization. + +# Current sync position - this is the last external commit that has been integrated +5539626281a502ba8c44fe59b8b57bce32fe4f16 + +# Sync metadata (for script reference) +# External repo HEAD at last check: 5539626281a502ba8c44fe59b8b57bce32fe4f16 +# Last sync date: 2025-08-29 +# Sync method: automated script with manual package transformation +# Integration branch: quest-helper-update-20250829_063754 +# Local commit: cb416ef02a (quest-helper: polish Enakhras Lament) + +# Sync history log: +# 2025-08-29 07:14:46 - Applied commit 55396262 (Polish enahkras lament #2294) +# - Enhanced EnakhrasLament.java with better code organization +# - Fixed Shadow Room brazier puzzle sequence and locations +# - Added missing chisel icon for cutOffLimb step +# - Files changed: 1 Java file, +359/-204 lines +# - Package transformation: com.questhelper -> net.runelite.client.plugins.microbot.questhelper +# - Status: SUCCESS +# +# 2025-08-28 16:35:20 - Complete sync to version 4.10.0 +# - Applied 76 patches successfully from external repository +# - Skipped 4 patches (build files, CI workflows not applicable to Microbot) +# - Base sync point: 6329d81ee15a07a4b4fb53217bd4305e978c7a0e +# - Status: SUCCESS +# +# 2025-08-28 16:28:12 - Partial sync (first 4 patches) +# - Initial automated sync setup and testing +# - Applied commits d2df892a..4c204e89 +# - Status: SUCCESS +# +# 2025-08-28 16:13:45 - Initial sync tracking setup +# - Created sync file and established tracking +# - Method: manual setup +# - Status: INITIALIZED + +# Integration status +# Current external version: 4.10.0+ +# Integration health: HEALTHY +# Last validation: 2025-08-29 +# Compilation status: SUCCESS +# Package transformation: COMPLETE +# Sync file location: CORRECT (moved from project root) \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/QuestHelperConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/QuestHelperConfig.java index 2277d5fad83..9047d7866fa 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/QuestHelperConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/QuestHelperConfig.java @@ -534,6 +534,17 @@ default boolean showWidgetHints() ) default boolean solvePuzzles() { return true; } + @ConfigItem( + keyName = "showWorldMapPoint", + name = "Display world map point", + description = "Choose whether the arrow & icon of your current step should be visible on the world map.
    Changing this will take effect next time your quest step updates.", + section = hintsSection + ) + default boolean showWorldMapPoint() + { + return true; + } + @ConfigItem( keyName = "useShortestPath", name = "Use 'Shortest Path' plugin", @@ -681,4 +692,24 @@ default boolean showCompletedQuests() { return false; } + + @ConfigSection( + position = 5, + name = "Development", + description = "Options that configure the quest helper development experience", + closedByDefault = true + ) + String developmentSection = "developmentSection"; + + @ConfigItem( + keyName = "devShowOverlayOnLaunch", + name = "Show overlay on launch", + description = "Show the dev overlay (::questhelperdebug) on launch", + position = 4, + section = developmentSection + ) + default boolean devShowOverlayOnLaunch() + { + return false; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/QuestHelperPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/QuestHelperPlugin.java index 349bc76f559..fa2c724236d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/QuestHelperPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/QuestHelperPlugin.java @@ -29,6 +29,7 @@ import com.google.inject.Injector; import com.google.inject.Module; import com.google.inject.Provides; +import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.questhelper.bank.banktab.BankTabItems; import net.runelite.client.plugins.microbot.questhelper.bank.banktab.PotionStorage; import net.runelite.client.plugins.microbot.questhelper.managers.*; @@ -71,6 +72,7 @@ import net.runelite.client.util.Text; import org.apache.commons.lang3.ArrayUtils; +import javax.annotation.Nullable; import javax.inject.Inject; import javax.inject.Named; import javax.swing.*; @@ -198,6 +200,14 @@ protected void startUp() throws IOException questOverlayManager.startUp(); + if (developerMode) + { + if (config.devShowOverlayOnLaunch()) + { + questOverlayManager.addDebugOverlay(); + } + } + final BufferedImage icon = Icon.QUEST_ICON.getImage(); panel = new QuestHelperPanel(this, questManager, configManager); @@ -229,6 +239,11 @@ protected void shutDown() eventBus.unregister(playerStateManager); eventBus.unregister(runeliteObjectManager); eventBus.unregister(worldMapAreaManager); + if (developerMode) + { + // We don't check if it was added, since removing an unadded overlay is a no-op + questOverlayManager.removeDebugOverlay(); + } questOverlayManager.shutDown(); playerStateManager.shutDown(); @@ -409,6 +424,18 @@ public void onConfigChanged(ConfigChanged event) questManager.getSelectedQuest().setSidebarOrder(loadSidebarOrder(questManager.getSelectedQuest())); } } + + if (developerMode && "devShowOverlayOnLaunch".equals(event.getKey())) + { + if (config.devShowOverlayOnLaunch()) + { + questOverlayManager.addDebugOverlay(); + } + else + { + questOverlayManager.removeDebugOverlay(); + } + } } @Subscribe @@ -462,7 +489,7 @@ public List getPluginBankTagItemsForSections() return questBankManager.getBankTagService().getPluginBankTagItemsForSections(false); } - public QuestHelper getSelectedQuest() + public @Nullable QuestHelper getSelectedQuest() { return questManager.getSelectedQuest(); } @@ -553,7 +580,7 @@ private void instantiate(QuestHelperQuest quest) binder.bind(QuestHelper.class).toInstance(questHelper); binder.install(questHelper); }; - Injector questInjector = RuneLite.getInjector().createChildInjector(questModule); + Injector questInjector = Microbot.getInjector().createChildInjector(questModule); injector.injectMembers(questHelper); questHelper.setInjector(questInjector); questHelper.setQuest(quest); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/collections/ItemCollections.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/collections/ItemCollections.java index 300131a53c1..07007445b54 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/collections/ItemCollections.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/collections/ItemCollections.java @@ -170,6 +170,7 @@ public enum ItemCollections SAW("Saw", ImmutableList.of( ItemID.POH_SAW, ItemID.WEARABLE_SAW, + ItemID.WEARABLE_SAW_OFFHAND, ItemID.EYEGLO_CRYSTAL_SAW )), @@ -485,7 +486,14 @@ public enum ItemCollections ItemID._4DOSEPRAYERRESTORE, ItemID._3DOSEPRAYERRESTORE, ItemID._2DOSEPRAYERRESTORE, - ItemID._1DOSEPRAYERRESTORE + ItemID._1DOSEPRAYERRESTORE, + ItemID._4DOSE2RESTORE, + ItemID._3DOSE2RESTORE, + ItemID._2DOSE2RESTORE, + ItemID._1DOSE2RESTORE, + ItemID.HUNTER_MIX_MOONMOTH_2DOSE, + ItemID.HUNTER_MIX_MOONMOTH_1DOSE, + ItemID.BUTTERFLY_JAR_MOONMOTH )), RESTORE_POTIONS(ImmutableList.of( @@ -1218,17 +1226,16 @@ public enum ItemCollections ItemID.DRAMEN_STAFF )), - ESSENCE_LOW(ImmutableList.of( - ItemID.BLANKRUNE_DAEYALT, - ItemID.BLANKRUNE_HIGH, - ItemID.BLANKRUNE - )), - ESSENCE_HIGH(ImmutableList.of( ItemID.BLANKRUNE_DAEYALT, ItemID.BLANKRUNE_HIGH )), + ESSENCE_LOW(new ImmutableList.Builder() + .addAll(ESSENCE_HIGH.items).add( + ItemID.BLANKRUNE).build() + ), + COINS(ImmutableList.of( ItemID.COINS, ItemID.MAGICTRAINING_COINS, diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/achievementdiaries/ardougne/ArdougneHard.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/achievementdiaries/ardougne/ArdougneHard.java index 25ef4b36aaa..1c2cc093fd6 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/achievementdiaries/ardougne/ArdougneHard.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/achievementdiaries/ardougne/ArdougneHard.java @@ -184,16 +184,20 @@ protected void setupRequirements() crystalTrink = new ItemRequirement("Crystal Trinket", ItemID.MOURNING_CRYSTAL_TRINKET).showConditioned(notDeathRune).isNotConsumed(); highEss = new ItemRequirement("Pure or Daeyalt essence", ItemCollections.ESSENCE_HIGH) .showConditioned(notDeathRune); - newKey = new KeyringRequirement("New key", configManager, KeyringCollection.NEW_KEY).showConditioned(notDeathRune).isNotConsumed(); + + var hasCompletedSOTE = new QuestRequirement(QuestHelperQuest.SONG_OF_THE_ELVES, QuestState.FINISHED); + + newKey = new KeyringRequirement("New key", configManager, KeyringCollection.NEW_KEY).showConditioned(notDeathRune).isNotConsumed().hideConditioned(hasCompletedSOTE); newKey.setTooltip("Another can be found on the desk in the south-east room of the Mourner HQ basement."); - mournerBoots = new ItemRequirement("Mourner boots", ItemID.MOURNING_MOURNER_BOOTS).isNotConsumed(); - gasMask = new ItemRequirement("Gas mask", ItemID.GASMASK).isNotConsumed(); - mournerGloves = new ItemRequirement("Mourner gloves", ItemID.MOURNING_MOURNER_GLOVES).isNotConsumed(); - mournerCloak = new ItemRequirement("Mourner cloak", ItemID.MOURNING_MOURNER_CLOAK).isNotConsumed(); - mournerTop = new ItemRequirement("Mourner top", ItemID.MOURNING_MOURNER_TOP).isNotConsumed(); - mournerTrousers = new ItemRequirement("Mourner trousers", ItemID.MOURNING_MOURNER_LEGS).isNotConsumed(); + + mournerBoots = new ItemRequirement("Mourner boots", ItemID.MOURNING_MOURNER_BOOTS).isNotConsumed().hideConditioned(hasCompletedSOTE); + gasMask = new ItemRequirement("Gas mask", ItemID.GASMASK).isNotConsumed().hideConditioned(hasCompletedSOTE); + mournerGloves = new ItemRequirement("Mourner gloves", ItemID.MOURNING_MOURNER_GLOVES).isNotConsumed().hideConditioned(hasCompletedSOTE); + mournerCloak = new ItemRequirement("Mourner cloak", ItemID.MOURNING_MOURNER_CLOAK).isNotConsumed().hideConditioned(hasCompletedSOTE); + mournerTop = new ItemRequirement("Mourner top", ItemID.MOURNING_MOURNER_TOP).isNotConsumed().hideConditioned(hasCompletedSOTE); + mournerTrousers = new ItemRequirement("Mourner trousers", ItemID.MOURNING_MOURNER_LEGS).isNotConsumed().hideConditioned(hasCompletedSOTE); mournersOutfit = new ItemRequirements("Full mourners' outfit", gasMask, mournerTop, mournerTrousers, - mournerCloak, mournerBoots, mournerGloves).showConditioned(notDeathRune).isNotConsumed(); + mournerCloak, mournerBoots, mournerGloves).showConditioned(notDeathRune).isNotConsumed().hideConditioned(hasCompletedSOTE); mournersOutfit.setTooltip("Another set can be obtained at the north entrance to Arandar."); rake = new ItemRequirement("Rake", ItemID.RAKE) .showConditioned(new Conditions(LogicType.OR, notPalmTree, notPoisonIvy)).isNotConsumed(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/miniquests/barbariantraining/BarbarianTraining.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/miniquests/barbariantraining/BarbarianTraining.java index 55ab7657130..7041dcc9171 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/miniquests/barbariantraining/BarbarianTraining.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/miniquests/barbariantraining/BarbarianTraining.java @@ -667,7 +667,8 @@ public List getItemRequirements() bow, oakLogs, tinderbox, axe, feathers, knife, hammer, bronzeBar.quantity(2), logs.quantity(3), - attackPotion, roe); + attackPotion, roe, + antifireShield, combatGear); } @Override @@ -676,6 +677,12 @@ public List getItemRecommended() return Arrays.asList(gamesNecklace.quantity(5), catherbyTeleport); } + @Override + public List getCombatRequirements() + { + return Collections.singletonList("Mithril Dragon (level 304)"); + } + @Override public List getNotes() { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/miniquests/valetotems/ValeTotems.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/miniquests/valetotems/ValeTotems.java new file mode 100644 index 00000000000..411e998c0b1 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/miniquests/valetotems/ValeTotems.java @@ -0,0 +1,333 @@ +/* + * Copyright (c) 2025, pajlada + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.microbot.questhelper.helpers.miniquests.valetotems; + +import net.runelite.client.plugins.microbot.questhelper.panel.PanelDetails; +import net.runelite.client.plugins.microbot.questhelper.questhelpers.BasicQuestHelper; +import net.runelite.client.plugins.microbot.questhelper.questinfo.QuestHelperQuest; +import net.runelite.client.plugins.microbot.questhelper.requirements.Requirement; +import net.runelite.client.plugins.microbot.questhelper.requirements.conditional.Conditions; +import net.runelite.client.plugins.microbot.questhelper.requirements.item.ItemRequirement; +import net.runelite.client.plugins.microbot.questhelper.requirements.player.SkillRequirement; +import net.runelite.client.plugins.microbot.questhelper.requirements.quest.QuestRequirement; +import static net.runelite.client.plugins.microbot.questhelper.requirements.util.LogicHelper.and; +import static net.runelite.client.plugins.microbot.questhelper.requirements.util.LogicHelper.or; +import net.runelite.client.plugins.microbot.questhelper.requirements.util.Operation; +import net.runelite.client.plugins.microbot.questhelper.requirements.var.VarbitRequirement; +import net.runelite.client.plugins.microbot.questhelper.requirements.zone.Zone; +import net.runelite.client.plugins.microbot.questhelper.rewards.UnlockReward; +import net.runelite.client.plugins.microbot.questhelper.steps.ConditionalStep; +import net.runelite.client.plugins.microbot.questhelper.steps.NpcStep; +import net.runelite.client.plugins.microbot.questhelper.steps.ObjectStep; +import net.runelite.client.plugins.microbot.questhelper.steps.QuestStep; +import net.runelite.client.plugins.microbot.questhelper.steps.widget.WidgetHighlight; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import net.runelite.api.QuestState; +import net.runelite.api.Skill; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.gameval.ItemID; +import net.runelite.api.gameval.NpcID; +import net.runelite.api.gameval.ObjectID; +import net.runelite.api.gameval.VarbitID; + +/** + * The quest guide for the "Vale Totems" OSRS quest + */ +public class ValeTotems extends BasicQuestHelper +{ + // Item requirements + ItemRequirement knife; + ItemRequirement oneOakLog; + ItemRequirement fourDecorativeItems; + ItemRequirement threeDecorativeItems; + ItemRequirement twoDecorativeItems; + ItemRequirement oneDecorativeItem; + + // Miscellaneous requirements + VarbitRequirement needToBuildTotem; + VarbitRequirement isTotemBaseBuilt; + + VarbitRequirement needToCarveAnimals; + + Conditions isBuffaloNearby; + Conditions isJaguarNearby; + Conditions isEagleNearby; + Conditions isSnakeNearby; + Conditions isScorpionNearby; + + Conditions missingBuffaloCarve; + Conditions missingJaguarCarve; + Conditions missingEagleCarve; + Conditions missingSnakeCarve; + Conditions missingScorpionCarve; + + VarbitRequirement isDoneCarving; + + VarbitRequirement needToDecorate; + + VarbitRequirement oneShieldAdded; + VarbitRequirement twoShieldsAdded; + VarbitRequirement threeShieldsAdded; + VarbitRequirement isDoneDecorating; + + // Steps + NpcStep startQuest; + + ObjectStep buildTotemBase; + + ObjectStep carveAnimalsYouSee; + ConditionalStep carveTotem; + NpcStep talkToIsadoraAfterCarvingTotem; + ConditionalStep decorateTotem; + ObjectStep decorateTotemFourShields; + NpcStep talkToIsadoraAfterDecoratingTotem; + ConditionalStep carveAndDecorateTotem; + + QuestStep claimOffering; + + QuestStep finishQuest; + NpcStep talkToIsadoraToLearnAboutCarving; + + @Override + public Map loadSteps() + { + initializeRequirements(); + setupSteps(); + + var steps = new HashMap(); + + steps.put(0, startQuest); + steps.put(10, startQuest); + steps.put(20, startQuest); + steps.put(30, carveAndDecorateTotem); + steps.put(40, talkToIsadoraAfterDecoratingTotem); + steps.put(50, claimOffering); + steps.put(60, finishQuest); + + return steps; + } + + @Override + protected void setupRequirements() + { + knife = new ItemRequirement("Knife", ItemID.KNIFE); + knife.setTooltip("There's a knife upstairs in the General Store south of the miniquest start point"); + oneOakLog = new ItemRequirement("Oak log", ItemID.OAK_LOGS, 1); + oneOakLog.setTooltip("You can also use Willow, Maple, Yew, Magic, or Redwood logs, but it needs to match the decorative items you're bringing."); + + var possibleDecorativeItems = List.of(ItemID.OAK_SHIELD, ItemID.UNSTRUNG_OAK_LONGBOW, ItemID.OAK_LONGBOW, ItemID.UNSTRUNG_OAK_SHORTBOW, ItemID.OAK_SHORTBOW); + + fourDecorativeItems = new ItemRequirement("Oak shield/longbow/shortbow", possibleDecorativeItems, 4); + fourDecorativeItems.setTooltip("You can also use Willow, Maple, Yew, Magic, or Redwood decorative items, but it needs to match the logs you used to build the totem."); + threeDecorativeItems = new ItemRequirement("Oak shield/longbow/shortbow", possibleDecorativeItems, 3); + threeDecorativeItems.setTooltip("You can also use Willow, Maple, Yew, Magic, or Redwood decorative items, but it needs to match the logs you used to build the totem."); + twoDecorativeItems = new ItemRequirement("Oak shield/longbow/shortbow", possibleDecorativeItems, 2); + twoDecorativeItems.setTooltip("You can also use Willow, Maple, Yew, Magic, or Redwood decorative items, but it needs to match the logs you used to build the totem."); + oneDecorativeItem = new ItemRequirement("Oak shield/longbow/shortbow", possibleDecorativeItems, 1); + oneDecorativeItem.setTooltip("You can also use Willow, Maple, Yew, Magic, or Redwood decorative items, but it needs to match the logs you used to build the totem."); + + needToBuildTotem = new VarbitRequirement(VarbitID.ENT_TOTEMS_BROKEN_CHAT, 1); + isTotemBaseBuilt = new VarbitRequirement(VarbitID.ENT_TOTEMS_SITE_1_BASE, 1); + + isBuffaloNearby = or( + new VarbitRequirement(VarbitID.ENT_TOTEMS_SITE_1_ANIMAL_1, 1), + new VarbitRequirement(VarbitID.ENT_TOTEMS_SITE_1_ANIMAL_2, 1), + new VarbitRequirement(VarbitID.ENT_TOTEMS_SITE_1_ANIMAL_3, 1) + ); + isJaguarNearby = or( + new VarbitRequirement(VarbitID.ENT_TOTEMS_SITE_1_ANIMAL_1, 2), + new VarbitRequirement(VarbitID.ENT_TOTEMS_SITE_1_ANIMAL_2, 2), + new VarbitRequirement(VarbitID.ENT_TOTEMS_SITE_1_ANIMAL_3, 2) + ); + isEagleNearby = or( + new VarbitRequirement(VarbitID.ENT_TOTEMS_SITE_1_ANIMAL_1, 3), + new VarbitRequirement(VarbitID.ENT_TOTEMS_SITE_1_ANIMAL_2, 3), + new VarbitRequirement(VarbitID.ENT_TOTEMS_SITE_1_ANIMAL_3, 3) + ); + isSnakeNearby = or( + new VarbitRequirement(VarbitID.ENT_TOTEMS_SITE_1_ANIMAL_1, 4), + new VarbitRequirement(VarbitID.ENT_TOTEMS_SITE_1_ANIMAL_2, 4), + new VarbitRequirement(VarbitID.ENT_TOTEMS_SITE_1_ANIMAL_3, 4) + ); + isScorpionNearby = or( + new VarbitRequirement(VarbitID.ENT_TOTEMS_SITE_1_ANIMAL_1, 5), + new VarbitRequirement(VarbitID.ENT_TOTEMS_SITE_1_ANIMAL_2, 5), + new VarbitRequirement(VarbitID.ENT_TOTEMS_SITE_1_ANIMAL_3, 5) + ); + + missingBuffaloCarve = and( + new VarbitRequirement(VarbitID.ENT_TOTEMS_SITE_1_LOW, 10, Operation.NOT_EQUAL), + new VarbitRequirement(VarbitID.ENT_TOTEMS_SITE_1_MID, 10, Operation.NOT_EQUAL), + new VarbitRequirement(VarbitID.ENT_TOTEMS_SITE_1_TOP, 10, Operation.NOT_EQUAL) + ); + + missingJaguarCarve = and( + new VarbitRequirement(VarbitID.ENT_TOTEMS_SITE_1_LOW, 11, Operation.NOT_EQUAL), + new VarbitRequirement(VarbitID.ENT_TOTEMS_SITE_1_MID, 11, Operation.NOT_EQUAL), + new VarbitRequirement(VarbitID.ENT_TOTEMS_SITE_1_TOP, 11, Operation.NOT_EQUAL) + ); + + missingEagleCarve = and( + new VarbitRequirement(VarbitID.ENT_TOTEMS_SITE_1_LOW, 12, Operation.NOT_EQUAL), + new VarbitRequirement(VarbitID.ENT_TOTEMS_SITE_1_MID, 12, Operation.NOT_EQUAL), + new VarbitRequirement(VarbitID.ENT_TOTEMS_SITE_1_TOP, 12, Operation.NOT_EQUAL) + ); + + missingSnakeCarve = and( + new VarbitRequirement(VarbitID.ENT_TOTEMS_SITE_1_LOW, 13, Operation.NOT_EQUAL), + new VarbitRequirement(VarbitID.ENT_TOTEMS_SITE_1_MID, 13, Operation.NOT_EQUAL), + new VarbitRequirement(VarbitID.ENT_TOTEMS_SITE_1_TOP, 13, Operation.NOT_EQUAL) + ); + + missingScorpionCarve = and( + new VarbitRequirement(VarbitID.ENT_TOTEMS_SITE_1_LOW, 14, Operation.NOT_EQUAL), + new VarbitRequirement(VarbitID.ENT_TOTEMS_SITE_1_MID, 14, Operation.NOT_EQUAL), + new VarbitRequirement(VarbitID.ENT_TOTEMS_SITE_1_TOP, 14, Operation.NOT_EQUAL) + ); + + isDoneCarving = new VarbitRequirement(VarbitID.ENT_TOTEMS_SITE_1_BASE_CARVED, 1); + + needToCarveAnimals = new VarbitRequirement(VarbitID.ENT_TOTEMS_CARVE_CHAT, 1); + needToDecorate = new VarbitRequirement(VarbitID.ENT_TOTEMS_DECORATE_CHAT, 1); + + oneShieldAdded = new VarbitRequirement(VarbitID.ENT_TOTEMS_SITE_1_DECORATIONS, 1); + twoShieldsAdded = new VarbitRequirement(VarbitID.ENT_TOTEMS_SITE_1_DECORATIONS, 2); + threeShieldsAdded = new VarbitRequirement(VarbitID.ENT_TOTEMS_SITE_1_DECORATIONS, 3); + isDoneDecorating = new VarbitRequirement(VarbitID.ENT_TOTEMS_SITE_1_DECORATIONS, 4); + } + + public void setupSteps() + { + startQuest = new NpcStep(this, new int[]{NpcID.ENT_TOTEMS_INTRO_RANULPH, NpcID.ENT_TOTEMS_INTRO_RANULPH_1OP, NpcID.ENT_TOTEMS_INTRO_RANULPH_2OP, NpcID.ENT_TOTEMS_INTRO_RANULPH_CS,}, new WorldPoint(1365, 3366, 0), "Talk to Ranulph in the west part of Auburnvale to start the quest."); + startQuest.addDialogStep("Yes."); + + buildTotemBase = new ObjectStep(this, ObjectID.ENT_TOTEMS_BASE_NONE, new WorldPoint(1370, 3375, 0), "Build the totem site.", knife, oneOakLog); + + talkToIsadoraToLearnAboutCarving = new NpcStep(this, NpcID.ENT_TOTEMS_INTRO_CHILD_VIS, new WorldPoint(1366, 3369, 0), "Talk to Isadora to learn about carving."); + talkToIsadoraAfterCarvingTotem = new NpcStep(this, NpcID.ENT_TOTEMS_INTRO_CHILD_VIS, new WorldPoint(1366, 3369, 0), "Return to Isadora after carving the spirit animals into the totem."); + + var carveBuffalo = new ObjectStep(this, ObjectID.ENT_TOTEMS_SITE_1_BASE, new WorldPoint(1370, 3375, 0), "Carve a Buffalo into the totem base."); + carveBuffalo.addWidgetHighlight(WidgetHighlight.createMultiskillByName("Buffalo")); + + var carveJaguar = new ObjectStep(this, ObjectID.ENT_TOTEMS_SITE_1_BASE, new WorldPoint(1370, 3375, 0), "Carve a Jaguar into the totem base."); + carveJaguar.addWidgetHighlight(WidgetHighlight.createMultiskillByName("Jaguar")); + + var carveEagle = new ObjectStep(this, ObjectID.ENT_TOTEMS_SITE_1_BASE, new WorldPoint(1370, 3375, 0), "Carve a Eagle into the totem base."); + carveEagle.addWidgetHighlight(WidgetHighlight.createMultiskillByName("Eagle")); + + var carveSnake = new ObjectStep(this, ObjectID.ENT_TOTEMS_SITE_1_BASE, new WorldPoint(1370, 3375, 0), "Carve a Snake into the totem base."); + carveSnake.addWidgetHighlight(WidgetHighlight.createMultiskillByName("Snake")); + + var carveScorpion = new ObjectStep(this, ObjectID.ENT_TOTEMS_SITE_1_BASE, new WorldPoint(1370, 3375, 0), "Carve a Scorpion into the totem base."); + carveScorpion.addWidgetHighlight(WidgetHighlight.createMultiskillByName("Scorpion")); + + // fallback step in case our detection fails + carveAnimalsYouSee = new ObjectStep(this, ObjectID.ENT_TOTEMS_SITE_1_BASE, new WorldPoint(1370, 3375, 0), "Carve spirit animals you see into the totem."); + carveAnimalsYouSee.addSubSteps(carveBuffalo, carveJaguar, carveEagle, carveSnake, carveScorpion); + + carveTotem = new ConditionalStep(this, carveAnimalsYouSee); + carveTotem.addStep(and(isBuffaloNearby, missingBuffaloCarve), carveBuffalo); + carveTotem.addStep(and(isJaguarNearby, missingJaguarCarve), carveJaguar); + carveTotem.addStep(and(isEagleNearby, missingEagleCarve), carveEagle); + carveTotem.addStep(and(isSnakeNearby, missingSnakeCarve), carveSnake); + carveTotem.addStep(and(isScorpionNearby, missingScorpionCarve), carveScorpion); + + var decorateTotemOneShield = new ObjectStep(this, ObjectID.ENT_TOTEMS_SITE_1_BASE, new WorldPoint(1370, 3375, 0), "Decorate the totem with one decorative item (shield, longbow, or shortbow) of the same wood type you used to build/carve the totem.", oneDecorativeItem); + var decorateTotemTwoShields = new ObjectStep(this, ObjectID.ENT_TOTEMS_SITE_1_BASE, new WorldPoint(1370, 3375, 0), "Decorate the totem with two decorative items (shield, longbow, or shortbow) of the same wood type you used to build/carve the totem.", twoDecorativeItems); + var decorateTotemThreeShields = new ObjectStep(this, ObjectID.ENT_TOTEMS_SITE_1_BASE, new WorldPoint(1370, 3375, 0), "Decorate the totem with three decorative items (shield, longbow, or shortbow) of the same wood type you used to build/carve the totem.", threeDecorativeItems); + decorateTotemFourShields = new ObjectStep(this, ObjectID.ENT_TOTEMS_SITE_1_BASE, new WorldPoint(1370, 3375, 0), "Decorate the totem with four decorative items (shield, longbow, or shortbow) of the same wood type you used to build/carve the totem.", fourDecorativeItems); + decorateTotemFourShields.addSubSteps(decorateTotemOneShield, decorateTotemTwoShields, decorateTotemThreeShields); + talkToIsadoraAfterDecoratingTotem = new NpcStep(this, NpcID.ENT_TOTEMS_INTRO_CHILD_VIS, new WorldPoint(1366, 3369, 0), "Return to Isadora to talk after decorating the totem."); + decorateTotem = new ConditionalStep(this, decorateTotemFourShields); + decorateTotem.addStep(oneShieldAdded, decorateTotemThreeShields); + decorateTotem.addStep(twoShieldsAdded, decorateTotemTwoShields); + decorateTotem.addStep(threeShieldsAdded, decorateTotemOneShield); + + carveAndDecorateTotem = new ConditionalStep(this, startQuest); + carveAndDecorateTotem.addStep(isDoneDecorating, talkToIsadoraAfterDecoratingTotem); + carveAndDecorateTotem.addStep(needToDecorate, decorateTotem); + carveAndDecorateTotem.addStep(isDoneCarving, talkToIsadoraAfterCarvingTotem); + carveAndDecorateTotem.addStep(needToCarveAnimals, carveTotem); + carveAndDecorateTotem.addStep(isTotemBaseBuilt, talkToIsadoraToLearnAboutCarving); + carveAndDecorateTotem.addStep(needToBuildTotem, buildTotemBase); + + claimOffering = new ObjectStep(this, ObjectID.ENT_TOTEMS_OFFERINGS_B, new WorldPoint(1370, 3374, 0), "Claim the offering the Ent left next to your totem."); + finishQuest = new NpcStep(this, NpcID.ENT_TOTEMS_INTRO_CHILD_VIS, new WorldPoint(1366, 3369, 0), "Return to Isadora to finish the quest."); + } + + @Override + public List getItemRequirements() + { + return List.of( + knife, + oneOakLog, + fourDecorativeItems + ); + } + + @Override + public List getGeneralRequirements() + { + return List.of( + new QuestRequirement(QuestHelperQuest.CHILDREN_OF_THE_SUN, QuestState.FINISHED), + new SkillRequirement(Skill.FLETCHING, 20) + ); + } + + @Override + public List getUnlockRewards() + { + return List.of( + new UnlockReward("Unlocks the Auburnvale fletching minigame") + ); + } + + @Override + public List getPanels() + { + var panels = new ArrayList(); + + panels.add(new PanelDetails("Repair the totem", List.of( + startQuest, + buildTotemBase, + talkToIsadoraToLearnAboutCarving, + carveAnimalsYouSee, + talkToIsadoraAfterCarvingTotem, + decorateTotemFourShields, + talkToIsadoraAfterDecoratingTotem, + claimOffering, + finishQuest + ), List.of( + knife, + oneOakLog, + fourDecorativeItems + ))); + + return panels; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/aporcineofinterest/APorcineOfInterest.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/aporcineofinterest/APorcineOfInterest.java index c8734b17ddc..9deed8869ec 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/aporcineofinterest/APorcineOfInterest.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/aporcineofinterest/APorcineOfInterest.java @@ -101,7 +101,7 @@ protected void setupRequirements() rope.setHighlightInInventory(true); slashItem = new ItemRequirement("A knife or slash weapon", ItemID.KNIFE).isNotConsumed(); - slashItem.setTooltip("Except abyssal whip, abyssal tentacle, or dragon claws."); + slashItem.setTooltip("Except abyssal whip, abyssal tentacle, noxious halberd, or dragon claws."); reinforcedGoggles = new ItemRequirement("Reinforced goggles", ItemID.SLAYER_REINFORCED_GOGGLES, 1, true).isNotConsumed(); reinforcedGoggles.setTooltip("You can get another pair from Spria"); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/clocktower/ClockTower.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/clocktower/ClockTower.java index b0afaea4ad9..795c01f1d08 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/clocktower/ClockTower.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/clocktower/ClockTower.java @@ -180,7 +180,7 @@ public void setupConditions() startedQuestDuringSession = new Conditions(true, new VarplayerRequirement(QuestVarPlayer.QUEST_CLOCK_TOWER.getId(), 0)); synced = new Conditions(true, LogicType.OR, - new WidgetTextRequirement(InterfaceID.Questjournal.TITLE, "Clock Tower"), + new WidgetTextRequirement(InterfaceID.QuestjournalOverview.TITLE, "Clock Tower"), startedQuestDuringSession ); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/cooksassistant/CooksAssistant.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/cooksassistant/CooksAssistant.java index 005813cdf04..30c33d4aabf 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/cooksassistant/CooksAssistant.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/cooksassistant/CooksAssistant.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2019, Trevor + * Copyright (c) 2025, pajlada * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -27,112 +28,128 @@ import net.runelite.client.plugins.microbot.questhelper.collections.ItemCollections; import net.runelite.client.plugins.microbot.questhelper.panel.PanelDetails; import net.runelite.client.plugins.microbot.questhelper.questhelpers.BasicQuestHelper; -import net.runelite.client.plugins.microbot.questhelper.requirements.Requirement; -import net.runelite.client.plugins.microbot.questhelper.requirements.conditional.Conditions; import net.runelite.client.plugins.microbot.questhelper.requirements.item.ItemRequirement; +import static net.runelite.client.plugins.microbot.questhelper.requirements.util.LogicHelper.and; +import static net.runelite.client.plugins.microbot.questhelper.requirements.util.LogicHelper.nor; +import static net.runelite.client.plugins.microbot.questhelper.requirements.util.LogicHelper.or; +import net.runelite.client.plugins.microbot.questhelper.requirements.npc.DialogRequirement; import net.runelite.client.plugins.microbot.questhelper.requirements.var.VarbitRequirement; import net.runelite.client.plugins.microbot.questhelper.requirements.zone.Zone; import net.runelite.client.plugins.microbot.questhelper.requirements.zone.ZoneRequirement; import net.runelite.client.plugins.microbot.questhelper.rewards.ExperienceReward; import net.runelite.client.plugins.microbot.questhelper.rewards.QuestPointReward; import net.runelite.client.plugins.microbot.questhelper.rewards.UnlockReward; -import net.runelite.client.plugins.microbot.questhelper.steps.*; +import net.runelite.client.plugins.microbot.questhelper.steps.ConditionalStep; +import net.runelite.client.plugins.microbot.questhelper.steps.ItemStep; +import net.runelite.client.plugins.microbot.questhelper.steps.NpcStep; +import net.runelite.client.plugins.microbot.questhelper.steps.ObjectStep; +import net.runelite.client.plugins.microbot.questhelper.steps.QuestStep; +import net.runelite.client.plugins.microbot.questhelper.steps.widget.WidgetHighlight; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import net.runelite.api.Skill; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.ItemID; import net.runelite.api.gameval.NpcID; import net.runelite.api.gameval.ObjectID; - -import java.util.*; +import net.runelite.api.gameval.VarbitID; public class CooksAssistant extends BasicQuestHelper { - //Items Required - ItemRequirement egg, milk, flour, bucket, pot, coins, grain; + // Required items + ItemRequirement egg; + ItemRequirement milk; + ItemRequirement flour; + ItemRequirement bucket; + ItemRequirement pot; + ItemRequirement coins; + ItemRequirement grain; - Requirement controlsUsed; + // Zones + Zone millSecond; + Zone millThird; - QuestStep getEgg, getWheat, milkCow, climbLadderOne, climbLadderTwoUp, climbLadderTwoDown, climbLadderThree, fillHopper, - operateControls, collectFlour, finishQuest; + // Miscellaneous requirements + DialogRequirement hasTurnedInMilk; + DialogRequirement hasTurnedInFlour; + DialogRequirement hasTurnedInEgg; + DialogRequirement hasTurnedInEverything; - NpcStep getPot, getBucket; + VarbitRequirement controlsUsed; - Zone millSecond, millThird; + ZoneRequirement inMillSecond; + ZoneRequirement inMillThird; - Requirement inMillSecond, inMillThird; + // Steps + NpcStep getBucket; + NpcStep getPot; + ObjectStep milkCow; + ItemStep getEgg; + ObjectStep getWheat; + ObjectStep climbLadderOne; + ObjectStep fillHopper; + ObjectStep operateControls; + ObjectStep climbLadderThree; + ObjectStep collectFlour; + ObjectStep climbLadderTwoUp; + ObjectStep climbLadderTwoDown; + NpcStep finishQuest; @Override - public Map loadSteps() + protected void setupZones() { - initializeRequirements(); - setupConditions(); - setupSteps(); - - Map steps = new HashMap<>(); - ConditionalStep doQuest = new ConditionalStep(this, getBucket); - doQuest.addStep(new Conditions(milk, flour, egg), finishQuest); - doQuest.addStep(new Conditions(milk, pot, egg, controlsUsed, inMillThird), climbLadderThree); - doQuest.addStep(new Conditions(milk, pot, egg, controlsUsed, inMillSecond), climbLadderTwoDown); - doQuest.addStep(new Conditions(milk, pot, egg, controlsUsed), collectFlour); - doQuest.addStep(new Conditions(milk, pot, egg, grain, inMillThird), fillHopper); - doQuest.addStep(new Conditions(milk, pot, egg, inMillThird), operateControls); - doQuest.addStep(new Conditions(milk, pot, egg, grain, inMillSecond), climbLadderTwoUp); - doQuest.addStep(new Conditions(milk, pot, egg, grain), climbLadderOne); - doQuest.addStep(new Conditions(milk, pot, egg), getWheat); - doQuest.addStep(new Conditions(milk, pot), getEgg); - doQuest.addStep(new Conditions(bucket, pot), milkCow); - doQuest.addStep(bucket, getPot); - - steps.put(0, doQuest); - steps.put(1, doQuest); - - return steps; + millSecond = new Zone(new WorldPoint(3162, 3311, 1), new WorldPoint(3171, 3302, 1)); + millThird = new Zone(new WorldPoint(3162, 3311, 2), new WorldPoint(3171, 3302, 2)); } @Override protected void setupRequirements() { + hasTurnedInMilk = new DialogRequirement("Here's a bucket of milk."); + hasTurnedInFlour = new DialogRequirement("Here's a pot of flour."); + hasTurnedInEgg = new DialogRequirement("Here's a fresh egg."); + hasTurnedInEverything = new DialogRequirement("You've brought me everything I need! I am saved!"); + egg = new ItemRequirement("Egg", ItemID.EGG); + egg.setConditionToHide(or(hasTurnedInEgg, hasTurnedInEverything)); egg.canBeObtainedDuringQuest(); milk = new ItemRequirement("Bucket of milk", ItemID.BUCKET_MILK); + milk.setConditionToHide(or(hasTurnedInMilk, hasTurnedInEverything)); milk.canBeObtainedDuringQuest(); flour = new ItemRequirement("Pot of flour", ItemID.POT_FLOUR); + flour.setConditionToHide(or(hasTurnedInFlour, hasTurnedInEverything)); flour.canBeObtainedDuringQuest(); bucket = new ItemRequirement("Bucket", ItemID.BUCKET_EMPTY); pot = new ItemRequirement("Pot", ItemID.POT_EMPTY); - coins = new ItemRequirement("Coins", ItemCollections.COINS); + coins = new ItemRequirement("Coins", ItemCollections.COINS, 3); coins.setTooltip("Necessary if you do not have a pot / bucket"); grain = new ItemRequirement("Grain", ItemID.GRAIN); - controlsUsed = new VarbitRequirement(4920, 1); - } + controlsUsed = new VarbitRequirement(VarbitID.MILL_FLOUR, 1); - @Override - protected void setupZones() - { - millSecond = new Zone(new WorldPoint(3162, 3311, 1), new WorldPoint(3171, 3302, 1)); - millThird = new Zone(new WorldPoint(3162, 3311, 2), new WorldPoint(3171, 3302, 2)); - } - - public void setupConditions() - { inMillSecond = new ZoneRequirement(millSecond); inMillThird = new ZoneRequirement(millThird); } public void setupSteps() { + var lumbridgeShopkeepers = new int[]{ + NpcID.GENERALSHOPKEEPER1, + NpcID.GENERALASSISTANT1, + }; + + getBucket = new NpcStep(this, lumbridgeShopkeepers, new WorldPoint(3212, 3246, 0), + "Purchase a bucket from the Lumbridge General Store.", coins.quantity(2)); + getBucket.addWidgetHighlight(WidgetHighlight.createShopItemHighlight(ItemID.BUCKET_EMPTY)); + + getPot = new NpcStep(this, lumbridgeShopkeepers, new WorldPoint(3212, 3246, 0), + "Purchase a pot from the Lumbridge General Store.", coins.quantity(1)); + getPot.addWidgetHighlight(WidgetHighlight.createShopItemHighlight(ItemID.POT_EMPTY)); + getEgg = new ItemStep(this, new WorldPoint(3177, 3296, 0), "Grab an egg from the farm north of Lumbridge.", egg); - getBucket = new NpcStep(this, NpcID.GENERALSHOPKEEPER1, new WorldPoint(3212, 3246, 0), - "Purchase a bucket from the Lumbridge General Store.", coins.quantity(3)); - getBucket.addWidgetHighlightWithItemIdRequirement(300, 16, ItemID.BUCKET_EMPTY, true); - getBucket.addAlternateNpcs(NpcID.GENERALASSISTANT1); - getPot = new NpcStep(this, NpcID.GENERALSHOPKEEPER1, new WorldPoint(3212, 3246, 0), - "Purchase a pot from the Lumbridge General Store.", coins.quantity(3)); - getPot.addAlternateNpcs(NpcID.GENERALASSISTANT1); - milkCow = new ObjectStep(this, ObjectID.FAT_COW, new WorldPoint(3254, 3272, 0), - "Milk the cow north-east of Lumbridge.", bucket); getWheat = new ObjectStep(this, ObjectID.FAI_VARROCK_WHEAT_CORNER, new WorldPoint(3161, 3292, 0), "Pick some wheat north of Lumbridge."); climbLadderOne = new ObjectStep(this, ObjectID.QIP_COOK_LADDER, new WorldPoint(3164, 3307, 0), @@ -155,26 +172,67 @@ public void setupSteps() collectFlour = new ObjectStep(this, ObjectID.MILLBASE_FLOUR, new WorldPoint(3166, 3306, 0), "Collect the flour in the bin.", pot.highlighted()); collectFlour.addIcon(ItemID.POT_EMPTY); - finishQuest = new NpcStep(this, NpcID.POH_SERVANT_COOK_WOMAN, new WorldPoint(3206, 3214, 0), + + milkCow = new ObjectStep(this, ObjectID.FAT_COW, new WorldPoint(3172, 3317, 0), + "Milk the dairy cow north of Lumbridge.", bucket); + + finishQuest = new NpcStep(this, NpcID.COOK, new WorldPoint(3206, 3214, 0), "Give the Cook in Lumbridge Castle's kitchen the required items to finish the quest.", egg, milk, flour); + finishQuest.addAlternateNpcs(NpcID.POH_SERVANT_COOK_WOMAN); finishQuest.addDialogSteps("What's wrong?", "Can I help?", "Yes."); } + @Override + public Map loadSteps() + { + initializeRequirements(); + setupSteps(); + + var steps = new HashMap(); + + + var getFlour = new ConditionalStep(this, getPot); + getFlour.addStep(and(pot, controlsUsed, inMillThird), climbLadderThree); + getFlour.addStep(and(pot, controlsUsed, inMillSecond), climbLadderTwoDown); + getFlour.addStep(and(pot, controlsUsed), collectFlour); + getFlour.addStep(and(pot, grain, inMillThird), fillHopper); + getFlour.addStep(and(pot, inMillThird), operateControls); + getFlour.addStep(and(pot, grain, inMillSecond), climbLadderTwoUp); + getFlour.addStep(and(pot, grain), climbLadderOne); + getFlour.addStep(and(pot), getWheat); + + var doQuest = new ConditionalStep(this, finishQuest); + doQuest.addStep(hasTurnedInEverything, finishQuest); + doQuest.addStep(nor(milk, bucket, hasTurnedInMilk), getBucket); + doQuest.addStep(nor(flour, pot, hasTurnedInFlour), getPot); + doQuest.addStep(nor(egg, hasTurnedInEgg), getEgg); + doQuest.addStep(nor(flour, hasTurnedInFlour), getFlour); + doQuest.addStep(nor(milk, hasTurnedInMilk), milkCow); + + steps.put(0, doQuest); + steps.put(1, doQuest); + + return steps; + } + + @Override public List getItemRequirements() { - ArrayList reqs = new ArrayList<>(); - reqs.add(egg); - reqs.add(flour); - reqs.add(milk); - return reqs; + return List.of( + egg, + flour, + milk + ); } @Override public List getItemRecommended() { - return Collections.singletonList(coins); + return List.of( + coins + ); } @Override @@ -186,26 +244,60 @@ public QuestPointReward getQuestPointReward() @Override public List getExperienceRewards() { - return Collections.singletonList(new ExperienceReward(Skill.COOKING, 300)); + return List.of( + new ExperienceReward(Skill.COOKING, 300) + ); } @Override public List getUnlockRewards() { - return Collections.singletonList(new UnlockReward("Permission to use The Cook's range.")); + return List.of( + new UnlockReward("Permission to use The Cook's range.") + ); } @Override public List getPanels() { - List allSteps = new ArrayList<>(); - allSteps.add(new PanelDetails("Starting off", Arrays.asList(getBucket, getPot), coins.quantity(3))); - allSteps.add(new PanelDetails("Getting the Milk", Collections.singletonList(milkCow), bucket)); - allSteps.add(new PanelDetails("Getting the Egg", Collections.singletonList(getEgg))); - allSteps.add(new PanelDetails("Getting the Flour", Arrays.asList(getWheat, climbLadderOne, fillHopper, - operateControls, climbLadderThree, collectFlour), pot)); - allSteps.add(new PanelDetails("Finishing up", Collections.singletonList(finishQuest), egg, flour, milk)); - - return allSteps; + var steps = new ArrayList(); + + steps.add(new PanelDetails("Starting off", List.of( + getBucket, + getPot + ), List.of( + coins + ))); + + steps.add(new PanelDetails("Getting the Egg", List.of( + getEgg + ))); + + steps.add(new PanelDetails("Getting the Flour", List.of( + getWheat, + climbLadderOne, + fillHopper, + operateControls, + climbLadderThree, + collectFlour + ), List.of( + pot + ))); + + steps.add(new PanelDetails("Getting the Milk", List.of( + milkCow + ), List.of( + bucket + ))); + + steps.add(new PanelDetails("Finishing up", List.of( + finishQuest + ), List.of( + egg, + flour, + milk + ))); + + return steps; } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/creatureoffenkenstrain/CreatureOfFenkenstrain.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/creatureoffenkenstrain/CreatureOfFenkenstrain.java index 7bdd85c071d..73cd4840736 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/creatureoffenkenstrain/CreatureOfFenkenstrain.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/creatureoffenkenstrain/CreatureOfFenkenstrain.java @@ -271,7 +271,7 @@ public void setupSteps() "Go back to the ground floor."); talkToGardenerForHead = new NpcStep(this, NpcID.FENK_GARDENER, new WorldPoint(3548, 3562, 0), - "Talk to the Gardener Ghost.", ghostSpeakAmulet.equipped()); + "Talk to the Gardener Ghost while wearing your Ghostspeak amulet.", ghostSpeakAmulet.equipped()); talkToGardenerForHead.addDialogStep("What happened to your head?"); goToHeadGrave = new DigStep(this, new WorldPoint(3608, 3490, 0), diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/deserttreasureii/VardorvisSteps.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/deserttreasureii/VardorvisSteps.java index 3a217ec3e51..6247bdafbac 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/deserttreasureii/VardorvisSteps.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/deserttreasureii/VardorvisSteps.java @@ -67,7 +67,7 @@ public class VardorvisSteps extends ConditionalStep inAnyStranglewood, inVardorvisArea, unlockedShortcut, defeatedVardorvis, templeKeyNearby, kasondeAggressive, givenKasondeKey, defeatedKasonde, kasondeRevealedMedallion, gotVardorvisMedallion, inVault; ItemRequirement potionNote, strangePotion, freezes, berry, herb, unfinishedSerum, serumWithHerb, stranglerSerum, templeKey, - vardorvisMedallion; + vardorvisMedallion, food; Zone stranglewood, towerDefenseRoom, stranglewoodPyramidRoom, vardorvisArea, vault; QuestBank questBank; @@ -139,6 +139,7 @@ protected void setupItemRequirements() potionNote = new ItemRequirement("Potion note", ItemID.DT2_KASONDE_NOTE); strangePotion = new ItemRequirement("Strange potion", ItemID.DT2_KASONDE_POTION); + food = new ItemRequirement("Bring high healing food to tank the infected.", -1); freezes = new ItemRequirement("Freezing spells STRONGLY recommended + reasonable mage accuracy", -1); berry = new ItemRequirement("Argian berries", ItemID.DT2_STRANGLEWOOD_BERRIES); berry.setTooltip("You can get another from the south-west corner of The Stranglewood"); @@ -249,6 +250,7 @@ protected void setupSteps() defendKasonde = new DetailedQuestStep(getQuestHelper(), "Defend Kasonde! Read the sidebar for more details."); defendKasonde.addRecommended(freezes); + defendKasonde.addRecommended(food); defendKasondeSidebar.addSubSteps(defendKasonde); // TODO: Get actual coordinate and ladder ID! diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/deviousminds/DeviousMinds.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/deviousminds/DeviousMinds.java index 7100b81635a..6e675842bfa 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/deviousminds/DeviousMinds.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/deviousminds/DeviousMinds.java @@ -112,9 +112,9 @@ public Map loadSteps() protected void setupRequirements() { //Recommended - fallyTele = new ItemRequirement("Falador Teleports", ItemID.POH_TABLET_FALADORTELEPORT); - lumberTele = new ItemRequirement("Lumberyard Teleports", ItemID.TELEPORTSCROLL_LUMBERYARD); - glory = new ItemRequirement("Amulet of Glory", ItemCollections.AMULET_OF_GLORIES); + fallyTele = new ItemRequirement("Falador Teleports", ItemID.POH_TABLET_FALADORTELEPORT, 2); + lumberTele = new ItemRequirement("Lumberyard Teleports", ItemID.TELEPORTSCROLL_LUMBERYARD, 3); + glory = new ItemRequirement("Amulet of Glory (Teleports to Port Sarim and Edgeville)", ItemCollections.AMULET_OF_GLORIES); //Required mith2h = new ItemRequirement("Mithril 2h Sword", ItemID.MITHRIL_2H_SWORD); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/elementalworkshopi/ElementalWorkshopI.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/elementalworkshopi/ElementalWorkshopI.java index 2c4a0142ddb..755bc53bafe 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/elementalworkshopi/ElementalWorkshopI.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/elementalworkshopi/ElementalWorkshopI.java @@ -137,8 +137,9 @@ protected void setupRequirements() knife.setHighlightInInventory(true); pickaxe = new ItemRequirement("Any pickaxe", ItemCollections.PICKAXES).isNotConsumed(); needle = new ItemRequirement("Needle", ItemID.NEEDLE).isNotConsumed(); - needle.setTooltip("You can obtain this during the quest"); + needle.setTooltip("Costume needle cannot be used as a substitute. You can obtain this during the quest"); thread = new ItemRequirement("Thread", ItemID.THREAD); + thread.setTooltip("Costume needle cannot be used as a substitute."); leather = new ItemRequirement("Leather", ItemID.LEATHER); leather.setTooltip("You can obtain this during the quest"); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/enakhraslament/EnakhrasLament.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/enakhraslament/EnakhrasLament.java index 2ad7f0d5977..e637dc28686 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/enakhraslament/EnakhrasLament.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/enakhraslament/EnakhrasLament.java @@ -55,123 +55,163 @@ public class EnakhrasLament extends BasicQuestHelper { - //Items Required - ItemRequirement pickaxe, chiselHighlighted, sandstone32, sandstone20, base, body, head, granite2, granite, leftArm, rightArm, leftLeg, - rightLeg, kSigil, rSigil, mSigil, zSigil, softClay, camelMould, camelHead, breadOrCake, fireSpellRunes, airSpellRunes, - mapleLog, log, oakLog, willowLog, coal, candle, air2, chaos, earth2, sandstone5, tinderbox, crumbleUndeadRunes, sandstone52, - airStaff, airRuneOrStaff, earthRuneOrStaff, earthStaff; - + // Required items + ItemRequirement pickaxe; + ItemRequirement chiselHighlighted; + ItemRequirement sandstone32; + ItemRequirement sandstone20; + ItemRequirement granite2; + ItemRequirement granite; + ItemRequirement softClay; + ItemRequirement breadOrCake; + ItemRequirement fireSpellRunes; + ItemRequirement airSpellRunes; + ItemRequirement mapleLog; + ItemRequirement log; + ItemRequirement oakLog; + ItemRequirement willowLog; + ItemRequirement coal; + ItemRequirement candle; + ItemRequirement air2; + ItemRequirement chaos; + ItemRequirement earth2; + ItemRequirement sandstone5; + ItemRequirement tinderbox; + ItemRequirement crumbleUndeadRunes; + ItemRequirement sandstone52; + ItemRequirement airStaff; + ItemRequirement airRuneOrStaff; + ItemRequirement earthRuneOrStaff; + ItemRequirement earthStaff; + + // Mid-quest requirements + ItemRequirement base; + ItemRequirement body; + ItemRequirement head; + ItemRequirement leftArm; + ItemRequirement rightArm; + ItemRequirement leftLeg; + ItemRequirement rightLeg; + ItemRequirement kSigil; + ItemRequirement rSigil; + ItemRequirement mSigil; + ItemRequirement zSigil; + ItemRequirement camelMould; + ItemRequirement camelHead; + + // Miscellaneous requirements SpellbookRequirement onNormals; - - Requirement hasPlacedBase, hasTalkedToLazimAfterBase, hasPlacedBody, chiseledStatue, canChooseHead, inTempleEntranceRoom, - inTempleGroundFloor, startedTemple, gottenLimbs, openedDoor1, openedDoor2, openedDoor3, openedDoor4, mPlaced, kPlaced, - rPlaced, zPlaced, goneUpstairs, hasGottenRightArm, hasGottenRightLeg, inCentreRoom, inPuzzleFloor, - fedBread, meltedFountain, cleanedFurnace, litBraziers, litLog, litOak, litWillow, litMaple, litCandle, litCoal, inNorthPuzzleRoom, - inTopRoom, inLastRoom, wallNeedsChisel, finishedWall, protectFromMelee; - - DetailedQuestStep talkToLazim, bringLazim32Sandstone, useChiselOn32Sandstone, placeBase, bringLazim20Sandstone, - useChiselOn20Sandstone, placeBody, talkToLazimToChooseHead, getGranite, craftHead, talkToLazimAboutBody, - chiselStatue, giveLazimHead, talkToLazimInTemple, enterTemple, enterTempleDownLadder, cutOffLimb, takeM, - talkToLazimForHead, enterDoor1, enterDoor2, enterDoor3, enterDoor4, enterKDoor, enterRDoor, enterMDoor, enterZDoor, - takeZ, takeK, takeR, useStoneHeadOnPedestal, useSoftClayOnPedestal, useChiselOnGranite, goUpToPuzzles, useBread, castAirSpell, - castFireSpell, useMapleLog, useOakLog, useLog, useWillowLog, useCoal, useCandle, passBarrier, goUpFromPuzzleRoom, castCrumbleUndead, - goDownToFinalRoom, protectThenTalk, repairWall, useChiselOnWall, talkToAkthankos; - - //Zones - Zone templeEntranceRoom, templeGroundFloor, centreRoom, puzzleFloor, northPuzzleRoom, topRoom, lastRoom; + VarbitRequirement hasPlacedBase; + VarbitRequirement hasTalkedToLazimAfterBase; + VarbitRequirement hasPlacedBody; + VarbitRequirement chiseledStatue; + VarbitRequirement canChooseHead; + ZoneRequirement inTempleEntranceRoom; + ZoneRequirement inTempleGroundFloor; + VarbitRequirement startedTemple; + VarbitRequirement gottenLimbs; + VarbitRequirement openedDoor1; + VarbitRequirement openedDoor2; + VarbitRequirement openedDoor3; + VarbitRequirement openedDoor4; + VarbitRequirement mPlaced; + VarbitRequirement kPlaced; + VarbitRequirement rPlaced; + VarbitRequirement zPlaced; + VarbitRequirement goneUpstairs; + VarbitRequirement hasGottenRightArm; + VarbitRequirement hasGottenRightLeg; + ZoneRequirement inCentreRoom; + ZoneRequirement inPuzzleFloor; + VarbitRequirement fedBread; + VarbitRequirement meltedFountain; + VarbitRequirement cleanedFurnace; + VarbitRequirement litBraziers; + VarbitRequirement litLog; + VarbitRequirement litOak; + VarbitRequirement litWillow; + VarbitRequirement litMaple; + VarbitRequirement litCandle; + VarbitRequirement litCoal; + ZoneRequirement inNorthPuzzleRoom; + ZoneRequirement inTopRoom; + ZoneRequirement inLastRoom; + VarbitRequirement wallNeedsChisel; + VarbitRequirement finishedWall; + PrayerRequirement protectFromMelee; + + // Steps + NpcStep talkToLazim; + NpcStep bringLazim32Sandstone; + DetailedQuestStep useChiselOn32Sandstone; + ObjectStep placeBase; + NpcStep bringLazim20Sandstone; + DetailedQuestStep useChiselOn20Sandstone; + ObjectStep placeBody; + NpcStep talkToLazimToChooseHead; + NpcStep getGranite; + DetailedQuestStep craftHead; + NpcStep talkToLazimAboutBody; + DetailedQuestStep chiselStatue; + NpcStep giveLazimHead; + NpcStep talkToLazimInTemple; + ObjectStep enterTemple; + ObjectStep enterTempleDownLadder; + ObjectStep cutOffLimb; + ObjectStep takeM; + NpcStep talkToLazimForHead; + ObjectStep enterDoor1; + ObjectStep enterDoor2; + ObjectStep enterDoor3; + ObjectStep enterDoor4; + ObjectStep enterKDoor; + ObjectStep enterRDoor; + ObjectStep enterMDoor; + ObjectStep enterZDoor; + ObjectStep takeZ; + ObjectStep takeK; + ObjectStep takeR; + ObjectStep useStoneHeadOnPedestal; + ObjectStep useSoftClayOnPedestal; + DetailedQuestStep useChiselOnGranite; + ObjectStep goUpToPuzzles; + NpcStep useBread; + NpcStep castAirSpell; + NpcStep castFireSpell; + ObjectStep useMapleLog; + ObjectStep useOakLog; + ObjectStep useLog; + ObjectStep useWillowLog; + ObjectStep useCoal; + ObjectStep useCandle; + ObjectStep passBarrier; + ObjectStep goUpFromPuzzleRoom; + NpcStep castCrumbleUndead; + ObjectStep goDownToFinalRoom; + NpcStep protectThenTalk; + ObjectStep repairWall; + ObjectStep useChiselOnWall; + NpcStep talkToAkthankos; + + // Zones + Zone templeEntranceRoom; + Zone templeGroundFloor; + Zone centreRoom; + Zone puzzleFloor; + Zone northPuzzleRoom; + Zone topRoom; + Zone lastRoom; @Override - public Map loadSteps() + protected void setupZones() { - initializeRequirements(); - setupConditions(); - setupSteps(); - Map steps = new HashMap<>(); - - steps.put(0, talkToLazim); - - ConditionalStep makeAndPlaceBase = new ConditionalStep(this, bringLazim32Sandstone); - makeAndPlaceBase.addStep(new Conditions(head, granite), giveLazimHead); - makeAndPlaceBase.addStep(new Conditions(granite2, canChooseHead), craftHead); - makeAndPlaceBase.addStep(canChooseHead, getGranite); - makeAndPlaceBase.addStep(chiseledStatue, talkToLazimToChooseHead); - makeAndPlaceBase.addStep(hasPlacedBody, chiselStatue); - makeAndPlaceBase.addStep(body, placeBody); - makeAndPlaceBase.addStep(sandstone20, useChiselOn20Sandstone); - makeAndPlaceBase.addStep(hasTalkedToLazimAfterBase, bringLazim20Sandstone); - makeAndPlaceBase.addStep(hasPlacedBase, talkToLazimAboutBody); - makeAndPlaceBase.addStep(base, placeBase); - makeAndPlaceBase.addStep(sandstone32, useChiselOn32Sandstone); - - steps.put(10, makeAndPlaceBase); - - ConditionalStep exploreBottomLayer = new ConditionalStep(this, enterTemple); - exploreBottomLayer.addStep(new Conditions(camelHead, inPuzzleFloor), useStoneHeadOnPedestal); - exploreBottomLayer.addStep(camelMould, useChiselOnGranite); - exploreBottomLayer.addStep(inPuzzleFloor, useSoftClayOnPedestal); - exploreBottomLayer.addStep(new Conditions(gottenLimbs, inTempleGroundFloor, openedDoor1, openedDoor2, openedDoor3, openedDoor4), goUpToPuzzles); - exploreBottomLayer.addStep(new Conditions(gottenLimbs, inTempleGroundFloor, openedDoor1, openedDoor2, openedDoor3, rSigil), enterDoor4); - exploreBottomLayer.addStep(new Conditions(gottenLimbs, inTempleGroundFloor, openedDoor1, openedDoor2, openedDoor3), takeR); - exploreBottomLayer.addStep(new Conditions(gottenLimbs, inTempleGroundFloor, openedDoor1, openedDoor2, kSigil), enterDoor3); - exploreBottomLayer.addStep(new Conditions(gottenLimbs, inTempleGroundFloor, openedDoor1, openedDoor2), takeK); - // It's possible to skip the rest of this, but it skips some of the quest story and leaves doors locked after you finish, so this encourages players to explore - exploreBottomLayer.addStep(new Conditions(gottenLimbs, inTempleGroundFloor, openedDoor1, zSigil), enterDoor2); - exploreBottomLayer.addStep(new Conditions(gottenLimbs, inTempleGroundFloor, openedDoor1), takeZ); - exploreBottomLayer.addStep(new Conditions(gottenLimbs, inTempleGroundFloor, mSigil), enterDoor1); - exploreBottomLayer.addStep(new Conditions(gottenLimbs, inTempleGroundFloor), takeM); - exploreBottomLayer.addStep(new Conditions(startedTemple, inTempleGroundFloor), cutOffLimb); - exploreBottomLayer.addStep(inTempleGroundFloor, talkToLazimInTemple); - exploreBottomLayer.addStep(inTempleEntranceRoom, enterTempleDownLadder); - - steps.put(20, exploreBottomLayer); - - ConditionalStep puzzles = new ConditionalStep(this, enterTemple); - puzzles.addStep(new Conditions(fedBread, inPuzzleFloor, meltedFountain, cleanedFurnace, litLog, litOak, litWillow, litMaple, litCandle), useCoal); - puzzles.addStep(new Conditions(fedBread, inPuzzleFloor, meltedFountain, cleanedFurnace, litLog, litOak, litWillow, litMaple), useCandle); - puzzles.addStep(new Conditions(fedBread, inPuzzleFloor, meltedFountain, cleanedFurnace, litLog, litOak, litWillow), useMapleLog); - puzzles.addStep(new Conditions(fedBread, inPuzzleFloor, meltedFountain, cleanedFurnace, litLog, litOak), useWillowLog); - puzzles.addStep(new Conditions(fedBread, inPuzzleFloor, meltedFountain, cleanedFurnace, litLog), useOakLog); - puzzles.addStep(new Conditions(fedBread, inPuzzleFloor, meltedFountain, cleanedFurnace), useLog); - puzzles.addStep(new Conditions(fedBread, inPuzzleFloor, meltedFountain), castAirSpell); - puzzles.addStep(new Conditions(fedBread, inPuzzleFloor), castFireSpell); - puzzles.addStep(inPuzzleFloor, useBread); - puzzles.addStep(inTempleGroundFloor, goUpToPuzzles); - puzzles.addStep(inTempleEntranceRoom, enterTempleDownLadder); - - steps.put(30, puzzles); - - ConditionalStep topFloorPuzzle = new ConditionalStep(this, enterTemple); - topFloorPuzzle.addStep(inTopRoom, castCrumbleUndead); - topFloorPuzzle.addStep(inNorthPuzzleRoom, goUpFromPuzzleRoom); - topFloorPuzzle.addStep(inPuzzleFloor, passBarrier); - topFloorPuzzle.addStep(inTempleGroundFloor, goUpToPuzzles); - topFloorPuzzle.addStep(inTempleEntranceRoom, enterTempleDownLadder); - - steps.put(40, topFloorPuzzle); - - ConditionalStep protectMeleePuzzle = new ConditionalStep(this, enterTemple); - protectMeleePuzzle.addStep(inLastRoom, protectThenTalk); - protectMeleePuzzle.addStep(inTopRoom, goDownToFinalRoom); - protectMeleePuzzle.addStep(inNorthPuzzleRoom, goUpFromPuzzleRoom); - protectMeleePuzzle.addStep(inPuzzleFloor, passBarrier); - protectMeleePuzzle.addStep(inTempleGroundFloor, goUpToPuzzles); - protectMeleePuzzle.addStep(inTempleEntranceRoom, enterTempleDownLadder); - - steps.put(50, protectMeleePuzzle); - - ConditionalStep repairWallForAkthankos = new ConditionalStep(this, enterTemple); - repairWallForAkthankos.addStep(new Conditions(inLastRoom, wallNeedsChisel), useChiselOnWall); - repairWallForAkthankos.addStep(new Conditions(inLastRoom, finishedWall), talkToAkthankos); - repairWallForAkthankos.addStep(inLastRoom, repairWall); - repairWallForAkthankos.addStep(inTopRoom, goDownToFinalRoom); - repairWallForAkthankos.addStep(inNorthPuzzleRoom, goUpFromPuzzleRoom); - repairWallForAkthankos.addStep(inPuzzleFloor, passBarrier); - repairWallForAkthankos.addStep(inTempleGroundFloor, goUpToPuzzles); - repairWallForAkthankos.addStep(inTempleEntranceRoom, enterTempleDownLadder); - - steps.put(60, repairWallForAkthankos); - - return steps; + templeEntranceRoom = new Zone(new WorldPoint(3124, 9328, 1), new WorldPoint(3128, 9330, 1)); + templeGroundFloor = new Zone(new WorldPoint(3074, 9282, 0), new WorldPoint(3133, 9341, 0)); + centreRoom = new Zone(new WorldPoint(3098, 9306, 0), new WorldPoint(3110, 9318, 0)); + puzzleFloor = new Zone(new WorldPoint(3086, 9305, 1), new WorldPoint(3121, 9326, 1)); + northPuzzleRoom = new Zone(new WorldPoint(2095, 9319, 1), new WorldPoint(3112, 9335, 1)); + topRoom = new Zone(new WorldPoint(3097, 9299, 2), new WorldPoint(3113, 9334, 2)); + lastRoom = new Zone(new WorldPoint(3096, 9291, 1), new WorldPoint(3112, 9302, 1)); } @Override @@ -260,22 +300,7 @@ protected void setupRequirements() tinderbox = new ItemRequirement("Tinderbox", ItemID.TINDERBOX).isNotConsumed(); onNormals = new SpellbookRequirement(Spellbook.NORMAL); - } - @Override - protected void setupZones() - { - templeEntranceRoom = new Zone(new WorldPoint(3124, 9328, 1), new WorldPoint(3128, 9330, 1)); - templeGroundFloor = new Zone(new WorldPoint(3074, 9282, 0), new WorldPoint(3133, 9341, 0)); - centreRoom = new Zone(new WorldPoint(3098, 9306, 0), new WorldPoint(3110, 9318, 0)); - puzzleFloor = new Zone(new WorldPoint(3086, 9305, 1), new WorldPoint(3121, 9326, 1)); - northPuzzleRoom = new Zone(new WorldPoint(2095, 9319, 1), new WorldPoint(3112, 9335, 1)); - topRoom = new Zone(new WorldPoint(3097, 9299, 2), new WorldPoint(3113, 9334, 2)); - lastRoom = new Zone(new WorldPoint(3096, 9291, 1), new WorldPoint(3112, 9302, 1)); - } - - public void setupConditions() - { hasPlacedBase = new VarbitRequirement(VarbitID.ENAKH_STATUE_MULTIVAR, 1); hasPlacedBody = new VarbitRequirement(VarbitID.ENAKH_STATUE_MULTIVAR, 2); chiseledStatue = new VarbitRequirement(VarbitID.ENAKH_STATUE_MULTIVAR, 3); @@ -327,11 +352,15 @@ public void setupConditions() protectFromMelee = new PrayerRequirement("Protect from Melee", Prayer.PROTECT_FROM_MELEE); } + public void setupSteps() { - talkToLazim = new NpcStep(this, NpcID.ENAKH_LAZIM, new WorldPoint(3190, 2925, 0), "Before you begin, ensure that you have enough prayer points to use Protect from Melee for around five seconds (you will need this later in the temple). Talk to Lazim in the quarry south of the Bandit Camp.", pickaxe, onNormals); + talkToLazim = new NpcStep(this, NpcID.ENAKH_LAZIM, new WorldPoint(3190, 2925, 0), "Before you begin, ensure that you have enough prayer points to use" + + " Protect from Melee for around five seconds (you will need this later in the temple). Talk to Lazim in the quarry south of the Bandit Camp.", + pickaxe, onNormals); talkToLazim.addDialogSteps("Yes.", "Of course!"); - bringLazim32Sandstone = new NpcStep(this, NpcID.ENAKH_LAZIM, new WorldPoint(3190, 2925, 0), "Get 32kg of sandstone and give it to Lazim. This can be done in batches, and you can mine some nearby."); + bringLazim32Sandstone = new NpcStep(this, NpcID.ENAKH_LAZIM, new WorldPoint(3190, 2925, 0), "Get 32kg of sandstone and give it to Lazim. This can be " + + "done in batches, and you can mine some nearby."); bringLazim32Sandstone.addDialogStep("Okay, I'll get on with it."); bringLazim32Sandstone.addDialogStep("Yes, I have more stone."); bringLazim32Sandstone.addDialogStep("Here's a large 10 kg block."); @@ -343,7 +372,8 @@ public void setupSteps() talkToLazimAboutBody = new NpcStep(this, NpcID.ENAKH_LAZIM, new WorldPoint(3190, 2925, 0), "Talk to Lazim again."); talkToLazimAboutBody.addDialogStep("I'll do it right away!"); - bringLazim20Sandstone = new NpcStep(this, NpcID.ENAKH_LAZIM, new WorldPoint(3190, 2925, 0), "Get 20kg of sandstone and give it to Lazim. This can be done in batches, and you can mine some nearby."); + bringLazim20Sandstone = new NpcStep(this, NpcID.ENAKH_LAZIM, new WorldPoint(3190, 2925, 0), "Get 20kg of sandstone and give it to Lazim. This can be " + + "done in batches, and you can mine some nearby."); bringLazim20Sandstone.addDialogStep("I'll do it right away!"); bringLazim20Sandstone.addDialogStep("Yes, I have more stone."); bringLazim20Sandstone.addDialogStep("Here's a large 10 kg block."); @@ -353,23 +383,31 @@ public void setupSteps() useChiselOn20Sandstone = new DetailedQuestStep(this, "Use a chisel on the sandstone 20kg.", chiselHighlighted, sandstone20); placeBody = new ObjectStep(this, ObjectID.ENAKH_STATUE_EAST_MULTILOC, new WorldPoint(3190, 2926, 0), "Place the body on the sandstone base.", body); - talkToLazimToChooseHead = new NpcStep(this, NpcID.ENAKH_LAZIM, new WorldPoint(3190, 2925, 0), "Talk to Lazim and choose the head you'd like the statue to have."); + talkToLazimToChooseHead = new NpcStep(this, NpcID.ENAKH_LAZIM, new WorldPoint(3190, 2925, 0), "Talk to Lazim and choose the head you'd like the " + + "statue to have."); getGranite = new NpcStep(this, NpcID.ENAKH_LAZIM, new WorldPoint(3190, 2925, 0), "Get 2 x granite (5kg). You can mine some nearby.", granite2); // TODO: Change head highlight text based on choice - craftHead = new DetailedQuestStep(this, "Use a chisel on a piece of granite 5kg, and choose the head you decided on to craft.", chiselHighlighted, granite); + craftHead = new DetailedQuestStep(this, "Use a chisel on a piece of granite 5kg, and choose the head you decided on to craft.", chiselHighlighted, + granite); - chiselStatue = new ObjectStep(this, ObjectID.ENAKH_STATUE_EAST_MULTILOC, new WorldPoint(3190, 2926, 0), "Use a chisel on the headless statue.", chiselHighlighted); + chiselStatue = new ObjectStep(this, ObjectID.ENAKH_STATUE_EAST_MULTILOC, new WorldPoint(3190, 2926, 0), "Use a chisel on the headless statue.", + chiselHighlighted); chiselStatue.addIcon(ItemID.CHISEL); giveLazimHead = new NpcStep(this, NpcID.ENAKH_LAZIM, new WorldPoint(3190, 2925, 0), "Give Lazim the head.", head); - enterTemple = new ObjectStep(this, ObjectID.ENAKH_SECRET_BOULDER_MULTILOC_E, new WorldPoint(3194, 2925, 0), "Enter the temple south of the Bandit's Camp."); - enterTempleDownLadder = new ObjectStep(this, ObjectID.ENAKH_TEMPLE_LADDERDOWN, new WorldPoint(3127, 9329, 1), "Enter the temple south of the Bandit's Camp."); + enterTemple = new ObjectStep(this, ObjectID.ENAKH_SECRET_BOULDER_MULTILOC_E, new WorldPoint(3194, 2925, 0), "Enter the temple south of the Bandit's " + + "Camp."); + enterTempleDownLadder = new ObjectStep(this, ObjectID.ENAKH_TEMPLE_LADDERDOWN, new WorldPoint(3127, 9329, 1), "Enter the temple south of the Bandit's" + + " Camp."); talkToLazimInTemple = new NpcStep(this, NpcID.ENAKH_LAZIM, new WorldPoint(3127, 9324, 0), "Talk to Lazim in the temple."); - cutOffLimb = new ObjectStep(this, ObjectID.ENAKH_FALLEN_STATUE_EAST_MULTILOC, new WorldPoint(3130, 9326, 0), "Use a chisel on the fallen statue to get all its limbs.", chiselHighlighted); - cutOffLimb.addDialogSteps("Remove the statue's left arm", "Remove the statue's right arm", "Remove the statue's left leg", "Remove the statue's right leg"); + cutOffLimb = new ObjectStep(this, ObjectID.ENAKH_FALLEN_STATUE_EAST_MULTILOC, new WorldPoint(3130, 9326, 0), "Use a chisel on the fallen statue to " + + "get all its limbs.", chiselHighlighted); + cutOffLimb.addDialogSteps("Remove the statue's left arm", "Remove the statue's right arm", "Remove the statue's left leg", "Remove the statue's right" + + " leg"); + cutOffLimb.addIcon(ItemID.CHISEL); takeM = new ObjectStep(this, ObjectID.ENAKH_PEDESTAL_SIGIL_M, new WorldPoint(3128, 9319, 0), "Take the M sigil from the pedestal in the room."); takeZ = new ObjectStep(this, ObjectID.ENAKH_PEDESTAL_SIGIL_Z, new WorldPoint(3097, 9336, 0), "Take the Z sigil from the pedestal in the north room."); @@ -392,101 +430,199 @@ public void setupSteps() enterMDoor = new ObjectStep(this, ObjectID.ENAKH_DOOR_M_SIGIL, new WorldPoint(3097, 9312, 0), "Enter the door with an M.", mSigil); enterZDoor = new ObjectStep(this, ObjectID.ENAKH_DOOR_Z_SIGIL, new WorldPoint(3104, 9305, 0), "Enter the door with a Z.", zSigil); - goUpToPuzzles = new ObjectStep(this, ObjectID.ENAKH_TEMPLE_LADDERUP, new WorldPoint(3104, 9309, 0), "Open the central room's doors using the metal letters. Go up the ladder in the central room."); + goUpToPuzzles = new ObjectStep(this, ObjectID.ENAKH_TEMPLE_LADDERUP, new WorldPoint(3104, 9309, 0), "Open the central room's doors using the metal " + + "letters. Go up the ladder in the central room."); useSoftClayOnPedestal = new ObjectStep(this, ObjectID.ENAKH_PEDESTAL_MULTILOC, new WorldPoint(3104, 9312, 1), "Use soft clay on the pedestal.", softClay.highlighted()); useChiselOnGranite = new DetailedQuestStep(this, "Use a chisel on granite (5kg).", granite, chiselHighlighted); - useStoneHeadOnPedestal = new ObjectStep(this, ObjectID.ENAKH_PEDESTAL_MULTILOC, new WorldPoint(3104, 9312, 1), "Use the camel stone head on the pedestal.", camelHead); + useStoneHeadOnPedestal = new ObjectStep(this, ObjectID.ENAKH_PEDESTAL_MULTILOC, new WorldPoint(3104, 9312, 1), "Use the camel stone head on the " + + "pedestal.", camelHead); useStoneHeadOnPedestal.addIcon(ItemID.ENAKH_STONE_HEAD_AKTHANAKOS); - useBread = new NpcStep(this, NpcID.ENAKH_PENTYN, new WorldPoint(3091, 9324, 1), "Right-click use bread or cake on Pentyn.", breadOrCake.highlighted()); - castFireSpell = new NpcStep(this, NpcID.ENAKH_DUMMY_FOUNTAIN, new WorldPoint(3092, 9308, 1), "Cast a fire spell on the frozen fountain.", fireSpellRunes, onNormals); - castAirSpell = new NpcStep(this, NpcID.ENAKH_DUMMY_FURNACE, new WorldPoint(3116, 9323, 1), "Cast an air spell on the furnace.", airSpellRunes, onNormals); - useMapleLog = new ObjectStep(this, ObjectID.ENAKH_BRAZIER_4_MULTILOC, new WorldPoint(3114, 9309, 1), "Use a maple log on the north west brazier.", mapleLog); - useMapleLog.addIcon(ItemID.MAPLE_LOGS); + useBread = new NpcStep(this, NpcID.ENAKH_PENTYN, new WorldPoint(3091, 9324, 1), "Right-click use bread or cake on Pentyn.", breadOrCake.highlighted()); + castFireSpell = new NpcStep(this, NpcID.ENAKH_DUMMY_FOUNTAIN, new WorldPoint(3092, 9308, 1), "Cast a fire spell on the frozen fountain.", + fireSpellRunes, onNormals); + castAirSpell = new NpcStep(this, NpcID.ENAKH_DUMMY_FURNACE, new WorldPoint(3116, 9323, 1), "Cast an air spell on the furnace.", airSpellRunes, + onNormals); + + // Shadow Room Puzzle + useLog = new ObjectStep(this, ObjectID.ENAKH_BRAZIER_1_MULTILOC, new WorldPoint(3114, 9306, 1), "Use a normal log on the south west brazier.", log); + useLog.addIcon(ItemID.LOGS); useOakLog = new ObjectStep(this, ObjectID.ENAKH_BRAZIER_2_MULTILOC, new WorldPoint(3116, 9306, 1), "Use an oak log on the south brazier.", oakLog); useOakLog.addIcon(ItemID.OAK_LOGS); - useWillowLog = new ObjectStep(this, ObjectID.ENAKH_BRAZIER_1_MULTILOC, new WorldPoint(3114, 9306, 1), "Use a willow log on the south east brazier.", willowLog); + useWillowLog = new ObjectStep(this, ObjectID.ENAKH_BRAZIER_3_MULTILOC, new WorldPoint(3118, 9306, 1), "Use a willow log on the south east brazier.", + willowLog); useWillowLog.addIcon(ItemID.WILLOW_LOGS); - useLog = new ObjectStep(this, ObjectID.ENAKH_BRAZIER_3_MULTILOC, new WorldPoint(3118, 9306, 1), "Use a normal log on the south west brazier.", log); - useLog.addIcon(ItemID.LOGS); - useCoal = new ObjectStep(this, ObjectID.ENAKH_BRAZIER_6_MULTILOC, new WorldPoint(3118, 9309, 1), "Use coal on the north east brazier.", coal); - useCoal.addIcon(ItemID.COAL); + useMapleLog = new ObjectStep(this, ObjectID.ENAKH_BRAZIER_4_MULTILOC, new WorldPoint(3114, 9309, 1), "Use a maple log on the north west brazier.", + mapleLog); + useMapleLog.addIcon(ItemID.MAPLE_LOGS); useCandle = new ObjectStep(this, ObjectID.ENAKH_BRAZIER_5_MULTILOC, new WorldPoint(3116, 9309, 1), "Use a candle on the north brazier.", candle); useCandle.addIcon(ItemID.UNLIT_CANDLE); + useCoal = new ObjectStep(this, ObjectID.ENAKH_BRAZIER_6_MULTILOC, new WorldPoint(3118, 9309, 1), "Use coal on the north east brazier.", coal); + useCoal.addIcon(ItemID.COAL); passBarrier = new ObjectStep(this, ObjectID.ENAKH_MAGIC_WALL, new WorldPoint(3104, 9319, 1), "Pass through the magic barrier and go up the ladder."); goUpFromPuzzleRoom = new ObjectStep(this, ObjectID.ENAKH_TEMPLE_LADDERUP, new WorldPoint(3104, 9332, 1), "Go up the ladder."); passBarrier.addSubSteps(goUpFromPuzzleRoom); - castCrumbleUndead = new NpcStep(this, NpcID.ENAKH_BONEGUARD, new WorldPoint(3104, 9307, 2), "Cast crumble undead on the Boneguard.", earth2, airRuneOrStaff, chaos, onNormals); + castCrumbleUndead = new NpcStep(this, NpcID.ENAKH_BONEGUARD, new WorldPoint(3104, 9307, 2), "Cast crumble undead on the Boneguard.", earth2, + airRuneOrStaff, chaos, onNormals); - goDownToFinalRoom = new ObjectStep(this, ObjectID.ENAKH_TEMPLE_PILLAR_LADDER_TOP, new WorldPoint(3105, 9300, 2), "Climb down the stone ladder past the Boneguard."); + goDownToFinalRoom = new ObjectStep(this, ObjectID.ENAKH_TEMPLE_PILLAR_LADDER_TOP, new WorldPoint(3105, 9300, 2), "Climb down the stone ladder past " + + "the Boneguard."); protectThenTalk = new NpcStep(this, NpcID.ENAKH_AKTHANAKOS_BONEGUARD, new WorldPoint(3105, 9297, 1), "Put on Protect from Melee, then talk to the Boneguard.", protectFromMelee); - repairWall = new ObjectStep(this, ObjectID.ENAKH_LARGEWALL_L_MULTILOC, new WorldPoint(3107, 9291, 1), "Take sandstone from the nearby rubble, and use it to repair the south wall. For each piece added, use a chisel on the wall.", sandstone5); + repairWall = new ObjectStep(this, ObjectID.ENAKH_LARGEWALL_L_MULTILOC, new WorldPoint(3107, 9291, 1), "Take sandstone from the nearby rubble, and use" + + " it to repair the south wall. For each piece added, use a chisel on the wall.", sandstone5); repairWall.addDialogSteps("Of course, I'll help you out.", "Okay, I'll start building."); repairWall.addIcon(ItemID.ENAKH_SANDSTONE_MEDIUM); - useChiselOnWall = new ObjectStep(this, ObjectID.ENAKH_LARGEWALL_L_MULTILOC, new WorldPoint(3107, 9291, 1), "Use a chisel on the wall.", chiselHighlighted); + useChiselOnWall = new ObjectStep(this, ObjectID.ENAKH_LARGEWALL_L_MULTILOC, new WorldPoint(3107, 9291, 1), "Use a chisel on the wall.", + chiselHighlighted); useChiselOnWall.addDialogSteps("Of course, I'll help you out.", "Okay, I'll start building."); useChiselOnWall.addIcon(ItemID.CHISEL); repairWall.addSubSteps(useChiselOnWall); talkToAkthankos = new NpcStep(this, NpcID.ENAKH_AKTHANAKOS_BONEGUARD, new WorldPoint(3105, 9297, 1), "Talk to the Boneguard to finish the quest."); - ((NpcStep) talkToAkthankos).addAlternateNpcs(NpcID.ENAKH_AKTHANAKOS_FREED); + talkToAkthankos.addAlternateNpcs(NpcID.ENAKH_AKTHANAKOS_FREED); + + } + + @Override + public Map loadSteps() + { + initializeRequirements(); + setupSteps(); + + var steps = new HashMap(); + + steps.put(0, talkToLazim); + + ConditionalStep makeAndPlaceBase = new ConditionalStep(this, bringLazim32Sandstone); + makeAndPlaceBase.addStep(new Conditions(head, granite), giveLazimHead); + makeAndPlaceBase.addStep(new Conditions(granite2, canChooseHead), craftHead); + makeAndPlaceBase.addStep(canChooseHead, getGranite); + makeAndPlaceBase.addStep(chiseledStatue, talkToLazimToChooseHead); + makeAndPlaceBase.addStep(hasPlacedBody, chiselStatue); + makeAndPlaceBase.addStep(body, placeBody); + makeAndPlaceBase.addStep(sandstone20, useChiselOn20Sandstone); + makeAndPlaceBase.addStep(hasTalkedToLazimAfterBase, bringLazim20Sandstone); + makeAndPlaceBase.addStep(hasPlacedBase, talkToLazimAboutBody); + makeAndPlaceBase.addStep(base, placeBase); + makeAndPlaceBase.addStep(sandstone32, useChiselOn32Sandstone); + + steps.put(10, makeAndPlaceBase); + ConditionalStep exploreBottomLayer = new ConditionalStep(this, enterTemple); + exploreBottomLayer.addStep(new Conditions(camelHead, inPuzzleFloor), useStoneHeadOnPedestal); + exploreBottomLayer.addStep(camelMould, useChiselOnGranite); + exploreBottomLayer.addStep(inPuzzleFloor, useSoftClayOnPedestal); + exploreBottomLayer.addStep(new Conditions(gottenLimbs, inTempleGroundFloor, openedDoor1, openedDoor2, openedDoor3, openedDoor4), goUpToPuzzles); + exploreBottomLayer.addStep(new Conditions(gottenLimbs, inTempleGroundFloor, openedDoor1, openedDoor2, openedDoor3, rSigil), enterDoor4); + exploreBottomLayer.addStep(new Conditions(gottenLimbs, inTempleGroundFloor, openedDoor1, openedDoor2, openedDoor3), takeR); + exploreBottomLayer.addStep(new Conditions(gottenLimbs, inTempleGroundFloor, openedDoor1, openedDoor2, kSigil), enterDoor3); + exploreBottomLayer.addStep(new Conditions(gottenLimbs, inTempleGroundFloor, openedDoor1, openedDoor2), takeK); + // It's possible to skip the rest of this, but it skips some of the quest story and leaves doors locked after you finish, so this encourages players + // to explore + exploreBottomLayer.addStep(new Conditions(gottenLimbs, inTempleGroundFloor, openedDoor1, zSigil), enterDoor2); + exploreBottomLayer.addStep(new Conditions(gottenLimbs, inTempleGroundFloor, openedDoor1), takeZ); + exploreBottomLayer.addStep(new Conditions(gottenLimbs, inTempleGroundFloor, mSigil), enterDoor1); + exploreBottomLayer.addStep(new Conditions(gottenLimbs, inTempleGroundFloor), takeM); + exploreBottomLayer.addStep(new Conditions(startedTemple, inTempleGroundFloor), cutOffLimb); + exploreBottomLayer.addStep(inTempleGroundFloor, talkToLazimInTemple); + exploreBottomLayer.addStep(inTempleEntranceRoom, enterTempleDownLadder); + + steps.put(20, exploreBottomLayer); + + ConditionalStep puzzles = new ConditionalStep(this, enterTemple); + puzzles.addStep(new Conditions(fedBread, inPuzzleFloor, meltedFountain, cleanedFurnace, litLog, litOak, litWillow, litMaple, litCandle), useCoal); + puzzles.addStep(new Conditions(fedBread, inPuzzleFloor, meltedFountain, cleanedFurnace, litLog, litOak, litWillow, litMaple), useCandle); + puzzles.addStep(new Conditions(fedBread, inPuzzleFloor, meltedFountain, cleanedFurnace, litLog, litOak, litWillow), useMapleLog); + puzzles.addStep(new Conditions(fedBread, inPuzzleFloor, meltedFountain, cleanedFurnace, litLog, litOak), useWillowLog); + puzzles.addStep(new Conditions(fedBread, inPuzzleFloor, meltedFountain, cleanedFurnace, litLog), useOakLog); + puzzles.addStep(new Conditions(fedBread, inPuzzleFloor, meltedFountain, cleanedFurnace), useLog); + puzzles.addStep(new Conditions(fedBread, inPuzzleFloor, meltedFountain), castAirSpell); + puzzles.addStep(new Conditions(fedBread, inPuzzleFloor), castFireSpell); + puzzles.addStep(inPuzzleFloor, useBread); + puzzles.addStep(inTempleGroundFloor, goUpToPuzzles); + puzzles.addStep(inTempleEntranceRoom, enterTempleDownLadder); + + steps.put(30, puzzles); + + ConditionalStep topFloorPuzzle = new ConditionalStep(this, enterTemple); + topFloorPuzzle.addStep(inTopRoom, castCrumbleUndead); + topFloorPuzzle.addStep(inNorthPuzzleRoom, goUpFromPuzzleRoom); + topFloorPuzzle.addStep(inPuzzleFloor, passBarrier); + topFloorPuzzle.addStep(inTempleGroundFloor, goUpToPuzzles); + topFloorPuzzle.addStep(inTempleEntranceRoom, enterTempleDownLadder); + + steps.put(40, topFloorPuzzle); + + ConditionalStep protectMeleePuzzle = new ConditionalStep(this, enterTemple); + protectMeleePuzzle.addStep(inLastRoom, protectThenTalk); + protectMeleePuzzle.addStep(inTopRoom, goDownToFinalRoom); + protectMeleePuzzle.addStep(inNorthPuzzleRoom, goUpFromPuzzleRoom); + protectMeleePuzzle.addStep(inPuzzleFloor, passBarrier); + protectMeleePuzzle.addStep(inTempleGroundFloor, goUpToPuzzles); + protectMeleePuzzle.addStep(inTempleEntranceRoom, enterTempleDownLadder); + + steps.put(50, protectMeleePuzzle); + + ConditionalStep repairWallForAkthankos = new ConditionalStep(this, enterTemple); + repairWallForAkthankos.addStep(new Conditions(inLastRoom, wallNeedsChisel), useChiselOnWall); + repairWallForAkthankos.addStep(new Conditions(inLastRoom, finishedWall), talkToAkthankos); + repairWallForAkthankos.addStep(inLastRoom, repairWall); + repairWallForAkthankos.addStep(inTopRoom, goDownToFinalRoom); + repairWallForAkthankos.addStep(inNorthPuzzleRoom, goUpFromPuzzleRoom); + repairWallForAkthankos.addStep(inPuzzleFloor, passBarrier); + repairWallForAkthankos.addStep(inTempleGroundFloor, goUpToPuzzles); + repairWallForAkthankos.addStep(inTempleEntranceRoom, enterTempleDownLadder); + + steps.put(60, repairWallForAkthankos); + + return steps; } @Override public List getItemRequirements() { - ArrayList reqs = new ArrayList<>(); - reqs.add(pickaxe); - reqs.add(chiselHighlighted); - reqs.add(softClay); - reqs.add(breadOrCake); - reqs.add(tinderbox); - reqs.add(log); - reqs.add(oakLog); - reqs.add(willowLog); - reqs.add(mapleLog); - reqs.add(candle); - reqs.add(coal); - reqs.add(fireSpellRunes); - reqs.add(airSpellRunes); - reqs.add(crumbleUndeadRunes); - int miningLevel = client.getRealSkillLevel(Skill.MINING); - if (miningLevel < 45) - { - reqs.add(granite2); - } - if (miningLevel < 35) - { - reqs.add(sandstone52); - } - return reqs; + return List.of( + pickaxe, + chiselHighlighted, + softClay, + breadOrCake, + tinderbox, + log, + oakLog, + willowLog, + mapleLog, + candle, + coal, + fireSpellRunes, + airSpellRunes, + crumbleUndeadRunes, + granite2.hideConditioned(new SkillRequirement(Skill.MINING, 45)), + sandstone52.hideConditioned(new SkillRequirement(Skill.MINING, 35)) + ); } @Override public List getGeneralRecommended() { - ArrayList req = new ArrayList<>(); - req.add(onNormals); - return req; + return List.of(onNormals); } @Override public List getGeneralRequirements() { - ArrayList req = new ArrayList<>(); - req.add(new SkillRequirement(Skill.CRAFTING, 50)); - req.add(new SkillRequirement(Skill.FIREMAKING, 45, true)); - req.add(new SkillRequirement(Skill.PRAYER, 43)); - req.add(new SkillRequirement(Skill.MAGIC, 39)); - return req; + return List.of( + new SkillRequirement(Skill.CRAFTING, 50), + new SkillRequirement(Skill.FIREMAKING, 45, true), + new SkillRequirement(Skill.PRAYER, 43), + new SkillRequirement(Skill.MAGIC, 39) + ); } @Override @@ -498,31 +634,50 @@ public QuestPointReward getQuestPointReward() @Override public List getExperienceRewards() { - return Arrays.asList( - new ExperienceReward(Skill.CRAFTING, 7000), - new ExperienceReward(Skill.MINING, 7000), - new ExperienceReward(Skill.FIREMAKING, 7000), - new ExperienceReward(Skill.MAGIC, 7000)); + return List.of( + new ExperienceReward(Skill.CRAFTING, 7000), + new ExperienceReward(Skill.MINING, 7000), + new ExperienceReward(Skill.FIREMAKING, 7000), + new ExperienceReward(Skill.MAGIC, 7000) + ); } @Override public List getItemRewards() { - return Collections.singletonList(new ItemReward("Akthanakos's Camulet", ItemID.CAMULET, 1)); + return List.of( + new ItemReward("Akthanakos's Camulet", ItemID.CAMULET, 1) + ); } @Override public List getPanels() { - List allSteps = new ArrayList<>(); - allSteps.add(new PanelDetails("Starting off", Collections.singletonList(talkToLazim))); - allSteps.add(new PanelDetails("Craft a statue", Arrays.asList(bringLazim32Sandstone, useChiselOn32Sandstone, placeBase, talkToLazimAboutBody, - bringLazim20Sandstone, useChiselOn20Sandstone, placeBody, chiselStatue, talkToLazimToChooseHead, getGranite, craftHead, giveLazimHead), - pickaxe, chiselHighlighted, softClay, breadOrCake, tinderbox, log, oakLog, willowLog, mapleLog, candle, coal, fireSpellRunes, airSpellRunes, earth2, air2, chaos)); - allSteps.add(new PanelDetails("Explore the ground floor", Arrays.asList(talkToLazimInTemple, cutOffLimb, takeM, enterDoor1, enterDoor2, enterMDoor, goUpToPuzzles))); - allSteps.add(new PanelDetails("Solve the puzzles", Arrays.asList(useSoftClayOnPedestal, useChiselOnGranite, useStoneHeadOnPedestal, useBread, castFireSpell, castAirSpell, - useLog, useOakLog, useWillowLog, useMapleLog, useCandle, useCoal))); - allSteps.add(new PanelDetails("Free Akthankos", Arrays.asList(passBarrier, goUpFromPuzzleRoom, castCrumbleUndead, goDownToFinalRoom, protectThenTalk, repairWall, talkToAkthankos))); + var allSteps = new ArrayList(); + + allSteps.add(new PanelDetails("Starting off", List.of( + talkToLazim + ))); + + allSteps.add(new PanelDetails("Craft a statue", List.of( + bringLazim32Sandstone, useChiselOn32Sandstone, placeBase, talkToLazimAboutBody, bringLazim20Sandstone, + useChiselOn20Sandstone, placeBody, chiselStatue, talkToLazimToChooseHead, getGranite, craftHead, giveLazimHead + ), List.of(pickaxe, chiselHighlighted, softClay, breadOrCake, tinderbox, log, oakLog, willowLog, mapleLog, candle, coal, fireSpellRunes, + airSpellRunes, earth2, air2, chaos + ))); + + allSteps.add(new PanelDetails("Explore the ground floor", List.of( + talkToLazimInTemple, cutOffLimb, takeM, enterDoor1, enterDoor2, enterMDoor, goUpToPuzzles + ))); + + allSteps.add(new PanelDetails("Solve the puzzles", List.of( + useSoftClayOnPedestal, useChiselOnGranite, useStoneHeadOnPedestal, useBread, castFireSpell, castAirSpell, + useLog, useOakLog, useWillowLog, useMapleLog, useCandle, useCoal + ))); + + allSteps.add(new PanelDetails("Free Akthankos", List.of( + passBarrier, goUpFromPuzzleRoom, castCrumbleUndead, goDownToFinalRoom, protectThenTalk, repairWall, talkToAkthankos + ))); return allSteps; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/misthalinmystery/MisthalinMystery.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/misthalinmystery/MisthalinMystery.java index dfc4edfe626..7735e135ae5 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/misthalinmystery/MisthalinMystery.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/misthalinmystery/MisthalinMystery.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2020, Zoinkwiz + * Copyright (c) 2025, pajlada * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -28,6 +29,7 @@ import net.runelite.client.plugins.microbot.questhelper.questhelpers.BasicQuestHelper; import net.runelite.client.plugins.microbot.questhelper.requirements.conditional.Conditions; import net.runelite.client.plugins.microbot.questhelper.requirements.item.ItemRequirement; +import static net.runelite.client.plugins.microbot.questhelper.requirements.util.LogicHelper.and; import net.runelite.client.plugins.microbot.questhelper.requirements.util.Operation; import net.runelite.client.plugins.microbot.questhelper.requirements.var.VarbitRequirement; import net.runelite.client.plugins.microbot.questhelper.requirements.widget.WidgetTextRequirement; @@ -36,21 +38,35 @@ import net.runelite.client.plugins.microbot.questhelper.rewards.ExperienceReward; import net.runelite.client.plugins.microbot.questhelper.rewards.ItemReward; import net.runelite.client.plugins.microbot.questhelper.rewards.QuestPointReward; -import net.runelite.client.plugins.microbot.questhelper.steps.*; +import net.runelite.client.plugins.microbot.questhelper.steps.ConditionalStep; +import net.runelite.client.plugins.microbot.questhelper.steps.DetailedQuestStep; +import net.runelite.client.plugins.microbot.questhelper.steps.NpcStep; +import net.runelite.client.plugins.microbot.questhelper.steps.ObjectStep; +import net.runelite.client.plugins.microbot.questhelper.steps.PuzzleWrapperStep; +import net.runelite.client.plugins.microbot.questhelper.steps.QuestStep; +import net.runelite.client.plugins.microbot.questhelper.steps.WidgetStep; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import net.runelite.api.Skill; import net.runelite.api.coords.WorldPoint; +import net.runelite.api.gameval.InterfaceID; import net.runelite.api.gameval.ItemID; import net.runelite.api.gameval.NpcID; import net.runelite.api.gameval.ObjectID; import net.runelite.api.gameval.VarbitID; -import java.util.*; - -import static net.runelite.client.plugins.microbot.questhelper.requirements.util.LogicHelper.and; - public class MisthalinMystery extends BasicQuestHelper { - // Requirements + // Zones + Zone island; + Zone outside1; + Zone outside2; + Zone outside3; + Zone bossRoom; + + // Miscellaneous requirements ItemRequirement bucket; ItemRequirement manorKey; ItemRequirement knife; @@ -108,18 +124,25 @@ public class MisthalinMystery extends BasicQuestHelper ObjectStep observeThroughTree; ObjectStep takeNote2; DetailedQuestStep readNotes2; + + PuzzleWrapperStep pwPlayPiano; + ConditionalStep cPlayPiano; ObjectStep playPiano; WidgetStep playD; WidgetStep playE; WidgetStep playA; WidgetStep playDAgain; DetailedQuestStep restartPiano; - ObjectStep searchThePiano; + PuzzleWrapperStep searchThePiano; + ObjectStep returnOverBrokenWall; ObjectStep openEmeraldDoor; ObjectStep enterBandosGodswordRoomStep; ObjectStep takeNote3; DetailedQuestStep readNotes3; + + PuzzleWrapperStep pwSolveFireplacePuzzle; + ConditionalStep solveFireplacePuzzle; ObjectStep useKnifeOnFireplace; ObjectStep searchFireplace; WidgetStep clickSapphire; @@ -129,7 +152,8 @@ public class MisthalinMystery extends BasicQuestHelper WidgetStep clickOnyx; WidgetStep clickRuby; DetailedQuestStep restartGems; - ObjectStep searchFireplaceForSapphireKey; + PuzzleWrapperStep searchFireplaceForSapphireKey; + ObjectStep goThroughSapphireDoor; DetailedQuestStep reflectKnives; ObjectStep continueThroughSapphireDoor; @@ -139,162 +163,6 @@ public class MisthalinMystery extends BasicQuestHelper ObjectStep leaveSapphireRoom; NpcStep talkToMandy; - // Zones - Zone island; - Zone outside1; - Zone outside2; - Zone outside3; - Zone bossRoom; - - @Override - public Map loadSteps() - { - initializeRequirements(); - setupConditions(); - setupSteps(); - - var steps = new HashMap(); - - steps.put(0, talkToAbigale); - - steps.put(5, talkToAbigale); - - var investigatingTheBarrel = new ConditionalStep(this, takeTheBoat); - investigatingTheBarrel.addStep(new Conditions(onIsland, bucket), searchTheBarrel); - investigatingTheBarrel.addStep(onIsland, takeTheBucket); - steps.put(10, investigatingTheBarrel); - steps.put(15, investigatingTheBarrel); - - var emptyTheBarrel = new ConditionalStep(this, takeTheBoat); - emptyTheBarrel.addStep(new Conditions(onIsland, bucket), useBucketOnBarrel); - emptyTheBarrel.addStep(onIsland, takeTheBucket); - steps.put(20, emptyTheBarrel); - - var enterTheHouse = new ConditionalStep(this, takeTheBoat); - enterTheHouse.addStep(new Conditions(onIsland, manorKey), openManorDoor); - enterTheHouse.addStep(onIsland, searchTheBarrelForKey); - steps.put(25, enterTheHouse); - - var pinkDoor = new ConditionalStep(this, takeTheBoat); - pinkDoor.addStep(knife, tryToOpenPinkKnobDoor); - pinkDoor.addStep(onIsland, takeKnife); - steps.put(30, pinkDoor); - - var pickUpAndReadNotes1 = new ConditionalStep(this, takeTheBoat); - pickUpAndReadNotes1.addStep(new Conditions(onIsland, notes1), readNotes1); - pickUpAndReadNotes1.addStep(onIsland, takeNote1); - steps.put(35, pickUpAndReadNotes1); - - var cutPainting = new ConditionalStep(this, takeTheBoat); - cutPainting.addStep(new Conditions(onIsland, knife), useKnifeOnPainting); - cutPainting.addStep(onIsland, takeKnife); - steps.put(40, cutPainting); - - var enterRubyRoom = new ConditionalStep(this, takeTheBoat); - enterRubyRoom.addStep(new Conditions(onIsland, rubyKey), goThroughRubyDoor); - enterRubyRoom.addStep(onIsland, searchPainting); - steps.put(45, enterRubyRoom); - - var lightCandles = new ConditionalStep(this, takeTheBoat); - lightCandles.addStep(new Conditions(onIsland, tinderbox, litCandle1, litCandle2, litCandle3), lightCandle4); - lightCandles.addStep(new Conditions(onIsland, tinderbox, litCandle1, litCandle2), lightCandle3); - lightCandles.addStep(new Conditions(onIsland, tinderbox, litCandle1), lightCandle2); - lightCandles.addStep(new Conditions(onIsland, tinderbox), lightCandle1); - lightCandles.addStep(onIsland, takeTinderbox); - steps.put(50, lightCandles); - - var lightFuseOnBarrel = new ConditionalStep(this, takeTheBoat); - lightFuseOnBarrel.addStep(new Conditions(onIsland, tinderbox), lightBarrel); - lightFuseOnBarrel.addStep(onIsland, takeTinderbox); - steps.put(55, lightFuseOnBarrel); - steps.put(60, leaveExplosionRoom); - - var goToLacey = new ConditionalStep(this, takeTheBoat); - goToLacey.addStep(inOutsideArea, observeThroughTree); - goToLacey.addStep(onIsland, climbWall); - steps.put(65, goToLacey); - - var pickUpAndReadNotes2 = new ConditionalStep(this, takeTheBoat); - pickUpAndReadNotes2.addStep(notes2, readNotes2); - pickUpAndReadNotes2.addStep(inOutsideArea, takeNote2); - pickUpAndReadNotes2.addStep(onIsland, climbWall); - steps.put(70, pickUpAndReadNotes2); - - var playMusic = new ConditionalStep(this, takeTheBoat); - playMusic.addStep(playedA, playDAgain); - playMusic.addStep(playedE, playA); - playMusic.addStep(playedD, playE); - playMusic.addStep(new Conditions(playedAnyKey, inPianoWidget), restartPiano); - playMusic.addStep(inPianoWidget, playD); - playMusic.addStep(inOutsideArea, playPiano); - playMusic.addStep(onIsland, climbWall); - steps.put(75, playMusic); - - var openingTheEmeraldDoor = new ConditionalStep(this, takeTheBoat); - openingTheEmeraldDoor.addStep(new Conditions(inOutsideArea, emeraldKey), returnOverBrokenWall); - openingTheEmeraldDoor.addStep(inOutsideArea, searchThePiano); - openingTheEmeraldDoor.addStep(new Conditions(onIsland, emeraldKey), openEmeraldDoor); - openingTheEmeraldDoor.addStep(onIsland, climbWall); - steps.put(80, openingTheEmeraldDoor); - - var enterBandosGodswordRoom = new ConditionalStep(this, takeTheBoat); - enterBandosGodswordRoom.addStep(onIsland, enterBandosGodswordRoomStep); - steps.put(85, enterBandosGodswordRoom); - - var startPuzzle3 = new ConditionalStep(this, takeTheBoat); - startPuzzle3.addStep(new Conditions(onIsland, notes3), readNotes3); - startPuzzle3.addStep(onIsland, takeNote3); - steps.put(90, startPuzzle3); - - var openFireplace = new ConditionalStep(this, takeTheBoat); - openFireplace.addStep(new Conditions(onIsland, knife), useKnifeOnFireplace); - openFireplace.addStep(onIsland, takeKnife); - steps.put(95, openFireplace); - - var solveFireplacePuzzle = new ConditionalStep(this, takeTheBoat); - solveFireplacePuzzle.addStep(selectedOnyx, clickRuby); - solveFireplacePuzzle.addStep(selectedEmerald, clickOnyx); - solveFireplacePuzzle.addStep(selectedZenyte, clickEmerald); - solveFireplacePuzzle.addStep(selectedDiamond, clickZenyte); - solveFireplacePuzzle.addStep(selectedSaphire, clickDiamond); - solveFireplacePuzzle.addStep(selectAnyGem, restartGems); - solveFireplacePuzzle.addStep(inGemWidget, clickSapphire); - solveFireplacePuzzle.addStep(onIsland, searchFireplace); - steps.put(100, solveFireplacePuzzle); - - var openSapphireDoor = new ConditionalStep(this, takeTheBoat); - openSapphireDoor.addStep(new Conditions(onIsland, sapphireKey), goThroughSapphireDoor); - openSapphireDoor.addStep(onIsland, searchFireplaceForSapphireKey); - steps.put(105, openSapphireDoor); - - var goDoBoss = new ConditionalStep(this, takeTheBoat); - goDoBoss.addStep(inBossRoom, reflectKnives); - goDoBoss.addStep(onIsland, goThroughSapphireDoor); - steps.put(110, goDoBoss); - steps.put(111, goDoBoss); - - var watchRevealCutscene = new ConditionalStep(this, takeTheBoat); - watchRevealCutscene.addStep(inBossRoom, watchTheKillersReveal); - watchRevealCutscene.addStep(onIsland, continueThroughSapphireDoor); - steps.put(115, watchRevealCutscene); - - var goFightAbigale = new ConditionalStep(this, takeTheBoat); - goFightAbigale.addStep(new Conditions(inBossRoom, killersKnife), fightAbigale); - goFightAbigale.addStep(inBossRoom, pickUpKillersKnife); - goFightAbigale.addStep(onIsland, continueThroughSapphireDoor); - steps.put(120, goFightAbigale); - - var attemptToLeaveSapphireRoom = new ConditionalStep(this, takeTheBoat); - attemptToLeaveSapphireRoom.addStep(onIsland, leaveSapphireRoom); - steps.put(125, attemptToLeaveSapphireRoom); - - var finishTheQuest = new ConditionalStep(this, takeTheBoat); - finishTheQuest.addStep(onIsland, talkToMandy); - steps.put(130, finishTheQuest); - - return steps; - } - @Override protected void setupZones() { @@ -305,19 +173,20 @@ protected void setupZones() bossRoom = new Zone(new WorldPoint(1619, 4825, 0), new WorldPoint(1627, 4834, 0)); } - public void setupConditions() + @Override + protected void setupRequirements() { onIsland = new ZoneRequirement(island); inOutsideArea = new ZoneRequirement(outside1, outside2, outside3); inBossRoom = new ZoneRequirement(bossRoom); - litCandle1 = new VarbitRequirement(4042, 1); - litCandle2 = new VarbitRequirement(4041, 1); - litCandle3 = new VarbitRequirement(4039, 1); + litCandle1 = new VarbitRequirement(VarbitID.MISTMYST_CANDLE4, 1); + litCandle2 = new VarbitRequirement(VarbitID.MISTMYST_CANDLE3, 1); + litCandle3 = new VarbitRequirement(VarbitID.MISTMYST_CANDLE1, 1); - playedD = and(new VarbitRequirement(4044, 1), new VarbitRequirement(4049, 1)); - playedE = and(new VarbitRequirement(4045, 1), new VarbitRequirement(4049, 2)); - playedA = and(new VarbitRequirement(4046, 1), new VarbitRequirement(4049, 3)); + playedD = and(new VarbitRequirement(VarbitID.MISTMYST_PIANO_D1, 1), new VarbitRequirement(VarbitID.MISTMYST_PIANO_ATTEMPTS, 1)); + playedE = and(new VarbitRequirement(VarbitID.MISTMYST_PIANO_E, 1), new VarbitRequirement(VarbitID.MISTMYST_PIANO_ATTEMPTS, 2)); + playedA = and(new VarbitRequirement(VarbitID.MISTMYST_PIANO_A, 1), new VarbitRequirement(VarbitID.MISTMYST_PIANO_ATTEMPTS, 3)); playedAnyKey = new VarbitRequirement(VarbitID.MISTMYST_PIANO_ATTEMPTS, 1, Operation.GREATER_EQUAL); inPianoWidget = new WidgetTextRequirement(554, 20, "C"); inGemWidget = new WidgetTextRequirement(555, 1, 1, "Gemstone switch panel"); @@ -327,11 +196,7 @@ public void setupConditions() selectedEmerald = and(new VarbitRequirement(4054, 1), new VarbitRequirement(4050, 4)); selectedOnyx = and(new VarbitRequirement(4055, 1), new VarbitRequirement(4050, 5)); selectAnyGem = new VarbitRequirement(VarbitID.MISTMYST_SWITCH_ATTEMPTS, 1, Operation.GREATER_EQUAL); - } - @Override - protected void setupRequirements() - { bucket = new ItemRequirement("Bucket", ItemID.BUCKET_EMPTY); manorKey = new ItemRequirement("Manor key", ItemID.MISTMYST_FRONTDOOR_KEY); knife = new ItemRequirement("Knife", ItemID.KNIFE); @@ -363,7 +228,7 @@ public void setupSteps() tryToOpenPinkKnobDoor = new ObjectStep(this, ObjectID.MISTMYST_DOOR_REDTOPAZ, new WorldPoint(1635, 4838, 0), "Try to open the door with the pink handle."); takeNote1 = new ObjectStep(this, ObjectID.MISTMYST_CLUE_LIBRARY, new WorldPoint(1635, 4839, 0), "Pick up the note that appeared."); readNotes1 = new DetailedQuestStep(this, "Read the notes.", notes1.highlighted()); - useKnifeOnPainting = new ObjectStep(this, ObjectID.MISTMYST_PAINTING, new WorldPoint(1632, 4833, 0), "Use a knife on the marked painting.", knife); + useKnifeOnPainting = new ObjectStep(this, ObjectID.MISTMYST_PAINTING, new WorldPoint(1632, 4833, 0), "Use a knife on the marked painting.", knife.highlighted()); useKnifeOnPainting.addIcon(ItemID.KNIFE); searchPainting = new ObjectStep(this, ObjectID.MISTMYST_PAINTING, new WorldPoint(1632, 4833, 0), "Search the painting for a ruby key."); goThroughRubyDoor = new ObjectStep(this, ObjectID.MISTMYST_DOOR_RUBY, new WorldPoint(1640, 4828, 0), "Go through the door with the ruby handle.", rubyKey); @@ -389,15 +254,24 @@ public void setupSteps() takeNote2 = new ObjectStep(this, ObjectID.MISTMYST_CLUE_OUTSIDE, new WorldPoint(1632, 4850, 0), "Pick up the note that appeared by the fence."); readNotes2 = new DetailedQuestStep(this, "Read the notes.", notes2.highlighted()); - playPiano = new ObjectStep(this, ObjectID.MISTMYST_PIANO, new WorldPoint(1647, 4842, 0), "Play the piano in the room to the south."); - playD = new WidgetStep(this, "Play the D key.", 554, 21); - playE = new WidgetStep(this, "Play the E key.", 554, 22); - playA = new WidgetStep(this, "Play the A key.", 554, 25); - playDAgain = new WidgetStep(this, "Play the D key again.", 554, 21); + playPiano = new ObjectStep(this, ObjectID.MISTMYST_PIANO, new WorldPoint(1647, 4841, 0), ""); + playD = new WidgetStep(this, "Play the D key.", InterfaceID.MistmystPiano.LABEL_D1); + playE = new WidgetStep(this, "Play the E key.", InterfaceID.MistmystPiano.LABEL_E1); + playA = new WidgetStep(this, "Play the A key.", InterfaceID.MistmystPiano.LABEL_A2); + playDAgain = new WidgetStep(this, "Play the D key again.", InterfaceID.MistmystPiano.LABEL_D1); restartPiano = new DetailedQuestStep(this, "Unfortunately you've played an incorrect key. Restart."); playPiano.addSubSteps(restartPiano); - searchThePiano = new ObjectStep(this, ObjectID.MISTMYST_PIANO, new WorldPoint(1647, 4842, 0), "Right-click search the piano for the emerald key."); + cPlayPiano = new ConditionalStep(this, playPiano, "Play the piano in the room to the south for the emerald key."); + cPlayPiano.addStep(playedA, playDAgain); + cPlayPiano.addStep(playedE, playA); + cPlayPiano.addStep(playedD, playE); + cPlayPiano.addStep(and(playedAnyKey, inPianoWidget), restartPiano); + cPlayPiano.addStep(inPianoWidget, playD); + + pwPlayPiano = cPlayPiano.puzzleWrapStepWithDefaultText("Find the emerald key in the room to the south."); + + searchThePiano = new ObjectStep(this, ObjectID.MISTMYST_PIANO, new WorldPoint(1647, 4842, 0), "Right-click search the piano for the emerald key.").puzzleWrapStep(true); returnOverBrokenWall = new ObjectStep(this, ObjectID.MISTMYST_DESTRUCTABLE_WALL_CLIMBABLE, new WorldPoint(1648, 4829, 0), "Climb back over the damaged wall into the manor.", emeraldKey); @@ -408,13 +282,12 @@ public void setupSteps() takeNote3 = new ObjectStep(this, ObjectID.MISTMYST_CLUE_KITCHEN, new WorldPoint(1630, 4842, 0), "Pick up the note that appeared by the door."); readNotes3 = new DetailedQuestStep(this, "Read the notes.", notes3.highlighted()); - useKnifeOnFireplace = new ObjectStep(this, ObjectID.MISTMYST_FIREPLACE, new WorldPoint(1647, 4836, 0), "Use a knife on the unlit fireplace in the eastern room.", knife); + useKnifeOnFireplace = new ObjectStep(this, ObjectID.MISTMYST_FIREPLACE, new WorldPoint(1647, 4836, 0), "Use a knife on the unlit fireplace in the eastern room.", knife.highlighted()); useKnifeOnFireplace.addIcon(ItemID.KNIFE); - searchFireplace = new ObjectStep(this, ObjectID.MISTMYST_FIREPLACE, new WorldPoint(1647, 4836, 0), "Search the fireplace."); + searchFireplace = new ObjectStep(this, ObjectID.MISTMYST_FIREPLACE, new WorldPoint(1647, 4836, 0), ""); restartGems = new DetailedQuestStep(this, "You've clicked a gem in the wrong order. Try restarting."); - searchFireplace.addSubSteps(restartGems); clickSapphire = new WidgetStep(this, "Click the sapphire.", 555, 19); clickDiamond = new WidgetStep(this, "Click the diamond.", 555, 4); @@ -423,7 +296,21 @@ public void setupSteps() clickOnyx = new WidgetStep(this, "Click the onyx.", 555, 7); clickRuby = new WidgetStep(this, "Click the ruby.", 555, 15); - searchFireplaceForSapphireKey = new ObjectStep(this, ObjectID.MISTMYST_FIREPLACE, new WorldPoint(1647, 4836, 0), "Search the fireplace again for the sapphire key."); + searchFireplaceForSapphireKey = new ObjectStep(this, ObjectID.MISTMYST_FIREPLACE, new WorldPoint(1647, 4836, 0), "Search the fireplace again for the sapphire key.").puzzleWrapStep(true); + + solveFireplacePuzzle = new ConditionalStep(this, takeTheBoat, "Search the fireplace and solve the puzzle for the sapphire key."); + solveFireplacePuzzle.addStep(selectedOnyx, clickRuby); + solveFireplacePuzzle.addStep(selectedEmerald, clickOnyx); + solveFireplacePuzzle.addStep(selectedZenyte, clickEmerald); + solveFireplacePuzzle.addStep(selectedDiamond, clickZenyte); + solveFireplacePuzzle.addStep(selectedSaphire, clickDiamond); + solveFireplacePuzzle.addStep(selectAnyGem, restartGems); + solveFireplacePuzzle.addStep(inGemWidget, clickSapphire); + solveFireplacePuzzle.addStep(onIsland, searchFireplace); + + pwSolveFireplacePuzzle = solveFireplacePuzzle.puzzleWrapStepWithDefaultText("Find the sapphire key in the room to the east."); + pwSolveFireplacePuzzle.addSubSteps(searchFireplaceForSapphireKey); + goThroughSapphireDoor = new ObjectStep(this, ObjectID.MISTMYST_DOOR_SAPPHIRE, new WorldPoint(1628, 4829, 0), "Go through the sapphire door."); reflectKnives = new DetailedQuestStep(this, "This puzzle requires you to move the mirror to reflect the knives the murderer throws. You can tell which wardrobe the murderer will throw from by a black swirl that'll surround it."); @@ -446,6 +333,140 @@ public void setupSteps() "Talk to Mandy just outside the manor to complete the quest."); } + @Override + public Map loadSteps() + { + initializeRequirements(); + setupSteps(); + + var steps = new HashMap(); + + steps.put(0, talkToAbigale); + + steps.put(5, talkToAbigale); + + var investigatingTheBarrel = new ConditionalStep(this, takeTheBoat); + investigatingTheBarrel.addStep(and(onIsland, bucket), searchTheBarrel); + investigatingTheBarrel.addStep(onIsland, takeTheBucket); + steps.put(10, investigatingTheBarrel); + steps.put(15, investigatingTheBarrel); + + var emptyTheBarrel = new ConditionalStep(this, takeTheBoat); + emptyTheBarrel.addStep(and(onIsland, bucket), useBucketOnBarrel); + emptyTheBarrel.addStep(onIsland, takeTheBucket); + steps.put(20, emptyTheBarrel); + + var enterTheHouse = new ConditionalStep(this, takeTheBoat); + enterTheHouse.addStep(and(onIsland, manorKey), openManorDoor); + enterTheHouse.addStep(onIsland, searchTheBarrelForKey); + steps.put(25, enterTheHouse); + + var pinkDoor = new ConditionalStep(this, takeTheBoat); + pinkDoor.addStep(knife, tryToOpenPinkKnobDoor); + pinkDoor.addStep(onIsland, takeKnife); + steps.put(30, pinkDoor); + + var pickUpAndReadNotes1 = new ConditionalStep(this, takeTheBoat); + pickUpAndReadNotes1.addStep(and(onIsland, notes1), readNotes1); + pickUpAndReadNotes1.addStep(onIsland, takeNote1); + steps.put(35, pickUpAndReadNotes1); + + var cutPainting = new ConditionalStep(this, takeTheBoat); + cutPainting.addStep(and(onIsland, knife), useKnifeOnPainting); + cutPainting.addStep(onIsland, takeKnife); + steps.put(40, cutPainting); + + var enterRubyRoom = new ConditionalStep(this, takeTheBoat); + enterRubyRoom.addStep(and(onIsland, rubyKey), goThroughRubyDoor); + enterRubyRoom.addStep(onIsland, searchPainting); + steps.put(45, enterRubyRoom); + + var lightCandles = new ConditionalStep(this, takeTheBoat); + lightCandles.addStep(and(onIsland, tinderbox, litCandle1, litCandle2, litCandle3), lightCandle4); + lightCandles.addStep(and(onIsland, tinderbox, litCandle1, litCandle2), lightCandle3); + lightCandles.addStep(and(onIsland, tinderbox, litCandle1), lightCandle2); + lightCandles.addStep(and(onIsland, tinderbox), lightCandle1); + lightCandles.addStep(onIsland, takeTinderbox); + steps.put(50, lightCandles); + + var lightFuseOnBarrel = new ConditionalStep(this, takeTheBoat); + lightFuseOnBarrel.addStep(and(onIsland, tinderbox), lightBarrel); + lightFuseOnBarrel.addStep(onIsland, takeTinderbox); + steps.put(55, lightFuseOnBarrel); + steps.put(60, leaveExplosionRoom); + + var goToLacey = new ConditionalStep(this, takeTheBoat); + goToLacey.addStep(inOutsideArea, observeThroughTree); + goToLacey.addStep(onIsland, climbWall); + steps.put(65, goToLacey); + + var pickUpAndReadNotes2 = new ConditionalStep(this, takeTheBoat); + pickUpAndReadNotes2.addStep(notes2, readNotes2); + pickUpAndReadNotes2.addStep(inOutsideArea, takeNote2); + pickUpAndReadNotes2.addStep(onIsland, climbWall); + steps.put(70, pickUpAndReadNotes2); + + var playMusic = new ConditionalStep(this, takeTheBoat); + playMusic.addStep(inOutsideArea, pwPlayPiano); + playMusic.addStep(onIsland, climbWall); + steps.put(75, playMusic); + + var openingTheEmeraldDoor = new ConditionalStep(this, takeTheBoat); + openingTheEmeraldDoor.addStep(and(inOutsideArea, emeraldKey), returnOverBrokenWall); + openingTheEmeraldDoor.addStep(inOutsideArea, searchThePiano); + openingTheEmeraldDoor.addStep(and(onIsland, emeraldKey), openEmeraldDoor); + openingTheEmeraldDoor.addStep(onIsland, climbWall); + steps.put(80, openingTheEmeraldDoor); + + var enterBandosGodswordRoom = new ConditionalStep(this, takeTheBoat); + enterBandosGodswordRoom.addStep(onIsland, enterBandosGodswordRoomStep); + steps.put(85, enterBandosGodswordRoom); + + var startPuzzle3 = new ConditionalStep(this, takeTheBoat); + startPuzzle3.addStep(and(onIsland, notes3), readNotes3); + startPuzzle3.addStep(onIsland, takeNote3); + steps.put(90, startPuzzle3); + + var openFireplace = new ConditionalStep(this, takeTheBoat); + openFireplace.addStep(and(onIsland, knife), useKnifeOnFireplace); + openFireplace.addStep(onIsland, takeKnife); + steps.put(95, openFireplace); + + steps.put(100, pwSolveFireplacePuzzle); + + var openSapphireDoor = new ConditionalStep(this, takeTheBoat); + openSapphireDoor.addStep(and(onIsland, sapphireKey), goThroughSapphireDoor); + openSapphireDoor.addStep(onIsland, searchFireplaceForSapphireKey); + steps.put(105, openSapphireDoor); + + var goDoBoss = new ConditionalStep(this, takeTheBoat); + goDoBoss.addStep(inBossRoom, reflectKnives); + goDoBoss.addStep(onIsland, goThroughSapphireDoor); + steps.put(110, goDoBoss); + steps.put(111, goDoBoss); + + var watchRevealCutscene = new ConditionalStep(this, takeTheBoat); + watchRevealCutscene.addStep(inBossRoom, watchTheKillersReveal); + watchRevealCutscene.addStep(onIsland, continueThroughSapphireDoor); + steps.put(115, watchRevealCutscene); + + var goFightAbigale = new ConditionalStep(this, takeTheBoat); + goFightAbigale.addStep(and(inBossRoom, killersKnife), fightAbigale); + goFightAbigale.addStep(inBossRoom, pickUpKillersKnife); + goFightAbigale.addStep(onIsland, continueThroughSapphireDoor); + steps.put(120, goFightAbigale); + + var attemptToLeaveSapphireRoom = new ConditionalStep(this, takeTheBoat); + attemptToLeaveSapphireRoom.addStep(onIsland, leaveSapphireRoom); + steps.put(125, attemptToLeaveSapphireRoom); + + var finishTheQuest = new ConditionalStep(this, takeTheBoat); + finishTheQuest.addStep(onIsland, talkToMandy); + steps.put(130, finishTheQuest); + + return steps; + } + @Override public QuestPointReward getQuestPointReward() { @@ -473,17 +494,71 @@ public List getItemRewards() @Override public List getPanels() { - var allSteps = new ArrayList(); - - allSteps.add(new PanelDetails("Talk to Abigale", Collections.singletonList(talkToAbigale))); - allSteps.add(new PanelDetails("Enter the manor", Arrays.asList(takeTheBoat, takeTheBucket, searchTheBarrel, useBucketOnBarrel, searchTheBarrelForKey, openManorDoor))); - allSteps.add(new PanelDetails("Solve the first puzzle", Arrays.asList(takeKnife, tryToOpenPinkKnobDoor, takeNote1, readNotes1, useKnifeOnPainting, searchPainting, goThroughRubyDoor))); - allSteps.add(new PanelDetails("Solve the second puzzle", Arrays.asList(takeTinderbox, lightCandle1, lightBarrel, leaveExplosionRoom, climbWall))); - allSteps.add(new PanelDetails("Solve the third puzzle", Arrays.asList(observeThroughTree, takeNote2, readNotes2, playPiano, playD, playE, playA, playDAgain, searchThePiano))); - allSteps.add(new PanelDetails("Witness another murder", Arrays.asList(returnOverBrokenWall, openEmeraldDoor, enterBandosGodswordRoomStep))); - allSteps.add(new PanelDetails("Solve the fourth puzzle", Arrays.asList(takeNote3, readNotes3, useKnifeOnFireplace, searchFireplace, clickSapphire, clickDiamond, clickZenyte, clickEmerald, clickOnyx, clickRuby, searchFireplaceForSapphireKey))); - allSteps.add(new PanelDetails("Confront the killer", Arrays.asList(goThroughSapphireDoor, reflectKnives, watchTheKillersReveal, pickUpKillersKnife, fightAbigale, leaveSapphireRoom, talkToMandy))); - - return allSteps; + var steps = new ArrayList(); + + steps.add(new PanelDetails("Talk to Abigale", List.of( + talkToAbigale + ))); + + steps.add(new PanelDetails("Enter the manor", List.of( + takeTheBoat, + takeTheBucket, + searchTheBarrel, + useBucketOnBarrel, + searchTheBarrelForKey, + openManorDoor + ))); + + steps.add(new PanelDetails("Solve the first puzzle", List.of( + takeKnife, + tryToOpenPinkKnobDoor, + takeNote1, + readNotes1, + useKnifeOnPainting, + searchPainting, + goThroughRubyDoor + ))); + + steps.add(new PanelDetails("Solve the second puzzle", List.of( + takeTinderbox, + lightCandle1, + lightBarrel, + leaveExplosionRoom, + climbWall + ))); + + steps.add(new PanelDetails("Solve the third puzzle", List.of( + observeThroughTree, + takeNote2, + readNotes2, + pwPlayPiano, + searchThePiano + ))); + + steps.add(new PanelDetails("Witness another murder", List.of( + returnOverBrokenWall, + openEmeraldDoor, + enterBandosGodswordRoomStep + ))); + + steps.add(new PanelDetails("Solve the fourth puzzle", List.of( + takeNote3, + readNotes3, + useKnifeOnFireplace, + pwSolveFireplacePuzzle, + searchFireplaceForSapphireKey + ))); + + steps.add(new PanelDetails("Confront the killer", List.of( + goThroughSapphireDoor, + reflectKnives, + watchTheKillersReveal, + pickUpKillersKnife, + fightAbigale, + leaveSapphireRoom, + talkToMandy + ))); + + return steps; } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/piratestreasure/PiratesTreasure.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/piratestreasure/PiratesTreasure.java index 50f2018ec9e..c3c4b80bb61 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/piratestreasure/PiratesTreasure.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/piratestreasure/PiratesTreasure.java @@ -27,115 +27,159 @@ import net.runelite.client.plugins.microbot.questhelper.collections.ItemCollections; import net.runelite.client.plugins.microbot.questhelper.panel.PanelDetails; import net.runelite.client.plugins.microbot.questhelper.questhelpers.BasicQuestHelper; -import net.runelite.client.plugins.microbot.questhelper.requirements.conditional.Conditions; import net.runelite.client.plugins.microbot.questhelper.requirements.item.ItemRequirement; +import net.runelite.client.plugins.microbot.questhelper.requirements.npc.NpcHintArrowRequirement; import net.runelite.client.plugins.microbot.questhelper.requirements.zone.Zone; import net.runelite.client.plugins.microbot.questhelper.requirements.zone.ZoneRequirement; import net.runelite.client.plugins.microbot.questhelper.rewards.ItemReward; import net.runelite.client.plugins.microbot.questhelper.rewards.QuestPointReward; -import net.runelite.client.plugins.microbot.questhelper.steps.*; +import net.runelite.client.plugins.microbot.questhelper.steps.ConditionalStep; +import net.runelite.client.plugins.microbot.questhelper.steps.DetailedQuestStep; +import net.runelite.client.plugins.microbot.questhelper.steps.DigStep; +import net.runelite.client.plugins.microbot.questhelper.steps.NpcStep; +import net.runelite.client.plugins.microbot.questhelper.steps.ObjectStep; +import net.runelite.client.plugins.microbot.questhelper.steps.QuestStep; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.ItemID; import net.runelite.api.gameval.NpcID; import net.runelite.api.gameval.ObjectID; -import java.util.*; - public class PiratesTreasure extends BasicQuestHelper { - //ItemRequirements - ItemRequirement sixtyCoins, spade, pirateMessage, chestKey; + // Required items + ItemRequirement sixtyCoins; + ItemRequirement spade; + ItemRequirement tenBananas; - private NpcStep speakToRedbeard; + // Recommended items + ItemRequirement teleportVarrock; + ItemRequirement teleportFalador; - private RumSmugglingStep smuggleRum; + // Mid-quest requirements + ItemRequirement pirateMessage; + ItemRequirement chestKey; - private QuestStep readPirateMessage; + // Zones + Zone blueMoonFirst; - private ObjectStep openChest, climbStairs; + // Miscellaneous requirements + ZoneRequirement inBlueMoonFirst; - private QuestStep digUpTreasure; + // Steps + NpcStep speakToRedbeard; - Zone blueMoonFirst; + QuestStep readPirateMessage; - ZoneRequirement inBlueMoonFirst; + RumSmugglingStep smuggleRum; + + ObjectStep openChest; + ObjectStep climbStairs; + + QuestStep digUpTreasure; + NpcStep killGardener; @Override - public Map loadSteps() + protected void setupZones() { - initializeRequirements(); + blueMoonFirst = new Zone(new WorldPoint(3213, 3405, 1), new WorldPoint(3234, 3391, 1)); + } + + @Override + protected void setupRequirements() + { + sixtyCoins = new ItemRequirement("Coins", ItemCollections.COINS, 60); + spade = new ItemRequirement("Spade", ItemID.SPADE).isNotConsumed(); - Map steps = new HashMap<>(); + teleportVarrock = new ItemRequirement("A teleport to Varrock", ItemID.POH_TABLET_VARROCKTELEPORT); + teleportFalador = new ItemRequirement("A teleport to Falador", ItemID.POH_TABLET_FALADORTELEPORT); + tenBananas = new ItemRequirement("Bananas", ItemID.BANANA, 10).canBeObtainedDuringQuest(); + + pirateMessage = new ItemRequirement("Pirate message", ItemID.PIRATEMESSAGE); + chestKey = new ItemRequirement("Chest key", ItemID.CHEST_KEY); + chestKey.setTooltip("You can get another one from Redbeard Frank"); + inBlueMoonFirst = new ZoneRequirement(blueMoonFirst); + } + + private void setupSteps() + { speakToRedbeard = new NpcStep(this, NpcID.REDBEARD_FRANK, new WorldPoint(3053, 3251, 0), "Talk to Redbeard Frank in Port Sarim."); speakToRedbeard.addDialogSteps("I'm in search of treasure.", "Yes."); - steps.put(0, speakToRedbeard); - smuggleRum = new RumSmugglingStep(this); + readPirateMessage = new DetailedQuestStep(this, "Read the Pirate message.", pirateMessage.highlighted()); - steps.put(1, smuggleRum); - readPirateMessage = new DetailedQuestStep(this, "Read the Pirate message.", pirateMessage.highlighted()); - climbStairs = new ObjectStep(this, ObjectID.FAI_VARROCK_STAIRS, new WorldPoint(3228, 3393, 0), - "Climb up the stairs in The Blue Moon Inn in Varrock."); - openChest = new ObjectStep(this, ObjectID.PIRATECHEST, new WorldPoint(3219, 3396, 1), - "Open the chest by using the key on it.", chestKey.highlighted()); + climbStairs = new ObjectStep(this, ObjectID.FAI_VARROCK_STAIRS, new WorldPoint(3228, 3393, 0), "Climb up the stairs in The Blue Moon Inn in Varrock.", chestKey); + climbStairs.addTeleport(teleportVarrock); + openChest = new ObjectStep(this, ObjectID.PIRATECHEST, new WorldPoint(3219, 3396, 1), "Open the chest by using the key on it.", chestKey.highlighted()); openChest.addDialogStep("Ok thanks, I'll go and get it."); openChest.addIcon(ItemID.CHEST_KEY); - blueMoonFirst = new Zone(new WorldPoint(3213, 3405, 1), new WorldPoint(3234, 3391, 1)); - inBlueMoonFirst = new ZoneRequirement(blueMoonFirst); + smuggleRum = new RumSmugglingStep(this); + + digUpTreasure = new DigStep(this, new WorldPoint(2999, 3383, 0), "Dig in the middle of the cross in Falador Park, and kill the Gardener (level 4) who appears. Once killed, dig again."); + // TODO: Add a teleport to DigStep + + killGardener = new NpcStep(this, NpcID.PIRATE_IRATE_GARDENER, new WorldPoint(2999, 3383, 0), "Kill the Gardener (level 4)."); + digUpTreasure.addSubSteps(killGardener); + } + + @Override + public Map loadSteps() + { + initializeRequirements(); + setupSteps(); + + var steps = new HashMap(); + + steps.put(0, speakToRedbeard); + + steps.put(1, smuggleRum); - ConditionalStep getTreasureMap = new ConditionalStep(this, climbStairs); - getTreasureMap.addStep(new Conditions(chestKey, inBlueMoonFirst), openChest); + var getTreasureMap = new ConditionalStep(this, climbStairs); getTreasureMap.addStep(pirateMessage, readPirateMessage); + getTreasureMap.addStep(inBlueMoonFirst, openChest); steps.put(2, getTreasureMap); - digUpTreasure = new DigStep(this, new WorldPoint(2999, 3383, 0), - "Dig in the middle of the cross in Falador Park, and kill the Gardener (level 4) who appears. Once killed, dig again."); + var cDigUpTreasure = new ConditionalStep(this, digUpTreasure); + cDigUpTreasure.addStep(new NpcHintArrowRequirement(NpcID.PIRATE_IRATE_GARDENER), killGardener); + steps.put(3, cDigUpTreasure); - steps.put(3, digUpTreasure); return steps; } - @Override - protected void setupRequirements() - { - sixtyCoins = new ItemRequirement("Coins", ItemCollections.COINS, 60); - spade = new ItemRequirement("Spade", ItemID.SPADE).isNotConsumed(); - pirateMessage = new ItemRequirement("Pirate message", ItemID.PIRATEMESSAGE); - chestKey = new ItemRequirement("Chest key", ItemID.CHEST_KEY); - chestKey.setTooltip("You can get another one from Redbeard Frank"); - } - @Override public List getItemRequirements() { - ArrayList reqs = new ArrayList<>(); - reqs.add(sixtyCoins); - reqs.add(spade); - - return reqs; + return List.of( + sixtyCoins, + spade, + tenBananas + ); } @Override public List getItemRecommended() { - ArrayList reqs = new ArrayList<>(); - reqs.add(new ItemRequirement("A teleport to Varrock", ItemID.POH_TABLET_VARROCKTELEPORT)); - reqs.add(new ItemRequirement("A teleport to Falador", ItemID.POH_TABLET_FALADORTELEPORT)); - reqs.add(new ItemRequirement("Bananas (obtainable in quest)", ItemID.BANANA, 10)); - - return reqs; + return List.of( + teleportVarrock, + teleportFalador + ); } @Override public List getCombatRequirements() { - return Collections.singletonList("Gardener (level 4)"); + return List.of( + "Gardener (level 4)" + ); } @Override @@ -147,22 +191,35 @@ public QuestPointReward getQuestPointReward() @Override public List getItemRewards() { - return Arrays.asList( - new ItemReward("A Gold Ring", ItemID.GOLD_RING, 1), - new ItemReward("An Emerald", ItemID.EMERALD, 1), - new ItemReward("Coins", ItemID.COINS, 450)); + return List.of( + new ItemReward("A Gold Ring", ItemID.GOLD_RING, 1), + new ItemReward("An Emerald", ItemID.EMERALD, 1), + new ItemReward("Coins", ItemID.COINS, 450) + ); } @Override public List getPanels() { - List allSteps = new ArrayList<>(); - - allSteps.add(new PanelDetails("Talk to Redbeard Frank", Collections.singletonList(speakToRedbeard), sixtyCoins)); - allSteps.addAll(smuggleRum.panelDetails()); - allSteps.add(new PanelDetails("Discover the treasure", Arrays.asList(climbStairs, openChest, readPirateMessage, - digUpTreasure), spade)); - - return allSteps; + var sections = new ArrayList(); + + sections.add(new PanelDetails("Talk to Redbeard Frank", List.of( + speakToRedbeard + ), List.of( + sixtyCoins + ))); + + sections.addAll(smuggleRum.panelDetails()); + + sections.add(new PanelDetails("Discover the treasure", List.of( + climbStairs, + openChest, + readPirateMessage, + digUpTreasure + ), List.of( + spade + ))); + + return sections; } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/piratestreasure/RumSmugglingStep.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/piratestreasure/RumSmugglingStep.java index faafa6e5a28..b4952501757 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/piratestreasure/RumSmugglingStep.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/piratestreasure/RumSmugglingStep.java @@ -26,35 +26,46 @@ import net.runelite.client.plugins.microbot.questhelper.collections.ItemCollections; import net.runelite.client.plugins.microbot.questhelper.panel.PanelDetails; -import net.runelite.client.plugins.microbot.questhelper.questhelpers.QuestHelper; import net.runelite.client.plugins.microbot.questhelper.requirements.ChatMessageRequirement; import net.runelite.client.plugins.microbot.questhelper.requirements.MesBoxRequirement; import net.runelite.client.plugins.microbot.questhelper.requirements.Requirement; import net.runelite.client.plugins.microbot.questhelper.requirements.conditional.Conditions; import net.runelite.client.plugins.microbot.questhelper.requirements.item.ItemRequirement; import net.runelite.client.plugins.microbot.questhelper.requirements.npc.DialogRequirement; +import static net.runelite.client.plugins.microbot.questhelper.requirements.util.LogicHelper.and; import net.runelite.client.plugins.microbot.questhelper.requirements.util.LogicType; import net.runelite.client.plugins.microbot.questhelper.requirements.widget.WidgetTextRequirement; import net.runelite.client.plugins.microbot.questhelper.requirements.zone.Zone; import net.runelite.client.plugins.microbot.questhelper.requirements.zone.ZoneRequirement; -import net.runelite.client.plugins.microbot.questhelper.steps.*; +import net.runelite.client.plugins.microbot.questhelper.steps.ConditionalStep; +import net.runelite.client.plugins.microbot.questhelper.steps.DetailedQuestStep; +import net.runelite.client.plugins.microbot.questhelper.steps.NpcStep; +import net.runelite.client.plugins.microbot.questhelper.steps.ObjectStep; +import net.runelite.client.plugins.microbot.questhelper.steps.QuestStep; +import java.util.ArrayList; +import java.util.List; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.InterfaceID; import net.runelite.api.gameval.ItemID; import net.runelite.api.gameval.NpcID; import net.runelite.api.gameval.ObjectID; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - public class RumSmugglingStep extends ConditionalStep { - private Zone karamjaZone1, karamjaZone2, karamjaBoat; + private final PiratesTreasure pt; + + // Zones + private Zone karamjaZone1; + private Zone karamjaZone2; + private Zone karamjaBoat; - private ItemRequirement karamjanRum, tenBananas, whiteApron, whiteApronEquipped, whiteApronHanging; + // Miscellaneous requirements + private ZoneRequirement onKaramja; - private Requirement onKaramja; + private ItemRequirement karamjanRum; + private ItemRequirement whiteApron; + private ItemRequirement whiteApronEquipped; + private ItemRequirement whiteApronHanging; private Conditions atStart; private Conditions employed; private Conditions stashedRum; @@ -66,57 +77,34 @@ public class RumSmugglingStep extends ConditionalStep private Conditions filledCrateWithBananasAndRum; private ChatMessageRequirement crateSent; private ChatMessageRequirement fillCrateWithBananasChat; - - private QuestStep talkToCustomsOfficer, getRumFromCrate, getWhiteApron, addBananasToCrate, addRumToCrate, talkToZambo, talkToLuthas, talkToLuthasAgain, goToKaramja, bringRumToRedbeard; - - public RumSmugglingStep(QuestHelper questHelper) + private Requirement haveYouCompletedyourTaskYet; + + // Steps + private QuestStep syncStep; + private QuestStep talkToCustomsOfficer; + private QuestStep getRumFromCrate; + private QuestStep getWhiteApron; + private QuestStep addBananasToCrate; + private QuestStep addRumToCrate; + private QuestStep talkToZambo; + private QuestStep talkToLuthas; + private QuestStep talkToLuthasAgain; + private QuestStep goToKaramja; + private QuestStep bringRumToRedbeard; + private MesBoxRequirement fillCrateBananas; + + public RumSmugglingStep(PiratesTreasure questHelper) { super(questHelper, new DetailedQuestStep(questHelper, "Please open Pirate Treasure's Quest Journal to sync the current quest state.")); - setupItemRequirements(); - setupZones(); - setupConditions(); - setupSteps(); - addSteps(); - } + pt = questHelper; - private void addSteps() - { - this.addStep(new Conditions(hasRumOffKaramja), bringRumToRedbeard); - this.addStep(new Conditions(verifiedAState, haveShippedRum, onKaramja), talkToCustomsOfficer); - this.addStep(new Conditions(verifiedAState, haveShippedRum, whiteApron), getRumFromCrate); - this.addStep(new Conditions(verifiedAState, haveShippedRum), getWhiteApron); - this.addStep(new Conditions(verifiedAState, filledCrateWithBananasAndRum, onKaramja), talkToLuthasAgain); - this.addStep(new Conditions(verifiedAState, stashedRum, onKaramja), addBananasToCrate); - this.addStep(new Conditions(verifiedAState, employed, karamjanRum, onKaramja), addRumToCrate); - this.addStep(new Conditions(verifiedAState, employed, onKaramja), talkToZambo); - this.addStep(new Conditions(verifiedAState, atStart, karamjanRum, onKaramja), talkToLuthas); - this.addStep(new Conditions(verifiedAState, atStart, onKaramja), talkToZambo); - this.addStep(verifiedAState, goToKaramja); - } + syncStep = this.steps.get(null); - @Override - protected void updateSteps() - { - if ((hadRumOffKaramja.check(client) && !hasRumOffKaramja.check(client)) - || lostRum.check(client)) - { - haveShippedRum.setHasPassed(false); - stashedRum.setHasPassed(false); - atStart.setHasPassed(true); - hadRumOffKaramja.setHasPassed(false); - lostRum.setHasPassed(false); - } - - if (crateSent.check(client)) - { - haveShippedRum.check(client); - employed.setHasPassed(false); - fillCrateWithBananasChat.setHasReceivedChatMessage(false); - filledCrateWithBananasAndRum.setHasPassed(false); - crateSent.setHasReceivedChatMessage(false); - } + setupZones(); + setupRequirements(); - super.updateSteps(); + setupSteps(); + addSteps(); } private void setupZones() @@ -126,18 +114,14 @@ private void setupZones() karamjaBoat = new Zone(new WorldPoint(2964, 3138, 0), new WorldPoint(2951, 3144, 1)); } - private void setupItemRequirements() + private void setupRequirements() { karamjanRum = new ItemRequirement("Karamjan rum", ItemID.KARAMJA_RUM); - tenBananas = new ItemRequirement("Banana", ItemID.BANANA, 10); whiteApron = new ItemRequirement("White apron", ItemID.WHITE_APRON); - whiteApronEquipped = new ItemRequirement("White apron", ItemID.WHITE_APRON, 1, true); + whiteApronEquipped = whiteApron.equipped(); whiteApronHanging = new ItemRequirement("White apron", ItemID.PIRATETREASURE_APRON); whiteApronHanging.addAlternates(ItemID.WHITE_APRON); - } - private void setupConditions() - { onKaramja = new ZoneRequirement(karamjaZone1, karamjaZone2, karamjaBoat); Requirement offKaramja = new ZoneRequirement(false, karamjaZone1, karamjaZone2, karamjaBoat); Requirement inPirateTreasureMenu = new WidgetTextRequirement(InterfaceID.Questjournal.TITLE, getQuestHelper().getQuest().getName()); @@ -162,16 +146,20 @@ private void setupConditions() Requirement employedFromDialog = new Conditions(new DialogRequirement("If you could fill it up with bananas, I'll pay you 30 gold.", "Have you completed your task yet?", "you should see the old crate")); employed = new Conditions(true, LogicType.OR, employedFromDialog, employedFromWidget, employedByWydinFromWidget); + // This can't be a dialog requirement because the check function doesn't do the actual checking + haveYouCompletedyourTaskYet = new WidgetTextRequirement(InterfaceID.ChatLeft.TEXT, "Have you completed your task yet?"); + Requirement stashedRumFromWidget = new Conditions(inPirateTreasureMenu, new WidgetTextRequirement(InterfaceID.Questjournal.TEXTLAYER, true, "I have hidden my")); Requirement stashedRumFromDialog = new MesBoxRequirement("You stash the rum in the crate."); Requirement stashedRumFromChat = new Conditions(new ChatMessageRequirement("There is also some rum stashed in here too.", "There's already some rum in here...", "There is some rum in here, although with no bananas to cover it. It is a little obvious.")); stashedRum = new Conditions(true, LogicType.OR, stashedRumFromDialog, stashedRumFromWidget, stashedRumFromChat, employedByWydinFromWidget); - MesBoxRequirement fillCrateBananas = new MesBoxRequirement("You fill the crate with bananas.", "You pack all your bananas into the crate."); - fillCrateBananas.setInvalidateRequirement(new ChatMessageRequirement("Have you completed your task yet?")); + var filledCrateWidget = and(inPirateTreasureMenu, new WidgetTextRequirement(InterfaceID.Questjournal.TEXTLAYER, true, "in the crate and filled it with")); + + fillCrateBananas = new MesBoxRequirement("You fill the crate with bananas.", "You pack all your bananas into the crate."); fillCrateWithBananasChat = new ChatMessageRequirement("The crate is full of bananas.", "The crate is already full."); - Requirement filledCrateWithBananas = new Conditions(false, LogicType.OR, fillCrateWithBananasChat, fillCrateBananas); + Requirement filledCrateWithBananas = new Conditions(false, LogicType.OR, fillCrateWithBananasChat, fillCrateBananas, filledCrateWidget); filledCrateWithBananasAndRum = new Conditions(true, LogicType.AND, filledCrateWithBananas, stashedRum); Requirement shippedRumFromWidget = new Conditions(inPirateTreasureMenu, new WidgetTextRequirement(InterfaceID.Questjournal.TEXTLAYER, true, "the crate has been shipped")); @@ -200,11 +188,11 @@ private void setupSteps() talkToLuthas.addDialogStep("Will you pay me for another crate full?"); addRumToCrate = new ObjectStep(getQuestHelper(), ObjectID.BANANACRATE, new WorldPoint(2943, 3151, 0), - "Put the Karamjan rum into the crate.", karamjanRum.highlighted(), tenBananas); + "Put the Karamjan rum into the crate.", karamjanRum.highlighted(), pt.tenBananas); addRumToCrate.addIcon(ItemID.KARAMJA_RUM); addBananasToCrate = new ObjectStep(getQuestHelper(), ObjectID.BANANACRATE, new WorldPoint(2943, 3151, 0), - "Right-click fill the rest of the crate with bananas, then talk to Luthas.", tenBananas); + "Right-click fill the rest of the crate with bananas, then talk to Luthas.", pt.tenBananas); talkToLuthasAgain = new NpcStep(getQuestHelper(), NpcID.LUTHAS, new WorldPoint(2938, 3154, 0), "Talk to Luthas and tell him you finished filling the crate."); @@ -228,12 +216,78 @@ private void setupSteps() karamjanRum); } - public List panelDetails() + private void addSteps() + { + this.addStep(hasRumOffKaramja, bringRumToRedbeard); + this.addStep(and(verifiedAState, haveShippedRum, onKaramja), talkToCustomsOfficer); + this.addStep(and(verifiedAState, haveShippedRum, whiteApron), getRumFromCrate); + this.addStep(and(verifiedAState, haveShippedRum), getWhiteApron); + this.addStep(and(verifiedAState, filledCrateWithBananasAndRum, onKaramja), talkToLuthasAgain); + this.addStep(and(verifiedAState, stashedRum, onKaramja), addBananasToCrate); + this.addStep(and(verifiedAState, employed, karamjanRum, onKaramja), addRumToCrate); + this.addStep(and(verifiedAState, employed, onKaramja), talkToZambo); + this.addStep(and(verifiedAState, atStart, karamjanRum, onKaramja), talkToLuthas); + this.addStep(and(verifiedAState, atStart, onKaramja), talkToZambo); + this.addStep(verifiedAState, goToKaramja); + } + + @Override + protected void updateSteps() { - List allSteps = new ArrayList<>(); + if (haveYouCompletedyourTaskYet.check(client)) + { + // When talking to Luthas, we've confirmed you have actually not filled up the crate + // with bananas. Reset the checks that mdae us think it was filled up. + // + // This can happen if the user fills the crate up with less than 10 bananas in one go. + fillCrateWithBananasChat.setHasReceivedChatMessage(false); + fillCrateBananas.setHasPassed(false); + filledCrateWithBananasAndRum.setHasPassed(false); + } + + if ((hadRumOffKaramja.check(client) && !hasRumOffKaramja.check(client)) + || lostRum.check(client)) + { + haveShippedRum.setHasPassed(false); + stashedRum.setHasPassed(false); + atStart.setHasPassed(true); + hadRumOffKaramja.setHasPassed(false); + lostRum.setHasPassed(false); + } + + if (crateSent.check(client)) + { + haveShippedRum.check(client); + employed.setHasPassed(false); + fillCrateWithBananasChat.setHasReceivedChatMessage(false); + filledCrateWithBananasAndRum.setHasPassed(false); + crateSent.setHasReceivedChatMessage(false); + } - allSteps.add(new PanelDetails("Rum smuggling", Arrays.asList(goToKaramja, talkToZambo, talkToLuthas, addRumToCrate, addBananasToCrate, talkToLuthas))); - allSteps.add(new PanelDetails("Back to Port Sarim", Arrays.asList(talkToCustomsOfficer, getWhiteApron, getRumFromCrate, bringRumToRedbeard))); - return allSteps; + super.updateSteps(); + } + + public List panelDetails() + { + List sections = new ArrayList<>(); + + sections.add(new PanelDetails("Rum smuggling", List.of( + syncStep, + goToKaramja, + talkToZambo, + talkToLuthas, + addRumToCrate, + addBananasToCrate, + talkToLuthasAgain + ))); + + sections.add(new PanelDetails("Back to Port Sarim", List.of( + talkToCustomsOfficer, + getWhiteApron, + getRumFromCrate, + bringRumToRedbeard + ))); + + return sections; } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/romeoandjuliet/RomeoAndJuliet.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/romeoandjuliet/RomeoAndJuliet.java index 4d41d8a334d..a3c07cb635a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/romeoandjuliet/RomeoAndJuliet.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/romeoandjuliet/RomeoAndJuliet.java @@ -117,7 +117,7 @@ public void setupSteps() giveLetterToRomeo = new NpcStep(this, NpcID.ROMEO, new WorldPoint(3211, 3422, 0), "Bring the letter to Romeo in Varrock Square.", letter); talkToLawrence = new NpcStep(this, NpcID.FATHER_LAWRENCE, new WorldPoint(3254, 3483, 0), "Talk to Father Lawrence in north east Varrock."); talkToLawrence.addDialogStep("Ok, thanks."); - talkToApothecary = new NpcStep(this, NpcID.APOTHECARY, new WorldPoint(3195, 3405, 0), "Bring the cadava berries to the Apothecary in south east Varrock.", cadavaBerry); + talkToApothecary = new NpcStep(this, NpcID.APOTHECARY, new WorldPoint(3195, 3405, 0), "Bring the cadava berries to the Apothecary in south west Varrock.", cadavaBerry); talkToApothecary.addDialogStep("Talk about something else."); talkToApothecary.addDialogStep("Talk about Romeo & Juliet."); goUpToJuliet2 = new ObjectStep(this, ObjectID.FAI_VARROCK_STAIRS_TALLER, new WorldPoint(3157, 3436, 0), "Bring the potion to Juliet in the house west of Varrock.", potion); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/scrambled/EggSolver.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/scrambled/EggSolver.java new file mode 100644 index 00000000000..12d28d95b3a --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/scrambled/EggSolver.java @@ -0,0 +1,206 @@ +package net.runelite.client.plugins.microbot.questhelper.helpers.quests.scrambled; + +import net.runelite.client.plugins.microbot.questhelper.QuestHelperPlugin; +import net.runelite.client.plugins.microbot.questhelper.questhelpers.QuestHelper; +import net.runelite.client.plugins.microbot.questhelper.requirements.SimpleRequirement; +import net.runelite.client.plugins.microbot.questhelper.steps.ConditionalStep; +import net.runelite.client.plugins.microbot.questhelper.steps.DetailedOwnerStep; +import net.runelite.client.plugins.microbot.questhelper.steps.DetailedQuestStep; +import net.runelite.client.plugins.microbot.questhelper.steps.QuestStep; +import net.runelite.client.plugins.microbot.questhelper.steps.widget.WidgetHighlight; +import java.awt.*; +import java.util.List; +import javax.annotation.Nullable; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Client; +import net.runelite.api.gameval.InterfaceID; +import net.runelite.api.widgets.Widget; + +@Slf4j +public class EggSolver extends DetailedOwnerStep +{ + + private ConditionalStep solvePuzzle; + + + public EggSolver(Scrambled parentHelper) + { + super(parentHelper, "Solve the puzzle by following the instructions in the overlay."); + } + + /** + * Find the widget with the given interface ID and, if it exists, look through its dynamic children for a widget + * matching the given model ID + */ + private static @Nullable Widget findWidgetByModelID(Client client, int interfaceID, int modelID) + { + var widget = client.getWidget(interfaceID); + + if (widget == null) + { + return null; + } + + var children = widget.getChildren(); + + if (children != null) + { + for (var w : children) + { + if (w != null && w.getModelId() == modelID) + { + return w; + } + } + } + + return null; + } + + @Override + public void startUp() + { + updateSteps(); + } + + @Override + protected void setupSteps() + { + var h = getQuestHelper(); + var unreachable = new DetailedQuestStep(h, "Something went wrong with the puzzle solver, please report this in the Quest Helper Discord with a screenshot of your puzzle."); + + solvePuzzle = new ConditionalStep(getQuestHelper(), unreachable); + + addEggPair(solvePuzzle, 57106, 194, 121); + addEggPair(solvePuzzle, 57109, 217, 27); + addEggPair(solvePuzzle, 57111, 260, 22); + addEggPair(solvePuzzle, 57107, 250, 41); + addEggPair(solvePuzzle, 57123, 175, 58); + addEggPair(solvePuzzle, 57125, 198, 60); + addEggPair(solvePuzzle, 57118, 288, 71); + addEggPair(solvePuzzle, 57104, 231, 71); + addEggPair(solvePuzzle, 57127, 231, 99); + addEggPair(solvePuzzle, 57115, 257, 113); + addEggPair(solvePuzzle, 57122, 273, 115); + addEggPair(solvePuzzle, 57116, 306, 119); + addEggPair(solvePuzzle, 57103, 243, 149); + addEggPair(solvePuzzle, 57120, 309, 160); + addEggPair(solvePuzzle, 57114, 269, 177); + addEggPair(solvePuzzle, 57102, 297, 217); + addEggPair(solvePuzzle, 57124, 252, 213); + addEggPair(solvePuzzle, 57126, 244, 255); + addEggPair(solvePuzzle, 57112, 199, 248); + addEggPair(solvePuzzle, 57121, 222, 199); + addEggPair(solvePuzzle, 57119, 187, 210); + addEggPair(solvePuzzle, 57105, 149, 179); + addEggPair(solvePuzzle, 57108, 209, 160); + addEggPair(solvePuzzle, 57101, 165, 141); + addEggPair(solvePuzzle, 57110, 157, 103); + addEggPair(solvePuzzle, 57117, 196, 105); + } + + protected void updateSteps() + { + startUpStep(solvePuzzle); + } + + @Override + public List getSteps() + { + return List.of( + this.solvePuzzle + ); + } + + private void addEggPair(ConditionalStep c, int modelID, int positionX, int positionY) + { + var step = new EggPieceStep(getQuestHelper(), modelID, positionX, positionY); + var requirement = new EggPieceRequirement(modelID); + + c.addStep(requirement, step); + } + + private static class EggPieceStep extends QuestStep + { + /// The rotationY required for the widget to be considered correctly rotated + private static final int REQUIRED_ROTATION = 0; + /// The width of the square we mark for the correct spot + private static final int SPOT_WIDTH = 50; + /// The height of the square we mark for the correct spot + private static final int SPOT_HEIGHT = 50; + + /// The model ID of the egg piece this step is handling + int modelID; + + /// The final correct X position for this egg piece + int positionX; + /// The final correct Y position for this egg piece + int positionY; + + public EggPieceStep(QuestHelper questHelper, int modelID, int positionX, int positionY) + { + super(questHelper, "Rotate the piece until you're prompted to move it to the correct spot. The square of the egg piece and the square marking the correct spot need to roughly line up."); + var modelHighlighter = new WidgetHighlight(InterfaceID.Jigsaw.PIECES, true); + modelHighlighter.setModelIdRequirement(57106); + this.addWidgetHighlight(modelHighlighter); + + this.modelID = modelID; + this.positionX = positionX; + this.positionY = positionY; + } + + @Override + public void makeWidgetOverlayHint(Graphics2D graphics, QuestHelperPlugin plugin) + { + super.makeWidgetOverlayHint(graphics, plugin); + var root = client.getWidget(InterfaceID.Jigsaw.UNIVERSE); + if (root == null) + { + return; + } + var rootBounds = root.getBounds(); + + var w = EggSolver.findWidgetByModelID(client, InterfaceID.Jigsaw.PIECES, modelID); + if (w == null) + { + return; + } + + // NOTE: We could hardcode Cyan here + graphics.setColor(getQuestHelper().getConfig().targetOverlayColor()); + + graphics.drawRect(w.getBounds().x, w.getBounds().y, w.getBounds().width, w.getBounds().height); + + // 256 per rotation, goal is + int rotationsLeft = (2048 - w.getRotationY()) / 256; + if (w.getRotationY() == REQUIRED_ROTATION) + { + graphics.drawString("move here", rootBounds.x + this.positionX, rootBounds.y + this.positionY); + graphics.drawRect(rootBounds.x + this.positionX, rootBounds.y + this.positionY, SPOT_WIDTH, SPOT_HEIGHT); + } + else + { + // TODO: We could technically prompt the user with exactly how many times to click the egg piece, but that's overkill imo + graphics.drawString("click to rotate " + rotationsLeft + " times", w.getBounds().x, w.getBounds().y); + } + + } + } + + private static class EggPieceRequirement extends SimpleRequirement + { + private final int modelID; + + public EggPieceRequirement(int modelID) + { + this.modelID = modelID; + } + + @Override + public boolean check(Client client) + { + var w = EggSolver.findWidgetByModelID(client, InterfaceID.Jigsaw.PIECES, modelID); + return w != null; + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/scrambled/Scrambled.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/scrambled/Scrambled.java new file mode 100644 index 00000000000..cd2246d6566 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/scrambled/Scrambled.java @@ -0,0 +1,610 @@ +/* + * Copyright (c) 2025, pajlada + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.microbot.questhelper.helpers.quests.scrambled; + +import net.runelite.client.plugins.microbot.questhelper.bank.banktab.BankSlotIcons; +import net.runelite.client.plugins.microbot.questhelper.collections.ItemCollections; +import net.runelite.client.plugins.microbot.questhelper.panel.PanelDetails; +import net.runelite.client.plugins.microbot.questhelper.questhelpers.BasicQuestHelper; +import net.runelite.client.plugins.microbot.questhelper.questinfo.QuestHelperQuest; +import net.runelite.client.plugins.microbot.questhelper.requirements.Requirement; +import net.runelite.client.plugins.microbot.questhelper.requirements.conditional.NpcCondition; +import net.runelite.client.plugins.microbot.questhelper.requirements.item.ItemRequirement; +import net.runelite.client.plugins.microbot.questhelper.requirements.npc.NpcHintArrowRequirement; +import net.runelite.client.plugins.microbot.questhelper.requirements.player.SkillRequirement; +import net.runelite.client.plugins.microbot.questhelper.requirements.quest.QuestRequirement; +import net.runelite.client.plugins.microbot.questhelper.requirements.util.Operation; +import net.runelite.client.plugins.microbot.questhelper.requirements.var.VarbitRequirement; +import net.runelite.client.plugins.microbot.questhelper.requirements.widget.WidgetPresenceRequirement; +import net.runelite.client.plugins.microbot.questhelper.requirements.zone.Zone; +import net.runelite.client.plugins.microbot.questhelper.requirements.zone.ZoneRequirement; +import net.runelite.client.plugins.microbot.questhelper.rewards.ExperienceReward; +import net.runelite.client.plugins.microbot.questhelper.rewards.QuestPointReward; +import net.runelite.client.plugins.microbot.questhelper.rewards.UnlockReward; +import net.runelite.client.plugins.microbot.questhelper.steps.*; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import net.runelite.api.gameval.InterfaceID; +import net.runelite.api.gameval.ObjectID; +import net.runelite.api.QuestState; +import net.runelite.api.Skill; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.gameval.NpcID; +import net.runelite.api.gameval.ItemID; +import net.runelite.api.gameval.VarbitID; +import static net.runelite.client.plugins.microbot.questhelper.requirements.util.LogicHelper.and; +import static net.runelite.client.plugins.microbot.questhelper.requirements.util.LogicHelper.not; +import static net.runelite.client.plugins.microbot.questhelper.requirements.util.LogicHelper.or; + +/** + * The quest guide for the "Scrambled" OSRS quest + */ +public class Scrambled extends BasicQuestHelper +{ + NpcStep startQuest; + NpcStep inspectEggAfterItFell; + NpcStep talkToKing; + ConditionalStep gatherTheMen; + NpcStep talkToGatheredMen; + ConditionalStep collectAllEggs; + NpcStep judgeEggs; + NpcStep panicWithKingsMen; + ConditionalStep cPutEggBackTogether; + QuestStep finishQuest; + + ItemRequirement combatGear; + ItemRequirement antifireShield; + ItemRequirement bowlOfWater; + ItemRequirement twoPlanks; + ItemRequirement sixNails; + ItemRequirement saw; + ItemRequirement hammer; + private ItemStep getEmptyBowl; + private VarbitRequirement acatzinAgreedToHelp; + private VarbitRequirement acatzinAgreedToFixWhetstone; + private VarbitRequirement acatzinhasFixedWhetstone; + private VarbitRequirement stillNeedToHelpAcatzin; + private ItemRequirement emptyBowl; + private ItemRequirement acatzinsDamagedAxe; + private ItemRequirement acatzinsRepairedAxe; + private Zone dragonCave; + private VarbitRequirement canStillTakeNails; + private VarbitRequirement kauayotlNeedToRepairCart; + private VarbitRequirement kauayotlhasRepairedCart; + private VarbitRequirement stillNeedToHelpKauayotl; + private ItemRequirement damianaLeaves; + private VarbitRequirement nezketiAgreedToHelp; + private ItemRequirement damianaWater; + private ItemRequirement damianaTea; + private ItemRequirement emptyCup; + private ItemRequirement cupOfDamianaTea; + private VarbitRequirement needToClickLargeEggToSpawnChicken; + private VarbitRequirement needToSpawnRedDragon; + private VarbitRequirement hasKilledRedDragon; + private VarbitRequirement hasTurnedInDragonEgg; + private VarbitRequirement needToSpawnJaguar; + private VarbitRequirement hasTurnedInJaguarEgg; + private VarbitRequirement hasTurnedInChickenEgg; + private ItemRequirement largeEgg; + private ItemRequirement jaguarEgg; + private ItemRequirement dragonEgg; + private SkillRequirement has40Agi; + private ZoneRequirement nearCaveEntrance; + private ZoneRequirement inDragonCave; + private Zone nearCaveEntranceZone; + private WidgetPresenceRequirement isPuzzleOpen; + private NpcStep acatzinTalkToBlacksmith; + private ObjectStep acatzinFixWhetstone; + private ObjectStep acatzinRepairAxe; + private ItemStep acatzinGetSaw; + private NpcStep acatzinReturnRepairedAxe; + private NpcStep talkToKauayotl; + private NpcStep talkToKauayotlAgain; + private ObjectStep kauayotlRepairCart; + private ItemStep getPlanks; + private ObjectStep getDamianaLeaves; + private NpcStep giveTeaToNezketi; + private NpcStep talkToNezketi; + private ObjectStep fillEmptyBowlWithWater; + private DetailedQuestStep mixWaterAndDamianaLeaves; + private ObjectStep boilDamianaWater; + private DetailedQuestStep pourTeaIntoCup; + private ConditionalStep cReturnToTempleWithEggs; + private ConditionalStep cCollectLargeEgg; + private ConditionalStep cCollectDragonEgg; + private ConditionalStep cCollectJaguarEgg; + private NpcStep talkToAcatzin; + private VarbitRequirement stillNeedToHelpNezketi; + + @Override + public Map loadSteps() + { + initializeRequirements(); + setupRequirements(); + setupSteps(); + + var steps = new HashMap(); + + steps.put(0, startQuest); + steps.put(2, startQuest); + steps.put(4, startQuest); + steps.put(6, inspectEggAfterItFell); + steps.put(8, talkToKing); + steps.put(10, talkToKing); + steps.put(12, talkToKing); + steps.put(14, gatherTheMen); + steps.put(16, talkToGatheredMen); + steps.put(18, collectAllEggs); + steps.put(19, collectAllEggs); + + // has turned in all eggs + steps.put(20, judgeEggs); + + steps.put(22, panicWithKingsMen); + steps.put(24, cPutEggBackTogether); + steps.put(26, finishQuest); + steps.put(28, finishQuest); + + return steps; + } + + @Override + protected void setupZones() + { + dragonCave = new Zone(5012); + + nearCaveEntranceZone = new Zone(new WorldPoint(1277, 3134, 0), new WorldPoint(1299, 3141, 0)); + } + + @Override + protected void setupRequirements() + { + acatzinAgreedToHelp = new VarbitRequirement(VarbitID.SCRAMBLED_KINGS_MAN_3, 2); + acatzinAgreedToFixWhetstone = new VarbitRequirement(VarbitID.SCRAMBLED_KINGS_MAN_3, 3); + acatzinhasFixedWhetstone = new VarbitRequirement(VarbitID.SCRAMBLED_KINGS_MAN_3, 4); + stillNeedToHelpAcatzin = new VarbitRequirement(VarbitID.SCRAMBLED_KINGS_MAN_3, 7, Operation.LESS); + + canStillTakeNails = new VarbitRequirement(VarbitID.SCRAMBLED_NAILS_GIVEN, 0); + kauayotlNeedToRepairCart = new VarbitRequirement(VarbitID.SCRAMBLED_KINGS_MAN_2, 2); + kauayotlhasRepairedCart = new VarbitRequirement(VarbitID.SCRAMBLED_KINGS_MAN_2, 3); + stillNeedToHelpKauayotl = new VarbitRequirement(VarbitID.SCRAMBLED_KINGS_MAN_2, 7, Operation.LESS); + nezketiAgreedToHelp = new VarbitRequirement(VarbitID.SCRAMBLED_KINGS_MAN_1, 2); + // probably no longer needed, we use it as the "conditional fallback step" + stillNeedToHelpNezketi = new VarbitRequirement(VarbitID.SCRAMBLED_KINGS_MAN_1, 7, Operation.LESS); + needToClickLargeEggToSpawnChicken = new VarbitRequirement(VarbitID.SCRAMBLED_REPLACEMENT_EGG_3, 1, Operation.GREATER_EQUAL); + + needToSpawnRedDragon = new VarbitRequirement(VarbitID.SCRAMBLED_REPLACEMENT_EGG_2, 1, Operation.GREATER_EQUAL); + hasKilledRedDragon = new VarbitRequirement(VarbitID.SCRAMBLED_REPLACEMENT_EGG_2, 3); + hasTurnedInDragonEgg = new VarbitRequirement(VarbitID.SCRAMBLED_REPLACEMENT_EGG_2, 4); + + needToSpawnJaguar = new VarbitRequirement(VarbitID.SCRAMBLED_REPLACEMENT_EGG_1, 1, Operation.GREATER_EQUAL); + hasTurnedInJaguarEgg = new VarbitRequirement(VarbitID.SCRAMBLED_REPLACEMENT_EGG_1, 4); + + hasTurnedInChickenEgg = new VarbitRequirement(VarbitID.SCRAMBLED_REPLACEMENT_EGG_3, 4); + + has40Agi = new SkillRequirement(Skill.AGILITY, 40); + nearCaveEntrance = new ZoneRequirement(nearCaveEntranceZone); + inDragonCave = new ZoneRequirement(dragonCave); + isPuzzleOpen = new WidgetPresenceRequirement(InterfaceID.Jigsaw.BACKGROUND); + + bowlOfWater = new ItemRequirement("Bowl of water", ItemID.BOWL_WATER).canBeObtainedDuringQuest().showConditioned(stillNeedToHelpNezketi); + // NOTE: Confirmed it has to be two basic planks + twoPlanks = new ItemRequirement("Plank", ItemID.WOODPLANK, 2).canBeObtainedDuringQuest().showConditioned(stillNeedToHelpKauayotl); + // NOTE: Confirmed other nails work + sixNails = new ItemRequirement("Nail", ItemCollections.NAILS, 6).canBeObtainedDuringQuest().showConditioned(stillNeedToHelpKauayotl); + saw = new ItemRequirement("Saw", ItemCollections.SAW).canBeObtainedDuringQuest().showConditioned(stillNeedToHelpKauayotl); + hammer = new ItemRequirement("Hammer", ItemCollections.HAMMER).canBeObtainedDuringQuest().showConditioned(or(stillNeedToHelpAcatzin, stillNeedToHelpKauayotl)); + + // If the user does not have a bowl of water with them, then the empty bowl requirement will be used in the middle + emptyBowl = new ItemRequirement("Empty bowl", ItemID.BOWL_EMPTY); + + acatzinsDamagedAxe = new ItemRequirement("Acatzin's damaged axe", ItemID.SCRAMBLED_AXE_DAMAGED); + acatzinsRepairedAxe = new ItemRequirement("Acatzin's axe", ItemID.SCRAMBLED_AXE_REPAIRED); + + combatGear = new ItemRequirement("Combat gear", -1, -1); + combatGear.setDisplayItemId(BankSlotIcons.getCombatGear()); + + antifireShield = new ItemRequirement("Antifire shield", ItemCollections.ANTIFIRE_SHIELDS); + + damianaLeaves = new ItemRequirement("Damiana leaves", ItemID.DAMIANA_LEAVES); + + damianaWater = new ItemRequirement("Damiana water", ItemID.BOWL_DAMIANA_WATER); + damianaTea = new ItemRequirement("Damiana tea", ItemID.BOWL_DAMIANA_TEA); + + emptyCup = new ItemRequirement("Empty cup", ItemID.CUP_EMPTY); + cupOfDamianaTea = new ItemRequirement("Cup of damiana tea", ItemID.CUP_DAMIANA_TEA); + + + largeEgg = new ItemRequirement("Large egg", ItemID.SCRAMBLED_CHICKEN_EGG).hideConditioned(hasTurnedInChickenEgg); + jaguarEgg = new ItemRequirement("Jaguar egg", ItemID.SCRAMBLED_JAGUAR_EGG).hideConditioned(hasTurnedInJaguarEgg); + dragonEgg = new ItemRequirement("Dragon egg", ItemID.SCRAMBLED_DRAGON_EGG).hideConditioned(hasTurnedInDragonEgg); + } + + public void setupSteps() + { + startQuest = new NpcStep(this, NpcID.SCRAMBLED_ALAN, new WorldPoint(1247, 3167, 0), "Talk to Alan in Tal Teok Temple, north of Tal Teklan, to start the quest."); + startQuest.addDialogStep("Yes."); + startQuest.addDialogStep("Of course!"); + + inspectEggAfterItFell = new NpcStep(this, NpcID.SCRAMBLED_EGG_DEAD, new WorldPoint(1247, 3170, 0), "Inspect the egg in front of you."); + + talkToKing = new NpcStep(this, NpcID.SCRAMBLED_KING, new WorldPoint(1224, 3119, 0), "Head south into Tal Teklan and talk to King in the pub."); + talkToKing.addDialogSteps("Are you the king?"); + talkToKing.addDialogStep(Pattern.compile(".* fell off a wall!")); + + getEmptyBowl = new ItemStep(this, new WorldPoint(1229, 3119, 0), "Get an empty bowl from the pub, will need it later. If it's not there, hop worlds or" + + " wait for it to spawn again.", emptyBowl); + + talkToAcatzin = new NpcStep(this, NpcID.SCRAMBLED_KINGS_MAN_3, new WorldPoint(1228, 3117, 0), "Talk to Acatzin inside the Tal Teklan pub."); + talkToAcatzin.addDialogStep("I can talk to the blacksmith."); + + acatzinTalkToBlacksmith = new NpcStep(this, NpcID.SCRAMBLED_BLACKSMITH, new WorldPoint(1209, 3109, 0), "Talk to the Blacksmith west of the Tal Teklan pub."); + + var acatzinGetHammer = new ItemStep(this, new WorldPoint(1207, 3108, 0), "Get the hammer from the table. If it's not there, hop worlds or wait for it" + + " to spawn again.", hammer); + + var acatzinGetNails = new ObjectStep(this, ObjectID.SCRAMBLED_WORKBENCH, new WorldPoint(1210, 3112, 0), "Get some nails from the blacksmith's workbench, we'll need them later."); + acatzinGetNails.addDialogStep("Take the nails."); + + acatzinGetSaw = new ItemStep(this, new WorldPoint(1212, 3093, 0), "Get the Saw from the house to the south, we'll need it later. If it's not there, " + + "hop worlds or wait for it to spawn again.", saw); + + acatzinFixWhetstone = new ObjectStep(this, ObjectID.SCRAMBLED_WHETSTONE_BROKEN_OP, new WorldPoint(1211, 3108, 0), "Fix the whetstone."); + acatzinFixWhetstone.addDialogStep("Yes."); + + acatzinFixWhetstone.addSubSteps(acatzinGetHammer); + acatzinFixWhetstone.addSubSteps(acatzinGetNails); + + var cAcatzinFixWhetstone = new ConditionalStep(this, acatzinFixWhetstone); + cAcatzinFixWhetstone.addStep(canStillTakeNails, acatzinGetNails); + cAcatzinFixWhetstone.addStep(not(hammer), acatzinGetHammer); + + var acatzinTalkToBlacksmithAgain = new NpcStep(this, NpcID.SCRAMBLED_BLACKSMITH, new WorldPoint(1209, 3109, 0), "Talk to the Blacksmith again after fixing the whetstone to receive a damaged axe."); + + acatzinRepairAxe = new ObjectStep(this, ObjectID.SCRAMBLED_WHETSTONE_FIXED_OP, new WorldPoint(1211, 3108, 0), "Repair the axe on the whetstone.", acatzinsDamagedAxe); + acatzinRepairAxe.addDialogStep("Yes."); + acatzinRepairAxe.addSubSteps(acatzinTalkToBlacksmithAgain); + + var cAcatzinRepairAxe = new ConditionalStep(this, acatzinRepairAxe); + cAcatzinRepairAxe.addStep(not(acatzinsDamagedAxe), acatzinTalkToBlacksmithAgain); + + acatzinReturnRepairedAxe = new NpcStep(this, NpcID.SCRAMBLED_KINGS_MAN_3, new WorldPoint(1228, 3117, 0), "Return the repaired axe to Acatzin in the Tel Teklan pub.", acatzinsRepairedAxe); + + + var helpAcatzin = new ConditionalStep(this, talkToAcatzin); + helpAcatzin.addStep(and(not(emptyBowl), not(bowlOfWater)), getEmptyBowl); + helpAcatzin.addStep(acatzinAgreedToHelp, acatzinTalkToBlacksmith); + helpAcatzin.addStep(acatzinAgreedToFixWhetstone, cAcatzinFixWhetstone); + helpAcatzin.addStep(and(acatzinsRepairedAxe, not(saw)), acatzinGetSaw); + helpAcatzin.addStep(acatzinsRepairedAxe, acatzinReturnRepairedAxe); + helpAcatzin.addStep(acatzinhasFixedWhetstone, cAcatzinRepairAxe); + + + talkToKauayotl = new NpcStep(this, NpcID.SCRAMBLED_KINGS_MAN_2, new WorldPoint(1251, 3104, 0), "Talk to Kauayotl at the east entrance of Tal Teklan." + , hammer, saw, sixNails, twoPlanks); + talkToKauayotl.addDialogStep("I see. Well, maybe I can help out with that?"); + + talkToKauayotlAgain = new NpcStep(this, NpcID.SCRAMBLED_KINGS_MAN_2, new WorldPoint(1251, 3104, 0), "Talk to Kauayotl again after repairing his cart."); + + kauayotlRepairCart = new ObjectStep(this, ObjectID.SCRAMBLED_CART_BROKEN_OP, new WorldPoint(1250, 3107, 0), "Repair Kauayotl's cart.", hammer, saw, sixNails, twoPlanks); + kauayotlRepairCart.addDialogStep("Yes."); + + getPlanks = new ItemStep(this, new WorldPoint(1238, 3076, 0), "Go south-west of Kauayotl and pick up two planks from besides the lake. If it's not " + + "there, hop worlds or wait for it to spawn again.", twoPlanks); + + getDamianaLeaves = new ObjectStep(this, ObjectID.DAMIANA_SHRUB, new WorldPoint(1250, 3110, 0), "Pick the damiana bush at the east entrance of Tal " + + "Teklan for some damiana leaves, we'll need these later."); + + var helpKauayotl = new ConditionalStep(this, talkToKauayotl); + helpKauayotl.addStep(kauayotlhasRepairedCart, talkToKauayotlAgain); + helpKauayotl.addStep(not(hammer), acatzinGetHammer); + helpKauayotl.addStep(not(saw), acatzinGetSaw); + helpKauayotl.addStep(not(damianaLeaves), getDamianaLeaves); + helpKauayotl.addStep(not(twoPlanks), getPlanks); + // helpKauayotl.addStep(not(onePlank), getPlanks); + helpKauayotl.addStep(kauayotlNeedToRepairCart, kauayotlRepairCart); + + + talkToNezketi = new NpcStep(this, NpcID.SCRAMBLED_KINGS_MAN_1, new WorldPoint(1224, 3105, 0), "Talk to Nezketi in the Tal Teklan temple."); + talkToNezketi.addDialogStep("I can get you some tea."); + + fillEmptyBowlWithWater = new ObjectStep(this, ObjectID.FORTIS_WATER_PUMP, new WorldPoint(1242, 3097, 0), "Fill your empty bowl with water at the water pump near the eastern entrance of Tal Teklan.", emptyBowl.highlighted()); + fillEmptyBowlWithWater.addIcon(ItemID.BOWL_EMPTY); + + mixWaterAndDamianaLeaves = new ItemStep(this, "Mix damiana leaves with your bowl of water.", damianaLeaves.highlighted(), bowlOfWater.highlighted()); + + boilDamianaWater = new ObjectStep(this, ObjectID.STOVE_CLAY01_TALKASTI01_NOOP, "Use your bowl of damiana water on the stove in the east part of Tal Teklan to boil it.", damianaWater.highlighted()); + boilDamianaWater.addIcon(ItemID.BOWL_DAMIANA_WATER); + + pourTeaIntoCup = new DetailedQuestStep(this, "Pour the damiana tea from your bowl into the empty cup", damianaTea.highlighted(), emptyCup.highlighted()); + + var cMakeTea = new ConditionalStep(this, getDamianaLeaves); + cMakeTea.addStep(and(damianaTea, emptyCup), pourTeaIntoCup); + cMakeTea.addStep(damianaWater, boilDamianaWater); + cMakeTea.addStep(not(emptyCup), talkToNezketi); + cMakeTea.addStep(and(damianaLeaves, bowlOfWater), mixWaterAndDamianaLeaves); + cMakeTea.addStep(not(bowlOfWater), fillEmptyBowlWithWater); + cMakeTea.addStep(not(emptyBowl), getEmptyBowl); + + giveTeaToNezketi = new NpcStep(this, NpcID.SCRAMBLED_KINGS_MAN_1, new WorldPoint(1224, 3105, 0), "Return to Nezketi in the Tal Teklan temple and give him the cup of damiana tea.", cupOfDamianaTea); + + var helpNezketi = new ConditionalStep(this, talkToNezketi); + helpNezketi.addStep(cupOfDamianaTea, giveTeaToNezketi); + helpNezketi.addStep(nezketiAgreedToHelp, cMakeTea); + + gatherTheMen = new ConditionalStep(this, helpNezketi); + gatherTheMen.addStep(stillNeedToHelpAcatzin, helpAcatzin); + gatherTheMen.addStep(stillNeedToHelpKauayotl, helpKauayotl); + + var anyOfTheKingsMen = new int[]{ + NpcID.SCRAMBLED_KINGS_MAN_1, + NpcID.SCRAMBLED_KINGS_MAN_2, + NpcID.SCRAMBLED_KINGS_MAN_3, + }; + + talkToGatheredMen = new NpcStep(this, anyOfTheKingsMen, new WorldPoint(1247, 3166, 0), "Return to the temple north of Tal Teklan and speak with one of King's men.", true); + + var collectLargeEgg = new ObjectStep(this, ObjectID.SCRAMBLED_CHICKEN_EGGS_OP, new WorldPoint(1238, 3137, 0), "Search the Eggs to get a Large egg."); + var collectLargeEggSpawnChicken = new ObjectStep(this, ObjectID.SCRAMBLED_CHICKEN_EGGS_OP, new WorldPoint(1238, 3137, 0), ""); + + var fightLargeChicken = new NpcStep(this, NpcID.SCRAMBLED_CHICKEN, new WorldPoint(1239, 3137, 0), "Kill the large chicken."); + var largeChickenNearby = new NpcHintArrowRequirement(NpcID.SCRAMBLED_CHICKEN); + + cCollectLargeEgg = new ConditionalStep(this, collectLargeEgg, "Fetch the Large egg from the chicken coop south of the large temple. Be ready to fight" + + " a level 17 chicken."); + cCollectLargeEgg.addStep(largeChickenNearby, fightLargeChicken); + cCollectLargeEgg.addStep(needToClickLargeEggToSpawnChicken, collectLargeEggSpawnChicken); + + var useDragonShortcut = new ObjectStep(this, ObjectID.TLATI_NORTH_RIVER_LOG_BALANCE_1, new WorldPoint(1283, 3146, 0), "Step across the log balance towards the dragon cave.", has40Agi); + + var enterDragonCave = new ObjectStep(this, ObjectID.TLATI_DRAGON_NEST_CAVE_ENTRY, new WorldPoint(1288, 3133, 0), "", List.of(), List.of(antifireShield)); + + // does protect from melee avoid all damage? + + var spawnDragonFromEgg = new ObjectStep(this, ObjectID.SCRAMBLED_DRAGON_EGGS_OP, new WorldPoint(1259, 9482, 0), "Search the Eggs in the dragon cave."); + + var fightRedDragon = new NpcStep(this, NpcID.SCRAMBLED_DRAGON, new WorldPoint(1257, 9482, 0), "Fight the red dragon. You can safespot it by standing " + + "south-east of the Eggs."); + fightRedDragon.addSafeSpots(new WorldPoint(1260, 9481, 0)); + var redDragonNearby = new NpcHintArrowRequirement(NpcID.SCRAMBLED_DRAGON); + var collectDragonEgg = new ObjectStep(this, ObjectID.SCRAMBLED_DRAGON_EGGS_OP, new WorldPoint(1259, 9482, 0), "Search the Eggs in the " + + "south-east of the dragon cave to collect your Dragon egg.", List.of(), List.of(antifireShield)); + + cCollectDragonEgg = new ConditionalStep(this, enterDragonCave, "Fetch the Dragon egg from the dragon cave south-east of the large temple, across " + + "the river. Be ready to fight a Red dragon."); + cCollectDragonEgg.addStep(and(inDragonCave, hasKilledRedDragon), collectDragonEgg); + cCollectDragonEgg.addStep(and(inDragonCave, redDragonNearby), fightRedDragon); + cCollectDragonEgg.addStep(and(inDragonCave, needToSpawnRedDragon), spawnDragonFromEgg); + cCollectDragonEgg.addStep(and(has40Agi, not(nearCaveEntrance)), useDragonShortcut); + + var exitDragonCave = new ObjectStep(this, ObjectID.TLATI_DRAGON_NEST_CAVE_EXIT, new WorldPoint(1244, 9528, 0), "Exit the dragon's cave."); + + var spawnJaguarFromEgg = new ObjectStep(this, ObjectID.SCRAMBLED_JAGUAR_EGGS_OP, new WorldPoint(1332, 3122, 0), ""); + + var fightJaguar = new NpcStep(this, NpcID.SCRAMBLED_JAGUAR, new WorldPoint(1329, 3122, 0), "Fight the Jaguar. You can safespot it by standing just " + + "north east of the camp."); + fightJaguar.addSafeSpots(new WorldPoint(1337, 3128, 0)); + var jaguarNearby = new NpcHintArrowRequirement(NpcID.SCRAMBLED_JAGUAR); + + var collectJaguarEgg = new ObjectStep(this, ObjectID.SCRAMBLED_JAGUAR_EGGS_OP, new WorldPoint(1332, 3122, 0), "Search the Eggs at the camp, east of the dragon age, and collect your Jaguar egg."); + + cCollectJaguarEgg = new ConditionalStep(this, collectJaguarEgg, "Fetch the Jaguar egg from the tent, east of the dragon cave. Be ready to fight a " + + "level 88 jaguar."); + cCollectJaguarEgg.addStep(inDragonCave, exitDragonCave); + cCollectJaguarEgg.addStep(jaguarNearby, fightJaguar); + cCollectJaguarEgg.addStep(needToSpawnJaguar, spawnJaguarFromEgg); + + var returnToTempleWithEggs = new NpcStep(this, anyOfTheKingsMen, new WorldPoint(1247, 3166, 0), "Return to the temple north of Tal Teklan with the egg replacements and speak with one of \"King\"'s men.", true, largeEgg, dragonEgg, jaguarEgg); + + var returnToTempleWithDragonEgg = new NpcStep(this, NpcID.SCRAMBLED_KINGS_MAN_2, new WorldPoint(1247, 3166, 0), "Give Kauayotl the Dragon egg.", largeEgg, dragonEgg, jaguarEgg); + var returnToTempleWithJaguarEgg = new NpcStep(this, NpcID.SCRAMBLED_KINGS_MAN_1, new WorldPoint(1247, 3166, 0), "Give Nezketi the Jaguar egg.", largeEgg, dragonEgg, jaguarEgg); + var returnToTempleWithChickenEgg = new NpcStep(this, NpcID.SCRAMBLED_KINGS_MAN_3, new WorldPoint(1247, 3166, 0), "Give Acatzin the Large egg.", largeEgg, dragonEgg, jaguarEgg); + + cReturnToTempleWithEggs = new ConditionalStep(this, returnToTempleWithEggs, "Give the eggs to King's men at the Tal Teok Temple, north of Tal Teklan."); + cReturnToTempleWithEggs.addStep(inDragonCave, exitDragonCave); + cReturnToTempleWithEggs.addStep(not(hasTurnedInDragonEgg), returnToTempleWithDragonEgg); + cReturnToTempleWithEggs.addStep(not(hasTurnedInJaguarEgg), returnToTempleWithJaguarEgg); + cReturnToTempleWithEggs.addStep(not(hasTurnedInChickenEgg), returnToTempleWithChickenEgg); + + collectAllEggs = new ConditionalStep(this, cReturnToTempleWithEggs); + collectAllEggs.addStep(and(not(largeEgg), not(hasTurnedInChickenEgg)), cCollectLargeEgg); + collectAllEggs.addStep(and(not(dragonEgg), not(hasTurnedInDragonEgg)), cCollectDragonEgg); + collectAllEggs.addStep(and(not(jaguarEgg), not(hasTurnedInJaguarEgg)), cCollectJaguarEgg); + + judgeEggs = new NpcStep(this, anyOfTheKingsMen, new WorldPoint(1247, 3166, 0), "Return to the large temple north of Tal Teklan and talk with one of King's men to evaluate the Humphrey Dumphrey alternatives.", true); + + // all eggs went from 4 -> 5 when they fell and broke + // and quest state went from 20 -> 22 + + panicWithKingsMen = new NpcStep(this, anyOfTheKingsMen, new WorldPoint(1247, 3166, 0), "Talk to one of King's men to figure out how to solve the broken-egg conundrum.", true); + + var putEggBackTogether = new NpcStep(this, NpcID.SCRAMBLED_EGG_FIX, new WorldPoint(1244,3168, 0), "Inspect the egg."); + + var puzzleSolver = new PuzzleWrapperStep(this, new EggSolver(this)); + + cPutEggBackTogether = new ConditionalStep(this, putEggBackTogether, "Put Lumpty Mumpty back together again."); + cPutEggBackTogether.addStep(isPuzzleOpen, puzzleSolver); + + finishQuest = new NpcStep(this, anyOfTheKingsMen, new WorldPoint(1247, 3166, 0), "Talk to one of King's men to finish the quest.", true); + } + + @Override + public List getItemRequirements() + { + return List.of( + bowlOfWater, + twoPlanks, + sixNails, + saw, + hammer, + combatGear + ); + } + + @Override + public List getItemRecommended() + { + return List.of( + antifireShield + ); + } + + @Override + public List getGeneralRecommended() + { + return List.of( + combatGear + ); + } + + @Override + public List getGeneralRequirements() + { + return List.of( + new QuestRequirement(QuestHelperQuest.CHILDREN_OF_THE_SUN, QuestState.FINISHED), + new SkillRequirement(Skill.CONSTRUCTION, 38), // TODO: boostable? + new SkillRequirement(Skill.COOKING, 36), // TODO: boostable? + new SkillRequirement(Skill.SMITHING, 35) // TODO: boostable? + ); + } + + @Override + public List getCombatRequirements() + { + return List.of( + "Large chicken (lvl 16)", + "Black jaguar (lvl 88)", + "Red dragon (lvl 106, can be safespotted)" + ); + } + + @Override + public QuestPointReward getQuestPointReward() + { + return new QuestPointReward(1); + } + + @Override + public List getExperienceRewards() + { + return List.of( + new ExperienceReward(Skill.CONSTRUCTION, 5000), + new ExperienceReward(Skill.COOKING, 5000), + new ExperienceReward(Skill.SMITHING, 5000) + ); + } + + @Override + public List getUnlockRewards() + { + return List.of( + new UnlockReward("Your very own egg") + ); + } + + @Override + public List getPanels() + { + var panels = new ArrayList(); + + panels.add(new PanelDetails("Humphrey Dumphrey sat on a wall", List.of( + startQuest + ), List.of( + // Requirements + ), List.of( + // Recommended + ))); + + panels.add(new PanelDetails("Wumty Scrumpty had a great fall", List.of( + inspectEggAfterItFell, + talkToKing + ), List.of( + // Requirements + ), List.of( + // Recommended + ))); + + panels.add(new PanelDetails("All the king's horses and all the king's men...", List.of( + getEmptyBowl, + + // Help Acatzin + talkToAcatzin, + acatzinTalkToBlacksmith, + acatzinFixWhetstone, + acatzinRepairAxe, + acatzinGetSaw, + acatzinReturnRepairedAxe, + + // Help Kauayotl + getDamianaLeaves, + getPlanks, + talkToKauayotl, + kauayotlRepairCart, + talkToKauayotlAgain, + + // Help Nezketi + talkToNezketi, + fillEmptyBowlWithWater, + mixWaterAndDamianaLeaves, + boilDamianaWater, + pourTeaIntoCup, + giveTeaToNezketi, + + talkToGatheredMen + ), List.of( + bowlOfWater, + twoPlanks, + sixNails, + saw, + hammer + // Requirements + ), List.of( + // Recommended + ))); + + panels.add(new PanelDetails("Couldn't put Bumpty Numpty back together again...", List.of( + cCollectLargeEgg, + cCollectDragonEgg, + cCollectJaguarEgg, + + cReturnToTempleWithEggs, + judgeEggs, + panicWithKingsMen, + cPutEggBackTogether, + finishQuest + ), List.of( + combatGear + ), List.of( + antifireShield + ))); + + return panels; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/shadowsofcustodia/ShadowsOfCustodia.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/shadowsofcustodia/ShadowsOfCustodia.java new file mode 100644 index 00000000000..2b0db258100 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/shadowsofcustodia/ShadowsOfCustodia.java @@ -0,0 +1,407 @@ +/* + * Copyright (c) 2025, pajlada + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.microbot.questhelper.helpers.quests.shadowsofcustodia; + +import net.runelite.client.plugins.microbot.questhelper.bank.banktab.BankSlotIcons; +import net.runelite.client.plugins.microbot.questhelper.collections.ItemCollections; +import net.runelite.client.plugins.microbot.questhelper.panel.PanelDetails; +import net.runelite.client.plugins.microbot.questhelper.questhelpers.BasicQuestHelper; +import net.runelite.client.plugins.microbot.questhelper.questinfo.QuestHelperQuest; +import net.runelite.client.plugins.microbot.questhelper.requirements.Requirement; +import net.runelite.client.plugins.microbot.questhelper.requirements.conditional.Conditions; +import net.runelite.client.plugins.microbot.questhelper.requirements.item.ItemRequirement; +import net.runelite.client.plugins.microbot.questhelper.requirements.item.TeleportItemRequirement; +import net.runelite.client.plugins.microbot.questhelper.requirements.player.FreeInventorySlotRequirement; +import net.runelite.client.plugins.microbot.questhelper.requirements.quest.QuestRequirement; +import static net.runelite.client.plugins.microbot.questhelper.requirements.util.LogicHelper.not; +import net.runelite.client.plugins.microbot.questhelper.requirements.util.Operation; +import net.runelite.client.plugins.microbot.questhelper.requirements.var.VarbitRequirement; +import net.runelite.client.plugins.microbot.questhelper.requirements.zone.Zone; +import net.runelite.client.plugins.microbot.questhelper.requirements.zone.ZoneRequirement; +import net.runelite.client.plugins.microbot.questhelper.rewards.ExperienceReward; +import net.runelite.client.plugins.microbot.questhelper.rewards.QuestPointReward; +import net.runelite.client.plugins.microbot.questhelper.rewards.UnlockReward; +import net.runelite.client.plugins.microbot.questhelper.steps.ConditionalStep; +import net.runelite.client.plugins.microbot.questhelper.steps.DetailedQuestStep; +import net.runelite.client.plugins.microbot.questhelper.steps.NpcStep; +import net.runelite.client.plugins.microbot.questhelper.steps.ObjectStep; +import net.runelite.client.plugins.microbot.questhelper.steps.QuestStep; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import net.runelite.api.QuestState; +import net.runelite.api.Skill; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.gameval.ItemID; +import net.runelite.api.gameval.NpcID; +import net.runelite.api.gameval.ObjectID; +import net.runelite.api.gameval.VarbitID; + +/** + * The quest guide for the "Shadows of Custodia" OSRS quest + */ +public class ShadowsOfCustodia extends BasicQuestHelper +{ + // Item requirements + ItemRequirement fishingRod; + ItemRequirement hammer; + ItemRequirement fourMapleLogs; + ItemRequirement fourWillowLongbows; + + // Item recommendations + ItemRequirement combatGear; + ItemRequirement energyOrStaminas; + TeleportItemRequirement startTeleport; + + // Miscellaneous requirements + VarbitRequirement needToTalkToCitizen; + VarbitRequirement needToTalkToBarkeep; + VarbitRequirement needToTalkToShopkeep; + VarbitRequirement needToTalkToIctus; + Conditions needToFishClothFromLog; + VarbitRequirement needsToHandInWillowLongbows; + VarbitRequirement needToReinforceWall; + FreeInventorySlotRequirement oneFreeInventorySlot; + + // Zones + Zone caveRegion01; + Zone caveRegion02; + Zone caveRegion03; + Zone upstairsOfParentsHouse; + + // Zone requirements + ZoneRequirement outsideOfCave; + ZoneRequirement isUpstairsOfParentsHouse; + + // Steps + ObjectStep startQuest; + ConditionalStep findOutAboutTheMissingPeople; + NpcStep talkToTheParents; + ObjectStep inspectWall; + ObjectStep inspectPuddle; + ObjectStep inspectPlank; + NpcStep returnToTheParentsWithCloth; + ObjectStep enterCave; + NpcStep talkToInjuredBoyInCave; + ConditionalStep findInjuredBoys; + NpcStep returnToTheParentsWithBoy; + ObjectStep reinforceWall; + NpcStep informCaptainAboutMissingPeople; + ConditionalStep reinforceWallAndTalkToCaptain; + NpcStep talkToEtzAboutWhatTheyRemember; + ConditionalStep talkToBoysUpstairs; + ObjectStep enterCave2; + NpcStep talkToAntos; + ConditionalStep saveAntos; + ConditionalStep talkToAntosAfterSavingHim; + NpcStep finishQuest; + + @Override + public Map loadSteps() + { + setupZones(); + setupRequirements(); + setupSteps(); + var steps = new HashMap(); + + steps.put(0, startQuest); + steps.put(2, findOutAboutTheMissingPeople); + steps.put(4, talkToTheParents); + steps.put(5, inspectWall); + steps.put(6, inspectPuddle); + steps.put(8, inspectPlank); + steps.put(9, returnToTheParentsWithCloth); + steps.put(10, findInjuredBoys); + steps.put(12, findInjuredBoys); + steps.put(14, returnToTheParentsWithBoy); + steps.put(15, reinforceWallAndTalkToCaptain); + steps.put(16, talkToBoysUpstairs); + steps.put(18, saveAntos); + steps.put(20, talkToAntosAfterSavingHim); + steps.put(22, finishQuest); + + return steps; + } + + @Override + protected void setupZones() + { + caveRegion01 = new Zone(5272); + caveRegion02 = new Zone(5273); + caveRegion03 = new Zone(5016); + + upstairsOfParentsHouse = new Zone(new WorldPoint(1378, 3357, 1), new WorldPoint(1383, 3360, 1)); + } + + @Override + protected void setupRequirements() + { + // For the step where you're prompted to talk to citizens about missing people + needToTalkToCitizen = new VarbitRequirement(VarbitID.SOC_CITIZEN, 0); + needToTalkToBarkeep = new VarbitRequirement(VarbitID.SOC_BARKEEP, 0); + needToTalkToShopkeep = new VarbitRequirement(VarbitID.SOC_SHOPKEEP, 0); + needToTalkToIctus = new VarbitRequirement(VarbitID.SOC_SILLYMAN, 0); + + needToFishClothFromLog = not(new QuestRequirement(QuestHelperQuest.SHADOWS_OF_CUSTODIA, 9)); + needsToHandInWillowLongbows = new VarbitRequirement(VarbitID.SOC_BOWSMADE, 2, Operation.NOT_EQUAL); + needToReinforceWall = new VarbitRequirement(VarbitID.SOC_WALL_STATE, 3, Operation.NOT_EQUAL); + + outsideOfCave = new ZoneRequirement(false, caveRegion01, caveRegion02, caveRegion03); + isUpstairsOfParentsHouse = new ZoneRequirement(upstairsOfParentsHouse); + + oneFreeInventorySlot = new FreeInventorySlotRequirement(1); + + startTeleport = new TeleportItemRequirement("Teleport to Auburnvale (Fairy ring AIS)", ItemCollections.FAIRY_STAFF); + startTeleport.setAdditionalOptions(new QuestRequirement(QuestHelperQuest.LUMBRIDGE_ELITE, QuestState.FINISHED)); + + fishingRod = new ItemRequirement("Fishing rod", ItemID.FISHING_ROD).showConditioned(needToFishClothFromLog).highlighted(); + fishingRod.appendToTooltip("Can be obtained during the quest, south-west of the area where you need it."); + + // NOTE: I have _not_ confirmed you can use any other hammer, this is an educated guess. + hammer = new ItemRequirement("Hammer", ItemCollections.HAMMER).showConditioned(needToReinforceWall); + fourMapleLogs = new ItemRequirement("Maple logs", ItemID.MAPLE_LOGS, 4).showConditioned(needToReinforceWall); + + fourWillowLongbows = new ItemRequirement("Willow longbow", ItemID.WILLOW_LONGBOW, 4).showConditioned(needsToHandInWillowLongbows); + + combatGear = new ItemRequirement("Combat gear", -1, -1); + combatGear.setDisplayItemId(BankSlotIcons.getCombatGear()); + + energyOrStaminas = new ItemRequirement("Energy/Stamina potions", ItemCollections.RUN_RESTORE_ITEMS); + } + + public void setupSteps() + { + var unreachableState = new DetailedQuestStep(this, "This state should not be reachable, please make a report with a screenshot in the Quest Helper discord."); + + startQuest = new ObjectStep(this, ObjectID.SOC_MISSING_PERSONS, new WorldPoint(1396, 3356, 0), "Read the noticeboard in the Auburnvale bar to start the quest."); + startQuest.addDialogStep("Yes."); + startQuest.addTeleport(startTeleport); + + var talkToCitizen = new NpcStep(this, NpcID.SOC_CITIZEN, new WorldPoint(1394, 3354, 0), "Talk to Marcus in the Auburnvale bar."); + var talkToBarkeep = new NpcStep(this, NpcID.AUBURN_BARTENDER, new WorldPoint(1391, 3354, 0), "Talk to the Bartender in the Auburnvale bar."); + talkToBarkeep.addDialogStep("About the missing people..."); + var talkToShopkeep = new NpcStep(this, NpcID.AUBURN_GENERAL_STORE, new WorldPoint(1380, 3348, 0), "Talk to the Shopkeep in the Auburnvale General store."); + talkToShopkeep.addDialogStep("About the missing people..."); + var talkToIctus = new NpcStep(this, NpcID.SOC_SILLYMAN, new WorldPoint(1409, 3375, 0), "Talk to Ictus, north of the Auburnvale Quetzal."); + + findOutAboutTheMissingPeople = new ConditionalStep(this, unreachableState, "Talk to citizens of Auburnvale about the missing people."); + findOutAboutTheMissingPeople.addStep(needToTalkToCitizen, talkToCitizen); + findOutAboutTheMissingPeople.addStep(needToTalkToBarkeep, talkToBarkeep); + findOutAboutTheMissingPeople.addStep(needToTalkToIctus, talkToIctus); + findOutAboutTheMissingPeople.addStep(needToTalkToShopkeep, talkToShopkeep); + + talkToTheParents = new NpcStep(this, NpcID.SOC_PARENT, new WorldPoint(1381, 3360, 0), "Talk to Aemilia and Francis, north of the Auburnvale General store, about their missing sons.", true); + talkToTheParents.addAlternateNpcs(NpcID.SOC_PARENT_2); + + inspectWall = new ObjectStep(this, ObjectID.SOC_WALL_INSPECT_OP, new WorldPoint(1378, 3358, 0), "Inspect the wall outside Aemilia and Francis' house."); + + inspectPuddle = new ObjectStep(this, ObjectID.SOC_PUDDLE, new WorldPoint(1376, 3357, 0), "Inspect the puddle outside Aemilia and Francis' house."); + inspectPlank = new ObjectStep(this, ObjectID.SOC_LOG_OP, new WorldPoint(1347, 3354, 0), "Follow the puddle trail and inspect the plank in the river, west of Auburnvale.", fishingRod); + + returnToTheParentsWithCloth = talkToTheParents.copy(); + returnToTheParentsWithCloth.setText("Return to Aemilia and Francis' house, north of the Auburnvale General store, and talk to them about the cloth you found."); + + // 16649: clicked plank but without a fishing rod + + enterCave = new ObjectStep(this, ObjectID.SOC_CAVE_ENTRANCE, new WorldPoint(1295, 3373, 0), "Follow the trail west of the city, then through the mountains, and enter the cave."); + + talkToInjuredBoyInCave = new NpcStep(this, NpcID.SOC_INJURED_PERSON, new WorldPoint(1298, 9757, 0), "Talk to the Injured boy inside the cave."); + + findInjuredBoys = new ConditionalStep(this, talkToInjuredBoyInCave); + findInjuredBoys.addStep(outsideOfCave, enterCave); + + returnToTheParentsWithBoy = talkToTheParents.copy(); + returnToTheParentsWithBoy.setText("Return to Aemilia and Francis's house, north of the Auburnvale General store, and tell them you found their missing boys."); + + reinforceWall = new ObjectStep(this, ObjectID.SOC_WALL_INSPECT_REINFORCE, new WorldPoint(1378, 3358, 0), "Reinforce the wall outside Aemilia and Francis' house.", hammer, fourMapleLogs); + + informCaptainAboutMissingPeople = new NpcStep(this, NpcID.AUBURNVALE_GUARD_CAPTAIN, new WorldPoint(1369, 3344, 0), "Talk to Captain Ariadna, south-west of the Auburnvale General store, and inform her about the missing people.", fourWillowLongbows); + + reinforceWallAndTalkToCaptain = new ConditionalStep(this, informCaptainAboutMissingPeople); + reinforceWallAndTalkToCaptain.addStep(needToReinforceWall, reinforceWall); + + var climbUpLadder = new ObjectStep(this, ObjectID.SOC_LADDER, new WorldPoint(1380, 3357, 0), "Climb upstairs of Aemilia and Francis' house and talk to Etz."); + + talkToEtzAboutWhatTheyRemember = new NpcStep(this, NpcID.SOC_ETZ, new WorldPoint(1381, 3360, 1), "Talk to Etz, upstairs of Aemilia and Francis' house, about what he remembers."); + talkToEtzAboutWhatTheyRemember.addSubSteps(climbUpLadder); + + talkToBoysUpstairs = new ConditionalStep(this, talkToEtzAboutWhatTheyRemember); + talkToBoysUpstairs.addStep(not(isUpstairsOfParentsHouse), climbUpLadder); + + var climbDownstairs = new ObjectStep(this, ObjectID.SOC_LADDERTOP, new WorldPoint(1380, 3357, 1), "Climb downstairs, then head back to the cave."); + enterCave2 = enterCave.copy(); + enterCave2.addSubSteps(climbDownstairs); + + var killCreatures = new NpcStep(this, NpcID.SOC_QUEST_JUVENILE, new WorldPoint(1337, 9753, 0), "Kill the strange creatures. Protect from Melee works to avoid most damage.", true); + + talkToAntos = new NpcStep(this, NpcID.SOC_ANTOS, new WorldPoint(1337, 9753, 0), "Talk to Antos in the eastern part of the cave, ready to fight three Strange creatures. Protect from Melee works to avoid all damage."); + talkToAntos.addSubSteps(killCreatures); + + var hasSpawnedCreatures = new VarbitRequirement(VarbitID.SOC_STALKERS_ENCOUNTERED, 1); + + saveAntos = new ConditionalStep(this, talkToAntos); + // TODO: technically a little route would be nice + saveAntos.addStep(isUpstairsOfParentsHouse, climbDownstairs); + saveAntos.addStep(outsideOfCave, enterCave2); + saveAntos.addStep(hasSpawnedCreatures, killCreatures); + + talkToAntosAfterSavingHim = new ConditionalStep(this, talkToAntos); + // TODO: technically a little route would be nice + talkToAntosAfterSavingHim.addStep(outsideOfCave, enterCave2); + + finishQuest = new NpcStep(this, NpcID.AUBURNVALE_GUARD_CAPTAIN, new WorldPoint(1368, 3343, 0), "Talk to Captain Ariadna in Auburnvale to finish the quest!"); + } + + @Override + public List getItemRequirements() + { + return List.of( + fishingRod, + fourMapleLogs, + hammer, + fourWillowLongbows + ); + } + + @Override + public List getItemRecommended() + { + return List.of( + startTeleport, + combatGear, + energyOrStaminas + ); + } + + @Override + public List getGeneralRecommended() + { + return List.of( + oneFreeInventorySlot + ); + } + + @Override + public List getNotes() + { + return List.of( + ); + } + + @Override + public List getGeneralRequirements() + { + return List.of( + ); + } + + @Override + public List getCombatRequirements() + { + return List.of( + "3 Strange creatures (level 93)" + ); + } + + @Override + public QuestPointReward getQuestPointReward() + { + return new QuestPointReward(2); + } + + @Override + public List getExperienceRewards() + { + return List.of( + new ExperienceReward(Skill.SLAYER, 10000), + new ExperienceReward(Skill.HUNTER, 4000), + new ExperienceReward(Skill.FISHING, 3000), + new ExperienceReward(Skill.CONSTRUCTION, 3000) + ); + } + + @Override + public List getUnlockRewards() + { + return List.of( + new UnlockReward("Access to the Custodia Pass Slayer Dungeon") + ); + } + + @Override + public ArrayList getPanels() + { + var panels = new ArrayList(); + + panels.add(new PanelDetails("Starting off", List.of( + startQuest, + findOutAboutTheMissingPeople, + talkToTheParents + ), List.of( + // Requirements + ), List.of( + startTeleport + ))); + + panels.add(new PanelDetails("Hunting the trail", List.of( + inspectWall, + inspectPuddle, + inspectPlank, + returnToTheParentsWithCloth, + enterCave, + talkToInjuredBoyInCave, + returnToTheParentsWithBoy + ), List.of( + fishingRod + ), List.of( + // Recommended + ))); + + panels.add(new PanelDetails("Inform the authorities", List.of( + reinforceWall, + informCaptainAboutMissingPeople, + talkToEtzAboutWhatTheyRemember + ), List.of( + fourMapleLogs, + hammer, + fourWillowLongbows + ), List.of( + // Recommended + ))); + + panels.add(new PanelDetails("Save Antos", List.of( + enterCave2, + talkToAntos, + finishQuest + ), List.of( + // Requirements + ), List.of( + combatGear + ))); + + return panels; + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/sheepshearer/SheepShearer.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/sheepshearer/SheepShearer.java index 7abb8a34ca0..3f35bb58810 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/sheepshearer/SheepShearer.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/sheepshearer/SheepShearer.java @@ -29,68 +29,68 @@ import net.runelite.client.plugins.microbot.questhelper.requirements.ManualRequirement; import net.runelite.client.plugins.microbot.questhelper.requirements.Requirement; import net.runelite.client.plugins.microbot.questhelper.requirements.item.ItemRequirement; +import static net.runelite.client.plugins.microbot.questhelper.requirements.util.LogicHelper.and; +import static net.runelite.client.plugins.microbot.questhelper.requirements.util.LogicHelper.nor; +import static net.runelite.client.plugins.microbot.questhelper.requirements.util.LogicHelper.or; import net.runelite.client.plugins.microbot.questhelper.requirements.zone.Zone; import net.runelite.client.plugins.microbot.questhelper.requirements.zone.ZoneRequirement; import net.runelite.client.plugins.microbot.questhelper.rewards.ExperienceReward; import net.runelite.client.plugins.microbot.questhelper.rewards.ItemReward; import net.runelite.client.plugins.microbot.questhelper.rewards.QuestPointReward; -import net.runelite.client.plugins.microbot.questhelper.steps.*; -import net.runelite.api.InventoryID; -import net.runelite.api.ItemContainer; +import net.runelite.client.plugins.microbot.questhelper.steps.ConditionalStep; +import net.runelite.client.plugins.microbot.questhelper.steps.ItemStep; +import net.runelite.client.plugins.microbot.questhelper.steps.NpcStep; +import net.runelite.client.plugins.microbot.questhelper.steps.ObjectStep; +import net.runelite.client.plugins.microbot.questhelper.steps.QuestStep; +import net.runelite.client.plugins.microbot.questhelper.steps.widget.WidgetHighlight; +import net.runelite.client.plugins.microbot.questhelper.util.QHObjectID; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.IntStream; import net.runelite.api.Skill; import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.ItemContainerChanged; +import net.runelite.api.gameval.InventoryID; import net.runelite.api.gameval.ItemID; import net.runelite.api.gameval.NpcID; import net.runelite.api.gameval.ObjectID; +import net.runelite.api.gameval.VarPlayerID; import net.runelite.client.eventbus.Subscribe; -import java.util.*; -import java.util.stream.IntStream; - -import static net.runelite.client.plugins.microbot.questhelper.requirements.util.LogicHelper.*; - public class SheepShearer extends BasicQuestHelper { - //Items Required - ItemRequirement ballOfWool, shears, woolOrBalls, onlyWool, totalWoolNeeded, totalBallsNeeded; - - QuestStep startStep, getSheers, climbStairsUp, climbStairsDown, spinBalls, turnInBalls; - - NpcStep shearSheep; - + // Required items + ItemRequirement ballOfWool; + ItemRequirement shears; + ItemRequirement woolOrBalls; + ItemRequirement onlyWool; + ItemRequirement totalWoolNeeded; + ItemRequirement totalBallsNeeded; + + // Zones Zone castleSecond; + // Miscellaneous requirements Requirement inCastleSecond; - + // TODO: We should be able to use a EmptyInvSlot requirement here ManualRequirement skipIfFullInventory; - int woolNeeded; + NpcStep startStep; + ItemStep getSheers; + NpcStep shearSheep; + ObjectStep climbStairsUp; + ObjectStep climbStairsDown; + ObjectStep spinBalls; + NpcStep turnInBalls; + + int woolNeeded = 20; @Override - public Map loadSteps() + protected void setupZones() { - initializeRequirements(); - setupConditions(); - setupSteps(); - - Map steps = new HashMap<>(); - - // If you have all the wool you need, OR you have filled your inventory with wool - Requirement hasAllWoolOrFullInv = or(totalWoolNeeded, and(woolOrBalls, skipIfFullInventory)); - // If you have all the balls needed, OR you've made all the wool you had in your inventory into balls of wool - Requirement hasAllBallsOrFullInv = or(totalBallsNeeded, and(nor(onlyWool), ballOfWool)); - ConditionalStep craftingBalls = new ConditionalStep(this, getSheers); - craftingBalls.addStep(and(hasAllBallsOrFullInv, inCastleSecond), climbStairsDown); - craftingBalls.addStep(hasAllBallsOrFullInv, turnInBalls); - craftingBalls.addStep(and(hasAllWoolOrFullInv, inCastleSecond), spinBalls); - craftingBalls.addStep(hasAllWoolOrFullInv, climbStairsUp); - craftingBalls.addStep(shears, shearSheep); - - steps.put(0, startStep); - IntStream.range(1, 20).forEach(i -> steps.put(i, craftingBalls)); - - return steps; + castleSecond = new Zone(new WorldPoint(3200, 3232, 1), new WorldPoint(3220, 3205, 1)); } @Override @@ -103,27 +103,20 @@ protected void setupRequirements() woolOrBalls.addAlternates(ItemID.BALL_OF_WOOL); onlyWool = new ItemRequirement("Wool", ItemID.WOOL); - woolNeeded = client.getVarpValue(179) > 1 ? 21 - client.getVarpValue(179) : 20; - totalWoolNeeded = woolOrBalls.quantity(woolNeeded); - totalBallsNeeded = ballOfWool.quantity(woolNeeded); - } - - @Override - protected void setupZones() - { - castleSecond = new Zone(new WorldPoint(3200, 3232, 1), new WorldPoint(3220, 3205, 1)); - } + totalWoolNeeded = woolOrBalls.quantity(20); + totalBallsNeeded = ballOfWool.quantity(20); - public void setupConditions() - { inCastleSecond = new ZoneRequirement(castleSecond); skipIfFullInventory = new ManualRequirement(); - ItemContainer inventory = client.getItemContainer(InventoryID.INVENTORY); - if (inventory == null) return; + var inventory = client.getItemContainer(InventoryID.INV); + if (inventory != null) + { + int itemsInInventory = inventory.count(); + skipIfFullInventory.setShouldPass(itemsInInventory == 28); + } - int itemsInInventory = inventory.count(); - skipIfFullInventory.setShouldPass(itemsInInventory == 28); + updateWoolNeeded(); } public void setupSteps() @@ -134,48 +127,81 @@ public void setupSteps() getSheers = new ItemStep(this, new WorldPoint(3190, 3273, 0), "Pickup the shears in Fred's house.", shears); shearSheep = new NpcStep(this, NpcID.SHEEPUNSHEERED3G, new WorldPoint(3201, 3268, 0), - "Shear " + woolNeeded + " sheep in the nearby field.", true, shears); + "Shear sheep in the nearby field.", true, shears, totalWoolNeeded); shearSheep.addAlternateNpcs(NpcID.SHEEPUNSHEERED3, NpcID.SHEEPUNSHEERED3W, NpcID.SHEEPUNSHEERED, NpcID.SHEEPUNSHEEREDG, NpcID.SHEEPUNSHEERED3, NpcID.SHEEPUNSHEEREDW); - climbStairsUp = new ObjectStep(this, ObjectID.SPIRALSTAIRS, new WorldPoint(3204, 3207, 0), - "Climb the staircase in the Lumbridge Castle to spin the wool into balls of wool.", totalWoolNeeded); + climbStairsUp = new ObjectStep(this, QHObjectID.LUMBRIDGE_CASTLE_F0_SOUTH_STAIRCASE, new WorldPoint(3204, 3207, 0), + "Climb the staircase in the Lumbridge Castle to spin the wool in your inventory into balls of wool.", totalWoolNeeded); spinBalls = new ObjectStep(this, ObjectID.SPINNINGWHEEL, new WorldPoint(3209, 3212, 1), "Spin your wool into balls.", totalWoolNeeded); - spinBalls.addWidgetHighlight(270, 14); + spinBalls.addWidgetHighlight(WidgetHighlight.createMultiskillByItemId(ItemID.BALL_OF_WOOL)); climbStairsDown = new ObjectStep(this, ObjectID.SPIRALSTAIRSMIDDLE, new WorldPoint(3204, 3207, 1), "Climb down the staircase.", totalBallsNeeded); climbStairsDown.addDialogSteps("Climb down the stairs."); turnInBalls = new NpcStep(this, NpcID.FRED_THE_FARMER, new WorldPoint(3190, 3273, 0), - "Bring Fred the Farmer north of Lumbridge " + woolNeeded + " balls of wool (UNNOTED) to finish the quest. If you only have some of the balls needed, you can still deposit them with him.", + "Bring Fred the Farmer north of Lumbridge your balls of wool (UNNOTED) to progress the quest. If you only have some of the balls needed, you can still deposit them with him.", totalBallsNeeded); turnInBalls.addDialogSteps("I need to talk to you about shearing these sheep!"); } - @Subscribe - public void onItemContainerChanged(ItemContainerChanged event) + @Override + public Map loadSteps() { - if (event.getContainerId() != InventoryID.INVENTORY.getId()) + initializeRequirements(); + setupSteps(); + + var steps = new HashMap(); + + // If you have all the wool you need, OR you have filled your inventory with wool + var hasAllWoolOrFullInv = or(totalWoolNeeded, and(woolOrBalls, skipIfFullInventory)); + // If you have all the balls needed, OR you've made all the wool you had in your inventory into balls of wool + var hasAllBallsOrFullInv = or(totalBallsNeeded, and(nor(onlyWool), ballOfWool)); + var craftingBalls = new ConditionalStep(this, getSheers); + craftingBalls.addStep(and(hasAllBallsOrFullInv, inCastleSecond), climbStairsDown); + craftingBalls.addStep(hasAllBallsOrFullInv, turnInBalls); + craftingBalls.addStep(and(hasAllWoolOrFullInv, inCastleSecond), spinBalls); + craftingBalls.addStep(hasAllWoolOrFullInv, climbStairsUp); + craftingBalls.addStep(shears, shearSheep); + + steps.put(0, startStep); + IntStream.range(1, 21).forEach(i -> steps.put(i, craftingBalls)); + + return steps; + } + + private void updateWoolNeeded() + { + var sheepVarp = client.getVarpValue(VarPlayerID.SHEEP); + var newWoolNeeded = sheepVarp > 1 ? 21 - sheepVarp : 20; + if (newWoolNeeded == woolNeeded) { return; } - - woolNeeded = client.getVarpValue(179) > 1 ? 21 - client.getVarpValue(179) : 20; + woolNeeded = newWoolNeeded; totalBallsNeeded.setQuantity(woolNeeded); totalWoolNeeded.setQuantity(woolNeeded); + } - turnInBalls.setText("Bring Fred the Farmer north of Lumbridge " + woolNeeded + " balls of wool (UNNOTED) to finish the quest."); - shearSheep.setText("Shear " + woolNeeded + " sheep in the nearby field."); + @Subscribe + public void onItemContainerChanged(ItemContainerChanged event) + { + if (event.getContainerId() != InventoryID.INV) + { + return; + } // If inventory full skipIfFullInventory.setShouldPass(event.getItemContainer().count() == 28); + + updateWoolNeeded(); } @Override public List getItemRequirements() { - ArrayList reqs = new ArrayList<>(); - reqs.add(ballOfWool.quantity(20)); - reqs.add(shears); - return reqs; + return List.of( + totalBallsNeeded, + shears + ); } @Override @@ -187,22 +213,36 @@ public QuestPointReward getQuestPointReward() @Override public List getExperienceRewards() { - return Collections.singletonList(new ExperienceReward(Skill.CRAFTING, 150)); + return List.of( + new ExperienceReward(Skill.CRAFTING, 150) + ); } @Override public List getItemRewards() { - return Collections.singletonList(new ItemReward("Coins", ItemID.COINS, 60)); + return List.of( + new ItemReward("Coins", ItemID.COINS, 60) + ); } @Override public List getPanels() { - List allSteps = new ArrayList<>(); + var steps = new ArrayList(); + + steps.add(new PanelDetails("Bring Fred Some Wool", List.of( + startStep, + getSheers, + shearSheep, + climbStairsUp, + spinBalls, + climbStairsDown, + turnInBalls + ), List.of( + totalBallsNeeded + ))); - allSteps.add(new PanelDetails("Bring Fred Some Wool", Arrays.asList(startStep, getSheers, shearSheep, - climbStairsUp, spinBalls, climbStairsDown, turnInBalls), ballOfWool.quantity(20))); - return allSteps; + return steps; } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/spiritsoftheelid/SpiritsOfTheElid.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/spiritsoftheelid/SpiritsOfTheElid.java index 6434bac3915..3876144ebd8 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/spiritsoftheelid/SpiritsOfTheElid.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/spiritsoftheelid/SpiritsOfTheElid.java @@ -133,7 +133,9 @@ protected void setupRequirements() lawRune = new ItemRequirement("Law Rune", ItemID.LAWRUNE, 1); needle = new ItemRequirement("Needle", ItemID.NEEDLE, 1).isNotConsumed(); needle.setHighlightInInventory(true); + needle.setTooltip("Costume needle cannot be used as a substitute."); thread = new ItemRequirement("Thread", ItemID.THREAD, 2); + thread.setTooltip("Costume needle cannot be used as a substitute."); crushWep = new ItemRequirement("Crush Weapon Style", -1, 1).isNotConsumed(); crushWep.setDisplayItemId(ItemID.RUNE_MACE); stabWep = new ItemRequirement("Stab Weapon Style", -1, 1).isNotConsumed(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/taibwowannaitrio/TaiBwoWannaiTrio.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/taibwowannaitrio/TaiBwoWannaiTrio.java index 185a9519d87..180f8520326 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/taibwowannaitrio/TaiBwoWannaiTrio.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/taibwowannaitrio/TaiBwoWannaiTrio.java @@ -546,7 +546,7 @@ public List getGeneralRequirements() req.add(new SkillRequirement(Skill.AGILITY, 15, false)); req.add(new SkillRequirement(Skill.COOKING, 30, false)); req.add(new SkillRequirement(Skill.FISHING, 5, false)); - req.add(new ItemRequirement("65 Fishing for Raw Karambwan if any type of Ironman account.", -1, -1)); + req.add(new ItemRequirement("65 Fishing for Raw Karambwan if any type of Ironman account, if you burn the one given to you.", -1, -1)); return req; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/thecurseofarrav/MetalDoorSolver.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/thecurseofarrav/MetalDoorSolver.java index b48bfb89037..af25c87a641 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/thecurseofarrav/MetalDoorSolver.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/thecurseofarrav/MetalDoorSolver.java @@ -37,6 +37,7 @@ import net.runelite.api.annotations.Interface; import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.GameTick; +import net.runelite.api.gameval.InterfaceID; import net.runelite.api.gameval.ItemID; import net.runelite.api.gameval.ObjectID; import net.runelite.client.eventbus.Subscribe; @@ -120,16 +121,6 @@ public class MetalDoorSolver extends DetailedOwnerStep private static final int PUZZLE_PASSWORD_3_CHILD_ID = 28; private static final int PUZZLE_PASSWORD_4_CHILD_ID = 29; - /** - * Group ID of the "MESBOX" widget containing our code - */ - private static final @Interface int MESBOX_GROUP_ID = 229; - - /** - * Child ID of the "MESBOX" widget containing our code - */ - private static final int MESBOX_CHILD_ID = 1; - private static final Pattern CODE_PATTERN = Pattern.compile("It reads ([A-I]{4})."); @Inject @@ -344,7 +335,7 @@ else if (Objects.equals(input4Text, "-") || Integer.parseInt(input4.getText()) ! return; } - var textWidget = client.getWidget(MESBOX_GROUP_ID, MESBOX_CHILD_ID); + var textWidget = client.getWidget(InterfaceID.Messagebox.TEXT); if (textWidget == null) { return; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/thefinaldawn/TheFinalDawn.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/thefinaldawn/TheFinalDawn.java new file mode 100644 index 00000000000..06697c74a04 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/thefinaldawn/TheFinalDawn.java @@ -0,0 +1,1272 @@ +/* + * Copyright (c) 2025, Zoinkwiz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.microbot.questhelper.helpers.quests.thefinaldawn; + +import net.runelite.client.plugins.microbot.questhelper.bank.banktab.BankSlotIcons; +import net.runelite.client.plugins.microbot.questhelper.collections.ItemCollections; +import net.runelite.client.plugins.microbot.questhelper.helpers.quests.deserttreasureii.ChestCodeStep; +import net.runelite.client.plugins.microbot.questhelper.panel.PanelDetails; +import net.runelite.client.plugins.microbot.questhelper.questhelpers.BasicQuestHelper; +import net.runelite.client.plugins.microbot.questhelper.questinfo.QuestHelperQuest; +import net.runelite.client.plugins.microbot.questhelper.requirements.ManualRequirement; +import net.runelite.client.plugins.microbot.questhelper.requirements.Requirement; +import net.runelite.client.plugins.microbot.questhelper.requirements.conditional.ObjectCondition; +import net.runelite.client.plugins.microbot.questhelper.requirements.item.ItemOnTileRequirement; +import net.runelite.client.plugins.microbot.questhelper.requirements.item.ItemRequirement; +import net.runelite.client.plugins.microbot.questhelper.requirements.item.ItemRequirements; +import net.runelite.client.plugins.microbot.questhelper.requirements.npc.NpcRequirement; +import net.runelite.client.plugins.microbot.questhelper.requirements.player.FreeInventorySlotRequirement; +import net.runelite.client.plugins.microbot.questhelper.requirements.player.SkillRequirement; +import net.runelite.client.plugins.microbot.questhelper.requirements.quest.QuestRequirement; +import net.runelite.client.plugins.microbot.questhelper.requirements.util.Operation; +import net.runelite.client.plugins.microbot.questhelper.requirements.var.VarbitRequirement; +import net.runelite.client.plugins.microbot.questhelper.requirements.widget.WidgetTextRequirement; +import net.runelite.client.plugins.microbot.questhelper.requirements.zone.Zone; +import net.runelite.client.plugins.microbot.questhelper.requirements.zone.ZoneRequirement; +import net.runelite.client.plugins.microbot.questhelper.rewards.ExperienceReward; +import net.runelite.client.plugins.microbot.questhelper.rewards.ItemReward; +import net.runelite.client.plugins.microbot.questhelper.rewards.QuestPointReward; +import net.runelite.client.plugins.microbot.questhelper.rewards.UnlockReward; +import net.runelite.client.plugins.microbot.questhelper.steps.*; + +import java.util.*; + +import net.runelite.client.plugins.microbot.questhelper.steps.tools.QuestPerspective; +import net.runelite.client.plugins.microbot.questhelper.steps.widget.WidgetHighlight; +import net.runelite.api.QuestState; +import net.runelite.api.Skill; +import net.runelite.api.Tile; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.GameTick; +import net.runelite.api.gameval.*; +import net.runelite.client.eventbus.Subscribe; + +import static net.runelite.client.plugins.microbot.questhelper.requirements.util.LogicHelper.*; + +/** + * The quest guide for the "The Final Dawn" OSRS quest + */ +public class TheFinalDawn extends BasicQuestHelper +{ + ItemRequirement emissaryRobesEquipped, emissaryRobes, bone, rangedGear; + + ItemRequirement combatGear, combatWeapon, food, prayerPotions, whistle, pendant, pendantToTwilight, civitasTeleport; + FreeInventorySlotRequirement freeInvSlots4, freeInvSlot1; + + ItemRequirement drawerKey, canvasPiece, emissaryScroll, potatoes, knife, coinPurse, coinPurseFullOrEmpty, branch, coinPurseWithSand, coinPurseEmpty, + emptySack, makeshiftBlackjack; + ItemRequirement steamforgedBrew, dwarvenStout, beer, emptyGlass, wizardsMindBomb, keystoneFragment, essence, roots, kindling, knifeBlade, stoneTablet; + + QuestStep startQuest, goToTempleWithAtes, goToTempleFromSalvager, goToTempleFromGorge, searchChestForEmissaryRobes, enterTwilightTemple, goDownStairsTemple, enterBackroom, + searchBed, openDrawers, openDrawers2; + DetailedQuestStep useCanvasPieceOnPicture, enterPassage, pickBlueChest, fightEnforcer, pickUpEmissaryScroll, readEmissaryScroll, talkToQueen, + climbStairsF0ToF1Palace, climbStairsF1ToF2Palace; + QuestStep openDoorWithGusCode; + QuestStep talkToCaptainVibia, inspectWindow, giveBonesOrMeatToDog, enterDoorCode, takePotato, removePotatoesFromSack, takeKnife, takeCoinPurse, + emptyCoinPurse, goToF1Hideout, goDownFromF2Hideout, goToF0Hideout, goToF0HideoutEnd, goF2ToF1HideoutEnd; + QuestStep goF1ToF2Hideout, useKnifeOnPottedFan, fillCoinPurse, useBranchOnCoinPurse, showSackToVibia, searchBodyForKey, enterTrapdoor, talkToQueenToGoCamTorum; + DetailedQuestStep enterCamTorum, talkToAttala, talkToServiusInCamTorum, goUpstairsPub, takeBeer, goDownstairsPub, useBeerOnGalna, enterCamTorumHouseBasement; + QuestStep takeBeerCabinet, drinkBeer, takeSteamforgeBrew, takeDwarvenStout, takeWizardsMindBomb, placeSteamforgedBrew, placeDwarvenStout, placeBeer, + takeBeerFromBarrel, placeEmptyGlass, placeMindBomb, inspectFireplace, useHole, watchCutsceneCamTorum, returnThroughHole, returnToServius; + DetailedQuestStep climbUpFromTeumoBasement, enterNeypotzli, talkToEyatalli, locateKeystone; + QuestStep enterStreamboundCavern, locateInStreambound, enterEarthboundCavernFromStreambound, enterEarthboundCavern, locateInEarthbound, + enterAncientPrison, enterAncientPrisonFromEarthbound, locateInAncientPrison, touchGlowingSymbol, defeatCultists, talkToAttalaAfterCultistFight; + + DetailedQuestStep talkToServiusAtTalTeklan, enterTonaliCavern, defeatFinalCultists, fightEnnius, tonaliGoDownStairsF2ToF1, tonaliGoDownStairsF1ToF0, + useRedTeleporter, useBlueTeleporter, crossLog, useBlueTeleporter2; + DetailedQuestStep useRedTeleporter2, useBlueTeleporterLizards, useRedTeleporter3, climbRope; + + QuestStep activateStrangePlatform, enterTonaliWithLift, descendIntoSunPuzzle, inspectSunStatue, getEssenceFromUrns, solveSunPuzzle, solveSunPuzzle2Step1MoveItzla, + solveSunPuzzle2Step1Craft, solveSunPuzzle2Step2MoveItzla, solveSunPuzzle2Step2Craft, solveSunPuzzle2Step3MoveItzla, solveSunPuzzle2Step3Craft; + QuestStep solveSunPuzzle1Step1MoveItzla, solveSunPuzzle1Step1Craft, solveSunPuzzle1Step2MoveItzla, solveSunPuzzle1Step2Craft, + solveSunPuzzle1Step3MoveItzla, solveSunPuzzle1Step3Craft; + + QuestStep goUpFromSunPuzzle, enterMoonPuzzle, moveItzlaNorth, moveItzlaSouth, pullTreeRoots, getKnifeBlade, placeRoots, fletchRoots, + repeatMoonPuzzleThreeTimes, leaveMoonPuzzleRoom; + + QuestStep enterFinalBossArea, approachMetzli, defeatFinalBoss, defeatFinalBossSidebar, watchFinalBossAfterCutscene, goToNorthOfFinalArea, + goToNorthOfFinalAreaAgilityShortcut, inspectRanulPillar, inspectRalosPillar, inspectDoor, inspectSkeleton, readStoneTablet, finishQuest; + + Zone templeArea, templeBasement, eastTempleBasement, hiddenRoom, palaceF1, palaceF2, hideoutGroundFloor, hideoutMiddleFloor, hideoutTopFloor, + hideoutBasement, camTorum, camTorumF2, camTorumBasement, hiddenTunnel, hiddenTunnel2; + + Zone antechamber, prison, streambound, earthbound, ancientShrine, neypotzliFightRoom, tonaliCavernF2, tonaliCavernF0P2South, tonaliCavernF0P2North, + tonaliCavernF1Stairs, tonaliCavernF0Start, tonaliCavernF1Rockslugs, tonaliCavernF0P3V1, tonaliCavernF1GrimyLizards, tonaliCavernF1Nagua, + tonaliCavernF2North, tonaliCavernF0P3V2, sunPuzzleRoom, moonPuzzleRoom, finalBossArea; + + Requirement inTempleArea, inTempleBasement, inEastTempleBasement, inHiddenRoom, inPalaceF1, inPalaceF2, inHideout, inHideoutF1, inHideoutF2, + inHideoutBasement, inCamTorum, inCamTorumF2, inCamTorumBasement, inCamTorumHiddenTunnel; + + Requirement inAntechamber, inPrison, inStreambound, inEarthbound, inAncientShrine, inNeypotzli, inNeypotzliFightRoom; + Requirement inTonaliCavern, inTonaliCavernF0P2South, inTonaliCavernF0P2North, inTonaliCavernF0Start, inTonaliCavernF0P3, inTonaliCavernF1Stairs, + inTonaliCavernF1Rockslugs, inTonaliCavernF1GrimyLizards, inTonaliCavernF1Nagua, inTonaliCavernF2North, inSunPuzzleRoom, inMoonPuzzleRoom, + inFinalBossArea; + + Requirement quetzalMadeSalvagerOverlook, hasAtesAndActivatedTeleport; + + Requirement isSouthDrawer, hasDrawerKeyOrOpened, usedSigilOnCanvas, emissaryScrollNearby, inChestInterface; + Requirement hasSackOfGivenSack, isGalnaDrunk, notPlacedMindBomb, notPlacedBeer, notPlacedSteamforgeBrew, notPlacedDwarvenStout, beerTakenFromBarrel; + Requirement locatedKeystone1, locatedKeystone2, liftActivated, inspectedSunStatue, itzlaInPosSunPuzzle2Step1, completedSunPuzzleP1; + Requirement itzlaInPosSunPuzzle1Step1, itzlaInPosSunPuzzle1Step2, itzlaInPosSunPuzzle1Step3; + Requirement itzlaInPosSunPuzzle2Step2, completedSunPuzzleP2, itzlaInPosSunPuzzle2Step3, completedSunPuzzleP3; + Requirement inMoonPuzzleP1, inMoonPuzzleP2, inMoonPuzzleP3, completedMoonPuzzle; + ManualRequirement northPlatformSolutionKnown, southPlatformSolutionKnown; + Requirement isPuzzleOrder1, isPuzzleOrder2; + + Requirement is72Agility, notInspectedRalosPillar, notInspectedRanulPillar, notInspectedSkeleton, notInspectedDoor; + + int lastKnownStateStep = 0; + int lastKnownStateDarkFlame, lastKnownStateLightFlame = -1; + + @Override + public Map loadSteps() + { + initializeRequirements(); + setupZones(); + setupRequirements(); + setupSteps(); + + lastKnownStateStep = -1; + lastKnownStateDarkFlame = -1; + lastKnownStateLightFlame = -1; + + var steps = new HashMap(); + + steps.put(0, startQuest); + steps.put(1, startQuest); + + ConditionalStep goEnterTemple = new ConditionalStep(this, goToTempleFromGorge); + goEnterTemple.addStep(and(inTempleArea, emissaryRobes), enterTwilightTemple); + goEnterTemple.addStep(inTempleArea, searchChestForEmissaryRobes); + goEnterTemple.addStep(hasAtesAndActivatedTeleport, goToTempleWithAtes); + goEnterTemple.addStep(quetzalMadeSalvagerOverlook, goToTempleFromSalvager); + steps.put(3, goEnterTemple); + + ConditionalStep goEnterTempleBasement = new ConditionalStep(this, goEnterTemple); + goEnterTempleBasement.addStep(inHiddenRoom, pickBlueChest); + goEnterTempleBasement.addStep(and(inEastTempleBasement, usedSigilOnCanvas), enterPassage); + goEnterTempleBasement.addStep(and(inEastTempleBasement, canvasPiece), useCanvasPieceOnPicture); + goEnterTempleBasement.addStep(and(inEastTempleBasement, hasDrawerKeyOrOpened, isSouthDrawer), openDrawers2); + goEnterTempleBasement.addStep(and(inEastTempleBasement, hasDrawerKeyOrOpened), openDrawers); + goEnterTempleBasement.addStep(inEastTempleBasement, searchBed); + goEnterTempleBasement.addStep(inTempleBasement, enterBackroom); + goEnterTempleBasement.addStep(and(inTempleArea, emissaryRobes), goDownStairsTemple); + + steps.put(4, goEnterTempleBasement); + steps.put(5, goEnterTempleBasement); + steps.put(6, goEnterTempleBasement); + steps.put(7, goEnterTempleBasement); + + ConditionalStep goFightInBasement = new ConditionalStep(this, goEnterTemple); + goFightInBasement.addStep(inEastTempleBasement, fightEnforcer); + goFightInBasement.addStep(inTempleBasement, enterBackroom); + goFightInBasement.addStep(and(inTempleArea, emissaryRobes), goDownStairsTemple); + steps.put(8, goFightInBasement); + + ConditionalStep goReadScroll = new ConditionalStep(this, goEnterTemple); + goReadScroll.addStep(emissaryScroll, readEmissaryScroll); + goReadScroll.addStep(and(or(inEastTempleBasement, inHiddenRoom), emissaryScrollNearby), pickUpEmissaryScroll); + goReadScroll.addStep(inEastTempleBasement, enterPassage); + goReadScroll.addStep(inHiddenRoom, pickBlueChest); + goReadScroll.addStep(inTempleBasement, enterBackroom); + goReadScroll.addStep(and(inTempleArea, emissaryRobes), goDownStairsTemple); + steps.put(9, goReadScroll); + + ConditionalStep goTalkToQueen = new ConditionalStep(this, climbStairsF0ToF1Palace); + goTalkToQueen.addStep(inPalaceF2, talkToQueen); + goTalkToQueen.addStep(inPalaceF1, climbStairsF1ToF2Palace); + steps.put(10, goTalkToQueen); + + steps.put(11, talkToCaptainVibia); + ConditionalStep goIntoHouse = new ConditionalStep(this, inspectWindow); + goIntoHouse.addStep(inHideout, giveBonesOrMeatToDog); + steps.put(12, goIntoHouse); + steps.put(13, goIntoHouse); + steps.put(14, goIntoHouse); + steps.put(15, goIntoHouse); + steps.put(16, goIntoHouse); + + ConditionalStep goPetDog = new ConditionalStep(this, inspectWindow); + goPetDog.addStep(inChestInterface, openDoorWithGusCode); + goPetDog.addStep(inHideout, enterDoorCode); + steps.put(17, goPetDog); + + ConditionalStep goDoHideoutStuff = new ConditionalStep(this, inspectWindow); + goDoHideoutStuff.addStep(and(inHideoutF1, hasSackOfGivenSack, makeshiftBlackjack), goToF0HideoutEnd); + goDoHideoutStuff.addStep(and(inHideoutF2, hasSackOfGivenSack, makeshiftBlackjack), goF2ToF1HideoutEnd); + goDoHideoutStuff.addStep(and(inHideout, hasSackOfGivenSack, makeshiftBlackjack), showSackToVibia); + goDoHideoutStuff.addStep(and(inHideoutF2, hasSackOfGivenSack, coinPurseWithSand, branch), useBranchOnCoinPurse); + goDoHideoutStuff.addStep(and(inHideoutF2, hasSackOfGivenSack, knife, coinPurseWithSand), useKnifeOnPottedFan); + goDoHideoutStuff.addStep(and(inHideoutF2, hasSackOfGivenSack, knife, coinPurseEmpty), fillCoinPurse); + goDoHideoutStuff.addStep(inHideoutF2, goDownFromF2Hideout); + goDoHideoutStuff.addStep(and(inHideoutF1, hasSackOfGivenSack, knife, coinPurseEmpty), goF1ToF2Hideout); + goDoHideoutStuff.addStep(and(coinPurse), emptyCoinPurse); + goDoHideoutStuff.addStep(and(inHideoutF1, hasSackOfGivenSack, knife), takeCoinPurse); + goDoHideoutStuff.addStep(and(inHideoutF1), goToF0Hideout); + goDoHideoutStuff.addStep(and(inHideout, hasSackOfGivenSack, knife), goToF1Hideout); + goDoHideoutStuff.addStep(and(inHideout, hasSackOfGivenSack), takeKnife); + goDoHideoutStuff.addStep(and(inHideout, potatoes), removePotatoesFromSack); + goDoHideoutStuff.addStep(inHideout, takePotato); + steps.put(18, goDoHideoutStuff); + // 19 was took coin purse, 20 was emptied it first time + steps.put(19, goDoHideoutStuff); + steps.put(20, goDoHideoutStuff); + steps.put(21, goDoHideoutStuff); + steps.put(22, goDoHideoutStuff); + + ConditionalStep goSearchJanus = new ConditionalStep(this, inspectWindow); + goSearchJanus.addStep(inHideout, searchBodyForKey); + steps.put(23, goSearchJanus); + + ConditionalStep goEnterTrapdoor = new ConditionalStep(this, inspectWindow); + goEnterTrapdoor.addStep(inHideoutBasement, talkToQueenToGoCamTorum); + goEnterTrapdoor.addStep(inHideout, enterTrapdoor); + steps.put(24, goEnterTrapdoor); + steps.put(25, goEnterTrapdoor); + steps.put(26, goEnterTrapdoor); + + ConditionalStep goTalkToAttala = new ConditionalStep(this, enterCamTorum); + goTalkToAttala.addStep(inCamTorum, talkToAttala); + // TODO: See if cut cutscene at 27, if you restart it by entering Cam Torum again or not + steps.put(27, goTalkToAttala); + steps.put(28, goTalkToAttala); + + ConditionalStep goTalkToServiusCamTorum = new ConditionalStep(this, enterCamTorum); + goTalkToServiusCamTorum.addStep(inCamTorum, talkToServiusInCamTorum); + steps.put(29, goTalkToServiusCamTorum); + + ConditionalStep doBasementPuzzle = new ConditionalStep(this, enterCamTorumHouseBasement); + doBasementPuzzle.addStep(and(inCamTorumBasement, notPlacedMindBomb, wizardsMindBomb), placeMindBomb); + doBasementPuzzle.addStep(and(inCamTorumBasement, notPlacedMindBomb), takeWizardsMindBomb); + + doBasementPuzzle.addStep(and(inCamTorumBasement, notPlacedBeer, beer), placeBeer); + doBasementPuzzle.addStep(and(inCamTorumBasement, notPlacedBeer), takeBeerCabinet); + + doBasementPuzzle.addStep(and(inCamTorumBasement, notPlacedSteamforgeBrew, steamforgedBrew), placeSteamforgedBrew); + doBasementPuzzle.addStep(and(inCamTorumBasement, notPlacedSteamforgeBrew), takeSteamforgeBrew); + + doBasementPuzzle.addStep(and(inCamTorumBasement, notPlacedDwarvenStout, dwarvenStout), placeDwarvenStout); + doBasementPuzzle.addStep(and(inCamTorumBasement, notPlacedDwarvenStout), takeDwarvenStout); + + doBasementPuzzle.addStep(and(inCamTorumBasement, beerTakenFromBarrel, emptyGlass), placeEmptyGlass); + doBasementPuzzle.addStep(and(inCamTorumBasement, beer), drinkBeer); + doBasementPuzzle.addStep(inCamTorumBasement, takeBeerFromBarrel); + + ConditionalStep goGetGalnaDrunk = new ConditionalStep(this, enterCamTorum); + goGetGalnaDrunk.addStep(and(inCamTorumF2, beer), goDownstairsPub); + goGetGalnaDrunk.addStep(and(inCamTorum, beer), useBeerOnGalna); + goGetGalnaDrunk.addStep(inCamTorumF2, takeBeer); + goGetGalnaDrunk.addStep(inCamTorum, goUpstairsPub); + + ConditionalStep goDoCamTorum = new ConditionalStep(this, enterCamTorum); + goDoCamTorum.addStep(and(inCamTorum, isGalnaDrunk), doBasementPuzzle); + goDoCamTorum.addStep(inCamTorum, goGetGalnaDrunk); + steps.put(30, goDoCamTorum); + steps.put(31, goDoCamTorum); + + ConditionalStep goEnterFireplace = new ConditionalStep(this, enterCamTorum); + goEnterFireplace.addStep(inCamTorumBasement, inspectFireplace); + goEnterFireplace.addStep(inCamTorum, enterCamTorumHouseBasement); + steps.put(32, goEnterFireplace); + + ConditionalStep goEnterHole = new ConditionalStep(this, enterCamTorum); + goEnterHole.addStep(inCamTorumHiddenTunnel, watchCutsceneCamTorum); + goEnterHole.addStep(inCamTorumBasement, useHole); + goEnterHole.addStep(inCamTorum, enterCamTorumHouseBasement); + steps.put(33, goEnterHole); + steps.put(34, goEnterHole); + + ConditionalStep goTalkToServiusBasement = new ConditionalStep(this, enterCamTorum); + goTalkToServiusBasement.addStep(inCamTorumHiddenTunnel, returnThroughHole); + goTalkToServiusBasement.addStep(inCamTorumBasement, returnToServius); + goTalkToServiusBasement.addStep(inCamTorum, enterCamTorumHouseBasement); + steps.put(35, goTalkToServiusBasement); + + ConditionalStep goTalkToEyat = new ConditionalStep(this, enterCamTorum); + goTalkToEyat.addStep(inCamTorumBasement, climbUpFromTeumoBasement); + goTalkToEyat.addStep(inNeypotzli, talkToEyatalli); + goTalkToEyat.addStep(inCamTorum, enterNeypotzli); + steps.put(36, goTalkToEyat); + steps.put(37, goTalkToEyat); + steps.put(38, goTalkToEyat); + + // Received keystone fragment, varbit VarbitID.VMQ4_MONOLITH_FRAGMENT_ATTEMPTED_GIVE went 0->1 + ConditionalStep goLocateKeystone = new ConditionalStep(this, enterCamTorum); + goLocateKeystone.addStep(and(inPrison, locatedKeystone2), locateInAncientPrison); + goLocateKeystone.addStep(and(inEarthbound, locatedKeystone2), enterAncientPrisonFromEarthbound); + goLocateKeystone.addStep(and(inNeypotzli, locatedKeystone2), enterAncientPrison); + + goLocateKeystone.addStep(and(inEarthbound, locatedKeystone1), locateInEarthbound); + goLocateKeystone.addStep(and(inStreambound, locatedKeystone1), enterEarthboundCavernFromStreambound); + goLocateKeystone.addStep(and(inNeypotzli, locatedKeystone1), enterEarthboundCavern); + + goLocateKeystone.addStep(inStreambound, locateInStreambound); + goLocateKeystone.addStep(inNeypotzli, enterStreamboundCavern); + goLocateKeystone.addStep(inCamTorum, enterNeypotzli); + steps.put(39, goLocateKeystone); + steps.put(40, goLocateKeystone); + + ConditionalStep goTouchSymbol = new ConditionalStep(this, enterCamTorum); + goTouchSymbol.addStep(and(inNeypotzliFightRoom), defeatCultists); + goTouchSymbol.addStep(and(inPrison), touchGlowingSymbol); + goTouchSymbol.addStep(and(inEarthbound), enterAncientPrisonFromEarthbound); + goTouchSymbol.addStep(and(inNeypotzli), enterAncientPrison); + goTouchSymbol.addStep(inCamTorum, enterNeypotzli); + steps.put(41, goTouchSymbol); + // Skipped 42? + steps.put(43, goTouchSymbol); + + ConditionalStep goTalkToAttalaAfterFight = new ConditionalStep(this, enterCamTorum); + goTalkToAttalaAfterFight.addStep(and(inNeypotzliFightRoom), talkToAttalaAfterCultistFight); + goTalkToAttalaAfterFight.addStep(and(inPrison), touchGlowingSymbol); + goTalkToAttalaAfterFight.addStep(and(inEarthbound), enterAncientPrisonFromEarthbound); + goTalkToAttalaAfterFight.addStep(and(inNeypotzli), enterAncientPrison); + goTalkToAttalaAfterFight.addStep(inCamTorum, enterNeypotzli); + steps.put(44, goTalkToAttalaAfterFight); + + steps.put(45, talkToServiusAtTalTeklan); + steps.put(46, enterTonaliCavern); + + ConditionalStep goDefeatFinalCultists = new ConditionalStep(this, enterTonaliCavern); + goDefeatFinalCultists.addStep(inTonaliCavern, defeatFinalCultists); + steps.put(47, goDefeatFinalCultists); + + ConditionalStep goDefeatEnnius = new ConditionalStep(this, enterTonaliCavern); + goDefeatEnnius.addStep(inTonaliCavern, fightEnnius); + steps.put(48, goDefeatEnnius); + steps.put(49, goDefeatEnnius); + steps.put(50, goDefeatEnnius); + steps.put(51, goDefeatEnnius); + + ConditionalStep goDoSunPuzzle = new ConditionalStep(this, getEssenceFromUrns); + goDoSunPuzzle.addStep(not(inspectedSunStatue), inspectSunStatue); + goDoSunPuzzle.addStep(completedSunPuzzleP3, goUpFromSunPuzzle); + goDoSunPuzzle.addStep(and(completedSunPuzzleP2, essence, isPuzzleOrder2, itzlaInPosSunPuzzle2Step3), solveSunPuzzle2Step3Craft); + goDoSunPuzzle.addStep(and(completedSunPuzzleP2, essence, isPuzzleOrder2), solveSunPuzzle2Step3MoveItzla); + + goDoSunPuzzle.addStep(and(completedSunPuzzleP1, essence, isPuzzleOrder2, itzlaInPosSunPuzzle2Step2), solveSunPuzzle2Step2Craft); + goDoSunPuzzle.addStep(and(completedSunPuzzleP1, essence, isPuzzleOrder2), solveSunPuzzle2Step2MoveItzla); + + goDoSunPuzzle.addStep(and(essence, isPuzzleOrder2, itzlaInPosSunPuzzle2Step1), solveSunPuzzle2Step1Craft); + goDoSunPuzzle.addStep(and(essence, isPuzzleOrder2), solveSunPuzzle2Step1MoveItzla); + + goDoSunPuzzle.addStep(and(completedSunPuzzleP2, essence, isPuzzleOrder1, itzlaInPosSunPuzzle1Step3), solveSunPuzzle1Step3Craft); + goDoSunPuzzle.addStep(and(completedSunPuzzleP2, essence, isPuzzleOrder1), solveSunPuzzle1Step3MoveItzla); + + goDoSunPuzzle.addStep(and(completedSunPuzzleP1, essence, isPuzzleOrder1, itzlaInPosSunPuzzle1Step2), solveSunPuzzle1Step2Craft); + goDoSunPuzzle.addStep(and(completedSunPuzzleP1, essence, isPuzzleOrder1), solveSunPuzzle1Step2MoveItzla); + + goDoSunPuzzle.addStep(and(essence, isPuzzleOrder1, itzlaInPosSunPuzzle1Step1), solveSunPuzzle1Step1Craft); + goDoSunPuzzle.addStep(and(essence, isPuzzleOrder1), solveSunPuzzle1Step1MoveItzla); + + goDoSunPuzzle.addStep(and(essence), solveSunPuzzle); + + ConditionalStep goDoMoonPuzzle = new ConditionalStep(this, getKnifeBlade); + goDoMoonPuzzle.addStep(completedMoonPuzzle, leaveMoonPuzzleRoom); + goDoMoonPuzzle.addStep(not(southPlatformSolutionKnown), moveItzlaSouth); + goDoMoonPuzzle.addStep(not(northPlatformSolutionKnown), moveItzlaNorth); + goDoMoonPuzzle.addStep(and(kindling), placeRoots); + goDoMoonPuzzle.addStep(and(roots, knifeBlade), fletchRoots); + goDoMoonPuzzle.addStep(knifeBlade, pullTreeRoots); + + ConditionalStep goDeeperIntoTonali = new ConditionalStep(this, enterTonaliCavern); + goDeeperIntoTonali.addStep(and(inMoonPuzzleRoom), goDoMoonPuzzle); + goDeeperIntoTonali.addStep(and(inSunPuzzleRoom), goDoSunPuzzle); + goDeeperIntoTonali.addStep(and(inTonaliCavernF2North, liftActivated, completedSunPuzzleP3), enterMoonPuzzle); + goDeeperIntoTonali.addStep(and(inTonaliCavernF2North, liftActivated), descendIntoSunPuzzle); + goDeeperIntoTonali.addStep(inTonaliCavernF2North, activateStrangePlatform); + goDeeperIntoTonali.addStep(inTonaliCavernF1Nagua, climbRope); + goDeeperIntoTonali.addStep(inTonaliCavernF0P3, useRedTeleporter3); + goDeeperIntoTonali.addStep(inTonaliCavernF0P2North, useRedTeleporter3); + goDeeperIntoTonali.addStep(inTonaliCavernF1GrimyLizards, useBlueTeleporterLizards); + goDeeperIntoTonali.addStep(inTonaliCavernF0P2South, useRedTeleporter2); + goDeeperIntoTonali.addStep(inTonaliCavernF1Rockslugs, useBlueTeleporter); + goDeeperIntoTonali.addStep(inTonaliCavernF0Start, useRedTeleporter); + goDeeperIntoTonali.addStep(inTonaliCavernF1Stairs, tonaliGoDownStairsF1ToF0); + goDeeperIntoTonali.addStep(inTonaliCavern, tonaliGoDownStairsF2ToF1); + goDeeperIntoTonali.addStep(liftActivated, enterTonaliWithLift); + steps.put(52, goDeeperIntoTonali); + steps.put(53, goDeeperIntoTonali); + steps.put(54, goDeeperIntoTonali); + + steps.put(55, goDeeperIntoTonali); + + ConditionalStep goFinalFight = new ConditionalStep(this, goDeeperIntoTonali); + goFinalFight.addStep(and(inFinalBossArea), approachMetzli); + goFinalFight.addStep(and(inTonaliCavernF2North, liftActivated), enterFinalBossArea); + steps.put(60, goFinalFight); + steps.put(61, goFinalFight); + + ConditionalStep fightFinalBoss = new ConditionalStep(this, goDeeperIntoTonali); + fightFinalBoss.addStep(and(inFinalBossArea), defeatFinalBoss); + fightFinalBoss.addStep(and(inTonaliCavernF2North, liftActivated), enterFinalBossArea); + steps.put(62, fightFinalBoss); + // Defeated boss + + ConditionalStep doFinalBossPostCutscene = new ConditionalStep(this, goDeeperIntoTonali); + doFinalBossPostCutscene.addStep(and(inFinalBossArea), watchFinalBossAfterCutscene); + doFinalBossPostCutscene.addStep(and(inTonaliCavernF2North, liftActivated), enterFinalBossArea); + steps.put(63, doFinalBossPostCutscene); + + ConditionalStep goNorthAfterFinalBoss = new ConditionalStep(this, goDeeperIntoTonali); + goNorthAfterFinalBoss.addStep(and(inFinalBossArea, is72Agility), goToNorthOfFinalAreaAgilityShortcut); + goNorthAfterFinalBoss.addStep(inFinalBossArea, goToNorthOfFinalArea); + steps.put(64, goNorthAfterFinalBoss); + + // Inspected + + // Look at tablet, statues + ConditionalStep goInspectFinalChamberItems = new ConditionalStep(this, goDeeperIntoTonali); + goInspectFinalChamberItems.addStep(and(inFinalBossArea, notInspectedRalosPillar), inspectRalosPillar); + goInspectFinalChamberItems.addStep(and(inFinalBossArea, notInspectedRanulPillar), inspectRanulPillar); + goInspectFinalChamberItems.addStep(and(inFinalBossArea, notInspectedDoor), inspectDoor); + goInspectFinalChamberItems.addStep(and(inFinalBossArea, notInspectedSkeleton, stoneTablet), readStoneTablet); + goInspectFinalChamberItems.addStep(and(inFinalBossArea, notInspectedSkeleton), inspectSkeleton); + goInspectFinalChamberItems.addStep(and(inTonaliCavernF2North, liftActivated), enterFinalBossArea); + steps.put(65, goInspectFinalChamberItems); + + ConditionalStep goFinishQuest = new ConditionalStep(this, enterTonaliCavern); + goFinishQuest.addStep(inTonaliCavern, finishQuest); + steps.put(66, goFinishQuest); + steps.put(67, goFinishQuest); + return steps; + } + + @Subscribe + public void onGameTick(GameTick gameTick) + { + Requirement darkBlueFlameMissing = not(new ObjectCondition(ObjectID.VMQ4_MOON_PUZZLE_FIRE_1, new WorldPoint(1296, 9455, 1))); + Requirement lightBlueFlameMissing = not(new ObjectCondition(ObjectID.VMQ4_MOON_PUZZLE_FIRE_2, new WorldPoint(1292, 9454, 1))); + if (inMoonPuzzleRoom == null || !inMoonPuzzleRoom.check(client)) return; + + int currentStep = client.getVarbitValue(VarbitID.VMQ4_MOON_PUZZLE_PROGRESS); + if (currentStep != lastKnownStateStep) + { + lastKnownStateDarkFlame = -1; + lastKnownStateLightFlame = -1; + northPlatformSolutionKnown.setShouldPass(false); + southPlatformSolutionKnown.setShouldPass(false); + lastKnownStateStep = currentStep; + placeRoots.setText("Have Itzla move between both the north and south " + + "platforms to see how many braziers are lit around the room total. Put that many into the statue."); + kindling.setQuantity(-1); + } + + if (lastKnownStateDarkFlame != -1 && lastKnownStateLightFlame != -1) return; + + if (darkBlueFlameMissing.check(client)) + { + lastKnownStateDarkFlame = getSumOfLitBraziers(); + northPlatformSolutionKnown.setShouldPass(true); + } + else if (lightBlueFlameMissing.check(client)) + { + lastKnownStateLightFlame = getSumOfLitBraziers(); + southPlatformSolutionKnown.setShouldPass(true); + } + + if (lastKnownStateDarkFlame >= 0 && lastKnownStateLightFlame >= 0) + { + int total = lastKnownStateLightFlame + lastKnownStateDarkFlame; + placeRoots.setText("Place " + total + " roots in the moon statue."); + kindling.setQuantity(total); + } + } + + private int getSumOfLitBraziers() + { + Tile[][] tiles; + if (client.getTopLevelWorldView().getScene() == null) return -1; + + List wps = List.of( + new WorldPoint(1296, 9457, 1), + new WorldPoint(1289, 9457, 1), + new WorldPoint(1286, 9454, 1), + new WorldPoint(1282, 9449, 1), + new WorldPoint(1282, 9443, 1), + new WorldPoint(1286, 9438, 1), + new WorldPoint(1289, 9435, 1), + new WorldPoint(1296, 9435, 1) + + ); + + int total = 0; + + for (WorldPoint wp : wps) + { + List localPoints = QuestPerspective.getInstanceLocalPointFromReal(client, wp); + + if (localPoints.isEmpty()) return -1; + + tiles = client.getTopLevelWorldView().getScene().getTiles()[client.getTopLevelWorldView().getPlane()]; + + for (LocalPoint localPoint : localPoints) + { + Tile b1Tile = tiles[localPoint.getSceneX()][localPoint.getSceneY()]; + boolean isMatch = Arrays.stream(b1Tile.getGameObjects()).anyMatch((obj) -> obj != null && obj.getId() == ObjectID.VMQ4_MOON_PUZZLE_BRAZIER_LIT); + if (isMatch) total++; + } + } + + return total; + } + + @Override + protected void setupZones() + { + templeArea = new Zone(new WorldPoint(1613, 3205, 0), new WorldPoint(1729, 3293, 0)); + templeBasement = new Zone(new WorldPoint(1660, 9680, 0), new WorldPoint(1725, 9720, 0)); + eastTempleBasement = new Zone(new WorldPoint(1707, 9696, 0), new WorldPoint(1718, 9715, 0)); + hiddenRoom = new Zone(new WorldPoint(1721, 9702, 0), new WorldPoint(1725, 9709, 0)); + palaceF1 = new Zone(new WorldPoint(1669, 3150, 1), new WorldPoint(1692, 3175, 1)); + palaceF2 = new Zone(new WorldPoint(1669, 3150, 2), new WorldPoint(1692, 3175, 2)); + hideoutGroundFloor = new Zone(new WorldPoint(1643, 3091, 0), new WorldPoint(1652, 3096, 0)); + hideoutMiddleFloor = new Zone(new WorldPoint(1643, 3091, 1), new WorldPoint(1652, 3096, 1)); + hideoutTopFloor = new Zone(new WorldPoint(1643, 3091, 2), new WorldPoint(1652, 3102, 2)); + hideoutBasement = new Zone(new WorldPoint(1643, 9486, 0), new WorldPoint(1657, 9500, 0)); + camTorum = new Zone(new WorldPoint(1378, 9502, 0), new WorldPoint(1524, 9600, 3)); + camTorumF2 = new Zone(new WorldPoint(1465, 9567, 2), new WorldPoint(1470, 9572, 2)); + camTorumBasement = new Zone(new WorldPoint(1464, 9564, 0), new WorldPoint(1472, 9574, 0)); + hiddenTunnel = new Zone(new WorldPoint(1468, 9561, 0), new WorldPoint(1476, 9563, 0)); + hiddenTunnel2 = new Zone(new WorldPoint(1477, 9536, 0), new WorldPoint(1500, 9570, 0)); + + antechamber = new Zone(5782, 1); + prison = new Zone(5525, 0); + earthbound = new Zone(5527, 0); + streambound = new Zone(6039, 0); + ancientShrine = new Zone(6037, 0); + + neypotzliFightRoom = new Zone(new WorldPoint(1352, 9501, 0), new WorldPoint(1372, 9524, 0)); + tonaliCavernF1Stairs = new Zone(new WorldPoint(1329, 9360, 1), new WorldPoint(1343, 9369, 1)); + tonaliCavernF1Rockslugs = new Zone(new WorldPoint(1319, 9374, 1), new WorldPoint(1329, 9386, 1)); + tonaliCavernF0Start = new Zone(new WorldPoint(1317, 9366, 0), new WorldPoint(1341, 9385, 0)); + tonaliCavernF0P2South = new Zone(new WorldPoint(1282, 9365, 0), new WorldPoint(1315, 9391, 0)); + tonaliCavernF0P2North = new Zone(new WorldPoint(1282, 9392, 0), new WorldPoint(1305, 9403, 0)); + tonaliCavernF2 = new Zone(new WorldPoint(1298, 9344, 2), new WorldPoint(1343, 9380, 2)); + + tonaliCavernF0P3V1 = new Zone(new WorldPoint(1306, 9392, 0), new WorldPoint(1340, 9420, 0)); + tonaliCavernF0P3V2 = new Zone(new WorldPoint(1320, 9387, 0), new WorldPoint(1340, 9391, 0)); + + tonaliCavernF1GrimyLizards = new Zone(new WorldPoint(1291, 9381, 1), new WorldPoint(1298, 9395, 1)); + tonaliCavernF1Nagua = new Zone(new WorldPoint(1283, 9399, 1), new WorldPoint(1310, 9425, 1)); + tonaliCavernF2North = new Zone(new WorldPoint(1303, 9399, 2), new WorldPoint(1317, 9468, 2)); + sunPuzzleRoom = new Zone(new WorldPoint(1318, 9433, 1), new WorldPoint(1345, 9458, 1)); + moonPuzzleRoom = new Zone(new WorldPoint(1279, 9433, 1), new WorldPoint(1305, 9460, 1)); + + finalBossArea = new Zone(new WorldPoint(1275, 9470, 0), new WorldPoint(1350, 9550, 1)); + } + + @Override + protected void setupRequirements() + { + var emissaryHood = new ItemRequirement("Emissary hood", ItemID.VMQ3_CULTIST_HOOD); + var emissaryTop = new ItemRequirement("Emissary top", ItemID.VMQ3_CULTIST_ROBE_TOP); + var emissaryBottom = new ItemRequirement("Emissary bottom", ItemID.VMQ3_CULTIST_ROBE_BOTTOM); + var emissaryBoots = new ItemRequirement("Emissary sandals", ItemID.VMQ3_CULTIST_SANDALS); + emissaryRobesEquipped = new ItemRequirements("Emissary robes", emissaryHood, emissaryTop, emissaryBottom, + emissaryBoots).equipped().highlighted(); + emissaryRobes = new ItemRequirements("Emissary robes", emissaryHood, emissaryTop, emissaryBottom, emissaryBoots); + + var givenBoneToDog = new VarbitRequirement(VarbitID.VMQ4, 17, Operation.GREATER_EQUAL); + bone = new ItemRequirement("Any type of bone or raw meat", ItemID.BONES); + bone.setConditionToHide(givenBoneToDog); + bone.addAlternates(ItemID.BIG_BONES, ItemID.BONES_BURNT, ItemID.WOLF_BONES, ItemID.BAT_BONES, ItemID.DAGANNOTH_KING_BONES, ItemID.TBWT_BEAST_BONES, + ItemID.WYRM_BONES, ItemID.BABYWYRM_BONES, ItemID.BABYDRAGON_BONES, ItemID.WYVERN_BONES, ItemID.DRAGON_BONES, ItemID.DRAKE_BONES, + ItemID.HYDRA_BONES, ItemID.LAVA_DRAGON_BONES, ItemID.DRAGON_BONES_SUPERIOR, ItemID.MM_NORMAL_MONKEY_BONES, + ItemID.MM_BEARDED_GORILLA_MONKEY_BONES, ItemID.MM_NORMAL_GORILLA_MONKEY_BONES, ItemID.MM_LARGE_ZOMBIE_MONKEY_BONES, + ItemID.MM_SMALL_ZOMBIE_MONKEY_BONES, ItemID.MM_SMALL_NINJA_MONKEY_BONES, ItemID.MM_MEDIUM_NINJA_MONKEY_BONES, ItemID.TBWT_JOGRE_BONES, + ItemID.TBWT_BURNT_JOGRE_BONES, ItemID.ZOGRE_BONES, ItemID.ZOGRE_ANCESTRAL_BONES_FAYG, ItemID.ZOGRE_ANCESTRAL_BONES_RAURG, + ItemID.ZOGRE_ANCESTRAL_BONES_OURG, ItemID.ALAN_BONES, ItemID.RAW_BEAR_MEAT, ItemID.RAW_BOAR_MEAT, ItemID.RAW_RAT_MEAT, + ItemID.RAW_UGTHANKI_MEAT, ItemID.YAK_MEAT_RAW); + + rangedGear = new ItemRequirement("Ranged/Magic Combat gear", -1, -1).isNotConsumed(); + rangedGear.setDisplayItemId(BankSlotIcons.getRangedCombatGear()); + + // Item Recommended + combatWeapon = new ItemRequirement("Combat weapon", -1, -1).isNotConsumed(); + combatWeapon.setDisplayItemId(BankSlotIcons.getCombatGear()); + + combatGear = new ItemRequirement("Melee Combat gear", -1, -1).isNotConsumed(); + combatGear.setDisplayItemId(BankSlotIcons.getCombatGear()); + + food = new ItemRequirement("Food", ItemCollections.GOOD_EATING_FOOD, -1); + food.setUrlSuffix("Food"); + + prayerPotions = new ItemRequirement("Prayer potions", ItemCollections.PRAYER_POTIONS, 3); + + whistle = new ItemRequirement("Quetzal whistle", ItemID.HG_QUETZALWHISTLE_BASIC); + whistle.addAlternates(ItemID.HG_QUETZALWHISTLE_ENHANCED, ItemID.HG_QUETZALWHISTLE_PERFECTED); + pendant = new ItemRequirement("Pendant of ates", ItemID.PENDANT_OF_ATES); + pendantToTwilight = new ItemRequirement("Pendant of ates ([2] Twilight Temple)", ItemID.PENDANT_OF_ATES); + civitasTeleport = new ItemRequirement("Civitas illa fortis teleport", ItemID.POH_TABLET_FORTISTELEPORT); + // Quest items + drawerKey = new ItemRequirement("Key", ItemID.VMQ4_DRAWER_KEY); + canvasPiece = new ItemRequirement("Canvas piece", ItemID.VMQ4_PAINTING_SIGIL); + emissaryScroll = new ItemRequirement("Emissary scroll", ItemID.VMQ4_CULT_MANIFEST); + knife = new ItemRequirement("Knife", ItemID.KNIFE); + potatoes = new ItemRequirement("Potatoes (?)", ItemID.SACK_POTATO_3); + potatoes.addAlternates(ItemID.SACK_POTATO_2, ItemID.SACK_POTATO_1); + + var givenSack = new VarbitRequirement(VarbitID.VMQ4_JANUS_SACK_GIVEN, 1, Operation.GREATER_EQUAL); + emptySack = new ItemRequirement("Empty sack", ItemID.SACK_EMPTY); + emptySack.setConditionToHide(givenSack); + coinPurse = new ItemRequirement("Coin purse", ItemID.VMQ4_JANUS_PURSE); + + coinPurseFullOrEmpty = new ItemRequirement("Coin purse", ItemID.VMQ4_JANUS_PURSE); + coinPurseFullOrEmpty.addAlternates(ItemID.VMQ4_JANUS_PURSE_EMPTY); + coinPurseEmpty = new ItemRequirement("Empty coin purse", ItemID.VMQ4_JANUS_PURSE_EMPTY); + coinPurseWithSand = new ItemRequirement("Sandy coin purse", ItemID.VMQ4_JANUS_PURSE_SAND); + branch = new ItemRequirement("Branch", ItemID.VMQ4_JANUS_REED); + makeshiftBlackjack = new ItemRequirement("Makeshift blackjack", ItemID.VMQ4_JANUS_SLAP); + + beer = new ItemRequirement("Beer", ItemID.BEER); + steamforgedBrew = new ItemRequirement("Steamforge brew", ItemID.STEAMFORGE_BREW); + dwarvenStout = new ItemRequirement("Dwarven stout", ItemID.DWARVEN_STOUT); + emptyGlass = new ItemRequirement("Empty glass", ItemID.BEER_GLASS); + wizardsMindBomb = new ItemRequirement("Wizard's mind bomb", ItemID.WIZARDS_MIND_BOMB); + + keystoneFragment = new ItemRequirement("Keystone fragment", ItemID.VMQ4_MONOLITH_FRAGMENT); + essence = new ItemRequirement("Kuhu essence", ItemID.VMQ4_ESSENCE, 2); + roots = new ItemRequirement("Ancient roots", ItemID.VMQ4_ROOTS); + kindling = new ItemRequirement("Root kindling", ItemID.VMQ4_ROOT_KINDLING); + knifeBlade = new ItemRequirement("Knife blade", ItemID.VMQ4_KNIFE); + + stoneTablet = new ItemRequirement("Stone tablet", ItemID.VMQ4_MOKI_TABLET); + + // Quest requirements + inTempleArea = new ZoneRequirement(templeArea); + inTempleBasement = new ZoneRequirement(templeBasement); + inEastTempleBasement = new ZoneRequirement(eastTempleBasement); + inHiddenRoom = new ZoneRequirement(hiddenRoom); + inPalaceF1 = new ZoneRequirement(palaceF1); + inPalaceF2 = new ZoneRequirement(palaceF2); + inHideout = new ZoneRequirement(hideoutGroundFloor); + inHideoutF1 = new ZoneRequirement(hideoutMiddleFloor); + inHideoutF2 = new ZoneRequirement(hideoutTopFloor); + inHideoutBasement = new ZoneRequirement(hideoutBasement); + inCamTorum = new ZoneRequirement(camTorum); + inCamTorumF2 = new ZoneRequirement(camTorumF2); + inCamTorumBasement = new ZoneRequirement(camTorumBasement); + inCamTorumHiddenTunnel = new ZoneRequirement(hiddenTunnel, hiddenTunnel2); + + inAntechamber = new ZoneRequirement(antechamber); + inPrison = new ZoneRequirement(prison); + inStreambound = new ZoneRequirement(streambound); + inEarthbound = new ZoneRequirement(earthbound); + inAncientShrine = new ZoneRequirement(ancientShrine); + inNeypotzli = new ZoneRequirement(antechamber, prison, streambound, earthbound, ancientShrine); + inNeypotzliFightRoom = new ZoneRequirement(neypotzliFightRoom); + + inTonaliCavern = new ZoneRequirement(tonaliCavernF2); + inTonaliCavernF1Stairs = new ZoneRequirement(tonaliCavernF1Stairs); + inTonaliCavernF1Rockslugs = new ZoneRequirement(tonaliCavernF1Rockslugs); + inTonaliCavernF0Start = new ZoneRequirement(tonaliCavernF0Start); + inTonaliCavernF0P2South = new ZoneRequirement(tonaliCavernF0P2South); + inTonaliCavernF0P2North = new ZoneRequirement(tonaliCavernF0P2North); + inTonaliCavernF0P3 = new ZoneRequirement(tonaliCavernF0P3V1, tonaliCavernF0P3V2); + inTonaliCavernF1GrimyLizards = new ZoneRequirement(tonaliCavernF1GrimyLizards); + inTonaliCavernF1Nagua = new ZoneRequirement(tonaliCavernF1Nagua); + inTonaliCavernF2North = new ZoneRequirement(tonaliCavernF2North); + inSunPuzzleRoom = new ZoneRequirement(sunPuzzleRoom); + inMoonPuzzleRoom = new ZoneRequirement(moonPuzzleRoom); + inFinalBossArea = new ZoneRequirement(finalBossArea); + + isSouthDrawer = new VarbitRequirement(VarbitID.VMQ4_CANVAS_DRAWER, 2); + hasDrawerKeyOrOpened = or(drawerKey, new VarbitRequirement(VarbitID.VMQ4_TEMPLE_DRAW_UNLOCKED, 1, Operation.GREATER_EQUAL)); + usedSigilOnCanvas = new VarbitRequirement(VarbitID.VMQ4, 7, Operation.GREATER_EQUAL); + emissaryScrollNearby = new ItemOnTileRequirement(emissaryScroll); + inChestInterface = new WidgetTextRequirement(809, 5, 9, "Confirm"); + + hasSackOfGivenSack = or(emptySack, givenSack); + isGalnaDrunk = new VarbitRequirement(VarbitID.VMQ4_TEUMO_WIFE_BEER_GIVEN, 1); + + notPlacedMindBomb = not(new ItemOnTileRequirement(ItemID.WIZARDS_MIND_BOMB, new WorldPoint(1471, 9567, 0))); + notPlacedBeer = not(new ItemOnTileRequirement(ItemID.BEER, new WorldPoint(1466, 9573, 0))); + notPlacedSteamforgeBrew = not(new ItemOnTileRequirement(ItemID.STEAMFORGE_BREW, new WorldPoint(1464, 9568, 0))); + notPlacedDwarvenStout = not(new ItemOnTileRequirement(ItemID.DWARVEN_STOUT, new WorldPoint(1466, 9568, 0))); + beerTakenFromBarrel = not(new ItemOnTileRequirement(ItemID.BEER, new WorldPoint(1469, 9571, 0))); + + locatedKeystone1 = new VarbitRequirement(VarbitID.VMQ4_NEYPOTZLI_CHECKPOINT, 1, Operation.GREATER_EQUAL); + locatedKeystone2 = new VarbitRequirement(VarbitID.VMQ4_NEYPOTZLI_CHECKPOINT, 2, Operation.GREATER_EQUAL); + + liftActivated = new VarbitRequirement(VarbitID.VMQ4_LIFT_ACTIVATED, 1); + + // Sun puzzle + inspectedSunStatue = or(new VarbitRequirement(VarbitID.VMQ4_SUN_PUZZLE_PROGRESS, 1, Operation.GREATER_EQUAL), new VarbitRequirement(VarbitID.VMQ4_SUN_PUZZLE_ITZLA_STATE, 1, Operation.GREATER_EQUAL)); + itzlaInPosSunPuzzle1Step1 = new NpcRequirement("Itzla at east altar", NpcID.VMQ4_ITZLA_CRYPT_PUZZLE_SUN, new WorldPoint(1338, 9446, 1)); + itzlaInPosSunPuzzle1Step2 = new NpcRequirement("Itzla at north altar", NpcID.VMQ4_ITZLA_CRYPT_PUZZLE_SUN, new WorldPoint(1330, 9454, 1)); + itzlaInPosSunPuzzle1Step3 = new NpcRequirement("Itzla at north-west altar", NpcID.VMQ4_ITZLA_CRYPT_PUZZLE_SUN, new WorldPoint(1324, 9452, 1)); + + itzlaInPosSunPuzzle2Step1 = new NpcRequirement("Itzla at north altar", NpcID.VMQ4_ITZLA_CRYPT_PUZZLE_SUN, new WorldPoint(1330, 9454, 1)); + itzlaInPosSunPuzzle2Step2 = new NpcRequirement("Itzla at north altar", NpcID.VMQ4_ITZLA_CRYPT_PUZZLE_SUN, new WorldPoint(1338, 9446, 1)); + itzlaInPosSunPuzzle2Step3 = new NpcRequirement("Itzla at north altar", NpcID.VMQ4_ITZLA_CRYPT_PUZZLE_SUN, new WorldPoint(1324, 9452, 1)); + completedSunPuzzleP1 = new VarbitRequirement(VarbitID.VMQ4_SUN_PUZZLE_PROGRESS, 1, Operation.GREATER_EQUAL); + completedSunPuzzleP2 = new VarbitRequirement(VarbitID.VMQ4_SUN_PUZZLE_PROGRESS, 2, Operation.GREATER_EQUAL); + completedSunPuzzleP3 = new VarbitRequirement(VarbitID.VMQ4_SUN_PUZZLE_PROGRESS, 3, Operation.GREATER_EQUAL); + isPuzzleOrder1 = new VarbitRequirement(VarbitID.VMQ4_SUN_PUZZLE_ORDER, 1); + isPuzzleOrder2 = new VarbitRequirement(VarbitID.VMQ4_SUN_PUZZLE_ORDER, 2); + + inMoonPuzzleP1 = new VarbitRequirement(VarbitID.VMQ4_MOON_PUZZLE_PROGRESS, 0); + inMoonPuzzleP2 = new VarbitRequirement(VarbitID.VMQ4_MOON_PUZZLE_PROGRESS, 1); + inMoonPuzzleP3 = new VarbitRequirement(VarbitID.VMQ4_MOON_PUZZLE_PROGRESS, 2); + completedMoonPuzzle = new VarbitRequirement(VarbitID.VMQ4_MOON_PUZZLE_PROGRESS, 3); + northPlatformSolutionKnown = new ManualRequirement(); + southPlatformSolutionKnown = new ManualRequirement(); + + is72Agility = new SkillRequirement(Skill.AGILITY, 72, true); + + notInspectedRalosPillar = not(new VarbitRequirement(VarbitID.VMQ4_FINAL_CHAMBER_RALOS_INSPECT, 1)); + notInspectedRanulPillar = not(new VarbitRequirement(VarbitID.VMQ4_FINAL_CHAMBER_RANUL_INSPECT, 1)); + notInspectedSkeleton = not(new VarbitRequirement(VarbitID.VMQ4_FINAL_CHAMBER_TABLET_INSPECT, 1)); + notInspectedDoor = not(new VarbitRequirement(VarbitID.VMQ4_FINAL_CHAMBER_DOOR_INSPECT, 1)); + quetzalMadeSalvagerOverlook = new VarbitRequirement(VarbitID.QUETZAL_SALVAGEROVERLOOK, 1); + hasAtesAndActivatedTeleport = and(pendant.alsoCheckBank(questBank), new VarbitRequirement(VarbitID.PENDANT_OF_ATES_TWILIGHT_FOUND, 1, Operation.GREATER_EQUAL)); + } + + public void setupSteps() + { + startQuest = new NpcStep(this, NpcID.VMQ3_SERVIUS_PALACE, new WorldPoint(1681, 3168, 0), "Talk to Servius in the Sunrise Palace in Civitas illa " + + "Fortis to start the quest."); + startQuest.addDialogStep("Yes."); + + goToTempleWithAtes = new DetailedQuestStep(this, new WorldPoint(1657, 3231, 0), "Use the pendant of ates to the Twilight Temple, or go there via" + + " Quetzal.", pendantToTwilight.highlighted()); + goToTempleWithAtes.addWidgetHighlight(new WidgetHighlight(InterfaceID.PendantOfAtes.TELEPORT_TWILIGHT, true).withModelRequirement(54541)); + + goToTempleFromSalvager = new DetailedQuestStep(this, new WorldPoint(1657, 3231, 0), "Take a quetzal to the Salvager Overlook and run south to the " + + "Twilight Temple."); + goToTempleFromSalvager.addWidgetHighlight(new WidgetHighlight(InterfaceID.QuetzalMenu.ICONS, true).withModelRequirement(54546)); + ((DetailedQuestStep) goToTempleFromSalvager).setLinePoints(List.of( + new WorldPoint(1613, 3299, 0), + new WorldPoint(1630, 3293, 0), + new WorldPoint(1644, 3279, 0), + new WorldPoint(1644, 3263, 0), + new WorldPoint(1639, 3254, 0), + new WorldPoint(1638, 3244, 0), + new WorldPoint(1644, 3240, 0), + new WorldPoint(1649, 3240, 0), + new WorldPoint(1657, 3231, 0) + )); + goToTempleFromGorge = new DetailedQuestStep(this, new WorldPoint(1657, 3231, 0), "Take a quetzal to the Salvager Overlook and run south to the " + + "Twilight Temple."); + goToTempleFromGorge.addWidgetHighlight(new WidgetHighlight(InterfaceID.QuetzalMenu.ICONS, true).withModelRequirement(54539)); + ((DetailedQuestStep) goToTempleFromGorge).setLinePoints(List.of( + new WorldPoint(1510, 3226, 0), + new WorldPoint(1522, 3252, 0), + new WorldPoint(1529, 3254, 0), + new WorldPoint(1538, 3267, 0), + new WorldPoint(1542, 3277, 0), + new WorldPoint(1551, 3274, 0), + new WorldPoint(1561, 3279, 0), + new WorldPoint(1581, 3279, 0), + new WorldPoint(1593, 3269, 0), + new WorldPoint(1600, 3268, 0), + new WorldPoint(1610, 3260, 0), + new WorldPoint(1618, 3254, 0), + new WorldPoint(1621, 3254, 0), + new WorldPoint(1644, 3240, 0), + new WorldPoint(1649, 3240, 0), + new WorldPoint(1657, 3231, 0) + )); + freeInvSlots4 = new FreeInventorySlotRequirement(4); + freeInvSlot1 = new FreeInventorySlotRequirement(1); + searchChestForEmissaryRobes = new ObjectStep(this, ObjectID.VMQ3_CULTIST_OUTFIT_CHEST, new WorldPoint(1638, 3217, 0), "Search the chest in the south " + + "of the Tower of Ascension south of Salvager Overlook for some emissary robes.", freeInvSlots4); + searchChestForEmissaryRobes.addSubSteps(goToTempleWithAtes, goToTempleFromGorge, goToTempleFromSalvager); + searchChestForEmissaryRobes.addWidgetHighlight(new WidgetHighlight(InterfaceID.QuetzalMenu.ICONS, true).withModelRequirement(54546)); + ((ObjectStep) searchChestForEmissaryRobes).addTeleport(pendant); + enterTwilightTemple = new DetailedQuestStep(this, new WorldPoint(1687, 3247, 0), "Enter the temple south-east of Salvager Overlook.", + emissaryRobesEquipped); + + var goDownStairsTempleBaseStep = new ObjectStep(this, ObjectID.TWILIGHT_TEMPLE_STAIRS, new WorldPoint(1677, 3248, 0), "Go down the " + + "stairs in the temple. The passphrase is 'Final' and 'Dawn'.", List.of(emissaryRobesEquipped), List.of(combatWeapon, food)); + goDownStairsTempleBaseStep.addDialogSteps("Final.", "Dawn."); + goDownStairsTemple = new PuzzleWrapperStep(this, goDownStairsTempleBaseStep, + new ObjectStep(this, ObjectID.TWILIGHT_TEMPLE_STAIRS, new WorldPoint(1677, 3248, 0), "Go down the " + + "stairs in the temple. You'll need to guess the password.", List.of(emissaryRobesEquipped), List.of(combatWeapon, food))); + + enterBackroom = new ObjectStep(this, ObjectID.TWILIGHT_TEMPLE_METZLI_CHAMBER_ENTRY, new WorldPoint(1706, 9706, 0), "Enter the far eastern room. Avoid" + + " the patrolling guard."); + + searchBed = new ObjectStep(this, ObjectID.VMQ4_TEMPLE_BED_WITH_KEY, new WorldPoint(1713, 9698, 0), "Search the bed in the south room."); + openDrawers = new ObjectStep(this, ObjectID.VMQ4_TEMPLE_CANVAS_DRAW_1_CLOSED, new WorldPoint(1713, 9714, 0), "Open the drawers in the north room."); + ((ObjectStep) openDrawers).addAlternateObjects(ObjectID.VMQ4_TEMPLE_CANVAS_DRAW_1_OPEN); + openDrawers.conditionToHideInSidebar(isSouthDrawer); + + openDrawers2 = new ObjectStep(this, ObjectID.VMQ4_TEMPLE_CANVAS_DRAW_2_CLOSED, new WorldPoint(1709, 9700, 0), "Open the drawers in the same room."); + ((ObjectStep) openDrawers2).addAlternateObjects(ObjectID.VMQ4_TEMPLE_CANVAS_DRAW_2_OPEN); + openDrawers2.conditionToHideInSidebar(not(isSouthDrawer)); + useCanvasPieceOnPicture = new ObjectStep(this, ObjectID.TWILIGHT_TEMPLE_METZLI_PAINTING, new WorldPoint(1719, 9706, 0), "Use canvas piece on the " + + "painting in the east of the middle room of the eastern rooms.", canvasPiece.highlighted()); + useCanvasPieceOnPicture.addIcon(ItemID.VMQ4_PAINTING_SIGIL); + enterPassage = new ObjectStep(this, ObjectID.TWILIGHT_TEMPLE_METZLI_PAINTING, new WorldPoint(1719, 9706, 0), "Enter the passage behind the painting."); + enterPassage.addDialogSteps("Enter the passage."); + pickBlueChest = new ObjectStep(this, ObjectID.TWILIGHT_TEMPLE_METZLI_CHAMBER_CHEST_CLOSED, new WorldPoint(1723, 9709, 0), "Picklock the chest in the " + + "hidden room. Be ready for a fight afterwards."); + fightEnforcer = new NpcStep(this, NpcID.VMQ4_TEMPLE_GUARD_BOSS_FIGHT, new WorldPoint(1712, 9706, 0), "Defeat the enforcer. You cannot use prayers" + + ". Step away each time he goes to attack, and step behind him or sideways if he says 'Traitor!' or 'Thief!'."); + pickUpEmissaryScroll = new ItemStep(this, "Pick up the emissary scroll.", emissaryScroll); + readEmissaryScroll = new DetailedQuestStep(this, "Read the emissary scroll.", emissaryScroll.highlighted()); + + // Part 2 + climbStairsF0ToF1Palace = new ObjectStep(this, ObjectID.CIVITAS_PALACE_STAIRS_UP, new WorldPoint(1672, 3164, 0), "Go back to Civitas illa Fortis." + + "Climb up the stairs to the top of the Sunrise Palace to talk to the queen."); + climbStairsF0ToF1Palace.addTeleport(civitasTeleport); + climbStairsF1ToF2Palace = new ObjectStep(this, ObjectID.CIVITAS_PALACE_STAIRS_UP, new WorldPoint(1671, 3169, 1), "Climb up the stairs to the top of " + + "the Sunrise Palace to talk to the queen."); + talkToQueen = new NpcStep(this, NpcID.VMQ4_QUEEN_PALACE, new WorldPoint(1673, 3156, 2), "Talk to the queen on the top floor of the Sunrise Palace."); + talkToQueen.addSubSteps(climbStairsF0ToF1Palace, climbStairsF1ToF2Palace); + talkToCaptainVibia = new NpcStep(this, NpcID.VMQ4_CAPTAIN_VIBIA_OUTSIDE_HOUSE, new WorldPoint(1652, 3088, 0), "Talk to Captain Vibia south of Civitas" + + " illa Fortis' west bank."); + inspectWindow = new ObjectStep(this, ObjectID.VMQ4_CIVITAS_JANUS_WINDOW, new WorldPoint(1652, 3093, 0), "Inspect the window on the east side of the " + + "house north of Captain Vibia, and then enter it.", bone); + inspectWindow.addDialogSteps("Force open the window."); + var giveBonesOrMeatToDogNoPuzzleWrap = new NpcStep(this, NpcID.VMQ4_JANUS_DOG, new WorldPoint(1650, 3094, 0), "Use bones or some raw meat on the dog to calm it down" + + ".", bone.highlighted()); + giveBonesOrMeatToDogNoPuzzleWrap.addIcon(ItemID.BONES); + giveBonesOrMeatToDog = giveBonesOrMeatToDogNoPuzzleWrap.puzzleWrapStep("Work out how to calm down the dog."); + enterDoorCode = new ObjectStep(this, ObjectID.VMQ4_JANUS_HOUSE_PUZZLE_DOOR, new WorldPoint(1649, 3093, 0), "Pet the dog to see the code for the door." + + " Open the door using the code 'GUS'.").puzzleWrapStep("Work out the door code."); + openDoorWithGusCode = new ChestCodeStep(this, "GUS", 10, 0, 4, 0).puzzleWrapStep(true); + enterDoorCode.addSubSteps(openDoorWithGusCode); + takePotato = new ItemStep(this, "Pick up the sack of potatoes (3).", potatoes).puzzleWrapStep("Work out how to make a way to trap Janus and how to " + + "knock him out."); + removePotatoesFromSack = new DetailedQuestStep(this, "Empty the sack of potatoes.", potatoes.highlighted()).puzzleWrapStep(true); + var takeKnifeNoPuzzleWrapped = new ItemStep(this, "Pick up the knife.", knife); + takeKnife = takeKnifeNoPuzzleWrapped.puzzleWrapStep(true); + takeCoinPurse = new ItemStep(this, "Pick up the coin purse.", coinPurseFullOrEmpty).puzzleWrapStep(true); + + goToF1Hideout = new ObjectStep(this, ObjectID.FORTIS_WOODEN_SPIRALSTAIRS_BOTTOM, new WorldPoint(1647, 3091, 0), "Go upstairs.").puzzleWrapStep(true); + goDownFromF2Hideout = new ObjectStep(this, ObjectID.FORTIS_WOODEN_SPIRALSTAIRS_TOP, new WorldPoint(1647, 3091, 2), "Go downstairs.").puzzleWrapStep(true); + goF1ToF2Hideout = new ObjectStep(this, ObjectID.FORTIS_WOODEN_SPIRALSTAIRS_MIDDLE, new WorldPoint(1647, 3091, 1), "Go to the top floor.").puzzleWrapStep(true); + goF1ToF2Hideout.addDialogStep("Climb up."); + useKnifeOnPottedFan = new ObjectStep(this, ObjectID.VMQ4_JANUS_HOUSE_PLANT, new WorldPoint(1650, 3095, 2), "Use the knife on the inspectable potted " + + "fan.", knife.highlighted(), freeInvSlot1).puzzleWrapStep(true); + useKnifeOnPottedFan.addIcon(ItemID.KNIFE); + fillCoinPurse = new ObjectStep(this, ObjectID.VMQ4_JANUS_HOUSE_EMPTY_POT, new WorldPoint(1645, 3098, 2), "Use the empty coin purse on the plant pot " + + "in the north-west of the roof with sand in it.", coinPurseEmpty.highlighted()).puzzleWrapStep(true); + fillCoinPurse.addIcon(ItemID.VMQ4_JANUS_PURSE_EMPTY); + emptyCoinPurse = new DetailedQuestStep(this, "Empty the coin purse.", coinPurse.highlighted()).puzzleWrapStep(true); + useBranchOnCoinPurse = new DetailedQuestStep(this, "Use the branch on the coin purse.", branch.highlighted(), coinPurseWithSand.highlighted()).puzzleWrapStep(true); + + goToF0Hideout = new ObjectStep(this, ObjectID.FORTIS_WOODEN_SPIRALSTAIRS_MIDDLE, new WorldPoint(1647, 3091, 1), "Go to the ground floor.").puzzleWrapStep(true); + goToF0Hideout.addDialogStep("Climb down."); + takeKnifeNoPuzzleWrapped.addSubSteps(goToF0Hideout); + goToF0HideoutEnd = new ObjectStep(this, ObjectID.FORTIS_WOODEN_SPIRALSTAIRS_MIDDLE, new WorldPoint(1647, 3091, 1), "Go to the ground floor.").puzzleWrapStep(true); + goToF0HideoutEnd.addDialogStep("Climb down."); + + goF2ToF1HideoutEnd = new ObjectStep(this, ObjectID.FORTIS_WOODEN_SPIRALSTAIRS_TOP, new WorldPoint(1647, 3091, 2), "Go downstairs back to Vibia.").puzzleWrapStep(true); + var showSackToVibiaNotPuzzleWrapped = new NpcStep(this, NpcID.VMQ4_CAPTAIN_VIBIA_INSIDE_HOUSE, new WorldPoint(1651, 3094, 0), "Show Captain Vibia the empty sack and " + + "makeshift blackjack.", emptySack, makeshiftBlackjack); + showSackToVibiaNotPuzzleWrapped.addDialogStep("I'll get searching."); + showSackToVibia = showSackToVibiaNotPuzzleWrapped.puzzleWrapStep(true); + showSackToVibiaNotPuzzleWrapped.addSubSteps(goF2ToF1HideoutEnd, goToF0HideoutEnd); + + takePotato.addSubSteps(removePotatoesFromSack, takeKnife, takeCoinPurse, goToF1Hideout, goDownFromF2Hideout, goF1ToF2Hideout, useKnifeOnPottedFan, + fillCoinPurse, emptyCoinPurse, useBranchOnCoinPurse, goToF0Hideout, goToF0HideoutEnd, goF2ToF1HideoutEnd, showSackToVibia); + + searchBodyForKey = new NpcStep(this, NpcID.VMQ4_JANUS_HOUSE_JANUS_UNCONSCIOUS, new WorldPoint(1647, 3093, 0), "Search Janus."); + enterTrapdoor = new ObjectStep(this, ObjectID.VMQ4_JANUS_BASEMENT_ENTRY, new WorldPoint(1643, 3092, 0), "Enter the basement in the hideout."); + + talkToQueenToGoCamTorum = new NpcStep(this, NpcID.VMQ4_QUEEN_BASEMENT, new WorldPoint(1653, 9493, 0), "Talk to the queen in the hideout basement."); + talkToQueenToGoCamTorum.addDialogStep("Yes, let's go."); + enterCamTorum = new ObjectStep(this, ObjectID.PMOON_TELEBOX, new WorldPoint(1436, 3129, 0), "Enter Cam Torum."); + + talkToAttala = new NpcStep(this, NpcID.CAM_TORUM_ATTALA, new WorldPoint(1442, 9550, 1), "Talk to Attala in Cam Torum's marketplace."); + talkToServiusInCamTorum = new NpcStep(this, NpcID.VMQ4_SERVIUS_TEUMO_HOUSE, new WorldPoint(1466, 9570, 1), "Talk to Servius in the house east of the " + + "bank in Cam Torum."); + goUpstairsPub = new ObjectStep(this, ObjectID.STAIRS_IMCANDO01_LOWER01, new WorldPoint(1467, 9572, 1), "Go upstairs in the house."); + takeBeer = new ItemStep(this, "Take the beer. Hop worlds if it's missing.", beer); + goDownstairsPub = new ObjectStep(this, ObjectID.STAIRS_IMCANDO01_UPPER01, new WorldPoint(1468, 9572, 2), "Go downstairs in the house."); + useBeerOnGalna = new NpcStep(this, NpcID.VMQ4_TEUMO_WIFE, new WorldPoint(1466, 9570, 1), "Use the beer on Galna.", beer.highlighted()); + useBeerOnGalna.addIcon(ItemID.BEER); + enterCamTorumHouseBasement = new ObjectStep(this, ObjectID.VMQ4_TEUMO_HOUSE_STAIRS_DOWN, new WorldPoint(1470, 9571, 1), "Enter the house's basement."); + takeBeerCabinet = new ObjectStep(this, ObjectID.VMQ4_TEUMO_BASEMENT_SHELF, new WorldPoint(1464, 9569, 0), "Search the south-west cabinet for a beer.") + .puzzleWrapStep("Solve the puzzle in the basement."); + takeBeerCabinet.addDialogStep("Beer."); + drinkBeer = new DetailedQuestStep(this, "Drink a beer.", beer.highlighted()).puzzleWrapStep(true); + takeSteamforgeBrew = new ObjectStep(this, ObjectID.VMQ4_TEUMO_BASEMENT_SHELF, new WorldPoint(1464, 9569, 0), "Search the south-west cabinet for " + + "steamforge brew.").puzzleWrapStep(true); + takeSteamforgeBrew.addDialogSteps("More options...", "Steamforge brew."); + takeDwarvenStout = new ObjectStep(this, ObjectID.VMQ4_TEUMO_BASEMENT_SHELF, new WorldPoint(1464, 9569, 0), "Search the south-west cabinet for dwarven" + + " stout.").puzzleWrapStep(true); + takeDwarvenStout.addDialogSteps("Previous options...", "Dwarven stout."); + takeWizardsMindBomb = new ObjectStep(this, ObjectID.VMQ4_TEUMO_BASEMENT_SHELF, new WorldPoint(1464, 9569, 0), "Search the south-west cabinet for " + + "wizard's mind bomb.").puzzleWrapStep(true); + takeWizardsMindBomb.addDialogSteps("More options...", "Wizard's mind bomb."); + placeSteamforgedBrew = new ObjectStep(this, ObjectID.VMQ4_TEUMO_BASEMENT_BARREL_3, new WorldPoint(1464, 9568, 0), "Place steamforge brew on the " + + "south-west barrel.", steamforgedBrew.highlighted()).puzzleWrapStep(true); + placeSteamforgedBrew.addIcon(ItemID.STEAMFORGE_BREW); + placeDwarvenStout = new ObjectStep(this, ObjectID.VMQ4_TEUMO_BASEMENT_BARREL_4, new WorldPoint(1466, 9568, 0), "Place dwarven stout on the " + + "south barrel.", dwarvenStout.highlighted()).puzzleWrapStep(true); + placeDwarvenStout.addIcon(ItemID.DWARVEN_STOUT); + + var nonPuzzleTakeBeerFromBarrel = new ItemStep(this, new WorldPoint(1469, 9571, 0), "Take the beer from on top of the barrel near the staircase.", + beer); + nonPuzzleTakeBeerFromBarrel.setOnlyHighlightItemsOnTile(true); + takeBeerFromBarrel = nonPuzzleTakeBeerFromBarrel.puzzleWrapStep(true); + placeBeer = new ObjectStep(this, ObjectID.VMQ4_TEUMO_BASEMENT_BARREL_2, new WorldPoint(1466, 9573, 0), "Place beer on the " + + "north barrel.", beer.highlighted()).puzzleWrapStep(true); + placeBeer.addIcon(ItemID.BEER); + + placeEmptyGlass = new ObjectStep(this, ObjectID.VMQ4_TEUMO_BASEMENT_BARREL_1, new WorldPoint(1469, 9571, 0), "Place empty glass on the " + + "east barrel.", emptyGlass.highlighted()).puzzleWrapStep(true); + placeEmptyGlass.addIcon(ItemID.BEER_GLASS); + placeMindBomb = new ObjectStep(this, ObjectID.VMQ4_TEUMO_BASEMENT_BARREL_5, new WorldPoint(1471, 9567, 0), "Place wizard's mind bomb on the " + + "south-east barrel.", wizardsMindBomb.highlighted()).puzzleWrapStep(true); + placeMindBomb.addIcon(ItemID.WIZARDS_MIND_BOMB); + + takeBeerCabinet.addSubSteps(drinkBeer, takeSteamforgeBrew, takeDwarvenStout, takeWizardsMindBomb, placeSteamforgedBrew, placeDwarvenStout, + takeBeerFromBarrel, placeBeer, placeEmptyGlass, placeMindBomb); + + inspectFireplace = new ObjectStep(this, ObjectID.VMQ4_TEUMO_BASEMENT_FIRE_OUT, new WorldPoint(1463, 9571, 0), "Inspect the fireplace."); + inspectFireplace.addDialogStep("Pull it."); + useHole = new ObjectStep(this, ObjectID.VMQ4_TEUMO_BASEMENT_SECRET_PASSAGE_ENTRY, new WorldPoint(1470, 9565, 0), "Enter the hole in the south-east " + + "corner of the room."); + watchCutsceneCamTorum = new DetailedQuestStep(this, "Watch the cutscene with Ennius."); + returnThroughHole = new ObjectStep(this, ObjectID.VMQ4_TEUMO_BASEMENT_SECRET_PASSAGE_EXIT, new WorldPoint(1470, 9563, 0), "Go back through the hole " + + "into the Teumo's basement."); + returnToServius = new NpcStep(this, NpcID.VMQ4_SERVIUS_TEUMO_HOUSE_DOWNSTAIRS, new WorldPoint(1468, 9569, 0), "Talk to Servius in the house basement."); + + // Neypotzli section + climbUpFromTeumoBasement = new ObjectStep(this, ObjectID.VMQ4_TEUMO_HOUSE_STAIRS_UP, new WorldPoint(1468, 9573, 0), "Go to Neypotzli."); + enterNeypotzli = new ObjectStep(this, ObjectID.PMOON_TELEBOX, new WorldPoint(1439, 9600, 1), + "Enter the Neypotzli entrance in the far north of the cavern."); + enterNeypotzli.addSubSteps(climbUpFromTeumoBasement); + talkToEyatalli = new NpcStep(this, NpcID.VMQ4_EYATLALLI, new WorldPoint(1440, 9628, 1), + "Talk to Eyatlalli."); + + locateKeystone = new DetailedQuestStep(this, "Use the keystone fragment to locate the keystone.", keystoneFragment.highlighted()); + + enterStreamboundCavern = new ObjectStep(this, ObjectID.PMOON_TELEBOX_DIAGONAL, new WorldPoint(1458, 9650, 1), + "Enter the north-east entrance to the streambound cavern.").puzzleWrapStep(locateKeystone); + locateInStreambound = new TileStep(this, new WorldPoint(1511, 9702, 0), "Locate with the keystone fragment on the marked tile north of the " + + "cooking stove.", keystoneFragment.highlighted()).puzzleWrapStep(locateKeystone, true); + enterEarthboundCavernFromStreambound = new ObjectStep(this, ObjectID.PMOON_TELEBOX_CAVE, new WorldPoint(1522, 9720, 0), "Enter the earthbound cavern " + + "via the north cave entrance.", keystoneFragment).puzzleWrapStep(locateKeystone, true); + enterEarthboundCavern = new ObjectStep(this, ObjectID.PMOON_TELEBOX_DIAGONAL, new WorldPoint(1421, 9650, 1), + "Enter the north-west entrance.").puzzleWrapStep(locateKeystone, true); + enterEarthboundCavernFromStreambound.addSubSteps(enterEarthboundCavern); + locateInEarthbound = new DetailedQuestStep(this, new WorldPoint(1375, 9684, 0), "Locate with the keystone fragment on the marked tile near where you " + + "trap lizards.", + keystoneFragment.highlighted()).puzzleWrapStep(locateKeystone, true); + enterAncientPrison = new ObjectStep(this, ObjectID.PMOON_TELEBOX_DIAGONAL, new WorldPoint(1421, 9613, 1), + "Enter the south-west entrance.", keystoneFragment).puzzleWrapStep(locateKeystone, true); + enterAncientPrisonFromEarthbound = new ObjectStep(this, ObjectID.PMOON_TELEBOX_3X3, new WorldPoint(1374, 9665, 0), "Enter the ancient prison via the " + + "large entrance in the south-west of the area.", keystoneFragment).puzzleWrapStep(locateKeystone, true); + enterAncientPrisonFromEarthbound.addSubSteps(enterAncientPrison); + locateInAncientPrison = new DetailedQuestStep(this, new WorldPoint(1373, 9539, 0), "Locate with the keystone fragment on the marked tile in the " + + "south-east room with a statue.", keystoneFragment.highlighted()).puzzleWrapStep(locateKeystone, true); + touchGlowingSymbol = new ObjectStep(this, ObjectID.VMQ4_KEYSTONE_CHAMBER_ENTRANCE, new WorldPoint(1375, 9537, 0), "Touch the glowing symbol that " + + "appeared. Be ready for a fight.", combatGear); + defeatCultists = new NpcStep(this, NpcID.VMQ4_KEYSTONE_CHAMBER_BOSS_MAGIC, new WorldPoint(1364, 9514, 0), "Defeat the cultists. Step into golden " + + "circle to avoid the mage's special. Avoid the arrow barrage from the ranger. When you defeat one of the cultists, the other will become " + + "enraged.", true); + ((NpcStep) defeatCultists).addAlternateNpcs(NpcID.VMQ4_KEYSTONE_CHAMBER_BOSS_MAGIC_CS, NpcID.VMQ4_KEYSTONE_CHAMBER_BOSS_RANGED, + NpcID.VMQ4_KEYSTONE_CHAMBER_BOSS_RANGED_CS); + talkToAttalaAfterCultistFight = new NpcStep(this, NpcID.VMQ4_ATTALA_KEYSTONE_CHAMBER, new WorldPoint(1363, 9516, 0), "Talk to Attala."); + + talkToServiusAtTalTeklan = new NpcStep(this, NpcID.VMQ4_SERVIUS_VIS, new WorldPoint(1236, 3105, 0), "Talk to Servius in Tal Teklan in the " + + "Tlati Rainforest, in the north-west of Varlamore. The easiest way here is via quetzal.", List.of(combatGear, food, prayerPotions), List.of(rangedGear)); + talkToServiusAtTalTeklan.addWidgetHighlight(new WidgetHighlight(InterfaceID.QuetzalMenu.ICONS, true).withModelRequirement(56665)); + + enterTonaliCavern = new ObjectStep(this, ObjectID.VMQ4_CRYPT_OF_TONALI_ENTRY, new WorldPoint(1305, 3034, 0), "Enter the passageway in the" + + " tree south-east of Tal Teklan, into the Crypt of Tonali."); + defeatFinalCultists = new NpcStep(this, NpcID.VMQ4_CRYPT_ATTACKER_MELEE_VARIANT_1A, new WorldPoint(1312, 9355, 2), "Defeat the attacking cultists.", + true); + ((NpcStep) defeatFinalCultists).addAlternateNpcs(NpcID.VMQ4_CRYPT_ATTACKER_MELEE_VARIANT_1B, NpcID.VMQ4_CRYPT_ATTACKER_MELEE_VARIANT_2A, + NpcID.VMQ4_CRYPT_ATTACKER_MELEE_VARIANT_2B, NpcID.VMQ4_CRYPT_ATTACKER_MAGIC_VARIANT_1, NpcID.VMQ4_CRYPT_ATTACKER_MAGIC_VARIANT_2, + NpcID.VMQ4_CRYPT_ATTACKER_RANGED_VARIANT_1, NpcID.VMQ4_CRYPT_ATTACKER_RANGED_VARIANT_2); + fightEnnius = new NpcStep(this, NpcID.VMQ4_CRYPT_ENNIUS_BOSS, new WorldPoint(1336, 9355, 2), "Defeat Ennius. Protect from Melee. Stand on the " + + "circles to avoid damage when they appear. Avoid the lines of yellow star icons on the floor when they appear. When they reach 0 health, " + + "they'll gain some back and do more damage."); + tonaliGoDownStairsF2ToF1 = new ObjectStep(this, ObjectID.VMQ4_CRYPT_STAIRS_TOP_ENNIUS, new WorldPoint(1335, 9360, 2), "Climb down the stairs."); + tonaliGoDownStairsF1ToF0 = new ObjectStep(this, ObjectID.VMQ4_CRYPT_STAIRS_TOP_ENNIUS, new WorldPoint(1332, 9367, 1), "Climb down the stairs."); + tonaliGoDownStairsF2ToF1.addSubSteps(tonaliGoDownStairsF1ToF0); + useRedTeleporter = new ObjectStep(this, ObjectID.VMQ4_SUN_TELEPORT, new WorldPoint(1332, 9382, 0), "Step onto the red teleporter to the north-east."); + useBlueTeleporter = new ObjectStep(this, ObjectID.VMQ4_MOON_TELEPORT, new WorldPoint(1319, 9384, 1), "Step onto the blue teleporter to the west."); + + useRedTeleporter2 = new ObjectStep(this, ObjectID.VMQ4_SUN_TELEPORT, new WorldPoint(1303, 9389, 0), "Step on the red teleporter to the west."); + useBlueTeleporterLizards = new ObjectStep(this, ObjectID.VMQ4_MOON_TELEPORT, new WorldPoint(1293, 9394, 1), "Step onto the blue teleporter to the " + + "north-west."); + useRedTeleporter3 = new ObjectStep(this, ObjectID.VMQ4_SUN_TELEPORT, new WorldPoint(1296, 9402, 0), "Step on the red teleporter to the north."); + + + // Unused for now + crossLog = new ObjectStep(this, ObjectID.VMQ4_CRYPT_LOG_BALANCE_1, new WorldPoint(1302, 9398, 0), "Walk across the log balance to the north."); + useBlueTeleporter2 = new ObjectStep(this, ObjectID.VMQ4_MOON_TELEPORT, new WorldPoint(1315, 9409, 0), "Step onto the blue teleporter to the north."); + climbRope = new ObjectStep(this, ObjectID.VMQ4_CRYPT_SHORTCUT_2_BOTTOM, new WorldPoint(1308, 9420, 1), "Climb the rope up to the north-east."); + activateStrangePlatform = new ObjectStep(this, ObjectID.VMQ4_CRYPT_LIFT, new WorldPoint(1311, 9428, 2), "Inspect the strange platform nearby to " + + "activate a shortcut lift from the surface."); + enterTonaliWithLift = new ObjectStep(this, ObjectID.VMQ4_CRYPT_LIFT_SURFACE, new WorldPoint(1310, 3103, 0), "Go down the lift north of the Crypt of " + + "Tonali in the Tlati rainforest."); + enterTonaliCavern.addSubSteps(enterTonaliWithLift); + descendIntoSunPuzzle = new ObjectStep(this, ObjectID.VMQ4_SUN_TELEPORT, new WorldPoint(1316, 9446, 2), "Step on the red teleporter to the north-east " + + "of the lift."); + getEssenceFromUrns = new ObjectStep(this, ObjectID.VMQ4_SUN_PUZZLE_URN, new WorldPoint(1323, 9449, 1), "Search the urns in the area for some essence" + + ".", true).puzzleWrapStep(true); + inspectSunStatue = new ObjectStep(this, ObjectID.VMQ4_SUN_PUZZLE_STATUE, new WorldPoint(1330, 9446, 1), "Inspect the statue in the middle of the room.").puzzleWrapStep(true); + var solveSunPuzzleNoPuzzleWrap = new DetailedQuestStep(this, "Look at the pillar in the middle of the room. The first word " + + "indicates where to tell Itzla to stand, " + + "and the second word where you craft the essence." + + "1 is the north-east altar, and incrementing numbers rotate clockwise. The words mean the following numbers: \n Oma = 2\n" + + " Naui = 4\n" + + " Kuli = 5\n" + + " Chaki = 6\n" + + " Koma = 7\n" + + " Ueai = 8\n" + + " Makti = 10"); + solveSunPuzzle = solveSunPuzzleNoPuzzleWrap.puzzleWrapStep("Solve the sun puzzle."); + + // Can get the text from statue with `Messagebox.TEXT` + solveSunPuzzle1Step1MoveItzla = new NpcStep(this, NpcID.VMQ4_ITZLA_CRYPT_PUZZLE_SUN, new WorldPoint(1327, 9446, 1), "Tell Itzla to stand in the east" + + " of the room.").puzzleWrapStep(true); + solveSunPuzzle1Step1MoveItzla.addDialogSteps("Can you go to an altar for me?", "East.", "Previous options..."); + solveSunPuzzle1Step1Craft = new ObjectStep(this, ObjectID.VMQ4_SUN_ALTAR, new WorldPoint(1320, 9446, 1), "Imbue the essence on the west altar.", + essence).puzzleWrapStep(true); + + solveSunPuzzle1Step2MoveItzla = new NpcStep(this, NpcID.VMQ4_ITZLA_CRYPT_PUZZLE_SUN, new WorldPoint(1327, 9446, 1), "Tell Itzla to stand in the north" + + " of the room.").puzzleWrapStep(true); + solveSunPuzzle1Step2MoveItzla.addDialogSteps("Can you go to an altar for me?", "North.", "Previous options..."); + solveSunPuzzle1Step2Craft = new ObjectStep(this, ObjectID.VMQ4_SUN_ALTAR, new WorldPoint(1330, 9436, 1), "Imbue the essence on the south altar.", + essence).puzzleWrapStep(true); + + solveSunPuzzle1Step3MoveItzla = new NpcStep(this, NpcID.VMQ4_ITZLA_CRYPT_PUZZLE_SUN, new WorldPoint(1327, 9446, 1), "Tell Itzla to stand in the " + + "north-west of the room.").puzzleWrapStep(true); + solveSunPuzzle1Step3MoveItzla.addDialogSteps("Can you go to an altar for me?", "North west.", "More options..."); + solveSunPuzzle1Step3Craft = new ObjectStep(this, ObjectID.VMQ4_SUN_ALTAR, new WorldPoint(1322, 9438, 1), "Imbue the essence on the south-west altar.", + essence).puzzleWrapStep(true); + + solveSunPuzzle2Step1MoveItzla = new NpcStep(this, NpcID.VMQ4_ITZLA_CRYPT_PUZZLE_SUN, new WorldPoint(1327, 9446, 1), "Tell Itzla to stand in the north" + + " of the room.").puzzleWrapStep(true); + solveSunPuzzle2Step1MoveItzla.addDialogSteps("Can you go to an altar for me?", "North.", "Previous options..."); + solveSunPuzzle2Step1Craft = new ObjectStep(this, ObjectID.VMQ4_SUN_ALTAR, new WorldPoint(1330, 9436, 1), "Imbue the essence on the south altar.", + essence).puzzleWrapStep(true); + + solveSunPuzzle2Step2MoveItzla = new NpcStep(this, NpcID.VMQ4_ITZLA_CRYPT_PUZZLE_SUN, new WorldPoint(1327, 9446, 1), "Tell Itzla to stand in the east" + + " of the room.").puzzleWrapStep(true); + solveSunPuzzle2Step2MoveItzla.addDialogSteps("Can you go to an altar for me?", "East.", "Previous options..."); + solveSunPuzzle2Step2Craft = new ObjectStep(this, ObjectID.VMQ4_SUN_ALTAR, new WorldPoint(1320, 9446, 1), "Imbue the essence on the west altar.", + essence).puzzleWrapStep(true); + + solveSunPuzzle2Step3MoveItzla = new NpcStep(this, NpcID.VMQ4_ITZLA_CRYPT_PUZZLE_SUN, new WorldPoint(1327, 9446, 1), "Tell Itzla to stand in the " + + "north-west of the room.").puzzleWrapStep(true); + solveSunPuzzle2Step3MoveItzla.addDialogSteps("Can you go to an altar for me?", "North west.", "More options..."); + solveSunPuzzle2Step3Craft = new ObjectStep(this, ObjectID.VMQ4_SUN_ALTAR, new WorldPoint(1322, 9438, 1), "Imbue the essence on the south-west altar.", + essence).puzzleWrapStep(true); + solveSunPuzzleNoPuzzleWrap.addSubSteps(inspectSunStatue, solveSunPuzzle1Step1MoveItzla, solveSunPuzzle1Step1Craft, solveSunPuzzle1Step2MoveItzla, solveSunPuzzle1Step2Craft, + solveSunPuzzle1Step3MoveItzla, solveSunPuzzle1Step3Craft, solveSunPuzzle2Step1Craft, solveSunPuzzle2Step1MoveItzla, + solveSunPuzzle2Step2MoveItzla, solveSunPuzzle2Step2Craft, solveSunPuzzle2Step3MoveItzla, solveSunPuzzle2Step3Craft); + solveSunPuzzle.addSubSteps(inspectSunStatue, solveSunPuzzle1Step1MoveItzla, solveSunPuzzle1Step1Craft, solveSunPuzzle1Step2MoveItzla, solveSunPuzzle1Step2Craft, + solveSunPuzzle1Step3MoveItzla, solveSunPuzzle1Step3Craft, solveSunPuzzle2Step1Craft, solveSunPuzzle2Step1MoveItzla, + solveSunPuzzle2Step2MoveItzla, solveSunPuzzle2Step2Craft, solveSunPuzzle2Step3MoveItzla, solveSunPuzzle2Step3Craft); + + goUpFromSunPuzzle = new ObjectStep(this, ObjectID.VMQ4_SUN_TELEPORT, new WorldPoint(1323, 9443, 1), "Go back up to the main area."); + enterMoonPuzzle = new ObjectStep(this, ObjectID.VMQ4_MOON_TELEPORT, new WorldPoint(1304, 9446, 2), "Go through the blue teleport to the west."); + + moveItzlaNorth = new ObjectStep(this, ObjectID.VMQ4_MOON_PUZZLE_PLATFORM_1, new WorldPoint(1298, 9448, 1), "Move-Itzla to the platform north of him " + + "by clicking on it to see which torches around the room light up."); + moveItzlaSouth = new ObjectStep(this, ObjectID.VMQ4_MOON_PUZZLE_PLATFORM_2, new WorldPoint(1298, 9444, 1), "Move-Itzla to the platform south of him " + + "by clicking on it to see which torches around the room light up."); + pullTreeRoots = new ObjectStep(this, ObjectID.VMQ4_MOON_PUZZLE_ROOT, new WorldPoint(1285, 9452, 1), "Pull some tree roots.", true) + .puzzleWrapStep("Solve the moon puzzle."); + getKnifeBlade = new ObjectStep(this, ObjectID.VMQ4_MOON_PUZZLE_OLD_TOOLS, new WorldPoint(1292, 9457, 1), + "Search the old tools for a knife blade. Avoid the lines of fire by having Itzla go on the north platform to remove dark flames, and the " + + "south one for light flames.").puzzleWrapStep(true); + fletchRoots = new DetailedQuestStep(this, "Fletch the roots into kindling.", roots.highlighted()).puzzleWrapStep(true); + placeRoots = new ObjectStep(this, ObjectID.VMQ4_MOON_PUZZLE_STATUE, new WorldPoint(1283, 9446, 1), "Have Itzla move between both the north and south " + + "platforms to see how many braziers are lit around the room total. Put that many into the statue.", kindling).puzzleWrapStep(true); + repeatMoonPuzzleThreeTimes = new DetailedQuestStep(this, "Repeat the kindling burning matching the total braziers lit two more times.").puzzleWrapStep(true); + pullTreeRoots.addSubSteps(moveItzlaNorth, moveItzlaSouth, pullTreeRoots, getKnifeBlade, fletchRoots, placeRoots, repeatMoonPuzzleThreeTimes); + leaveMoonPuzzleRoom = new ObjectStep(this, ObjectID.VMQ4_MOON_TELEPORT, new WorldPoint(1299, 9455, 1), "Leave the moon puzzle room."); + + enterFinalBossArea = new ObjectStep(this, ObjectID.VMQ4_CRYPT_DOOR_TO_MOKI, new WorldPoint(1311, 9468, 2), "Try to open the door to the " + + "north. Be ready for the final boss!", rangedGear); + approachMetzli = new NpcStep(this, NpcID.VMQ4_CRYPT_METZLI_NOOPS, new WorldPoint(1311, 9497, 1), "Approach Augur Metzli, ready for a fight."); + defeatFinalBoss = new NpcStep(this, NpcID.VMQ4_METZLI_BOSS, new WorldPoint(1311, 9497, 1), "Defeat Metzli. Read the sidebar for more details."); + defeatFinalBossSidebar = new NpcStep(this, NpcID.VMQ4_METZLI_BOSS, new WorldPoint(1311, 9497, 1), "Defeat Metzli." + + "\n\nStart with Protect from Missiles." + + "\n\nUse the gaps in the wave attacks to dodge the walls as they approach. " + + "\n\nIf circles appear, stand where they appeared." + + "\n\nEvery time a teleporter appears to jump over a wave, the boss will switch attack styles alternating between mage and ranged. " + + "\n\nThe boss will enter an enrage phase, it is much easier to range but melee is still possible. " + + "\n\nAttack the boss and then immediately click on the next teleporter to avoid taking damage."); + defeatFinalBossSidebar.addSubSteps(defeatFinalBoss); + + watchFinalBossAfterCutscene = new NpcStep(this, NpcID.VMQ4_MOKI_METZLI_FIGHT_DEFEATED_NOOPS, new WorldPoint(1311, 9497, 1), "Watch Metzli's cutscene."); + + goToNorthOfFinalAreaAgilityShortcut = new ObjectStep(this, ObjectID.MOKI_ENTRANCE_TO_DOM_BOSS, new WorldPoint(1311, 9533, 1), "Enter the entrance in the north of " + + "the area."); + ((ObjectStep) goToNorthOfFinalAreaAgilityShortcut).setLinePoints(List.of( + new WorldPoint(1310, 9497, 1), + new WorldPoint(1310, 9510, 1), + new WorldPoint(1310, 9520, 1), + new WorldPoint(1311, 9531, 1) + )); + + goToNorthOfFinalArea = new ObjectStep(this, ObjectID.MOKI_ENTRANCE_TO_DOM_BOSS, new WorldPoint(1311, 9533, 1), "Enter the entrance in the north of " + + "the area."); + ((ObjectStep) goToNorthOfFinalArea).setLinePoints(List.of( + new WorldPoint(1310, 9497, 1), + new WorldPoint(1304, 9497, 1), + new WorldPoint(1300, 9497, 0), + new WorldPoint(1287, 9497, 0), + new WorldPoint(1283, 9497, 1), + new WorldPoint(1283, 9513, 1), + new WorldPoint(1311, 9513, 1), + new WorldPoint(1311, 9531, 1) + )); + goToNorthOfFinalArea.addSubSteps(goToNorthOfFinalAreaAgilityShortcut); + + inspectRanulPillar = new ObjectStep(this, ObjectID.VMQ4_MOKI_MEMORIAL_RANUL, new WorldPoint(1317, 9527, 1), "Inspect the ranul pillar south-east " + + "of the north door."); + inspectRalosPillar = new ObjectStep(this, ObjectID.VMQ4_MOKI_MEMORIAL_RALOS, new WorldPoint(1304, 9527, 1), "Inspect the ralos pillar " + + "south-west of the north door."); + inspectDoor = new ObjectStep(this, ObjectID.MOKI_ENTRANCE_TO_DOM_BOSS, new WorldPoint(1311, 9533, 1), "Inspect the entrance in the north of " + + "the area again."); + inspectSkeleton = new ObjectStep(this, ObjectID.VMQ4_MOKI_SKELETON_TABLET, new WorldPoint(1307, 9532, 1), "Inspect the skeleton west of the north " + + "door."); + readStoneTablet = new DetailedQuestStep(this, "Read the stone tablet.", stoneTablet.highlighted()); + + finishQuest = new NpcStep(this, NpcID.VMQ4_ITZLA_CRYPT_DONE, new WorldPoint(1315, 9355, 2), "Talk to Prince Itzla Arkan to complete the quest!"); + } + + @Override + public List getItemRequirements() + { + return List.of( + bone, combatGear + ); + } + + @Override + public List getItemRecommended() + { + return List.of( + rangedGear, food, prayerPotions, pendant, whistle, civitasTeleport + ); + } + + @Override + public List getGeneralRequirements() + { + return List.of( + new QuestRequirement(QuestHelperQuest.THE_HEART_OF_DARKNESS, QuestState.FINISHED), + new QuestRequirement(QuestHelperQuest.PERILOUS_MOON, QuestState.FINISHED), + new SkillRequirement(Skill.THIEVING, 66), + new SkillRequirement(Skill.FLETCHING, 52), + new SkillRequirement(Skill.RUNECRAFT, 52) + ); + } + + @Override + public List getCombatRequirements() + { + return List.of( + "Emissary Enforcer (lvl-196)", + "Chimalli (lvl-160) and Lucius (lvl-160)", + "Multiple waves of Twilight Emissaries (lvl-70 to lvl-90)", + "Ennius Tullus (lvl-306)", + "Augur Metzli (lvl-396)" + ); + } + + @Override + public QuestPointReward getQuestPointReward() + { + return new QuestPointReward(3); + } + + @Override + public List getExperienceRewards() + { + return List.of( + new ExperienceReward(Skill.THIEVING, 55000), + new ExperienceReward(Skill.FLETCHING, 25000), + new ExperienceReward(Skill.RUNECRAFT, 25000) + ); + } + + @Override + public List getUnlockRewards() + { + return List.of( + new UnlockReward("The Arkan Blade"), + new UnlockReward("Access to Mokhaiotl"), + new UnlockReward("Access to Crypt of Tonali") + ); + } + + @Override + public List getItemRewards() + { + return Arrays.asList( + new ItemReward("55,000 Experience Lamps (Combat Skills)", ItemID.VMQ4_REWARD_LAMP, 1), + new ItemReward("Arkan blade", ItemID.ARKAN_BLADE) + ); + } + + @Override + public List getPanels() + { + var panels = new ArrayList(); + + panels.add(new PanelDetails("Starting off", List.of( + startQuest, searchChestForEmissaryRobes, enterTwilightTemple, goDownStairsTemple, enterBackroom, searchBed, openDrawers, openDrawers2, + useCanvasPieceOnPicture, enterPassage, pickBlueChest, fightEnforcer, pickUpEmissaryScroll, readEmissaryScroll + ), List.of( + combatWeapon, food + ), List.of( + civitasTeleport + ))); + panels.add(new PanelDetails("The hideout", List.of(talkToQueen, talkToCaptainVibia, inspectWindow, giveBonesOrMeatToDog, enterDoorCode, takePotato, + takeKnife, goToF1Hideout, takeCoinPurse, goF1ToF2Hideout, useKnifeOnPottedFan, fillCoinPurse, useBranchOnCoinPurse, showSackToVibia, + searchBodyForKey, enterTrapdoor, talkToQueenToGoCamTorum), + List.of(bone), + List.of())); + panels.add(new PanelDetails("The dwarves", List.of(enterCamTorum, talkToAttala, talkToServiusInCamTorum, goUpstairsPub, takeBeer, goDownstairsPub, + useBeerOnGalna, enterCamTorumHouseBasement, takeWizardsMindBomb, placeMindBomb, takeBeerCabinet, + placeBeer, takeSteamforgeBrew, placeSteamforgedBrew, takeDwarvenStout, placeDwarvenStout,takeBeerFromBarrel, drinkBeer, placeEmptyGlass, + inspectFireplace, useHole, watchCutsceneCamTorum, returnThroughHole, returnToServius), + List.of(), + List.of())); + panels.add(new PanelDetails("Ancient keys", List.of(enterNeypotzli, talkToEyatalli, enterStreamboundCavern, locateInStreambound, + enterEarthboundCavernFromStreambound, locateInEarthbound, enterAncientPrisonFromEarthbound, locateInAncientPrison, touchGlowingSymbol, + defeatCultists, talkToAttalaAfterCultistFight), + List.of(combatGear, food, prayerPotions))); + + panels.add(new PanelDetails("Crypt of Tonali", List.of(talkToServiusAtTalTeklan, enterTonaliCavern, defeatFinalCultists, fightEnnius, + tonaliGoDownStairsF2ToF1, useRedTeleporter, useBlueTeleporter, useRedTeleporter2, useBlueTeleporterLizards, useRedTeleporter3, climbRope, + activateStrangePlatform, descendIntoSunPuzzle, inspectSunStatue, getEssenceFromUrns, solveSunPuzzle, goUpFromSunPuzzle, enterMoonPuzzle, + moveItzlaNorth, moveItzlaSouth, pullTreeRoots, getKnifeBlade, fletchRoots, placeRoots, repeatMoonPuzzleThreeTimes, leaveMoonPuzzleRoom), + List.of(combatGear, food, prayerPotions))); + + panels.add(new PanelDetails("Doom", List.of(enterFinalBossArea, approachMetzli, defeatFinalBossSidebar, watchFinalBossAfterCutscene, + goToNorthOfFinalArea, inspectRalosPillar, inspectRanulPillar, inspectDoor, inspectSkeleton, readStoneTablet, finishQuest), + List.of(rangedGear, food, prayerPotions))); + return panels; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/thefremennikisles/TheFremennikIsles.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/thefremennikisles/TheFremennikIsles.java index 086be2c2e9c..cca413db0f6 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/thefremennikisles/TheFremennikIsles.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/thefremennikisles/TheFremennikIsles.java @@ -63,7 +63,8 @@ public class TheFremennikIsles extends BasicQuestHelper { //Items Required - ItemRequirement tuna, mithrilOre, coal, tinOre, jesterHat, jesterTights, jesterTop, jesterBoots, arcticLogs8, splitLogs8, + ItemRequirement tuna, mithrilOre, coal, tinOre, jesterHat, jesterTights, jesterTop, jesterBoots, equipJesterHat, + equipJesterTights, equipJesterTop, equipJesterBoots, arcticLogs8, splitLogs8, knife, rope8, rope4, splitLogs4, yakTop, yakBottom, royalDecree, roundShield, yakTopWorn, yakBottomWorn, shieldWorn, meleeWeapon, food, head, needle, thread, coins15, bronzeNail, hammer, rope, axe, rope9; @@ -312,10 +313,14 @@ protected void setupRequirements() tinOre = new ItemRequirement("Tin ore", ItemID.TIN_ORE, 8).showConditioned(useTin); tinOre.setTooltip("You can mine some in the underground mine north west of Jatizso."); - jesterHat = new ItemRequirement("Silly jester hat", ItemID.FRISD_JESTER_HAT, 1, true); - jesterTop = new ItemRequirement("Silly jester body", ItemID.FRISD_JESTER_TOP, 1, true); - jesterTights = new ItemRequirement("Silly jester tights", ItemID.FRISD_JESTER_LEGS, 1, true); - jesterBoots = new ItemRequirement("Silly jester boots", ItemID.FRISD_JESTER_BOOTS, 1, true); + jesterHat = new ItemRequirement("Silly jester hat", ItemID.FRISD_JESTER_HAT); + jesterTop = new ItemRequirement("Silly jester body", ItemID.FRISD_JESTER_TOP); + jesterTights = new ItemRequirement("Silly jester tights", ItemID.FRISD_JESTER_LEGS); + jesterBoots = new ItemRequirement("Silly jester boots", ItemID.FRISD_JESTER_BOOTS); + equipJesterHat = jesterHat.equipped(); + equipJesterTop = jesterTop.equipped(); + equipJesterTights = jesterTights.equipped(); + equipJesterBoots = jesterBoots.equipped(); arcticLogs8 = new ItemRequirement("Arctic pine logs", ItemID.ARCTIC_PINE_LOG, 8); splitLogs8 = new ItemRequirement("Split log", ItemID.ARCTIC_PINE_SPLIT, 8); splitLogs4 = new ItemRequirement("Split log", ItemID.ARCTIC_PINE_SPLIT, 4); @@ -430,7 +435,7 @@ public void setupSteps() returnToRellekkaFromJatizso = new NpcStep(this, NpcID.FRIS_R_FERRYMAN_IZSO, new WorldPoint(2420, 3781, 0), "Return to Rellekka with Mord."); returnToRellekkaFromJatizso.addDialogStep("Can you ferry me to Rellekka?"); - talkToSlug = new NpcStep(this, NpcID.FRIS_SPYMASTER, new WorldPoint(2335, 3811, 0), "Talk to Slug Hemligssen wearing nothing but your Silly Jester outfit.", jesterHat, jesterTop, jesterTights, jesterBoots); + talkToSlug = new NpcStep(this, NpcID.FRIS_SPYMASTER, new WorldPoint(2335, 3811, 0), "Talk to Slug Hemligssen wearing nothing but your Silly Jester outfit.", equipJesterHat, equipJesterTop, equipJesterTights, equipJesterBoots); talkToSlug.addSubSteps(returnToRellekkaFromJatizso, travelToNeitiznot); talkToSlug.addDialogStep("Free stuff please."); talkToSlug.addDialogStep("I am ready."); @@ -442,7 +447,7 @@ public void setupSteps() getJesterOutfit.addDialogStep("Take the jester's boots."); performForMawnis = new DetailedQuestStep(this, "Perform the actions that Mawnis requests of you."); - goSpyOnMawnis = new NpcStep(this, NpcID.FRIS_R_BURGHER_CROWN, new WorldPoint(2335, 3800, 0), "Talk to Mawnis in Neitiznot to start spying on him.", jesterHat, jesterTop, jesterTights, jesterBoots); + goSpyOnMawnis = new NpcStep(this, NpcID.FRIS_R_BURGHER_CROWN, new WorldPoint(2335, 3800, 0), "Talk to Mawnis in Neitiznot to start spying on him.", equipJesterHat, equipJesterTop, equipJesterTights, equipJesterBoots); goSpyOnMawnis.addSubSteps(performForMawnis); tellSlugReport1 = new NpcStep(this, NpcID.FRIS_SPYMASTER, new WorldPoint(2335, 3811, 0), "Report back to Slug Hemligssen."); @@ -501,12 +506,12 @@ public void setupSteps() travelToNeitiznotToSpyAgain = new NpcStep(this, NpcID.FRIS_R_FERRY_RELLIKKA, new WorldPoint(2644, 3710, 0), "Travel to Neitiznot with Maria Gunnars."); returnToRellekkaFromJatizsoToSpyAgain = new NpcStep(this, NpcID.FRIS_R_FERRYMAN_IZSO, new WorldPoint(2420, 3781, 0), "Return to Rellekka with Mord."); returnToRellekkaFromJatizsoToSpyAgain.addDialogStep("Can you ferry me to Rellekka?"); - talkToSlugToSpyAgain = new NpcStep(this, NpcID.FRIS_SPYMASTER, new WorldPoint(2335, 3811, 0), "Talk to Slug Hemligssen wearing nothing but your Silly Jester outfit.", jesterHat, jesterTop, jesterTights, jesterBoots); + talkToSlugToSpyAgain = new NpcStep(this, NpcID.FRIS_SPYMASTER, new WorldPoint(2335, 3811, 0), "Talk to Slug Hemligssen wearing nothing but your Silly Jester outfit.", equipJesterHat, equipJesterTop, equipJesterTights, equipJesterBoots); talkToSlugToSpyAgain.addSubSteps(travelToNeitiznotToSpyAgain, returnToRellekkaFromJatizsoToSpyAgain); performForMawnisAgain = new DetailedQuestStep(this, "Perform the actions that Mawnis requests of you."); - goSpyOnMawnisAgain = new NpcStep(this, NpcID.FRIS_R_BURGHER_CROWN, new WorldPoint(2335, 3800, 0), "Talk to Mawnis to start spying on him.", jesterHat, jesterTop, jesterTights, jesterBoots); + goSpyOnMawnisAgain = new NpcStep(this, NpcID.FRIS_R_BURGHER_CROWN, new WorldPoint(2335, 3800, 0), "Talk to Mawnis to start spying on him.", equipJesterHat, equipJesterTop, equipJesterTights, equipJesterBoots); goSpyOnMawnisAgain.addSubSteps(performForMawnisAgain); reportBackToSlugAgain = new NpcStep(this, NpcID.FRIS_SPYMASTER, new WorldPoint(2335, 3811, 0), "Report to Slug Hemligssen."); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/thegreatbrainrobbery/TheGreatBrainRobbery.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/thegreatbrainrobbery/TheGreatBrainRobbery.java index dad4db33d96..385940d0181 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/thegreatbrainrobbery/TheGreatBrainRobbery.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/thegreatbrainrobbery/TheGreatBrainRobbery.java @@ -218,7 +218,7 @@ protected void setupRequirements() plank = new ItemRequirement("Plank", ItemID.WOODPLANK); fur = new ItemRequirement("Fur", ItemID.FUR); fur.addAlternates(ItemID.WEREWOLVE_FUR, ItemID.GREY_WOLF_FUR); - hammer = new ItemRequirement("Hammer", ItemCollections.HAMMER).isNotConsumed(); + hammer = new ItemRequirement("Hammer", ItemID.HAMMER); hammer.setTooltip("a standard hammer, NOT Imcando Hammer, as it will be given to Dr. Fenkenstrain"); nails = new ItemRequirement("Nails", ItemCollections.NAILS); holySymbol = new ItemRequirement("Holy symbol", ItemID.BLESSEDSTAR).isNotConsumed(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/therestlessghost/TheRestlessGhost.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/therestlessghost/TheRestlessGhost.java index 859707b5434..534620bed0c 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/therestlessghost/TheRestlessGhost.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/therestlessghost/TheRestlessGhost.java @@ -27,10 +27,10 @@ import net.runelite.client.plugins.microbot.questhelper.panel.PanelDetails; import net.runelite.client.plugins.microbot.questhelper.questhelpers.BasicQuestHelper; import net.runelite.client.plugins.microbot.questhelper.requirements.Requirement; -import net.runelite.client.plugins.microbot.questhelper.requirements.conditional.Conditions; import net.runelite.client.plugins.microbot.questhelper.requirements.conditional.NpcCondition; import net.runelite.client.plugins.microbot.questhelper.requirements.conditional.ObjectCondition; import net.runelite.client.plugins.microbot.questhelper.requirements.item.ItemRequirement; +import static net.runelite.client.plugins.microbot.questhelper.requirements.util.LogicHelper.and; import net.runelite.client.plugins.microbot.questhelper.requirements.var.VarbitRequirement; import net.runelite.client.plugins.microbot.questhelper.requirements.zone.Zone; import net.runelite.client.plugins.microbot.questhelper.requirements.zone.ZoneRequirement; @@ -41,77 +41,62 @@ import net.runelite.client.plugins.microbot.questhelper.steps.NpcStep; import net.runelite.client.plugins.microbot.questhelper.steps.ObjectStep; import net.runelite.client.plugins.microbot.questhelper.steps.QuestStep; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import net.runelite.api.Skill; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.ItemID; import net.runelite.api.gameval.NpcID; import net.runelite.api.gameval.ObjectID; - -import java.util.*; +import net.runelite.api.gameval.VarbitID; public class TheRestlessGhost extends BasicQuestHelper { - //Items Required - private ItemRequirement ghostspeakAmulet, skull; - - //Items Recommended - private ItemRequirement lumbridgeTeleports, passage; - - private Requirement ghostSpawned, coffinOpened, inBasement, hasSkull; - - private QuestStep talkToAereck, talkToUrhney, speakToGhost, openCoffin, searchCoffin, enterWizardsTowerBasement, searchAltarAndRun, exitWizardsTowerBasement, - openCoffinToPutSkullIn, putSkullInCoffin; - - //Zones - private Zone basement; - - @Override - public Map loadSteps() - { - initializeRequirements(); - setupConditions(); - setupSteps(); - Map steps = new HashMap<>(); - - steps.put(0, talkToAereck); - steps.put(1, talkToUrhney); - - ConditionalStep talkToGhost = new ConditionalStep(this, openCoffin); - talkToGhost.addStep(ghostSpawned, speakToGhost); - talkToGhost.addStep(coffinOpened, searchCoffin); - steps.put(2, talkToGhost); - - ConditionalStep getSkullForGhost = new ConditionalStep(this, enterWizardsTowerBasement); - getSkullForGhost.addStep(inBasement, searchAltarAndRun); - steps.put(3, getSkullForGhost); - - ConditionalStep returnSkullToGhost = new ConditionalStep(this, enterWizardsTowerBasement); - returnSkullToGhost.addStep(new Conditions(inBasement, hasSkull), exitWizardsTowerBasement); - returnSkullToGhost.addStep(new Conditions(hasSkull, coffinOpened), putSkullInCoffin); - returnSkullToGhost.addStep(hasSkull, openCoffinToPutSkullIn); - returnSkullToGhost.addStep(inBasement, searchAltarAndRun); - steps.put(4, returnSkullToGhost); - - return steps; - } + // Recommended items + ItemRequirement lumbridgeTeleports; + ItemRequirement passage; + + // Mid-quest required items + ItemRequirement ghostspeakAmulet; + ItemRequirement skull; + + // Miscellaneous requirements + Requirement ghostSpawned; + Requirement coffinOpened; + Requirement inBasement; + Requirement hasSkull; + + // Zones + Zone wizardsTowerBasement; + + // Steps + NpcStep talkToAereck; + NpcStep talkToUrhney; + ObjectStep openCoffin; + ObjectStep searchCoffin; + NpcStep speakToGhost; + ObjectStep enterWizardsTowerBasement; + ObjectStep searchAltarAndRun; + ObjectStep exitWizardsTowerBasement; + ObjectStep openCoffinToPutSkullIn; + ObjectStep putSkullInCoffin; @Override protected void setupZones() { - basement = new Zone(new WorldPoint(3094, 9553, 0), new WorldPoint(3125, 9582, 0)); + wizardsTowerBasement = new Zone(new WorldPoint(3094, 9553, 0), new WorldPoint(3125, 9582, 0)); } - public void setupConditions() + @Override + protected void setupRequirements() { ghostSpawned = new NpcCondition(NpcID.GHOSTX); coffinOpened = new ObjectCondition(ObjectID.OPENGHOSTCOFFIN); - inBasement = new ZoneRequirement(basement); - hasSkull = new VarbitRequirement(2130, 1); - } + inBasement = new ZoneRequirement(wizardsTowerBasement); + hasSkull = new VarbitRequirement(VarbitID.RESTLESS_GHOST_ALTAR_VAR, 1); - @Override - protected void setupRequirements() - { lumbridgeTeleports = new ItemRequirement("Lumbridge teleports", ItemID.POH_TABLET_LUMBRIDGETELEPORT, 2); ghostspeakAmulet = new ItemRequirement("Ghostspeak amulet", ItemID.AMULET_OF_GHOSTSPEAK, 1, true).isNotConsumed(); ghostspeakAmulet.setTooltip("If you've lost it you can get another from Father Urhney in his hut in the south east of Lumbridge Swamp"); @@ -125,23 +110,15 @@ public void setupSteps() { talkToAereck = new NpcStep(this, NpcID.FATHER_AERECK, new WorldPoint(3243, 3206, 0), "Talk to Father Aereck in the Lumbridge Church."); talkToAereck.addDialogStep("I'm looking for a quest!"); - talkToAereck.addDialogStep("Ok, let me help then."); + talkToAereck.addDialogStep("Yes."); - talkToUrhney = new NpcStep(this, NpcID.FATHER_URHNEY, new WorldPoint(3147, 3175, 0), "Talk to Father Urhney in the south west of Lumbridge Swamp."); + talkToUrhney = new NpcStep(this, NpcID.FATHER_URHNEY, new WorldPoint(3147, 3175, 0), "Talk to Father Urhney, west of the Lumbridge Swamp."); talkToUrhney.addDialogStep("Father Aereck sent me to talk to you."); talkToUrhney.addDialogStep("He's got a ghost haunting his graveyard."); openCoffin = new ObjectStep(this, ObjectID.SHUTGHOSTCOFFIN, new WorldPoint(3250, 3193, 0), "Open the coffin in the Lumbridge Graveyard to spawn the ghost.", ghostspeakAmulet); - openCoffin.addDialogStep("Yep, now tell me what the problem is."); - openCoffin.addDialogStep("Yep, clever aren't I?."); - openCoffin.addDialogStep("Yes, ok. Do you know WHY you're a ghost?"); - openCoffin.addDialogStep("Yes, ok. Do you know why you're a ghost?"); searchCoffin = new ObjectStep(this, ObjectID.OPENGHOSTCOFFIN, new WorldPoint(3250, 3193, 0), "Search the coffin in the Lumbridge Graveyard to spawn the ghost.", ghostspeakAmulet); - searchCoffin.addDialogStep("Yep, now tell me what the problem is."); - searchCoffin.addDialogStep("Yep, clever aren't I?."); - searchCoffin.addDialogStep("Yes, ok. Do you know WHY you're a ghost?"); - searchCoffin.addDialogStep("Yes, ok. Do you know why you're a ghost?"); speakToGhost = new NpcStep(this, NpcID.GHOSTX, new WorldPoint(3250, 3195, 0), "Speak to the Ghost that appears whilst wearing your Ghostspeak Amulet.", ghostspeakAmulet); speakToGhost.addDialogStep("Yep, now tell me what the problem is."); @@ -149,20 +126,51 @@ public void setupSteps() speakToGhost.addDialogStep("Yes, ok. Do you know WHY you're a ghost?"); speakToGhost.addDialogStep("Yes, ok. Do you know why you're a ghost?"); - enterWizardsTowerBasement = new ObjectStep(this, ObjectID.WIZARDS_TOWER_LADDERTOP, new WorldPoint(3104, 3162, 0), "Enter the Wizards' Tower basement."); + enterWizardsTowerBasement = new ObjectStep(this, ObjectID.WIZARDS_TOWER_LADDERTOP, new WorldPoint(3104, 3162, 0), "Enter the Wizards' Tower basement, south of Draynor village."); searchAltarAndRun = new ObjectStep(this, ObjectID.RESTLESS_GHOST_ALTAR, new WorldPoint(3120, 9567, 0), "Search the Altar. A skeleton (level 13) will appear and attack you, but you can just run away."); exitWizardsTowerBasement = new ObjectStep(this, ObjectID.WIZARDS_TOWER_LADDER, new WorldPoint(3103, 9576, 0), "Leave the basement.", skull); - openCoffinToPutSkullIn = new ObjectStep(this, ObjectID.SHUTGHOSTCOFFIN, new WorldPoint(3250, 3193, 0), "Open the ghost's coffin in Lumbridge graveyard.", skull); - putSkullInCoffin = new ObjectStep(this, ObjectID.OPENGHOSTCOFFIN, new WorldPoint(3250, 3193, 0), "Search the coffin.", skull); + openCoffinToPutSkullIn = new ObjectStep(this, ObjectID.SHUTGHOSTCOFFIN, new WorldPoint(3250, 3193, 0), "Open the ghost's coffin in the Lumbridge graveyard.", skull); + putSkullInCoffin = new ObjectStep(this, ObjectID.OPENGHOSTCOFFIN, new WorldPoint(3250, 3193, 0), "Search the coffin to put the skull back and finish the quest.", skull); + } + + @Override + public Map loadSteps() + { + initializeRequirements(); + setupSteps(); + + var steps = new HashMap(); + + steps.put(0, talkToAereck); + steps.put(1, talkToUrhney); + + var talkToGhost = new ConditionalStep(this, openCoffin); + talkToGhost.addStep(ghostSpawned, speakToGhost); + talkToGhost.addStep(coffinOpened, searchCoffin); + steps.put(2, talkToGhost); + + var getSkullForGhost = new ConditionalStep(this, enterWizardsTowerBasement); + getSkullForGhost.addStep(inBasement, searchAltarAndRun); + steps.put(3, getSkullForGhost); + + var returnSkullToGhost = new ConditionalStep(this, enterWizardsTowerBasement); + returnSkullToGhost.addStep(and(inBasement, hasSkull), exitWizardsTowerBasement); + returnSkullToGhost.addStep(and(hasSkull, coffinOpened), putSkullInCoffin); + returnSkullToGhost.addStep(hasSkull, openCoffinToPutSkullIn); + returnSkullToGhost.addStep(inBasement, searchAltarAndRun); + steps.put(4, returnSkullToGhost); + + return steps; } + @Override public List getItemRecommended() { - ArrayList recommended = new ArrayList<>(); - recommended.add(lumbridgeTeleports); - recommended.add(passage); - return recommended; + return List.of( + lumbridgeTeleports, + passage + ); } @Override @@ -174,30 +182,51 @@ public QuestPointReward getQuestPointReward() @Override public List getExperienceRewards() { - return Collections.singletonList(new ExperienceReward(Skill.PRAYER, 1125)); + return List.of( + new ExperienceReward(Skill.PRAYER, 1125) + ); } @Override public List getItemRewards() { - return Collections.singletonList(new ItemReward("Ghostspeak Amulet", ItemID.AMULET_OF_GHOSTSPEAK, 1)); + return List.of( + new ItemReward("Ghostspeak amulet", ItemID.AMULET_OF_GHOSTSPEAK) + ); } @Override - public List getPanels() + public List getCombatRequirements() { - List allSteps = new ArrayList<>(); - - allSteps.add(new PanelDetails("Talk to Father Aereck", Collections.singletonList(talkToAereck))); - allSteps.add(new PanelDetails("Get a ghostspeak amulet", Collections.singletonList(talkToUrhney))); - allSteps.add(new PanelDetails("Talk to the ghost", Arrays.asList(openCoffin, searchCoffin, speakToGhost))); - allSteps.add(new PanelDetails("Return the ghost's skull", Arrays.asList(enterWizardsTowerBasement, searchAltarAndRun, exitWizardsTowerBasement, openCoffinToPutSkullIn, putSkullInCoffin))); - return allSteps; + return List.of( + "A skeleton (level 13) you can run away from" + ); } @Override - public List getCombatRequirements() + public List getPanels() { - return Collections.singletonList("A skeleton (level 13) you can run away from"); + var panels = new ArrayList(); + + panels.add(new PanelDetails("Talk to Father Aereck", List.of( + talkToAereck + ))); + panels.add(new PanelDetails("Get a ghostspeak amulet", List.of( + talkToUrhney + ))); + panels.add(new PanelDetails("Talk to the ghost", List.of( + openCoffin, + searchCoffin, + speakToGhost + ))); + panels.add(new PanelDetails("Return the ghost's skull", List.of( + enterWizardsTowerBasement, + searchAltarAndRun, + exitWizardsTowerBasement, + openCoffinToPutSkullIn, + putSkullInCoffin + ))); + + return panels; } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/treegnomevillage/TreeGnomeVillage.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/treegnomevillage/TreeGnomeVillage.java index 10fada31695..9beea133524 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/treegnomevillage/TreeGnomeVillage.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/treegnomevillage/TreeGnomeVillage.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2020, Patyfatycake + * Copyright (c) 2025, pajlada * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,21 +30,29 @@ import net.runelite.client.plugins.microbot.questhelper.panel.PanelDetails; import net.runelite.client.plugins.microbot.questhelper.questhelpers.BasicQuestHelper; import net.runelite.client.plugins.microbot.questhelper.questinfo.QuestVarPlayer; -import net.runelite.client.plugins.microbot.questhelper.requirements.Requirement; -import net.runelite.client.plugins.microbot.questhelper.requirements.conditional.Conditions; import net.runelite.client.plugins.microbot.questhelper.requirements.item.ItemOnTileRequirement; import net.runelite.client.plugins.microbot.questhelper.requirements.item.ItemRequirement; import net.runelite.client.plugins.microbot.questhelper.requirements.npc.NpcHintArrowRequirement; -import net.runelite.client.plugins.microbot.questhelper.requirements.util.LogicType; +import static net.runelite.client.plugins.microbot.questhelper.requirements.util.LogicHelper.and; import net.runelite.client.plugins.microbot.questhelper.requirements.util.Operation; import net.runelite.client.plugins.microbot.questhelper.requirements.var.VarbitRequirement; import net.runelite.client.plugins.microbot.questhelper.requirements.var.VarplayerRequirement; import net.runelite.client.plugins.microbot.questhelper.requirements.zone.Zone; import net.runelite.client.plugins.microbot.questhelper.requirements.zone.ZoneRequirement; import net.runelite.client.plugins.microbot.questhelper.rewards.ExperienceReward; +import net.runelite.client.plugins.microbot.questhelper.rewards.ItemReward; import net.runelite.client.plugins.microbot.questhelper.rewards.QuestPointReward; import net.runelite.client.plugins.microbot.questhelper.rewards.UnlockReward; -import net.runelite.client.plugins.microbot.questhelper.steps.*; +import net.runelite.client.plugins.microbot.questhelper.steps.ConditionalStep; +import net.runelite.client.plugins.microbot.questhelper.steps.DetailedQuestStep; +import net.runelite.client.plugins.microbot.questhelper.steps.ItemStep; +import net.runelite.client.plugins.microbot.questhelper.steps.NpcStep; +import net.runelite.client.plugins.microbot.questhelper.steps.ObjectStep; +import net.runelite.client.plugins.microbot.questhelper.steps.QuestStep; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import net.runelite.api.Skill; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.ItemID; @@ -51,174 +60,121 @@ import net.runelite.api.gameval.ObjectID; import net.runelite.api.gameval.VarbitID; -import java.util.*; - public class TreeGnomeVillage extends BasicQuestHelper { - //Items Required - ItemRequirement logRequirement, orbsOfProtection; - - private QuestStep talkToCommanderMontai, bringWoodToCommanderMontai, talkToCommanderMontaiAgain, - firstTracker, secondTracker, thirdTracker, fireBallista, fireBallista1, fireBallista2, fireBallista3, fireBallista4, climbTheLadder, - talkToKingBolrenFirstOrb, talkToTheWarlord, fightTheWarlord, returnOrbs, finishQuestDialog, elkoySkip; - - Requirement completeFirstTracker, completeSecondTracker, completeThirdTracker, handedInOrbs, - notCompleteFirstTracker, notCompleteSecondTracker, notCompleteThirdTracker, orbsOfProtectionNearby, givenWood; - - private Conditions talkToSecondTracker, talkToThirdTracker, completedTrackers, - shouldFireBallista1, shouldFireBallista2, shouldFireBallista3, shouldFireBallista4; - - private ConditionalStep retrieveOrb, talkToBolrenAtCentreOfMaze, fireBalistaConditional, returnFirstOrb; - - //Zones - Zone upstairsTower, zoneVillage; - ZoneRequirement isUpstairsTower, insideGnomeVillage; - - private final int TRACKER_1_VARBITID = 599; - private final int TRACKER_2_VARBITID = 600; - private final int TRACKER_3_VARBITID = 601; + // Required items + ItemRequirement sixLogs; + ItemRequirement combatGear; + + // Recommended items + ItemRequirement food; + + // Miscellaneous requirement + VarplayerRequirement givenWood; + + VarbitRequirement needToTalkToFirstTracker; + VarbitRequirement needToTalkToSecondTracker; + VarbitRequirement needToTalkToThirdTracker; + VarbitRequirement shouldFireBallista1; + VarbitRequirement shouldFireBallista2; + VarbitRequirement shouldFireBallista3; + VarbitRequirement shouldFireBallista4; + NpcHintArrowRequirement fightingWarlord; + ItemOnTileRequirement orbsOfProtectionNearby; + /// Has handed in all orbs to King Bolren + VarbitRequirement handedInOrbs; + + ZoneRequirement isUpstairsTower; + ZoneRequirement insideGnomeVillage; + + /// First orb of protection from the battlefield + ItemRequirement firstOrb; + /// Remaining orbs of protection from the Khazard warlord + ItemRequirement orbsOfProtection; + + // Zones + Zone upstairsTower; + Zone zoneVillage; + + // Steps + ConditionalStep talkToBolrenAtCentreOfMaze; + NpcStep talkToCommanderMontai; + NpcStep bringWoodToCommanderMontai; + NpcStep talkToCommanderMontaiAgain; + NpcStep firstTracker; + NpcStep secondTracker; + NpcStep thirdTracker; + ObjectStep fireBallista; + ObjectStep fireBallista1; + ObjectStep fireBallista2; + ObjectStep fireBallista3; + ObjectStep fireBallista4; + ConditionalStep fireBalistaConditional; + ObjectStep climbTheLadder; + ConditionalStep cRetrieveOrb; + NpcStep elkoySkip; + NpcStep talkToKingBolrenFirstOrb; + ConditionalStep returnFirstOrb; + NpcStep talkToTheWarlord; + NpcStep fightTheWarlord; + ItemStep pickupOrb; + NpcStep returnOrbs; + NpcStep finishQuestDialog; + NpcStep elkoySkip2; + ConditionalStep cReturnOrbs; @Override - public Map loadSteps() - { - initializeRequirements(); - setupConditions(); - setupSteps(); - - return CreateSteps(); - } - - private Map CreateSteps() - { - Map steps = new HashMap<>(); - steps.put(0, talkToBolrenAtCentreOfMaze); - steps.put(1, talkToCommanderMontai); - steps.put(2, bringWoodToCommanderMontai); - steps.put(3, talkToCommanderMontaiAgain); - steps.put(4, talkToTrackersStep()); - steps.put(5, retrieveOrbStep()); - steps.put(6, returnFirstOrb); - steps.put(7, defeatWarlordStep()); - steps.put(8, returnOrbsStep()); - return steps; - } - - private QuestStep talkToTrackersStep() - { - fireBalistaConditional = new ConditionalStep(this, fireBallista, "Fire the ballista at the tower."); - fireBalistaConditional.addStep(shouldFireBallista1, fireBallista1); - fireBalistaConditional.addStep(shouldFireBallista2, fireBallista2); - fireBalistaConditional.addStep(shouldFireBallista3, fireBallista3); - fireBalistaConditional.addStep(shouldFireBallista4, fireBallista4); - fireBalistaConditional.addSubSteps(fireBallista, fireBallista1, fireBallista2, fireBallista3, fireBallista4); - - ConditionalStep talkToTrackers = new ConditionalStep(this, firstTracker); - talkToTrackers.addStep(talkToSecondTracker, secondTracker); - talkToTrackers.addStep(talkToThirdTracker, thirdTracker); - talkToTrackers.addStep(completedTrackers, fireBalistaConditional); - - return talkToTrackers; - } - - private QuestStep retrieveOrbStep() + protected void setupZones() { - retrieveOrb = new ConditionalStep(this, climbTheLadder, "Enter the tower by the Crumbled wall and climb the ladder to retrieve the first orb from chest."); - ObjectStep getOrbFromChest = new ObjectStep(this, ObjectID.CHESTCLOSED_KHAZARD, new WorldPoint(2506, 3259, 1), "Retrieve the first orb from chest."); - getOrbFromChest.addAlternateObjects(ObjectID.CHESTOPEN_KHAZARD); - retrieveOrb.addStep(isUpstairsTower, getOrbFromChest); - retrieveOrb.addSubSteps(getOrbFromChest, climbTheLadder); - return retrieveOrb; + upstairsTower = new Zone(new WorldPoint(2500, 3251, 1), new WorldPoint(2506, 3259, 1)); + zoneVillage = new Zone(new WorldPoint(2514, 3158, 0), new WorldPoint(2542, 3175, 0)); } - private QuestStep defeatWarlordStep() + @Override + protected void setupRequirements() { - NpcHintArrowRequirement fightingWarlord = new NpcHintArrowRequirement(NpcID.KHAZARD_WARLORD_COMBAT); - - fightTheWarlord = new NpcStep(this, NpcID.KHAZARD_WARLORD_COMBAT, new WorldPoint(2456, 3301, 0), - "Defeat the warlord and retrieve orbs."); - talkToTheWarlord = new NpcStep(this, NpcID.KHAZARD_WARLORD_CHAT, new WorldPoint(2456, 3301, 0), - "Talk to the Warlord south west of West Ardougne, ready to fight him."); - - ItemRequirement food = new ItemRequirement("Food", ItemCollections.GOOD_EATING_FOOD, -1); - - ItemRequirement combatGear = new ItemRequirement("A Weapon & Armour (magic is best)", -1); - combatGear.setDisplayItemId(BankSlotIcons.getMagicCombatGear()); + givenWood = new VarplayerRequirement(QuestVarPlayer.QUEST_TREE_GNOME_VILLAGE.getId(), 3, Operation.GREATER_EQUAL); - ConditionalStep defeatTheWarlord = new ConditionalStep(this, talkToTheWarlord, - food, - combatGear); + needToTalkToFirstTracker = new VarbitRequirement(VarbitID.GNOMETRACKER_H, 0); + needToTalkToSecondTracker = new VarbitRequirement(VarbitID.GNOMETRACKER_Y, 0); + needToTalkToThirdTracker = new VarbitRequirement(VarbitID.GNOMETRACKER_X, 0); - defeatTheWarlord.addStep(fightingWarlord, fightTheWarlord); + insideGnomeVillage = new ZoneRequirement(zoneVillage); + isUpstairsTower = new ZoneRequirement(upstairsTower); + shouldFireBallista1 = new VarbitRequirement(VarbitID.BALLISTA, 0); + shouldFireBallista2 = new VarbitRequirement(VarbitID.BALLISTA, 1); + shouldFireBallista3 = new VarbitRequirement(VarbitID.BALLISTA, 2); + shouldFireBallista4 = new VarbitRequirement(VarbitID.BALLISTA, 3); - return defeatTheWarlord; - } + fightingWarlord = new NpcHintArrowRequirement(NpcID.KHAZARD_WARLORD_COMBAT); - private QuestStep returnOrbsStep() - { + orbsOfProtectionNearby = new ItemOnTileRequirement(ItemID.ORBS_OF_PROTECTION); handedInOrbs = new VarbitRequirement(VarbitID.BOLREN_GOT_ORBS, 1, Operation.GREATER_EQUAL); - orbsOfProtectionNearby = new ItemOnTileRequirement(ItemID.ORBS_OF_PROTECTION); - ItemStep pickupOrb = new ItemStep(this, - "Pick up the nearby Orbs of Protection.", orbsOfProtection); - returnOrbs.addSubSteps(pickupOrb); + food = new ItemRequirement("Food", ItemCollections.GOOD_EATING_FOOD, -1); + combatGear = new ItemRequirement("Combat gear (magic is best)", -1, -1); + combatGear.setDisplayItemId(BankSlotIcons.getMagicCombatGear()); - ConditionalStep returnOrbsSteps = new ConditionalStep(this, returnOrbs); - returnOrbsSteps.addStep(orbsOfProtectionNearby, pickupOrb); - returnOrbsSteps.addStep(handedInOrbs, finishQuestDialog); + sixLogs = new ItemRequirement("Logs", ItemID.LOGS, 6).hideConditioned(givenWood); - return returnOrbsSteps; - } + firstOrb = new ItemRequirement("Orb of protection", ItemID.ORB_OF_PROTECTION, 1); + firstOrb.setTooltip("If you have lost the orb you can get another from the chest"); - @Override - protected void setupRequirements() - { - givenWood = new VarplayerRequirement(QuestVarPlayer.QUEST_TREE_GNOME_VILLAGE.getId(), 3, Operation.GREATER_EQUAL); - logRequirement = new ItemRequirement("Logs", ItemID.LOGS, 6).hideConditioned(givenWood); orbsOfProtection = new ItemRequirement("Orbs of protection", ItemID.ORBS_OF_PROTECTION); orbsOfProtection.setTooltip("You can retrieve the orbs of protection again by killing the Khazard Warlord again."); } - @Override - protected void setupZones() - { - upstairsTower = new Zone(new WorldPoint(2500, 3251, 1), new WorldPoint(2506, 3259, 1)); - zoneVillage = new Zone(new WorldPoint(2514, 3158, 0), new WorldPoint(2542, 3175, 0)); - } - - public void setupConditions() - { - notCompleteFirstTracker = new VarbitRequirement(TRACKER_1_VARBITID, 0); - notCompleteSecondTracker = new VarbitRequirement(TRACKER_2_VARBITID, 0); - notCompleteThirdTracker = new VarbitRequirement(TRACKER_3_VARBITID, 0); - - completeFirstTracker = new VarbitRequirement(TRACKER_1_VARBITID, 1); - completeSecondTracker = new VarbitRequirement(TRACKER_2_VARBITID, 1); - completeThirdTracker = new VarbitRequirement(TRACKER_3_VARBITID, 1); - - insideGnomeVillage = new ZoneRequirement(zoneVillage); - isUpstairsTower = new ZoneRequirement(upstairsTower); - - talkToSecondTracker = new Conditions(LogicType.AND, completeFirstTracker, notCompleteSecondTracker); - talkToThirdTracker = new Conditions(LogicType.AND, completeFirstTracker, notCompleteThirdTracker); - - completedTrackers = new Conditions(LogicType.AND, completeFirstTracker, completeSecondTracker, completeThirdTracker); - - shouldFireBallista1 = new Conditions(LogicType.AND, completedTrackers, new VarbitRequirement(602, 0)); - shouldFireBallista2 = new Conditions(LogicType.AND, completedTrackers, new VarbitRequirement(602, 1)); - shouldFireBallista3 = new Conditions(LogicType.AND, completedTrackers, new VarbitRequirement(602, 2)); - shouldFireBallista4 = new Conditions(LogicType.AND, completedTrackers, new VarbitRequirement(602, 3)); - } - private void setupSteps() { - QuestStep talkToKingBolren = new NpcStep(this, NpcID.KING_BOLREN, new WorldPoint(2541, 3170, 0), ""); + var talkToKingBolren = new NpcStep(this, NpcID.KING_BOLREN, new WorldPoint(2541, 3170, 0), ""); talkToKingBolren.addDialogStep("Can I help at all?"); talkToKingBolren.addDialogStep("I would be glad to help."); + talkToKingBolren.addDialogStep("Yes."); - DetailedQuestStep goThroughMaze = new DetailedQuestStep(this, new WorldPoint(2541, 3170, 0), "Follow the marked path to walk through the maze."); - List pathThroughMaze = Arrays.asList( + var goThroughMaze = new DetailedQuestStep(this, new WorldPoint(2541, 3170, 0), "Follow the marked path to walk through the maze."); + var pathThroughMaze = List.of( new WorldPoint(2505, 3190, 0), new WorldPoint(2512, 3190, 0), new WorldPoint(2512, 3188, 0), @@ -258,7 +214,8 @@ private void setupSteps() new WorldPoint(2545, 3156, 0), new WorldPoint(2520, 3156, 0), new WorldPoint(2520, 3159, 0), - new WorldPoint(2515, 3159, 0)); + new WorldPoint(2515, 3159, 0) + ); goThroughMaze.setLinePoints(pathThroughMaze); talkToBolrenAtCentreOfMaze = new ConditionalStep(this, goThroughMaze, @@ -266,17 +223,17 @@ private void setupSteps() talkToBolrenAtCentreOfMaze.addStep(insideGnomeVillage, talkToKingBolren); talkToBolrenAtCentreOfMaze.addSubSteps(talkToKingBolren, goThroughMaze); - talkToCommanderMontai = new NpcStep(this, NpcID.COMMANDER_MONTAI, new WorldPoint(2523, 3208, 0), "Speak with Commander Montai."); + talkToCommanderMontai = new NpcStep(this, NpcID.COMMANDER_MONTAI, new WorldPoint(2523, 3208, 0), "Speak with Commander Montai, north-east of the maze entrance."); talkToCommanderMontai.addDialogStep("Ok, I'll gather some wood."); - bringWoodToCommanderMontai = new NpcStep(this, NpcID.COMMANDER_MONTAI, new WorldPoint(2523, 3208, 0), "Speak with Commander Montai again to give him the wood.", logRequirement); + bringWoodToCommanderMontai = new NpcStep(this, NpcID.COMMANDER_MONTAI, new WorldPoint(2523, 3208, 0), "Speak with Commander Montai again to give him the wood.", sixLogs); talkToCommanderMontaiAgain = new NpcStep(this, NpcID.COMMANDER_MONTAI, new WorldPoint(2523, 3208, 0), "Speak with Commander Montai."); talkToCommanderMontaiAgain.addDialogStep("I'll try my best."); - firstTracker = new NpcStep(this, NpcID.TRACKER1, new WorldPoint(2501, 3261, 0), "Talk to the first tracker gnome to the northwest."); - secondTracker = new NpcStep(this, NpcID.TRACKER2, new WorldPoint(2524, 3257, 0), "Talk to the second tracker gnome inside the jail."); - thirdTracker = new NpcStep(this, NpcID.TRACKER3, new WorldPoint(2497, 3234, 0), "Talk to the third tracker gnome to the southwest."); + firstTracker = new NpcStep(this, NpcID.TRACKER1, new WorldPoint(2501, 3261, 0), "Talk to the first tracker gnome to the north-west of the battlefield for the height coordinate."); + secondTracker = new NpcStep(this, NpcID.TRACKER2, new WorldPoint(2524, 3257, 0), "Talk to the second tracker gnome inside the jail for the y coordinate."); + thirdTracker = new NpcStep(this, NpcID.TRACKER3, new WorldPoint(2497, 3234, 0), "Talk to the third tracker gnome to the south-west of the jail."); fireBallista = new ObjectStep(this, ObjectID.CATABOW, new WorldPoint(2509, 3211, 0), ""); fireBallista1 = new ObjectStep(this, ObjectID.CATABOW, new WorldPoint(2509, 3211, 0), ""); @@ -288,38 +245,110 @@ private void setupSteps() fireBallista4 = new ObjectStep(this, ObjectID.CATABOW, new WorldPoint(2509, 3211, 0), ""); fireBallista4.addDialogStep("0004"); + fireBalistaConditional = new ConditionalStep(this, fireBallista, "Fire the ballista at the tower."); + fireBalistaConditional.addStep(shouldFireBallista1, fireBallista1); + fireBalistaConditional.addStep(shouldFireBallista2, fireBallista2); + fireBalistaConditional.addStep(shouldFireBallista3, fireBallista3); + fireBalistaConditional.addStep(shouldFireBallista4, fireBallista4); + climbTheLadder = new ObjectStep(this, ObjectID.LADDER, new WorldPoint(2503, 3252, 0), "Climb the ladder"); - ItemRequirement firstOrb = new ItemRequirement("Orb of protection", ItemID.ORB_OF_PROTECTION, 1); - firstOrb.setTooltip("If you have lost the orb you can get another from the chest"); + var getOrbFromChest = new ObjectStep(this, ObjectID.CHESTCLOSED_KHAZARD, new WorldPoint(2506, 3259, 1), "Retrieve the first orb from chest."); + getOrbFromChest.addAlternateObjects(ObjectID.CHESTOPEN_KHAZARD); + + cRetrieveOrb = new ConditionalStep(this, climbTheLadder, "Enter the tower by the Crumbled wall and climb the ladder to retrieve the first orb from chest."); + cRetrieveOrb.addStep(isUpstairsTower, getOrbFromChest); + cRetrieveOrb.addSubSteps(getOrbFromChest, climbTheLadder); + + elkoySkip = new NpcStep(this, NpcID.ELKOY_2OPS, new WorldPoint(2505, 3191, 0), + "Talk to Elkoy outside the maze to travel to the centre.", firstOrb); + elkoySkip.addDialogStep("Yes please."); + talkToKingBolrenFirstOrb = new NpcStep(this, NpcID.KING_BOLREN, new WorldPoint(2541, 3170, 0), - "Speak to King Bolren in the centre of the Tree Gnome Maze.", firstOrb); + "", firstOrb); talkToKingBolrenFirstOrb.addDialogStep("I will find the warlord and bring back the orbs."); - elkoySkip = new NpcStep(this, NpcID.ELKOY_2OPS, new WorldPoint(2505, 3191, 0), - "Talk to Elkoy outside the maze to travel to the centre."); returnFirstOrb = new ConditionalStep(this, elkoySkip, - "Speak to King Bolren in the centre of the Tree Gnome Maze."); + "Return the Orb of protection to King Bolren in the centre of the Tree Gnome Maze."); returnFirstOrb.addStep(insideGnomeVillage, talkToKingBolrenFirstOrb); returnFirstOrb.addSubSteps(talkToKingBolrenFirstOrb, elkoySkip); - returnOrbs = new NpcStep(this, NpcID.KING_BOLREN, new WorldPoint(2541, 3170, 0), - "Talk to King Bolren in the centre of the Tree Gnome Maze.", orbsOfProtection); + talkToTheWarlord = new NpcStep(this, NpcID.KHAZARD_WARLORD_CHAT, new WorldPoint(2456, 3301, 0), + "Talk to the Khazard warlord, south west of West Ardougne, ready to fight him."); + + fightTheWarlord = new NpcStep(this, NpcID.KHAZARD_WARLORD_COMBAT, new WorldPoint(2456, 3301, 0), + "Defeat the Khazard warlord and retrieve orbs."); + + pickupOrb = new ItemStep(this, "Pick up the nearby Orbs of Protection.", orbsOfProtection); - finishQuestDialog = new NpcStep(this, NpcID.KING_BOLREN, new WorldPoint(2541, 3170, 0), - "Speak to King Bolren in the centre of the Tree Gnome Maze."); - returnOrbs.addSubSteps(finishQuestDialog); + returnOrbs = new NpcStep(this, NpcID.KING_BOLREN, new WorldPoint(2541, 3170, 0), "", orbsOfProtection); + + finishQuestDialog = new NpcStep(this, NpcID.KING_BOLREN, new WorldPoint(2541, 3170, 0), ""); + + elkoySkip2 = new NpcStep(this, NpcID.ELKOY_2OPS, new WorldPoint(2505, 3191, 0), + "Talk to Elkoy outside the maze to travel to the centre.", orbsOfProtection); + elkoySkip2.addDialogStep("Yes please."); + + cReturnOrbs = new ConditionalStep(this, elkoySkip2, "Return the Orbs of protection to King Bolren in the centre of the Tree Gnome Maze."); + cReturnOrbs.addStep(orbsOfProtectionNearby, pickupOrb); + cReturnOrbs.addStep(and(insideGnomeVillage, handedInOrbs), finishQuestDialog); + cReturnOrbs.addStep(insideGnomeVillage, returnOrbs); + } + + @Override + public Map loadSteps() + { + initializeRequirements(); + setupSteps(); + + var steps = new HashMap(); + + steps.put(0, talkToBolrenAtCentreOfMaze); + + steps.put(1, talkToCommanderMontai); + steps.put(2, bringWoodToCommanderMontai); + steps.put(3, talkToCommanderMontaiAgain); + + var cTalkToTrackers = new ConditionalStep(this, fireBalistaConditional); + cTalkToTrackers.addStep(needToTalkToFirstTracker, firstTracker); + cTalkToTrackers.addStep(needToTalkToSecondTracker, secondTracker); + cTalkToTrackers.addStep(needToTalkToThirdTracker, thirdTracker); + steps.put(4, cTalkToTrackers); + + steps.put(5, cRetrieveOrb); + steps.put(6, returnFirstOrb); + + var cDefeatTheWarlord = new ConditionalStep(this, talkToTheWarlord, food, combatGear); + cDefeatTheWarlord.addStep(fightingWarlord, fightTheWarlord); + steps.put(7, cDefeatTheWarlord); + + steps.put(8, cReturnOrbs); + + return steps; } @Override public List getItemRequirements() { - return Collections.singletonList(logRequirement); + return List.of( + sixLogs, + combatGear + ); + } + + @Override + public List getItemRecommended() + { + return List.of( + food + ); } @Override public List getCombatRequirements() { - return Collections.singletonList("Khazard Warlord (level 112)"); + return List.of( + "Khazard Warlord (level 112)" + ); } @Override @@ -331,31 +360,59 @@ public QuestPointReward getQuestPointReward() @Override public List getExperienceRewards() { - return Collections.singletonList(new ExperienceReward(Skill.ATTACK, 11450)); + return List.of( + new ExperienceReward(Skill.ATTACK, 11450) + ); + } + + @Override + public List getItemRewards() + { + return List.of( + new ItemReward("Gnome amulet", ItemID.GNOME_AMULET) + ); } @Override public List getUnlockRewards() { - return Collections.singletonList(new UnlockReward("Use of the Spirit Tree transportation method.")); + return List.of( + new UnlockReward("Use of the Spirit Tree transportation method.") + ); } @Override public List getPanels() { - List steps = new ArrayList<>(); - - steps.add(new PanelDetails("Getting started", Collections.singletonList(talkToBolrenAtCentreOfMaze))); - steps.add(new PanelDetails("The three trackers", Arrays.asList( - talkToCommanderMontai, bringWoodToCommanderMontai, talkToCommanderMontaiAgain, - firstTracker, secondTracker, thirdTracker, fireBalistaConditional), logRequirement)); - - ItemRequirement food = new ItemRequirement("Food", ItemCollections.GOOD_EATING_FOOD, -1); - ItemRequirement combatGear = new ItemRequirement("Weapon & Armour (magic is best)", -1); - combatGear.setDisplayItemId(BankSlotIcons.getMagicCombatGear()); - - steps.add(new PanelDetails("Retrieving the orbs", Arrays.asList(retrieveOrb, elkoySkip, talkToKingBolrenFirstOrb, - talkToTheWarlord, fightTheWarlord, returnOrbs), combatGear, food)); - return steps; + var sections = new ArrayList(); + + sections.add(new PanelDetails("Getting started", List.of( + talkToBolrenAtCentreOfMaze + ))); + + sections.add(new PanelDetails("The three trackers", List.of( + talkToCommanderMontai, + bringWoodToCommanderMontai, + talkToCommanderMontaiAgain, + firstTracker, + secondTracker, + thirdTracker, + fireBalistaConditional + ), List.of( + sixLogs + ))); + + sections.add(new PanelDetails("Retrieving the orbs", List.of( + cRetrieveOrb, + returnFirstOrb, + talkToTheWarlord, + fightTheWarlord, + cReturnOrbs + ), List.of( + combatGear, + food + ))); + + return sections; } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/witchspotion/WitchsPotion.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/witchspotion/WitchsPotion.java index 6ca38b3b3c8..3342851ab88 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/witchspotion/WitchsPotion.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/witchspotion/WitchsPotion.java @@ -33,39 +33,29 @@ import net.runelite.client.plugins.microbot.questhelper.steps.NpcStep; import net.runelite.client.plugins.microbot.questhelper.steps.ObjectStep; import net.runelite.client.plugins.microbot.questhelper.steps.QuestStep; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import net.runelite.api.Skill; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.ItemID; import net.runelite.api.gameval.NpcID; import net.runelite.api.gameval.ObjectID; -import java.util.*; - public class WitchsPotion extends BasicQuestHelper { - //Items Required - ItemRequirement ratTail, onion, burntMeat, eyeOfNewt; - - QuestStep talkToWitch, killRat, returnToWitch, drinkPotion; - - @Override - public Map loadSteps() - { - initializeRequirements(); - setupSteps(); - Map steps = new HashMap<>(); - - steps.put(0, talkToWitch); - - ConditionalStep getIngredients = new ConditionalStep(this, killRat); - getIngredients.addStep(ratTail.alsoCheckBank(questBank), returnToWitch); - - steps.put(1, getIngredients); - - steps.put(2, drinkPotion); - - return steps; - } + // Required items + ItemRequirement ratTail; + ItemRequirement onion; + ItemRequirement burntMeat; + ItemRequirement eyeOfNewt; + + // Steps + QuestStep talkToWitch; + QuestStep killRat; + QuestStep returnToWitch; + QuestStep drinkPotion; @Override protected void setupRequirements() @@ -84,30 +74,51 @@ public void setupSteps() talkToWitch = new NpcStep(this, NpcID.HETTY, new WorldPoint(2968, 3205, 0), "Talk to Hetty in Rimmington.", onion, eyeOfNewt, burntMeat); talkToWitch.addDialogStep("I am in search of a quest."); - talkToWitch.addDialogStep("Yes, help me become one with my darker side."); + talkToWitch.addDialogStep("Yes."); killRat = new NpcStep(this, NpcID.RAT_INDOORS, new WorldPoint(2956, 3203, 0), "Kill a rat in the house to the west for a rat tail.", ratTail); returnToWitch = new NpcStep(this, NpcID.HETTY, new WorldPoint(2968, 3205, 0), "Bring the ingredients to Hetty.", onion, eyeOfNewt, burntMeat, ratTail); drinkPotion = new ObjectStep(this, ObjectID.HETTYCAULDRON, new WorldPoint(2967, 3205, 0), "Drink from the cauldron to finish off the quest."); + } + @Override + public Map loadSteps() + { + initializeRequirements(); + setupSteps(); + + var steps = new HashMap(); + + steps.put(0, talkToWitch); + + var getIngredients = new ConditionalStep(this, killRat); + getIngredients.addStep(ratTail.alsoCheckBank(questBank), returnToWitch); + + steps.put(1, getIngredients); + + steps.put(2, drinkPotion); + + return steps; } @Override public List getItemRequirements() { - ArrayList reqs = new ArrayList<>(); - reqs.add(onion); - reqs.add(burntMeat); - reqs.add(eyeOfNewt); - return reqs; + return List.of( + onion, + burntMeat, + eyeOfNewt + ); } @Override public List getCombatRequirements() { - return Arrays.asList("Rat (level 1)"); + return List.of( + "Rat (level 1)" + ); } @Override @@ -119,17 +130,33 @@ public QuestPointReward getQuestPointReward() @Override public List getExperienceRewards() { - return Collections.singletonList(new ExperienceReward(Skill.MAGIC, 325)); + return List.of( + new ExperienceReward(Skill.MAGIC, 325) + ); } @Override public List getPanels() { - List allSteps = new ArrayList<>(); + var steps = new ArrayList(); + + steps.add(new PanelDetails("Starting off", List.of( + talkToWitch + ))); + + steps.add(new PanelDetails("Getting a rat's tail", List.of( + killRat + ))); - allSteps.add(new PanelDetails("Starting off", Collections.singletonList(talkToWitch))); - allSteps.add(new PanelDetails("Getting a rat's tail", Collections.singletonList(killRat))); - allSteps.add(new PanelDetails("Make the potion", Collections.singletonList(returnToWitch), ratTail, onion, burntMeat, eyeOfNewt)); - return allSteps; + steps.add(new PanelDetails("Make the potion", List.of( + returnToWitch + ), List.of( + ratTail, + onion, + burntMeat, + eyeOfNewt + ))); + + return steps; } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/managers/ItemAndLastUpdated.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/managers/ItemAndLastUpdated.java index 0e4cc31572b..88811e34f05 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/managers/ItemAndLastUpdated.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/managers/ItemAndLastUpdated.java @@ -30,6 +30,7 @@ import lombok.extern.slf4j.Slf4j; import net.runelite.api.Item; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.concurrent.Callable; @@ -42,7 +43,7 @@ public class ItemAndLastUpdated // last game tick item container was changed @Getter private int lastUpdated = -1; - private Item[] items; + private Item[] items = new Item[0]; @Setter private Callable specialMethodToObtainItems; @@ -65,7 +66,7 @@ public void update(int updateTick, Item[] items) * * @return an {@link Item}[] of items currently thought to be in the container. */ - public @Nullable Item[] getItems() + public @Nonnull Item[] getItems() { if (specialMethodToObtainItems != null) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/managers/QuestContainerManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/managers/QuestContainerManager.java index 3ae835354cd..03a1bac8dc2 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/managers/QuestContainerManager.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/managers/QuestContainerManager.java @@ -101,12 +101,11 @@ public static void updateInventory(Client client) if (result) { Item[] runesInPouch = QuestContainerManager.getRunePouchData().getItems(); - if(runesInPouch != null) { - for (Item runePouchItem : runesInPouch) - { - inventoryMap.computeIfPresent(runePouchItem.getId(), (currentVal, existingItem) -> new Item(currentVal, existingItem.getQuantity() + runePouchItem.getQuantity())); - inventoryMap.putIfAbsent(runePouchItem.getId(), runePouchItem); - } + for (Item runePouchItem : runesInPouch) + { + if (runePouchItem == null) continue; + inventoryMap.computeIfPresent(runePouchItem.getId(), (currentVal, existingItem) -> new Item(currentVal, existingItem.getQuantity() + runePouchItem.getQuantity())); + inventoryMap.putIfAbsent(runePouchItem.getId(), runePouchItem); } } QuestContainerManager.getInventoryData().update(client.getTickCount(), inventoryMap.values().toArray(new Item[0])); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/managers/QuestManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/managers/QuestManager.java index 48ca5fb7bda..36dafc24eb6 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/managers/QuestManager.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/managers/QuestManager.java @@ -44,6 +44,7 @@ import net.runelite.client.eventbus.EventBus; import net.runelite.client.plugins.PluginManager; +import javax.annotation.Nullable; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; @@ -93,6 +94,7 @@ public class QuestManager private boolean developerMode; @Getter + @Nullable private QuestHelper selectedQuest; private boolean loadQuestList = false; private QuestHelperPanel panel; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/panel/PanelDetails.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/panel/PanelDetails.java index 14c4d4efcb9..c433e44cfc0 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/panel/PanelDetails.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/panel/PanelDetails.java @@ -98,6 +98,16 @@ public PanelDetails withId(int id) return this; } + public static PanelDetails lockedPanel(String header, Requirement displayCondition, QuestStep lockingStep, List steps, Requirement... requirements) + { + var section = new PanelDetails(header, steps, requirements); + + section.setDisplayCondition(displayCondition); + section.setLockingStep(lockingStep); + + return section; + } + public void setDisplayCondition(Requirement req) { setHideCondition(new Conditions(LogicType.NOR, req)); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/panel/QuestHelperPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/panel/QuestHelperPanel.java index c1f533439bd..f4221958012 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/panel/QuestHelperPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/panel/QuestHelperPanel.java @@ -95,6 +95,8 @@ public class QuestHelperPanel extends PluginPanel private static final ImageIcon COLLAPSED_ICON; private static final ImageIcon EXPANDED_ICON; + private int nextDesiredScrollValue = 0; + static { DISCORD_ICON = Icon.DISCORD.getIcon(img -> ImageUtil.resizeImage(img, 16, 16)); @@ -389,6 +391,23 @@ public void mousePressed(MouseEvent mouseEvent) questOverviewWrapper.setLayout(new BorderLayout()); questOverviewWrapper.add(questOverviewPanel, BorderLayout.NORTH); + if (questHelperPlugin.isDeveloperMode()) + { + // If in developer mode, add this "reload quest" button. + // It's always visible under the search bar, and reloads the currently + // active quest, and ensures you're scrolled back to where you were. + var reloadQuest = new JButton("reload quest"); + reloadQuest.addActionListener((ev) -> { + nextDesiredScrollValue = scrollableContainer.getVerticalScrollBar().getValue(); + var currentQuest = questHelperPlugin.getSelectedQuest(); + if (currentQuest != null) { + currentQuest.uninitializeRequirements(); + } + setSelectedQuest(questHelperPlugin.getSelectedQuest()); + }); + searchQuestsPanel.add(reloadQuest, BorderLayout.SOUTH); + } + refreshSkillFiltering(); } @@ -534,7 +553,10 @@ public void addQuest(QuestHelper quest, boolean isActive) questOverviewPanel.addQuest(quest, isActive); questActive = true; - SwingUtilities.invokeLater(() -> scrollableContainer.getVerticalScrollBar().setValue(0)); + SwingUtilities.invokeLater(() -> { + scrollableContainer.getVerticalScrollBar().setValue(nextDesiredScrollValue); + nextDesiredScrollValue = 0; + }); repaint(); revalidate(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/panel/QuestOverviewPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/panel/QuestOverviewPanel.java index ba567a68d02..fe8f83510ef 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/panel/QuestOverviewPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/panel/QuestOverviewPanel.java @@ -91,8 +91,6 @@ public class QuestOverviewPanel extends JPanel private static final ImageIcon CLOSE_ICON = Icon.CLOSE.getIcon(); - private final JButton collapseBtn = new JButton(); - private final List questStepPanelList = new CopyOnWriteArrayList<>(); private QuestStepPanel draggingPanel = null; @@ -306,25 +304,6 @@ public void addQuest(QuestHelper quest, boolean isActive) } questStepPanelList.add(newStep); questStepsContainer.add(newStep); - newStep.addMouseListener(new MouseAdapter() - { - @Override - public void mouseClicked(MouseEvent e) - { - if (e.getButton() == MouseEvent.BUTTON1) - { - if (newStep.isCollapsed()) - { - newStep.expand(); - } - else - { - newStep.collapse(); - } - updateCollapseText(); - } - } - }); repaint(); revalidate(); } @@ -396,11 +375,6 @@ private void closeHelper() questManager.shutDownQuest(false); } - void updateCollapseText() - { - collapseBtn.setSelected(isAllCollapsed()); - } - public boolean isAllCollapsed() { return questStepPanelList.stream() diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/panel/QuestStepPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/panel/QuestStepPanel.java index cc891aef0c9..9e7ab9efdb9 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/panel/QuestStepPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/panel/QuestStepPanel.java @@ -38,12 +38,14 @@ import javax.swing.*; import javax.swing.border.EmptyBorder; import java.awt.*; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; -public class QuestStepPanel extends JPanel +public class QuestStepPanel extends JPanel implements MouseListener { private static final int TITLE_PADDING = 5; @@ -52,7 +54,7 @@ public class QuestStepPanel extends JPanel private final QuestHelperPlugin questHelperPlugin; private final JPanel headerPanel = new JPanel(); - private final JLabel headerLabel = JGenerator.makeJLabel(); + private final JTextPane headerLabel = JGenerator.makeJTextPane(); private final JPanel bodyPanel = new JPanel(); private final JCheckBox lockStep = new JCheckBox(); @Getter @@ -76,13 +78,16 @@ public QuestStepPanel(PanelDetails panelDetails, QuestStep currentStep, QuestMan leftTitleContainer = new JPanel(new BorderLayout(5, 0)); + headerLabel.addMouseListener(this); + addMouseListener(this); + headerLabel.setText(panelDetails.getHeader()); headerLabel.setFont(FontManager.getRunescapeBoldFont()); headerLabel.setMinimumSize(new Dimension(1, headerLabel.getPreferredSize().height)); headerPanel.setLayout(new BoxLayout(headerPanel, BoxLayout.X_AXIS)); - headerPanel.setBorder(new EmptyBorder(7, 7, 7, 7)); + headerPanel.setBorder(new EmptyBorder(7, 7, 3, 7)); headerPanel.add(Box.createRigidArea(new Dimension(TITLE_PADDING, 0))); leftTitleContainer.add(headerLabel, BorderLayout.CENTER); @@ -334,7 +339,7 @@ private void lockSection(boolean locked) } } - void collapse() + private void collapse() { if (!isCollapsed()) { @@ -343,7 +348,7 @@ void collapse() } } - void expand() + private void expand() { if (isCollapsed()) { @@ -404,4 +409,40 @@ private QuestStep currentlyActiveQuestSidebarStep() { return questHelperPlugin.getSelectedQuest().getCurrentStep().getActiveStep(); } + + @Override + public void mouseClicked(MouseEvent e) + { + if (e.getButton() == MouseEvent.BUTTON1) + { + if (isCollapsed()) + { + expand(); + } + else + { + collapse(); + } + } + } + + @Override + public void mousePressed(MouseEvent mouseEvent) + { + } + + @Override + public void mouseReleased(MouseEvent mouseEvent) + { + } + + @Override + public void mouseEntered(MouseEvent mouseEvent) + { + } + + @Override + public void mouseExited(MouseEvent mouseEvent) + { + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/panel/questorders/IronmanOptimalQuestGuide.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/panel/questorders/IronmanOptimalQuestGuide.java index c83a2445167..7906f898f78 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/panel/questorders/IronmanOptimalQuestGuide.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/panel/questorders/IronmanOptimalQuestGuide.java @@ -252,6 +252,10 @@ public class IronmanOptimalQuestGuide QuestHelperQuest.THE_CORSAIR_CURSE, QuestHelperQuest.IN_SEARCH_OF_KNOWLEDGE, QuestHelperQuest.HOPESPEARS_WILL, + // Unsorted + QuestHelperQuest.THE_FINAL_DAWN, + QuestHelperQuest.SHADOWS_OF_CUSTODIA, + QuestHelperQuest.SCRAMBLED, // Quests & mini quests that are not part of the OSRS Wiki's Optimal Ironman Quest Guide QuestHelperQuest.BALLOON_TRANSPORT_CRAFTING_GUILD, QuestHelperQuest.BALLOON_TRANSPORT_GRAND_TREE, @@ -263,6 +267,7 @@ public class IronmanOptimalQuestGuide QuestHelperQuest.FAMILY_PEST, QuestHelperQuest.THE_MAGE_ARENA, QuestHelperQuest.THE_MAGE_ARENA_II, + QuestHelperQuest.VALE_TOTEMS, QuestHelperQuest.DESERT_MEDIUM, QuestHelperQuest.WESTERN_MEDIUM, QuestHelperQuest.ARDOUGNE_HARD, diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/panel/questorders/OptimalQuestGuide.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/panel/questorders/OptimalQuestGuide.java index 9508354e14c..cf346d647ea 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/panel/questorders/OptimalQuestGuide.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/panel/questorders/OptimalQuestGuide.java @@ -259,6 +259,10 @@ public class OptimalQuestGuide QuestHelperQuest.SONG_OF_THE_ELVES, QuestHelperQuest.CLOCK_TOWER, QuestHelperQuest.THE_CORSAIR_CURSE, + // Unsorted + QuestHelperQuest.THE_FINAL_DAWN, + QuestHelperQuest.SHADOWS_OF_CUSTODIA, + QuestHelperQuest.SCRAMBLED, // Quests & mini quests that are not part of the OSRS Wiki's Optimal Quest Guide QuestHelperQuest.BARBARIAN_TRAINING, QuestHelperQuest.BEAR_YOUR_SOUL, @@ -266,6 +270,7 @@ public class OptimalQuestGuide QuestHelperQuest.FAMILY_PEST, QuestHelperQuest.THE_MAGE_ARENA, QuestHelperQuest.THE_MAGE_ARENA_II, + QuestHelperQuest.VALE_TOTEMS, QuestHelperQuest.ARDOUGNE_HARD, QuestHelperQuest.DESERT_HARD, QuestHelperQuest.FALADOR_HARD, diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/panel/questorders/ReleaseDate.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/panel/questorders/ReleaseDate.java index db064e72096..e1350cd9362 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/panel/questorders/ReleaseDate.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/panel/questorders/ReleaseDate.java @@ -237,6 +237,10 @@ public class ReleaseDate //QuestHelperQuest.THE_FROZEN_DOOR, - Placeholder for future addition. QuestHelperQuest.HOPESPEARS_WILL, //QuestHelperQuest.INTO_THE_TOMBS, - Placeholder for future addition. - QuestHelperQuest.HIS_FAITHFUL_SERVANTS + QuestHelperQuest.HIS_FAITHFUL_SERVANTS, + QuestHelperQuest.THE_FINAL_DAWN, + QuestHelperQuest.SHADOWS_OF_CUSTODIA, + QuestHelperQuest.SCRAMBLED, + QuestHelperQuest.VALE_TOTEMS ); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/playerquests/bikeshedder/BikeShedder.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/playerquests/bikeshedder/BikeShedder.java index dcf6e074115..31cf0efb50e 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/playerquests/bikeshedder/BikeShedder.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/playerquests/bikeshedder/BikeShedder.java @@ -88,6 +88,8 @@ public class BikeShedder extends BasicQuestHelper private ItemRequirement lightbearer; private ItemRequirement elemental30Unique; private ItemRequirement elemental30; + private ItemRequirement anyCoins; + private ItemStep getCoins; @Override public Map loadSteps() @@ -103,6 +105,7 @@ public Map loadSteps() steps.addStep(new ZoneRequirement(new WorldPoint(3223, 3218, 0)), useLogOnBush); steps.addStep(new ZoneRequirement(new WorldPoint(3222, 3217, 0)), useCoinOnBush); steps.addStep(new ZoneRequirement(new WorldPoint(3223, 3216, 0)), useManyCoinsOnBush); + steps.addStep(new ZoneRequirement(new WorldPoint(3224, 3216, 0)), getCoins); steps.addStep(conditionalRequirementZoneRequirement, conditionalRequirementLookAtCoins); steps.addStep(new ZoneRequirement(new WorldPoint(3224, 3221, 0)), lookAtCooksAssistant); return new ImmutableMap.Builder() @@ -184,6 +187,9 @@ protected void setupRequirements() ItemID.FIRERUNE), 30); elemental30.setTooltip("You have potato"); haveRunes = new DetailedQuestStep(this, "Compare rune checks for ItemRequirement and ItemRequirements with OR.", elemental30, elemental30Unique); + + anyCoins = new ItemRequirement("Coins", ItemCollections.COINS); + getCoins = new ItemStep(this, new WorldPoint(3224, 3215, 0), "Get coins", anyCoins); } @Override @@ -202,6 +208,7 @@ public List getPanels() panels.add(new PanelDetails("Use log on mysterious bush", List.of(useLogOnBush), List.of(anyLog))); panels.add(new PanelDetails("Use coins on mysterious bush", List.of(useCoinOnBush, useManyCoinsOnBush), List.of(oneCoin, manyCoins))); panels.add(new PanelDetails("Conditional requirement", List.of(conditionalRequirementLookAtCoins), List.of(conditionalRequirementCoins, conditionalRequirementGoldBar))); + panels.add(new PanelDetails("Item step", List.of(getCoins), List.of(anyCoins))); panels.add(new PanelDetails("Quest state", List.of(lookAtCooksAssistant), List.of(lookAtCooksAssistantRequirement, lookAtCooksAssistantTextRequirement))); panels.add(new PanelDetails("Ensure staircase upstairs in Sunrise Palace is highlighted", List.of(goDownstairsInSunrisePalace), List.of())); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/questhelpers/QuestHelper.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/questhelpers/QuestHelper.java index 5d079e4562f..99276e9fad3 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/questhelpers/QuestHelper.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/questhelpers/QuestHelper.java @@ -256,6 +256,14 @@ public void initializeRequirements() hasInitialized = true; } + /// Uninitialize requirements, meaning next time the quest is started it'll recreate all zones & requirements. + /// + /// Intended for developer mode + public void uninitializeRequirements() + { + hasInitialized = false; + } + public List getItemRequirements() { return null; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/questinfo/QuestHelperQuest.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/questinfo/QuestHelperQuest.java index 8791c8cd1c9..8b7261f3e55 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/questinfo/QuestHelperQuest.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/questinfo/QuestHelperQuest.java @@ -85,6 +85,7 @@ import net.runelite.client.plugins.microbot.questhelper.helpers.miniquests.themagearenai.TheMageArenaI; import net.runelite.client.plugins.microbot.questhelper.helpers.miniquests.themagearenaii.MA2Locator; import net.runelite.client.plugins.microbot.questhelper.helpers.miniquests.themagearenaii.TheMageArenaII; +import net.runelite.client.plugins.microbot.questhelper.helpers.miniquests.valetotems.ValeTotems; import net.runelite.client.plugins.microbot.questhelper.helpers.mischelpers.allneededitems.AllNeededItems; import net.runelite.client.plugins.microbot.questhelper.helpers.mischelpers.knightswaves.KnightWaves; import net.runelite.client.plugins.microbot.questhelper.helpers.mischelpers.strongholdofsecurity.StrongholdOfSecurity; @@ -201,10 +202,12 @@ import net.runelite.client.plugins.microbot.questhelper.helpers.quests.rumdeal.RumDeal; import net.runelite.client.plugins.microbot.questhelper.helpers.quests.runemysteries.RuneMysteries; import net.runelite.client.plugins.microbot.questhelper.helpers.quests.scorpioncatcher.ScorpionCatcher; +import net.runelite.client.plugins.microbot.questhelper.helpers.quests.scrambled.Scrambled; import net.runelite.client.plugins.microbot.questhelper.helpers.quests.seaslug.SeaSlug; import net.runelite.client.plugins.microbot.questhelper.helpers.quests.secretsofthenorth.SecretsOfTheNorth; import net.runelite.client.plugins.microbot.questhelper.helpers.quests.shadesofmortton.ShadesOfMortton; import net.runelite.client.plugins.microbot.questhelper.helpers.quests.shadowofthestorm.ShadowOfTheStorm; +import net.runelite.client.plugins.microbot.questhelper.helpers.quests.shadowsofcustodia.ShadowsOfCustodia; import net.runelite.client.plugins.microbot.questhelper.helpers.quests.sheepherder.SheepHerder; import net.runelite.client.plugins.microbot.questhelper.helpers.quests.sheepshearer.SheepShearer; import net.runelite.client.plugins.microbot.questhelper.helpers.quests.shieldofarrav.ShieldOfArravBlackArmGang; @@ -227,6 +230,7 @@ import net.runelite.client.plugins.microbot.questhelper.helpers.quests.thedigsite.TheDigSite; import net.runelite.client.plugins.microbot.questhelper.helpers.quests.theeyesofglouphrie.TheEyesOfGlouphrie; import net.runelite.client.plugins.microbot.questhelper.helpers.quests.thefeud.TheFeud; +import net.runelite.client.plugins.microbot.questhelper.helpers.quests.thefinaldawn.TheFinalDawn; import net.runelite.client.plugins.microbot.questhelper.helpers.quests.theforsakentower.TheForsakenTower; import net.runelite.client.plugins.microbot.questhelper.helpers.quests.thefremennikexiles.TheFremennikExiles; import net.runelite.client.plugins.microbot.questhelper.helpers.quests.thefremennikisles.TheFremennikIsles; @@ -467,6 +471,9 @@ public enum QuestHelperQuest MEAT_AND_GREET(new MeatAndGreet(), Quest.MEAT_AND_GREET, QuestVarbits.QUEST_MEAT_AND_GREET, QuestDetails.Type.P2P, QuestDetails.Difficulty.EXPERIENCED), THE_HEART_OF_DARKNESS(new TheHeartOfDarkness(), Quest.THE_HEART_OF_DARKNESS, QuestVarbits.QUEST_THE_HEART_OF_DARKNESS, QuestDetails.Type.P2P, QuestDetails.Difficulty.EXPERIENCED), THE_CURSE_OF_ARRAV(new TheCurseOfArrav(), Quest.THE_CURSE_OF_ARRAV, QuestVarbits.QUEST_THE_CURSE_OF_ARRAV, QuestDetails.Type.P2P, QuestDetails.Difficulty.MASTER), + THE_FINAL_DAWN(new TheFinalDawn(), Quest.THE_FINAL_DAWN, QuestVarbits.QUEST_THE_FINAL_DAWN, QuestDetails.Type.P2P, QuestDetails.Difficulty.MASTER), + SHADOWS_OF_CUSTODIA(new ShadowsOfCustodia(), Quest.SHADOWS_OF_CUSTODIA, QuestVarbits.QUEST_SHADOWS_OF_CUSTODIA, QuestDetails.Type.P2P, QuestDetails.Difficulty.MASTER /* TODO: CONFIRM DIFFICULTY */), + SCRAMBLED(new Scrambled(), Quest.SCRAMBLED, QuestVarbits.QUEST_SCRAMBLED, QuestDetails.Type.P2P, QuestDetails.Difficulty.INTERMEDIATE), //Miniquests ENTER_THE_ABYSS(new EnterTheAbyss(), Quest.ENTER_THE_ABYSS, QuestVarPlayer.QUEST_ENTER_THE_ABYSS, QuestDetails.Type.MINIQUEST, QuestDetails.Difficulty.MINIQUEST), @@ -485,6 +492,7 @@ public enum QuestHelperQuest HOPESPEARS_WILL(new HopespearsWill(), Quest.HOPESPEARS_WILL, QuestVarbits.QUEST_HOPESPEARS_WILL, QuestDetails.Type.MINIQUEST, QuestDetails.Difficulty.MINIQUEST), HIS_FAITHFUL_SERVANTS(new HisFaithfulServants(), Quest.HIS_FAITHFUL_SERVANTS, QuestVarbits.HIS_FAITHFUL_SERVANTS, QuestDetails.Type.MINIQUEST, QuestDetails.Difficulty.MINIQUEST), BARBARIAN_TRAINING(new BarbarianTraining(), Quest.BARBARIAN_TRAINING, QuestVarbits.BARBARIAN_TRAINING, QuestDetails.Type.MINIQUEST, QuestDetails.Difficulty.MINIQUEST), + VALE_TOTEMS(new ValeTotems(), Quest.VALE_TOTEMS, QuestVarbits.QUEST_VALE_TOTEMS, QuestDetails.Type.MINIQUEST, QuestDetails.Difficulty.MINIQUEST), // Fake miniquests KNIGHT_WAVES_TRAINING_GROUNDS(new KnightWaves(), "Knight Waves Training Grounds", QuestVarbits.KNIGHT_WAVES_TRAINING_GROUNDS, 8, diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/questinfo/QuestVarbits.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/questinfo/QuestVarbits.java index 50c6d3af6e6..35d5c638d04 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/questinfo/QuestVarbits.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/questinfo/QuestVarbits.java @@ -121,6 +121,9 @@ public enum QuestVarbits QUEST_MEAT_AND_GREET(VarbitID.MAG), QUEST_THE_HEART_OF_DARKNESS(VarbitID.VMQ3), QUEST_THE_CURSE_OF_ARRAV(VarbitID.COA), + QUEST_THE_FINAL_DAWN(VarbitID.VMQ4), + QUEST_SHADOWS_OF_CUSTODIA(VarbitID.SOC), + QUEST_SCRAMBLED(VarbitID.SCRAMBLED), /** * mini-quest varbits, these don't hold the completion value. */ @@ -136,6 +139,7 @@ public enum QuestVarbits QUEST_IN_SEARCH_OF_KNOWLEDGE(VarbitID.HOSDUN_KNOWLEDGE_SEARCH), QUEST_DADDYS_HOME(VarbitID.DADDYSHOME_STATUS), QUEST_HOPESPEARS_WILL(VarbitID.HOPESPEAR), + QUEST_VALE_TOTEMS(VarbitID.ENT_TOTEMS_INTRO), HIS_FAITHFUL_SERVANTS(VarbitID.HFS), BARBARIAN_TRAINING(VarbitID.BRUT_MINIQUEST), diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/requirements/item/ItemRequirement.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/requirements/item/ItemRequirement.java index 4e62061f3eb..854e6517e10 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/requirements/item/ItemRequirement.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/requirements/item/ItemRequirement.java @@ -548,9 +548,11 @@ public boolean isActualItem() /** * Appends a tooltip indicating that the item can be obtained during the quest. */ - public void canBeObtainedDuringQuest() + public ItemRequirement canBeObtainedDuringQuest() { appendToTooltip("Can be obtained during the quest."); + + return this; } /** diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/requirements/item/ItemRequirements.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/requirements/item/ItemRequirements.java index 4292b14f406..b9915cb80bb 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/requirements/item/ItemRequirements.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/requirements/item/ItemRequirements.java @@ -335,6 +335,7 @@ public boolean checkItems(Client client, List items) public ItemRequirement copy() { ItemRequirements newItem = new ItemRequirements(getLogicType(), getName(), getItemRequirements()); + newItem.setEquip(equip); newItem.addAlternates(alternateItems); newItem.setDisplayItemId(getDisplayItemId()); newItem.setHighlightInInventory(highlightInInventory); @@ -392,7 +393,6 @@ public ItemRequirement equipped() @Override public void setEquip(boolean shouldEquip) { - itemRequirements.forEach(itemRequirement -> itemRequirement.setEquip(true)); equip = shouldEquip; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/requirements/npc/NpcRequirement.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/requirements/npc/NpcRequirement.java index 53c3f566d02..8ccdf7e21b3 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/requirements/npc/NpcRequirement.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/requirements/npc/NpcRequirement.java @@ -132,7 +132,7 @@ public NpcRequirement(int npcID, String npcName) public boolean check(Client client) { List found = client.getTopLevelWorldView().npcs().stream() - .filter(npc -> npc.getId() == npcID) + .filter(npc -> npc.getId() == npcID || npc.getComposition().getId() == npcID) .filter(npc -> npcName == null || (npc.getName() != null && npc.getName().equals(npcName))) .collect(Collectors.toList()); @@ -142,7 +142,7 @@ public boolean check(Client client) { for (NPC npc : found) { - WorldPoint npcLocation = WorldPoint.fromLocalInstance(client, npc.getLocalLocation(), 2); + WorldPoint npcLocation = WorldPoint.fromLocalInstance(client, npc.getLocalLocation(), npc.getWorldLocation().getPlane()); if (npcLocation != null) { boolean inZone = zone.contains(npcLocation); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/DetailedQuestStep.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/DetailedQuestStep.java index 0a953a5f756..070b92c5909 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/DetailedQuestStep.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/DetailedQuestStep.java @@ -52,6 +52,7 @@ import net.runelite.api.events.GameTick; import net.runelite.api.events.ItemDespawned; import net.runelite.api.events.ItemSpawned; +import net.runelite.api.widgets.Widget; import net.runelite.client.eventbus.EventBus; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.events.PluginMessage; @@ -124,6 +125,9 @@ public class DetailedQuestStep extends QuestStep public boolean considerBankForItemHighlight; public int iconToUseForNeededItems = -1; + @Setter + private boolean onlyHighlightItemsOnTile; + public DetailedQuestStep(QuestHelper questHelper, String text, Requirement... requirements) { @@ -169,8 +173,11 @@ public void startUp() super.startUp(); if (worldPoint != null) { - mapPoint = new QuestHelperWorldMapPoint(worldPoint, getQuestImage()); - worldMapPointManager.add(mapPoint); + if (questHelper.getConfig().showWorldMapPoint()) + { + mapPoint = new QuestHelperWorldMapPoint(worldPoint, getQuestImage()); + worldMapPointManager.add(mapPoint); + } setShortestPath(); } @@ -251,8 +258,11 @@ public void setWorldPoint(WorldPoint worldPoint) } if (worldPoint != null) { - mapPoint = new QuestHelperWorldMapPoint(worldPoint, getQuestImage()); - worldMapPointManager.add(mapPoint); + if (questHelper.getConfig().showWorldMapPoint()) + { + mapPoint = new QuestHelperWorldMapPoint(worldPoint, getQuestImage()); + worldMapPointManager.add(mapPoint); + } } else { @@ -561,6 +571,7 @@ public void onItemSpawned(ItemSpawned itemSpawned) { TileItem item = itemSpawned.getItem(); Tile tile = itemSpawned.getTile(); + if (onlyHighlightItemsOnTile && !QuestPerspective.getInstanceLocalPointFromReal(client, worldPoint).contains(tile.getLocalLocation())) return; for (Requirement requirement : requirements) { if (isItemRequirement(requirement) && requirementContainsID((ItemRequirement) requirement, item.getId())) @@ -610,6 +621,7 @@ protected void addItemTiles(Collection requirements) { continue; } + if (onlyHighlightItemsOnTile && !QuestPerspective.getInstanceLocalPointFromReal(client, worldPoint).contains(tile.getLocalLocation())) continue; for (Requirement requirement : requirements) { if (isValidRequirementForTileItem(requirement, item)) @@ -823,6 +835,12 @@ protected boolean isActionForRequiredItem(MenuEntry entry) option.equals("Take")); } + @Override + protected boolean isValidRenderRequirementInInventory(ItemRequirement requirement, Widget item) + { + return (teleport.contains(requirement) || requirement.shouldHighlightInInventory(client)) && requirement.getAllIds().contains(item.getItemId()); + } + @Override public void setShortestPath() { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/DigStep.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/DigStep.java index 4e1f68dc7f3..aae07b61a5d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/DigStep.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/DigStep.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2019, Trevor + * Copyright (c) 2025, pajlada * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -28,8 +29,9 @@ import net.runelite.client.plugins.microbot.questhelper.questhelpers.QuestHelper; import net.runelite.client.plugins.microbot.questhelper.requirements.Requirement; import net.runelite.client.plugins.microbot.questhelper.requirements.item.ItemRequirement; -import net.runelite.client.plugins.microbot.questhelper.requirements.util.InventorySlots; -import net.runelite.api.Item; +import java.awt.*; +import java.awt.image.BufferedImage; +import lombok.Setter; import net.runelite.api.Player; import net.runelite.api.coords.LocalPoint; import net.runelite.api.coords.WorldPoint; @@ -38,46 +40,55 @@ import net.runelite.client.eventbus.Subscribe; import net.runelite.client.ui.overlay.OverlayUtil; -import java.awt.*; -import java.awt.image.BufferedImage; -import java.util.function.Predicate; - public class DigStep extends DetailedQuestStep { - private final ItemRequirement SPADE = new ItemRequirement("Spade", ItemID.SPADE); - private Predicate expectedItemPredicate = i -> i.getId() == -1; - private boolean hasExpectedItem = false; - public DigStep(QuestHelper questHelper, WorldPoint worldPoint, String text, Requirement... requirements) + private final ItemRequirement spade; + + @Setter + private WhenToHighlight whenToHighlight = WhenToHighlight.InScene; + + /// Private ctor requiring a spade requirement, to be used by public ctors & builders + private DigStep(QuestHelper questHelper, WorldPoint worldPoint, String text, ItemRequirement spade, Requirement... requirements) { super(questHelper, worldPoint, text, requirements); - this.getRequirements().add(SPADE); + this.spade = spade; + this.getRequirements().add(this.spade); } - public void setExpectedItem(int itemID) + public DigStep(QuestHelper questHelper, WorldPoint worldPoint, String text, Requirement... requirements) { - setExpectedItem(i -> i.getId() == itemID); + this(questHelper, worldPoint, text, new ItemRequirement("Spade", ItemID.SPADE), requirements); } - public void setExpectedItem(Predicate predicate) + /// Creates a DigStep with a custom spade requirement, allowing you to pass through custom tooltips / tips to the player + public static DigStep withCustomSpadeRequirement(QuestHelper questHelper, WorldPoint worldPoint, String text, ItemRequirement spade, Requirement... requirements) { - this.expectedItemPredicate = predicate == null ? i -> true : predicate; + return new DigStep(questHelper, worldPoint, text, spade, requirements); } @Subscribe public void onGameTick(GameTick event) { super.onGameTick(event); - hasExpectedItem = InventorySlots.INVENTORY_SLOTS.contains(client, expectedItemPredicate); - if (!hasExpectedItem) + + Player player = client.getLocalPlayer(); + if (player == null) + { + return; + } + WorldPoint targetLocation = worldPoint; + boolean shouldHighlightSpade = false; + switch (this.whenToHighlight) { - Player player = client.getLocalPlayer(); - if (player == null) { - return; - } - WorldPoint targetLocation = worldPoint; - boolean shouldHighlightSpade = targetLocation.isInScene(client); - SPADE.setHighlightInInventory(shouldHighlightSpade); + case InScene: + shouldHighlightSpade = targetLocation.isInScene(client); + break; + + case OnTile: + shouldHighlightSpade = targetLocation.distanceTo(player.getWorldLocation()) == 0; + break; } + spade.setHighlightInInventory(shouldHighlightSpade); } @Override @@ -104,4 +115,12 @@ private BufferedImage getSpadeImage() { return itemManager.getImage(ItemID.SPADE); } + + public enum WhenToHighlight + { + /// Highlight the spade whenever the target tile is in the same scene as the player + InScene, + /// Highlight the spade whenever the player is standing on the target tile + OnTile, + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/ItemStep.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/ItemStep.java index be281229025..967e2bc406d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/ItemStep.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/ItemStep.java @@ -26,6 +26,10 @@ public ItemStep(QuestHelper questHelper, String text, Requirement... requirement @Override public void renderArrow(Graphics2D graphics) { + if (!questHelper.getConfig().showMiniMapArrow()) { + return; + } + tileHighlights.forEach((tile, ids) -> { LocalPoint lp = tile.getLocalLocation(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/NpcStep.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/NpcStep.java index 80d0e2c5658..c7380fff76d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/NpcStep.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/NpcStep.java @@ -40,6 +40,7 @@ import net.runelite.api.events.NpcChanged; import net.runelite.api.events.NpcDespawned; import net.runelite.api.events.NpcSpawned; +import net.runelite.api.gameval.NpcID; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.ui.overlay.OverlayUtil; import net.runelite.client.util.ColorUtil; @@ -178,7 +179,7 @@ public NpcStep copy() protected boolean npcPassesChecks(NPC npc) { if (npcName != null && (npc.getName() == null || !npc.getName().equals(npcName))) return false; - return npcID == npc.getId() || alternateNpcIDs.contains(npc.getId()); + return npcID == npc.getId() || npcID == npc.getComposition().getId() || alternateNpcIDs.contains(npc.getId()) || alternateNpcIDs.contains(npc.getComposition().getId()); } @Override diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/PuzzleWrapperStep.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/PuzzleWrapperStep.java index 7edeead5462..f73b5a4a231 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/PuzzleWrapperStep.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/PuzzleWrapperStep.java @@ -35,6 +35,7 @@ import lombok.NonNull; import net.runelite.client.ui.overlay.components.PanelComponent; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -44,11 +45,12 @@ public class PuzzleWrapperStep extends ConditionalStep { + public static String DEFAULT_TEXT = "If you want help with this, enable 'Show Puzzle Solutions' in the Quest Helper configuration settings."; final QuestHelperConfig questHelperConfig; - final DetailedQuestStep noSolvingStep; + final QuestStep noSolvingStep; ManualRequirement shouldHideHiddenPuzzleHintInSidebar = new ManualRequirement(); - public PuzzleWrapperStep(QuestHelper questHelper, QuestStep step, DetailedQuestStep hiddenStep, Requirement... requirements) + public PuzzleWrapperStep(QuestHelper questHelper, QuestStep step, QuestStep hiddenStep, Requirement... requirements) { super(questHelper, step, "", requirements); this.text = hiddenStep.getText(); @@ -61,7 +63,7 @@ public PuzzleWrapperStep(QuestHelper questHelper, QuestStep step, DetailedQuestS public PuzzleWrapperStep(QuestHelper questHelper, QuestStep step, Requirement... requirements) { - this(questHelper, step, new DetailedQuestStep(questHelper, "If you want help with this, enable 'Show Puzzle Solutions' in the Quest Helper configuration settings."), requirements); + this(questHelper, step, new DetailedQuestStep(questHelper, DEFAULT_TEXT), requirements); } public PuzzleWrapperStep(QuestHelper questHelper, QuestStep step, String text, Requirement... requirements) @@ -110,7 +112,7 @@ public void makeOverlayHint(PanelComponent panelComponent, QuestHelperPlugin plu } else { - super.makeOverlayHint(panelComponent, plugin, additionalText, additionalRequirements); + noSolvingStep.makeOverlayHint(panelComponent, plugin, additionalText, additionalRequirements); } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/QuestStep.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/QuestStep.java index bebe4c25e82..559bf4c854d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/QuestStep.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/QuestStep.java @@ -664,9 +664,9 @@ private boolean isValidRequirementForRenderInInventory(Requirement requirement, return requirement instanceof ItemRequirement && isValidRenderRequirementInInventory((ItemRequirement) requirement, item); } - private boolean isValidRenderRequirementInInventory(ItemRequirement requirement, Widget item) + protected boolean isValidRenderRequirementInInventory(ItemRequirement requirement, Widget item) { - return requirement.shouldHighlightInInventory(client) && requirement.getAllIds().contains(item.getItemId()); + return (requirement.shouldHighlightInInventory(client)) && requirement.getAllIds().contains(item.getItemId()); } protected void renderHoveredItemTooltip(String tooltipText) @@ -694,8 +694,29 @@ public PuzzleWrapperStep puzzleWrapStep() return new PuzzleWrapperStep(getQuestHelper(), this); } + public PuzzleWrapperStep puzzleWrapStep(QuestStep questStep) + { + return new PuzzleWrapperStep(getQuestHelper(), this, questStep); + } + + public PuzzleWrapperStep puzzleWrapStep(QuestStep questStep, boolean hiddenInSidebar) + { + return new PuzzleWrapperStep(getQuestHelper(), this, questStep).withNoHelpHiddenInSidebar(hiddenInSidebar); + } + public PuzzleWrapperStep puzzleWrapStep(String alternateText) { return new PuzzleWrapperStep(getQuestHelper(), this, alternateText); } + + /// Wraps this step in a PuzzleWrapperStep with the given alternate text and the default text on a new line. + public PuzzleWrapperStep puzzleWrapStepWithDefaultText(String alternateText) + { + return new PuzzleWrapperStep(getQuestHelper(), this, alternateText + "\n" + PuzzleWrapperStep.DEFAULT_TEXT); + } + + public PuzzleWrapperStep puzzleWrapStep(boolean hiddenInSidebar) + { + return new PuzzleWrapperStep(getQuestHelper(), this).withNoHelpHiddenInSidebar(hiddenInSidebar); + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/widget/WidgetHighlight.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/widget/WidgetHighlight.java index 6416fc6f562..42c15e62e34 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/widget/WidgetHighlight.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/widget/WidgetHighlight.java @@ -31,6 +31,7 @@ import net.runelite.api.gameval.InterfaceID; import net.runelite.api.widgets.Widget; +import javax.annotation.Nullable; import java.awt.*; public class WidgetHighlight extends AbstractWidgetHighlight @@ -52,6 +53,9 @@ public class WidgetHighlight extends AbstractWidgetHighlight @Getter protected String requiredText; + @Nullable + private String nameToCheckFor = null; + protected final boolean checkChildren; @@ -62,6 +66,14 @@ public WidgetHighlight(int interfaceID) this.checkChildren = false; } + public WidgetHighlight(int interfaceID, boolean checkChildren) + { + this.interfaceID = interfaceID; + this.childChildId = -1; + this.checkChildren = checkChildren; + } + + public WidgetHighlight(int groupId, int childId) { this.interfaceID = groupId << 16 | childId; @@ -99,6 +111,33 @@ public WidgetHighlight(int groupId, int childId, String requiredText, boolean ch this.checkChildren = checkChildren; } + public static WidgetHighlight createMultiskillByName(String roughName) + { + var w = new WidgetHighlight(InterfaceID.Skillmulti.BOTTOM, true); + w.nameToCheckFor = roughName; + return w; + } + + + public static WidgetHighlight createMultiskillByItemId(int itemId) + { + var w = new WidgetHighlight(InterfaceID.Skillmulti.BOTTOM, true); + w.itemIdRequirement = itemId; + return w; + } + + /** + * Create a widget highlight that highlights an item inside the shop interface (e.g. general store) + * @param itemIdRequirement The ID of the item to highlight + * @return a fully built WidgetHighlight + */ + public static WidgetHighlight createShopItemHighlight(int itemIdRequirement) + { + var w = new WidgetHighlight(InterfaceID.Shopmain.ITEMS, true); + w.itemIdRequirement = itemIdRequirement; + return w; + } + @Override public void highlightChoices(Graphics2D graphics, Client client, QuestHelperPlugin questHelper) { @@ -140,13 +179,20 @@ private void highlightChoices(Widget parentWidget, Graphics2D graphics, QuestHel @Override protected void highlightWidget(Graphics2D graphics, QuestHelperPlugin questHelper, Widget widgetToHighlight) { - if (widgetToHighlight == null || !itemCheckPasses(widgetToHighlight) || !modelCheckPasses(widgetToHighlight) || - (requiredText != null && (widgetToHighlight.getText() == null || !widgetToHighlight.getText().contains(requiredText))) - ) return; + if (widgetToHighlight == null) return; + if (!itemCheckPasses(widgetToHighlight)) return; + if (!modelCheckPasses(widgetToHighlight)) return; + if (!requiredTextCheckPasses(widgetToHighlight)) return; + if (!roughNameCheckPasses(widgetToHighlight)) return; super.highlightWidget(graphics, questHelper, widgetToHighlight); } + public WidgetHighlight withModelRequirement(int modelIdRequirement) + { + this.modelIdRequirement = modelIdRequirement; + return this; + } private boolean itemCheckPasses(Widget widgetToHighlight) { @@ -157,4 +203,19 @@ private boolean modelCheckPasses(Widget widget) { return (modelIdRequirement == null || widget.getModelId() == modelIdRequirement); } + + private boolean requiredTextCheckPasses(Widget widget) + { + if (requiredText == null) return true; + if (widget.getText() == null) return false; + return widget.getText().contains(requiredText); + } + + private boolean roughNameCheckPasses(Widget widget) + { + if (nameToCheckFor == null) return true; + var widgetName = widget.getName(); + if (widgetName == null || widgetName.isEmpty()) return false; + return widgetName.contains(nameToCheckFor); + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/util/QHObjectID.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/util/QHObjectID.java index 118496d043d..39900c0be2f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/util/QHObjectID.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/util/QHObjectID.java @@ -45,4 +45,8 @@ public class QHObjectID * Ladder used at the top floor of the Grand Tree in the Tree Gnome Stronghold */ public static final int GRAND_TREE_F3_LADDER = ObjectID.GRANDTREE_LADDERTOP; + /** + * Southern staircase at the bottom floor of Lumbridge Castle + */ + public static final int LUMBRIDGE_CASTLE_F0_SOUTH_STAIRCASE = ObjectID.SPIRALSTAIRSBOTTOM_3; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/runecrafting/arceuus/Altar.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/runecrafting/arceuus/Altar.java deleted file mode 100644 index 8a86602ac9b..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/runecrafting/arceuus/Altar.java +++ /dev/null @@ -1,7 +0,0 @@ -package net.runelite.client.plugins.microbot.runecrafting.arceuus; - -public enum Altar { - AUTO, - BLOOD, - SOUL; -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/runecrafting/arceuus/ArceuusRcConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/runecrafting/arceuus/ArceuusRcConfig.java deleted file mode 100644 index 1020f0fee75..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/runecrafting/arceuus/ArceuusRcConfig.java +++ /dev/null @@ -1,44 +0,0 @@ -package net.runelite.client.plugins.microbot.runecrafting.arceuus; - -import net.runelite.client.config.Config; -import net.runelite.client.config.ConfigGroup; -import net.runelite.client.config.ConfigInformation; -import net.runelite.client.config.ConfigItem; - -@ConfigGroup("arceuusRc") -@ConfigInformation("
    " - + "

    S-1D Arceuus Runecrafting

    " - + "

    Start the plugin near the Dense runestone pillars.


    " - + "

    You only need a pickaxe and a chisel in your inventory.


    " - + "
    ") -public interface ArceuusRcConfig extends Config { - @ConfigItem( - keyName = "altar", - name = "Altar", - description = "Which altar to craft runes at", - position = 1 - ) - default Altar getAltar() { - return Altar.AUTO; - } - - @ConfigItem( - keyName = "chipEssenceFast", - name = "Chip Essence Fast", - description = "Should the Chisel & Essence be repeatably combined", - position = 2 - ) - default boolean getChipEssenceFast() { - return false; - } - - @ConfigItem( - keyName = "updateMessage", - name = "Show Update Message", - description = "Whether the update message should be shown", - position = 3 - ) - default boolean showUpdateMessage() { - return true; - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/runecrafting/arceuus/ArceuusRcOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/runecrafting/arceuus/ArceuusRcOverlay.java deleted file mode 100644 index 5428de7c337..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/runecrafting/arceuus/ArceuusRcOverlay.java +++ /dev/null @@ -1,51 +0,0 @@ -package net.runelite.client.plugins.microbot.runecrafting.arceuus; - - -import net.runelite.client.plugins.microbot.Microbot; -import net.runelite.client.ui.overlay.OverlayPanel; -import net.runelite.client.ui.overlay.OverlayPosition; -import net.runelite.client.ui.overlay.components.LineComponent; -import net.runelite.client.ui.overlay.components.TitleComponent; - -import javax.inject.Inject; -import java.awt.*; - -public class ArceuusRcOverlay extends OverlayPanel { - - private final ArceuusRcPlugin plugin; - - @Inject - ArceuusRcOverlay(ArceuusRcPlugin plugin) { - super(plugin); - this.plugin = plugin; - setPosition(OverlayPosition.TOP_LEFT); - setNaughty(); - } - - @Override - public Dimension render(Graphics2D graphics) { - try { - panelComponent.setPreferredSize(new Dimension(200, 300)); - - panelComponent.getChildren().add(TitleComponent.builder() - .text("\uD83E\uDD86 Arceuus Runecrafting \uD83E\uDD86") - .color(Color.ORANGE) - .build()); - - panelComponent.getChildren().add(LineComponent.builder().build()); - - panelComponent.getChildren().add(LineComponent.builder() - .left("Status: " + Microbot.status).right("Version: " + ArceuusRcScript.version) - .build()); - - if (plugin.getArceuusRcScript() != null) { - panelComponent.getChildren().add(LineComponent.builder() - .left("State: " + plugin.getArceuusRcScript().getState()).build() - ); - } - } catch (Exception ex) { - Microbot.logStackTrace(this.getClass().getSimpleName(), ex); - } - return super.render(graphics); - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/runecrafting/arceuus/ArceuusRcPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/runecrafting/arceuus/ArceuusRcPlugin.java deleted file mode 100644 index 92a6737095c..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/runecrafting/arceuus/ArceuusRcPlugin.java +++ /dev/null @@ -1,48 +0,0 @@ -package net.runelite.client.plugins.microbot.runecrafting.arceuus; - -import com.google.inject.Provides; -import lombok.Getter; -import net.runelite.client.config.ConfigManager; -import net.runelite.client.plugins.Plugin; -import net.runelite.client.plugins.PluginDescriptor; -import net.runelite.client.ui.overlay.OverlayManager; - -import javax.inject.Inject; - -@PluginDescriptor( - name = PluginDescriptor.See1Duck + "Arceuus RC", - description = "Runecrafting at Arceuus", - tags = {"runecrafting", "blood rune", "soul rune" ,"arceuus", "microbot"}, - enabledByDefault = false -) -public class ArceuusRcPlugin extends Plugin { - @Getter - @Inject - private ArceuusRcConfig config; - - @Provides - ArceuusRcConfig provideConfig(ConfigManager configManager) { - return configManager.getConfig(ArceuusRcConfig.class); - } - - @Inject - private OverlayManager overlayManager; - @Inject - private ArceuusRcOverlay arceuusRcOverlay; - @Getter - @Inject - ArceuusRcScript arceuusRcScript; - - @Override - protected void startUp() { - if (overlayManager != null) { - overlayManager.add(arceuusRcOverlay); - } - arceuusRcScript.run(config); - } - - protected void shutDown() { - arceuusRcScript.shutdown(); - overlayManager.remove(arceuusRcOverlay); - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/runecrafting/arceuus/ArceuusRcScript.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/runecrafting/arceuus/ArceuusRcScript.java deleted file mode 100644 index f6365b0f0d9..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/runecrafting/arceuus/ArceuusRcScript.java +++ /dev/null @@ -1,357 +0,0 @@ -package net.runelite.client.plugins.microbot.runecrafting.arceuus; - -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import net.runelite.api.GameObject; -import net.runelite.api.Skill; -import net.runelite.api.coords.WorldArea; -import net.runelite.api.coords.WorldPoint; -import net.runelite.api.gameval.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.antiban.Rs2Antiban; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; -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.player.Rs2Player; -import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; -import org.apache.commons.lang3.NotImplementedException; - -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -@Slf4j -public class ArceuusRcScript extends Script { - public static final String version = "1.0.2"; - - private static ArceuusRcConfig config; - - private static final int BLOOD_ESSENCE_ACTIVE = ItemID.BLOOD_ESSENCE_ACTIVE; - private static final int BLOOD_ESSENCE = ItemID.BLOOD_ESSENCE_INACTIVE; - - private static final int DARK_ESSENCE_FRAGMENTS = ItemID.BIGBLANKRUNE; - private static final int DENSE_ESSENCE_BLOCK = ItemID.ARCEUUS_ESSENCE_BLOCK; - private static final int DARK_ESSENCE_BLOCK = ItemID.ARCEUUS_ESSENCE_BLOCK_DARK; - - private static final String DARK_ALTAR = "Dark altar"; - private static final String STR_DENSE_RUNESTONE = "Dense runestone"; - - private static final WorldArea ARCEUUS_RC_AREA = new WorldArea(1672, 3819, 171, 93, 0); - - private static final WorldPoint ARCEUUS_BLOOD_ALTAR = new WorldPoint(1720, 3828, 0); - private static final WorldPoint ARCEUUS_SOUL_ALTAR = new WorldPoint(1815, 3856, 0); - private static final WorldPoint ARCEUUS_DARK_ALTAR = new WorldPoint(1718, 3880, 0); - private static final WorldPoint DENSE_RUNESTONE = new WorldPoint(1760, 3853, 0); - - private static final int REACHED_DISTANCE = 5; - - @Getter - private String state = "Unknown"; - private boolean hasChippedEssence = false; - - public boolean run(ArceuusRcConfig config) { - ArceuusRcScript.config = config; - Rs2Antiban.antibanSetupTemplates.applyUniversalAntibanSetup(); - hasChippedEssence = false; - if(Microbot.isLoggedIn()) { - hasChippedEssence = Rs2Inventory.hasItem(DARK_ESSENCE_FRAGMENTS); - } - if (config.showUpdateMessage()) log.info("Arceuus RC - Try out the new faster chiseling & soul altar!"); - mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(this::executeTask, 0, 600, TimeUnit.MILLISECONDS); - return true; - } - - private State getCurrentState() { - final WorldPoint myLocation = Rs2Player.getWorldLocation(); - - final int distanceToRuneStone = myLocation.distanceTo(DENSE_RUNESTONE); - if (distanceToRuneStone < 20) { - log.debug("At Runestone"); - if (Rs2Inventory.isFull()) { - if (Rs2Inventory.hasItem(DENSE_ESSENCE_BLOCK)) { - return State.GO_TO_DARK_ALTAR; - } - log.warn("Was at rune stone but mined no essence blocks"); - if (Rs2Inventory.hasItem(DARK_ESSENCE_FRAGMENTS)) { - return State.GO_TO_ALTAR; - } - log.error("At runestone with full inv and no essence"); - return State.UNKNOWN; - } - if (distanceToRuneStone <= REACHED_DISTANCE) return State.MINE_ESSENCE; - // walk closer so the object search can find it - log.warn("Reactivating walker to walk closer to dense runestone"); - return State.GO_TO_RUNESTONE; - } - - final int distanceToAltar = myLocation.distanceTo(getAltarWorldPoint()); - if (distanceToAltar < 20) { - log.debug("At soul or blood altar"); - if (Rs2Inventory.hasItem(DARK_ESSENCE_FRAGMENTS)) { - if (distanceToAltar <= REACHED_DISTANCE) return State.USE_ALTAR; - // walk closer so the object search can find it - log.warn("Reactivating walker to walk closer to altar"); - return State.GO_TO_ALTAR; - } - if (Rs2Inventory.hasItem(DARK_ESSENCE_BLOCK)) return State.CHIP_ESSENCE; - return State.GO_TO_RUNESTONE; - } - - final int distanceToDarkAltar = myLocation.distanceTo(ARCEUUS_DARK_ALTAR); - if (distanceToDarkAltar < 20) { - log.debug("At Dark Altar"); - if (Rs2Inventory.hasItem(DENSE_ESSENCE_BLOCK)) { - if (distanceToDarkAltar <= REACHED_DISTANCE) return State.USE_DARK_ALTAR; - log.warn("Reactivating walker to walk closer to dark altar"); - return State.GO_TO_DARK_ALTAR; - } - - if (hasChippedEssence) { - if (Rs2Inventory.isFull() || Rs2Inventory.hasItem(DARK_ESSENCE_BLOCK)) return State.GO_TO_ALTAR; - } else if (Rs2Inventory.hasItem(DARK_ESSENCE_BLOCK)) return State.CHIP_ESSENCE; - return State.GO_TO_RUNESTONE; - - } - - // user or walker error if we end up here - if (ARCEUUS_RC_AREA.contains(myLocation)) { - log.warn("Detected script error attempting recovery"); - hasChippedEssence = Rs2Inventory.hasItem(DARK_ESSENCE_FRAGMENTS); - if (Rs2Inventory.isFull()) { - if (Rs2Inventory.hasItem(DENSE_ESSENCE_BLOCK)) return State.GO_TO_DARK_ALTAR; - if (!hasChippedEssence && Rs2Inventory.hasItem(DARK_ESSENCE_BLOCK)) return State.CHIP_ESSENCE; - return State.GO_TO_ALTAR; - } - return State.GO_TO_RUNESTONE; - } - - // wait for user to navigate to area - while (!ARCEUUS_RC_AREA.contains(Rs2Player.getWorldLocation())) { - log.error("We are not near anything - Please walk to the starting location"); - sleepUntil(() -> ARCEUUS_RC_AREA.contains(Rs2Player.getWorldLocation()), () -> {}, 60_000, 1_000); - } - int resumeSeconds = 4; - while (resumeSeconds-- > 0) { - log.info("Arceuus RC taking over in {}", resumeSeconds); - sleep(1_000); - } - return State.UNKNOWN; - } - - private void logWalk(WorldPoint dst) { - WorldPoint myLocation = Rs2Player.getWorldLocation(); - if (myLocation == null) { - log.error("MyLocation is null"); - return; - } - BreakHandlerScript.setLockState(true); - log.info("Walking from ({},{},{}) to ({},{},{})", - myLocation.getX(), myLocation.getY(), myLocation.getPlane(), - dst.getX(), dst.getY(), dst.getPlane() - ); - Rs2Walker.walkTo(dst, REACHED_DISTANCE); - BreakHandlerScript.setLockState(false); - } - - private void executeTask() { - try { - if (!super.run() || !Microbot.isLoggedIn()) { - state = "Disabled"; - return; - } - - State state = getCurrentState(); - log.debug("Current State={}", state); - this.state = String.format("(%s) %s", getAltarName(), state); - switch (state) { - case GO_TO_RUNESTONE: - logWalk(DENSE_RUNESTONE); - break; - case GO_TO_DARK_ALTAR: - logWalk(ARCEUUS_DARK_ALTAR); - break; - case GO_TO_ALTAR: - logWalk(getAltarWorldPoint()); - break; - case MINE_ESSENCE: - mineEssence(); - break; - case USE_DARK_ALTAR: - useDarkAltar(); - break; - case CHIP_ESSENCE: - this.state += config.getChipEssenceFast() ? "_FAST" : ""; - chipEssence(config.getChipEssenceFast()); - break; - case USE_ALTAR: - useAltar(); - break; - case UNKNOWN: - break; - default: - log.error("Action not defined for State={}", state); - } - } catch (Exception e) { - // in-case we error before setting it - hasChippedEssence = Rs2Inventory.hasItem(DARK_ESSENCE_FRAGMENTS); - Microbot.log("Error in Arceuus Runecrafter: " + e.getMessage()); - } - } - - public Altar getAltar() { - if (config.getAltar() != Altar.AUTO) return config.getAltar(); - final int level = Microbot.getClient().getRealSkillLevel(Skill.RUNECRAFT); - // Cache not updated - but we don't want to shut down - if (level == 0) throw new IllegalStateException("Runecraft Level cannot be 0"); - if (level >= 90) return Altar.SOUL; - if (level >= 77) return Altar.BLOOD; - - this.shutdown(); - this.state = "Runecraft Level " + level + " to low"; - throw new IllegalStateException("Runecraft Level " + level + " to low"); - } - - public WorldPoint getAltarWorldPoint() { - if (getAltar() == Altar.BLOOD) return ARCEUUS_BLOOD_ALTAR; - return ARCEUUS_SOUL_ALTAR; - } - - public String getAltarName() { - if (getAltar() == Altar.BLOOD) return "Blood Altar"; - return "Soul Altar"; - } - - public void useAltar() { - final GameObject altar = Rs2GameObject.getGameObject(getAltarName(), true, 11); - if (altar != null) { - if (Rs2GameObject.interact(altar,"Bind")) Rs2Inventory.waitForInventoryChanges(6_000); - hasChippedEssence = Rs2Inventory.hasItem(DARK_ESSENCE_FRAGMENTS); - } - } - - public boolean moveChisel() { - if (Rs2Inventory.slotContains(27, ItemID.CHISEL)) return true; - final Rs2ItemModel chisel = Rs2Inventory.get(ItemID.CHISEL); - if (chisel == null) { - Microbot.log("No chisel found in inventory"); - return false; - } - if (Rs2Inventory.moveItemToSlot(chisel,27)) { - if (!sleepUntil(() -> Rs2Inventory.slotContains(27, ItemID.CHISEL),6_000)) { - Microbot.log("Failed to move chisel to slot 27"); - return false; - } - } - return true; - } - - public void chipEssence(boolean fast) { - if (!moveChisel()) return; - if (fast) chipEssenceFast(); - else chipEssenceSlow(); - hasChippedEssence = Rs2Inventory.hasItem(DARK_ESSENCE_FRAGMENTS); - if (!hasChippedEssence) log.error("Failed to chip essence"); - } - - public boolean chipEssenceSlow() { - if(Rs2Inventory.combineClosest(DARK_ESSENCE_BLOCK,ItemID.CHISEL)) { - int blocks = Rs2Inventory.count(DARK_ESSENCE_BLOCK); - while (blocks > 0) { - Rs2Inventory.waitForInventoryChanges(5_000); - final int newBlocks = Rs2Inventory.count(DARK_ESSENCE_BLOCK); - if (newBlocks == blocks) { - log.warn("Failed to chip full inventory"); - return false; - } - blocks = newBlocks; - } - return true; - } - log.warn("Failed to combine closest"); - return false; - } - - private void reverse(int[] ints) { - if (ints.length != 2) throw new NotImplementedException("reverse does not support length != 2"); - final int tmp = ints[0]; - ints[0] = ints[1]; - ints[1] = tmp; - } - - public boolean chipEssenceFast() { - final int[] ids = {ItemID.CHISEL, DARK_ESSENCE_BLOCK}; - long lastUpdate = System.currentTimeMillis(); - int blocks = Rs2Inventory.count(DARK_ESSENCE_BLOCK); - while (Rs2Inventory.hasItem(ItemID.CHISEL) && blocks > 0) { - if (!Rs2Inventory.combineClosest(ids[0], ids[1])) { - Microbot.log("Failed to combine closest chisel & dark essence block"); - return false; - } - reverse(ids); - if (System.currentTimeMillis()-lastUpdate > 3_000) { - log.warn("Probably have max essence stopping combine"); - return true; - } - final int newBlocks = Rs2Inventory.count(DARK_ESSENCE_BLOCK); - if (newBlocks < blocks) { - lastUpdate = System.currentTimeMillis(); - blocks = newBlocks; - } - } - return true; - } - - public void useDarkAltar() { - final GameObject darkAltar = Rs2GameObject.getGameObject(DARK_ALTAR, true, 11); - if (darkAltar == null) return; - - Rs2GameObject.interact(darkAltar,"Venerate"); - sleepUntil(()->!Rs2Inventory.hasItem(DENSE_ESSENCE_BLOCK),6_000); - } - - public void mineEssence() { - if(getAltar() == Altar.BLOOD && !Rs2Inventory.hasItem(BLOOD_ESSENCE_ACTIVE)){ - Rs2Inventory.interact(BLOOD_ESSENCE, "Activate"); - } - final GameObject runeStone = Rs2GameObject.getGameObject(STR_DENSE_RUNESTONE, true, 11); - if (runeStone == null) { // should never happen bc shouldMineEssence checks for the runestone - Microbot.log("Cannot find runestone"); - return; - } - Rs2GameObject.interact(runeStone,"Chip"); - - // this checks if we are gaining essence from mining - final AtomicInteger emptyCount = new AtomicInteger(Rs2Inventory.emptySlotCount()); - Rs2Player.waitForAnimation(10_000); - while (emptyCount.get() > 0) { - if (!Rs2Player.isAnimating(1_800)) return; // runestone probably mined - need to switch - if (sleepUntil(() -> { - if (!Rs2Player.isAnimating(1_800)) return true; - final int newEmptyCount = Rs2Inventory.emptySlotCount(); - if (newEmptyCount >= emptyCount.get()) return false; - emptyCount.set(newEmptyCount); - return true; - }, 10_000)) continue; - log.warn("Failed to await mining essence"); - return; - } - - } - - @Override - public void shutdown() { - super.shutdown(); - } - - private enum State { - GO_TO_RUNESTONE, - GO_TO_DARK_ALTAR, - GO_TO_ALTAR, // blood or soul - MINE_ESSENCE, - USE_DARK_ALTAR, - CHIP_ESSENCE, - USE_ALTAR, - UNKNOWN - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/DebugOverlayPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/DebugOverlayPanel.java index 66794df4ab8..014e4874be0 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/DebugOverlayPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/DebugOverlayPanel.java @@ -40,7 +40,7 @@ private LineComponent makeLine(String left, String right) { public Dimension render(Graphics2D graphics) { Pathfinder pathfinder = ShortestPathPlugin.getPathfinder(); Pathfinder.PathfinderStats stats; - if (pathfinder == null || (stats = pathfinder.getStats()) == null) { + if (pathfinder == null || !pathfinder.isDone() || (stats = pathfinder.getStats()) == null) { return null; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/PathMapTooltipOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/PathMapTooltipOverlay.java index c0fc80d08c7..468c6bd5339 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/PathMapTooltipOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/PathMapTooltipOverlay.java @@ -46,7 +46,7 @@ public Dimension render(Graphics2D graphics) { return null; } - if (ShortestPathPlugin.getPathfinder() != null) { + if (ShortestPathPlugin.getPathfinder() != null && ShortestPathPlugin.getPathfinder().isDone()) { List path = ShortestPathPlugin.getPathfinder().getPath(); Point cursorPos = client.getMouseCanvasPosition(); for (int i = 0; i < path.size(); i++) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/ShortestPathPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/ShortestPathPanel.java index f0c89cfb3f3..0f0878c9fa1 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/ShortestPathPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/ShortestPathPanel.java @@ -48,6 +48,15 @@ import net.runelite.client.plugins.microbot.util.walker.enums.Trees; import net.runelite.client.ui.PluginPanel; import net.runelite.client.util.ImageUtil; +import net.runelite.client.plugins.microbot.questhelper.QuestHelperPlugin; +import net.runelite.client.plugins.microbot.questhelper.questhelpers.QuestHelper; +import net.runelite.client.plugins.microbot.questhelper.steps.QuestStep; +import net.runelite.client.plugins.microbot.questhelper.steps.DetailedQuestStep; +import net.runelite.client.plugins.microbot.questhelper.steps.ConditionalStep; +import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin; +import net.runelite.client.plugins.cluescrolls.clues.ClueScroll; +import net.runelite.client.plugins.cluescrolls.clues.LocationClueScroll; +import net.runelite.client.plugins.cluescrolls.clues.LocationsClueScroll; public class ShortestPathPanel extends PluginPanel { @@ -73,6 +82,8 @@ public class ShortestPathPanel extends PluginPanel private JComboBox kebbitsJComboBox; private JComboBox salamandersComboBox; private JComboBox specialHuntingAreasJComboBox; + private javax.swing.Timer questInfoTimer; + private javax.swing.Timer clueInfoTimer; @Inject private ShortestPathPanel(ShortestPathPlugin plugin) @@ -91,6 +102,10 @@ private ShortestPathPanel(ShortestPathPlugin plugin) add(Box.createRigidArea(new Dimension(0, 10))); add(createSlayerMasterPanel()); add(Box.createRigidArea(new Dimension(0, 10))); + add(createQuestLocationPanel()); + add(Box.createRigidArea(new Dimension(0, 10))); + add(createClueLocationPanel()); + add(Box.createRigidArea(new Dimension(0, 10))); add(createFarmingPanel()); add(Box.createRigidArea(new Dimension(0, 10))); add(createHunterCreaturePanel()); @@ -342,6 +357,74 @@ private JPanel createSlayerMasterPanel() return panel; } + private JPanel createQuestLocationPanel() + { + JPanel panel = new JPanel(); + panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); + panel.setBorder(createCenteredTitledBorder("Travel to Quest Location", "/net/runelite/client/plugins/microbot/questhelper/quest_icon.png")); + + // Quest info label + JLabel questInfoLabel = new JLabel("Loading quest info..."); + questInfoLabel.setHorizontalAlignment(SwingConstants.CENTER); + questInfoLabel.setAlignmentX(Component.CENTER_ALIGNMENT); + questInfoLabel.setMaximumSize(new Dimension(Integer.MAX_VALUE, questInfoLabel.getPreferredSize().height * 2)); + + // Update quest info dynamically + questInfoTimer = new javax.swing.Timer(1000, e -> { + String questInfo = getCurrentQuestInfo(); + questInfoLabel.setText("
    " + questInfo + "
    "); + }); + questInfoTimer.start(); + + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + JButton startButton = new JButton("Start"); + JButton stopButton = new JButton("Stop"); + + startButton.addActionListener(e -> { + WorldPoint questLocation = getCurrentQuestLocation(); + if (questLocation != null) + { + Microbot.log("Walking to quest objective location"); + startWalking(questLocation); + } + else + { + QuestHelperPlugin qhp = getQuestHelperPlugin(); + if (qhp == null) + { + Microbot.log("Cannot walk to quest location: QuestHelper plugin not enabled"); + } + else if (qhp.getSelectedQuest() == null) + { + Microbot.log("Cannot walk to quest location: No quest selected in QuestHelper"); + } + else + { + Microbot.log("Cannot walk to quest location: Current quest step has no location"); + } + } + }); + + stopButton.addActionListener(e -> stopWalking()); + + buttonPanel.add(startButton); + buttonPanel.add(stopButton); + + JPanel helpPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + JLabel helpLabel = new JLabel("
    Requires QuestHelper plugin
    with an active quest
    "); + helpLabel.setHorizontalAlignment(SwingConstants.CENTER); + helpPanel.add(helpLabel); + + panel.add(Box.createRigidArea(new Dimension(0, 5))); + panel.add(questInfoLabel); + panel.add(Box.createRigidArea(new Dimension(0, 10))); + panel.add(buttonPanel); + panel.add(Box.createRigidArea(new Dimension(0, 2))); + panel.add(helpPanel); + + return panel; + } + private JPanel createFarmingPanel() { JPanel panel = new JPanel(); @@ -573,4 +656,279 @@ private void stopWalking() plugin.getShortestPathScript().setTriggerWalker(null); Rs2Walker.setTarget(null); } + + private QuestHelperPlugin getQuestHelperPlugin() + { + return (QuestHelperPlugin) Microbot.getPluginManager().getPlugins().stream() + .filter(x -> x instanceof QuestHelperPlugin) + .findFirst() + .orElse(null); + } + + private WorldPoint getCurrentQuestLocation() + { + QuestHelperPlugin questHelper = getQuestHelperPlugin(); + if (questHelper == null || questHelper.getSelectedQuest() == null) + { + return null; + } + + try + { + QuestStep currentStep = questHelper.getSelectedQuest().getCurrentStep(); + if (currentStep == null) + { + return null; + } + + // Get the active step (handles ConditionalStep) + QuestStep activeStep = currentStep; + if (currentStep instanceof ConditionalStep) + { + activeStep = ((ConditionalStep) currentStep).getActiveStep(); + } + + // Extract WorldPoint from DetailedQuestStep + if (activeStep instanceof DetailedQuestStep) + { + return ((DetailedQuestStep) activeStep).getWorldPoint(); + } + } + catch (Exception e) + { + Microbot.log("Error getting quest location: " + e.getMessage()); + } + + return null; + } + + private String getCurrentQuestInfo() + { + QuestHelperPlugin questHelper = getQuestHelperPlugin(); + if (questHelper == null) + { + return "QuestHelper plugin not enabled"; + } + + if (questHelper.getSelectedQuest() == null) + { + return "No quest selected"; + } + + try + { + QuestHelper quest = questHelper.getSelectedQuest(); + String questName = quest.getQuest() != null ? quest.getQuest().getName() : "Unknown Quest"; + + QuestStep currentStep = quest.getCurrentStep(); + if (currentStep != null) + { + // Try to get step description + String stepText = "Current objective"; + if (currentStep instanceof ConditionalStep) + { + QuestStep activeStep = ((ConditionalStep) currentStep).getActiveStep(); + if (activeStep instanceof DetailedQuestStep) + { + DetailedQuestStep detailedStep = (DetailedQuestStep) activeStep; + if (detailedStep.getText() != null && !detailedStep.getText().isEmpty()) + { + stepText = detailedStep.getText().get(0); + // Truncate if too long for display + if (stepText.length() > 40) + { + stepText = stepText.substring(0, 37) + "..."; + } + } + } + } + else if (currentStep instanceof DetailedQuestStep) + { + DetailedQuestStep detailedStep = (DetailedQuestStep) currentStep; + if (detailedStep.getText() != null && !detailedStep.getText().isEmpty()) + { + stepText = detailedStep.getText().get(0); + // Truncate if too long for display + if (stepText.length() > 40) + { + stepText = stepText.substring(0, 37) + "..."; + } + } + } + + return questName + " - " + stepText; + } + + return questName + " - No active step"; + } + catch (Exception e) + { + return "Error reading quest info"; + } + } + + private JPanel createClueLocationPanel() + { + JPanel panel = new JPanel(); + panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); + panel.setBorder(createCenteredTitledBorder("Travel to Clue Location", "/net/runelite/client/plugins/microbot/shortestpath/Clue_scroll_icon.png")); + + // Clue info label + JLabel clueInfoLabel = new JLabel("Loading clue info..."); + clueInfoLabel.setHorizontalAlignment(SwingConstants.CENTER); + clueInfoLabel.setAlignmentX(Component.CENTER_ALIGNMENT); + clueInfoLabel.setMaximumSize(new Dimension(Integer.MAX_VALUE, clueInfoLabel.getPreferredSize().height * 2)); + + // Update clue info dynamically + clueInfoTimer = new javax.swing.Timer(1000, e -> { + String clueInfo = getCurrentClueInfo(); + clueInfoLabel.setText("
    " + clueInfo + "
    "); + }); + clueInfoTimer.start(); + + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + JButton startButton = new JButton("Start"); + JButton stopButton = new JButton("Stop"); + + startButton.addActionListener(e -> { + WorldPoint clueLocation = getCurrentClueLocation(); + if (clueLocation != null) + { + Microbot.log("Walking to clue scroll location"); + startWalking(clueLocation); + } + else + { + ClueScrollPlugin cluePlugin = getCluePlugin(); + if (cluePlugin == null) + { + Microbot.log("Cannot walk to clue location: ClueScroll plugin not enabled"); + } + else if (cluePlugin.getClue() == null) + { + Microbot.log("Cannot walk to clue location: No active clue scroll"); + } + else + { + Microbot.log("Cannot walk to clue location: Current clue has no location"); + } + } + }); + + stopButton.addActionListener(e -> stopWalking()); + + buttonPanel.add(startButton); + buttonPanel.add(stopButton); + + JPanel helpPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + JLabel helpLabel = new JLabel("
    Requires ClueScroll plugin
    with an active clue
    "); + helpLabel.setHorizontalAlignment(SwingConstants.CENTER); + helpPanel.add(helpLabel); + + panel.add(Box.createRigidArea(new Dimension(0, 5))); + panel.add(clueInfoLabel); + panel.add(Box.createRigidArea(new Dimension(0, 10))); + panel.add(buttonPanel); + panel.add(Box.createRigidArea(new Dimension(0, 2))); + panel.add(helpPanel); + + return panel; + } + + private ClueScrollPlugin getCluePlugin() + { + return (ClueScrollPlugin) Microbot.getPluginManager().getPlugins().stream() + .filter(x -> x instanceof ClueScrollPlugin) + .findFirst() + .orElse(null); + } + + private WorldPoint getCurrentClueLocation() + { + ClueScrollPlugin cluePlugin = getCluePlugin(); + if (cluePlugin == null) + { + return null; + } + + ClueScroll clue = cluePlugin.getClue(); + if (clue == null) + { + return null; + } + + // Check if clue implements LocationClueScroll (single location) + if (clue instanceof LocationClueScroll) + { + WorldPoint location = ((LocationClueScroll) clue).getLocation(cluePlugin); + if (location != null) + { + return location; + } + } + + // Check if clue implements LocationsClueScroll (multiple locations) + if (clue instanceof LocationsClueScroll) + { + WorldPoint[] locations = ((LocationsClueScroll) clue).getLocations(cluePlugin); + if (locations != null && locations.length > 0) + { + // Return the first location for now + // Could be improved to find the nearest one + return locations[0]; + } + } + + return null; + } + + private String getCurrentClueInfo() + { + ClueScrollPlugin cluePlugin = getCluePlugin(); + if (cluePlugin == null) + { + return "ClueScroll plugin not enabled"; + } + + ClueScroll clue = cluePlugin.getClue(); + if (clue == null) + { + return "No active clue scroll"; + } + + // Get clue type from class name + String clueType = clue.getClass().getSimpleName(); + + // Remove "Clue" suffix if present + if (clueType.endsWith("Clue")) + { + clueType = clueType.substring(0, clueType.length() - 4); + } + + // Add spaces between camelCase words + clueType = clueType.replaceAll("([a-z])([A-Z])", "$1 $2"); + + // Check if clue has a location + WorldPoint location = getCurrentClueLocation(); + if (location == null) + { + return clueType + " - No location"; + } + + return clueType + " clue"; + } + + public void disposeTimers() + { + if (questInfoTimer != null) + { + questInfoTimer.stop(); + questInfoTimer = null; + } + if (clueInfoTimer != null) + { + clueInfoTimer.stop(); + clueInfoTimer = null; + } + } } \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/ShortestPathPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/ShortestPathPlugin.java index 801e28e0c17..b5de8522216 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/ShortestPathPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/ShortestPathPlugin.java @@ -240,6 +240,9 @@ protected void shutDown() { overlayManager.remove(debugOverlayPanel); clientToolbar.removeNavigation(navButton); navButton = null; + if (panel != null) { + panel.disposeTimers(); + } panel = null; shortestPathScript.shutdown(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandConfig.java deleted file mode 100644 index 44157a3e1d0..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandConfig.java +++ /dev/null @@ -1,87 +0,0 @@ -package net.runelite.client.plugins.microbot.tutorialisland; - -import net.runelite.client.config.Config; -import net.runelite.client.config.ConfigGroup; -import net.runelite.client.config.ConfigItem; -import net.runelite.client.config.ConfigSection; - -@ConfigGroup(TutorialIslandConfig.configGroup) -public interface TutorialIslandConfig extends Config { - - String configGroup = "MicroTutIsland"; - String toggleMusic = "toggleMusic"; - String toggleRoofs = "toggleRoofs"; - String toggleLevelUp = "toggleLevelUp"; - String toggleShiftDrop = "toggleShiftDrop"; - String toggleDevOverlay = "toggleDevOverlay"; - - @ConfigSection( - name = "QOL Settings", - description = "Configure in-game settings", - position = 0 - ) - String qolSection = "qol"; - - @ConfigItem( - keyName = toggleMusic, - name = "Toggle Music", - description = "Turns off in-game music", - position = 0, - section = qolSection - ) - default boolean toggleMusic() { - return true; - } - - @ConfigItem( - keyName = toggleRoofs, - name = "Toggle Roofs", - description = "Turns on 'hide roofs' in-game", - position = 1, - section = qolSection - ) - default boolean toggleRoofs() { - return true; - } - - @ConfigItem( - keyName = toggleLevelUp, - name = "Toggle Disable Level-up Notifications", - description = "Turns on 'disable level-up notifications'", - position = 2, - section = qolSection - ) - default boolean toggleDisableLevelUp() { - return true; - } - - @ConfigItem( - keyName = toggleShiftDrop, - name = "Toggle Shift Dropping", - description = "Turns on 'shift dropping'", - position = 3, - section = qolSection - ) - default boolean toggleShiftDrop() { - return true; - } - - @ConfigSection( - name = "Overlay Settings", - description = "Configure overlay settings", - position = 1, - closedByDefault = true - ) - String overlaySection = "overlay"; - - @ConfigItem( - keyName = toggleDevOverlay, - name = "Toggle developer overlay", - description = "Turns on developer info in overlay", - position = 0, - section = overlaySection - ) - default boolean toggleDevOverlay() { - return false; - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandOverlay.java deleted file mode 100644 index 63ff31f535f..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandOverlay.java +++ /dev/null @@ -1,56 +0,0 @@ -package net.runelite.client.plugins.microbot.tutorialisland; - -import net.runelite.client.plugins.microbot.Microbot; -import net.runelite.client.ui.overlay.OverlayPanel; -import net.runelite.client.ui.overlay.OverlayPosition; -import net.runelite.client.ui.overlay.components.LineComponent; -import net.runelite.client.ui.overlay.components.TitleComponent; - -import javax.inject.Inject; -import java.awt.*; - -public class TutorialIslandOverlay extends OverlayPanel { - - TutorialislandPlugin plugin; - @Inject - TutorialIslandOverlay(TutorialislandPlugin plugin) { - super(plugin); - this.plugin = plugin; - setPosition(OverlayPosition.TOP_LEFT); - setNaughty(); - } - - @Override - public Dimension render(Graphics2D graphics) { - try { - panelComponent.setPreferredSize(new Dimension(200, 300)); - panelComponent.getChildren().add(TitleComponent.builder() - .text("Micro TutorialIsland V" + TutorialIslandScript.version) - .color(Color.GREEN) - .build()); - - panelComponent.getChildren().add(LineComponent.builder().build()); - - panelComponent.getChildren().add(LineComponent.builder() - .left(Microbot.status) - .build()); - - if (plugin.isToggleDevOverlay()) { - if (TutorialIslandScript.status != null) { - panelComponent.getChildren().add(LineComponent.builder() - .left("State:") - .right(TutorialIslandScript.status.toString()) - .build()); - } - - panelComponent.getChildren().add(LineComponent.builder() - .left("Progress (281):") - .right(Integer.toString(Microbot.getVarbitPlayerValue(281))) - .build()); - } - } catch(Exception ex) { - System.out.println(ex.getMessage()); - } - return super.render(graphics); - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandScript.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandScript.java deleted file mode 100644 index b2008859a90..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandScript.java +++ /dev/null @@ -1,787 +0,0 @@ -package net.runelite.client.plugins.microbot.tutorialisland; - -import net.runelite.api.*; -import net.runelite.api.coords.WorldPoint; -import net.runelite.api.widgets.Widget; -import net.runelite.client.plugins.microbot.Microbot; -import net.runelite.client.plugins.microbot.Script; -import net.runelite.client.plugins.microbot.util.antiban.Rs2Antiban; -import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; -import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; -import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; -import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; -import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; -import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; -import net.runelite.client.plugins.microbot.util.magic.Rs2Magic; -import net.runelite.client.plugins.microbot.util.math.Rs2Random; -import net.runelite.client.plugins.microbot.util.misc.Rs2UiHelper; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; -import net.runelite.client.plugins.microbot.util.player.NameGenerator; -import net.runelite.client.plugins.microbot.util.player.Rs2Player; -import net.runelite.client.plugins.microbot.util.tabs.Rs2Tab; -import net.runelite.client.plugins.microbot.util.tile.Rs2Tile; -import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; -import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; -import net.runelite.client.plugins.skillcalculator.skills.MagicAction; - -import javax.inject.Inject; -import java.awt.event.KeyEvent; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.TimeUnit; - -import static net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue.*; -import static net.runelite.client.plugins.microbot.util.settings.Rs2Settings.*; - -public class TutorialIslandScript extends Script { - - public static double version = 1.3; - public static Status status = Status.NAME; - final int CharacterCreation = 679; - final int[] CharacterCreation_Arrows = new int[]{13, 17, 21, 25, 29, 33, 37, 44, 48, 52, 56, 60}; - private final TutorialislandPlugin plugin; - private final int NameCreation = 558; - private boolean toggledSettings = false; - private boolean toggledMusic = false; - private boolean hasSelectedGender = false; - - @Inject - public TutorialIslandScript(TutorialislandPlugin plugin) { - this.plugin = plugin; - } - - public boolean run(TutorialIslandConfig config) { - Microbot.enableAutoRunOn = false; - Rs2Antiban.resetAntibanSettings(); - Rs2AntibanSettings.naturalMouse = true; - Rs2AntibanSettings.moveMouseRandomly = true; - Rs2AntibanSettings.simulateMistakes = true; - mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { - try { - if (!Microbot.isLoggedIn()) return; - if (!super.run()) return; - - CalculateStatus(); - - if (hasContinue()) { - clickContinue(); - return; - } - - if (Rs2Player.isMoving() || Rs2Player.isAnimating()) return; - - switch (status) { - case NAME: - Widget nameSearchBar = Rs2Widget.getWidget(NameCreation, 12); // enterName Field text - - String nameSearchBarText = nameSearchBar.getText(); - - if (nameSearchBarText.endsWith("*")) { - nameSearchBarText = nameSearchBarText.substring(0, nameSearchBarText.length() - 1); - } - - if (!nameSearchBarText.isEmpty()) { - Rs2Widget.clickWidget(NameCreation, 7); // enterName Field - Rs2Random.waitEx(1200, 300); - - for (int i = 0; i < nameSearchBarText.length(); i++) { - Rs2Keyboard.keyPress(KeyEvent.VK_BACK_SPACE); - Rs2Random.waitEx(600, 100); - } - - return; - } - - String name = new NameGenerator(Rs2Random.between(7, 10)).getName(); - Rs2Widget.clickWidget(NameCreation, 7); // enterName Field - Rs2Random.waitEx(1200, 300); - Rs2Keyboard.typeString(name); - Rs2Random.waitEx(2400, 600); - Rs2Widget.clickWidget(NameCreation, 18); // lookupName Button - Rs2Random.waitEx(4800, 600); - - Widget responseWidget = Rs2Widget.getWidget(NameCreation, 13); // responseText Widget - - if (responseWidget != null) { - String widgetText = responseWidget.getText(); - String cleanedWidgetText = Rs2UiHelper.stripColTags(widgetText); - String expectedText = "Great! The display name " + name + " is available"; - boolean nameAvailable = cleanedWidgetText.startsWith(expectedText); - - if (nameAvailable) { - Rs2Widget.clickWidget(NameCreation, 19); // setName Button - Rs2Random.waitEx(4800, 600); - - sleepUntil(() -> !isNameCreationVisible()); - } - } - break; - case CHARACTER: - RandomizeCharacter(); - break; - case GETTING_STARTED: - GettingStarted(); - break; - case SURVIVAL_GUIDE: - SurvivalGuide(); - break; - case COOKING_GUIDE: - CookingGuide(); - break; - case QUEST_GUIDE: - QuestGuide(); - break; - case MINING_GUIDE: - MiningGuide(); - break; - case COMBAT_GUIDE: - CombatGuide(); - break; - case BANKER_GUIDE: - BankerGuide(); - break; - case PRAYER_GUIDE: - PrayerGuide(); - break; - case MAGE_GUIDE: - MageGuide(); - break; - case FINISHED: - shutdown(); - break; - } - } catch (Exception ex) { - System.out.println(ex.getMessage()); - } - }, 0, 600, TimeUnit.MILLISECONDS); - return true; - } - - @Override - public void shutdown() { - super.shutdown(); - Rs2Antiban.resetAntibanSettings(); - } - - private boolean isNameCreationVisible() { - return Rs2Widget.isWidgetVisible(NameCreation, 2); - } - - private boolean isCharacterCreationVisible() { - return Rs2Widget.isWidgetVisible(CharacterCreation, 4); - } - - public void CalculateStatus() { - if (isNameCreationVisible()) { - status = Status.NAME; - } else if (isCharacterCreationVisible()) { - status = Status.CHARACTER; - } else if (Microbot.getVarbitPlayerValue(281) < 10) { - status = Status.GETTING_STARTED; - } else if (Microbot.getVarbitPlayerValue(281) >= 10 && Microbot.getVarbitPlayerValue(281) < 120) { - status = Status.SURVIVAL_GUIDE; - } else if (Microbot.getVarbitPlayerValue(281) >= 120 && Microbot.getVarbitPlayerValue(281) < 200) { - status = Status.COOKING_GUIDE; - } else if (Microbot.getVarbitPlayerValue(281) >= 200 && Microbot.getVarbitPlayerValue(281) <= 250) { - status = Status.QUEST_GUIDE; - } else if (Microbot.getVarbitPlayerValue(281) >= 260 && Microbot.getVarbitPlayerValue(281) <= 360) { - status = Status.MINING_GUIDE; - } else if (Microbot.getVarbitPlayerValue(281) > 360 && Microbot.getVarbitPlayerValue(281) < 510) { - status = Status.COMBAT_GUIDE; - } else if (Microbot.getVarbitPlayerValue(281) >= 510 && Microbot.getVarbitPlayerValue(281) < 540) { - status = Status.BANKER_GUIDE; - } else if (Microbot.getVarbitPlayerValue(281) >= 540 && Microbot.getVarbitPlayerValue(281) < 610) { - status = Status.PRAYER_GUIDE; - } else if (Microbot.getVarbitPlayerValue(281) >= 610 && Microbot.getVarbitPlayerValue(281) < 1000) { - status = Status.MAGE_GUIDE; - } else if (Microbot.getVarbitPlayerValue(281) == 1000) { - status = Status.FINISHED; - } - } - - public void RandomizeCharacter() { - if (Rs2Random.diceFractional(0.2)) { - selectGender(); - - if (Rs2Random.diceFractional(0.25)) { // chance to change pronouns - System.out.println("changing pronouns..."); - Widget pronounWidget = Rs2Widget.getWidget(CharacterCreation, 72); // open pronouns DropDown - Widget currentPronoun = Arrays.stream(pronounWidget.getDynamicChildren()).filter(pnw -> pnw.getText().toLowerCase().contains("he/him") || pnw.getText().toLowerCase().contains("they/them") || pnw.getText().toLowerCase().contains("she/her")).findFirst().orElse(null); - Rs2Widget.clickWidget(pronounWidget); - Rs2Random.waitEx(1200, 300); - sleepUntil(() -> Rs2Widget.isWidgetVisible(CharacterCreation, 76)); // Pronoun DropDown Options - Widget[] dynamicPronounWidgets = Rs2Widget.getWidget(CharacterCreation, 78).getDynamicChildren(); - Widget pronounSelectionWidget; - - if (currentPronoun != null) { - if (currentPronoun.getText().toLowerCase().contains("he/him")) { - if (Rs2Random.diceFractional(0.5)) { - pronounSelectionWidget = Arrays.stream(dynamicPronounWidgets).filter(dpw -> dpw.getText().toLowerCase().contains("they/them")).findFirst().orElse(null); - } else { - pronounSelectionWidget = Arrays.stream(dynamicPronounWidgets).filter(dpw -> dpw.getText().toLowerCase().contains("she/her")).findFirst().orElse(null); - } - } else { - if (Rs2Random.diceFractional(0.5)) { - pronounSelectionWidget = Arrays.stream(dynamicPronounWidgets).filter(dpw -> dpw.getText().toLowerCase().contains("they/them")).findFirst().orElse(null); - } else { - pronounSelectionWidget = Arrays.stream(dynamicPronounWidgets).filter(dpw -> dpw.getText().toLowerCase().contains("he/him")).findFirst().orElse(null); - } - } - - Rs2Widget.clickWidget(pronounSelectionWidget); - Rs2Random.waitEx(1200, 300); - sleepUntil(() -> !Rs2Widget.isWidgetVisible(CharacterCreation, 76)); // Pronoun DropDown Options - } - } - - Rs2Widget.clickWidget(CharacterCreation, 74); // confirm Button - Rs2Random.waitEx(1200, 300); - sleepUntil(() -> !isCharacterCreationVisible()); - } - - int randomIndex = (int) Math.floor(Math.random() * CharacterCreation_Arrows.length); - int item = CharacterCreation_Arrows[randomIndex]; - item += Math.random() < 0.5 ? 2 : 3; // Select Up / Down Arrow for random index - Widget widget = Rs2Widget.getWidget(CharacterCreation, item); - - for (int i = 0; i < Rs2Random.between(1, 6); i++) { - Rs2Widget.clickWidget(widget.getId()); - Rs2Random.waitEx(300, 50); - } - } - - /** - * Selects the gender of the character during the character creation process. - * - *

    This method randomly decides whether to change the gender of the character - * if it has not been selected yet. It checks the current gender selection and - * switches to the opposite gender if necessary.

    - */ - private void selectGender() { - // Check if the gender should be changed and if it has not been selected yet - if (Rs2Random.diceFractional(0.5) && !hasSelectedGender) { // chance to change gender - System.out.println("changing gender..."); - Widget maleWidget = Rs2Widget.getWidget(CharacterCreation, 68); // maleButton - Widget femaleWidget = Rs2Widget.getWidget(CharacterCreation, 69); // femaleButton.. nice.. - int selectedColor = 0xaaaaaa; - - // Check if the male gender is currently selected - boolean hasMaleSelected = Arrays.stream(maleWidget.getDynamicChildren()).anyMatch(mdw -> mdw != null && mdw.getTextColor() == selectedColor); - // Check if the female gender is currently selected - boolean hasFemaleSelected = Arrays.stream(femaleWidget.getDynamicChildren()).anyMatch(fdw -> fdw != null && fdw.getTextColor() == selectedColor); - - // Switch to male gender if female is currently selected - if (hasFemaleSelected) { - Rs2Widget.clickWidget(maleWidget); - Rs2Random.waitEx(1200, 300); - sleepUntil(() -> hasMaleSelected); - // Switch to female gender if male is currently selected - } else if (hasMaleSelected) { - Rs2Widget.clickWidget(femaleWidget); - Rs2Random.waitEx(1200, 300); - sleepUntil(() -> hasFemaleSelected); - } - } - // Mark the gender as selected - hasSelectedGender = true; - } - - public void GettingStarted() { - var npc = Rs2Npc.getNpc(NpcID.GIELINOR_GUIDE); - - if (hasContinue()) return; - - if (Microbot.getVarbitPlayerValue(281) < 3) { - if (isInDialogue()) { - Rs2Keyboard.typeString(Integer.toString(Rs2Random.between(1, 3))); - return; - } - - if (Rs2Npc.interact(npc, "Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } - } else if (Microbot.getVarbitPlayerValue(281) < 8) { - - if (!toggledSettings) { - Rs2Widget.clickWidget(164, 41); - toggledSettings = true; - Rs2Random.waitEx(1200, 300); - return; - } - - if (plugin.isToggleMusic() && !toggledMusic) { - turnOffMusic(); - toggledMusic = true; - Rs2Random.waitEx(1200, 300); - return; - } - - if (plugin.isToggleRoofs() && !isHideRoofsEnabled()) { - hideRoofs(false); - Rs2Random.waitEx(1200, 300); - return; - } - - if (plugin.isToggleShiftDrop() && !isDropShiftSettingEnabled()) { - enableDropShiftSetting(false); - Rs2Random.waitEx(1200, 300); - return; - } - - if (plugin.isToggleLevelUp() && isLevelUpNotificationsEnabled()) { - disableLevelUpNotifications(true); - Rs2Random.waitEx(1200, 300); - return; - } - - Rs2Camera.setZoom(Rs2Random.between(400, 450)); - Rs2Random.waitEx(300, 100); - Rs2Camera.setPitch(280); - - sleepUntil(() -> Rs2Camera.getPitch() > 250); - - if (Rs2Npc.interact(npc, "Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } - - } else { - if (Rs2Npc.interact(npc, "Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } - } - } - - public void SurvivalGuide() { - var npc = Rs2Npc.getNpc(NpcID.SURVIVAL_EXPERT); - - if (Microbot.getVarbitPlayerValue(281) == 10 || Microbot.getVarbitPlayerValue(281) == 20 || Microbot.getVarbitPlayerValue(281) == 60) { - if (!Rs2Npc.hasLineOfSight(npc)) { - Rs2Walker.walkTo(npc.getWorldLocation(), 4); - Rs2Player.waitForWalking(); - } - if (Rs2Npc.interact(npc, "talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } - } else if (Microbot.getVarbitPlayerValue(281) < 40) { - Rs2Random.waitEx(1200, 300); - var widget = Rs2Widget.findWidget("Inventory", true); - Rs2Widget.clickWidget(widget); // switchToInventoryTab - Rs2Random.waitEx(1200, 300); - } else if (Microbot.getVarbitPlayerValue(281) < 50) { - fishShrimp(); - } else if (Microbot.getVarbitPlayerValue(281) < 70) { - var widget = Rs2Widget.findWidget("Skills", true); - Rs2Widget.clickWidget(widget); // switchToSkillsTab - Rs2Random.waitEx(1200, 300); - if (Rs2Npc.interact(npc, "talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } - } else if (Microbot.getVarbitPlayerValue(281) <= 90) { - if (!Rs2Inventory.hasItem("Bronze Axe") || !Rs2Inventory.hasItem("Tinderbox")) { - if (Rs2Npc.interact(npc, "talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } - return; - } - if (!Rs2Inventory.contains("Raw shrimps")) { - fishShrimp(); - return; - } - if (!Rs2Inventory.contains("Logs") && (!Rs2GameObject.exists(ObjectID.FIRE_26185) || Rs2Player.getRealSkillLevel(Skill.WOODCUTTING) == 0)) { - CutTree(); - return; - } - if (!Rs2GameObject.exists(ObjectID.FIRE_26185)) { - LightFire(); - return; - } - Rs2Inventory.useItemOnObject(ItemID.RAW_SHRIMPS_2514, ObjectID.FIRE_26185); - } - } - - public void MageGuide() { - var npc = Rs2Npc.getNpc(NpcID.MAGIC_INSTRUCTOR); - - if (Microbot.getVarbitPlayerValue(281) == 610 || Microbot.getVarbitPlayerValue(281) == 620) { - WorldPoint worldPoint = new WorldPoint(3141, 3088, 0); - WorldPoint targetPoint = (npc != null) ? npc.getWorldLocation() : worldPoint; - int distance = Rs2Player.distanceTo(targetPoint); - - if (distance > 8) { - Rs2Walker.walkTo(targetPoint, 8); - } else { - if (Rs2Npc.interact(npc, "Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } - } - } else if (Microbot.getVarbitPlayerValue(281) == 630) { - var widget = Rs2Widget.findWidget("Magic", true); - Rs2Widget.clickWidget(widget); // switchToMagicTab - Rs2Random.waitEx(1200, 300); - } else if (Microbot.getVarbitPlayerValue(281) == 640) { - if (Rs2Npc.interact(npc, "Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } - } else if (Microbot.getVarbitPlayerValue(281) == 650) { - Rs2NpcModel chicken = Rs2Npc.getNpcs("chicken").findFirst().orElse(null); - Rs2Magic.castOn(MagicAction.WIND_STRIKE, chicken); - } else if (Microbot.getVarbitPlayerValue(281) == 670) { - Rs2Dialogue.clickContinue(); - if (isInDialogue()) { - if (Rs2Widget.hasWidget("Do you want to go to the mainland?")) { - Rs2Keyboard.typeString("1"); - return; - } - if (hasSelectAnOption()) { - Widget widgetOptions = Rs2Widget.getWidget(219, 1); - Widget[] dynamicWidgetOptions = widgetOptions.getDynamicChildren(); - - for (int i = 0; i < dynamicWidgetOptions.length; i++) { - String optionText = dynamicWidgetOptions[i].getText(); - - if (optionText.contains("Yes, send me to the mainland") || optionText.contains("No, I'm not planning to do that")) { - Rs2Keyboard.typeString(String.valueOf(i)); - break; - } - } - } - } else { - if (Rs2Npc.interact(npc, "Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } - } - } - } - - public void PrayerGuide() { - var npc = Rs2Npc.getNpc(NpcID.BROTHER_BRACE); - - if (Microbot.getVarbitPlayerValue(281) == 640 || Microbot.getVarbitPlayerValue(281) == 550 || Microbot.getVarbitPlayerValue(281) == 540) { - Rs2Walker.walkTo(new WorldPoint(3124, 3106, 0)); - if (Rs2Npc.interact(npc, "Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } - } else if (Microbot.getVarbitPlayerValue(281) == 560) { - var widget = Rs2Widget.findWidget("Prayer", true); - Rs2Widget.clickWidget(widget); // switchToPrayerTab - Rs2Random.waitEx(1200, 300); - } else if (Microbot.getVarbitPlayerValue(281) == 570) { - if (Rs2Npc.interact(npc, "Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } - } else if (Microbot.getVarbitPlayerValue(281) == 580) { - var widget = Rs2Widget.findWidget("Friends list", true); - Rs2Widget.clickWidget(widget); // switchToFriendsTab - Rs2Random.waitEx(1200, 300); - } else if (Microbot.getVarbitPlayerValue(281) == 600) { - if (Rs2Npc.interact(npc, "Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } - } - } - - public void BankerGuide() { - var npc = Rs2Npc.getNpc(NpcID.ACCOUNT_GUIDE); - - if (Microbot.getVarbitPlayerValue(281) == 510) { - Rs2GameObject.interact(ObjectID.BANK_BOOTH_10083); - sleepUntil(() -> Microbot.getVarbitPlayerValue(281) != 510); - - - } else if (Microbot.getVarbitPlayerValue(281) == 520) { - if (Rs2Widget.isWidgetVisible(289, 5)) { - Widget widgetOptions = Rs2Widget.getWidget(289, 4); - Widget[] dynamicWidgetOptions = widgetOptions.getDynamicChildren(); - - for (Widget dynamicWidgetOption : dynamicWidgetOptions) { - String widgetText = dynamicWidgetOption.getText(); - - if (widgetText != null) { - if (widgetText.equalsIgnoreCase("Want more bank space?")) { - Rs2Widget.clickWidget(289, 7); - Rs2Random.waitEx(1200, 300); - break; - } - } - } - } - - Rs2Bank.closeBank(); - sleepUntil(() -> !Rs2Bank.isOpen()); - Rs2GameObject.interact(26815); //interactWithPollBooth - sleepUntil(() -> Microbot.getVarbitPlayerValue(281) != 520); - } else if (Microbot.getVarbitPlayerValue(281) == 525 || Microbot.getVarbitPlayerValue(281) == 530) { - if (Rs2Widget.isWidgetVisible(310, 2)) { - Widget widgetOptions = Rs2Widget.getWidget(310, 2); - Widget[] dynamicWidgetOptions = widgetOptions.getDynamicChildren(); - - for (Widget dynamicWidgetOption : dynamicWidgetOptions) { - String[] actionsText = dynamicWidgetOption.getActions(); - - if (actionsText != null) { - if (Arrays.stream(actionsText).anyMatch(at -> at.equalsIgnoreCase("close"))) { - Rs2Widget.clickWidget(dynamicWidgetOption); - Rs2Random.waitEx(1200, 300); - break; - } - } - } - } - - Rs2Walker.walkTo(npc.getWorldLocation(), 3); - Rs2Player.waitForWalking(); - if (Rs2Npc.interact(npc, "Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } - } else if (Microbot.getVarbitPlayerValue(281) == 531) { - var widget = Rs2Widget.findWidget("Account Management", true); - Rs2Widget.clickWidget(widget); // switchToAccountManagementTab - Rs2Random.waitEx(1200, 300); - } else if (Microbot.getVarbitPlayerValue(281) == 532) { - if (Rs2Dialogue.isInDialogue()) { - clickContinue(); - return; - } - if (Rs2Npc.interact(npc, "Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } - } - } - - public void CombatGuide() { - var npc = Rs2Npc.getNpc(NpcID.COMBAT_INSTRUCTOR); - - if (Microbot.getVarbitPlayerValue(281) <= 370) { - Rs2Walker.walkTo(new WorldPoint(Rs2Random.between(3106, 3108), Rs2Random.between(9508, 9510), 0)); - Rs2Player.waitForWalking(); - if (Rs2Npc.interact(npc, "Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } - } else if (Microbot.getVarbitPlayerValue(281) <= 410) { - if (isInDialogue()) { - clickContinue(); - return; - } - var widget = Rs2Widget.findWidget("Worn Equipment", true); - Rs2Widget.clickWidget(widget); // switchToEquipmentMenu - Rs2Random.waitEx(1200, 300); - Rs2Widget.clickWidget(387, 1); //openEquipmentStats - sleepUntil(() -> Rs2Widget.getWidget(84, 1) != null); - Rs2Random.waitEx(1200, 300); - Rs2Widget.clickWidget("Bronze dagger"); - Rs2Random.waitEx(2400, 300); - - if (Rs2Widget.isWidgetVisible(84, 3)) { - Widget widgetOptions = Rs2Widget.getWidget(84, 3); - Widget[] dynamicWidgetOptions = widgetOptions.getDynamicChildren(); - - for (Widget dynamicWidgetOption : dynamicWidgetOptions) { - String[] actionsText = dynamicWidgetOption.getActions(); - - if (actionsText != null) { - if (Arrays.stream(actionsText).anyMatch(at -> at.equalsIgnoreCase("close"))) { - Rs2Widget.clickWidget(dynamicWidgetOption); - Rs2Random.waitEx(1200, 300); - break; - } - } - } - } - - if (Rs2Npc.interact(npc, "Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } - } else if (Microbot.getVarbitPlayerValue(281) == 500) { - Rs2Walker.walkTo(new WorldPoint(3111, 9526, Rs2Player.getWorldLocation().getPlane())); - Rs2Player.waitForWalking(); - Rs2GameObject.interact("Ladder", "Climb-up"); - sleepUntil(() -> Microbot.getVarbitPlayerValue(281) != 500); - } else if (Microbot.getVarbitPlayerValue(281) == 480 || Microbot.getVarbitPlayerValue(281) == 490) { - Actor rat = Rs2Player.getInteracting(); - if (rat != null && rat.getName().equalsIgnoreCase("giant rat")) return; - Rs2Inventory.wield("Shortbow"); - Rs2Random.waitEx(600, 100); - Rs2Inventory.wield("Bronze arrow"); - Rs2Random.waitEx(600, 100); - if (Rs2Random.between(1, 5) == 2) { - Rs2Walker.walkTo(new WorldPoint(3110, 9523, 0), 4); - } - Rs2Player.waitForWalking(); - Rs2Npc.attack("Giant rat"); - } else if (Microbot.getVarbitPlayerValue(281) == 470) { - Rs2Walker.walkTo(npc.getWorldLocation()); - Rs2Player.waitForWalking(); - if (Rs2Npc.interact(npc, "Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } - } else if (Microbot.getVarbitPlayerValue(281) >= 420) { - if (Microbot.getClient().getLocalPlayer().isInteracting() || Rs2Player.isAnimating()) return; - if (Rs2Equipment.isWearing("Bronze sword")) { - var widget = Rs2Widget.findWidget("Combat Options", true); - Rs2Widget.clickWidget(widget); // switchToQuestTab - Rs2Random.waitEx(1200, 300); - WorldPoint worldPoint = new WorldPoint(3105, 9517, 0); - Rs2Walker.walkTo(worldPoint, 3); - Rs2Player.waitForWalking(); - Rs2Npc.attack("Giant rat"); - } else { - Rs2Tab.switchToInventoryTab(); - Rs2Random.waitEx(600, 100); - Rs2Inventory.wield("Bronze sword"); - Rs2Random.waitEx(600, 100); - Rs2Inventory.wield("Wooden shield"); - } - } - } - - public void MiningGuide() { - var npc = Rs2Npc.getNpc(NpcID.MINING_INSTRUCTOR); - - if (Microbot.getVarbitPlayerValue(281) == 260) { - Rs2Walker.walkTo(new WorldPoint(Rs2Random.between(3082, 3085), Rs2Random.between(9502, 9505), 0)); - if (Rs2Npc.interact(npc, "Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } - } else { - if (Rs2Inventory.contains("Bronze dagger")) { - Rs2GameObject.interact(ObjectID.GATE_9718, "Open"); - sleepUntil(() -> Microbot.getVarbitPlayerValue(281) > 360); - return; - } - if (Rs2Inventory.contains("Bronze bar") && Rs2Inventory.contains("Hammer")) { - Rs2GameObject.interact("Anvil", "Smith"); - sleepUntil(Rs2Widget::isSmithingWidgetOpen); - Rs2Widget.clickWidget(312, 9); // Smith Bronze Dagger - Rs2Random.waitEx(1200, 300); - sleepUntil(() -> Rs2Inventory.contains("Bronze dagger") && !Rs2Player.isAnimating(1800)); - return; - } - if (Rs2Inventory.contains("Bronze bar") && !Rs2Inventory.contains("Hammer")) { - if (Rs2Npc.interact(npc, "Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } - return; - } - if (Rs2Inventory.contains("Bronze pickaxe") && (!Rs2Inventory.contains("Copper ore") || !Rs2Inventory.contains("Tin ore"))) { - List rockIds = new ArrayList<>(); - if (!Rs2Inventory.contains("Copper ore")) { - rockIds.add(ObjectID.COPPER_ROCKS); - } - if (!Rs2Inventory.contains("Tin ore")) { - rockIds.add(ObjectID.TIN_ROCKS); - } - - Collections.shuffle(rockIds); - int rockId = rockIds.get(0); - - Rs2GameObject.interact(rockId, "Mine"); - sleepUntil(() -> { - if (rockId == ObjectID.COPPER_ROCKS) { - return Rs2Inventory.contains("Copper ore") && !Rs2Player.isAnimating(1800); - } else { - return Rs2Inventory.contains("Tin ore") && !Rs2Player.isAnimating(1800); - } - }); - } else if (Rs2Inventory.contains("Copper ore") && Rs2Inventory.contains("Tin ore")) { - int[] ores = {ItemID.TIN_ORE, ItemID.COPPER_ORE}; - Collections.shuffle(Arrays.asList(ores)); - Rs2Inventory.useItemOnObject(ores[0], ObjectID.FURNACE_10082); - sleepUntil(() -> Rs2Inventory.contains("Bronze bar") && !Rs2Player.isAnimating(1800)); - } - } - } - - public void QuestGuide() { - var npc = Rs2Npc.getNpc(NpcID.QUEST_GUIDE); - - if (Microbot.getVarbitPlayerValue(281) == 200 || Microbot.getVarbitPlayerValue(281) == 210) { - Rs2Walker.walkTo(new WorldPoint(Rs2Random.between(3083, 3086), Rs2Random.between(3127, 3129), 0)); - Rs2GameObject.interact(9716, "Open"); - Rs2Random.waitEx(1200, 300); - } else if (Microbot.getVarbitPlayerValue(281) == 220 || Microbot.getVarbitPlayerValue(281) == 240) { - Rs2Npc.interact(npc, "Talk-to"); - sleepUntil(Rs2Dialogue::isInDialogue); - } else if (Microbot.getVarbitPlayerValue(281) == 230) { - var widget = Rs2Widget.findWidget("Quest List", true); - Rs2Widget.clickWidget(widget); // switchToQuestTab - Rs2Random.waitEx(1200, 300); - } else { - Rs2Tab.switchToInventoryTab(); - Rs2Random.waitEx(600, 100); - Rs2GameObject.interact(9726, "Climb-down"); - Rs2Random.waitEx(2400, 100); - } - } - - public void CookingGuide() { - var npc = Rs2Npc.getNpc(NpcID.MASTER_CHEF); - - if (Microbot.getVarbitPlayerValue(281) == 120) { - Rs2Random.waitEx(1200, 300); - Rs2Keyboard.keyPress(KeyEvent.VK_ESCAPE); - Rs2GameObject.interact(ObjectID.GATE_9470, "Open"); - sleepUntil(() -> Microbot.getVarbitPlayerValue(281) != 120); - } else if (Microbot.getVarbitPlayerValue(281) == 130) { - Rs2GameObject.interact(ObjectID.DOOR_9709, "Open"); - sleepUntil(() -> Microbot.getVarbitPlayerValue(281) != 130); - } else if (Microbot.getVarbitPlayerValue(281) == 140) { - if (Rs2Npc.interact(npc, "Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } - } else if (Microbot.getVarbitPlayerValue(281) >= 150 && Microbot.getVarbitPlayerValue(281) < 200) { - if (!Rs2Inventory.contains("Bread dough") && !Rs2Inventory.contains("Bread")) { - Rs2Inventory.combine("Bucket of water", "Pot of flour"); - sleepUntil(() -> Rs2Inventory.contains("Dough"), 2000); - } else if (Rs2Inventory.contains("Bread dough")) { - Rs2Inventory.interact("Bread dough"); - Rs2GameObject.interact(9736, "Use"); - sleepUntil(() -> Rs2Inventory.contains("Bread")); - } else if (Rs2Inventory.contains("Bread")) { - if (Rs2GameObject.interact(9710, "Open")) { - Rs2Random.waitEx(2400, 100); - } - } - } - } - - public void LightFire() { - if (Rs2Player.isStandingOnGameObject()) { - WorldPoint nearestWalkable = Rs2Tile.getNearestWalkableTileWithLineOfSight(Rs2Player.getWorldLocation()); - Rs2Walker.walkFastCanvas(nearestWalkable); - Rs2Player.waitForWalking(); - } - Rs2Inventory.combine("Logs", "Tinderbox"); - sleepUntil(() -> !Rs2Inventory.hasItem("Logs") && !Rs2Player.isAnimating(2400)); - } - - public void CutTree() { - Rs2GameObject.interact("Tree", "Chop down"); - sleepUntil(() -> Rs2Inventory.hasItem("Logs") && !Rs2Player.isAnimating(2400)); - } - - public void fishShrimp() { - Rs2Npc.interact(NpcID.FISHING_SPOT_3317, "Net"); - sleepUntil(() -> Rs2Inventory.contains("Raw shrimps")); - } - - enum Status { - NAME, - CHARACTER, - GETTING_STARTED, - SURVIVAL_GUIDE, - COOKING_GUIDE, - QUEST_GUIDE, - MINING_GUIDE, - COMBAT_GUIDE, - BANKER_GUIDE, - PRAYER_GUIDE, - IRONMAN_GUIDE, - MAGE_GUIDE, - FINISHED - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialislandPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialislandPlugin.java deleted file mode 100644 index fddf5d9d131..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialislandPlugin.java +++ /dev/null @@ -1,97 +0,0 @@ -package net.runelite.client.plugins.microbot.tutorialisland; - -import com.google.inject.Provides; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import net.runelite.client.config.ConfigManager; -import net.runelite.client.eventbus.Subscribe; -import net.runelite.client.events.ConfigChanged; -import net.runelite.client.plugins.Plugin; -import net.runelite.client.plugins.PluginDescriptor; -import net.runelite.client.ui.overlay.OverlayManager; - -import javax.inject.Inject; -import java.awt.*; - -@PluginDescriptor( - name = PluginDescriptor.Mocrosoft + "TutorialIsland", - description = "Microbot tutorialIsland plugin", - tags = {"TutorialIsland", "microbot"}, - enabledByDefault = false -) -@Slf4j -public class TutorialislandPlugin extends Plugin { - - @Getter - private boolean toggleMusic; - @Getter - private boolean toggleRoofs; - @Getter - private boolean toggleLevelUp; - @Getter - private boolean toggleShiftDrop; - @Getter - private boolean toggleDevOverlay; - - - @Inject - private TutorialIslandConfig config; - @Provides - TutorialIslandConfig provideConfig(ConfigManager configManager) { - return configManager.getConfig(TutorialIslandConfig.class); - } - - @Inject - private OverlayManager overlayManager; - @Inject - private TutorialIslandOverlay tutorialIslandOverlay; - - @Inject - TutorialIslandScript tutorialIslandScript; - - - @Override - protected void startUp() throws AWTException { - toggleMusic = config.toggleMusic(); - toggleRoofs = config.toggleRoofs(); - toggleLevelUp = config.toggleDisableLevelUp(); - toggleShiftDrop = config.toggleShiftDrop(); - toggleDevOverlay = config.toggleDevOverlay(); - - if (overlayManager != null) { - overlayManager.add(tutorialIslandOverlay); - } - - tutorialIslandScript.run(config); - } - - protected void shutDown() { - tutorialIslandScript.shutdown(); - overlayManager.remove(tutorialIslandOverlay); - } - - @Subscribe - public void onConfigChanged(final ConfigChanged event) { - if (!event.getGroup().equals(TutorialIslandConfig.configGroup)) return; - - if (event.getKey().equals(TutorialIslandConfig.toggleMusic)) { - toggleMusic = config.toggleMusic(); - } - - if (event.getKey().equals(TutorialIslandConfig.toggleRoofs)) { - toggleRoofs = config.toggleRoofs(); - } - - if (event.getKey().equals(TutorialIslandConfig.toggleLevelUp)) { - toggleLevelUp = config.toggleDisableLevelUp(); - } - - if (event.getKey().equals(TutorialIslandConfig.toggleShiftDrop)) { - toggleShiftDrop = config.toggleShiftDrop(); - } - - if (event.getKey().equals(TutorialIslandConfig.toggleDevOverlay)) { - toggleDevOverlay = config.toggleDevOverlay(); - } - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/ui/MicrobotPluginHubPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/ui/MicrobotPluginHubPanel.java index 63b60a29ec8..72e49660522 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/ui/MicrobotPluginHubPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/ui/MicrobotPluginHubPanel.java @@ -38,11 +38,8 @@ import net.runelite.client.plugins.microbot.externalplugins.MicrobotPluginClient; import net.runelite.client.plugins.microbot.externalplugins.MicrobotPluginManager; import net.runelite.client.plugins.microbot.externalplugins.MicrobotPluginManifest; -import net.runelite.client.ui.ClientUI; -import net.runelite.client.ui.ColorScheme; -import net.runelite.client.ui.DynamicGridLayout; -import net.runelite.client.ui.FontManager; -import net.runelite.client.ui.PluginPanel; +import net.runelite.client.plugins.microbot.util.misc.Rs2UiHelper; +import net.runelite.client.ui.*; import net.runelite.client.ui.components.IconTextField; import net.runelite.client.util.ImageUtil; import net.runelite.client.util.LinkBrowser; @@ -285,7 +282,7 @@ private class PluginItem extends JPanel implements SearchablePlugin { addrm.setBackground(new Color(0x28BE28)); addrm.addActionListener(l -> { // Check version compatibility before installing - if (!microbotPluginManager.isClientVersionCompatible(manifest.getMinClientVersion())) { + if (!Rs2UiHelper.isClientVersionCompatible(manifest.getMinClientVersion())) { String _currentMicrobotVersion = RuneLiteProperties.getMicrobotVersion(); String requiredVersion = manifest.getMinClientVersion(); @@ -343,7 +340,7 @@ private class PluginItem extends JPanel implements SearchablePlugin { addrm.setText("Installing"); addrm.setBackground(ColorScheme.MEDIUM_GRAY_COLOR); - microbotPluginManager.install(manifest); + microbotPluginManager.installPlugin(manifest); }); } else if (installed) { // Check if update is available @@ -363,9 +360,8 @@ private class PluginItem extends JPanel implements SearchablePlugin { addrm.addActionListener(l -> { addrm.setText("Updating"); addrm.setBackground(ColorScheme.MEDIUM_GRAY_COLOR); - microbotPluginManager.remove(manifest.getInternalName()); - microbotPluginManager.install(manifest); // This will update the plugin - reloadPluginList(); + microbotPluginManager.update(); + reloadPluginList(); }); } else { addrm.setText("Remove"); @@ -373,7 +369,7 @@ private class PluginItem extends JPanel implements SearchablePlugin { addrm.addActionListener(l -> { addrm.setText("Removing"); addrm.setBackground(ColorScheme.MEDIUM_GRAY_COLOR); - microbotPluginManager.remove(manifest.getInternalName()); + microbotPluginManager.removePlugin(manifest); }); } } else { @@ -571,43 +567,38 @@ public void changedUpdate(DocumentEvent e) { reloadPluginList(); } - private void reloadPluginList() - { - if (refreshing.isVisible()) - { - return; - } - - refreshing.setVisible(true); - mainPanel.removeAll(); - - executor.submit(() -> - { - Collection manifestCollection = microbotPluginManager.getManifestMap().values(); - - Map pluginCounts = Collections.emptyMap(); - try - { - pluginCounts = microbotPluginClient.getPluginCounts(); - } - catch (IOException e) - { - log.warn("Unable to download plugin counts", e); - SwingUtilities.invokeLater(() -> - { - refreshing.setVisible(false); - mainPanel.add(new JLabel("Downloading the plugin manifest failed")); - - JButton retry = new JButton("Retry"); - retry.addActionListener(l -> reloadPluginList()); - mainPanel.add(retry); - mainPanel.revalidate(); - }); - } - - reloadPluginList(manifestCollection, pluginCounts); - }); - } + private void reloadPluginList() { + if (refreshing.isVisible()) { + return; + } + + refreshing.setVisible(true); + mainPanel.removeAll(); + + executor.submit(() -> + { + Collection manifestCollection = microbotPluginManager.getManifestMap().values(); + + Map pluginCounts = Collections.emptyMap(); + try { + pluginCounts = microbotPluginClient.getPluginCounts(); + } catch (IOException e) { + log.warn("Unable to download plugin counts", e); + SwingUtilities.invokeLater(() -> + { + refreshing.setVisible(false); + mainPanel.add(new JLabel("Downloading the plugin manifest failed")); + + JButton retry = new JButton("Retry"); + retry.addActionListener(l -> reloadPluginList()); + mainPanel.add(retry); + mainPanel.revalidate(); + }); + } + + reloadPluginList(manifestCollection, pluginCounts); + }); + } private void reloadPluginList(Collection manifest, Map pluginCounts) { @@ -625,7 +616,7 @@ private void reloadPluginList(Collection manifest, Map installed = new HashSet<>(microbotPluginManager.getInstalledPlugins()); + List installed = new ArrayList<>(microbotPluginManager.getInstalledPlugins()); // Pre-index manifests by internalName (lowercased) - using filtered list Map manifestByName = enabledManifest.stream() @@ -633,27 +624,27 @@ private void reloadPluginList(Collection manifest, Map m.getInternalName().toLowerCase(Locale.ROOT), Function.identity(), - (a, b) -> a // keep first on duplicates + (a, b) -> a )); - // Index loaded plugins by simple name (lowercased) → all instances for that name + // Index loaded plugins by simple name (lowercased) - all instances for that name Map> pluginsByName = loadedPlugins.stream() .collect(Collectors.groupingBy( p -> p.getClass().getSimpleName().toLowerCase(Locale.ROOT), LinkedHashMap::new, - Collectors.toCollection(LinkedHashSet::new) // stable, no dups + Collectors.toCollection(LinkedHashSet::new) )); // Build PluginItem list by looping over manifests plugins = manifestByName.entrySet().stream() .map(e -> { - String key = e.getKey(); // lowercased internalName + String key = e.getKey(); MicrobotPluginManifest m = e.getValue(); - String simpleName = m.getInternalName(); // original case + String simpleName = m.getInternalName(); Collection group = pluginsByName.getOrDefault(key, Collections.emptySet()); int count = pluginCounts.getOrDefault(simpleName, -1); - boolean isInstalled = installed.contains(simpleName); + boolean isInstalled = installed.stream().anyMatch(im -> im.getInternalName().equalsIgnoreCase(simpleName)); return new PluginItem(m, group, count, isInstalled); }) @@ -720,10 +711,10 @@ public void onDeactivate() { } } - @Subscribe - private void onExternalPluginsChanged(ExternalPluginsChanged ev) { - reloadPluginList(); - } + @Subscribe + private void onExternalPluginsChanged(ExternalPluginsChanged ev) { + reloadPluginList(); + } // A utility class copied from the original PluginHubPanel private static class FixedWidthPanel extends JPanel { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/grounditem/LootingParameters.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/grounditem/LootingParameters.java index 4314284409b..6884d96d363 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/grounditem/LootingParameters.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/grounditem/LootingParameters.java @@ -1,9 +1,14 @@ package net.runelite.client.plugins.microbot.util.grounditem; +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter public class LootingParameters { private int minValue, maxValue, range, minItems, minQuantity, minInvSlots; - private boolean delayedLooting, antiLureProtection; + private boolean delayedLooting, antiLureProtection, eatFoodForSpace; private String[] names; private String[] ignoredNames; @@ -20,7 +25,7 @@ public class LootingParameters { * @param antiLureProtection A boolean indicating whether anti-lure protection should be enabled. */ public LootingParameters(int minValue, int maxValue, int range, int minItems, int minInvSlots, boolean delayedLooting, boolean antiLureProtection) { - setValues(minValue, maxValue, range, minItems, 1, minInvSlots, delayedLooting, antiLureProtection, null, null); + setValues(minValue, maxValue, range, minItems, 1, minInvSlots, delayedLooting, antiLureProtection,false, null, null); } /** @@ -36,7 +41,7 @@ public LootingParameters(int minValue, int maxValue, int range, int minItems, in * @param names The names of the items to be looted. */ public LootingParameters(int range, int minItems, int minQuantity, int minInvSlots, boolean delayedLooting, boolean antiLureProtection, String... names) { - setValues(0, 0, range, minItems, minQuantity, minInvSlots, delayedLooting, antiLureProtection, null, names); + setValues(0, 0, range, minItems, minQuantity, minInvSlots, delayedLooting, antiLureProtection,false, null, names); } /** @@ -54,10 +59,10 @@ public LootingParameters(int range, int minItems, int minQuantity, int minInvSlo */ public LootingParameters(int minValue, int maxValue, int range, int minItems, int minInvSlots, boolean delayedLooting, boolean antiLureProtection, String[] ignoredNames) { - setValues(minValue, maxValue, range, minItems, 1, minInvSlots, delayedLooting, antiLureProtection, ignoredNames, null); + setValues(minValue, maxValue, range, minItems, 1, minInvSlots, delayedLooting, antiLureProtection,false, ignoredNames, null); } - private void setValues(int minValue, int maxValue, int range, int minItems, int minQuantity, int minInvSlots, boolean delayedLooting, boolean antiLureProtection, String[] ignoredNames, String[] names) { + private void setValues(int minValue, int maxValue, int range, int minItems, int minQuantity, int minInvSlots, boolean delayedLooting, boolean antiLureProtection,boolean eatFoodForSpace, String[] ignoredNames, String[] names) { this.minValue = minValue; this.maxValue = maxValue; this.range = range; @@ -66,50 +71,11 @@ private void setValues(int minValue, int maxValue, int range, int minItems, int this.minInvSlots = minInvSlots; this.delayedLooting = delayedLooting; this.antiLureProtection = antiLureProtection; + this.eatFoodForSpace = eatFoodForSpace; this.ignoredNames = ignoredNames; this.names = names; } - // Getters - public int getMinValue() { - return minValue; - } - - public int getMaxValue() { - return maxValue; - } - - public int getRange() { - return range; - } - - public int getMinItems() { - return minItems; - } - - public int getMinQuantity() { - return minQuantity; - } - - public int getMinInvSlots() { - return minInvSlots; - } - - public boolean isDelayedLooting() { - return delayedLooting; - } - - public boolean isAntiLureProtection() { - return antiLureProtection; - } - - public String[] getIgnoredNames() { - return ignoredNames; - } - - public String[] getNames() { - return names; - } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/grounditem/Rs2GroundItem.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/grounditem/Rs2GroundItem.java index 2f804e4200d..436f22b4f24 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/grounditem/Rs2GroundItem.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/grounditem/Rs2GroundItem.java @@ -32,6 +32,8 @@ */ @Slf4j public class Rs2GroundItem { + private static final int DESPAWN_DELAY_THRESHOLD_TICKS = 150; + private static boolean runWhilePaused(BooleanSupplier booleanSupplier) { final boolean paused = Microbot.pauseAllScripts.getAndSet(true); final boolean success = booleanSupplier.getAsBoolean(); @@ -173,7 +175,7 @@ public static RS2Item[] getAll(int range) { /** * Retrieves all RS2Item objects within a specified range of a WorldPoint, sorted by distance. - * + * * @param range The radius in tiles to search around the given world point * @param worldPoint The center WorldPoint to search around * @return An array of RS2Item objects found within the specified range, sorted by proximity @@ -288,131 +290,140 @@ private static boolean validateLoot(Predicate filter) { } - public static boolean lootItemBasedOnValue(LootingParameters params) { - final Predicate filter = groundItem -> groundItem.getGePrice() > params.getMinValue() && (groundItem.getGePrice() / groundItem.getQuantity()) < params.getMaxValue() && - groundItem.getLocation().distanceTo(Microbot.getClient().getLocalPlayer().getWorldLocation()) < params.getRange() && - (!params.isAntiLureProtection() || (params.isAntiLureProtection() && groundItem.getOwnership() == OWNERSHIP_SELF)); - List groundItems = getGroundItems().values().stream() - .filter(filter) - .collect(Collectors.toList()); - if (groundItems.size() < params.getMinItems()) return false; - if (params.isDelayedLooting()) { - // Get the ground item with the lowest despawn time - GroundItem item = groundItems.stream().min(Comparator.comparingInt(Rs2GroundItem::calculateDespawnTime)).orElse(null); - assert item != null; - if (calculateDespawnTime(item) > 150) return false; + + private static Predicate baseRangeAndOwnershipFilter(LootingParameters params) { + final WorldPoint me = Microbot.getClient().getLocalPlayer().getWorldLocation(); + final boolean anti = params.isAntiLureProtection(); + return gi -> + gi.getLocation().distanceTo(me) < params.getRange() && + (!anti || gi.getOwnership() == OWNERSHIP_SELF); + } + + private static boolean passesIgnoredNames(GroundItem gi, Set ignoredLower) { + if (ignoredLower == null || ignoredLower.isEmpty()) return true; + final String name = gi.getName() == null ? "" : gi.getName().trim().toLowerCase(); + for (String needle : ignoredLower) { + if (name.contains(needle)) return false; + } + return true; + } + + private static boolean ensureSpaceFor(GroundItem gi, LootingParameters params) { + if (Rs2Inventory.emptySlotCount() > params.getMinInvSlots()) { + return true; } - return runWhilePaused(() -> { - for (GroundItem groundItem : groundItems) { - if (groundItem.getQuantity() < params.getMinItems()) continue; - if (params.getIgnoredNames() != null && Arrays.stream(params.getIgnoredNames()).anyMatch(name -> groundItem.getName().trim().toLowerCase().contains(name.trim().toLowerCase()))) continue; - if (Rs2Inventory.emptySlotCount() < params.getMinInvSlots()) return true; - coreLoot(groundItem); + if (params.isEatFoodForSpace() && !canTakeGroundItem(gi) && !Rs2Inventory.getInventoryFood().isEmpty()) { + if (Rs2Player.eatAt(100)) { + Rs2Player.waitForAnimation(); } - return validateLoot(filter); - }); + } + return canTakeGroundItem(gi); } - public static boolean lootItemsBasedOnNames(LootingParameters params) { - final Predicate filter = groundItem -> - groundItem.getLocation().distanceTo(Microbot.getClient().getLocalPlayer().getWorldLocation()) < params.getRange() && - (!params.isAntiLureProtection() || (params.isAntiLureProtection() && groundItem.getOwnership() == OWNERSHIP_SELF)) && - Arrays.stream(params.getNames()).anyMatch(name -> groundItem.getName().trim().toLowerCase().contains(name.trim().toLowerCase())); - List groundItems = getGroundItems().values().stream() - .filter(filter) + private static boolean lootWithFilter( + LootingParameters params, + Predicate itemPredicate, + Set ignoredLower + ) { + final Predicate base = baseRangeAndOwnershipFilter(params); + final Predicate combined = base.and(itemPredicate); + + final List groundItems = getGroundItems().values().stream() + .filter(combined) .collect(Collectors.toList()); + if (groundItems.size() < params.getMinItems()) return false; + if (params.isDelayedLooting()) { - // Get the ground item with the lowest despawn time - GroundItem item = groundItems.stream().min(Comparator.comparingInt(Rs2GroundItem::calculateDespawnTime)).orElse(null); - assert item != null; - if (calculateDespawnTime(item) > 150) return false; + final GroundItem soonest = groundItems.stream() + .min(Comparator.comparingInt(Rs2GroundItem::calculateDespawnTime)) + .orElse(null); + if (soonest == null) return false; + if (calculateDespawnTime(soonest) > DESPAWN_DELAY_THRESHOLD_TICKS) return false; } return runWhilePaused(() -> { - for (GroundItem groundItem : groundItems) { - if (groundItem.getQuantity() < params.getMinQuantity()) continue; - if (Rs2Inventory.emptySlotCount() <= params.getMinInvSlots()) return true; - coreLoot(groundItem); + for (GroundItem gi : groundItems) { + if (gi.getQuantity() < params.getMinQuantity()) continue; + if (!passesIgnoredNames(gi, ignoredLower)) continue; + if (!ensureSpaceFor(gi, params)) continue; + coreLoot(gi); } - return validateLoot(filter); + return validateLoot(combined); }); } - /** - * Loots items based on their location and item ID. - * @param location - * @param itemId - * @return - */ - public static boolean lootItemsBasedOnLocation(WorldPoint location, int itemId) { - final Predicate filter = groundItem -> - groundItem.getLocation().equals(location) && groundItem.getItemId() == itemId; + private static Set toLowerTrimmedSet(String[] arr) { + if (arr == null || arr.length == 0) return Collections.emptySet(); + Set out = new HashSet<>(arr.length); + for (String s : arr) { + if (s != null) { + final String t = s.trim().toLowerCase(); + if (!t.isEmpty()) out.add(t); + } + } + return out; + } - List groundItems = getGroundItems().values().stream() - .filter(filter) - .collect(Collectors.toList()); - return runWhilePaused(() -> { - for (GroundItem groundItem : groundItems) { - coreLoot(groundItem); + public static boolean lootItemBasedOnValue(LootingParameters params) { + Predicate byValue = gi -> { + final int qty = Math.max(1, gi.getQuantity()); + final int price = gi.getGePrice(); + return price > params.getMinValue() && (price / qty) < params.getMaxValue(); + }; + + final Set ignoredLower = toLowerTrimmedSet(params.getIgnoredNames()); + return lootWithFilter(params, byValue, ignoredLower); + } + + public static boolean lootItemsBasedOnNames(LootingParameters params) { + final Set needles = toLowerTrimmedSet(params.getNames()); + if (needles.isEmpty()) return false; + + Predicate byNames = gi -> { + final String n = gi.getName() == null ? "" : gi.getName().trim().toLowerCase(); + for (String needle : needles) { + if (n.contains(needle)) return true; } - return validateLoot(filter); - }); + return false; + }; + + return lootWithFilter(params, byNames, /*ignoredLower*/ null); } - // Loot untradables public static boolean lootUntradables(LootingParameters params) { - final Predicate filter = groundItem -> - groundItem.getLocation().distanceTo(Microbot.getClient().getLocalPlayer().getWorldLocation()) < params.getRange() && - (!params.isAntiLureProtection() || (params.isAntiLureProtection() && groundItem.getOwnership() == OWNERSHIP_SELF)) && - !groundItem.isTradeable() && - groundItem.getId() != ItemID.COINS_995; - List groundItems = getGroundItems().values().stream() - .filter(filter) - .collect(Collectors.toList()); - if (groundItems.size() < params.getMinItems()) return false; - if (params.isDelayedLooting()) { - // Get the ground item with the lowest despawn time - GroundItem item = groundItems.stream().min(Comparator.comparingInt(Rs2GroundItem::calculateDespawnTime)).orElse(null); - assert item != null; - if (calculateDespawnTime(item) > 150) return false; - } + Predicate untradables = gi -> + !gi.isTradeable() && gi.getId() != ItemID.COINS_995; - return runWhilePaused(() -> { - for (GroundItem groundItem : groundItems) { - if (groundItem.getQuantity() < params.getMinQuantity()) continue; - if (Rs2Inventory.emptySlotCount() <= params.getMinInvSlots()) return true; - coreLoot(groundItem); - } - return validateLoot(filter); - }); + return lootWithFilter(params, untradables, /*ignoredLower*/ null); } - // Loot coins public static boolean lootCoins(LootingParameters params) { + Predicate coins = gi -> gi.getId() == ItemID.COINS_995; + return lootWithFilter(params, coins, /*ignoredLower*/ null); + } + + + /** + * Loots items based on their location and item ID. + * @param location + * @param itemId + * @return + */ + public static boolean lootItemsBasedOnLocation(WorldPoint location, int itemId) { final Predicate filter = groundItem -> - groundItem.getLocation().distanceTo(Microbot.getClient().getLocalPlayer().getWorldLocation()) < params.getRange() && - (!params.isAntiLureProtection() || (params.isAntiLureProtection() && groundItem.getOwnership() == OWNERSHIP_SELF)) && - groundItem.getId() == ItemID.COINS_995; + groundItem.getLocation().equals(location) && groundItem.getItemId() == itemId; + List groundItems = getGroundItems().values().stream() .filter(filter) .collect(Collectors.toList()); - if (groundItems.size() < params.getMinItems()) return false; - if (params.isDelayedLooting()) { - // Get the ground item with the lowest despawn time - GroundItem item = groundItems.stream().min(Comparator.comparingInt(Rs2GroundItem::calculateDespawnTime)).orElse(null); - assert item != null; - if (calculateDespawnTime(item) > 150) return false; - } return runWhilePaused(() -> { for (GroundItem groundItem : groundItems) { - if (groundItem.getQuantity() < params.getMinQuantity()) continue; - if (Rs2Inventory.emptySlotCount() <= params.getMinInvSlots()) return true; coreLoot(groundItem); } return validateLoot(filter); @@ -420,6 +431,7 @@ public static boolean lootCoins(LootingParameters params) { } + private static boolean hasLootableItems(Predicate filter) { List groundItems = getGroundItems().values().stream() .filter(filter) @@ -539,4 +551,16 @@ public static boolean loot(final WorldPoint worldPoint, final int itemId) public static Table getGroundItems() { return GroundItemsPlugin.getCollectedGroundItems(); } + + private static boolean canTakeGroundItem(GroundItem groundItem) { + int maxQuantity = groundItem.isStackable() ? 1 : groundItem.getQuantity(); + int availableSlots = Rs2Inventory.emptySlotCount(); + int quantity = Math.min(maxQuantity, availableSlots); + + if (quantity == 0 && groundItem.isStackable()) { + return Rs2Inventory.hasItem(groundItem.getId()); + } + + return quantity > 0; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/magic/Rs2Magic.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/magic/Rs2Magic.java index 2b3818d8d8f..1d58c568c4f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/magic/Rs2Magic.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/magic/Rs2Magic.java @@ -9,6 +9,7 @@ import net.runelite.client.plugins.microbot.globval.enums.InterfaceTab; import net.runelite.client.plugins.microbot.util.Global; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; +import net.runelite.client.plugins.microbot.util.cache.Rs2SkillCache; import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; @@ -32,8 +33,8 @@ import java.awt.*; import java.awt.event.KeyEvent; -import java.util.List; import java.util.*; +import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -58,9 +59,16 @@ public static boolean oneTimeSpellBookCheck() { return true; } - public static boolean canCast(Spell spell) { - return canCast(spell.getMagicAction()); - } + /** + * Determines if the player can cast the given spell by verifying the + * player's active spellbook and the availability of the required runes. + * + * @param spell the spell that needs to be checked for rune availability + * @return true if the player has the correct spellbook, high enough level and enough runes to cast the spell + */ + public static boolean canCast(Spell spell) { + return spell.hasRequirements() && hasRequiredRunes(spell); + } /** * Checks if a specific spell can be cast diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/magic/Rs2Spellbook.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/magic/Rs2Spellbook.java index e1b985310d7..c88f8a7783f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/magic/Rs2Spellbook.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/magic/Rs2Spellbook.java @@ -75,7 +75,7 @@ public enum Rs2Spellbook { * Located at Dark Altar north of Arceuus - requires speaking to Tyss NPC */ ARCEUUS(3, - new WorldPoint(1694, 3878, 0), // Dark Altar location north of Arceuus + new WorldPoint(1712, 3883, 0), // Dark Altar location north of Arceuus NpcID.ARCEUUS_DARKGUARDIAN, // Tyss NPC ID null, "Dark Altar north of Arceuus - speak to Tyss to access Arceuus spellbook"); @@ -205,6 +205,7 @@ public boolean switchTo() { case MODERN: // For switching to MODERN, try altar method first (most reliable) // We determine which altar to use based on current spellbook + if(Rs2Magic.getSpellbook().equals(Rs2Spellbook.ARCEUUS)) return switchViaNpc(); //Needed for swapping from Arceuus to Modern. return switchViaAltar(); case ANCIENT: diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/misc/Rs2UiHelper.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/misc/Rs2UiHelper.java index d2638f98d48..aa5ea9d875d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/misc/Rs2UiHelper.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/misc/Rs2UiHelper.java @@ -3,9 +3,11 @@ import java.util.concurrent.ThreadLocalRandom; import java.util.regex.Matcher; import java.util.regex.Pattern; +import lombok.extern.slf4j.Slf4j; import net.runelite.api.Point; import net.runelite.api.*; import net.runelite.api.coords.LocalPoint; +import net.runelite.client.RuneLiteProperties; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; import net.runelite.client.plugins.microbot.util.math.Rs2Random; @@ -13,6 +15,7 @@ import java.awt.*; +@Slf4j public class Rs2UiHelper { public static final Pattern COL_TAG_PATTERN = Pattern.compile("]+>|"); @@ -68,7 +71,7 @@ public static boolean isMouseWithinRectangle(Rectangle rectangle) { public static Rectangle getActorClickbox(Actor actor) { LocalPoint lp = actor.getLocalLocation(); if (lp == null) { - Microbot.log("LocalPoint is null"); + log.warn("LocalPoint is null"); return getDefaultRectangle(); } @@ -156,4 +159,69 @@ public static int extractNumber(String input) { // Return -1 if no number is found return -1; } + + /** + * Check if the current client version is compatible with the required minimum version + */ + public static boolean isClientVersionCompatible(String minClientVersion) { + if (minClientVersion == null || minClientVersion.isEmpty()) { + return true; + } + + String currentVersion = RuneLiteProperties.getMicrobotVersion(); + if (currentVersion == null) { + log.warn("Unable to determine current Microbot version"); + return false; + } + + return compareVersions(currentVersion, minClientVersion) >= 0; + } + + /** + * Compare two version strings using semantic versioning with support for 4-part versions + * Supports formats like: 1.9.7, 1.9.7.1, 1.9.8, 1.9.8.1 + * @param version1 The first version to compare + * @param version2 The second version to compare + * @return -1 if version1 < version2, 0 if equal, 1 if version1 > version2 + */ + public static int compareVersions(String version1, String version2) { + if (version1 == null && version2 == null) return 0; + if (version1 == null) return -1; + if (version2 == null) return 1; + + // Split versions by dots and handle up to 4 parts (major.minor.patch.build) + String[] v1Parts = version1.split("\\."); + String[] v2Parts = version2.split("\\."); + + int maxLength = Math.max(v1Parts.length, v2Parts.length); + + for (int i = 0; i < maxLength; i++) { + int v1Part = i < v1Parts.length ? parseVersionPart(v1Parts[i]) : 0; + int v2Part = i < v2Parts.length ? parseVersionPart(v2Parts[i]) : 0; + + if (v1Part < v2Part) return -1; + if (v1Part > v2Part) return 1; + } + + return 0; + } + + /** + * Parse a version part, extracting only the numeric portion + */ + public static int parseVersionPart(String part) { + if (part == null || part.isEmpty()) return 0; + + StringBuilder numericPart = new StringBuilder(); + for (char c : part.toCharArray()) { + if (!Character.isDigit(c)) break; + numericPart.append(c); + } + + try { + return numericPart.length() > 0 ? Integer.parseInt(numericPart.toString()) : 0; + } catch (NumberFormatException e) { + return 0; + } + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/npc/Rs2Npc.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/npc/Rs2Npc.java index 9b330d161f5..aad28be8d7a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/npc/Rs2Npc.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/npc/Rs2Npc.java @@ -205,6 +205,8 @@ public static double getHealth(ActorModel npc) { return (double) ratio / (double) scale * 100; } + private static final Rs2NpcModel[] EMPTY_ARRAY = new Rs2NpcModel[0]; + /** * Retrieves a stream of NPCs filtered by a given condition. * @@ -216,88 +218,92 @@ public static double getHealth(ActorModel npc) { */ public static Stream getNpcs(Predicate predicate) { try { - // Defensive null checks for client and world view - if (Microbot.getClient() == null) { - log.warn("Client is null, returning empty NPC stream"); - return Stream.empty(); - } - - if (Microbot.getClient().getTopLevelWorldView() == null) { - log.warn("TopLevelWorldView is null, returning empty NPC stream"); - return Stream.empty(); - } - - if (Microbot.getClient().getTopLevelWorldView().npcs() == null) { - log.warn("NPCs collection is null, returning empty NPC stream"); - return Stream.empty(); - } - - if (Microbot.getClient().getLocalPlayer() == null) { - log.warn("Local player is null, returning empty NPC stream"); - return Stream.empty(); - } - - if (Microbot.getClient().getLocalPlayer().getLocalLocation() == null) { - log.warn("Local player location is null, returning empty NPC stream"); - return Stream.empty(); - } - - // Make local copies to avoid null issues during stream processing - final Stream npcStream = Microbot.getClient().getTopLevelWorldView().npcs().stream(); - final LocalPoint playerLocation = Microbot.getClient().getLocalPlayer().getLocalLocation(); - - // Safe predicate wrapper to prevent null issues - Predicate safePredicate = predicate != null ? predicate : (npc -> true); - List npcList = npcStream - .filter(Objects::nonNull) // Filter out null NPCs - .map(npc -> { - try { - return new Rs2NpcModel(npc); - } catch (Exception e) { - log.debug("Error creating Rs2NpcModel: {}", e.getMessage()); - return null; - } - }) - .filter(Objects::nonNull) // Filter out failed model creations - .filter(npcModel -> { - try { - // Additional safety checks for Rs2NpcModel - return npcModel.getName() != null && - npcModel.getLocalLocation() != null; - } catch (Exception e) { - log.debug("Error accessing Rs2NpcModel properties: {}", e.getMessage()); - return false; - } - }) - .filter(npcModel -> { - try { - return safePredicate.test(npcModel); - } catch (Exception e) { - log.debug("Error in predicate test: {}", e.getMessage()); - return false; - } - }) - .sorted(Comparator.comparingInt(value -> { - try { - if (value != null && value.getLocalLocation() != null && playerLocation != null) { - return value.getLocalLocation().distanceTo(playerLocation); + // Execute all game object access on client thread to prevent race conditions + Rs2NpcModel[] npcArray = Microbot.getClientThread().runOnClientThreadOptional(() -> { + // Defensive null checks for client and world view + if (Microbot.getClient() == null) { + log.warn("Client is null, returning empty NPC stream"); + return EMPTY_ARRAY; + } + + if (Microbot.getClient().getTopLevelWorldView() == null) { + log.warn("TopLevelWorldView is null, returning empty NPC stream"); + return EMPTY_ARRAY; + } + + if (Microbot.getClient().getTopLevelWorldView().npcs() == null) { + log.warn("NPCs collection is null, returning empty NPC stream"); + return EMPTY_ARRAY; + } + + if (Microbot.getClient().getLocalPlayer() == null) { + log.warn("Local player is null, returning empty NPC stream"); + return EMPTY_ARRAY; + } + + if (Microbot.getClient().getLocalPlayer().getLocalLocation() == null) { + log.warn("Local player location is null, returning empty NPC stream"); + return EMPTY_ARRAY; + } + + // Make local copies to avoid null issues during stream processing + final Stream npcStream = Microbot.getClient().getTopLevelWorldView().npcs().stream(); + final LocalPoint playerLocation = Microbot.getClient().getLocalPlayer().getLocalLocation(); + + // Safe predicate wrapper to prevent null issues + Predicate safePredicate = predicate != null ? predicate : (npc -> true); + return npcStream + .filter(Objects::nonNull) // Filter out null NPCs + .map(npc -> { + try { + return new Rs2NpcModel(npc); + } catch (Exception e) { + log.debug("Error creating Rs2NpcModel: {}", e.getMessage()); + return null; + } + }) + .filter(Objects::nonNull) // Filter out failed model creations + .filter(npcModel -> { + try { + // Additional safety checks for Rs2NpcModel + return npcModel.getName() != null && + npcModel.getLocalLocation() != null; + } catch (Exception e) { + log.debug("Error accessing Rs2NpcModel properties: {}", e.getMessage()); + return false; } - return Integer.MAX_VALUE; // Put problematic NPCs at the end - } catch (Exception e) { - log.debug("Error calculating distance: {}", e.getMessage()); - return Integer.MAX_VALUE; - } - })) - .collect(Collectors.toList()); - - return npcList.stream(); - + }) + .filter(npcModel -> { + try { + return safePredicate.test(npcModel); + } catch (Exception e) { + log.debug("Error in predicate test: {}", e.getMessage()); + return false; + } + }) + .sorted(Comparator.comparingInt(value -> { + try { + if (value != null && value.getLocalLocation() != null && playerLocation != null) { + return value.getLocalLocation().distanceTo(playerLocation); + } + return Integer.MAX_VALUE; // Put problematic NPCs at the end + } catch (Exception e) { + log.debug("Error calculating distance: {}", e.getMessage()); + return Integer.MAX_VALUE; + } + })) + .toArray(Rs2NpcModel[]::new); + }).orElse(EMPTY_ARRAY); + + // Convert array back to stream for API compatibility + return Arrays.stream(npcArray); + } catch (Exception e) { log.debug("Unexpected error in getNpcs: {}", e.getMessage(), e); return Stream.empty(); } } - + /** * Retrieves a stream of all NPCs in the game world. * diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/npc/Rs2NpcManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/npc/Rs2NpcManager.java index 984407bdd07..134bbd3bc56 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/npc/Rs2NpcManager.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/npc/Rs2NpcManager.java @@ -291,6 +291,7 @@ public static List getNpcLocations(String npcName) * @param avoidWilderness Whether to avoid locations in the Wilderness. */ public static MonsterLocation getClosestLocation(String npcName, int minClustering, boolean avoidWilderness) { + boolean originalUseBankItems = ShortestPathPlugin.getPathfinderConfig().isUseBankItems(); ShortestPathPlugin.getPathfinderConfig().setUseBankItems(true); Microbot.log(Level.INFO,"Finding closest location for: " + npcName); @@ -332,6 +333,7 @@ public static MonsterLocation getClosestLocation(String npcName, int minClusteri } Microbot.log(Level.INFO,"Closest location for " + npcName + ": " + closest.getLocationName()); + ShortestPathPlugin.getPathfinderConfig().setUseBankItems(originalUseBankItems); return closest; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/player/Rs2Player.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/player/Rs2Player.java index 5676321723a..bdbe6706f46 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/player/Rs2Player.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/player/Rs2Player.java @@ -77,6 +77,7 @@ public class Rs2Player { public static int antiPoisonTime = -1; public static int teleBlockTime = -1; public static int goadingTime = -1; + public static int moonlightTime = -1; public static Instant lastAnimationTime = null; private static final long COMBAT_TIMEOUT_MS = 10000; private static long lastCombatTime = 0; @@ -119,6 +120,10 @@ public static boolean hasGoadingActive() { return goadingTime > 0; } + public static boolean hasMoonlightActive() { + return moonlightTime > 0; + } + public static boolean hasAttackActive(int threshold) { return getBoostedSkillLevel(Skill.ATTACK) - threshold > getRealSkillLevel(Skill.ATTACK); } @@ -177,6 +182,9 @@ public static void handlePotionTimers(VarbitChanged event) { if (event.getVarbitId() == Varbits.BUFF_GOADING_POTION) { goadingTime = event.getValue(); } + if (event.getVarbitId() == Varbits.MOONLIGHT_POTION) { + moonlightTime = event.getValue(); + } if (event.getVarbitId() == Varbits.DIVINE_MAGIC) { divineMagicTime = event.getValue(); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/prayer/Rs2Prayer.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/prayer/Rs2Prayer.java index a700a61d4aa..f12ac73c16a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/prayer/Rs2Prayer.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/prayer/Rs2Prayer.java @@ -1,23 +1,25 @@ package net.runelite.client.plugins.microbot.util.prayer; +import lombok.extern.slf4j.Slf4j; import net.runelite.api.MenuAction; import net.runelite.api.Skill; import net.runelite.api.annotations.Component; import net.runelite.api.gameval.VarbitID; +import net.runelite.api.widgets.Widget; import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.globval.enums.InterfaceTab; import net.runelite.client.plugins.microbot.util.menu.NewMenuEntry; -import net.runelite.client.plugins.microbot.util.player.Rs2Player; +import net.runelite.client.plugins.microbot.util.misc.Rs2UiHelper; +import net.runelite.client.plugins.microbot.util.tabs.Rs2Tab; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; import java.awt.*; import java.util.Arrays; import java.util.stream.Stream; -import static net.runelite.api.Varbits.QUICK_PRAYER; -import static net.runelite.client.plugins.microbot.globval.VarbitIndices.SELECTED_QUICK_PRAYERS; -import static net.runelite.client.plugins.microbot.globval.VarbitValues.QUICK_PRAYER_ENABLED; import static net.runelite.client.plugins.microbot.util.Global.sleepUntil; +@Slf4j public class Rs2Prayer { @Component @@ -27,37 +29,135 @@ public class Rs2Prayer { @Component private static final int QUICK_PRAYER_ORB_COMPONENT_ID = 10485779; - public static void toggle(Rs2PrayerEnum name) { - if (!Rs2Player.hasPrayerPoints()) return; - Microbot.doInvoke(new NewMenuEntry(-1, name.getIndex(), MenuAction.CC_OP.getId(), 1,-1, "Activate"), new Rectangle(1, 1, Microbot.getClient().getCanvasWidth(), Microbot.getClient().getCanvasHeight())); - // Rs2Reflection.invokeMenu(-1, name.getIndex(), MenuAction.CC_OP.getId(), 1,-1, "Activate", "", -1, -1); - } - - public static void toggle(Rs2PrayerEnum name, boolean on) { - final int varBit = name.getVarbit(); - if(!on) { - if (Microbot.getVarbitValue(varBit) == 0) return; - } else { - if (Microbot.getVarbitValue(varBit) == 1) return; - } - - if (!Rs2Player.hasPrayerPoints()) return; - - Microbot.doInvoke(new NewMenuEntry(-1, name.getIndex(), MenuAction.CC_OP.getId(), 1,-1, "Activate"), new Rectangle(1, 1, Microbot.getClient().getCanvasWidth(), Microbot.getClient().getCanvasHeight())); - //Rs2Reflection.invokeMenu(-1, name.getIndex(), MenuAction.CC_OP.getId(), 1,-1, "Activate", "", -1, -1); - } + /** + * Toggles a prayer on or off. If the prayer is already in the desired state, no action is taken. + * + * @param prayer the prayer to toggle + */ + public static void toggle(Rs2PrayerEnum prayer) { + if (isOutOfPrayer()) return; + invokePrayer(prayer, false); + } + + /** + * Toggles a prayer to a specific state (on or off). + * + * @param prayer the prayer to toggle + * @param on true to enable the prayer, false to disable it + * @return true if the prayer is in the desired state after the operation, false otherwise + */ + public static boolean toggle(Rs2PrayerEnum prayer, boolean on) { + return toggle(prayer, on, false); // Default to not using the mouse to ensure compatibility with previous behavior + } + + /** + * Toggles a prayer to a specific state (on or off) with optional mouse control. + * If using mouse, will automatically switch to the prayer tab if not already active. + * + * @param prayer the prayer to toggle + * @param on true to enable the prayer, false to disable it + * @param withMouse true to use mouse + * @return true if the prayer is in the desired state after the operation, false otherwise + */ + public static boolean toggle(Rs2PrayerEnum prayer, boolean on, boolean withMouse) { + if (isOutOfPrayer()) return false; + + if (isPrayerActive(prayer) == on) return true; + + if (withMouse && Rs2Tab.getCurrentTab() != InterfaceTab.PRAYER) + { + Rs2Tab.switchTo(InterfaceTab.PRAYER); + } + + invokePrayer(prayer, withMouse); + + return sleepUntil(() -> isPrayerActive(prayer) == on, 10_000); + } + + /** + * Invokes a prayer action + * Creates a menu entry and executes it with the appropriate bounds. + * + * @param prayer the prayer to invoke + * @param withMouse true to use mouse clicks with prayer bounds + */ + private static void invokePrayer(Rs2PrayerEnum prayer, boolean withMouse) { + NewMenuEntry menuEntry = new NewMenuEntry( + -1, + prayer.getIndex(), + MenuAction.CC_OP.getId(), + 1, + -1, + "Activate" + ); + + Rectangle prayerBounds = withMouse ? getPrayerBounds(prayer) : Rs2UiHelper.getDefaultRectangle(); + + Microbot.doInvoke(menuEntry, prayerBounds); + } + + /** + * Gets the bounds of a specific prayer widget + * Returns a default rectangle if the widget is not found or has invalid bounds. + * + * @param prayer the prayer to get bounds for + * @return the bounds of the prayer widget, or default rectangle if not available + */ + private static Rectangle getPrayerBounds(Rs2PrayerEnum prayer) { + Widget prayerWidget = Rs2Widget.getWidget(prayer.getIndex()); + if (prayerWidget == null) { + log.warn("Prayer widget not found: {}", prayer.getName()); + return Rs2UiHelper.getDefaultRectangle(); // return a default rectangle if the widget is not found + } + + Rectangle bounds = prayerWidget.getBounds(); + if (bounds == null || bounds.width <= 0 || bounds.height <= 0) { + log.warn("Invalid prayer bounds for: {}", prayer.getName()); + return Rs2UiHelper.getDefaultRectangle(); // return a default rectangle if bounds are invalid + } + + return bounds; + } + /** + * Checks if a specific prayer is set as a quick prayer. + * Quick prayers are prayers that can be activated/deactivated with the quick prayer orb. + * + * @param prayer the prayer to check + * @return true if the prayer is set as a quick prayer, false otherwise + */ public static boolean isQuickPrayerSet(Rs2PrayerEnum prayer) { - int selectedQuickPrayersVarbit = Microbot.getVarbitValue(SELECTED_QUICK_PRAYERS); - return (selectedQuickPrayersVarbit & (1 << prayer.getQuickPrayerIndex())) != 0; - } - public static boolean isPrayerActive(Rs2PrayerEnum name) { - final int varBit = name.getVarbit(); - return Microbot.getVarbitValue(varBit) == 1; + final int selectedQuickPrayers = Microbot.getVarbitValue(VarbitID.QUICKPRAYER_SELECTED); + return (selectedQuickPrayers & (1 << prayer.getQuickPrayerIndex())) != 0; } + /** + * Checks if the player has any quick prayers configured. + * + * @return true if at least one quick prayer is set, false if no quick prayers are configured + */ + public static boolean hasAnyQuickPrayers() { + return Microbot.getVarbitValue(VarbitID.QUICKPRAYER_SELECTED) > 0; + } + + /** + * Checks if a specific prayer is currently active. + * + * @param prayer the prayer to check + * @return true if the prayer is currently active, false otherwise + */ + public static boolean isPrayerActive(Rs2PrayerEnum prayer) { + return Microbot.getVarbitValue(prayer.getVarbit()) == 1; + } + + /** + * Checks if quick prayers are currently enabled/active. + * When quick prayers are active, all configured quick prayers are turned on. + * + * @return true if quick prayers are currently active, false otherwise + */ public static boolean isQuickPrayerEnabled() { - return Microbot.getVarbitValue(QUICK_PRAYER) == QUICK_PRAYER_ENABLED.getValue(); + return Microbot.getVarbitValue(VarbitID.QUICKPRAYER_ACTIVE) == 1; } public static boolean setQuickPrayers(Rs2PrayerEnum[] prayers) { @@ -77,28 +177,151 @@ public static boolean setQuickPrayers(Rs2PrayerEnum[] prayers) { return true; } - public static boolean toggleQuickPrayer(boolean on) { - boolean bit = Microbot.getVarbitValue(QUICK_PRAYER) == QUICK_PRAYER_ENABLED.getValue(); + /** + * Toggles quick prayers on or off + * Does nothing if the player is out of prayer points or has no quick prayers configured. + */ + public static void toggleQuickPrayer() { + if (isOutOfPrayer() || !hasAnyQuickPrayers()) return; + invokeQuickPrayer(false); + } + + /** + * Toggles quick prayers to a specific state + * + * @param on true to enable quick prayers, false to disable them + * @return true if quick prayers are in the desired state after the operation, false otherwise + */ + public static boolean toggleQuickPrayer(boolean on) { + return toggleQuickPrayer(on, false); // Default to not using the mouse to ensure compatibility with previous behavior + } - boolean isQuickPrayerSet = Microbot.getVarbitValue(4102) > 0; - if (!isQuickPrayerSet) return false; + /** + * Toggles quick prayers to a specific state with optional mouse control. + * Quick prayers allow activating/deactivating multiple configured prayers at once. + * + * @param on true to enable quick prayers, false to disable them + * @param withMouse true to use mouse + * @return true if quick prayers are in the desired state after the operation, false otherwise + */ + public static boolean toggleQuickPrayer(boolean on, boolean withMouse) { + if (!hasAnyQuickPrayers()) return false; + if (isOutOfPrayer()) return false; + if (on == isQuickPrayerEnabled()) return true; - if (Rs2Widget.isHidden(QUICK_PRAYER_ORB_COMPONENT_ID)) return false; - if (!Rs2Player.hasPrayerPoints()) return false; - if (on == bit) return true; + boolean wasActive = isQuickPrayerEnabled(); - Microbot.doInvoke(new NewMenuEntry(-1, QUICK_PRAYER_ORB_COMPONENT_ID, MenuAction.CC_OP.getId(), 1, -1, "Quick-prayers"), new Rectangle(1, 1, Microbot.getClient().getCanvasWidth(), Microbot.getClient().getCanvasHeight())); - return true; + invokeQuickPrayer(withMouse); + + return sleepUntil(() -> isQuickPrayerEnabled() != wasActive, 10_000); } + /** + * Invokes the quick prayer orb action + * Creates a menu entry for the quick prayer orb and executes it. + * + * @param withMouse true to use mouse with orb bounds + */ + private static void invokeQuickPrayer(boolean withMouse) { + NewMenuEntry entry = new NewMenuEntry( + -1, + QUICK_PRAYER_ORB_COMPONENT_ID, + MenuAction.CC_OP.getId(), + 1, + -1, + "Quick-prayers" + ); + + Microbot.doInvoke(entry, withMouse ? getQuickPrayerOrbBounds() : Rs2UiHelper.getDefaultRectangle()); + } + + /** + * Gets the bounds of the quick prayer orb widget + * The quick prayer orb is used to toggle all configured quick prayers at once. + * Returns a default rectangle if the widget is not found or has invalid bounds. + * + * @return the bounds of the quick prayer orb widget, or default rectangle if not available + */ + private static Rectangle getQuickPrayerOrbBounds() { + Widget quickPrayerOrbWidget = Rs2Widget.getWidget(QUICK_PRAYER_ORB_COMPONENT_ID); + if (quickPrayerOrbWidget == null) { + log.warn("Quick prayer orb widget not found"); + return Rs2UiHelper.getDefaultRectangle(); // return a default rectangle if the widget is not found + } + + Rectangle bounds = quickPrayerOrbWidget.getBounds(); + if (bounds == null || bounds.width <= 0 || bounds.height <= 0) { + log.warn("Invalid quick prayer orb bounds"); + return Rs2UiHelper.getDefaultRectangle(); // return a default rectangle if bounds are invalid + } + + return bounds; + } + + /** + * Checks if the player has run out of prayer points. + * When out of prayer points, prayers cannot be activated and existing prayers will be disabled. + * + * @return true if the player's current prayer level is 0 or below, false otherwise + */ public static boolean isOutOfPrayer() { return Microbot.getClient().getBoostedSkillLevel(Skill.PRAYER) <= 0; } + /** * Disables all active prayers. */ public static void disableAllPrayers() { - Arrays.stream(Rs2PrayerEnum.values()).filter(Rs2Prayer::isPrayerActive).forEach(Rs2Prayer::toggle); + disableAllPrayers(false); + } + + /** + * Disables all active prayers. + * @param withMouse whether to use mouse clicks for disabling prayers + */ + public static void disableAllPrayers(boolean withMouse) { + Arrays.stream(Rs2PrayerEnum.values()) + .filter(Rs2Prayer::isPrayerActive) + .forEach(prayer -> Rs2Prayer.toggle(prayer, false, withMouse)); + } + + /** + * Disables all active prayers except the ones specified in the array. + * @param prayersToKeep array of prayers to keep active + */ + public static void disableAllPrayersExcept(Rs2PrayerEnum[] prayersToKeep) { + disableAllPrayersExcept(prayersToKeep, false); + } + + /** + * Disables all active prayers except the ones specified in the array. + * @param prayersToKeep array of prayers to keep active + * @param withMouse whether to use mouse clicks for disabling prayers + */ + public static void disableAllPrayersExcept(Rs2PrayerEnum[] prayersToKeep, boolean withMouse) { + Arrays.stream(Rs2PrayerEnum.values()) + .filter(Rs2Prayer::isPrayerActive) + .filter(prayer -> !Arrays.asList(prayersToKeep).contains(prayer)) + .forEach(prayer -> Rs2Prayer.toggle(prayer, false, withMouse)); + } + + /** + * Enables the specified prayers. + * @param prayers array of prayers to enable + */ + public static void enablePrayers(Rs2PrayerEnum[] prayers) { + enablePrayers(prayers, false); + } + + /** + * Enables the specified prayers. + * @param prayers array of prayers to enable + * @param withMouse whether to use mouse clicks for enabling prayers + */ + public static void enablePrayers(Rs2PrayerEnum[] prayers, boolean withMouse) { + Arrays.stream(prayers) + .filter(prayer -> !Rs2Prayer.isPrayerActive(prayer)) + .forEach(prayer -> Rs2Prayer.toggle(prayer, true, withMouse)); } public static Rs2PrayerEnum getActiveProtectionPrayer() { @@ -149,7 +372,7 @@ public static boolean isMeleePrayerActive() { public static Rs2PrayerEnum getBestMagePrayer() { int prayerLevel = Microbot.getClient().getRealSkillLevel(Skill.PRAYER); - boolean auguryUnlocked = Microbot.getVarbitValue(5452) == 1; + boolean auguryUnlocked = isAuguryUnlocked(); if (auguryUnlocked && prayerLevel >= Rs2PrayerEnum.AUGURY.getLevel()) return Rs2PrayerEnum.AUGURY; @@ -165,7 +388,7 @@ public static Rs2PrayerEnum getBestMagePrayer() { public static Rs2PrayerEnum getBestRangePrayer() { int prayerLevel = Microbot.getClient().getRealSkillLevel(Skill.PRAYER); - boolean rigourUnlocked = Microbot.getVarbitValue(5451) == 1; + boolean rigourUnlocked = isRigourUnlocked(); if (rigourUnlocked && prayerLevel >= Rs2PrayerEnum.RIGOUR.getLevel()) return Rs2PrayerEnum.RIGOUR; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/slayer/Rs2Slayer.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/slayer/Rs2Slayer.java index 4002a4d536e..bb9accc0d61 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/slayer/Rs2Slayer.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/slayer/Rs2Slayer.java @@ -1,7 +1,7 @@ package net.runelite.client.plugins.microbot.util.slayer; -import net.runelite.api.EnumID; import net.runelite.api.coords.WorldPoint; +import net.runelite.api.gameval.DBTableID; import net.runelite.api.gameval.ItemID; import net.runelite.api.gameval.VarPlayerID; import net.runelite.client.plugins.microbot.Microbot; @@ -65,8 +65,42 @@ public static String getSlayerTask() { if (taskId == 0) { return null; } - return Microbot.getEnum(693) - .getStringValue(taskId); + + int taskDBRow; + if (taskId == 98 /* Bosses, from [proc,helper_slayer_current_assignment] */) + { + var bossRows = Microbot.getDBRowsByValue( + DBTableID.SlayerTaskSublist.ID, + DBTableID.SlayerTaskSublist.COL_SUBTABLE_ID, + 0, + taskId); + + if (bossRows.isEmpty()) + { + return null; + } + taskDBRow = (Integer) Microbot.getDBTableField( + bossRows.get(0), + DBTableID.SlayerTaskSublist.COL_TASK, 0)[0]; + } + else + { + var taskRows = Microbot.getDBRowsByValue( + DBTableID.SlayerTask.ID, + DBTableID.SlayerTask.COL_ID, + 0, + taskId); + if (taskRows.isEmpty()) + { + return null; + } + taskDBRow = taskRows.get(0); + } + + return (String) Microbot.getDBTableField( + taskDBRow, + DBTableID.SlayerTask.COL_NAME_UPPERCASE, + 0)[0]; } /** diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/Rs2Walker.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/Rs2Walker.java index 52b2a874679..933737a6a9d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/Rs2Walker.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/Rs2Walker.java @@ -99,6 +99,12 @@ public class Rs2Walker { public static boolean disableTeleports = false; + // Trapdoor and manhole mappings for open/closed states + private static final Map OPEN_TO_CLOSED_MAPPINGS = Map.of( + 1581, 1579, // open trapdoor -> closed trapdoor + 882, 881 // open manhole -> closed manhole + ); + public static boolean walkTo(int x, int y, int plane) { return walkTo(x, y, plane, config.reachedDistance()); } @@ -1474,12 +1480,6 @@ private static boolean handleTransports(List path, int indexOfStartP } } - if (handleTrapdoor(transport)) { - sleepUntil(() -> !Rs2Player.isAnimating()); - sleepUntilTrue(() -> Rs2Player.getWorldLocation().distanceTo(transport.getDestination()) < 10); - break; - } - if (transport.getType() == TransportType.CANOE) { if (handleCanoe(transport)) { sleep(600 * 2); // wait 2 extra ticks before walking @@ -1558,7 +1558,16 @@ private static boolean handleTransports(List path, int indexOfStartP if (transport.getObjectId() <= 0) break; - List objects = Rs2GameObject.getAll(o -> o.getId() == transport.getObjectId(), transport.getOrigin(), 10).stream() + // Use class-level constants for object ID mapping + List objectIdsToSearch = new ArrayList<>(); + objectIdsToSearch.add(transport.getObjectId()); + + // If this transport is for an open trapdoor, also search for the closed version + if (OPEN_TO_CLOSED_MAPPINGS.containsKey(transport.getObjectId())) { + objectIdsToSearch.add(OPEN_TO_CLOSED_MAPPINGS.get(transport.getObjectId())); + } + + List objects = Rs2GameObject.getAll(o -> objectIdsToSearch.contains(o.getId()), transport.getOrigin(), 10).stream() .sorted(Comparator.comparingInt(o -> o.getWorldLocation().distanceTo(transport.getOrigin()))) .collect(Collectors.toList()); @@ -1569,7 +1578,8 @@ private static boolean handleTransports(List path, int indexOfStartP .min(Comparator.comparing(o -> ((TileObject) o).getWorldLocation().distanceTo(transport.getOrigin())) .thenComparing(o -> ((TileObject) o).getWorldLocation().distanceTo(transport.getDestination()))).orElse(null); } - if (object != null && object.getId() == transport.getObjectId()) { + + if (object != null) { System.out.println("Object Type: " + Rs2GameObject.getObjectType(object)); if (!(object instanceof GroundObject)) { @@ -1616,6 +1626,27 @@ private static void handleObject(Transport transport, TileObject tileObject) { } private static boolean handleObjectExceptions(Transport transport, TileObject tileObject) { + for (Map.Entry entry : OPEN_TO_CLOSED_MAPPINGS.entrySet()) { + final int closedTrapdoorId = entry.getKey(); + final int openTrapdoorId = entry.getValue(); + + if (transport.getObjectId() == openTrapdoorId) { + if (tileObject.getId() == closedTrapdoorId) { + Rs2GameObject.interact(tileObject, "Open"); + sleepUntil(() -> Rs2GameObject.exists(openTrapdoorId)); + TileObject openTrapdoor = Rs2GameObject.getAll(o -> o.getId() == openTrapdoorId, tileObject.getWorldLocation(), 10).stream().findFirst().orElse(null); + if (openTrapdoor != null) { + Rs2GameObject.interact(openTrapdoor, transport.getAction()); + } + } else if (tileObject.getId() == openTrapdoorId) { + Rs2GameObject.interact(tileObject, transport.getAction()); + } + sleepUntil(() -> !Rs2Player.isAnimating()); + sleepUntilTrue(() -> Rs2Player.getWorldLocation().distanceTo(transport.getDestination()) < OFFSET); + return true; + } + } + //Al kharid broken wall will animate once and then stop and then animate again if (tileObject.getId() == ObjectID.KHARID_POSHWALL_TOPLESS || tileObject.getId() == ObjectID.KHARID_BIGWINDOW) { Rs2Player.waitForAnimation(); @@ -1734,6 +1765,13 @@ private static boolean handleObjectExceptions(Transport transport, TileObject ti Rs2Player.waitForAnimation(600 * 4); return true; } + + if (tileObject.getId() == ObjectID.AERIAL_FISHING_BOAT) { + Rs2Dialogue.sleepUntilSelectAnOption(); + Rs2Dialogue.clickOption(transport.getDisplayInfo(), true); + sleepUntil(() -> Rs2Player.getWorldLocation().distanceTo2D(transport.getDestination()) < OFFSET, 10000); + return true; + } return false; } @@ -1910,25 +1948,6 @@ private static boolean handleWearableTeleports(Transport transport, int itemId) return false; } - private static boolean handleTrapdoor(Transport transport) { - Map trapdoors = new HashMap<>(); - trapdoors.put(1579, 1581); // closed trapdoor -> open trapdoor - trapdoors.put(881, 882); // closed manhole -> open manhole (used for varrock sewers) - - for (Map.Entry entry : trapdoors.entrySet()) { - int closedTrapdoorId = entry.getKey(); - int openTrapdoorId = entry.getValue(); - - if (transport.getObjectId() == openTrapdoorId) { - if (Rs2GameObject.interact(closedTrapdoorId, "Open")) { - sleepUntil(() -> Rs2GameObject.exists(openTrapdoorId)); - } - return Rs2GameObject.interact(openTrapdoorId, transport.getAction()); - } - } - return false; - } - /** * Checks if the player's current location is within the specified area defined by the given world points. * @@ -2757,15 +2776,15 @@ public static List getTransportsForDestination(WorldPoint destination ShortestPathPlugin.getPathfinderConfig().setUseBankItems(useBankItems); ShortestPathPlugin.getPathfinderConfig().refresh(destination); // Use target-based refresh List path = getWalkPath(destination); - + // Get path and extract relevant transports with filtering applied List transports = getTransportsForPath(path, 0, prefTransportType, true); - + // Log found transports for debugging transports.forEach(t -> log.debug("Transport found: " + t)); return transports; - + } finally { // Always restore original configuration ShortestPathPlugin.getPathfinderConfig().setUseBankItems(originalUseBankItems); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/vorkath/Teleport.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/vorkath/Teleport.java deleted file mode 100644 index f5fc99af940..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/vorkath/Teleport.java +++ /dev/null @@ -1,22 +0,0 @@ -package net.runelite.client.plugins.microbot.vorkath; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor -public enum Teleport { - VARROCK_TAB("Varrock teleport", "break"), - CRAFTING_CAPE("Crafting Cape", "teleport"), - JEWELLERY_BOX("Jewellery Box", "Teleport Menu"); - - - ;private final String itemName; - private final String action; - - @Override - public String toString() - { - return itemName; - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/vorkath/VorkathConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/vorkath/VorkathConfig.java deleted file mode 100644 index 6a7edd217e8..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/vorkath/VorkathConfig.java +++ /dev/null @@ -1,141 +0,0 @@ -package net.runelite.client.plugins.microbot.vorkath; - -import net.runelite.client.config.Config; -import net.runelite.client.config.ConfigGroup; -import net.runelite.client.config.ConfigItem; -import net.runelite.client.config.ConfigSection; - -@ConfigGroup("Vorkath Config") -public interface VorkathConfig extends Config { - @ConfigItem( - keyName = "guide", - name = "How to use", - description = "How to use this plugin", - position = 0 - ) - default String GUIDE() { - return "1.Use the equipment inventory plugin to setup your plugin and give it the name 'vorkath'\n" + - "More information about inventory plugin: https://github.com/dillydill123/inventory-setups?tab=readme-ov-file#creating-a-new-setup" + - "2.Make sure to start at a bank"; - } - - @ConfigSection( - name = "Loot", - description = "Loot", - position = 3 - ) - String lootSection = "Loot"; - - @ConfigSection( - name = "Teleports", - description = "Teleports", - position = 4 - ) - String teleportSection = "Teleports"; - - @ConfigSection( - name = "POH", - description = "POH", - position = 5 - ) - String pohSection = "POH"; - - @ConfigSection( - name = "Prayers", - description = "Prayers", - position = 6 - ) - String prayerSection = "Prayers"; - - @ConfigItem( - keyName = "Teleport", - name = "Teleport", - description = "Choose your mode of Teleport", - position = 0, - section = teleportSection - ) - default Teleport teleportMode() - { - return Teleport.VARROCK_TAB; - } - - @ConfigItem( - keyName = "Price of items to loot", - name = "Price of items to loot", - description = "Price of items to loot comma seperated", - position = 0, - section = lootSection - ) - default int priceOfItemsToLoot() - { - return 5000; - } - - @ConfigItem( - keyName = "SellItemsAtXKills", - name = "Sell items every X kills", - description = "Sell items every X kills", - position = 1, - section = lootSection - ) - default int SellItemsAtXKills() - { - return 15; - } - - @ConfigItem( - keyName = "ItemsToNotSell", - name = "Items to not sell", - description = "Items to not sell comma seperated", - position = 2, - section = lootSection - ) - default String ItemsToNotSell() - { - return "item1,item2"; - } - - @ConfigItem( - keyName = "KillsPerTrip", - name = "KC per Trip", - description = "kills per trip before banking(0 to disable it)", - position = 1, - section = lootSection - ) - default int KillsPerTrip() { - return 0; - } - - @ConfigItem( - keyName = "PohInRellekka", - name = "POH in Rellekka", - description = "Teleport to POH in Rellekka and exit via portal", - position = 0, - section = pohSection - ) - default boolean pohInRellekka() { - return false; - } - - @ConfigItem( - keyName = "RejuvenationPool", - name = "Rejuvenation Pool", - description = "Use POH rejuv Pool to restore stats", - position = 1, - section = pohSection - ) - default boolean rejuvinationPool() { - return false; - } - - @ConfigItem( - keyName = "Rigour", - name = "Rigour", - description = "Activate Rigour? (Make sure you have it unlocked and have 74 prayer!)", - position = 4, - section = prayerSection - ) - default boolean activateRigour() { - return false; - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/vorkath/VorkathOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/vorkath/VorkathOverlay.java deleted file mode 100644 index cb8d1e94145..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/vorkath/VorkathOverlay.java +++ /dev/null @@ -1,55 +0,0 @@ -package net.runelite.client.plugins.microbot.vorkath; - -import net.runelite.client.plugins.microbot.Microbot; -import net.runelite.client.ui.overlay.OverlayPanel; -import net.runelite.client.ui.overlay.OverlayPosition; -import net.runelite.client.ui.overlay.components.LineComponent; -import net.runelite.client.ui.overlay.components.TitleComponent; - -import javax.inject.Inject; -import java.awt.*; - -public class VorkathOverlay extends OverlayPanel { - private final VorkathPlugin plugin; - - @Inject - VorkathOverlay(VorkathPlugin plugin) - { - super(plugin); - this.plugin = plugin; - setPosition(OverlayPosition.TOP_LEFT); - setNaughty(); - } - @Override - public Dimension render(Graphics2D graphics) { - try { - panelComponent.setPreferredSize(new Dimension(300, 300)); - panelComponent.getChildren().add(TitleComponent.builder() - .text("Micro Vorkath V" + VorkathScript.version) - .color(Color.GREEN) - .build()); - - panelComponent.getChildren().add(LineComponent.builder().build()); - - panelComponent.getChildren().add(LineComponent.builder() - .left(Microbot.status) - .build()); - - panelComponent.getChildren().add(LineComponent.builder() - .left(plugin.vorkathScript.state.toString()) - .build()); - - panelComponent.getChildren().add(LineComponent.builder() - .left("Vorkath kills: " + plugin.vorkathScript.vorkathSessionKills) - .build()); - panelComponent.getChildren().add(LineComponent.builder() - .left("Vorkath kills until selling: " + plugin.vorkathScript.tempVorkathKills) - .build()); - - - } catch(Exception ex) { - System.out.println(ex.getMessage()); - } - return super.render(graphics); - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/vorkath/VorkathPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/vorkath/VorkathPlugin.java deleted file mode 100644 index 92f0fa2fff4..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/vorkath/VorkathPlugin.java +++ /dev/null @@ -1,71 +0,0 @@ -package net.runelite.client.plugins.microbot.vorkath; - -import com.google.inject.Provides; -import lombok.extern.slf4j.Slf4j; -import net.runelite.api.ChatMessageType; -import net.runelite.api.Client; -import net.runelite.api.coords.WorldPoint; -import net.runelite.api.events.ChatMessage; -import net.runelite.api.events.ProjectileMoved; -import net.runelite.client.config.ConfigManager; -import net.runelite.client.eventbus.Subscribe; -import net.runelite.client.plugins.Plugin; -import net.runelite.client.plugins.PluginDescriptor; -import net.runelite.client.ui.overlay.OverlayManager; - -import javax.inject.Inject; -import java.awt.*; - -@PluginDescriptor( - name = PluginDescriptor.Mocrosoft + "Vorkath", - description = "Microbot Vorkath plugin", - tags = {"vorkath", "microbot"}, - enabledByDefault = false -) -@Slf4j -public class VorkathPlugin extends Plugin { - @Inject - Client client; - @Inject - private VorkathConfig config; - @Provides - VorkathConfig provideConfig(ConfigManager configManager) { - return configManager.getConfig(VorkathConfig.class); - } - - @Inject - private OverlayManager overlayManager; - @Inject - private VorkathOverlay exampleOverlay; - - @Inject - public VorkathScript vorkathScript; - - @Override - protected void startUp() throws AWTException { - if (overlayManager != null) { - overlayManager.add(exampleOverlay); - } - vorkathScript.run(); - } - - protected void shutDown() { - vorkathScript.shutdown(); - overlayManager.remove(exampleOverlay); - } - - @Subscribe - public void onProjectileMoved(ProjectileMoved e) - { - if (e.getProjectile().getId() == vorkathScript.getAcidProjectileId()) { - vorkathScript.getAcidPools().add(WorldPoint.fromLocal(client, e.getPosition())); - } - } - - @Subscribe - public void onChatMessage(ChatMessage event) { - if (event.getType() == ChatMessageType.GAMEMESSAGE && event.getMessage().equalsIgnoreCase("oh dear, you are dead!")) { - vorkathScript.state = State.DEAD_WALK; - } - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/vorkath/VorkathScript.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/vorkath/VorkathScript.java deleted file mode 100644 index 9d15005f9e3..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/vorkath/VorkathScript.java +++ /dev/null @@ -1,669 +0,0 @@ -/** - * Credits to Jrod7938 - */ - -package net.runelite.client.plugins.microbot.vorkath; - -import javax.inject.Inject; -import lombok.Getter; -import net.runelite.api.EquipmentInventorySlot; -import net.runelite.api.NPC; -import net.runelite.api.gameval.NpcID; -import net.runelite.api.gameval.ObjectID; -import net.runelite.api.Projectile; -import net.runelite.api.Skill; -import net.runelite.api.TileObject; -import net.runelite.api.coords.LocalPoint; -import net.runelite.api.coords.WorldArea; -import net.runelite.api.coords.WorldPoint; -import net.runelite.client.plugins.loottracker.LootTrackerRecord; -import net.runelite.client.plugins.microbot.Microbot; -import net.runelite.client.plugins.microbot.Script; -import net.runelite.client.plugins.microbot.util.Rs2InventorySetup; -import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; -import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; -import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; -import net.runelite.client.plugins.microbot.util.grandexchange.Rs2GrandExchange; -import net.runelite.client.plugins.microbot.util.grounditem.LootingParameters; -import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; -import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.magic.Rs2Magic; -import net.runelite.client.plugins.microbot.util.magic.Rs2Spells; -import net.runelite.client.plugins.microbot.util.math.Rs2Random; -import net.runelite.client.plugins.microbot.util.misc.Rs2Potion; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; -import net.runelite.client.plugins.microbot.util.player.Rs2Player; -import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; -import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; -import net.runelite.client.plugins.microbot.util.tabs.Rs2Tab; -import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; -import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; -import net.runelite.client.plugins.skillcalculator.skills.MagicAction; - -import java.util.*; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - -import static net.runelite.client.plugins.microbot.Microbot.log; - - -enum State { - BANKING, - TELEPORT_TO_RELLEKKA, - WALK_TO_VORKATH_ISLAND, - WALK_TO_VORKATH, - PREPARE_FIGHT, - FIGHT_VORKATH, - ZOMBIE_SPAWN, - ACID, - LOOT_ITEMS, - TELEPORT_AWAY, - DEAD_WALK, - SELLING_ITEMS -} - -public class VorkathScript extends Script { - public static String version = "1.3.9"; - private final VorkathConfig config; - @Getter - public final int acidProjectileId = 1483; - final String ZOMBIFIED_SPAWN = "Zombified Spawn"; - private final int whiteProjectileId = 395; - private final int redProjectileId = 1481; - private final int acidRedProjectileId = 1482; - @Getter - private final HashSet acidPools = new HashSet<>(); - public int vorkathSessionKills = 0; - public int tempVorkathKills = 0; - public int kcPerTrip = 0; - State state = State.ZOMBIE_SPAWN; - Rs2NpcModel vorkath; - boolean hasEquipment = false; - boolean hasInventory = false; - boolean init = true; - String primaryBolts = ""; - Rs2InventorySetup rs2InventorySetup; - - private static void walkToCenter() { - Rs2Walker.walkFastLocal( - LocalPoint.fromScene(48, 58, Microbot.getClient().getTopLevelWorldView().getScene()) - ); - } - - private static void drinkPrayer() { - if ((Microbot.getClient().getBoostedSkillLevel(Skill.PRAYER) * 100) / Microbot.getClient().getRealSkillLevel(Skill.PRAYER) < Rs2Random.between(25, 30)) { - Rs2Inventory.interact(Rs2Potion.getPrayerPotionsVariants().toArray(String[]::new), "drink"); - } - } - - private void calculateState() { - if (Rs2Npc.getNpc(NpcID.VORKATH) != null) { - state = State.FIGHT_VORKATH; - return; - } - if (Rs2Npc.getNpc(NpcID.VORKATH_SLEEPING) != null) { - state = State.PREPARE_FIGHT; - return; - } - if (Rs2GameObject.findObjectById(ObjectID.UNGAEL_CRATER_ENTRANCE) != null) { - state = State.WALK_TO_VORKATH; - return; - } - if (isCloseToRelleka()) { - state = State.WALK_TO_VORKATH_ISLAND; - return; - } - if (Rs2Npc.getNpc(NpcID.TORFINN_COLLECT_UNGAEL) != null) { - state = State.WALK_TO_VORKATH; - } - } - - @Inject - public VorkathScript(VorkathConfig config) - { - this.config = config; - } - - public boolean run() { - Microbot.enableAutoRunOn = false; - Microbot.pauseAllScripts.compareAndSet(true, false); - init = true; - state = State.BANKING; - hasEquipment = false; - hasInventory = false; - tempVorkathKills = config.SellItemsAtXKills(); - Microbot.getSpecialAttackConfigs().setSpecialAttack(true); - - mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { - try { - if (!Microbot.isLoggedIn()) return; - if (!super.run()) return; - if (Rs2AntibanSettings.naturalMouse) { - Rs2AntibanSettings.naturalMouse = false; - log("Woox walk is not compatible with natural mouse."); - } - - if (init) { - rs2InventorySetup = new Rs2InventorySetup("vorkath", mainScheduledFuture); - if (!rs2InventorySetup.hasSpellBook()) { - Microbot.showMessage("Your spellbook is not matching the inventory setup."); - sleep(10000); - return; - } - calculateState(); - primaryBolts = Rs2Equipment.get(EquipmentInventorySlot.AMMO) != null ? Rs2Equipment.get(EquipmentInventorySlot.AMMO).getName() : ""; - } - - if (state == State.FIGHT_VORKATH && Rs2Equipment.get(EquipmentInventorySlot.AMMO) == null) { - leaveVorkath(); - } - - switch (state) { - case BANKING: - kcPerTrip = 0; - if (checkSellingItems(config)) return; - if (!init && Rs2Equipment.get(EquipmentInventorySlot.AMMO) == null) { - Microbot.showMessage("Out of ammo!"); - sleep(5000); - return; - } - if (isCloseToRelleka() && Rs2Inventory.count() >= 27) { - state = State.WALK_TO_VORKATH_ISLAND; - } - hasEquipment = rs2InventorySetup.doesEquipmentMatch(); - hasInventory = rs2InventorySetup.doesInventoryMatch(); - if (!Rs2Bank.isOpen()) { - Rs2Bank.walkToBankAndUseBank(); - } - if (!hasEquipment) { - hasEquipment = rs2InventorySetup.loadEquipment(); - } - if (!hasInventory && rs2InventorySetup.doesEquipmentMatch()) { - hasInventory = rs2InventorySetup.loadInventory(); - sleep(1000); - } - if (hasEquipment && hasInventory) { - healAndDrinkPrayerPotion(); - if (hasEquipment && hasInventory) { - state = State.TELEPORT_TO_RELLEKKA; - } - } - break; - case TELEPORT_TO_RELLEKKA: - if (!config.pohInRellekka() && !Rs2Inventory.hasItem("Rellekka teleport")) { - state = State.BANKING; - return; - } - if(config.pohInRellekka() && (!Rs2Magic.hasRequiredRunes(Rs2Spells.TELEPORT_TO_HOUSE) && !Rs2Inventory.hasItem("Teleport to house"))){ - state = State.BANKING; - return; - } - if (Rs2Bank.isOpen()) { - Rs2Bank.closeBank(); - sleepUntil(() -> !Rs2Bank.isOpen()); - } - if (!isCloseToRelleka()) { - - if(config.pohInRellekka()) { - teleToPoh(); - sleepUntil(() -> Rs2GameObject.findObjectById(4525) != null); - Rs2GameObject.interact(4525, "Enter"); - sleepUntil(this::isCloseToRelleka); - - } - else - { - Rs2Inventory.interact("Rellekka teleport", "break"); - sleepUntil(this::isCloseToRelleka); - } - } - if (isCloseToRelleka()) { - state = State.WALK_TO_VORKATH_ISLAND; - } - break; - case WALK_TO_VORKATH_ISLAND: - Rs2Player.toggleRunEnergy(true); - Rs2Walker.walkTo(new WorldPoint(2640, 3693, 0)); - var torfin = Rs2Npc.getNpc(NpcID.TORFINN_COLLECT_RELLEKKA); - if (torfin != null) { - Rs2Npc.interact(torfin, "Ungael"); - sleepUntil(() -> Rs2Npc.getNpc(NpcID.TORFINN_COLLECT_UNGAEL) != null); - } - if (Rs2Npc.getNpc(NpcID.TORFINN_COLLECT_UNGAEL) != null) { - state = State.WALK_TO_VORKATH; - } - break; - case WALK_TO_VORKATH: - kcPerTrip = 0; - Rs2Walker.walkTo(new WorldPoint(2272, 4052, 0)); - TileObject iceChunks = Rs2GameObject.findObjectById(ObjectID.UNGAEL_CRATER_ENTRANCE); - if (iceChunks != null) { - Rs2GameObject.interact(ObjectID.UNGAEL_CRATER_ENTRANCE, "Climb-over"); - sleepUntil(() -> Rs2GameObject.findObjectById(ObjectID.UNGAEL_CRATER_ENTRANCE) == null); - } - if (Rs2GameObject.findObjectById(ObjectID.UNGAEL_CRATER_ENTRANCE) == null) { - state = State.PREPARE_FIGHT; - } - break; - case PREPARE_FIGHT: - Rs2Player.toggleRunEnergy(false); - - boolean result = drinkPotions(); - - if (result) { - Rs2Npc.interact(NpcID.VORKATH_SLEEPING, "Poke"); - Rs2Player.waitForWalking(); - Rs2Npc.interact(NpcID.VORKATH_SLEEPING, "Poke"); - Rs2Player.waitForAnimation(1000); - walkToCenter(); - Rs2Player.waitForWalking(); - handlePrayer(); - sleepUntil(() -> Rs2Npc.getNpc(NpcID.VORKATH) != null); - if (doesProjectileExistById(redProjectileId)) { - handleRedBall(); - sleep(300); - } - state = State.FIGHT_VORKATH; - } - break; - case FIGHT_VORKATH: - vorkath = Rs2Npc.getNpc(NpcID.VORKATH); - if (vorkath == null || vorkath.isDead()) { - vorkathSessionKills++; - tempVorkathKills--; - kcPerTrip++; - state = State.LOOT_ITEMS; - sleep(300, 600); - Rs2Inventory.wield(primaryBolts); - togglePrayer(false); - sleepUntil(() -> Rs2GroundItem.exists("Superior dragon bones", 20), 15000); - return; - } - if (Microbot.getClient().getBoostedSkillLevel(Skill.HITPOINTS) <= 0) { - state = State.DEAD_WALK; - return; - } - if (config.KillsPerTrip() > 0 && kcPerTrip>= config.KillsPerTrip()) { - leaveVorkath(); - return; - } - if (Rs2Inventory.getInventoryFood().isEmpty()) { - double treshHold = (double) (Microbot.getClient().getBoostedSkillLevel(Skill.HITPOINTS) * 100) / Microbot.getClient().getRealSkillLevel(Skill.HITPOINTS); - if (treshHold < 50) { - leaveVorkath(); - return; - } - } - - if (Rs2Npc.attack(vorkath)) - sleep(600); - if (Microbot.getClient().getLocalPlayer().getLocalLocation().getSceneY() >= 59) { - walkToCenter(); - } - drinkPotions(); - handlePrayer(); - Rs2Player.eatAt(75); - handleRedBall(); - if (doesProjectileExistById(whiteProjectileId)) { - state = State.ZOMBIE_SPAWN; - walkToCenter(); - Rs2Tab.switchToMagicTab(); - } - if ((doesProjectileExistById(acidProjectileId) || doesProjectileExistById(acidRedProjectileId))) { - state = State.ACID; - } - if (vorkath.getHealthRatio() < 60 && vorkath.getHealthRatio() != -1) { - Rs2Inventory.wield("diamond bolts (e)", "diamond dragon bolts (e)"); - } else if (vorkath.getHealthRatio() >= 60 && !Rs2Equipment.isWearing(primaryBolts)) { - Rs2Inventory.wield(primaryBolts); - } - break; - case ZOMBIE_SPAWN: - if (Rs2Npc.getNpc(NpcID.VORKATH) == null) { - state = State.FIGHT_VORKATH; - } - togglePrayer(false); - Rs2Player.eatAt(80); - drinkPrayer(); - NPC zombieSpawn = Rs2Npc.getNpc(ZOMBIFIED_SPAWN); - if (zombieSpawn != null) { - while (Rs2Npc.getNpc(ZOMBIFIED_SPAWN) != null && !Rs2Npc.getNpc(ZOMBIFIED_SPAWN).isDead() - && !doesProjectileExistById(146)) { - Rs2Magic.castOn(MagicAction.CRUMBLE_UNDEAD, zombieSpawn); - sleep(600); - } - Rs2Player.eatAt(75); - togglePrayer(true); - Rs2Tab.switchToInventoryTab(); - state = State.FIGHT_VORKATH; - sleepUntil(() -> Rs2Npc.getNpc("Zombified Spawn") == null); - if (doesProjectileExistById(redProjectileId)) { - handleRedBall(); - sleep(300); - } - - } - break; - case ACID: - Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_RANGE, false); - handleAcidWalk(); - break; - case LOOT_ITEMS: - if (Rs2Inventory.isFull()) { - boolean hasFood = !Rs2Inventory.getInventoryFood().isEmpty(); - if (hasFood) { - Rs2Player.eatAt(100); - Rs2Player.waitForAnimation(); - } else { - state = State.PREPARE_FIGHT; - } - } - togglePrayer(false); - LootingParameters valueParams = new LootingParameters( - config.priceOfItemsToLoot(), - Integer.MAX_VALUE, - 20, - 1, - 0, - false, - false - ); - - Rs2GroundItem.loot("Vorkath's head", 20); - Rs2GroundItem.lootItemBasedOnValue(valueParams); - int foodInventorySize = Rs2Inventory.getInventoryFood().size(); - boolean hasVenom = Rs2Inventory.hasItem("venom"); - boolean hasSuperAntifire = Rs2Inventory.hasItem("super antifire"); - boolean hasPrayerPotion = Rs2Inventory.hasItem(Rs2Potion.getPrayerPotionsVariants().toArray(String[]::new)); - boolean hasRangePotion = Rs2Inventory.hasItem(Rs2Potion.getRangePotionsVariants().toArray(String[]::new)); - sleep(600, 2000); - if (!Rs2GroundItem.isItemBasedOnValueOnGround(config.priceOfItemsToLoot(), 20) && !Rs2GroundItem.exists("Vorkath's head", 20)) { - if (config.KillsPerTrip() > 0 && kcPerTrip >= config.KillsPerTrip()){ - leaveVorkath(); - } - if (foodInventorySize < 3 || !hasVenom || !hasSuperAntifire || !hasRangePotion || (!hasPrayerPotion && !Rs2Player.hasPrayerPoints())) { - leaveVorkath(); - } else { - calculateState(); - } - - } - break; - case TELEPORT_AWAY: - togglePrayer(false); - Rs2Player.toggleRunEnergy(true); - Rs2Inventory.wield(primaryBolts); - boolean reachedDestination = Rs2Bank.walkToBank(); - if (reachedDestination) { - healAndDrinkPrayerPotion(); - state = State.BANKING; - } - break; - case DEAD_WALK: - if (isCloseToRelleka()) { - Rs2Walker.walkTo(new WorldPoint(2640, 3693, 0)); - torfin = Rs2Npc.getNpc(NpcID.TORFINN_COLLECT_RELLEKKA); - if (torfin != null) { - Rs2Npc.interact(torfin, "Collect"); - sleepUntil(() -> Rs2Widget.hasWidget("Retrieval Service"), 1500); - if (Rs2Widget.hasWidget("I'm afraid I don't have anything")) { // this means we looted all our stuff - leaveVorkath(); - return; - } - final int invSize = Rs2Inventory.count(); - Rs2Widget.clickWidget(39452678); - sleep(600); - Rs2Widget.clickWidget(39452678); - sleepUntil(() -> Rs2Inventory.count() != invSize); - boolean isWearingOriginalEquipment = rs2InventorySetup.wearEquipment(); - if (!isWearingOriginalEquipment) { - int finalInvSize = Rs2Inventory.count(); - Rs2Widget.clickWidget(39452678); - sleepUntil(() -> Rs2Inventory.count() != finalInvSize); - rs2InventorySetup.wearEquipment(); - } - } - } else { - togglePrayer(false); - if (Rs2Inventory.hasItem("Rellekka teleport")) { - Rs2Inventory.interact("Rellekka teleport", "break"); - Rs2Player.waitForAnimation(); - return; - } - Rs2Bank.walkToBank(); - Rs2Bank.openBank(); - Rs2Bank.withdrawItem("Rellekka teleport"); - sleep(150, 400); - Rs2Bank.closeBank(); - sleepUntil(() -> Rs2Inventory.hasItem("Rellekka teleport"), 1000); - } - break; - case SELLING_ITEMS: - boolean soldAllItems = Rs2GrandExchange.sellLoot("vorkath", Arrays.stream(config.ItemsToNotSell().split(",")).collect(Collectors.toList())); - if (soldAllItems) { - state = State.BANKING; - } - break; - } - - init = false; - - } catch (Exception ex) { - System.out.println(ex.getMessage()); - } - }, 0, 80, TimeUnit.MILLISECONDS); - return true; - } - - /** - * Checks if we have killed x amount of vorkaths based on a config to sell items - * @param config - * @return true if we need to sell items - */ - private boolean checkSellingItems(VorkathConfig config) { - if (tempVorkathKills > 0) return false; - LootTrackerRecord lootRecord = Microbot.getAggregateLootRecords("vorkath"); - if (lootRecord != null) { - if (tempVorkathKills % config.SellItemsAtXKills() == 0) { - state = State.SELLING_ITEMS; - tempVorkathKills = config.SellItemsAtXKills(); - return true; - } - } - return false; - } - - /** - * will heal and drink pray pots - */ - private void healAndDrinkPrayerPotion() { - while (!Rs2Player.isFullHealth() && !Rs2Inventory.getInventoryFood().isEmpty()) { - Rs2Bank.closeBank(); - Rs2Player.eatAt(99); - Rs2Player.waitForAnimation(); - hasInventory = false; - } - while (Microbot.getClient().getRealSkillLevel(Skill.PRAYER) != Microbot.getClient().getBoostedSkillLevel(Skill.PRAYER) && Rs2Inventory.hasItem(Rs2Potion.getPrayerPotionsVariants().toArray(String[]::new))) { - Rs2Bank.closeBank(); - Rs2Inventory.interact(Rs2Potion.getPrayerPotionsVariants().toArray(String[]::new), "drink"); - Rs2Player.waitForAnimation(); - hasInventory = false; - } - } - - private void leaveVorkath() { - kcPerTrip = 0; - togglePrayer(false); - Rs2Player.toggleRunEnergy(true); - switch(config.teleportMode()) { - case VARROCK_TAB: - Rs2Inventory.interact(config.teleportMode().getItemName(), config.teleportMode().getAction()); - break; - case CRAFTING_CAPE: - if (Rs2Equipment.isWearing("crafting cape")) { - Rs2Equipment.interact("crafting cape", "teleport"); - } else { - Rs2Inventory.interact("crafting cape", "teleport"); - } - break; - case JEWELLERY_BOX: - teleToPoh(); - if(config.rejuvinationPool()){ - Rs2GameObject.interact(29241, "Drink"); - sleepUntil(() -> Rs2Player.isFullHealth()); - } - Rs2GameObject.interact(29156, "Teleport Menu"); - sleepUntil(()->Rs2Widget.hasWidget("Castle Wars")); - Rs2Widget.clickWidget("Castle Wars"); - break; - } - Rs2Player.waitForAnimation(); - sleepUntil(() -> !Microbot.getClient().isInInstancedRegion()); - state = State.TELEPORT_AWAY; - - } - - private boolean drinkPotions() { - if (Rs2Player.isAnimating()) return false; - boolean drinkRangePotion = !Rs2Player.hasDivineBastionActive() && !Rs2Player.hasDivineRangedActive() && !Rs2Player.hasRangingPotionActive(5); - boolean drinkAntiFire = !Rs2Player.hasAntiFireActive() && !Rs2Player.hasSuperAntiFireActive(); - boolean drinkAntiVenom = !Rs2Player.hasAntiVenomActive(); - - if (drinkRangePotion) { - Rs2Inventory.interact(Rs2Potion.getRangePotionsVariants().toArray(String[]::new), "drink"); - } - if (drinkAntiFire) { - Rs2Inventory.interact("super antifire", "drink"); - } - if (drinkAntiVenom) { - Rs2Inventory.interact("venom", "drink"); - } - - if (!Microbot.getClient().getLocalPlayer().isInteracting() && state == State.PREPARE_FIGHT && (drinkRangePotion || drinkAntiFire || drinkAntiVenom)) - Rs2Player.waitForAnimation(); - - return !drinkRangePotion && !drinkAntiFire && !drinkAntiVenom; - } - - public void togglePrayer(boolean onOff) { - if (Rs2Prayer.isOutOfPrayer()) return; - if (Microbot.getClient().getRealSkillLevel(Skill.PRAYER) >= 74 && Microbot.getClient().getRealSkillLevel(Skill.DEFENCE) >= 70 && config.activateRigour()) { - Rs2Prayer.toggle(Rs2PrayerEnum.RIGOUR, onOff); - } else { - Rs2Prayer.toggle(Rs2PrayerEnum.EAGLE_EYE, onOff); - } - Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_RANGE, onOff); - } - - private void handleRedBall() { - if (doesProjectileExistById(redProjectileId)) { - redBallWalk(); - Rs2Npc.interact("Vorkath", "attack"); - } - } - - private void handlePrayer() { - drinkPrayer(); - togglePrayer(true); - } - - private boolean doesProjectileExistById(int id) { - for (Projectile projectile : Microbot.getClient().getProjectiles()) { - if (projectile.getId() == id) { - //println("Projectile $id found") - return true; - } - } - return false; - } - - private boolean isCloseToRelleka() { - if (Microbot.getClient().getLocalPlayer() == null) return false; - return Microbot.getClient().getLocalPlayer().getWorldLocation().distanceTo(new WorldPoint(2670, 3634, 0)) < 80; - } - - private boolean teleToPoh(){ - if(Rs2Magic.canCast(MagicAction.TELEPORT_TO_HOUSE)){ - Rs2Magic.cast(MagicAction.TELEPORT_TO_HOUSE); - sleepUntil(()->Rs2GameObject.findObjectById(4525) != null); - return true; - }else - if(Rs2Inventory.hasItem("Teleport to house")){ - Rs2Inventory.interact("Teleport to house", "break"); - sleepUntil(()->Rs2GameObject.findObjectById(4525) != null); - return true; - } - return false; -} - - private void redBallWalk() { - WorldPoint currentPlayerLocation = Microbot.getClient().getLocalPlayer().getWorldLocation(); - WorldPoint sideStepLocation = new WorldPoint(currentPlayerLocation.getX() + 2, currentPlayerLocation.getY(), 0); - if (Rs2Random.between(0, 2) == 1) { - sideStepLocation = new WorldPoint(currentPlayerLocation.getX() - 2, currentPlayerLocation.getY(), 0); - } - final WorldPoint _sideStepLocation = sideStepLocation; - Rs2Walker.walkFastLocal(LocalPoint.fromWorld(Microbot.getClient(), _sideStepLocation)); - Rs2Player.waitForWalking(); - sleepUntil(() -> Microbot.getClient().getLocalPlayer().getWorldLocation().equals(_sideStepLocation)); - } - - WorldPoint findSafeTile() { - WorldPoint swPoint = new WorldPoint(vorkath.getWorldLocation().getX() + 1, vorkath.getWorldLocation().getY() - 8, 0); - WorldArea wooxWalkArea = new WorldArea(swPoint, 5, 1); - - List safeTiles = wooxWalkArea.toWorldPointList().stream().filter(this::isTileSafe).collect(Collectors.toList()); - - // Find the closest safe tile by x-coordinate to the player - return safeTiles.stream().min(Comparator.comparingInt(tile -> Math.abs(tile.getX() - Microbot.getClient().getLocalPlayer().getWorldLocation().getX()))).orElse(null); - } - - - boolean isTileSafe(WorldPoint tile) { - return !acidPools.contains(tile) - && !acidPools.contains(new WorldPoint(tile.getX(), tile.getY() + 1, tile.getPlane())); - - } - - private void handleAcidWalk() { - if (!doesProjectileExistById(acidProjectileId) && !doesProjectileExistById(acidRedProjectileId) && Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.VORKATH_ACID).isEmpty()) { - Rs2Npc.interact(vorkath, "attack"); - state = State.FIGHT_VORKATH; - acidPools.clear(); - return; - } - - Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.VORKATH_ACID).forEach(tileObject -> acidPools.add(tileObject.getWorldLocation())); - Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.OLM_ACID_POOL).forEach(tileObject -> acidPools.add(tileObject.getWorldLocation())); - Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.MYQ5_ACID_POOL).forEach(tileObject -> acidPools.add(tileObject.getWorldLocation())); - - WorldPoint safeTile = findSafeTile(); - WorldPoint playerLocation = Microbot.getClient().getLocalPlayer().getWorldLocation(); - - if (safeTile != null) { - if (playerLocation.equals(safeTile)) { - Rs2Npc.interact(vorkath, "attack"); - } else { - Rs2Player.eatAt(60); - Rs2Walker.walkFastLocal(LocalPoint.fromWorld(Microbot.getClient(), safeTile)); - } - } - } - //Only use this for testing purpose on sleeping vorkath - private void testWooxWalk() { - vorkath = Rs2Npc.getNpc(NpcID.VORKATH_SLEEPING); - WorldPoint safeTile = findSafeTile(); - WorldPoint playerLocation = Microbot.getClient().getLocalPlayer().getWorldLocation(); - - if (safeTile != null) { - if (playerLocation.equals(safeTile)) { - Rs2Npc.interact(vorkath, "attack"); - } else { - Rs2Player.eatAt(60); - Rs2Walker.walkFastLocal(LocalPoint.fromWorld(Microbot.getClient(), safeTile)); - } - } - } -} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/zerozero/birdhunter/BirdHunterScript.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/zerozero/birdhunter/BirdHunterScript.java index 00f812695ea..7334df610a0 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/zerozero/birdhunter/BirdHunterScript.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/zerozero/birdhunter/BirdHunterScript.java @@ -90,7 +90,7 @@ private boolean hasRequiredSnares() { int hunterLevel = Rs2Player.getRealSkillLevel(Skill.HUNTER); int allowedSnares = getAvailableTraps(hunterLevel); // Calculate the allowed number of snares - int snaresInInventory = Rs2Inventory.count(ItemID.HUNTING_OJIBWAY_BIRD_SNARE); + int snaresInInventory = Rs2Inventory.itemQuantity(ItemID.HUNTING_OJIBWAY_BIRD_SNARE); Microbot.log("Allowed snares: " + allowedSnares + ", Snares in inventory: " + snaresInInventory); return snaresInInventory >= allowedSnares; // Return true if enough snares, false otherwise diff --git a/runelite-client/src/main/java/net/runelite/client/proxy/ProxyChecker.java b/runelite-client/src/main/java/net/runelite/client/proxy/ProxyChecker.java new file mode 100644 index 00000000000..5689685742d --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/proxy/ProxyChecker.java @@ -0,0 +1,61 @@ +package net.runelite.client.proxy; + +import lombok.extern.slf4j.Slf4j; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +import java.io.IOException; +import java.util.List; +import java.util.regex.Pattern; + +@Slf4j +public class ProxyChecker { + private static final Pattern IPV4 = + Pattern.compile("^(?:25[0-5]|2[0-4]\\d|1?\\d?\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1?\\d?\\d)){3}$"); + + /** + * Detects the external IP address by querying a list of endpoints. + * @param okHttpClient + * @return + */ + public static String getDetectedIp(OkHttpClient okHttpClient) { + List endpoints = List.of( + "https://microbot.cloud/api/network/ip" + ); + + for (String url : endpoints) { + String ip = fetchIp(okHttpClient, url); + if (ip != null) { + return ip; + } + } + return ""; + } + + /** + * Fetches the IP address from a given URL using the provided OkHttpClient. + * @param client + * @param url + * @return + */ + private static String fetchIp(OkHttpClient client, String url) { + Request req = new Request.Builder().url(url).build(); + try (Response res = client.newCall(req).execute()) { + if (!res.isSuccessful() || res.body() == null) { + log.warn("IP endpoint failed: {} (code {})", url, res.code()); + return null; + } + String ip = res.body().string().trim(); + if (!IPV4.matcher(ip).matches()) { + log.warn("Invalid IP payload from {}: '{}'", url, ip); + return null; + } + return ip; + } catch (IOException e) { + log.error("Error calling {}", url, e); + return null; + } + } + +} diff --git a/runelite-client/src/main/java/net/runelite/client/proxy/ProxyConfiguration.java b/runelite-client/src/main/java/net/runelite/client/proxy/ProxyConfiguration.java new file mode 100644 index 00000000000..a2c97757e4d --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/proxy/ProxyConfiguration.java @@ -0,0 +1,123 @@ +package net.runelite.client.proxy; + +import joptsimple.ArgumentAcceptingOptionSpec; +import joptsimple.OptionSet; +import net.runelite.client.plugins.microbot.Microbot; + +import java.net.Authenticator; +import java.net.PasswordAuthentication; +import java.net.URI; +import java.util.Locale; +import java.util.Optional; + +/** + * Configures the JVM to use a SOCKS5 proxy if the appropriate command line argument is provided. + */ +public class ProxyConfiguration { + + /** + * Sets up the proxy configuration based on the provided options. + * @param options + * @param proxyInfo + */ + public static void setupProxy(OptionSet options, ArgumentAcceptingOptionSpec proxyInfo) { + if (!options.has(proxyInfo)) { + return; + } + + URI uri = URI.create(options.valueOf(proxyInfo)); + + if (options.has("proxy-type")) { + Microbot.showMessage("Proxy type is no longer supported, please use the format -proxy=socks://user:pass@host:port or http://user:pass@host:port"); + System.exit(1); + } + + String host = uri.getHost(); + String scheme = Optional.ofNullable(uri.getScheme()).orElse("").toLowerCase(Locale.ROOT); + + validateProxyScheme(scheme); + + int port = validatePort(uri.getPort()); + + String[] credentials = extractCredentials(uri); + String user = credentials[0]; + String pass = credentials[1]; + + configureProxy(host, port); + + if (user != null) { + setupAuthenticator(user, pass); + } + } + + /** + * Validates the proxy scheme to ensure it is SOCKS5. + * @param scheme + */ + private static void validateProxyScheme(String scheme) { + boolean isHttpProxy = scheme.equals("http") || scheme.equals("https"); + if (isHttpProxy) { + Microbot.showMessage("HTTP(S) proxies are not supported, please use a SOCKS5 proxy. \n\n This is to make sure that osrs traffic is also routed through the proxy."); + System.exit(1); + } + + boolean isSocksProxy = scheme.equals("socks") || scheme.equals("socks5"); + if (!isSocksProxy) { + Microbot.showMessage("Proxy scheme must be socks(5)."); + System.exit(1); + } + } + + /** + * Validates the proxy port to ensure it is a positive integer. + * @param port + * @return + */ + private static int validatePort(int port) { + if (port <= 0) { + Microbot.showMessage("Invalid proxy port"); + System.exit(1); + } + return port; + } + + /** + * Extracts the username and password from the URI's user info. + * @param uri + * @return + */ + private static String[] extractCredentials(URI uri) { + String user = null; + String pass = null; + if (uri.getUserInfo() != null && uri.getUserInfo().contains(":")) { + String[] userInfo = uri.getUserInfo().split(":", 2); + user = userInfo[0]; + pass = userInfo[1]; + } + return new String[]{user, pass}; + } + + /** + * Configures the JVM to use the specified SOCKS5 proxy. + * @param host + * @param port + */ + private static void configureProxy(String host, int port) { + System.setProperty("socksProxyHost", host); + System.setProperty("socksProxyPort", String.valueOf(port > 0 ? port : 1080)); + } + + /** + * Sets up the default authenticator for proxy authentication. + * @param user + * @param pass + */ + private static void setupAuthenticator(String user, String pass) { + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(user, pass != null ? pass.toCharArray() : new char[0]); + } + }); + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java b/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java index 5e91845fcb4..620ad7232bf 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java @@ -30,70 +30,6 @@ import com.google.common.base.Strings; import com.google.common.collect.Iterables; import com.google.inject.Inject; -import java.awt.AWTException; -import java.awt.Canvas; -import java.awt.Component; -import java.awt.Container; -import java.awt.Cursor; -import java.awt.Desktop; -import java.awt.Dimension; -import java.awt.Frame; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.GraphicsConfiguration; -import java.awt.GraphicsDevice; -import java.awt.GraphicsEnvironment; -import java.awt.Image; -import java.awt.Insets; -import java.awt.KeyboardFocusManager; -import java.awt.LayoutManager2; -import java.awt.Point; -import java.awt.Rectangle; -import java.awt.SystemTray; -import java.awt.Taskbar; -import java.awt.Toolkit; -import java.awt.TrayIcon; -import java.awt.desktop.QuitStrategy; -import java.awt.event.ComponentAdapter; -import java.awt.event.ComponentEvent; -import java.awt.event.KeyEvent; -import java.awt.event.MouseEvent; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.awt.event.WindowFocusListener; -import java.awt.image.BufferedImage; -import java.time.Duration; -import java.util.ArrayDeque; -import java.util.Arrays; -import java.util.Deque; -import java.util.List; -import java.util.TreeSet; -import java.util.function.Function; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import javax.inject.Named; -import javax.inject.Provider; -import javax.inject.Singleton; -import javax.swing.Box; -import javax.swing.Icon; -import javax.swing.ImageIcon; -import javax.swing.JButton; -import javax.swing.JDialog; -import javax.swing.JEditorPane; -import javax.swing.JFrame; -import javax.swing.JMenuBar; -import javax.swing.JMenuItem; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JPopupMenu; -import javax.swing.JRootPane; -import javax.swing.JTabbedPane; -import javax.swing.SwingUtilities; -import javax.swing.Timer; -import javax.swing.ToolTipManager; -import javax.swing.border.EmptyBorder; -import javax.swing.border.MatteBorder; -import javax.swing.event.HyperlinkEvent; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -121,13 +57,26 @@ import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.ui.laf.RuneLiteLAF; import net.runelite.client.ui.laf.RuneLiteRootPaneUI; -import net.runelite.client.util.HotkeyListener; -import net.runelite.client.util.ImageUtil; -import net.runelite.client.util.LinkBrowser; -import net.runelite.client.util.OSType; -import net.runelite.client.util.OSXUtil; -import net.runelite.client.util.SwingUtil; -import net.runelite.client.util.WinUtil; +import net.runelite.client.util.*; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.inject.Named; +import javax.inject.Provider; +import javax.inject.Singleton; +import javax.swing.Timer; +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import javax.swing.border.MatteBorder; +import javax.swing.event.HyperlinkEvent; +import java.awt.*; +import java.awt.desktop.QuitStrategy; +import java.awt.event.*; +import java.awt.image.BufferedImage; +import java.time.Duration; +import java.util.List; +import java.util.*; +import java.util.function.Function; import static javax.swing.JOptionPane.INFORMATION_MESSAGE; @@ -217,8 +166,7 @@ private ClientUI( this.clientThreadProvider = clientThreadProvider; this.eventBus = eventBus; this.safeMode = safeMode; - this.title = title + (safeMode ? " (safe mode)" : " V" + version) + - (proxyMessage.contains(":") ? " " + proxyMessage.split(":")[0] + ":" + proxyMessage.split(":")[1] : ""); + this.title = title + (safeMode ? " (safe mode)" : " V" + version) + " " + proxyMessage; normalBoundsTimer = new Timer(250, _ev -> setLastNormalBounds()); normalBoundsTimer.setRepeats(false); diff --git a/runelite-client/src/main/java/net/runelite/client/ui/SplashScreen.java b/runelite-client/src/main/java/net/runelite/client/ui/SplashScreen.java index cb3afb850f4..c68d83fb539 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/SplashScreen.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/SplashScreen.java @@ -22,9 +22,11 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + package net.runelite.client.ui; import lombok.extern.slf4j.Slf4j; +import net.runelite.client.plugins.microbot.RandomFactClient; import net.runelite.client.ui.laf.RuneLiteLAF; import net.runelite.client.util.ImageUtil; @@ -35,206 +37,354 @@ import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.geom.RoundRectangle2D; import java.awt.image.BufferedImage; +import java.beans.PropertyChangeSupport; import java.lang.reflect.InvocationTargetException; import java.util.Arrays; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; @Slf4j -public class SplashScreen extends JFrame implements ActionListener -{ - private static final int WIDTH = 200; - private static final int PAD = 10; - - private static SplashScreen INSTANCE; - - private final JLabel action = new JLabel("Loading"); - private final JProgressBar progress = new JProgressBar(); - private final JLabel subAction = new JLabel(); - private final Timer timer; - - private volatile double overallProgress = 0; - private volatile String actionText = "Loading"; - private volatile String subActionText = ""; - private volatile String progressText = null; - private SplashScreen() - { - BufferedImage logo = ImageUtil.loadImageResource(SplashScreen.class, "runelite_splash.png"); - - setTitle("Microbot"); - - setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - setUndecorated(true); - setIconImages(Arrays.asList(ClientUI.ICON_128, ClientUI.ICON_16)); - setLayout(null); - Container pane = getContentPane(); - pane.setBackground(ColorScheme.DARKER_GRAY_COLOR); - - Font font = new Font(Font.DIALOG, Font.PLAIN, 12); - - JLabel logoLabel = new JLabel(new ImageIcon(logo)); - pane.add(logoLabel); - logoLabel.setBounds(0, 0, WIDTH, WIDTH); - - int y = WIDTH; - - pane.add(action); - action.setForeground(Color.WHITE); - action.setBounds(0, y, WIDTH, 16); - action.setHorizontalAlignment(SwingConstants.CENTER); - action.setFont(font); - y += action.getHeight() + PAD; - - pane.add(progress); - progress.setForeground(ColorScheme.BRAND_ORANGE); - progress.setBackground(ColorScheme.BRAND_ORANGE.darker().darker()); - progress.setBorder(new EmptyBorder(0, 0, 0, 0)); - progress.setBounds(0, y, WIDTH, 14); - progress.setFont(font); - progress.setUI(new BasicProgressBarUI() - { - @Override - protected Color getSelectionBackground() - { - return Color.BLACK; - } - - @Override - protected Color getSelectionForeground() - { - return Color.BLACK; - } - }); - y += 12 + PAD; - - pane.add(subAction); - subAction.setForeground(Color.LIGHT_GRAY); - subAction.setBounds(0, y, WIDTH, 16); - subAction.setHorizontalAlignment(SwingConstants.CENTER); - subAction.setFont(font); - y += subAction.getHeight() + PAD; - - setSize(WIDTH, y); - setLocationRelativeTo(null); - - timer = new Timer(100, this); - timer.setRepeats(true); - timer.start(); - - setVisible(true); - } - - @Override - public void actionPerformed(ActionEvent e) - { - action.setText(actionText); - subAction.setText(subActionText); - progress.setMaximum(1000); - progress.setValue((int) (overallProgress * 1000)); - - String progressText = this.progressText; - if (progressText == null) - { - progress.setStringPainted(false); - } - else - { - progress.setStringPainted(true); - progress.setString(progressText); - } - } - - public static boolean isOpen() - { - return INSTANCE != null; - } - - public static void init() - { - try - { - SwingUtilities.invokeAndWait(() -> - { - if (INSTANCE != null) - { - return; - } - - try - { - boolean hasLAF = UIManager.getLookAndFeel() instanceof RuneLiteLAF; - if (!hasLAF) - { - UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); - } - INSTANCE = new SplashScreen(); - } - catch (Exception e) - { - log.warn("Unable to start splash screen", e); - } - }); - } - catch (InterruptedException | InvocationTargetException bs) - { - throw new RuntimeException(bs); - } - } - - public static void stop() - { - SwingUtilities.invokeLater(() -> - { - if (INSTANCE == null) - { - return; - } - - INSTANCE.timer.stop(); - // The CLOSE_ALL_WINDOWS quit strategy on MacOS dispatches WINDOW_CLOSING events to each frame - // from Window.getWindows. However, getWindows uses weak refs and relies on gc to remove windows - // from its list, causing events to get dispatched to disposed frames. The frames handle the events - // regardless of being disposed and will run the configured close operation. Set the close operation - // to DO_NOTHING_ON_CLOSE prior to disposing to prevent this. - INSTANCE.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); - INSTANCE.dispose(); - INSTANCE = null; - }); - } - - public static void stage(double overallProgress, @Nullable String actionText, String subActionText) - { - stage(overallProgress, actionText, subActionText, null); - } - - public static void stage(double startProgress, double endProgress, - @Nullable String actionText, String subActionText, - int done, int total, boolean mib) - { - String progress; - if (mib) - { - final double MiB = 1024 * 1024; - final double CEIL = 1.d / 10.d; - progress = String.format("%.1f / %.1f MiB", done / MiB, (total / MiB) + CEIL); - } - else - { - progress = done + " / " + total; - } - stage(startProgress + ((endProgress - startProgress) * done / total), actionText, subActionText, progress); - } - - public static void stage(double overallProgress, @Nullable String actionText, String subActionText, @Nullable String progressText) - { - if (INSTANCE != null) - { - INSTANCE.overallProgress = overallProgress; - if (actionText != null) - { - INSTANCE.actionText = actionText; - } - INSTANCE.subActionText = subActionText; - INSTANCE.progressText = progressText; - } - } -} +public class SplashScreen extends JFrame implements ActionListener { + private static final int WIDTH = 360; + private static final int PAD = 14; + private static final int ARC = 18; + private static final int CLOSE_BUTTON_SIZE = 20; + + private static SplashScreen INSTANCE; + + private final JLabel action = new JLabel("Loading"); + private final JProgressBar progress = new JProgressBar(); + private final JLabel subAction = new JLabel(); + private final Timer timer; + + private volatile double overallProgress = 0; + private volatile String actionText = "Loading"; + private volatile String subActionText = ""; + private volatile String progressText = null; + + private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); + private static String factValue = "Fetching a tip..."; + + public static String getFact() { + return factValue; + } + + public static void setFact(String newFact) { + if (INSTANCE != null && newFact != null && !String.valueOf(factValue).equals(newFact)) { + String oldValue = factValue; + factValue = newFact; + INSTANCE.pcs.firePropertyChange("fact", oldValue, newFact); + } + } + + private static ScheduledExecutorService scheduledRandomFactExecutorService; + private static ScheduledFuture scheduledRandomFactFuture; + + static String escape(String s) { + return s.replace("&", "&").replace("<", "<").replace(">", ">"); + } + + private JPanel createCloseButton() { + JPanel closePanel = new JPanel(); + closePanel.setOpaque(false); + closePanel.setLayout(new FlowLayout(FlowLayout.RIGHT, 0, 0)); + closePanel.setPreferredSize(new Dimension(WIDTH, CLOSE_BUTTON_SIZE + 5)); + + JLabel closeButton = new JLabel("×"); + closeButton.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 16)); + closeButton.setForeground(new Color(180, 180, 180)); + closeButton.setHorizontalAlignment(SwingConstants.CENTER); + closeButton.setVerticalAlignment(SwingConstants.CENTER); + closeButton.setPreferredSize(new Dimension(CLOSE_BUTTON_SIZE, CLOSE_BUTTON_SIZE)); + closeButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + + // Hover effect + closeButton.addMouseListener(new MouseAdapter() { + @Override + public void mouseEntered(MouseEvent e) { + closeButton.setForeground(new Color(255, 100, 100)); + } + + @Override + public void mouseExited(MouseEvent e) { + closeButton.setForeground(new Color(180, 180, 180)); + } + + @Override + public void mouseClicked(MouseEvent e) { + // Stop the application + System.exit(0); + } + }); + + closePanel.add(closeButton); + return closePanel; + } + + private SplashScreen() { + BufferedImage logo = ImageUtil.loadImageResource(SplashScreen.class, "microbot_splash.png"); + + setTitle("Microbot"); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setUndecorated(true); + setIconImages(Arrays.asList(ClientUI.ICON_128, ClientUI.ICON_16)); + + // Rounded window + getRootPane().setBorder(BorderFactory.createLineBorder(new Color(35, 35, 35))); + setBackground(new Color(0, 0, 0, 0)); // allow shaping + addComponentListener(new java.awt.event.ComponentAdapter() { + @Override + public void componentResized(java.awt.event.ComponentEvent e) { + setShape(new RoundRectangle2D.Double(0, 0, getWidth(), getHeight(), ARC, ARC)); + } + }); + + JPanel root = new JPanel(new GridBagLayout()); + root.setBorder(new EmptyBorder(PAD, PAD, PAD, PAD)); + root.setBackground(ColorScheme.DARKER_GRAY_COLOR); + setContentPane(root); + + final Font titleFont = new Font(Font.SANS_SERIF, Font.BOLD, 16); + final Font bodyFont = new Font(Font.SANS_SERIF, Font.PLAIN, 13); + final Color fg = new Color(230, 230, 230); + final Color fgMuted = new Color(180, 180, 180); + + GridBagConstraints gc = new GridBagConstraints(); + gc.gridx = 0; + gc.gridy = 0; + gc.weightx = 1; + gc.fill = GridBagConstraints.HORIZONTAL; + + // Close button + gc.insets = new Insets(0, 0, PAD / 2, 0); + root.add(createCloseButton(), gc); + + // Logo + JLabel logoLabel = new JLabel(new ImageIcon(logo)); + logoLabel.setHorizontalAlignment(SwingConstants.CENTER); + gc.gridy++; + gc.insets = new Insets(0, 0, PAD, 0); + root.add(logoLabel, gc); + + // Primary action + action.setHorizontalAlignment(SwingConstants.CENTER); + action.setFont(titleFont); + action.setForeground(fg); + gc.gridy++; + gc.insets = new Insets(0, 0, 6, 0); + root.add(action, gc); + + // Sub action + subAction.setHorizontalAlignment(SwingConstants.CENTER); + subAction.setFont(bodyFont); + subAction.setForeground(fgMuted); + gc.gridy++; + gc.insets = new Insets(0, 0, PAD, 0); + root.add(subAction, gc); + + // Progress + progress.setMaximum(1000); + progress.setStringPainted(false); + progress.setBorderPainted(false); + progress.setFont(bodyFont); + progress.setBackground(new Color(40, 40, 40)); + progress.setForeground(ColorScheme.BRAND_ORANGE); + progress.setUI(new BasicProgressBarUI() { + @Override + protected void paintDeterminate(Graphics g, JComponent c) { + int w = c.getWidth(), h = c.getHeight(); + Graphics2D g2 = (Graphics2D) g.create(); + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + // Track + g2.setColor(new Color(55, 55, 55)); + g2.fillRoundRect(0, 0, w, h, 10, 10); + + // Bar + int pw = (int) Math.round(progress.getPercentComplete() * w); + g2.setColor(ColorScheme.BRAND_ORANGE); + g2.fillRoundRect(0, 0, Math.max(ARC, pw), h, 10, 10); + + // Text + if (progress.isStringPainted()) { + String s = progress.getString(); + FontMetrics fm = g2.getFontMetrics(); + int tx = (w - fm.stringWidth(s)) / 2; + int ty = (h + fm.getAscent() - fm.getDescent()) / 2; + g2.setColor(Color.BLACK); + g2.drawString(s, tx + 1, ty + 1); + g2.setColor(Color.WHITE); + g2.drawString(s, tx, ty); + } + g2.dispose(); + } + }); + gc.gridy++; + gc.insets = new Insets(0, 0, PAD, 0); + root.add(progress, gc); + + // Divider + JSeparator sep = new JSeparator(); + sep.setForeground(new Color(70, 70, 70)); + sep.setBackground(new Color(70, 70, 70)); + gc.gridy++; + gc.insets = new Insets(0, 0, PAD, 0); + root.add(sep, gc); + + // Facts panel + JPanel factCard = new JPanel(new BorderLayout()); + factCard.setOpaque(false); + factCard.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createLineBorder(new Color(55, 55, 55)), + new EmptyBorder(PAD, PAD, PAD, PAD) + )); + + JLabel factTitle = new JLabel("💡 Did you know?"); + factTitle.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 13)); + factTitle.setForeground(fg); + factTitle.setBorder(new EmptyBorder(0, 0, 8, 0)); + factCard.add(factTitle, BorderLayout.NORTH); + + // --- build facts panel --- + // Replace the JTextPane section with this JTextArea approach + JTextArea factArea = new JTextArea(); + factArea.setEditable(false); + factArea.setFocusable(false); + factArea.setOpaque(false); + factArea.setLineWrap(true); + factArea.setWrapStyleWord(true); + factArea.setFont(bodyFont); + factArea.setForeground(fgMuted); + factArea.setBackground(new Color(0, 0, 0, 0)); + factArea.setText(getFact()); + factArea.setPreferredSize(new Dimension(WIDTH - (PAD * 4), 160)); + // Add the factPane directly to the card + factCard.add(factArea, BorderLayout.CENTER); + + // updates + pcs.addPropertyChangeListener("fact", evt -> SwingUtilities.invokeLater(() -> { + factArea.setText(String.valueOf(evt.getNewValue())); + factArea.setCaretPosition(0); + factArea.revalidate(); + factCard.revalidate(); + })); + + // Proper GridBag constraints for the fact card + gc.gridy++; + gc.insets = new Insets(0, 0, 0, 0); + gc.weighty = 1.0; // Allow vertical expansion + gc.fill = GridBagConstraints.BOTH; // Fill both horizontal and vertical space + root.add(factCard, gc); + + // Size and center + setSize(WIDTH, 520); + setMinimumSize(new Dimension(WIDTH, 420)); + setLocationRelativeTo(null); + + timer = new Timer(100, this); + timer.setRepeats(true); + timer.start(); + + setVisible(true); + + ScheduledExecutorService scheduledRandomFactExecutorService = Executors.newSingleThreadScheduledExecutor(); + scheduledRandomFactFuture = scheduledRandomFactExecutorService.scheduleAtFixedRate( + () -> { + RandomFactClient.getRandomFactAsync(SplashScreen::setFact); + }, + 0, 20, TimeUnit.SECONDS); + } + + @Override + public void actionPerformed(ActionEvent e) { + action.setText(actionText); + subAction.setText(subActionText); + progress.setMaximum(1000); + progress.setValue((int) (overallProgress * 1000)); + + String progressText = this.progressText; + if (progressText == null) { + progress.setStringPainted(false); + } else { + progress.setStringPainted(true); + progress.setString(progressText); + } + } + + public static boolean isOpen() { + return INSTANCE != null; + } + + public static void init() { + try { + SwingUtilities.invokeAndWait(() -> { + if (INSTANCE != null) return; + + try { + boolean hasLAF = UIManager.getLookAndFeel() instanceof RuneLiteLAF; + if (!hasLAF) { + UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); + } + INSTANCE = new SplashScreen(); + } catch (Exception e) { + log.warn("Unable to start splash screen", e); + } + }); + } catch (InterruptedException | InvocationTargetException bs) { + throw new RuntimeException(bs); + } + } + + public static void stop() { + SwingUtilities.invokeLater(() -> { + if (INSTANCE == null) return; + + INSTANCE.timer.stop(); + if (scheduledRandomFactFuture != null) { + scheduledRandomFactFuture.cancel(true); + } + if (scheduledRandomFactExecutorService != null) { + scheduledRandomFactExecutorService.shutdownNow(); + scheduledRandomFactExecutorService = null; + } + INSTANCE.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + INSTANCE.dispose(); + INSTANCE = null; + }); + } + + public static void stage(double overallProgress, @Nullable String actionText, String subActionText) { + stage(overallProgress, actionText, subActionText, null); + } + + public static void stage(double startProgress, double endProgress, + @Nullable String actionText, String subActionText, + int done, int total, boolean mib) { + String progress; + if (mib) { + final double MiB = 1024 * 1024; + final double CEIL = 1.d / 10.d; + progress = String.format("%.1f / %.1f MiB", done / MiB, (total / MiB) + CEIL); + } else { + progress = done + " / " + total; + } + stage(startProgress + ((endProgress - startProgress) * done / total), actionText, subActionText, progress); + } + + public static void stage(double overallProgress, @Nullable String actionText, String subActionText, @Nullable String progressText) { + if (INSTANCE != null) { + INSTANCE.overallProgress = overallProgress; + if (actionText != null) { + INSTANCE.actionText = actionText; + } + INSTANCE.subActionText = subActionText; + INSTANCE.progressText = progressText; + } + } +} \ No newline at end of file diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/Clue_scroll_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/Clue_scroll_icon.png new file mode 100644 index 00000000000..6b98fdee12e Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/Clue_scroll_icon.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/boats.tsv b/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/boats.tsv index d5d265a077c..33a5f202896 100644 --- a/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/boats.tsv +++ b/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/boats.tsv @@ -110,18 +110,18 @@ 1195 3452 0 1229 3469 0 Travel;Rowboat;48720 Desert Treasure II - The Fallen Empire 7 # Molch Island, Molch, Battlefront, Shayzien -1384 3665 0 1369 3639 0 Board;Boaty;33614 4 Molch Island -1384 3665 0 1341 3645 0 Board;Boaty;33614 4 Battlefront -1384 3665 0 1408 3612 0 Board;Boaty;33614 4 Shayzien -1342 3645 0 1369 3639 0 Board;Boaty;33614 4 Molch Island -1342 3645 0 1384 3665 0 Board;Boaty;33614 4 Molch -1342 3645 0 1408 3612 0 Board;Boaty;33614 4 Shayzien -1408 3612 0 1369 3639 0 Board;Boaty;33614 4 Molch Island -1408 3612 0 1384 3665 0 Board;Boaty;33614 4 Molch -1408 3612 0 1341 3645 0 Board;Boaty;33614 4 Battlefront -1369 3639 0 1384 3665 0 Board;Boaty;33614 4 Molch -1369 3639 0 1341 3645 0 Board;Boaty;33614 4 Battlefront -1369 3639 0 1408 3612 0 Board;Boaty;33614 4 Shayzien +1342 3645 0 1369 3639 0 Board;Boaty;33614 5 Molch Island +1342 3645 0 1384 3665 0 Board;Boaty;33614 5 Battlefront +1342 3645 0 1408 3612 0 Board;Boaty;33614 5 Shayzien +1369 3639 0 1342 3645 0 Board;Boaty;33614 5 Molch +1369 3639 0 1384 3665 0 Board;Boaty;33614 5 Battlefront +1369 3639 0 1408 3612 0 Board;Boaty;33614 5 Shayzien +1384 3665 0 1369 3639 0 Board;Boaty;33614 5 Molch Island +1384 3665 0 1342 3645 0 Board;Boaty;33614 5 Molch +1384 3665 0 1408 3612 0 Board;Boaty;33614 5 Shayzien +1408 3612 0 1369 3639 0 Board;Boaty;33614 5 Molch Island +1408 3612 0 1342 3645 0 Board;Boaty;33614 5 Molch +1408 3612 0 1384 3665 0 Board;Boaty;33614 5 Battlefront # Burgh de Rott, Meiyerditch, Icyene Graveyard, Slepe 3525 3170 0 3605 3161 1 Board;Boat;38089 7255>41 13 1: Meiyerditch. diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/gnome_gliders.tsv b/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/gnome_gliders.tsv index 77ff10142db..5f84fe6cc45 100644 --- a/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/gnome_gliders.tsv +++ b/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/gnome_gliders.tsv @@ -1,36 +1,36 @@ # Origin Destination menuOption menuTarget objectID Quests Duration Display info # Ta Quir Priw (Gnome Stronghold) -2465 3501 3 Glider;Captain Errdo;10467 The Grand Tree +2465 3501 3 Glider;Captain Errdo;10467 The Grand Tree 8 # Gandius (Karamja) -2970 2972 0 Glider;Captain Klemfoodle;10479 The Grand Tree -2969 2973 0 Glider;Captain Klemfoodle;10479 The Grand Tree -2970 2974 0 Glider;Captain Klemfoodle;10479 The Grand Tree +2970 2972 0 Glider;Captain Klemfoodle;10479 The Grand Tree 8 +2969 2973 0 Glider;Captain Klemfoodle;10479 The Grand Tree 8 +2970 2974 0 Glider;Captain Klemfoodle;10479 The Grand Tree 8 # Kar-Hewo (Al-Kharid) -3284 3211 0 Glider;Captain Dalbur;10452 The Grand Tree -3285 3212 0 Glider;Captain Dalbur;10452 The Grand Tree -3284 3213 0 Glider;Captain Dalbur;10452 The Grand Tree +3284 3211 0 Glider;Captain Dalbur;10452 The Grand Tree 8 +3285 3212 0 Glider;Captain Dalbur;10452 The Grand Tree 8 +3284 3213 0 Glider;Captain Dalbur;10452 The Grand Tree 8 # Sindarpos (White Wolf Mountain) -2847 3498 0 Glider;Captain Bleemadge;10459 The Grand Tree -2846 3499 0 Glider;Captain Bleemadge;10459 The Grand Tree +2847 3498 0 Glider;Captain Bleemadge;10459 The Grand Tree 8 +2846 3499 0 Glider;Captain Bleemadge;10459 The Grand Tree 8 # Lemantolly Undri (Feldip Hills) -2544 2972 0 Glider;Gnormadium Avlafrim;7517 The Grand Tree;One Small Favour -2545 2973 0 Glider;Gnormadium Avlafrim;7517 The Grand Tree;One Small Favour -2544 2974 0 Glider;Gnormadium Avlafrim;7517 The Grand Tree;One Small Favour +2544 2972 0 Glider;Gnormadium Avlafrim;7517 The Grand Tree;One Small Favour 8 +2545 2973 0 Glider;Gnormadium Avlafrim;7517 The Grand Tree;One Small Favour 8 +2544 2974 0 Glider;Gnormadium Avlafrim;7517 The Grand Tree;One Small Favour 8 # Ookookolly Undri (Ape Atoll) -2711 2801 0 Glider;Captain Shoracks;7178 The Grand Tree;Monkey Madness II -2711 2803 0 Glider;Captain Shoracks;7178 The Grand Tree;Monkey Madness II -2710 2802 0 Glider;Captain Shoracks;7178 The Grand Tree;Monkey Madness II +2711 2801 0 Glider;Captain Shoracks;7178 The Grand Tree;Monkey Madness II 8 +2711 2803 0 Glider;Captain Shoracks;7178 The Grand Tree;Monkey Madness II 8 +2710 2802 0 Glider;Captain Shoracks;7178 The Grand Tree;Monkey Madness II 8 # Ta Quir Priw (Gnome Stronghold) - 2465 3501 3 The Grand Tree 4 Ta Quir Priw + 2465 3501 3 The Grand Tree 8 Ta Quir Priw # Gandius (Karamja) - 2971 2968 0 The Grand Tree 4 Gandius + 2971 2968 0 The Grand Tree 8 Gandius # Kar-Hewo (Al-Kharid) - 3284 3210 0 The Grand Tree 4 Kar-Hewo + 3284 3210 0 The Grand Tree 8 Kar-Hewo # Sindarpos (White Wolf Mountain) - 2850 3498 0 The Grand Tree 4 Sindarpos + 2850 3498 0 The Grand Tree 8 Sindarpos # Lemanto Andra (Digsite) - 3321 3429 0 The Grand Tree 4 Lemanto Andra + 3321 3429 0 The Grand Tree 8 Lemanto Andra # Lemantolly Undri (Feldip Hills) - 2550 2970 0 The Grand Tree;One Small Favour 4 Lemantolly Undri + 2550 2970 0 The Grand Tree;One Small Favour 8 Lemantolly Undri # Ookookolly Undri (Ape Atoll) - 2711 2803 0 The Grand Tree;Monkey Madness II 4 Ookookolly Undri \ No newline at end of file + 2711 2803 0 The Grand Tree;Monkey Madness II 8 Ookookolly Undri \ No newline at end of file diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/restrictions.tsv b/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/restrictions.tsv index b141d97bbe4..539c6d881fd 100644 --- a/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/restrictions.tsv +++ b/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/restrictions.tsv @@ -231,4 +231,9 @@ 2482 2875 0 6076=1 # Ruins of Unkah 3169 2818 0 -3169 2817 0 \ No newline at end of file +3169 2817 0 +# Castle Wars +2432 3118 0 +2431 3118 0 +2385 3068 0 +2385 3069 0 \ No newline at end of file diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/teleportation_spells.tsv b/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/teleportation_spells.tsv index 9a56d00bb83..6c7588084a5 100644 --- a/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/teleportation_spells.tsv +++ b/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/teleportation_spells.tsv @@ -42,7 +42,7 @@ 1680 3134 0 54 Magic Twilight's Promise 19 4070=0 Y 4 Civitas illa Fortis Teleport # Watchtower Teleport -2549 3112 2 58 Magic Watchtower 19 4070=0;4548=0 212=14 Y 10 Watchtower Teleport +2932 4711 2 58 Magic Watchtower 19 4070=0;4548=0 212=14 Y 10 Watchtower Teleport # Varbit 4460 is DIARY_ARDOUGNE_HARD 2584 3097 0 58 Magic Watchtower 19 4070=0;4460=1;4548=1 212=14 Y 10 Watchtower Teleport: Yanille diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/transports.tsv b/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/transports.tsv index 7d29a6082fc..4e011021b58 100644 --- a/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/transports.tsv +++ b/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/transports.tsv @@ -4692,9 +4692,9 @@ # watchtower 2933 4712 2 2549 3112 1 Climb-down;Ladder;2797 -2549 3112 1 2933 4712 2 Climb-down;Ladder;2797 +2549 3112 1 2933 4712 2 Climb-up;Ladder;2796 2544 3112 1 2544 3112 0 Climb-down;Ladder;17122 -2544 3112 0 2544 3112 1 Climb-down;Ladder;17122 +2544 3112 0 2544 3112 1 Climb-up;Ladder;2833 # kourend 1618 3680 0 1614 3680 1 Climb-up;Staircase;11807 @@ -4704,12 +4704,12 @@ 1639 3673 0 1666 10050 0 Investigate;Statue;27785 1666 10050 0 1639 3673 0 Climb-up;Vine ladder;28894 1562 3791 0 1617 10101 0 Enter;Hole;28921 5090=1 -1617 10101 0 1562 3791 0 Climb-up;Vine;28895 +1617 10101 0 1562 3791 0 Climb-up;Vine;28895 5090=1 1469 3653 0 1650 9987 0 Enter;Hole;28919 5080=1 -1650 9987 0 1469 3653 0 Climb-up;Vine;28895 +1650 9987 0 1469 3653 0 Climb-up;Vine;28895 5080=1 1696 3864 0 1719 10101 0 Enter;Hole;28920 5070=1 -1719 10101 0 1696 3864 0 Climb-up;Vine;28898 -1727 9993 0 1803 9968 0 Climb-up;Vine;28897 +1719 10101 0 1696 3864 0 Climb-up;Vine;28898 5070=1 +1727 9993 0 1803 9968 0 Climb-up;Vine;28897 5087=1 1803 9968 0 1727 9993 0 Enter;Strange passage;28918 5087=1 1670 3569 0 1800 9968 0 Climb-down;Staircase;34865 1800 9968 0 1670 3569 0 Climb-up;Staircase;34864 @@ -5185,3 +5185,7 @@ # CRASH SITE 2026 5611 0 2128 5647 0 Enter;Cavern Entrance;28686 2128 5647 0 2026 5611 0 Climb-up;Rope;28687 + +# Isle of Souls +2310 2919 0 2167 9308 0 Enter;Cave;40736 +2167 9308 0 2310 2919 0 Exit;Opening;40737 \ No newline at end of file diff --git a/runelite-client/src/main/resources/net/runelite/client/ui/microbot_splash.png b/runelite-client/src/main/resources/net/runelite/client/ui/microbot_splash.png new file mode 100644 index 00000000000..aca6febde06 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/ui/microbot_splash.png differ