From cec67f1a322288b948be4d5b05fab7767637df87 Mon Sep 17 00:00:00 2001 From: chsami Date: Sun, 18 Jan 2026 06:20:04 +0100 Subject: [PATCH 1/2] feat(breakhandler): implement plugin stop options with lead time configuration --- .../breakhandlerv2/BreakHandlerV2Config.java | 32 +++- .../breakhandlerv2/BreakHandlerV2Overlay.java | 11 +- .../breakhandlerv2/BreakHandlerV2Script.java | 169 ++++++++++++---- .../breakhandlerv2/MicrobotPluginChoice.java | 31 +++ .../breakhandlerv2/PluginStopHelper.java | 181 ++++++++++++++++++ .../breakhandlerv2/PluginStopOption.java | 69 +++++++ .../microbot/ui/MicrobotConfigPanel.java | 61 +++++- 7 files changed, 505 insertions(+), 49 deletions(-) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/PluginStopHelper.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/PluginStopOption.java diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Config.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Config.java index 81438679ab..f32cfd3313 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Config.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Config.java @@ -4,7 +4,7 @@ import net.runelite.client.plugins.microbot.util.antiban.enums.PlaySchedule; import net.runelite.client.plugins.microbot.util.world.RegionPreference; import net.runelite.client.plugins.microbot.util.world.WorldSelectionMode; -import net.runelite.client.plugins.microbot.breakhandler.breakhandlerv2.MicrobotPluginChoice; +import net.runelite.client.plugins.microbot.breakhandler.breakhandlerv2.PluginStopOption; @ConfigGroup(BreakHandlerV2Config.configGroup) public interface BreakHandlerV2Config extends Config { @@ -99,12 +99,36 @@ default boolean safetyCheck() { @ConfigItem( keyName = "pluginToStop", name = "Stop Plugin On Break", - description = "Select a Microbot plugin to stop automatically when a break begins.", + description = "Select a Microbot or Plugin Hub plugin to stop automatically when a break begins.", position = 2, section = breakBehaviorOptions ) - default MicrobotPluginChoice pluginToStop() { - return MicrobotPluginChoice.NONE; + default String pluginToStop() { + return PluginStopOption.NONE_VALUE; + } + + @ConfigItem( + keyName = "stopPluginLeadSeconds", + name = "Stop Lead Time (sec)", + description = "Stop the selected plugin this many seconds before a break starts.", + position = 3, + section = breakBehaviorOptions + ) + @Range(min = 0, max = 300) + default int stopPluginLeadSeconds() { + return 0; + } + + @ConfigItem( + keyName = "startPluginDelaySeconds", + name = "Restart Delay (sec)", + description = "Wait this many seconds after a break ends before restarting the stopped plugin.", + position = 4, + section = breakBehaviorOptions + ) + @Range(min = 0, max = 300) + default int startPluginDelaySeconds() { + return 0; } // ========== LOGIN & WORLD SECTION ========== diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Overlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Overlay.java index ff5978d486..579afdaeb8 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Overlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Overlay.java @@ -3,7 +3,7 @@ import lombok.extern.slf4j.Slf4j; import net.runelite.client.config.ConfigProfile; import net.runelite.client.plugins.microbot.Microbot; -import net.runelite.client.plugins.microbot.breakhandler.breakhandlerv2.MicrobotPluginChoice; +import net.runelite.client.plugins.microbot.breakhandler.breakhandlerv2.PluginStopHelper; import net.runelite.client.plugins.microbot.util.security.LoginManager; import net.runelite.client.ui.overlay.OverlayPanel; import net.runelite.client.ui.overlay.OverlayPosition; @@ -125,11 +125,14 @@ public Dimension render(Graphics2D graphics) { .rightColor(config.ignoreWorldSwitching() ? Color.GREEN : Color.LIGHT_GRAY) .build()); - MicrobotPluginChoice stopChoice = config.pluginToStop(); + String stopConfigValue = config.pluginToStop(); + String stopDisplay = PluginStopHelper.resolveDisplayName(stopConfigValue, Microbot.getPluginManager()); + boolean hasStopTarget = !PluginStopHelper.isNone(PluginStopHelper.normalizeStoredValue(stopConfigValue, Microbot.getPluginManager())); + panelComponent.getChildren().add(LineComponent.builder() .left("Stop plugin:") - .right(stopChoice != null ? stopChoice.toString() : MicrobotPluginChoice.NONE.toString()) - .rightColor(stopChoice != null && stopChoice != MicrobotPluginChoice.NONE ? Color.ORANGE : Color.LIGHT_GRAY) + .right(stopDisplay) + .rightColor(hasStopTarget ? Color.ORANGE : Color.LIGHT_GRAY) .build()); panelComponent.getChildren().add(LineComponent.builder() diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Script.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Script.java index 353f6c5dab..a2772b892a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Script.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Script.java @@ -9,13 +9,11 @@ import net.runelite.client.plugins.microbot.util.discord.Rs2Discord; 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.security.Login; import net.runelite.client.plugins.microbot.util.security.LoginManager; import net.runelite.client.plugins.microbot.util.world.Rs2WorldUtil; import net.runelite.client.ui.ClientUI; import net.runelite.http.api.worlds.WorldRegion; import net.runelite.client.plugins.Plugin; -import net.runelite.client.plugins.microbot.breakhandler.breakhandlerv2.MicrobotPluginChoice; import javax.inject.Singleton; import java.awt.Color; @@ -59,7 +57,9 @@ public BreakHandlerV2Script() { private String originalWindowTitle = ""; private boolean pluginStopTriggered = false; private boolean pluginRestartPending = false; - private MicrobotPluginChoice stoppedPluginChoice = MicrobotPluginChoice.NONE; + private String stoppedPluginClassName = PluginStopOption.NONE_VALUE; + private Instant pluginStopEarliestTime = Instant.MIN; + private Instant pluginRestartAllowedAt = Instant.MIN; // Persisted break keys private static final String PERSISTED_BREAK_END_KEY = "persistedBreakEnd"; @@ -179,6 +179,8 @@ private void handleWaitingForBreak() { return; } + applyPreBreakPluginStopLead(); + // When play schedule is enabled, take a break as soon as the schedule window ends if (config.usePlaySchedule()) { if (nextBreakTime != null && Instant.now().isAfter(nextBreakTime)) { @@ -463,6 +465,9 @@ private void handleLoginExtendedSleep() { private void handleBreakEnding() { log.info("[BreakHandlerV2] Break cycle complete"); + // set restart delay window + pluginRestartAllowedAt = Instant.now().plusSeconds(Math.max(0, config.startPluginDelaySeconds())); + startConfiguredPluginIfNeeded(); // Reset variables @@ -473,8 +478,8 @@ private void handleBreakEnding() { preBreakWorld = -1; unexpectedLogoutDetected = false; pluginStopTriggered = false; - pluginRestartPending = false; - stoppedPluginChoice = MicrobotPluginChoice.NONE; + pluginRestartAllowedAt = Instant.MIN; + pluginStopEarliestTime = Instant.MIN; // Unpause scripts Microbot.pauseAllScripts.set(false); @@ -696,15 +701,16 @@ private void scheduleNextBreak() { if (!config.playSchedule().isOutsideSchedule()) { Duration timeUntilEnd = config.playSchedule().timeUntilScheduleEnds(); nextBreakTime = Instant.now().plus(timeUntilEnd); - log.info("[BreakHandlerV2] Play schedule active ({}), break when schedule ends in {} minutes", - config.playSchedule().name(), timeUntilEnd.toMinutes()); - } else { - nextBreakTime = null; - log.info("[BreakHandlerV2] Outside play schedule ({}), currently on break", - config.playSchedule().name()); - } - return; - } + log.info("[BreakHandlerV2] Play schedule active ({}), break when schedule ends in {} minutes", + config.playSchedule().name(), timeUntilEnd.toMinutes()); + } else { + nextBreakTime = null; + log.info("[BreakHandlerV2] Outside play schedule ({}), currently on break", + config.playSchedule().name()); + } + updatePluginStopLeadTime(); + return; + } int minMinutes = config.minPlaytime(); int maxMinutes = config.maxPlaytime(); @@ -713,16 +719,54 @@ private void scheduleNextBreak() { nextBreakTime = Instant.now().plus(playtimeMinutes, ChronoUnit.MINUTES); log.info("[BreakHandlerV2] Next break in {} minutes", playtimeMinutes); + + updatePluginStopLeadTime(); } + /** + * Calculates when we should pre-stop the selected plugin before the break. + */ + private void updatePluginStopLeadTime() { + if (config == null) { + pluginStopEarliestTime = Instant.MIN; + return; + } + + int leadSeconds = Math.max(0, config.stopPluginLeadSeconds()); + if (nextBreakTime != null && leadSeconds > 0) { + pluginStopEarliestTime = nextBreakTime.minusSeconds(leadSeconds); + } else { + pluginStopEarliestTime = Instant.MIN; + } + } + + /** + * If within the lead window, stop the configured plugin ahead of the break. + */ + private void applyPreBreakPluginStopLead() { + updatePluginStopLeadTime(); + + if (pluginStopTriggered || config == null) { + return; + } + + if (pluginStopEarliestTime == null || pluginStopEarliestTime == Instant.MIN) { + return; + } + + if (Instant.now().isAfter(pluginStopEarliestTime) || Instant.now().equals(pluginStopEarliestTime)) { + stopConfiguredPluginIfNeeded(); + } + } + /** * Calculate break duration */ - private long calculateBreakDuration() { - // If outside play schedule, break until next play time - if (isOutsidePlaySchedule()) { - Duration timeUntilPlaySchedule = config.playSchedule().timeUntilNextSchedule(); - long durationMs = timeUntilPlaySchedule.toMillis(); + private long calculateBreakDuration() { + // If outside play schedule, break until next play time + if (isOutsidePlaySchedule()) { + Duration timeUntilPlaySchedule = config.playSchedule().timeUntilNextSchedule(); + long durationMs = timeUntilPlaySchedule.toMillis(); log.info("[BreakHandlerV2] Play schedule break duration: {} minutes (until next scheduled play time)", durationMs / 60000); return durationMs; @@ -745,28 +789,50 @@ private void stopConfiguredPluginIfNeeded() { return; } - MicrobotPluginChoice choice = config.pluginToStop(); - if (choice == null || choice == MicrobotPluginChoice.NONE) { + // Respect pre-break lead time while waiting + if (BreakHandlerV2State.getCurrentState() == BreakHandlerV2State.WAITING_FOR_BREAK + && pluginStopEarliestTime != null + && pluginStopEarliestTime != Instant.MIN + && Instant.now().isBefore(pluginStopEarliestTime)) { return; } - Class pluginClass = choice.getPluginClass(); - if (pluginClass == null) { - log.warn("[BreakHandlerV2] Selected plugin {} has no mapped class; skipping stop request", choice); - pluginStopTriggered = true; + String normalizedSelection = PluginStopHelper.normalizeStoredValue(config.pluginToStop(), Microbot.getPluginManager()); + if (PluginStopHelper.isNone(normalizedSelection)) { + return; + } + + Plugin pluginInstance = PluginStopHelper.findPluginInstance( + normalizedSelection, + config.pluginToStop(), + Microbot.getPluginManager()); + + if (pluginInstance == null) { + log.warn("[BreakHandlerV2] Could not resolve plugin to stop for value '{}' (normalized '{}')", + config.pluginToStop(), normalizedSelection); return; } - Plugin pluginInstance = Microbot.getPlugin(pluginClass); boolean wasEnabled = Microbot.isPluginEnabled(pluginInstance); + boolean stopResult = Microbot.stopPlugin(pluginInstance); + boolean nowEnabled = Microbot.isPluginEnabled(pluginInstance); - boolean stopped = Microbot.stopPlugin(pluginClass); - log.info("[BreakHandlerV2] Stop request for {} -> {}", choice, stopped ? "stopped/closed" : "not active"); + log.info("[BreakHandlerV2] Stop request for {} -> {} (wasEnabled={}, nowEnabled={}, stopResult={})", + PluginStopHelper.resolveDisplayName(normalizedSelection, Microbot.getPluginManager()), + (!nowEnabled) ? "stopped/closed" : "still active", + wasEnabled, nowEnabled, stopResult); - pluginStopTriggered = true; - if (wasEnabled) { + sleep(5000); + + if (!nowEnabled) { + pluginStopTriggered = true; pluginRestartPending = true; - stoppedPluginChoice = choice; + stoppedPluginClassName = normalizedSelection; + // Ensure any running scripts pause while stopped + Microbot.pauseAllScripts.set(true); + } else { + // Leave pluginStopTriggered false so we can retry on the next tick + pluginRestartPending = false; } } @@ -774,18 +840,41 @@ private void stopConfiguredPluginIfNeeded() { * Starts previously stopped plugin after break ends. */ private void startConfiguredPluginIfNeeded() { - if (!pluginRestartPending || stoppedPluginChoice == null || stoppedPluginChoice == MicrobotPluginChoice.NONE) { + if (!pluginRestartPending || PluginStopHelper.isNone(stoppedPluginClassName)) { return; } - Class pluginClass = stoppedPluginChoice.getPluginClass(); - if (pluginClass != null) { - boolean started = Microbot.startPlugin(pluginClass); - log.info("[BreakHandlerV2] Restart request for {} -> {}", stoppedPluginChoice, started ? "started" : "not started"); + if (pluginRestartAllowedAt != null + && pluginRestartAllowedAt != Instant.MIN + && Instant.now().isBefore(pluginRestartAllowedAt)) { + return; } - pluginRestartPending = false; - stoppedPluginChoice = MicrobotPluginChoice.NONE; + Plugin pluginInstance = PluginStopHelper.findPluginInstance( + stoppedPluginClassName, + stoppedPluginClassName, + Microbot.getPluginManager()); + + if (pluginInstance == null) { + log.warn("[BreakHandlerV2] Could not resolve plugin to restart for stored class '{}'", stoppedPluginClassName); + } + + boolean started = pluginInstance != null + ? Microbot.startPlugin(pluginInstance) + : Microbot.startPlugin(stoppedPluginClassName); + boolean nowEnabled = Microbot.isPluginEnabled(pluginInstance != null ? pluginInstance : Microbot.getPlugin(stoppedPluginClassName)); + + log.info("[BreakHandlerV2] Restart request for {} -> {} (startedCall={}, nowEnabled={})", + PluginStopHelper.resolveDisplayName(stoppedPluginClassName, Microbot.getPluginManager()), + nowEnabled ? "running" : "not running", + started, nowEnabled); + + if (nowEnabled) { + Microbot.pauseAllScripts.set(false); + pluginRestartPending = false; + stoppedPluginClassName = PluginStopOption.NONE_VALUE; + pluginRestartAllowedAt = Instant.MIN; + } } /** @@ -885,7 +974,9 @@ public void shutdown() { logoutBreakActive = false; pluginStopTriggered = false; pluginRestartPending = false; - stoppedPluginChoice = MicrobotPluginChoice.NONE; + stoppedPluginClassName = PluginStopOption.NONE_VALUE; + pluginRestartAllowedAt = Instant.MIN; + pluginStopEarliestTime = Instant.MIN; } private void updateWindowTitle() { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/MicrobotPluginChoice.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/MicrobotPluginChoice.java index 9e322bcfde..98048aeb46 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/MicrobotPluginChoice.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/MicrobotPluginChoice.java @@ -33,6 +33,37 @@ public enum MicrobotPluginChoice { this.pluginClass = pluginClass; } + /** + * Returns the fully qualified class name for this choice, or {@code null} for NONE. + */ + public String getClassName() { + return pluginClass != null ? pluginClass.getName() : null; + } + + /** + * Attempts to resolve a stored configuration value (enum name, display name, or class name) + * back to a {@link MicrobotPluginChoice}. + * + * @param value persisted config value + * @return optional matching choice + */ + public static java.util.Optional fromConfigValue(String value) { + if (value == null) { + return java.util.Optional.empty(); + } + + for (MicrobotPluginChoice choice : values()) { + if (choice.name().equalsIgnoreCase(value) + || choice.displayName.equalsIgnoreCase(value) + || (choice.pluginClass != null && choice.pluginClass.getName().equalsIgnoreCase(value)) + || (choice.pluginClass != null && choice.pluginClass.getSimpleName().equalsIgnoreCase(value))) { + return java.util.Optional.of(choice); + } + } + + return java.util.Optional.empty(); + } + @Override public String toString() { return displayName; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/PluginStopHelper.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/PluginStopHelper.java new file mode 100644 index 0000000000..49a3b14ca9 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/PluginStopHelper.java @@ -0,0 +1,181 @@ +package net.runelite.client.plugins.microbot.breakhandler.breakhandlerv2; + +import com.google.common.base.Strings; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.plugins.PluginManager; +import net.runelite.client.util.Text; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Utility helpers for mapping BreakHandler V2 "Stop plugin" selections to actual plugins. + */ +public final class PluginStopHelper { + + private PluginStopHelper() { + } + + /** + * Normalises a stored config value to a fully qualified class name or {@link PluginStopOption#NONE_VALUE}. + * Accepts legacy enum names and display names from {@link MicrobotPluginChoice}. + */ + public static String normalizeStoredValue(String rawValue, PluginManager pluginManager) { + if (Strings.isNullOrEmpty(rawValue)) { + return PluginStopOption.NONE_VALUE; + } + + Optional builtIn = MicrobotPluginChoice.fromConfigValue(rawValue); + if (builtIn.isPresent()) { + String className = builtIn.get().getClassName(); + return className == null ? PluginStopOption.NONE_VALUE : className; + } + + String sanitizedRaw = sanitize(rawValue); + + if (pluginManager != null) { + for (PluginStopOption option : buildOptions(pluginManager)) { + if (sanitize(option.getDisplayName()).equalsIgnoreCase(sanitizedRaw)) { + return option.getClassName(); + } + } + + for (Plugin plugin : pluginManager.getPlugins()) { + PluginDescriptor descriptor = plugin.getClass().getAnnotation(PluginDescriptor.class); + String className = plugin.getClass().getName(); + String simple = plugin.getClass().getSimpleName(); + String descriptorName = descriptor != null ? sanitize(descriptor.name()) : ""; + + if (sanitizedRaw.equalsIgnoreCase(simple) + || (!descriptorName.isEmpty() && sanitizedRaw.equalsIgnoreCase(descriptorName))) { + return className; + } + } + } + + return rawValue; + } + + /** + * Backwards-compatible normalizer that doesn't have PluginManager context. + */ + public static String normalizeStoredValue(String rawValue) { + return normalizeStoredValue(rawValue, null); + } + + public static boolean isNone(String normalizedValue) { + return Strings.isNullOrEmpty(normalizedValue) + || PluginStopOption.NONE_VALUE.equalsIgnoreCase(normalizedValue); + } + + /** + * Builds the combined list of built-in Microbot plugins and installed external (Plugin Hub) plugins. + */ + public static List buildOptions(PluginManager pluginManager) { + List options = new ArrayList<>(); + options.add(PluginStopOption.none()); + + for (MicrobotPluginChoice choice : MicrobotPluginChoice.values()) { + if (choice == MicrobotPluginChoice.NONE || choice.getClassName() == null) { + continue; + } + options.add(PluginStopOption.builtIn(choice.toString(), choice.getClassName())); + } + + if (pluginManager != null) { + List external = pluginManager.getPlugins().stream() + .filter(p -> { + PluginDescriptor descriptor = p.getClass().getAnnotation(PluginDescriptor.class); + return descriptor != null && descriptor.isExternal() && !descriptor.hidden(); + }) + .map(p -> { + PluginDescriptor descriptor = p.getClass().getAnnotation(PluginDescriptor.class); + String rawName = descriptor != null && !Strings.isNullOrEmpty(descriptor.name()) + ? descriptor.name() + : p.getClass().getSimpleName(); + String displayName = sanitize(rawName); + return PluginStopOption.external(displayName, p.getClass().getName()); + }) + .sorted(Comparator.comparing(PluginStopOption::getDisplayName, String.CASE_INSENSITIVE_ORDER)) + .collect(Collectors.toList()); + + options.addAll(external); + } + + // Deduplicate by class name while preserving order (built-ins first, then external). + Map deduped = options.stream() + .collect(Collectors.toMap( + PluginStopOption::getClassName, + Function.identity(), + (first, ignored) -> first, + LinkedHashMap::new + )); + + return new ArrayList<>(deduped.values()); + } + + /** + * Resolves a friendly display name for the stored config value. + */ + public static String resolveDisplayName(String rawValue, PluginManager pluginManager) { + String normalized = normalizeStoredValue(rawValue, pluginManager); + + if (isNone(normalized)) { + return MicrobotPluginChoice.NONE.toString(); + } + + Optional builtIn = MicrobotPluginChoice.fromConfigValue(normalized); + if (builtIn.isPresent()) { + return sanitize(builtIn.get().toString()); + } + + if (pluginManager != null) { + for (Plugin plugin : pluginManager.getPlugins()) { + if (plugin.getClass().getName().equals(normalized)) { + PluginDescriptor descriptor = plugin.getClass().getAnnotation(PluginDescriptor.class); + if (descriptor != null && !Strings.isNullOrEmpty(descriptor.name())) { + return sanitize(descriptor.name()); + } + return sanitize(plugin.getClass().getSimpleName()); + } + } + } + + return sanitize(normalized); + } + + public static String sanitize(String value) { + return Text.removeTags(Strings.nullToEmpty(value)).trim(); + } + + /** + * Attempts to find a plugin instance using a normalized class name or a raw/sanitized label. + */ + public static Plugin findPluginInstance(String normalizedClassName, String rawValue, PluginManager pluginManager) { + if (pluginManager == null) { + return null; + } + + String sanitizedRaw = sanitize(rawValue); + + for (Plugin plugin : pluginManager.getPlugins()) { + Class clazz = plugin.getClass(); + PluginDescriptor descriptor = clazz.getAnnotation(PluginDescriptor.class); + String className = clazz.getName(); + String simpleName = clazz.getSimpleName(); + String descriptorName = descriptor != null ? sanitize(descriptor.name()) : ""; + + if (className.equals(normalizedClassName) + || className.equalsIgnoreCase(rawValue) + || simpleName.equalsIgnoreCase(normalizedClassName) + || simpleName.equalsIgnoreCase(sanitizedRaw) + || (!descriptorName.isEmpty() && descriptorName.equalsIgnoreCase(sanitizedRaw))) { + return plugin; + } + } + + return null; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/PluginStopOption.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/PluginStopOption.java new file mode 100644 index 0000000000..4eac97e2d8 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/PluginStopOption.java @@ -0,0 +1,69 @@ +package net.runelite.client.plugins.microbot.breakhandler.breakhandlerv2; + +import java.util.Objects; + +/** + * Simple DTO representing a selectable plugin target. + * Uses plain class form for Java 11 compatibility (no records). + */ +public final class PluginStopOption { + public static final String NONE_VALUE = "NONE"; + + private final String displayName; + private final String className; + private final boolean external; + + private PluginStopOption(String displayName, String className, boolean external) { + this.displayName = displayName; + this.className = className; + this.external = external; + } + + public static PluginStopOption none() { + return new PluginStopOption("None", NONE_VALUE, false); + } + + public static PluginStopOption builtIn(String displayName, String className) { + return new PluginStopOption(displayName, className, false); + } + + public static PluginStopOption external(String displayName, String className) { + return new PluginStopOption(displayName, className, true); + } + + public String getDisplayName() { + return displayName; + } + + public String getClassName() { + return className; + } + + public boolean isExternal() { + return external; + } + + public boolean isNone() { + return NONE_VALUE.equalsIgnoreCase(className); + } + + @Override + public String toString() { + return displayName; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PluginStopOption)) return false; + PluginStopOption that = (PluginStopOption) o; + return external == that.external && + Objects.equals(displayName, that.displayName) && + Objects.equals(className, that.className); + } + + @Override + public int hashCode() { + return Objects.hash(displayName, className, external); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/ui/MicrobotConfigPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/ui/MicrobotConfigPanel.java index 6dfcae8a9d..adc7d27d5e 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/ui/MicrobotConfigPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/ui/MicrobotConfigPanel.java @@ -39,6 +39,9 @@ import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginManager; import net.runelite.client.plugins.microbot.MicrobotConfigManager; +import net.runelite.client.plugins.microbot.breakhandler.breakhandlerv2.BreakHandlerV2Config; +import net.runelite.client.plugins.microbot.breakhandler.breakhandlerv2.PluginStopHelper; +import net.runelite.client.plugins.microbot.breakhandler.breakhandlerv2.PluginStopOption; import net.runelite.client.plugins.microbot.inventorysetups.InventorySetup; import net.runelite.client.plugins.microbot.inventorysetups.MInventorySetupsPlugin; import net.runelite.client.plugins.microbot.mouserecorder.MouseMacroRecorderPlugin; @@ -364,7 +367,9 @@ public void mouseClicked(MouseEvent e) { item.add(configEntryName, BorderLayout.CENTER); } - if (cid.getType() == boolean.class) { + if (isBreakHandlerStopConfig(cd, cid)) { + item.add(createPluginStopComboBox(cd, cid), BorderLayout.EAST); + } else if (cid.getType() == boolean.class) { item.add(createCheckbox(cd, cid), BorderLayout.EAST); } else if (cid.getType() == int.class) { item.add(createIntSpinner(cd, cid), BorderLayout.EAST); @@ -433,6 +438,53 @@ public void mouseClicked(MouseEvent e) { applyFilter(); } + private boolean isBreakHandlerStopConfig(ConfigDescriptor cd, ConfigItemDescriptor cid) { + return BreakHandlerV2Config.configGroup.equals(cd.getGroup().value()) + && "pluginToStop".equals(cid.getItem().keyName()); + } + + private JComboBox createPluginStopComboBox(ConfigDescriptor cd, ConfigItemDescriptor cid) { + List options = PluginStopHelper.buildOptions(pluginManager); + JComboBox box = new JComboBox<>(new DefaultComboBoxModel<>(options.toArray(new PluginStopOption[0]))); + + box.setRenderer(new DefaultListCellRenderer() { + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if (value instanceof PluginStopOption) { + setText(((PluginStopOption) value).getDisplayName()); + } + return this; + } + }); + + String storedRaw = configManager.getConfiguration(cd.getGroup().value(), cid.getItem().keyName()); + String normalized = PluginStopHelper.normalizeStoredValue(storedRaw, pluginManager); + PluginStopOption selected = options.stream() + .filter(opt -> opt.getClassName().equals(normalized)) + .findFirst() + .orElse(PluginStopOption.none()); + + // migrate legacy enum values to class names + if (!Objects.equals(storedRaw, normalized)) { + configManager.setConfiguration(cd.getGroup().value(), cid.getItem().keyName(), normalized); + } + + box.setSelectedItem(selected); + box.setToolTipText(selected.getDisplayName()); + box.setPreferredSize(new Dimension(box.getPreferredSize().width, 22)); + + box.addItemListener(e -> { + if (e.getStateChange() == ItemEvent.SELECTED) { + PluginStopOption opt = (PluginStopOption) e.getItem(); + configManager.setConfiguration(cd.getGroup().value(), cid.getItem().keyName(), opt.getClassName()); + box.setToolTipText(opt.getDisplayName()); + } + }); + + return box; + } + private void buildInformationPanel(ConfigInformation ci) { // Create the main panel (similar to a Bootstrap panel) JPanel panel = new JPanel(); @@ -813,7 +865,12 @@ private void changeConfiguration(Component component, ConfigDescriptor cd, Confi configManager.setConfiguration(cd.getGroup().value(), cid.getItem().keyName(), colorPicker.getSelectedColor().getRGB() + ""); } else if (component instanceof JComboBox) { JComboBox jComboBox = (JComboBox) component; - configManager.setConfiguration(cd.getGroup().value(), cid.getItem().keyName(), ((Enum) jComboBox.getSelectedItem()).name()); + Object selected = jComboBox.getSelectedItem(); + if (selected instanceof Enum) { + configManager.setConfiguration(cd.getGroup().value(), cid.getItem().keyName(), ((Enum) selected).name()); + } else { + configManager.setConfiguration(cd.getGroup().value(), cid.getItem().keyName(), selected != null ? selected.toString() : ""); + } } else if (component instanceof MicrobotHotkeyButton) { MicrobotHotkeyButton hotkeyButton = (MicrobotHotkeyButton) component; configManager.setConfiguration(cd.getGroup().value(), cid.getItem().keyName(), hotkeyButton.getValue()); From 6fc0a76fc0a7af99b043b5bd3c34f3ffa8bfe5c3 Mon Sep 17 00:00:00 2001 From: chsami Date: Sun, 18 Jan 2026 06:20:20 +0100 Subject: [PATCH 2/2] fix(gradle): update microbot version to 2.1.12 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index c34675e171..10f9b5a860 100644 --- a/gradle.properties +++ b/gradle.properties @@ -31,7 +31,7 @@ project.build.group=net.runelite project.build.version=1.12.12.1 glslang.path= -microbot.version=2.1.11 +microbot.version=2.1.12 microbot.commit.sha=nogit microbot.repo.url=http://138.201.81.246:8081/repository/microbot-snapshot/ microbot.repo.username=