diff --git a/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarOverlay.java b/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarOverlay.java index f0b6cb63bb..2a624756a0 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarOverlay.java +++ b/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarOverlay.java @@ -15,6 +15,7 @@ import net.runelite.client.plugins.microbot.shootingstar.ShootingStarConfig; import net.runelite.client.plugins.microbot.shootingstar.ShootingStarPlugin; import net.runelite.client.plugins.microbot.shootingstar.ShootingStarScript; +import net.runelite.client.plugins.microbot.shootingstar.enums.ShootingStarState; import net.runelite.client.plugins.microbot.util.misc.TimeUtils; import net.runelite.client.ui.overlay.OverlayPanel; import net.runelite.client.ui.overlay.OverlayPosition; @@ -77,11 +78,12 @@ public Dimension render(Graphics2D graphics) if (!config.isHideDevOverlay()) { - if (ShootingStarScript.state != null) + ShootingStarState state = ShootingStarScript.state; + if (state != null) { panelComponent.getChildren().add(LineComponent.builder() .left("Current State:") - .right(ShootingStarScript.state.name()) + .right(state.name()) .build()); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarPlugin.java index 45d9ed1082..41a2e68d58 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarPlugin.java @@ -76,7 +76,7 @@ public class ShootingStarPlugin extends Plugin { - static final String version = "1.4.10"; + static final String version = "1.4.11"; @Getter private final CopyOnWriteArrayList starList = new CopyOnWriteArrayList<>(); @@ -278,7 +278,7 @@ public void onChatMessage(ChatMessage event) { if (event.getMessage().equalsIgnoreCase("oh dear, you are dead!") && config.shutdownOnDeath()) { Rs2Walker.setTarget(null); - shutDown(); + Microbot.stopPlugin(this); } } @@ -412,16 +412,29 @@ private void removePanel() public void updatePanelList(boolean fullUpdate) { - List stars = new ArrayList<>(starList); - - if (fullUpdate) - { - SwingUtilities.invokeLater(() -> panel.updateList(stars)); - } - else + if (panel == null) { - SwingUtilities.invokeLater(() -> panel.refreshList(stars)); + return; } + + List stars = new ArrayList<>(starList); + + SwingUtilities.invokeLater(() -> { + ShootingStarPanel currentPanel = panel; + if (currentPanel == null) + { + return; + } + + if (fullUpdate) + { + currentPanel.updateList(stars); + } + else + { + currentPanel.refreshList(stars); + } + }); } private void toggleOverlay(boolean hideOverlay) diff --git a/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarScript.java b/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarScript.java index 5b8f02c5a2..7eb0415816 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarScript.java @@ -23,6 +23,7 @@ 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.antiban.enums.Category; 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.combat.Rs2Combat; @@ -74,6 +75,9 @@ public boolean run() startTime = Instant.now(); Microbot.enableAutoRunOn = true; initialPlayerLocation = null; + state = null; + currentStar = null; + pickaxe = null; hasEquipment = false; hasInventory = false; walkBeforeHopStar = null; @@ -110,7 +114,7 @@ public boolean run() return; } - if (Rs2Player.isMoving() || Rs2Antiban.getCategory().isBusy()) + if (Rs2Player.isMoving() || isAntibanBusy(Rs2Antiban.getCategory())) { return; } @@ -302,12 +306,14 @@ public boolean run() public void shutdown() { super.shutdown(); - state = null; - currentStar = null; - pickaxe = null; Rs2Antiban.resetAntibanSettings(); } + static boolean isAntibanBusy(Category category) + { + return category != null && category.isBusy(); + } + void handleWalking(Star star) { toggleLockState(true); diff --git a/src/test/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarStopRaceTest.java b/src/test/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarStopRaceTest.java new file mode 100644 index 0000000000..7e21c001b4 --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarStopRaceTest.java @@ -0,0 +1,135 @@ +package net.runelite.client.plugins.microbot.shootingstar; + +import java.lang.reflect.Field; +import java.util.concurrent.atomic.AtomicReference; +import javax.swing.SwingUtilities; +import net.runelite.client.plugins.microbot.shootingstar.enums.ShootingStarState; +import net.runelite.client.plugins.microbot.shootingstar.model.Star; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +class ShootingStarStopRaceTest +{ + private final AtomicReference edtFailure = new AtomicReference<>(); + private Thread.UncaughtExceptionHandler previousHandler; + private ShootingStarPlugin plugin; + + @BeforeEach + void setUp() + { + previousHandler = Thread.getDefaultUncaughtExceptionHandler(); + Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> edtFailure.set(throwable)); + plugin = new ShootingStarPlugin(); + } + + @AfterEach + void tearDown() + { + Thread.setDefaultUncaughtExceptionHandler(previousHandler); + } + + @Test + void updatePanelListIsNoOpWithoutPanel() throws Exception + { + plugin.updatePanelList(true); + plugin.updatePanelList(false); + flushEdt(); + + assertNull(edtFailure.get()); + } + + @Test + void queuedPanelUpdateSkipsRemovedPanel() throws Exception + { + ShootingStarPanel panel = mock(ShootingStarPanel.class); + setPanel(panel); + + SwingUtilities.invokeAndWait(() -> { + plugin.updatePanelList(true); + plugin.updatePanelList(false); + setPanelUnchecked(null); + }); + flushEdt(); + + assertNull(edtFailure.get()); + verify(panel, never()).updateList(anyList()); + verify(panel, never()).refreshList(anyList()); + } + + @Test + void queuedPanelUpdateReachesLivePanel() throws Exception + { + ShootingStarPanel panel = mock(ShootingStarPanel.class); + setPanel(panel); + + plugin.updatePanelList(true); + plugin.updatePanelList(false); + flushEdt(); + + assertNull(edtFailure.get()); + verify(panel).updateList(anyList()); + verify(panel).refreshList(anyList()); + } + + @Test + void missingAntibanCategoryIsNotBusy() + { + assertFalse(ShootingStarScript.isAntibanBusy(null)); + } + + @Test + void stoppingScriptKeepsLoopStateForInFlightIteration() throws Exception + { + ShootingStarScript script = new ShootingStarScript(plugin, mock(ShootingStarConfig.class)); + Star star = mock(Star.class); + Field currentStar = ShootingStarScript.class.getDeclaredField("currentStar"); + currentStar.setAccessible(true); + currentStar.set(script, star); + ShootingStarScript.state = ShootingStarState.MINING; + + try + { + script.shutdown(); + + assertSame(ShootingStarState.MINING, ShootingStarScript.state); + assertSame(star, currentStar.get(script)); + } + finally + { + ShootingStarScript.state = null; + } + } + + private static void flushEdt() throws Exception + { + SwingUtilities.invokeAndWait(() -> { }); + } + + private void setPanelUnchecked(ShootingStarPanel value) + { + try + { + setPanel(value); + } + catch (ReflectiveOperationException e) + { + throw new IllegalStateException(e); + } + } + + private void setPanel(ShootingStarPanel value) throws ReflectiveOperationException + { + Field field = ShootingStarPlugin.class.getDeclaredField("panel"); + field.setAccessible(true); + field.set(plugin, value); + } +}