diff --git a/runelite-api/src/main/java/net/runelite/api/Varbits.java b/runelite-api/src/main/java/net/runelite/api/Varbits.java index b1862fc17c..2af7644c11 100644 --- a/runelite-api/src/main/java/net/runelite/api/Varbits.java +++ b/runelite-api/src/main/java/net/runelite/api/Varbits.java @@ -652,6 +652,14 @@ public enum Varbits */ COLLECTION_LOG_NOTIFICATION(11959), + /** + * Combat Achievements popup settings whenever a new task is completed + * + * 0 = popup notification enabled + * 1 = popup notification disabled + */ + COMBAT_ACHIEVEMENTS_POPUP(12455), + /** * Show boss health overlay setting * 0 = on diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotConfig.java index f7498f7af3..a461c1fc08 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotConfig.java @@ -74,6 +74,28 @@ public interface ScreenshotConfig extends Config return true; } + @ConfigItem( + keyName = "uploadScreenshot", + name = "Upload", + description = "Configures whether or not screenshots are uploaded to Imgur, or placed on your clipboard", + position = 3 + ) + default ImageUploadStyle uploadScreenshot() + { + return ImageUploadStyle.NEITHER; + } + + @ConfigItem( + keyName = "hotkey", + name = "Screenshot hotkey", + description = "When you press this key a screenshot will be taken", + position = 4 + ) + default Keybind hotkey() + { + return Keybind.NOT_SET; + } + @ConfigItem( keyName = "rewards", name = "Screenshot Rewards", @@ -122,17 +144,6 @@ public interface ScreenshotConfig extends Config return true; } - @ConfigItem( - keyName = "uploadScreenshot", - name = "Upload", - description = "Configures whether or not screenshots are uploaded to Imgur, or placed on your clipboard", - position = 7 - ) - default ImageUploadStyle uploadScreenshot() - { - return ImageUploadStyle.NEITHER; - } - @ConfigItem( keyName = "kills", name = "Screenshot PvP Kills", @@ -278,13 +289,14 @@ public interface ScreenshotConfig extends Config } @ConfigItem( - keyName = "hotkey", - name = "Screenshot hotkey", - description = "When you press this key a screenshot will be taken", - position = 20 + keyName = "combatAchievements", + name = "Screenshot combat achievements", + description = "Take a screenshot when completing a combat achievement task", + position = 20, + section = whatSection ) - default Keybind hotkey() + default boolean screenshotCombatAchievements() { - return Keybind.NOT_SET; + return true; } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotPlugin.java index 160e06518e..f9ecc59d02 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotPlugin.java @@ -112,6 +112,7 @@ public class ScreenshotPlugin extends Plugin private static final Pattern DUEL_END_PATTERN = Pattern.compile("You have now (won|lost) ([0-9,]+) duels?\\."); private static final Pattern QUEST_PATTERN_1 = Pattern.compile(".+?ve\\.*? (?been|rebuilt|.+?ed)? ?(?:the )?'?(?.+?)'?(?: [Qq]uest)?[!.]?$"); private static final Pattern QUEST_PATTERN_2 = Pattern.compile("'?(?.+?)'?(?: [Qq]uest)? (?[a-z]\\w+?ed)?(?: f.*?)?[!.]?$"); + private static final Pattern COMBAT_ACHIEVEMENTS_PATTERN = Pattern.compile("Congratulations, you've completed an? (?\\w+) combat task: (?(.+))\\."); private static final ImmutableList RFD_TAGS = ImmutableList.of("Another Cook", "freed", "defeated", "saved"); private static final ImmutableList WORD_QUEST_IN_NAME_TAGS = ImmutableList.of("Another Cook", "Doric", "Heroes", "Legends", "Observatory", "Olaf", "Waterfall"); private static final ImmutableList PET_MESSAGES = ImmutableList.of("You have a funny feeling like you're being followed", @@ -134,6 +135,7 @@ public class ScreenshotPlugin extends Plugin private static final String SD_COLLECTION_LOG = "Collection Log"; private static final String SD_PVP_KILLS = "PvP Kills"; private static final String SD_DEATHS = "Deaths"; + private static final String SD_COMBAT_ACHIEVEMENTS = "Combat Achievements"; private String clueType; private Integer clueNumber; @@ -494,6 +496,15 @@ public class ScreenshotPlugin extends Plugin String fileName = "Collection log (" + entry + ")"; takeScreenshot(fileName, SD_COLLECTION_LOG); } + + if (chatMessage.contains("combat task") && config.screenshotCombatAchievements() && client.getVar(Varbits.COMBAT_ACHIEVEMENTS_POPUP) == 1) + { + String fileName = parseCombatAchievementWidget(chatMessage); + if (!fileName.isEmpty()) + { + takeScreenshot(fileName, SD_COMBAT_ACHIEVEMENTS); + } + } } @Subscribe @@ -652,6 +663,12 @@ public class ScreenshotPlugin extends Plugin String fileName = "Collection log (" + entry + ")"; takeScreenshot(fileName, SD_COLLECTION_LOG); } + if (topText.equalsIgnoreCase("Combat Task Completed!") && config.screenshotCombatAchievements() && client.getVar(Varbits.COMBAT_ACHIEVEMENTS_POPUP) == 0) + { + String entry = Text.removeTags(bottomText).substring("Task Completed: ".length()); + String fileName = "Combat task (" + entry.replaceAll("[:?]", "") + ")"; + takeScreenshot(fileName, SD_COMBAT_ACHIEVEMENTS); + } notificationStarted = false; break; } @@ -752,6 +769,24 @@ public class ScreenshotPlugin extends Plugin return "High Gamble(count not found)"; } + /** + * Parses a combat achievement success chat message into a filename-safe string. + * + * @param text A received chat message which may or may not be from completing a combat achievement. + * @return A formatted string of the achieved combat task name, or the empty string if the passed message + * is not a combat achievement completion message. + */ + @VisibleForTesting + static String parseCombatAchievementWidget(final String text) + { + final Matcher m = COMBAT_ACHIEVEMENTS_PATTERN.matcher(text); + if (m.matches()) + { + String task = m.group("task").replaceAll("[:?]", ""); + return "Combat task (" + task + ")"; + } + return ""; + } /** * Saves a screenshot of the client window to the screenshot folder as a PNG, @@ -760,7 +795,8 @@ public class ScreenshotPlugin extends Plugin * @param fileName Filename to use, without file extension. * @param subDir Subdirectory to store the captured screenshot in. */ - private void takeScreenshot(String fileName, String subDir) + @VisibleForTesting + void takeScreenshot(String fileName, String subDir) { if (client.getGameState() == GameState.LOGIN_SCREEN) { diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/screenshot/ScreenshotPluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/screenshot/ScreenshotPluginTest.java index 9ded0bfe8c..655fe4d42e 100644 --- a/runelite-client/src/test/java/net/runelite/client/plugins/screenshot/ScreenshotPluginTest.java +++ b/runelite-client/src/test/java/net/runelite/client/plugins/screenshot/ScreenshotPluginTest.java @@ -28,7 +28,6 @@ import com.google.inject.Guice; import com.google.inject.testing.fieldbinder.Bind; import com.google.inject.testing.fieldbinder.BoundFieldModule; import java.util.concurrent.ScheduledExecutorService; -import java.util.function.Consumer; import javax.inject.Inject; import static net.runelite.api.ChatMessageType.GAMEMESSAGE; import static net.runelite.api.ChatMessageType.TRADE; @@ -57,15 +56,14 @@ import static org.junit.Assert.assertEquals; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import org.mockito.Mock; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import org.mockito.junit.MockitoJUnitRunner; @@ -132,6 +130,7 @@ public class ScreenshotPluginTest public void before() { Guice.createInjector(BoundFieldModule.of(this)).injectMembers(this); + screenshotPlugin = spy(screenshotPlugin); when(screenshotConfig.screenshotLevels()).thenReturn(true); when(screenshotConfig.screenshotValuableDrop()).thenReturn(true); when(screenshotConfig.valuableDropThreshold()).thenReturn(1000); @@ -183,7 +182,7 @@ public class ScreenshotPluginTest widgetLoaded.setGroupId(WidgetID.THEATRE_OF_BLOOD_REWARD_GROUP_ID); screenshotPlugin.onWidgetLoaded(widgetLoaded); - verify(drawManager).requestNextFrameListener(any(Consumer.class)); + verify(screenshotPlugin).takeScreenshot("Theatre of Blood(73)", "Boss Kills"); } @Test @@ -201,7 +200,7 @@ public class ScreenshotPluginTest widgetLoaded.setGroupId(WidgetID.THEATRE_OF_BLOOD_REWARD_GROUP_ID); screenshotPlugin.onWidgetLoaded(widgetLoaded); - verify(drawManager).requestNextFrameListener(any(Consumer.class)); + verify(screenshotPlugin).takeScreenshot("Theatre of Blood Story Mode(73)", "Boss Kills"); } @Test @@ -219,7 +218,7 @@ public class ScreenshotPluginTest widgetLoaded.setGroupId(WidgetID.THEATRE_OF_BLOOD_REWARD_GROUP_ID); screenshotPlugin.onWidgetLoaded(widgetLoaded); - verify(drawManager).requestNextFrameListener(any(Consumer.class)); + verify(screenshotPlugin).takeScreenshot("Theatre of Blood Hard Mode(73)", "Boss Kills"); } @Test @@ -228,12 +227,12 @@ public class ScreenshotPluginTest ChatMessage chatMessageEvent = new ChatMessage(null, GAMEMESSAGE, "", NOT_SO_VALUABLE_DROP, null, 0); screenshotPlugin.onChatMessage(chatMessageEvent); - verifyNoInteractions(drawManager); + verify(screenshotPlugin, never()).takeScreenshot(anyString(), anyString()); when(screenshotConfig.valuableDropThreshold()).thenReturn(0); screenshotPlugin.onChatMessage(chatMessageEvent); - verify(drawManager).requestNextFrameListener(any(Consumer.class)); + verify(screenshotPlugin).takeScreenshot("Valuable drop 6 x Bronze arrow (42 coins)", "Valuable Drops"); } @Test @@ -243,12 +242,12 @@ public class ScreenshotPluginTest when(screenshotConfig.valuableDropThreshold()).thenReturn(100_000); screenshotPlugin.onChatMessage(chatMessageEvent); - verifyNoInteractions(drawManager); + verify(screenshotPlugin, never()).takeScreenshot(anyString(), anyString()); when(screenshotConfig.valuableDropThreshold()).thenReturn(1000); screenshotPlugin.onChatMessage(chatMessageEvent); - verify(drawManager).requestNextFrameListener(any(Consumer.class)); + verify(screenshotPlugin).takeScreenshot("Valuable drop Rune scimitar (25,600 coins)", "Valuable Drops"); } @Test @@ -257,7 +256,7 @@ public class ScreenshotPluginTest ChatMessage chatMessageEvent = new ChatMessage(null, GAMEMESSAGE, "", UNTRADEABLE_DROP, null, 0); screenshotPlugin.onChatMessage(chatMessageEvent); - verify(drawManager).requestNextFrameListener(any(Consumer.class)); + verify(screenshotPlugin).takeScreenshot("Untradeable drop Rusty sword", "Untradeable Drops"); } @Test @@ -277,7 +276,7 @@ public class ScreenshotPluginTest GameTick tick = new GameTick(); screenshotPlugin.onGameTick(tick); - verify(drawManager).requestNextFrameListener(any(Consumer.class)); + verify(screenshotPlugin).takeScreenshot("Hitpoints(99)", "Levels"); } @Test @@ -297,7 +296,7 @@ public class ScreenshotPluginTest GameTick tick = new GameTick(); screenshotPlugin.onGameTick(tick); - verify(drawManager).requestNextFrameListener(any(Consumer.class)); + verify(screenshotPlugin).takeScreenshot("Firemaking(9)", "Levels"); } @Test @@ -317,7 +316,7 @@ public class ScreenshotPluginTest GameTick tick = new GameTick(); screenshotPlugin.onGameTick(tick); - verify(drawManager).requestNextFrameListener(any(Consumer.class)); + verify(screenshotPlugin).takeScreenshot("Attack(70)", "Levels"); } @Test @@ -337,7 +336,7 @@ public class ScreenshotPluginTest GameTick tick = new GameTick(); screenshotPlugin.onGameTick(tick); - verify(drawManager).requestNextFrameListener(any(Consumer.class)); + verify(screenshotPlugin).takeScreenshot("Hunter(2)", "Levels"); } @Test @@ -353,6 +352,17 @@ public class ScreenshotPluginTest assertEquals("Quest(quest not found)", ScreenshotPlugin.parseQuestCompletedWidget("Sins of the Father forgiven!")); } + @Test + public void testCombatAchievementsParsing() + { + assertEquals("Combat task (Into the Den of Giants)", ScreenshotPlugin.parseCombatAchievementWidget("Congratulations, you've completed an easy combat task: Into the Den of Giants.")); + assertEquals("Combat task (I'd Rather Not Learn)", ScreenshotPlugin.parseCombatAchievementWidget("Congratulations, you've completed a medium combat task: I'd Rather Not Learn.")); + assertEquals("Combat task (Why Cook)", ScreenshotPlugin.parseCombatAchievementWidget("Congratulations, you've completed a hard combat task: Why Cook?.")); + assertEquals("Combat task (From Dusk...)", ScreenshotPlugin.parseCombatAchievementWidget("Congratulations, you've completed an elite combat task: From Dusk....")); + assertEquals("Combat task (Perfect Olm (Trio))", ScreenshotPlugin.parseCombatAchievementWidget("Congratulations, you've completed a master combat task: Perfect Olm (Trio).")); + assertEquals("Combat task (Chambers of Xeric CM (5-Scale) Speed-Runner)", ScreenshotPlugin.parseCombatAchievementWidget("Congratulations, you've completed a grandmaster combat task: Chambers of Xeric: CM (5-Scale) Speed-Runner.")); + } + @Test public void testBAHighGambleRewardParsing() { @@ -375,7 +385,7 @@ public class ScreenshotPluginTest screenshotPlugin.onGameTick(new GameTick()); - verify(drawManager, times(0)).requestNextFrameListener(any(Consumer.class)); + verify(screenshotPlugin, never()).takeScreenshot(anyString(), anyString()); } @Test @@ -394,7 +404,7 @@ public class ScreenshotPluginTest screenshotPlugin.onGameTick(new GameTick()); - verify(drawManager, times(0)).requestNextFrameListener(any(Consumer.class)); + verify(screenshotPlugin, never()).takeScreenshot(anyString(), anyString()); } @Test @@ -409,7 +419,7 @@ public class ScreenshotPluginTest ScriptPreFired notificationDelay = new ScriptPreFired(ScriptID.NOTIFICATION_DELAY); screenshotPlugin.onScriptPreFired(notificationDelay); - verify(drawManager).requestNextFrameListener(any(Consumer.class)); + verify(screenshotPlugin).takeScreenshot("Collection log (Chompy bird hat)", "Collection Log"); } @Test @@ -420,12 +430,13 @@ public class ScreenshotPluginTest when(client.getVar(Varbits.COLLECTION_LOG_NOTIFICATION)).thenReturn(1); screenshotPlugin.onChatMessage(chatMessageEvent); - verify(drawManager).requestNextFrameListener(any(Consumer.class)); + verify(screenshotPlugin).takeScreenshot("Collection log (Chompy bird hat)", "Collection Log"); + reset(screenshotPlugin); when(client.getVar(Varbits.COLLECTION_LOG_NOTIFICATION)).thenReturn(3); screenshotPlugin.onChatMessage(chatMessageEvent); - verifyNoMoreInteractions(drawManager); + verify(screenshotPlugin, never()).takeScreenshot(anyString(), anyString()); } @Test @@ -434,12 +445,13 @@ public class ScreenshotPluginTest ChatMessage chatMessageEvent = new ChatMessage(null, TRADE, "", "You won! You have now won 1,909 duels.", null, 0); screenshotPlugin.onChatMessage(chatMessageEvent); - verify(drawManager).requestNextFrameListener(any(Consumer.class)); + verify(screenshotPlugin).takeScreenshot("Duel won (1909)", "Duels"); + reset(screenshotPlugin); chatMessageEvent = new ChatMessage(null, TRADE, "", "You have lost 145 duels.", null, 0); screenshotPlugin.onChatMessage(chatMessageEvent); - verifyNoMoreInteractions(drawManager); + verify(screenshotPlugin, never()).takeScreenshot(anyString(), anyString()); } @Test @@ -448,11 +460,42 @@ public class ScreenshotPluginTest ChatMessage chatMessageEvent = new ChatMessage(null, TRADE, "", "You were defeated! You have won 1,909 duels.", null, 0); screenshotPlugin.onChatMessage(chatMessageEvent); - verify(drawManager, never()).requestNextFrameListener(any(Consumer.class)); + verify(screenshotPlugin, never()).takeScreenshot(anyString(), anyString()); chatMessageEvent = new ChatMessage(null, TRADE, "", "You have now lost 1,909 duels.", null, 0); screenshotPlugin.onChatMessage(chatMessageEvent); - verify(drawManager).requestNextFrameListener(any(Consumer.class)); + verify(screenshotPlugin).takeScreenshot("Duel lost (1909)", "Duels"); + } + + @Test + public void testCombatAchievementsPopup() + { + when(screenshotConfig.screenshotCombatAchievements()).thenReturn(true); + + ScriptPreFired notificationStart = new ScriptPreFired(ScriptID.NOTIFICATION_START); + screenshotPlugin.onScriptPreFired(notificationStart); + + when(client.getVar(VarClientStr.NOTIFICATION_TOP_TEXT)).thenReturn("Combat Task Completed!"); + when(client.getVar(VarClientStr.NOTIFICATION_BOTTOM_TEXT)).thenReturn("Task Completed: Handyman"); + + ScriptPreFired notificationDelay = new ScriptPreFired(ScriptID.NOTIFICATION_DELAY); + screenshotPlugin.onScriptPreFired(notificationDelay); + + verify(screenshotPlugin).takeScreenshot("Combat task (Handyman)", "Combat Achievements"); + } + + @Test + public void testCombatAchievementsChat() + { + when(screenshotConfig.screenshotCombatAchievements()).thenReturn(true); + + when(client.getVar(Varbits.COMBAT_ACHIEVEMENTS_POPUP)).thenReturn(1); + + ChatMessage chatMessageEvent = new ChatMessage(null, GAMEMESSAGE, "", + "Congratulations, you've completed an easy combat task: Handyman.", null, 0); + screenshotPlugin.onChatMessage(chatMessageEvent); + + verify(screenshotPlugin).takeScreenshot("Combat task (Handyman)", "Combat Achievements"); } }