Merge pull request #10565 from Trevor159/script-events

Add script fired events and add script inspector devtool
This commit is contained in:
Abex
2020-02-10 04:17:00 -07:00
committed by GitHub
6 changed files with 612 additions and 1 deletions

View File

@@ -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.
*/

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) 2020, Trevor <https://github.com/Trevor159>
* 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;
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright (c) 2020, Trevor <https://github.com/Trevor159>
* 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;
}

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -0,0 +1,494 @@
/*
* Copyright (c) 2020, Trevor <https://github.com/Trevor159>
* 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<Integer> blacklist;
private Set<Integer> 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<Integer> 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<Integer> set = getSet();
for (Integer i : set)
{
listModel.addElement(i);
}
}
private Set<Integer> getSet()
{
Set<Integer> 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;
}
}