From e4a6cbec2f3e8b7f623dce0e1d5218816cf341f7 Mon Sep 17 00:00:00 2001 From: 15987632 Date: Sat, 1 Feb 2020 12:14:35 -0500 Subject: [PATCH 01/18] rl-api: add invoke and return opcodes --- .../src/main/java/net/runelite/api/Opcodes.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/runelite-api/src/main/java/net/runelite/api/Opcodes.java b/runelite-api/src/main/java/net/runelite/api/Opcodes.java index 1ee96df082..b5fe92196a 100644 --- a/runelite-api/src/main/java/net/runelite/api/Opcodes.java +++ b/runelite-api/src/main/java/net/runelite/api/Opcodes.java @@ -29,6 +29,16 @@ package net.runelite.api; */ public class Opcodes { + /** + * opcode used to return from scripts. + */ + public static final int RETURN = 21; + + /** + * opcode used to invoke scripts. + */ + public static final int INVOKE = 40; + /** * RuneLite execution opcode used to inject scripts. */ From 37c66bb64aebdaa34f4f7448e5f6dd01ec255389 Mon Sep 17 00:00:00 2001 From: 15987632 Date: Sat, 1 Feb 2020 12:14:54 -0500 Subject: [PATCH 02/18] rl-api: add script fired events --- .../runelite/api/events/ScriptPostFired.java | 39 ++++++++++++++++ .../runelite/api/events/ScriptPreFired.java | 45 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 runelite-api/src/main/java/net/runelite/api/events/ScriptPostFired.java create mode 100644 runelite-api/src/main/java/net/runelite/api/events/ScriptPreFired.java diff --git a/runelite-api/src/main/java/net/runelite/api/events/ScriptPostFired.java b/runelite-api/src/main/java/net/runelite/api/events/ScriptPostFired.java new file mode 100644 index 0000000000..1337dcead1 --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/events/ScriptPostFired.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2020, Trevor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.api.events; + +import lombok.Value; + +/** + * An event that is fired after the designated script is ran + */ +@Value +public class ScriptPostFired +{ + /** + * The script id of the invoked script + */ + private final int scriptId; +} diff --git a/runelite-api/src/main/java/net/runelite/api/events/ScriptPreFired.java b/runelite-api/src/main/java/net/runelite/api/events/ScriptPreFired.java new file mode 100644 index 0000000000..c509303784 --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/events/ScriptPreFired.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020, Trevor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.api.events; + +import lombok.Data; +import net.runelite.api.ScriptEvent; + +/** + * An event that is fired before the designated script is ran + */ +@Data +public class ScriptPreFired +{ + /** + * The script id of the invoked script + */ + private final int scriptId; + + /** + * The input of the script invoke, this will be null unless it is the root script + */ + private ScriptEvent scriptEvent; +} From 13e69f8317d8fe59adc25882edfb5739ee450e34 Mon Sep 17 00:00:00 2001 From: Jordan Atwood Date: Mon, 3 Feb 2020 22:39:24 -0800 Subject: [PATCH 03/18] ConfigPlugin: Expand RuneLite plugin tags This commit adds a number of related tags to the RuneLite configuration in order to make it more easily discoverable as many users are not able to find the settings it contains via search. Closes runelite/runelite#8491 --- .../java/net/runelite/client/plugins/config/ConfigPlugin.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/config/ConfigPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/config/ConfigPlugin.java index 3024dfc786..4e4b1fce9b 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/config/ConfigPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/config/ConfigPlugin.java @@ -73,7 +73,8 @@ public class ConfigPlugin extends Plugin { pluginListPanel = pluginListPanelProvider.get(); pluginListPanel.addFakePlugin(new PluginConfigurationDescriptor( - "RuneLite", "RuneLite client settings", new String[]{"client"}, + "RuneLite", "RuneLite client settings", + new String[]{"client", "notification", "size", "position", "window", "chrome", "focus", "font", "overlay", "tooltip", "infobox"}, null, runeLiteConfig, configManager.getConfigDescriptor(runeLiteConfig) ), new PluginConfigurationDescriptor( From 31aa207bb1d51a65078aacc4c68b458b6a696f37 Mon Sep 17 00:00:00 2001 From: 15987632 Date: Sat, 1 Feb 2020 12:15:38 -0500 Subject: [PATCH 04/18] devtools: add script inspector --- .../plugins/devtools/DevToolsPanel.java | 23 +- .../plugins/devtools/DevToolsPlugin.java | 2 + .../plugins/devtools/ScriptInspector.java | 494 ++++++++++++++++++ 3 files changed, 518 insertions(+), 1 deletion(-) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/devtools/ScriptInspector.java diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPanel.java index a888df3334..2c276aee0a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPanel.java @@ -43,15 +43,23 @@ class DevToolsPanel extends PluginPanel private final WidgetInspector widgetInspector; private final VarInspector varInspector; + private final ScriptInspector scriptInspector; @Inject - private DevToolsPanel(Client client, DevToolsPlugin plugin, WidgetInspector widgetInspector, VarInspector varInspector, Notifier notifier) + private DevToolsPanel( + Client client, + DevToolsPlugin plugin, + WidgetInspector widgetInspector, + VarInspector varInspector, + ScriptInspector scriptInspector, + Notifier notifier) { super(); this.client = client; this.plugin = plugin; this.widgetInspector = widgetInspector; this.varInspector = varInspector; + this.scriptInspector = scriptInspector; this.notifier = notifier; setBackground(ColorScheme.DARK_GRAY_COLOR); @@ -133,6 +141,19 @@ class DevToolsPanel extends PluginPanel }); container.add(notificationBtn); + container.add(plugin.getScriptInspector()); + plugin.getScriptInspector().addActionListener((ev) -> + { + if (plugin.getScriptInspector().isActive()) + { + scriptInspector.close(); + } + else + { + scriptInspector.open(); + } + }); + return container; } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPlugin.java index 0f6a4e0648..7f2f6ff885 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPlugin.java @@ -129,6 +129,7 @@ public class DevToolsPlugin extends Plugin private DevToolsButton widgetInspector; private DevToolsButton varInspector; private DevToolsButton soundEffects; + private DevToolsButton scriptInspector; private NavigationButton navButton; @Provides @@ -170,6 +171,7 @@ public class DevToolsPlugin extends Plugin widgetInspector = new DevToolsButton("Widget Inspector"); varInspector = new DevToolsButton("Var Inspector"); soundEffects = new DevToolsButton("Sound Effects"); + scriptInspector = new DevToolsButton("Script Inspector"); overlayManager.add(overlay); overlayManager.add(locationOverlay); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/ScriptInspector.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/ScriptInspector.java new file mode 100644 index 0000000000..1cc8582c9a --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/ScriptInspector.java @@ -0,0 +1,494 @@ +/* + * Copyright (c) 2020, Trevor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.devtools; + +import com.google.common.collect.Lists; +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.event.AdjustmentEvent; +import java.awt.event.AdjustmentListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Set; +import javax.inject.Inject; +import javax.swing.BorderFactory; +import javax.swing.DefaultListModel; +import javax.swing.JButton; +import javax.swing.JFormattedTextField; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JScrollBar; +import javax.swing.JScrollPane; +import javax.swing.JSpinner; +import javax.swing.JTree; +import javax.swing.ListSelectionModel; +import javax.swing.SwingUtilities; +import javax.swing.border.CompoundBorder; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreePath; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Client; +import net.runelite.api.events.ScriptPostFired; +import net.runelite.api.events.ScriptPreFired; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetInfo; +import static net.runelite.api.widgets.WidgetInfo.TO_CHILD; +import static net.runelite.api.widgets.WidgetInfo.TO_GROUP; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.EventBus; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.ui.ClientUI; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.DynamicGridLayout; +import net.runelite.client.ui.FontManager; +import net.runelite.client.util.Text; + +@Slf4j +public class ScriptInspector extends JFrame +{ + // These scripts are the only ones that fire every client tick regardless of location. + private final static String DEFAULT_BLACKLIST = "3174,1004"; + private final static int MAX_LOG_ENTRIES = 10000; + + private final Client client; + private final EventBus eventBus; + private final ConfigManager configManager; + + private final JPanel tracker = new JPanel(); + private ScriptTreeNode currentNode; + private int lastTick; + private Set blacklist; + private Set highlights; + private JList jList; + private DefaultListModel listModel; + private ListState state = ListState.BLACKLIST; + + private enum ListState + { + BLACKLIST, + HIGHLIGHT + } + + @Data + private class ScriptTreeNode extends DefaultMutableTreeNode + { + private final int scriptId; + private Widget source; + private int duplicateNumber = 1; + + @Override + public String toString() + { + String output = Integer.toString(scriptId); + + if (duplicateNumber != 1) + { + output += " (" + duplicateNumber + ")"; + } + + if (source != null) + { + int id = source.getId(); + output += " - " + TO_GROUP(id) + "." + TO_CHILD(id); + + if (source.getIndex() != -1) + { + output += "[" + source.getIndex() + "]"; + } + + WidgetInfo info = WidgetInspector.getWidgetInfo(id); + if (info != null) + { + output += " " + info.name(); + } + } + + return output; + } + } + + @Inject + ScriptInspector(Client client, EventBus eventBus, DevToolsPlugin plugin, ConfigManager configManager) + { + this.eventBus = eventBus; + this.client = client; + this.configManager = configManager; + + setTitle("RuneLite Script Inspector"); + setIconImage(ClientUI.ICON); + + setLayout(new BorderLayout()); + + setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter() + { + @Override + public void windowClosing(WindowEvent e) + { + close(); + plugin.getScriptInspector().setActive(false); + } + }); + + tracker.setLayout(new DynamicGridLayout(0, 1, 0, 3)); + + final JPanel leftSide = new JPanel(); + leftSide.setLayout(new BorderLayout()); + + final JPanel trackerWrapper = new JPanel(); + trackerWrapper.setLayout(new BorderLayout()); + trackerWrapper.add(tracker, BorderLayout.NORTH); + + final JScrollPane trackerScroller = new JScrollPane(trackerWrapper); + trackerScroller.setPreferredSize(new Dimension(400, 400)); + + final JScrollBar vertical = trackerScroller.getVerticalScrollBar(); + vertical.addAdjustmentListener(new AdjustmentListener() + { + int lastMaximum = actualMax(); + + private int actualMax() + { + return vertical.getMaximum() - vertical.getModel().getExtent(); + } + + @Override + public void adjustmentValueChanged(AdjustmentEvent e) + { + if (vertical.getValue() >= lastMaximum) + { + vertical.setValue(actualMax()); + } + lastMaximum = actualMax(); + } + }); + + leftSide.add(trackerScroller, BorderLayout.CENTER); + + final JPanel bottomLeftRow = new JPanel(); + final JButton clearBtn = new JButton("Clear"); + clearBtn.addActionListener(e -> + { + tracker.removeAll(); + tracker.revalidate(); + }); + + bottomLeftRow.add(clearBtn); + leftSide.add(bottomLeftRow, BorderLayout.SOUTH); + add(leftSide, BorderLayout.CENTER); + + String blacklistConfig = configManager.getConfiguration("devtools", "blacklist"); + + if (blacklistConfig == null) + { + blacklistConfig = DEFAULT_BLACKLIST; + } + + try + { + blacklist = new HashSet<>(Lists.transform(Text.fromCSV(blacklistConfig), Integer::parseInt)); + } + catch (NumberFormatException e) + { + blacklist = new HashSet<>(Lists.transform(Text.fromCSV(DEFAULT_BLACKLIST), Integer::parseInt)); + } + + String highlightsConfig = configManager.getConfiguration("devtools", "highlights"); + + if (highlightsConfig == null) + { + highlightsConfig = ""; + } + + try + { + highlights = new HashSet<>(Lists.transform(Text.fromCSV(highlightsConfig), Integer::parseInt)); + } + catch (NumberFormatException e) + { + blacklist = new HashSet<>(); + } + + final JPanel rightSide = new JPanel(); + rightSide.setLayout(new BorderLayout()); + + listModel = new DefaultListModel(); + changeState(ListState.BLACKLIST); + jList = new JList(listModel); + jList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + JScrollPane listScrollPane = new JScrollPane(jList); + + final JButton blacklistButton = new JButton("Blacklist"); + blacklistButton.addActionListener(e -> changeState(ListState.BLACKLIST)); + + final JButton highlightsButton = new JButton("Highlights"); + highlightsButton.addActionListener(e -> changeState(ListState.HIGHLIGHT)); + + final JPanel topLeftRow = new JPanel(); + topLeftRow.setLayout(new FlowLayout()); + topLeftRow.add(blacklistButton); + topLeftRow.add(highlightsButton); + + rightSide.add(topLeftRow, BorderLayout.NORTH); + rightSide.add(listScrollPane, BorderLayout.CENTER); + + final JSpinner jSpinner = new JSpinner(); + Component mySpinnerEditor = jSpinner.getEditor(); + JFormattedTextField textField = ((JSpinner.DefaultEditor) mySpinnerEditor).getTextField(); + textField.setColumns(5); + + final JButton addButton = new JButton("Add"); + addButton.addActionListener(e -> addToSet(jSpinner)); + + final JButton removeButton = new JButton("Remove"); + removeButton.addActionListener(e -> removeSelectedFromSet()); + + final JPanel bottomButtonRow = new JPanel(); + bottomButtonRow.setLayout(new FlowLayout()); + bottomButtonRow.add(addButton); + bottomButtonRow.add(jSpinner); + bottomButtonRow.add(removeButton); + + rightSide.add(bottomButtonRow, BorderLayout.SOUTH); + + add(rightSide, BorderLayout.EAST); + + pack(); + } + + @Subscribe + public void onScriptPreFired(ScriptPreFired event) + { + ScriptTreeNode newNode = new ScriptTreeNode(event.getScriptId()); + if (event.getScriptEvent() != null) + { + newNode.setSource(event.getScriptEvent().getSource()); + } + + if (currentNode == null) + { + currentNode = newNode; + } + else + { + int count = 0; + Enumeration children = currentNode.children(); + if (children != null) + { + while (children.hasMoreElements()) + { + ScriptTreeNode child = (ScriptTreeNode) children.nextElement(); + + if (child.getScriptId() == event.getScriptId()) + { + count++; + } + } + + newNode.setDuplicateNumber(count + 1); + } + + currentNode.add(newNode); + currentNode = newNode; + } + } + + @Subscribe + public void onScriptPostFired(ScriptPostFired event) + { + if (currentNode == null || currentNode.getScriptId() != event.getScriptId()) + { + log.warn("a script was post-fired that was never pre-fired. Script id: " + event.getScriptId()); + return; + } + + if (currentNode.getParent() != null) + { + currentNode = (ScriptTreeNode) currentNode.getParent(); + } + else + { + addScriptLog(currentNode); + currentNode = null; + } + } + + public void open() + { + eventBus.register(this); + setVisible(true); + toFront(); + repaint(); + } + + public void close() + { + configManager.setConfiguration("devtools", "highlights", + Text.toCSV(Lists.transform(new ArrayList<>(highlights), String::valueOf))); + configManager.setConfiguration("devtools", "blacklist", + Text.toCSV(Lists.transform(new ArrayList<>(blacklist), String::valueOf))); + currentNode = null; + eventBus.unregister(this); + setVisible(false); + } + + private void addScriptLog(ScriptTreeNode treeNode) + { + if (blacklist.contains(treeNode.getScriptId())) + { + return; + } + + int tick = client.getTickCount(); + SwingUtilities.invokeLater(() -> + { + if (tick != lastTick) + { + lastTick = tick; + JLabel header = new JLabel("Tick " + tick); + header.setFont(FontManager.getRunescapeSmallFont()); + header.setBorder(new CompoundBorder( + BorderFactory.createMatteBorder(0, 0, 1, 0, ColorScheme.LIGHT_GRAY_COLOR), + BorderFactory.createEmptyBorder(3, 6, 0, 0) + )); + tracker.add(header); + } + DefaultTreeModel treeModel = new DefaultTreeModel(treeNode); + JTree tree = new JTree(treeModel); + tree.setRootVisible(true); + tree.setShowsRootHandles(true); + tree.collapsePath(new TreePath(treeNode)); + + ScriptTreeNode highlightNode = findHighlightPathNode(treeNode); + + if (highlightNode != null) + { + tree.setExpandsSelectedPaths(true); + tree.setSelectionPath(new TreePath(treeModel.getPathToRoot(highlightNode))); + } + + tracker.add(tree); + + // Cull very old stuff + for (; tracker.getComponentCount() > MAX_LOG_ENTRIES; ) + { + tracker.remove(0); + } + + tracker.revalidate(); + }); + } + + private void changeState(ListState state) + { + this.state = state; + refreshList(); + } + + private void addToSet(JSpinner spinner) + { + int script = (Integer) spinner.getValue(); + Set set = getSet(); + set.add(script); + refreshList(); + spinner.setValue(0); + } + + private void removeSelectedFromSet() + { + int index = jList.getSelectedIndex(); + + if (index == -1) + { + return; + } + + int script = (Integer) listModel.get(index); + getSet().remove(script); + refreshList(); + } + + private void refreshList() + { + listModel.clear(); + Set set = getSet(); + + for (Integer i : set) + { + listModel.addElement(i); + } + } + + private Set getSet() + { + Set set; + + if (state == ListState.BLACKLIST) + { + set = blacklist; + } + else + { + set = highlights; + } + + return set; + } + + private ScriptTreeNode findHighlightPathNode(ScriptTreeNode node) + { + if (highlights.contains(node.getScriptId())) + { + return node; + } + + Enumeration children = node.children(); + if (children != null) + { + while (children.hasMoreElements()) + { + ScriptTreeNode child = (ScriptTreeNode) children.nextElement(); + + ScriptTreeNode find = findHighlightPathNode(child); + + if (find != null) + { + return find; + } + } + } + + return null; + } +} From 901c9723de3f17f9caf337c28b4dfe6c160b43d1 Mon Sep 17 00:00:00 2001 From: mkowen1 Date: Sat, 8 Feb 2020 13:47:04 -0600 Subject: [PATCH 05/18] item id: add hunter, agility, fishing, and guthix balance potions --- .../plugins/itemidentification/ItemIdentification.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentification.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentification.java index ad62f6f8e6..97f9f4a643 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentification.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentification.java @@ -147,6 +147,7 @@ enum ItemIdentification DIVINE_MAGIC(Type.POTION, "Magic", "M", ItemID.DIVINE_MAGIC_POTION4, ItemID.DIVINE_MAGIC_POTION3, ItemID.DIVINE_MAGIC_POTION2, ItemID.DIVINE_MAGIC_POTION1), RESTORE(Type.POTION, "Restore", "Re", ItemID.RESTORE_POTION4, ItemID.RESTORE_POTION3, ItemID.RESTORE_POTION2, ItemID.RESTORE_POTION1), + GUTHIX_BALANCE(Type.POTION, "GuthBal", "G.B.", ItemID.GUTHIX_BALANCE4, ItemID.GUTHIX_BALANCE3, ItemID.GUTHIX_BALANCE2, ItemID.GUTHIX_BALANCE1), SUPER_RESTORE(Type.POTION, "S.Rest", "S.Re", ItemID.SUPER_RESTORE4, ItemID.SUPER_RESTORE3, ItemID.SUPER_RESTORE2, ItemID.SUPER_RESTORE1), PRAYER(Type.POTION, "Prayer", "P", ItemID.PRAYER_POTION4, ItemID.PRAYER_POTION3, ItemID.PRAYER_POTION2, ItemID.PRAYER_POTION1), ENERGY(Type.POTION, "Energy", "En", ItemID.ENERGY_POTION4, ItemID.ENERGY_POTION3, ItemID.ENERGY_POTION2, ItemID.ENERGY_POTION1), @@ -176,6 +177,10 @@ enum ItemIdentification SERUM_208(Type.POTION, "Ser208", "S8", ItemID.SERUM_208_4, ItemID.SERUM_208_3, ItemID.SERUM_208_2, ItemID.SERUM_208_1), COMPOST(Type.POTION, "Compost", "Cp", ItemID.COMPOST_POTION4, ItemID.COMPOST_POTION3, ItemID.COMPOST_POTION2, ItemID.COMPOST_POTION1), + AGILITY(Type.POTION, "Agility", "Ag", ItemID.AGILITY_POTION4, ItemID.AGILITY_POTION3, ItemID.AGILITY_POTION2, ItemID.AGILITY_POTION1), + FISHING(Type.POTION, "Fishing", "Fi", ItemID.FISHING_POTION4, ItemID.FISHING_POTION3, ItemID.FISHING_POTION2, ItemID.FISHING_POTION1), + HUNTER(Type.POTION, "Hunter", "Hu", ItemID.HUNTER_POTION4, ItemID.HUNTER_POTION3, ItemID.HUNTER_POTION2, ItemID.HUNTER_POTION1), + // Unfinished Potions GUAM_POTION(Type.POTION, "Guam", "G", ItemID.GUAM_POTION_UNF), MARRENTILL_POTION(Type.POTION, "Marren", "M", ItemID.MARRENTILL_POTION_UNF), From 5c88c965945308262be442c575aed82dd6a31625 Mon Sep 17 00:00:00 2001 From: mkowen1 Date: Sat, 8 Feb 2020 13:50:44 -0600 Subject: [PATCH 06/18] item id: reformat extraneous spaces --- .../client/plugins/itemidentification/ItemIdentification.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentification.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentification.java index 97f9f4a643..059c7acd8f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentification.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentification.java @@ -46,7 +46,7 @@ enum ItemIdentification DWARF_WEED_SEED(Type.SEED, "Dwarf", "D", ItemID.DWARF_WEED_SEED), TORSTOL_SEED(Type.SEED, "Torstol", "TOR", ItemID.TORSTOL_SEED), POISON_IVY_SEED(Type.SEED, "Ivy", "I", ItemID.POISON_IVY_SEED), - WHITEBERRY_SEED( Type.SEED, "White", "W", ItemID.WHITEBERRY_SEED), + WHITEBERRY_SEED(Type.SEED, "White", "W", ItemID.WHITEBERRY_SEED), //Herbs GUAM(Type.HERB, "Guam", "G", ItemID.GUAM_LEAF, ItemID.GRIMY_GUAM_LEAF), @@ -202,7 +202,7 @@ enum ItemIdentification final String shortName; final int[] itemIDs; - ItemIdentification(Type type, String medName, String shortName, int ... ids) + ItemIdentification(Type type, String medName, String shortName, int... ids) { this.type = type; this.medName = medName; From 8c009aeeac8d063d1a864776a2d9d7a1ca32947e Mon Sep 17 00:00:00 2001 From: Owain van Brakel Date: Sun, 9 Feb 2020 11:42:40 +0100 Subject: [PATCH 07/18] xpupdater: Fix config not showing --- .../net/runelite/client/plugins/xpupdater/XpUpdaterConfig.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/xpupdater/XpUpdaterConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/xpupdater/XpUpdaterConfig.java index 722a2ce44f..fa513c24ac 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/xpupdater/XpUpdaterConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/xpupdater/XpUpdaterConfig.java @@ -24,11 +24,12 @@ */ package net.runelite.client.plugins.xpupdater; +import net.runelite.client.config.Config; import net.runelite.client.config.ConfigGroup; import net.runelite.client.config.ConfigItem; @ConfigGroup("xpupdater") -public interface XpUpdaterConfig +public interface XpUpdaterConfig extends Config { @ConfigItem( position = 1, From 9a5337db3c7a65aa405b35522fe116cd74f6753b Mon Sep 17 00:00:00 2001 From: Max Weber Date: Sun, 9 Feb 2020 04:14:57 -0700 Subject: [PATCH 08/18] runelite-client: typecheck configs extend Config --- .../main/java/net/runelite/client/config/ConfigManager.java | 4 ++-- .../net/runelite/client/plugins/devtools/DevToolsConfig.java | 3 ++- .../src/test/java/net/runelite/client/config/TestConfig.java | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java b/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java index fdc2475ef0..b76849be96 100644 --- a/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java +++ b/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java @@ -341,7 +341,7 @@ public class ConfigManager } } - public T getConfig(Class clazz) + public T getConfig(Class clazz) { if (!Modifier.isPublic(clazz.getModifiers())) { @@ -439,7 +439,7 @@ public class ConfigManager eventBus.post(configChanged); } - public ConfigDescriptor getConfigDescriptor(Object configurationProxy) + public ConfigDescriptor getConfigDescriptor(Config configurationProxy) { Class inter = configurationProxy.getClass().getInterfaces()[0]; ConfigGroup group = inter.getAnnotation(ConfigGroup.class); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsConfig.java index a08edfd26c..ef12183573 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsConfig.java @@ -24,11 +24,12 @@ */ package net.runelite.client.plugins.devtools; +import net.runelite.client.config.Config; import net.runelite.client.config.ConfigGroup; import net.runelite.client.config.ConfigItem; @ConfigGroup("devtools") -public interface DevToolsConfig +public interface DevToolsConfig extends Config { @ConfigItem( keyName = "inspectorAlwaysOnTop", diff --git a/runelite-client/src/test/java/net/runelite/client/config/TestConfig.java b/runelite-client/src/test/java/net/runelite/client/config/TestConfig.java index 9bc5f29b37..66e659d98a 100644 --- a/runelite-client/src/test/java/net/runelite/client/config/TestConfig.java +++ b/runelite-client/src/test/java/net/runelite/client/config/TestConfig.java @@ -25,7 +25,7 @@ package net.runelite.client.config; @ConfigGroup("test") -public interface TestConfig +public interface TestConfig extends Config { @ConfigItem( keyName = "key", From b05fe5b68f8b9e817467c164de4facc71e792928 Mon Sep 17 00:00:00 2001 From: Max Weber Date: Sun, 2 Feb 2020 14:25:11 -0700 Subject: [PATCH 09/18] PluginHubPanel: Limit height of description to 2 lines, enable word wrap --- .../client/plugins/config/PluginHubPanel.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/config/PluginHubPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/config/PluginHubPanel.java index b809c079c0..d07036ee4c 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/config/PluginHubPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/config/PluginHubPanel.java @@ -28,6 +28,7 @@ import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; +import com.google.common.html.HtmlEscapers; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; @@ -157,8 +158,14 @@ class PluginHubPanel extends PluginPanel version.setFont(FontManager.getRunescapeSmallFont()); version.setToolTipText(manifest.getVersion()); - JLabel description = new JLabel(manifest.getDescription()); - description.setToolTipText(manifest.getDescription()); + String descriptionText = manifest.getDescription(); + if (!descriptionText.startsWith("")) + { + descriptionText = "" + HtmlEscapers.htmlEscaper().escape(descriptionText) + ""; + } + JLabel description = new JLabel(descriptionText); + description.setVerticalAlignment(JLabel.TOP); + description.setToolTipText(descriptionText); JLabel icon = new JLabel(); icon.setHorizontalAlignment(JLabel.CENTER); @@ -277,15 +284,16 @@ class PluginHubPanel extends PluginPanel .addComponent(addrm, 0, 50, GroupLayout.PREFERRED_SIZE) .addGap(5)))); + int lineHeight = description.getFontMetrics(description.getFont()).getHeight(); layout.setVerticalGroup(layout.createParallelGroup() - .addComponent(icon, HEIGHT, HEIGHT, HEIGHT) + .addComponent(icon, HEIGHT, GroupLayout.DEFAULT_SIZE, HEIGHT + lineHeight) .addGroup(layout.createSequentialGroup() .addGap(5) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(pluginName) .addComponent(author)) .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE) - .addComponent(description) + .addComponent(description, lineHeight, GroupLayout.PREFERRED_SIZE, lineHeight * 2) .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(version, BOTTOM_LINE_HEIGHT, BOTTOM_LINE_HEIGHT, BOTTOM_LINE_HEIGHT) From 089a793ba01f0c20745dd933cbc9550784444360 Mon Sep 17 00:00:00 2001 From: Max Weber Date: Fri, 31 Jan 2020 18:10:28 -0700 Subject: [PATCH 10/18] PluginManager: require start/stop to be invoked on the EDT The current way of invoking it on the executor would let the client have a start and a stop for a plugin sitting in the executor's queue, making it toggle endlessly. This only was because we were using invokeAndWait in startPlugin, which can't be invoked on the EDT because it would deadlock with itself. --- .../ExternalPluginManager.java | 49 ++++++++++-- .../client/plugins/PluginManager.java | 74 +++++++++---------- .../plugins/config/PluginListPanel.java | 42 +++++------ .../client/plugins/gpu/GpuPlugin.java | 20 +++-- 4 files changed, 106 insertions(+), 79 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/externalplugins/ExternalPluginManager.java b/runelite-client/src/main/java/net/runelite/client/externalplugins/ExternalPluginManager.java index 531f443cf9..2a3c3a828c 100644 --- a/runelite-client/src/main/java/net/runelite/client/externalplugins/ExternalPluginManager.java +++ b/runelite-client/src/main/java/net/runelite/client/externalplugins/ExternalPluginManager.java @@ -33,6 +33,7 @@ import com.google.common.hash.HashingInputStream; import com.google.common.io.Files; import java.io.File; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.util.ArrayList; import java.util.Collection; @@ -44,6 +45,7 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.function.Function; import javax.inject.Inject; import javax.inject.Singleton; +import javax.swing.SwingUtilities; import lombok.extern.slf4j.Slf4j; import net.runelite.client.RuneLite; import net.runelite.client.RuneLiteProperties; @@ -242,9 +244,19 @@ public class ExternalPluginManager log.info("Stopping external plugin \"{}\"", p.getClass()); try { - pluginManager.stopPlugin(p); + SwingUtilities.invokeAndWait(() -> + { + try + { + pluginManager.stopPlugin(p); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + }); } - catch (PluginInstantiationException e) + catch (InterruptedException | InvocationTargetException e) { log.warn("Unable to stop external plugin \"{}\"", p.getClass().getName(), e); } @@ -272,15 +284,25 @@ public class ExternalPluginManager clazzes.add(cl.loadClass(className)); } - newPlugins = pluginManager.loadPlugins(clazzes, null); + List newPlugins2 = newPlugins = pluginManager.loadPlugins(clazzes, null); if (!startup) { pluginManager.loadDefaultPluginConfiguration(newPlugins); - for (Plugin p : newPlugins) + SwingUtilities.invokeAndWait(() -> { - pluginManager.startPlugin(p); - } + try + { + for (Plugin p : newPlugins2) + { + pluginManager.startPlugin(p); + } + } + catch (PluginInstantiationException e) + { + throw new RuntimeException(e); + } + }); } } catch (Exception e) @@ -292,10 +314,21 @@ public class ExternalPluginManager { try { - pluginManager.stopPlugin(p); + SwingUtilities.invokeAndWait(() -> + { + try + { + pluginManager.stopPlugin(p); + } + catch (Exception e2) + { + throw new RuntimeException(e2); + } + }); } - catch (Exception inner) + catch (InterruptedException | InvocationTargetException e2) { + log.info("Unable to fully stop plugin \"{}\"", manifest.getInternalName(), e2); } pluginManager.remove(p); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java index 3cbcc57385..ed814d27cb 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java @@ -132,8 +132,9 @@ public class PluginManager private void refreshPlugins() { loadDefaultPluginConfiguration(null); - getPlugins() - .forEach(plugin -> executor.submit(() -> + SwingUtilities.invokeLater(() -> + { + for (Plugin plugin : getPlugins()) { try { @@ -153,7 +154,8 @@ public class PluginManager { log.warn("Error during starting/stopping plugin {}", plugin.getClass().getSimpleName(), e); } - })); + } + }); } public Config getPluginConfigProxy(Plugin plugin) @@ -215,12 +217,22 @@ public class PluginManager { try { - startPlugin(plugin); + SwingUtilities.invokeAndWait(() -> + { + try + { + startPlugin(plugin); + } + catch (PluginInstantiationException ex) + { + log.warn("Unable to start plugin {}", plugin.getClass().getSimpleName(), ex); + plugins.remove(plugin); + } + }); } - catch (PluginInstantiationException ex) + catch (InterruptedException | InvocationTargetException e) { - log.warn("Unable to start plugin {}", plugin.getClass().getSimpleName(), ex); - plugins.remove(plugin); + throw new RuntimeException(e); } loaded++; @@ -325,8 +337,11 @@ public class PluginManager return newPlugins; } - public synchronized boolean startPlugin(Plugin plugin) throws PluginInstantiationException + public boolean startPlugin(Plugin plugin) throws PluginInstantiationException { + // plugins always start in the EDT + assert SwingUtilities.isEventDispatchThread(); + if (activePlugins.contains(plugin) || !isPluginEnabled(plugin)) { return false; @@ -336,18 +351,7 @@ public class PluginManager try { - // plugins always start in the event thread - SwingUtilities.invokeAndWait(() -> - { - try - { - plugin.startUp(); - } - catch (Exception ex) - { - throw new RuntimeException(ex); - } - }); + plugin.startUp(); log.debug("Plugin {} is now running", plugin.getClass().getSimpleName()); if (!isOutdated && sceneTileManager != null) @@ -363,7 +367,7 @@ public class PluginManager schedule(plugin); eventBus.post(new PluginChanged(plugin, true)); } - catch (InterruptedException | InvocationTargetException | IllegalArgumentException ex) + catch (Exception ex) { throw new PluginInstantiationException(ex); } @@ -371,36 +375,27 @@ public class PluginManager return true; } - public synchronized boolean stopPlugin(Plugin plugin) throws PluginInstantiationException + public boolean stopPlugin(Plugin plugin) throws PluginInstantiationException { + // plugins always stop in the EDT + assert SwingUtilities.isEventDispatchThread(); + if (!activePlugins.remove(plugin)) { return false; } + unschedule(plugin); + eventBus.unregister(plugin); + try { - unschedule(plugin); - eventBus.unregister(plugin); - - // plugins always stop in the event thread - SwingUtilities.invokeAndWait(() -> - { - try - { - plugin.shutDown(); - } - catch (Exception ex) - { - throw new RuntimeException(ex); - } - }); + plugin.shutDown(); log.debug("Plugin {} is now stopped", plugin.getClass().getSimpleName()); eventBus.post(new PluginChanged(plugin, false)); - } - catch (InterruptedException | InvocationTargetException ex) + catch (Exception ex) { throw new PluginInstantiationException(ex); } @@ -555,6 +550,7 @@ public class PluginManager /** * Topologically sort a graph. Uses Kahn's algorithm. + * * @param graph * @param * @return diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/config/PluginListPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/config/PluginListPanel.java index b7b08b780b..2e96a4dae9 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/config/PluginListPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/config/PluginListPanel.java @@ -311,36 +311,30 @@ class PluginListPanel extends PluginPanel void startPlugin(Plugin plugin) { - executorService.submit(() -> - { - pluginManager.setPluginEnabled(plugin, true); + pluginManager.setPluginEnabled(plugin, true); - try - { - pluginManager.startPlugin(plugin); - } - catch (PluginInstantiationException ex) - { - log.warn("Error when starting plugin {}", plugin.getClass().getSimpleName(), ex); - } - }); + try + { + pluginManager.startPlugin(plugin); + } + catch (PluginInstantiationException ex) + { + log.warn("Error when starting plugin {}", plugin.getClass().getSimpleName(), ex); + } } void stopPlugin(Plugin plugin) { - executorService.submit(() -> - { - pluginManager.setPluginEnabled(plugin, false); + pluginManager.setPluginEnabled(plugin, false); - try - { - pluginManager.stopPlugin(plugin); - } - catch (PluginInstantiationException ex) - { - log.warn("Error when stopping plugin {}", plugin.getClass().getSimpleName(), ex); - } - }); + try + { + pluginManager.stopPlugin(plugin); + } + catch (PluginInstantiationException ex) + { + log.warn("Error when stopping plugin {}", plugin.getClass().getSimpleName(), ex); + } } private List getPinnedPluginNames() diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java index 66e0977b32..e4a0233c0f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java @@ -48,6 +48,7 @@ import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.util.function.Function; import javax.inject.Inject; +import javax.swing.SwingUtilities; import jogamp.nativewindow.SurfaceScaleUtils; import jogamp.nativewindow.jawt.x11.X11JAWTWindow; import jogamp.newt.awt.NewtFactoryAWT; @@ -334,15 +335,18 @@ public class GpuPlugin extends Plugin implements DrawCallbacks { log.error("Error starting GPU plugin", e); - try + SwingUtilities.invokeLater(() -> { - pluginManager.setPluginEnabled(this, false); - pluginManager.stopPlugin(this); - } - catch (PluginInstantiationException ex) - { - log.error("error stopping plugin", ex); - } + try + { + pluginManager.setPluginEnabled(this, false); + pluginManager.stopPlugin(this); + } + catch (PluginInstantiationException ex) + { + log.error("error stopping plugin", ex); + } + }); shutDown(); } From 30171938e9b3107c28e637a4a732b3065df6c2da Mon Sep 17 00:00:00 2001 From: Noah Haig <000786224@mohawkcollege.ca> Date: Sun, 2 Feb 2020 01:45:49 -0500 Subject: [PATCH 11/18] clues: add description to Prif elite coord clue --- .../client/plugins/cluescrolls/clues/CoordinateClue.java | 1 + 1 file changed, 1 insertion(+) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CoordinateClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CoordinateClue.java index e485b6fe11..c9c7ea66c8 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CoordinateClue.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CoordinateClue.java @@ -194,6 +194,7 @@ public class CoordinateClue extends ClueScroll implements TextClueScroll, Locati .put(new WorldPoint(2316, 3814, 0), new CoordinateClueInfo("West of Neitiznot, near the bridge.")) .put(new WorldPoint(2872, 3937, 0), new CoordinateClueInfo("Weiss.")) .put(new WorldPoint(2484, 4016, 0), new CoordinateClueInfo("Northeast corner of the Island of Stone.")) + .put(new WorldPoint(2222, 3331, 0), new CoordinateClueInfo("Prifddinas, west of the Tower of Voices")) // Master .put(new WorldPoint(2178, 3209, 0), new CoordinateClueInfo("South of Iorwerth Camp.")) .put(new WorldPoint(2155, 3100, 0), new CoordinateClueInfo("South of Port Tyras (BJS).")) From 99e26aa17410c8743fb87f13ab89c6de375961e6 Mon Sep 17 00:00:00 2001 From: Rob Mckennie Date: Mon, 7 Oct 2019 10:42:56 +1300 Subject: [PATCH 12/18] npcunaggroarea: add notification for timer expiration --- .../npcunaggroarea/NpcAggroAreaConfig.java | 11 +++++++++++ .../npcunaggroarea/NpcAggroAreaPlugin.java | 19 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/npcunaggroarea/NpcAggroAreaConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/npcunaggroarea/NpcAggroAreaConfig.java index 99084a9172..b13901a4cc 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/npcunaggroarea/NpcAggroAreaConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/npcunaggroarea/NpcAggroAreaConfig.java @@ -103,4 +103,15 @@ public interface NpcAggroAreaConfig extends Config { return false; } + + @ConfigItem( + keyName = "notifyExpire", + name = "Notify Expiration", + description = "Send a notifcation when the unaggressive timer expires", + position = 7 + ) + default boolean notifyExpire() + { + return false; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/npcunaggroarea/NpcAggroAreaPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/npcunaggroarea/NpcAggroAreaPlugin.java index f7d96bd59b..1053ae0a5d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/npcunaggroarea/NpcAggroAreaPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/npcunaggroarea/NpcAggroAreaPlugin.java @@ -52,6 +52,7 @@ import net.runelite.api.events.GameStateChanged; import net.runelite.api.events.GameTick; import net.runelite.api.events.NpcSpawned; import net.runelite.api.geometry.Geometry; +import net.runelite.client.Notifier; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.game.ItemManager; @@ -110,6 +111,9 @@ public class NpcAggroAreaPlugin extends Plugin @Inject private ConfigManager configManager; + @Inject + private Notifier notifier; + @Getter private final WorldPoint[] safeCenters = new WorldPoint[2]; @@ -125,6 +129,8 @@ public class NpcAggroAreaPlugin extends Plugin private WorldPoint lastPlayerLocation; private WorldPoint previousUnknownCenter; private boolean loggingIn; + private boolean notifyOnce; + private List npcNamePatterns; @Provides @@ -223,6 +229,7 @@ public class NpcAggroAreaPlugin extends Plugin { infoBoxManager.removeInfoBox(currentTimer); currentTimer = null; + notifyOnce = false; } private void createTimer(Duration duration) @@ -231,6 +238,7 @@ public class NpcAggroAreaPlugin extends Plugin BufferedImage image = itemManager.getImage(ItemID.ENSOULED_DEMON_HEAD); currentTimer = new AggressionTimer(duration, image, this, active && config.showTimer()); infoBoxManager.addInfoBox(currentTimer); + notifyOnce = true; } private void resetTimer() @@ -317,6 +325,17 @@ public class NpcAggroAreaPlugin extends Plugin public void onGameTick(GameTick event) { WorldPoint newLocation = client.getLocalPlayer().getWorldLocation(); + + if (active && currentTimer != null && currentTimer.cull() && notifyOnce) + { + if (config.notifyExpire()) + { + notifier.notify("NPC agression has expired!"); + } + + notifyOnce = false; + } + if (lastPlayerLocation != null) { if (safeCenters[1] == null && newLocation.distanceTo2D(lastPlayerLocation) > SAFE_AREA_RADIUS * 4) From d3740d302a288b40c2d52b8b9dd17f7e4fb140c8 Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 8 Feb 2020 20:30:44 -0500 Subject: [PATCH 13/18] clue plugin: add reset overlay option --- .../plugins/cluescrolls/ClueScrollOverlay.java | 2 ++ .../plugins/cluescrolls/ClueScrollPlugin.java | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollOverlay.java index d08433c7d3..65ab528c5a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollOverlay.java @@ -34,6 +34,7 @@ import javax.inject.Inject; import net.runelite.api.Client; import net.runelite.api.Item; import static net.runelite.api.ItemID.*; +import static net.runelite.api.MenuAction.RUNELITE_OVERLAY; import static net.runelite.api.MenuAction.RUNELITE_OVERLAY_CONFIG; import net.runelite.client.plugins.cluescrolls.clues.ClueScroll; import net.runelite.client.plugins.cluescrolls.clues.item.AnyRequirementCollection; @@ -89,6 +90,7 @@ public class ClueScrollOverlay extends Overlay this.client = client; setPriority(OverlayPriority.LOW); getMenuEntries().add(new OverlayMenuEntry(RUNELITE_OVERLAY_CONFIG, OPTION_CONFIGURE, "Clue Scroll overlay")); + getMenuEntries().add(new OverlayMenuEntry(RUNELITE_OVERLAY, "Reset", "Clue Scroll overlay")); } @Override diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollPlugin.java index 6e27f10c44..8e09fd6a85 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollPlugin.java @@ -53,6 +53,7 @@ import net.runelite.api.InventoryID; import net.runelite.api.Item; import net.runelite.api.ItemComposition; import net.runelite.api.ItemID; +import net.runelite.api.MenuAction; import net.runelite.api.NPC; import net.runelite.api.ObjectComposition; import net.runelite.api.Point; @@ -77,6 +78,7 @@ import net.runelite.api.widgets.WidgetInfo; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.events.ConfigChanged; +import net.runelite.client.events.OverlayMenuClicked; import net.runelite.client.game.ItemManager; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; @@ -100,6 +102,7 @@ import net.runelite.client.plugins.cluescrolls.clues.SkillChallengeClue; import net.runelite.client.plugins.cluescrolls.clues.TextClueScroll; import net.runelite.client.plugins.cluescrolls.clues.ThreeStepCrypticClue; import net.runelite.client.ui.overlay.OverlayManager; +import net.runelite.client.ui.overlay.OverlayMenuEntry; import net.runelite.client.ui.overlay.OverlayUtil; import net.runelite.client.ui.overlay.components.TextComponent; import net.runelite.client.ui.overlay.worldmap.WorldMapPointManager; @@ -242,6 +245,18 @@ public class ClueScrollPlugin extends Plugin } } + @Subscribe + public void onOverlayMenuClicked(OverlayMenuClicked overlayMenuClicked) + { + OverlayMenuEntry overlayMenuEntry = overlayMenuClicked.getEntry(); + if (overlayMenuEntry.getMenuAction() == MenuAction.RUNELITE_OVERLAY + && overlayMenuClicked.getEntry().getOption().equals("Reset") + && overlayMenuClicked.getOverlay() == clueScrollOverlay) + { + resetClue(true); + } + } + @Subscribe public void onMenuOptionClicked(final MenuOptionClicked event) { From e06fa3ba723505d56c6ca79ca310268172fee99c Mon Sep 17 00:00:00 2001 From: ralphmtk <60431551+ralphmtk@users.noreply.github.com> Date: Mon, 10 Feb 2020 01:24:07 +0200 Subject: [PATCH 14/18] timers: correct dfs spec cooldown time --- .../main/java/net/runelite/client/plugins/timers/GameTimer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timers/GameTimer.java b/runelite-client/src/main/java/net/runelite/client/plugins/timers/GameTimer.java index b2c3aa7289..43deff2bb4 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timers/GameTimer.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timers/GameTimer.java @@ -75,7 +75,7 @@ enum GameTimer ABYSSAL_SIRE_STUN(ItemID.ABYSSAL_ORPHAN, GameTimerImageType.ITEM, "Abyssal Sire Stun", 30, ChronoUnit.SECONDS, true), HOME_TELEPORT(SpriteID.SPELL_LUMBRIDGE_HOME_TELEPORT, GameTimerImageType.SPRITE, "Home Teleport", 30, ChronoUnit.MINUTES), MINIGAME_TELEPORT(SpriteID.TAB_QUESTS_RED_MINIGAMES, GameTimerImageType.SPRITE, "Minigame Teleport", 20, ChronoUnit.MINUTES), - DRAGON_FIRE_SHIELD(ItemID.DRAGONFIRE_SHIELD_11284, GameTimerImageType.ITEM, "Dragonfire Shield Special", 2, ChronoUnit.MINUTES), + DRAGON_FIRE_SHIELD(ItemID.DRAGONFIRE_SHIELD_11284, GameTimerImageType.ITEM, "Dragonfire Shield Special", 115, ChronoUnit.SECONDS), DIVINE_SUPER_ATTACK(ItemID.DIVINE_SUPER_ATTACK_POTION4, GameTimerImageType.ITEM, "Divine Super Attack", 5, ChronoUnit.MINUTES), DIVINE_SUPER_STRENGTH(ItemID.DIVINE_SUPER_STRENGTH_POTION4, GameTimerImageType.ITEM, "Divine Super Strength", 5, ChronoUnit.MINUTES), DIVINE_SUPER_DEFENCE(ItemID.DIVINE_SUPER_DEFENCE_POTION4, GameTimerImageType.ITEM, "Divine Super Defence", 5, ChronoUnit.MINUTES), From e806e1bc4173984a5ad35314b0954374ec452fa6 Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 9 Feb 2020 18:53:49 -0500 Subject: [PATCH 15/18] loot manager: add support for nightmare Co-authored-by: Hexagon --- .../net/runelite/client/game/LootManager.java | 89 ++++++++++++++++++- 1 file changed, 88 insertions(+), 1 deletion(-) diff --git a/runelite-client/src/main/java/net/runelite/client/game/LootManager.java b/runelite-client/src/main/java/net/runelite/client/game/LootManager.java index a2c10cf04b..ddd8157502 100644 --- a/runelite-client/src/main/java/net/runelite/client/game/LootManager.java +++ b/runelite-client/src/main/java/net/runelite/client/game/LootManager.java @@ -38,12 +38,13 @@ import javax.inject.Singleton; import lombok.extern.slf4j.Slf4j; import net.runelite.api.AnimationID; import net.runelite.api.Client; -import net.runelite.api.TileItem; import net.runelite.api.ItemID; import net.runelite.api.NPC; +import net.runelite.api.NPCComposition; import net.runelite.api.NpcID; import net.runelite.api.Player; import net.runelite.api.Tile; +import net.runelite.api.TileItem; import net.runelite.api.coords.LocalPoint; import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.AnimationChanged; @@ -51,6 +52,7 @@ import net.runelite.api.events.GameTick; import net.runelite.api.events.ItemDespawned; import net.runelite.api.events.ItemQuantityChanged; import net.runelite.api.events.ItemSpawned; +import net.runelite.api.events.NpcChanged; import net.runelite.api.events.NpcDespawned; import net.runelite.api.events.PlayerDespawned; import net.runelite.client.eventbus.EventBus; @@ -73,6 +75,9 @@ public class LootManager private WorldPoint playerLocationLastTick; private WorldPoint krakenPlayerLocation; + private NPC delayedLootNpc; + private int delayedLootTickLimit; + @Inject private LootManager(EventBus eventBus, Client client) { @@ -84,6 +89,13 @@ public class LootManager public void onNpcDespawned(NpcDespawned npcDespawned) { final NPC npc = npcDespawned.getNpc(); + + if (npc == delayedLootNpc) + { + delayedLootNpc = null; + delayedLootTickLimit = 0; + } + if (!npc.isDead()) { int id = npc.getId(); @@ -219,14 +231,61 @@ public class LootManager } } + @Subscribe + public void onNpcChanged(NpcChanged npcChanged) + { + final NPC npc = npcChanged.getNpc(); + if (npc.getId() == NpcID.THE_NIGHTMARE_9433) + { + delayedLootNpc = npc; + delayedLootTickLimit = 15; + } + } + @Subscribe public void onGameTick(GameTick gameTick) { + if (delayedLootNpc != null && delayedLootTickLimit-- > 0) + { + processDelayedLoot(); + } + playerLocationLastTick = client.getLocalPlayer().getWorldLocation(); + itemSpawns.clear(); killPoints.clear(); } + private void processDelayedLoot() + { + final WorldPoint adjacentLootTile = getAdjacentSquareLootTile(delayedLootNpc); + final LocalPoint localPoint = LocalPoint.fromWorld(client, adjacentLootTile); + + if (localPoint == null) + { + log.debug("Scene changed away from delayed loot location"); + delayedLootNpc = null; + delayedLootTickLimit = 0; + return; + } + + final int sceneX = localPoint.getSceneX(); + final int sceneY = localPoint.getSceneY(); + final int packed = sceneX << 8 | sceneY; + final List itemStacks = itemSpawns.get(packed); + if (itemStacks.isEmpty()) + { + // no loot yet + return; + } + + log.debug("Got delayed loot stack from {}: {}", delayedLootNpc.getName(), itemStacks); + eventBus.post(new NpcLootReceived(delayedLootNpc, itemStacks)); + + delayedLootNpc = null; + delayedLootTickLimit = 0; + } + private void processNpcLoot(NPC npc) { final LocalPoint location = LocalPoint.fromWorld(client, getDropLocation(npc, npc.getWorldLocation())); @@ -316,4 +375,32 @@ public class LootManager return worldLocation; } + + private WorldPoint getAdjacentSquareLootTile(NPC npc) + { + final NPCComposition composition = npc.getComposition(); + final WorldPoint worldLocation = npc.getWorldLocation(); + int x = worldLocation.getX(); + int y = worldLocation.getY(); + + if (playerLocationLastTick.getX() < x) + { + x -= 1; + } + else + { + x += Math.min(playerLocationLastTick.getX() - x, composition.getSize()); + } + + if (playerLocationLastTick.getY() < y) + { + y -= 1; + } + else + { + y += Math.min(playerLocationLastTick.getY() - y, composition.getSize()); + } + + return new WorldPoint(x, y, worldLocation.getPlane()); + } } From 4d5857214eaf433f5264a3d38b4e6039e0f87b11 Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 9 Feb 2020 16:06:40 -0500 Subject: [PATCH 16/18] loot tracker: add pickpocket events Co-authored-by: Daniel Cimento --- .../http/api/loottracker/LootRecordType.java | 1 + .../loottracker/LootTrackerService.java | 2 +- .../loottracker/LootTrackerPlugin.java | 106 ++++++++++++++---- .../loottracker/LootTrackerPluginTest.java | 94 ++++++++++++++++ 4 files changed, 183 insertions(+), 20 deletions(-) create mode 100644 runelite-client/src/test/java/net/runelite/client/plugins/loottracker/LootTrackerPluginTest.java diff --git a/http-api/src/main/java/net/runelite/http/api/loottracker/LootRecordType.java b/http-api/src/main/java/net/runelite/http/api/loottracker/LootRecordType.java index c91817b410..33e622de45 100644 --- a/http-api/src/main/java/net/runelite/http/api/loottracker/LootRecordType.java +++ b/http-api/src/main/java/net/runelite/http/api/loottracker/LootRecordType.java @@ -29,5 +29,6 @@ public enum LootRecordType NPC, PLAYER, EVENT, + PICKPOCKET, UNKNOWN } diff --git a/http-service/src/main/java/net/runelite/http/service/loottracker/LootTrackerService.java b/http-service/src/main/java/net/runelite/http/service/loottracker/LootTrackerService.java index f7f9ac5004..ea2f5176a7 100644 --- a/http-service/src/main/java/net/runelite/http/service/loottracker/LootTrackerService.java +++ b/http-service/src/main/java/net/runelite/http/service/loottracker/LootTrackerService.java @@ -47,7 +47,7 @@ public class LootTrackerService " `first_time` timestamp NOT NULL DEFAULT current_timestamp(),\n" + " `last_time` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),\n" + " `accountId` int(11) NOT NULL,\n" + - " `type` enum('NPC','PLAYER','EVENT','UNKNOWN') NOT NULL,\n" + + " `type` enum('NPC','PLAYER','EVENT','PICKPOCKET','UNKNOWN') NOT NULL,\n" + " `eventId` varchar(255) NOT NULL,\n" + " `amount` int(11) NOT NULL,\n" + " PRIMARY KEY (`id`),\n" + diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java index 0864776476..44619a56b2 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java @@ -25,9 +25,12 @@ */ package net.runelite.client.plugins.loottracker; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.HashMultiset; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Multimap; import com.google.common.collect.Multiset; import com.google.common.collect.Multisets; import com.google.inject.Provides; @@ -66,6 +69,7 @@ import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.ChatMessage; import net.runelite.api.events.GameStateChanged; import net.runelite.api.events.ItemContainerChanged; +import net.runelite.api.events.MenuOptionClicked; import net.runelite.api.events.WidgetLoaded; import net.runelite.api.widgets.WidgetID; import net.runelite.client.account.AccountSession; @@ -95,6 +99,7 @@ import net.runelite.http.api.loottracker.LootRecord; import net.runelite.http.api.loottracker.LootRecordType; import net.runelite.http.api.loottracker.LootTrackerClient; import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.text.WordUtils; @PluginDescriptor( name = "Loot Tracker", @@ -137,6 +142,18 @@ public class LootTrackerPlugin extends Plugin // Last man standing map regions private static final Set LAST_MAN_STANDING_REGIONS = ImmutableSet.of(13658, 13659, 13914, 13915, 13916); + private static final Pattern PICKPOCKET_REGEX = Pattern.compile("You pick (the )?(?.+)'s? pocket.*"); + + /* + * This map is used when a pickpocket target has a different name in the chat message than their in-game name. + * Note that if the two NPCs can be found in the same place, there is a chance of race conditions + * occurring when changing targets mid-pickpocket, in which case a different solution may need to be considered. + */ + private static final Multimap PICKPOCKET_DISAMBIGUATION_MAP = ImmutableMultimap.of( + "H.A.M. Member", "Man", + "H.A.M. Member", "Woman" + ); + @Inject private ClientToolbar clientToolbar; @@ -166,8 +183,12 @@ public class LootTrackerPlugin extends Plugin private LootTrackerPanel panel; private NavigationButton navButton; - private String eventType; + @VisibleForTesting + String eventType; + @VisibleForTesting + LootRecordType lootRecordType; private boolean chestLooted; + private String lastPickpocketTarget; private List ignoredItems = new ArrayList<>(); @@ -370,13 +391,14 @@ public class LootTrackerPlugin extends Plugin } @Subscribe - public void onWidgetLoaded(WidgetLoaded event) + public void onWidgetLoaded(WidgetLoaded widgetLoaded) { + final String event; final ItemContainer container; - switch (event.getGroupId()) + switch (widgetLoaded.getGroupId()) { case (WidgetID.BARROWS_REWARD_GROUP_ID): - eventType = "Barrows"; + event = "Barrows"; container = client.getItemContainer(InventoryID.BARROWS_REWARD); break; case (WidgetID.CHAMBERS_OF_XERIC_REWARD_GROUP_ID): @@ -384,7 +406,7 @@ public class LootTrackerPlugin extends Plugin { return; } - eventType = "Chambers of Xeric"; + event = "Chambers of Xeric"; container = client.getItemContainer(InventoryID.CHAMBERS_OF_XERIC_CHEST); chestLooted = true; break; @@ -398,21 +420,22 @@ public class LootTrackerPlugin extends Plugin { return; } - eventType = "Theatre of Blood"; + event = "Theatre of Blood"; container = client.getItemContainer(InventoryID.THEATRE_OF_BLOOD_CHEST); chestLooted = true; break; case (WidgetID.CLUE_SCROLL_REWARD_GROUP_ID): // event type should be set via ChatMessage for clue scrolls. // Clue Scrolls use same InventoryID as Barrows + event = eventType; container = client.getItemContainer(InventoryID.BARROWS_REWARD); break; case (WidgetID.KINGDOM_GROUP_ID): - eventType = "Kingdom of Miscellania"; + event = "Kingdom of Miscellania"; container = client.getItemContainer(InventoryID.KINGDOM_OF_MISCELLANIA); break; case (WidgetID.FISHING_TRAWLER_REWARD_GROUP_ID): - eventType = "Fishing Trawler"; + event = "Fishing Trawler"; container = client.getItemContainer(InventoryID.FISHING_TRAWLER_REWARD); break; default: @@ -432,11 +455,11 @@ public class LootTrackerPlugin extends Plugin if (items.isEmpty()) { - log.debug("No items to find for Event: {} | Container: {}", eventType, container); + log.debug("No items to find for Event: {} | Container: {}", event, container); return; } - addLoot(eventType, -1, LootRecordType.EVENT, items); + addLoot(event, -1, LootRecordType.EVENT, items); } @Subscribe @@ -458,6 +481,7 @@ public class LootTrackerPlugin extends Plugin } eventType = CHEST_EVENT_TYPES.get(regionID); + lootRecordType = LootRecordType.EVENT; takeInventorySnapshot(); return; @@ -466,6 +490,7 @@ public class LootTrackerPlugin extends Plugin if (message.equals(HERBIBOAR_LOOTED_MESSAGE)) { eventType = HERBIBOAR_EVENT; + lootRecordType = LootRecordType.EVENT; takeInventorySnapshot(); return; @@ -475,6 +500,7 @@ public class LootTrackerPlugin extends Plugin if (HESPORI_REGION == regionID && message.equals(HESPORI_LOOTED_MESSAGE)) { eventType = HESPORI_EVENT; + lootRecordType = LootRecordType.EVENT; takeInventorySnapshot(); return; } @@ -482,6 +508,29 @@ public class LootTrackerPlugin extends Plugin if (GAUNTLET_LOBBY_REGION == regionID && message.equals(GAUNTLET_LOOTED_MESSAGE)) { eventType = GAUNTLET_EVENT; + lootRecordType = LootRecordType.EVENT; + takeInventorySnapshot(); + return; + } + + final Matcher pickpocketMatcher = PICKPOCKET_REGEX.matcher(message); + if (pickpocketMatcher.matches()) + { + // Get the target's name as listed in the chat box + String pickpocketTarget = WordUtils.capitalize(pickpocketMatcher.group("target")); + + // Occasional edge case where the pickpocket message doesn't list the correct name of the NPC (e.g. H.A.M. Members) + if (PICKPOCKET_DISAMBIGUATION_MAP.get(lastPickpocketTarget).contains(pickpocketTarget)) + { + eventType = lastPickpocketTarget; + lootRecordType = LootRecordType.PICKPOCKET; + } + else + { + eventType = pickpocketTarget; + lootRecordType = LootRecordType.PICKPOCKET; + } + takeInventorySnapshot(); return; } @@ -495,21 +544,27 @@ public class LootTrackerPlugin extends Plugin { case "beginner": eventType = "Clue Scroll (Beginner)"; + lootRecordType = LootRecordType.EVENT; break; case "easy": eventType = "Clue Scroll (Easy)"; + lootRecordType = LootRecordType.EVENT; break; case "medium": eventType = "Clue Scroll (Medium)"; + lootRecordType = LootRecordType.EVENT; break; case "hard": eventType = "Clue Scroll (Hard)"; + lootRecordType = LootRecordType.EVENT; break; case "elite": eventType = "Clue Scroll (Elite)"; + lootRecordType = LootRecordType.EVENT; break; case "master": eventType = "Clue Scroll (Master)"; + lootRecordType = LootRecordType.EVENT; break; } } @@ -518,18 +573,31 @@ public class LootTrackerPlugin extends Plugin @Subscribe public void onItemContainerChanged(ItemContainerChanged event) { + if (event.getContainerId() != InventoryID.INVENTORY.getId()) + { + return; + } + if (CHEST_EVENT_TYPES.containsValue(eventType) || HERBIBOAR_EVENT.equals(eventType) || HESPORI_EVENT.equals(eventType) - || GAUNTLET_EVENT.equals(eventType)) + || GAUNTLET_EVENT.equals(eventType) + || lootRecordType == LootRecordType.PICKPOCKET) { - if (event.getItemContainer() != client.getItemContainer(InventoryID.INVENTORY)) - { - return; - } - - processChestLoot(eventType, event.getItemContainer()); + processInventoryLoot(eventType, lootRecordType, event.getItemContainer()); eventType = null; + lootRecordType = null; + } + } + + @Subscribe + public void onMenuOptionClicked(MenuOptionClicked event) + { + // There are some pickpocket targets who show up in the chat box with a different name (e.g. H.A.M. members -> man/woman) + // We use the value selected from the right-click menu as a fallback for the event lookup in those cases. + if (event.getMenuOption().equals("Pickpocket")) + { + lastPickpocketTarget = Text.removeTags(event.getMenuTarget()); } } @@ -578,7 +646,7 @@ public class LootTrackerPlugin extends Plugin } } - private void processChestLoot(String chestType, ItemContainer inventoryContainer) + private void processInventoryLoot(String event, LootRecordType lootRecordType, ItemContainer inventoryContainer) { if (inventorySnapshot != null) { @@ -592,7 +660,7 @@ public class LootTrackerPlugin extends Plugin .map(e -> new ItemStack(e.getElement(), e.getCount(), client.getLocalPlayer().getLocalLocation())) .collect(Collectors.toList()); - addLoot(chestType, -1, LootRecordType.EVENT, items); + addLoot(event, -1, lootRecordType, items); inventorySnapshot = null; } diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/loottracker/LootTrackerPluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/loottracker/LootTrackerPluginTest.java new file mode 100644 index 0000000000..29a690c694 --- /dev/null +++ b/runelite-client/src/test/java/net/runelite/client/plugins/loottracker/LootTrackerPluginTest.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2020, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.loottracker; + +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 javax.inject.Inject; +import net.runelite.api.ChatMessageType; +import net.runelite.api.Client; +import net.runelite.api.Player; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.ChatMessage; +import net.runelite.client.game.SpriteManager; +import net.runelite.client.ui.overlay.infobox.InfoBoxManager; +import net.runelite.http.api.loottracker.LootRecordType; +import static org.junit.Assert.assertEquals; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class LootTrackerPluginTest +{ + @Mock + @Bind + private ScheduledExecutorService scheduledExecutorService; + + @Mock + @Bind + private Client client; + + @Mock + @Bind + private SpriteManager spriteManager; + + @Mock + @Bind + private InfoBoxManager infoBoxManager; + + @Inject + private LootTrackerPlugin lootTrackerPlugin; + + @Mock + @Bind + private LootTrackerConfig lootTrackerConfig; + + @Before + public void setUp() + { + Guice.createInjector(BoundFieldModule.of(this)).injectMembers(this); + } + + @Test + public void testPickPocket() + { + Player player = mock(Player.class); + when(player.getWorldLocation()).thenReturn(new WorldPoint(0, 0, 0)); + when(client.getLocalPlayer()).thenReturn(player); + + ChatMessage chatMessage = new ChatMessage(null, ChatMessageType.SPAM, "", "You pick the hero's pocket.", "", 0); + lootTrackerPlugin.onChatMessage(chatMessage); + + assertEquals("Hero", lootTrackerPlugin.eventType); + assertEquals(LootRecordType.PICKPOCKET, lootTrackerPlugin.lootRecordType); + } +} \ No newline at end of file From 63e537e0e02967ae5d78e23e250e3eb6237faa67 Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 9 Feb 2020 17:11:37 -0500 Subject: [PATCH 17/18] loot tracker: store and match events by type --- .../plugins/loottracker/LootTrackerBox.java | 6 +++++- .../plugins/loottracker/LootTrackerPanel.java | 17 +++++++++++++---- .../plugins/loottracker/LootTrackerPlugin.java | 4 ++-- .../plugins/loottracker/LootTrackerRecord.java | 2 ++ 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerBox.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerBox.java index 4bfb960917..8d7413f047 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerBox.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerBox.java @@ -57,6 +57,7 @@ import net.runelite.client.util.AsyncBufferedImage; import net.runelite.client.util.ImageUtil; import net.runelite.client.util.QuantityFormatter; import net.runelite.client.util.Text; +import net.runelite.http.api.loottracker.LootRecordType; class LootTrackerBox extends JPanel { @@ -70,6 +71,7 @@ class LootTrackerBox extends JPanel private final ItemManager itemManager; @Getter(AccessLevel.PACKAGE) private final String id; + private final LootRecordType lootRecordType; private final LootTrackerPriceType priceType; private final boolean showPriceType; @@ -84,6 +86,7 @@ class LootTrackerBox extends JPanel LootTrackerBox( final ItemManager itemManager, final String id, + final LootRecordType lootRecordType, @Nullable final String subtitle, final boolean hideIgnoredItems, final LootTrackerPriceType priceType, @@ -91,6 +94,7 @@ class LootTrackerBox extends JPanel final BiConsumer onItemToggle) { this.id = id; + this.lootRecordType = lootRecordType; this.itemManager = itemManager; this.onItemToggle = onItemToggle; this.hideIgnoredItems = hideIgnoredItems; @@ -151,7 +155,7 @@ class LootTrackerBox extends JPanel */ boolean matches(final LootTrackerRecord record) { - return record.getTitle().equals(id); + return record.getTitle().equals(id) && record.getType() == lootRecordType; } /** diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java index 5f1d4d4867..da064f524b 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java @@ -61,6 +61,7 @@ import net.runelite.client.util.ColorUtil; import net.runelite.client.util.ImageUtil; import net.runelite.client.util.QuantityFormatter; import net.runelite.client.util.SwingUtil; +import net.runelite.http.api.loottracker.LootRecordType; import net.runelite.http.api.loottracker.LootTrackerClient; class LootTrackerPanel extends PluginPanel @@ -337,10 +338,18 @@ class LootTrackerPanel extends PluginPanel * Creates a subtitle, adds a new entry and then passes off to the render methods, that will decide * how to display this new data. */ - void add(final String eventName, final int actorLevel, LootTrackerItem[] items) + void add(final String eventName, final LootRecordType type, final int actorLevel, LootTrackerItem[] items) { - final String subTitle = actorLevel > -1 ? "(lvl-" + actorLevel + ")" : ""; - final LootTrackerRecord record = new LootTrackerRecord(eventName, subTitle, items, 1); + final String subTitle; + if (type == LootRecordType.PICKPOCKET) + { + subTitle = "(pickpocket)"; + } + else + { + subTitle = actorLevel > -1 ? "(lvl-" + actorLevel + ")" : ""; + } + final LootTrackerRecord record = new LootTrackerRecord(eventName, subTitle, type, items, 1); sessionRecords.add(record); LootTrackerBox box = buildBox(record); @@ -487,7 +496,7 @@ class LootTrackerPanel extends PluginPanel overallPanel.setVisible(true); // Create box - final LootTrackerBox box = new LootTrackerBox(itemManager, record.getTitle(), record.getSubTitle(), + final LootTrackerBox box = new LootTrackerBox(itemManager, record.getTitle(), record.getType(), record.getSubTitle(), hideIgnoredItems, config.priceType(), config.showPriceType(), plugin::toggleItem); box.addKill(record); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java index 44619a56b2..b48684ddf9 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java @@ -348,7 +348,7 @@ public class LootTrackerPlugin extends Plugin void addLoot(String name, int combatLevel, LootRecordType type, Collection items) { final LootTrackerItem[] entries = buildEntries(stack(items)); - SwingUtilities.invokeLater(() -> panel.add(name, combatLevel, entries)); + SwingUtilities.invokeLater(() -> panel.add(name, type, combatLevel, entries)); if (config.saveLoot()) { @@ -729,7 +729,7 @@ public class LootTrackerPlugin extends Plugin buildLootTrackerItem(itemStack.getId(), itemStack.getQty()) ).toArray(LootTrackerItem[]::new); - return new LootTrackerRecord(record.getEventId(), "", drops, record.getAmount()); + return new LootTrackerRecord(record.getEventId(), "", record.getType(), drops, record.getAmount()); }) .collect(Collectors.toCollection(ArrayList::new)); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerRecord.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerRecord.java index 502401a968..0763337109 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerRecord.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerRecord.java @@ -25,12 +25,14 @@ package net.runelite.client.plugins.loottracker; import lombok.Value; +import net.runelite.http.api.loottracker.LootRecordType; @Value class LootTrackerRecord { private final String title; private final String subTitle; + private final LootRecordType type; private final LootTrackerItem[] items; private final int kills; From 8137fd87c2ba682c6e31709548b8eae721d77b63 Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 9 Feb 2020 17:18:44 -0500 Subject: [PATCH 18/18] loot tracker: filter by type in details view --- .../plugins/loottracker/LootTrackerBox.java | 7 ++++--- .../plugins/loottracker/LootTrackerPanel.java | 15 +++++++++------ .../plugins/loottracker/LootTrackerRecord.java | 4 ++-- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerBox.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerBox.java index 8d7413f047..eda30ffb3a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerBox.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerBox.java @@ -159,19 +159,20 @@ class LootTrackerBox extends JPanel } /** - * Checks if this box matches specified id + * Checks if this box matches specified id and type * * @param id other record id + * @param type other record type * @return true if match is made */ - boolean matches(final String id) + boolean matches(final String id, final LootRecordType type) { if (id == null) { return true; } - return this.id.equals(id); + return this.id.equals(id) && lootRecordType == type; } /** diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java index da064f524b..141f306b57 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java @@ -124,6 +124,7 @@ class LootTrackerPanel extends PluginPanel private boolean groupLoot; private boolean hideIgnoredItems; private String currentView; + private LootRecordType currentType; static { @@ -236,6 +237,7 @@ class LootTrackerPanel extends PluginPanel backBtn.addActionListener(ev -> { currentView = null; + currentType = null; backBtn.setVisible(false); detailsTitle.setText(""); rebuild(); @@ -285,9 +287,9 @@ class LootTrackerPanel extends PluginPanel } // If not in detailed view, remove all, otherwise only remove for the currently detailed title - sessionRecords.removeIf(r -> r.matches(currentView)); - aggregateRecords.removeIf(r -> r.matches(currentView)); - boxes.removeIf(b -> b.matches(currentView)); + sessionRecords.removeIf(r -> r.matches(currentView, currentType)); + aggregateRecords.removeIf(r -> r.matches(currentView, currentType)); + boxes.removeIf(b -> b.matches(currentView, currentType)); updateOverall(); logsContainer.removeAll(); logsContainer.repaint(); @@ -472,7 +474,7 @@ class LootTrackerPanel extends PluginPanel private LootTrackerBox buildBox(LootTrackerRecord record) { // If this record is not part of current view, return - if (!record.matches(currentView)) + if (!record.matches(currentView, currentType)) { return null; } @@ -532,7 +534,7 @@ class LootTrackerPanel extends PluginPanel { Predicate match = groupLoot // With grouped loot, remove any record with this title - ? r -> r.matches(record.getTitle()) + ? r -> r.matches(record.getTitle(), record.getType()) // Otherwise remove specifically this entry : r -> r.equals(record); sessionRecords.removeIf(match); @@ -557,6 +559,7 @@ class LootTrackerPanel extends PluginPanel details.addActionListener(e -> { currentView = record.getTitle(); + currentType = record.getType(); detailsTitle.setText(currentView); backBtn.setVisible(true); rebuild(); @@ -584,7 +587,7 @@ class LootTrackerPanel extends PluginPanel for (LootTrackerRecord record : concat(aggregateRecords, sessionRecords)) { - if (!record.matches(currentView)) + if (!record.matches(currentView, currentType)) { continue; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerRecord.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerRecord.java index 0763337109..a7f455c1f7 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerRecord.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerRecord.java @@ -42,13 +42,13 @@ class LootTrackerRecord * @param id other record id * @return true if match is made */ - boolean matches(final String id) + boolean matches(final String id, LootRecordType type) { if (id == null) { return true; } - return title.equals(id); + return title.equals(id) && this.type == type; } } \ No newline at end of file