99import net .runelite .client .plugins .microbot .util .discord .Rs2Discord ;
1010import net .runelite .client .plugins .microbot .util .math .Rs2Random ;
1111import net .runelite .client .plugins .microbot .util .player .Rs2Player ;
12- import net .runelite .client .plugins .microbot .util .security .Login ;
1312import net .runelite .client .plugins .microbot .util .security .LoginManager ;
1413import net .runelite .client .plugins .microbot .util .world .Rs2WorldUtil ;
1514import net .runelite .client .ui .ClientUI ;
1615import net .runelite .http .api .worlds .WorldRegion ;
1716import net .runelite .client .plugins .Plugin ;
18- import net .runelite .client .plugins .microbot .breakhandler .breakhandlerv2 .MicrobotPluginChoice ;
1917
2018import javax .inject .Singleton ;
2119import java .awt .Color ;
@@ -59,7 +57,9 @@ public BreakHandlerV2Script() {
5957 private String originalWindowTitle = "" ;
6058 private boolean pluginStopTriggered = false ;
6159 private boolean pluginRestartPending = false ;
62- private MicrobotPluginChoice stoppedPluginChoice = MicrobotPluginChoice .NONE ;
60+ private String stoppedPluginClassName = PluginStopOption .NONE_VALUE ;
61+ private Instant pluginStopEarliestTime = Instant .MIN ;
62+ private Instant pluginRestartAllowedAt = Instant .MIN ;
6363
6464 // Persisted break keys
6565 private static final String PERSISTED_BREAK_END_KEY = "persistedBreakEnd" ;
@@ -179,6 +179,8 @@ private void handleWaitingForBreak() {
179179 return ;
180180 }
181181
182+ applyPreBreakPluginStopLead ();
183+
182184 // When play schedule is enabled, take a break as soon as the schedule window ends
183185 if (config .usePlaySchedule ()) {
184186 if (nextBreakTime != null && Instant .now ().isAfter (nextBreakTime )) {
@@ -463,6 +465,9 @@ private void handleLoginExtendedSleep() {
463465 private void handleBreakEnding () {
464466 log .info ("[BreakHandlerV2] Break cycle complete" );
465467
468+ // set restart delay window
469+ pluginRestartAllowedAt = Instant .now ().plusSeconds (Math .max (0 , config .startPluginDelaySeconds ()));
470+
466471 startConfiguredPluginIfNeeded ();
467472
468473 // Reset variables
@@ -473,8 +478,8 @@ private void handleBreakEnding() {
473478 preBreakWorld = -1 ;
474479 unexpectedLogoutDetected = false ;
475480 pluginStopTriggered = false ;
476- pluginRestartPending = false ;
477- stoppedPluginChoice = MicrobotPluginChoice . NONE ;
481+ pluginRestartAllowedAt = Instant . MIN ;
482+ pluginStopEarliestTime = Instant . MIN ;
478483
479484 // Unpause scripts
480485 Microbot .pauseAllScripts .set (false );
@@ -696,15 +701,16 @@ private void scheduleNextBreak() {
696701 if (!config .playSchedule ().isOutsideSchedule ()) {
697702 Duration timeUntilEnd = config .playSchedule ().timeUntilScheduleEnds ();
698703 nextBreakTime = Instant .now ().plus (timeUntilEnd );
699- log .info ("[BreakHandlerV2] Play schedule active ({}), break when schedule ends in {} minutes" ,
700- config .playSchedule ().name (), timeUntilEnd .toMinutes ());
701- } else {
702- nextBreakTime = null ;
703- log .info ("[BreakHandlerV2] Outside play schedule ({}), currently on break" ,
704- config .playSchedule ().name ());
705- }
706- return ;
707- }
704+ log .info ("[BreakHandlerV2] Play schedule active ({}), break when schedule ends in {} minutes" ,
705+ config .playSchedule ().name (), timeUntilEnd .toMinutes ());
706+ } else {
707+ nextBreakTime = null ;
708+ log .info ("[BreakHandlerV2] Outside play schedule ({}), currently on break" ,
709+ config .playSchedule ().name ());
710+ }
711+ updatePluginStopLeadTime ();
712+ return ;
713+ }
708714
709715 int minMinutes = config .minPlaytime ();
710716 int maxMinutes = config .maxPlaytime ();
@@ -713,16 +719,54 @@ private void scheduleNextBreak() {
713719 nextBreakTime = Instant .now ().plus (playtimeMinutes , ChronoUnit .MINUTES );
714720
715721 log .info ("[BreakHandlerV2] Next break in {} minutes" , playtimeMinutes );
722+
723+ updatePluginStopLeadTime ();
716724 }
717725
726+ /**
727+ * Calculates when we should pre-stop the selected plugin before the break.
728+ */
729+ private void updatePluginStopLeadTime () {
730+ if (config == null ) {
731+ pluginStopEarliestTime = Instant .MIN ;
732+ return ;
733+ }
734+
735+ int leadSeconds = Math .max (0 , config .stopPluginLeadSeconds ());
736+ if (nextBreakTime != null && leadSeconds > 0 ) {
737+ pluginStopEarliestTime = nextBreakTime .minusSeconds (leadSeconds );
738+ } else {
739+ pluginStopEarliestTime = Instant .MIN ;
740+ }
741+ }
742+
743+ /**
744+ * If within the lead window, stop the configured plugin ahead of the break.
745+ */
746+ private void applyPreBreakPluginStopLead () {
747+ updatePluginStopLeadTime ();
748+
749+ if (pluginStopTriggered || config == null ) {
750+ return ;
751+ }
752+
753+ if (pluginStopEarliestTime == null || pluginStopEarliestTime == Instant .MIN ) {
754+ return ;
755+ }
756+
757+ if (Instant .now ().isAfter (pluginStopEarliestTime ) || Instant .now ().equals (pluginStopEarliestTime )) {
758+ stopConfiguredPluginIfNeeded ();
759+ }
760+ }
761+
718762 /**
719763 * Calculate break duration
720764 */
721- private long calculateBreakDuration () {
722- // If outside play schedule, break until next play time
723- if (isOutsidePlaySchedule ()) {
724- Duration timeUntilPlaySchedule = config .playSchedule ().timeUntilNextSchedule ();
725- long durationMs = timeUntilPlaySchedule .toMillis ();
765+ private long calculateBreakDuration () {
766+ // If outside play schedule, break until next play time
767+ if (isOutsidePlaySchedule ()) {
768+ Duration timeUntilPlaySchedule = config .playSchedule ().timeUntilNextSchedule ();
769+ long durationMs = timeUntilPlaySchedule .toMillis ();
726770 log .info ("[BreakHandlerV2] Play schedule break duration: {} minutes (until next scheduled play time)" ,
727771 durationMs / 60000 );
728772 return durationMs ;
@@ -745,47 +789,92 @@ private void stopConfiguredPluginIfNeeded() {
745789 return ;
746790 }
747791
748- MicrobotPluginChoice choice = config .pluginToStop ();
749- if (choice == null || choice == MicrobotPluginChoice .NONE ) {
792+ // Respect pre-break lead time while waiting
793+ if (BreakHandlerV2State .getCurrentState () == BreakHandlerV2State .WAITING_FOR_BREAK
794+ && pluginStopEarliestTime != null
795+ && pluginStopEarliestTime != Instant .MIN
796+ && Instant .now ().isBefore (pluginStopEarliestTime )) {
750797 return ;
751798 }
752799
753- Class <? extends Plugin > pluginClass = choice .getPluginClass ();
754- if (pluginClass == null ) {
755- log .warn ("[BreakHandlerV2] Selected plugin {} has no mapped class; skipping stop request" , choice );
756- pluginStopTriggered = true ;
800+ String normalizedSelection = PluginStopHelper .normalizeStoredValue (config .pluginToStop (), Microbot .getPluginManager ());
801+ if (PluginStopHelper .isNone (normalizedSelection )) {
802+ return ;
803+ }
804+
805+ Plugin pluginInstance = PluginStopHelper .findPluginInstance (
806+ normalizedSelection ,
807+ config .pluginToStop (),
808+ Microbot .getPluginManager ());
809+
810+ if (pluginInstance == null ) {
811+ log .warn ("[BreakHandlerV2] Could not resolve plugin to stop for value '{}' (normalized '{}')" ,
812+ config .pluginToStop (), normalizedSelection );
757813 return ;
758814 }
759815
760- Plugin pluginInstance = Microbot .getPlugin (pluginClass );
761816 boolean wasEnabled = Microbot .isPluginEnabled (pluginInstance );
817+ boolean stopResult = Microbot .stopPlugin (pluginInstance );
818+ boolean nowEnabled = Microbot .isPluginEnabled (pluginInstance );
762819
763- boolean stopped = Microbot .stopPlugin (pluginClass );
764- log .info ("[BreakHandlerV2] Stop request for {} -> {}" , choice , stopped ? "stopped/closed" : "not active" );
820+ log .info ("[BreakHandlerV2] Stop request for {} -> {} (wasEnabled={}, nowEnabled={}, stopResult={})" ,
821+ PluginStopHelper .resolveDisplayName (normalizedSelection , Microbot .getPluginManager ()),
822+ (!nowEnabled ) ? "stopped/closed" : "still active" ,
823+ wasEnabled , nowEnabled , stopResult );
765824
766- pluginStopTriggered = true ;
767- if (wasEnabled ) {
825+ sleep (5000 );
826+
827+ if (!nowEnabled ) {
828+ pluginStopTriggered = true ;
768829 pluginRestartPending = true ;
769- stoppedPluginChoice = choice ;
830+ stoppedPluginClassName = normalizedSelection ;
831+ // Ensure any running scripts pause while stopped
832+ Microbot .pauseAllScripts .set (true );
833+ } else {
834+ // Leave pluginStopTriggered false so we can retry on the next tick
835+ pluginRestartPending = false ;
770836 }
771837 }
772838
773839 /**
774840 * Starts previously stopped plugin after break ends.
775841 */
776842 private void startConfiguredPluginIfNeeded () {
777- if (!pluginRestartPending || stoppedPluginChoice == null || stoppedPluginChoice == MicrobotPluginChoice . NONE ) {
843+ if (!pluginRestartPending || PluginStopHelper . isNone ( stoppedPluginClassName ) ) {
778844 return ;
779845 }
780846
781- Class <? extends Plugin > pluginClass = stoppedPluginChoice . getPluginClass ();
782- if ( pluginClass != null ) {
783- boolean started = Microbot . startPlugin ( pluginClass );
784- log . info ( "[BreakHandlerV2] Restart request for {} -> {}" , stoppedPluginChoice , started ? "started" : "not started" ) ;
847+ if ( pluginRestartAllowedAt != null
848+ && pluginRestartAllowedAt != Instant . MIN
849+ && Instant . now (). isBefore ( pluginRestartAllowedAt )) {
850+ return ;
785851 }
786852
787- pluginRestartPending = false ;
788- stoppedPluginChoice = MicrobotPluginChoice .NONE ;
853+ Plugin pluginInstance = PluginStopHelper .findPluginInstance (
854+ stoppedPluginClassName ,
855+ stoppedPluginClassName ,
856+ Microbot .getPluginManager ());
857+
858+ if (pluginInstance == null ) {
859+ log .warn ("[BreakHandlerV2] Could not resolve plugin to restart for stored class '{}'" , stoppedPluginClassName );
860+ }
861+
862+ boolean started = pluginInstance != null
863+ ? Microbot .startPlugin (pluginInstance )
864+ : Microbot .startPlugin (stoppedPluginClassName );
865+ boolean nowEnabled = Microbot .isPluginEnabled (pluginInstance != null ? pluginInstance : Microbot .getPlugin (stoppedPluginClassName ));
866+
867+ log .info ("[BreakHandlerV2] Restart request for {} -> {} (startedCall={}, nowEnabled={})" ,
868+ PluginStopHelper .resolveDisplayName (stoppedPluginClassName , Microbot .getPluginManager ()),
869+ nowEnabled ? "running" : "not running" ,
870+ started , nowEnabled );
871+
872+ if (nowEnabled ) {
873+ Microbot .pauseAllScripts .set (false );
874+ pluginRestartPending = false ;
875+ stoppedPluginClassName = PluginStopOption .NONE_VALUE ;
876+ pluginRestartAllowedAt = Instant .MIN ;
877+ }
789878 }
790879
791880 /**
@@ -885,7 +974,9 @@ public void shutdown() {
885974 logoutBreakActive = false ;
886975 pluginStopTriggered = false ;
887976 pluginRestartPending = false ;
888- stoppedPluginChoice = MicrobotPluginChoice .NONE ;
977+ stoppedPluginClassName = PluginStopOption .NONE_VALUE ;
978+ pluginRestartAllowedAt = Instant .MIN ;
979+ pluginStopEarliestTime = Instant .MIN ;
889980 }
890981
891982 private void updateWindowTitle () {
0 commit comments