Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 ==========
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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
Expand All @@ -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);
Expand Down Expand Up @@ -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();
Expand All @@ -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;
Expand All @@ -745,47 +789,92 @@ 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<? extends Plugin> 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;
}
}

/**
* Starts previously stopped plugin after break ends.
*/
private void startConfiguredPluginIfNeeded() {
if (!pluginRestartPending || stoppedPluginChoice == null || stoppedPluginChoice == MicrobotPluginChoice.NONE) {
if (!pluginRestartPending || PluginStopHelper.isNone(stoppedPluginClassName)) {
return;
}

Class<? extends Plugin> 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;
}
}

/**
Expand Down Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<MicrobotPluginChoice> 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;
Expand Down
Loading