Dev Tools - Add Inventory Inspector
This commit is contained in:
@@ -51,6 +51,7 @@ class DevToolsPanel extends PluginPanel
|
|||||||
private final WidgetInspector widgetInspector;
|
private final WidgetInspector widgetInspector;
|
||||||
private final VarInspector varInspector;
|
private final VarInspector varInspector;
|
||||||
private final ScriptInspector scriptInspector;
|
private final ScriptInspector scriptInspector;
|
||||||
|
private final InventoryInspector inventoryInspector;
|
||||||
private final InfoBoxManager infoBoxManager;
|
private final InfoBoxManager infoBoxManager;
|
||||||
private final ScheduledExecutorService scheduledExecutorService;
|
private final ScheduledExecutorService scheduledExecutorService;
|
||||||
|
|
||||||
@@ -61,6 +62,7 @@ class DevToolsPanel extends PluginPanel
|
|||||||
WidgetInspector widgetInspector,
|
WidgetInspector widgetInspector,
|
||||||
VarInspector varInspector,
|
VarInspector varInspector,
|
||||||
ScriptInspector scriptInspector,
|
ScriptInspector scriptInspector,
|
||||||
|
InventoryInspector inventoryInspector,
|
||||||
Notifier notifier,
|
Notifier notifier,
|
||||||
InfoBoxManager infoBoxManager,
|
InfoBoxManager infoBoxManager,
|
||||||
ScheduledExecutorService scheduledExecutorService)
|
ScheduledExecutorService scheduledExecutorService)
|
||||||
@@ -70,6 +72,7 @@ class DevToolsPanel extends PluginPanel
|
|||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.widgetInspector = widgetInspector;
|
this.widgetInspector = widgetInspector;
|
||||||
this.varInspector = varInspector;
|
this.varInspector = varInspector;
|
||||||
|
this.inventoryInspector = inventoryInspector;
|
||||||
this.scriptInspector = scriptInspector;
|
this.scriptInspector = scriptInspector;
|
||||||
this.notifier = notifier;
|
this.notifier = notifier;
|
||||||
this.infoBoxManager = infoBoxManager;
|
this.infoBoxManager = infoBoxManager;
|
||||||
@@ -180,6 +183,19 @@ class DevToolsPanel extends PluginPanel
|
|||||||
clearInfoboxBtn.addActionListener(e -> infoBoxManager.removeIf(i -> true));
|
clearInfoboxBtn.addActionListener(e -> infoBoxManager.removeIf(i -> true));
|
||||||
container.add(clearInfoboxBtn);
|
container.add(clearInfoboxBtn);
|
||||||
|
|
||||||
|
container.add(plugin.getInventoryInspector());
|
||||||
|
plugin.getInventoryInspector().addActionListener((ev) ->
|
||||||
|
{
|
||||||
|
if (plugin.getInventoryInspector().isActive())
|
||||||
|
{
|
||||||
|
inventoryInspector.close();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
inventoryInspector.open();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ public class DevToolsPlugin extends Plugin
|
|||||||
private DevToolsButton varInspector;
|
private DevToolsButton varInspector;
|
||||||
private DevToolsButton soundEffects;
|
private DevToolsButton soundEffects;
|
||||||
private DevToolsButton scriptInspector;
|
private DevToolsButton scriptInspector;
|
||||||
|
private DevToolsButton inventoryInspector;
|
||||||
private NavigationButton navButton;
|
private NavigationButton navButton;
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -182,6 +183,7 @@ public class DevToolsPlugin extends Plugin
|
|||||||
varInspector = new DevToolsButton("Var Inspector");
|
varInspector = new DevToolsButton("Var Inspector");
|
||||||
soundEffects = new DevToolsButton("Sound Effects");
|
soundEffects = new DevToolsButton("Sound Effects");
|
||||||
scriptInspector = new DevToolsButton("Script Inspector");
|
scriptInspector = new DevToolsButton("Script Inspector");
|
||||||
|
inventoryInspector = new DevToolsButton("Inventory Inspector");
|
||||||
|
|
||||||
overlayManager.add(overlay);
|
overlayManager.add(overlay);
|
||||||
overlayManager.add(locationOverlay);
|
overlayManager.add(locationOverlay);
|
||||||
|
|||||||
@@ -0,0 +1,223 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020, TheStonedTurtle <https://github.com/TheStonedTurtle>
|
||||||
|
* 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 java.awt.Color;
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.awt.GridLayout;
|
||||||
|
import java.awt.Rectangle;
|
||||||
|
import java.awt.event.ComponentAdapter;
|
||||||
|
import java.awt.event.ComponentEvent;
|
||||||
|
import java.awt.event.MouseAdapter;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.swing.BoxLayout;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.Scrollable;
|
||||||
|
import javax.swing.SwingConstants;
|
||||||
|
import javax.swing.border.EmptyBorder;
|
||||||
|
import net.runelite.api.Constants;
|
||||||
|
import net.runelite.api.Item;
|
||||||
|
import net.runelite.client.game.ItemManager;
|
||||||
|
import net.runelite.client.ui.ColorScheme;
|
||||||
|
import net.runelite.client.ui.FontManager;
|
||||||
|
|
||||||
|
class InventoryDeltaPanel extends JPanel implements Scrollable
|
||||||
|
{
|
||||||
|
private static final DecimalFormat COMMA_FORMAT = new DecimalFormat("#,###");
|
||||||
|
private static final Dimension ITEM_SIZE = new Dimension(Constants.ITEM_SPRITE_WIDTH + 4, Constants.ITEM_SPRITE_HEIGHT);
|
||||||
|
|
||||||
|
private final ItemManager itemManager;
|
||||||
|
private final JPanel addedGrid = new JPanel();
|
||||||
|
private final JPanel removedGrid = new JPanel();
|
||||||
|
private final JPanel currentGrid = new JPanel();
|
||||||
|
|
||||||
|
InventoryDeltaPanel(final ItemManager itemManager)
|
||||||
|
{
|
||||||
|
this.itemManager = itemManager;
|
||||||
|
|
||||||
|
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
|
||||||
|
|
||||||
|
final EmptyBorder border = new EmptyBorder(2, 2, 2, 2);
|
||||||
|
setBorder(border);
|
||||||
|
addedGrid.setBorder(border);
|
||||||
|
removedGrid.setBorder(border);
|
||||||
|
currentGrid.setBorder(border);
|
||||||
|
|
||||||
|
final GridLayout layout = new GridLayout(0, 1, 1, 1);
|
||||||
|
addedGrid.setLayout(layout);
|
||||||
|
removedGrid.setLayout(layout);
|
||||||
|
currentGrid.setLayout(layout);
|
||||||
|
|
||||||
|
// Listen for resize events
|
||||||
|
addComponentListener(new ComponentAdapter()
|
||||||
|
{
|
||||||
|
public void componentResized(final ComponentEvent componentEvent)
|
||||||
|
{
|
||||||
|
// Account for container and slot padding
|
||||||
|
final int cols = Math.max((getWidth() - 4) / (ITEM_SIZE.width + 1), 1);
|
||||||
|
final GridLayout layout = new GridLayout(0, cols, 1, 1);
|
||||||
|
addedGrid.setLayout(layout);
|
||||||
|
removedGrid.setLayout(layout);
|
||||||
|
currentGrid.setLayout(layout);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear()
|
||||||
|
{
|
||||||
|
addedGrid.removeAll();
|
||||||
|
removedGrid.removeAll();
|
||||||
|
currentGrid.removeAll();
|
||||||
|
removeAll();
|
||||||
|
revalidate();
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
void displayItems(final InventoryItem[] items, @Nullable final InventoryItem[] added, @Nullable final InventoryItem[] removed)
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
|
||||||
|
if (added != null && added.length > 0)
|
||||||
|
{
|
||||||
|
final JLabel label = new JLabel("Items Added:", JLabel.CENTER);
|
||||||
|
label.setAlignmentX(JLabel.CENTER_ALIGNMENT);
|
||||||
|
add(label);
|
||||||
|
add(addedGrid);
|
||||||
|
|
||||||
|
for (final InventoryItem item : added)
|
||||||
|
{
|
||||||
|
addItemToPanel(item, addedGrid).setBackground(new Color(0, 100, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removed != null && removed.length > 0)
|
||||||
|
{
|
||||||
|
final JLabel label = new JLabel("Items Removed:", JLabel.CENTER);
|
||||||
|
label.setAlignmentX(JLabel.CENTER_ALIGNMENT);
|
||||||
|
add(label);
|
||||||
|
add(removedGrid);
|
||||||
|
|
||||||
|
for (final InventoryItem item : removed)
|
||||||
|
{
|
||||||
|
addItemToPanel(item, removedGrid).setBackground(new Color(120, 0, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final JLabel label = new JLabel("Items in Inventory:", JLabel.CENTER);
|
||||||
|
label.setAlignmentX(JLabel.CENTER_ALIGNMENT);
|
||||||
|
add(label);
|
||||||
|
add(currentGrid);
|
||||||
|
|
||||||
|
for (final InventoryItem item : items)
|
||||||
|
{
|
||||||
|
final JLabel gridItem = addItemToPanel(item, currentGrid);
|
||||||
|
// Add hover effect
|
||||||
|
gridItem.addMouseListener(new MouseAdapter()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void mouseEntered(MouseEvent e)
|
||||||
|
{
|
||||||
|
final JLabel label = (JLabel) e.getSource();
|
||||||
|
label.setBackground(ColorScheme.DARKER_GRAY_HOVER_COLOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseExited(MouseEvent e)
|
||||||
|
{
|
||||||
|
final JLabel label = (JLabel) e.getSource();
|
||||||
|
label.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
gridItem.setToolTipText("<html>Name: " + item.getName()
|
||||||
|
+ "<br/>Item ID: " + item.getItem().getId()
|
||||||
|
+ "<br/>Quantity: " + COMMA_FORMAT.format(item.getItem().getQuantity())
|
||||||
|
+ "<br/>Slot: " + item.getSlot()
|
||||||
|
+ "</html>");
|
||||||
|
}
|
||||||
|
|
||||||
|
revalidate();
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
private JLabel addItemToPanel(final InventoryItem inventoryItem, final JPanel panel)
|
||||||
|
{
|
||||||
|
final JLabel gridItem = new JLabel();
|
||||||
|
gridItem.setOpaque(true);
|
||||||
|
gridItem.setPreferredSize(ITEM_SIZE);
|
||||||
|
gridItem.setVerticalAlignment(SwingConstants.CENTER);
|
||||||
|
gridItem.setHorizontalAlignment(SwingConstants.CENTER);
|
||||||
|
gridItem.setFont(FontManager.getRunescapeSmallFont());
|
||||||
|
|
||||||
|
final Item item = inventoryItem.getItem();
|
||||||
|
if (item.getId() == -1)
|
||||||
|
{
|
||||||
|
gridItem.setText("EMPTY");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
itemManager.getImage(item.getId(), item.getQuantity(), item.getQuantity() > 1).addTo(gridItem);
|
||||||
|
gridItem.setToolTipText("<html>Name: " + inventoryItem.getName()
|
||||||
|
+ "<br/>Item ID: " + item.getId()
|
||||||
|
+ "<br/>Quantity: " + COMMA_FORMAT.format(item.getQuantity())
|
||||||
|
+ "</html>");
|
||||||
|
}
|
||||||
|
|
||||||
|
panel.add(gridItem);
|
||||||
|
return gridItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Dimension getPreferredScrollableViewportSize()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction)
|
||||||
|
{
|
||||||
|
return 1 + (orientation == SwingConstants.VERTICAL ? ITEM_SIZE.height : ITEM_SIZE.width);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction)
|
||||||
|
{
|
||||||
|
return 1 + (orientation == SwingConstants.VERTICAL ? ITEM_SIZE.height : ITEM_SIZE.width);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getScrollableTracksViewportWidth()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getScrollableTracksViewportHeight()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,361 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020, TheStonedTurtle <https://github.com/TheStonedTurtle>
|
||||||
|
* 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 java.awt.BorderLayout;
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.awt.event.AdjustmentEvent;
|
||||||
|
import java.awt.event.AdjustmentListener;
|
||||||
|
import java.awt.event.WindowAdapter;
|
||||||
|
import java.awt.event.WindowEvent;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.JFrame;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JScrollBar;
|
||||||
|
import javax.swing.JScrollPane;
|
||||||
|
import javax.swing.JSplitPane;
|
||||||
|
import javax.swing.JTree;
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
|
import javax.swing.border.EmptyBorder;
|
||||||
|
import javax.swing.tree.DefaultMutableTreeNode;
|
||||||
|
import javax.swing.tree.DefaultTreeModel;
|
||||||
|
import javax.swing.tree.TreeNode;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.runelite.api.Client;
|
||||||
|
import net.runelite.api.InventoryID;
|
||||||
|
import net.runelite.api.Item;
|
||||||
|
import net.runelite.api.ItemComposition;
|
||||||
|
import net.runelite.api.events.ItemContainerChanged;
|
||||||
|
import net.runelite.client.callback.ClientThread;
|
||||||
|
import net.runelite.client.eventbus.EventBus;
|
||||||
|
import net.runelite.client.eventbus.Subscribe;
|
||||||
|
import net.runelite.client.game.ItemManager;
|
||||||
|
import net.runelite.client.ui.ClientUI;
|
||||||
|
import net.runelite.client.ui.ColorScheme;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Singleton
|
||||||
|
class InventoryInspector extends JFrame
|
||||||
|
{
|
||||||
|
private static final int MAX_LOG_ENTRIES = 25;
|
||||||
|
|
||||||
|
private final Client client;
|
||||||
|
private final EventBus eventBus;
|
||||||
|
private final ItemManager itemManager;
|
||||||
|
|
||||||
|
private final Map<Integer, InventoryTreeNode> nodeMap = new HashMap<>();
|
||||||
|
private final Map<Integer, InventoryLog> logMap = new HashMap<>();
|
||||||
|
private final DefaultMutableTreeNode trackerRootNode = new DefaultMutableTreeNode();
|
||||||
|
private final JTree tree = new JTree(trackerRootNode);
|
||||||
|
private final InventoryDeltaPanel deltaPanel;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
InventoryInspector(Client client, EventBus eventBus, DevToolsPlugin plugin, ItemManager itemManager, ClientThread clientThread)
|
||||||
|
{
|
||||||
|
this.client = client;
|
||||||
|
this.eventBus = eventBus;
|
||||||
|
this.itemManager = itemManager;
|
||||||
|
|
||||||
|
this.deltaPanel = new InventoryDeltaPanel(itemManager);
|
||||||
|
|
||||||
|
setLayout(new BorderLayout());
|
||||||
|
setTitle("RuneLite Inventory Inspector");
|
||||||
|
setIconImage(ClientUI.ICON);
|
||||||
|
|
||||||
|
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
|
||||||
|
// Reset highlight on close
|
||||||
|
addWindowListener(new WindowAdapter()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void windowClosing(WindowEvent e)
|
||||||
|
{
|
||||||
|
close();
|
||||||
|
plugin.getInventoryInspector().setActive(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
tree.setBorder(new EmptyBorder(2, 2, 2, 2));
|
||||||
|
tree.setRootVisible(false);
|
||||||
|
tree.setShowsRootHandles(true);
|
||||||
|
tree.addTreeSelectionListener(e ->
|
||||||
|
{
|
||||||
|
if (e.getNewLeadSelectionPath() == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Object node = e.getNewLeadSelectionPath().getLastPathComponent();
|
||||||
|
if (node instanceof InventoryLogNode)
|
||||||
|
{
|
||||||
|
clientThread.invoke(() -> displayItemSnapshot((InventoryLogNode) node));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
tree.setModel(new DefaultTreeModel(trackerRootNode));
|
||||||
|
|
||||||
|
final JPanel leftSide = new JPanel();
|
||||||
|
leftSide.setLayout(new BorderLayout());
|
||||||
|
|
||||||
|
final JScrollPane trackerScroller = new JScrollPane(tree);
|
||||||
|
trackerScroller.setPreferredSize(new Dimension(200, 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 JButton refreshBtn = new JButton("Refresh");
|
||||||
|
refreshBtn.setFocusable(false);
|
||||||
|
refreshBtn.addActionListener(e -> refreshTracker());
|
||||||
|
|
||||||
|
final JButton clearBtn = new JButton("Clear");
|
||||||
|
clearBtn.setFocusable(false);
|
||||||
|
clearBtn.addActionListener(e -> clearTracker());
|
||||||
|
|
||||||
|
final JPanel bottomRow = new JPanel();
|
||||||
|
bottomRow.add(refreshBtn);
|
||||||
|
bottomRow.add(clearBtn);
|
||||||
|
|
||||||
|
leftSide.add(bottomRow, BorderLayout.SOUTH);
|
||||||
|
|
||||||
|
final JScrollPane gridScroller = new JScrollPane(deltaPanel);
|
||||||
|
gridScroller.getViewport().setBackground(ColorScheme.DARK_GRAY_COLOR);
|
||||||
|
gridScroller.setPreferredSize(new Dimension(200, 400));
|
||||||
|
|
||||||
|
final JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftSide, gridScroller);
|
||||||
|
add(split, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
pack();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void open()
|
||||||
|
{
|
||||||
|
eventBus.register(this);
|
||||||
|
setVisible(true);
|
||||||
|
toFront();
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close()
|
||||||
|
{
|
||||||
|
eventBus.unregister(this);
|
||||||
|
clearTracker();
|
||||||
|
setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onItemContainerChanged(ItemContainerChanged event)
|
||||||
|
{
|
||||||
|
final int id = event.getContainerId();
|
||||||
|
final InventoryLog log = new InventoryLog(id, getNameForInventoryID(id), event.getItemContainer().getItems(), client.getTickCount());
|
||||||
|
|
||||||
|
// Delay updates until refresh button is pressed
|
||||||
|
logMap.put(id, log);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addLog(final InventoryLog invLog)
|
||||||
|
{
|
||||||
|
final InventoryTreeNode node = nodeMap.computeIfAbsent(invLog.getContainerId(), (k) -> new InventoryTreeNode(invLog.getContainerId(), invLog.getContainerName()));
|
||||||
|
node.add(new InventoryLogNode(invLog));
|
||||||
|
|
||||||
|
// Cull very old stuff
|
||||||
|
for (; node.getChildCount() > MAX_LOG_ENTRIES; )
|
||||||
|
{
|
||||||
|
node.remove(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearTracker()
|
||||||
|
{
|
||||||
|
logMap.clear();
|
||||||
|
nodeMap.clear();
|
||||||
|
deltaPanel.clear();
|
||||||
|
trackerRootNode.removeAllChildren();
|
||||||
|
tree.setModel(new DefaultTreeModel(trackerRootNode));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshTracker()
|
||||||
|
{
|
||||||
|
deltaPanel.clear();
|
||||||
|
|
||||||
|
if (logMap.size() > 0)
|
||||||
|
{
|
||||||
|
logMap.values().forEach(this::addLog);
|
||||||
|
logMap.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
SwingUtilities.invokeLater(() ->
|
||||||
|
{
|
||||||
|
trackerRootNode.removeAllChildren();
|
||||||
|
nodeMap.values().forEach(trackerRootNode::add);
|
||||||
|
tree.setModel(new DefaultTreeModel(trackerRootNode));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void displayItemSnapshot(final InventoryLogNode logNode)
|
||||||
|
{
|
||||||
|
final InventoryTreeNode treeNode = nodeMap.get(logNode.getLog().getContainerId());
|
||||||
|
if (treeNode == null)
|
||||||
|
{
|
||||||
|
log.warn("Clicked on a JTree node that doesn't map anywhere: {}", logNode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Item[] curItems = logNode.getLog().getItems();
|
||||||
|
final InventoryItem[] curInventory = convertToInventoryItems(curItems);
|
||||||
|
|
||||||
|
InventoryItem[][] deltas = null;
|
||||||
|
// Compare against previous snapshot
|
||||||
|
if (treeNode.getIndex(logNode) > 0)
|
||||||
|
{
|
||||||
|
final TreeNode prevNode = treeNode.getChildBefore(logNode);
|
||||||
|
if (prevNode instanceof InventoryLogNode)
|
||||||
|
{
|
||||||
|
final InventoryLogNode prevLogNode = (InventoryLogNode) prevNode;
|
||||||
|
deltas = compareItemSnapshots(prevLogNode.getLog().getItems(), curItems);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final InventoryItem[] added = deltas == null ? null : deltas[0];
|
||||||
|
final InventoryItem[] removed = deltas == null ? null : deltas[1];
|
||||||
|
|
||||||
|
SwingUtilities.invokeLater(() -> deltaPanel.displayItems(curInventory, added, removed));
|
||||||
|
}
|
||||||
|
|
||||||
|
private InventoryItem[] convertToInventoryItems(final Item[] items)
|
||||||
|
{
|
||||||
|
final InventoryItem[] out = new InventoryItem[items.length];
|
||||||
|
for (int i = 0; i < items.length; i++)
|
||||||
|
{
|
||||||
|
final Item item = items[i];
|
||||||
|
final ItemComposition c = itemManager.getItemComposition(item.getId());
|
||||||
|
out[i] = new InventoryItem(i, item, c.getName(), c.isStackable());
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares the current inventory to the old one returning the InventoryItems that were added and removed.
|
||||||
|
* @param previous old snapshot
|
||||||
|
* @param current new snapshot
|
||||||
|
* @return The first InventoryItem[] contains the items that were added and the second contains the items that were removed
|
||||||
|
*/
|
||||||
|
private InventoryItem[][] compareItemSnapshots(final Item[] previous, final Item[] current)
|
||||||
|
{
|
||||||
|
final Map<Integer, Integer> qtyMap = new HashMap<>();
|
||||||
|
|
||||||
|
// ItemContainers shouldn't become smaller over time, but just in case
|
||||||
|
final int maxSlots = Math.max(previous.length, current.length);
|
||||||
|
for (int i = 0; i < maxSlots; i++)
|
||||||
|
{
|
||||||
|
final Item prev = previous.length > i ? previous[i] : null;
|
||||||
|
final Item cur = current.length > i ? current[i] : null;
|
||||||
|
|
||||||
|
if (prev != null)
|
||||||
|
{
|
||||||
|
qtyMap.merge(prev.getId(), -1 * prev.getQuantity(), Integer::sum);
|
||||||
|
}
|
||||||
|
if (cur != null)
|
||||||
|
{
|
||||||
|
qtyMap.merge(cur.getId(), cur.getQuantity(), Integer::sum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final Map<Boolean, List<InventoryItem>> result = qtyMap.entrySet().stream()
|
||||||
|
.filter(e -> e.getValue() != 0)
|
||||||
|
.flatMap(e ->
|
||||||
|
{
|
||||||
|
final int id = e.getKey();
|
||||||
|
final int qty = e.getValue();
|
||||||
|
final ItemComposition c = itemManager.getItemComposition(e.getKey());
|
||||||
|
|
||||||
|
InventoryItem[] items = new InventoryItem[]{
|
||||||
|
new InventoryItem(-1, new Item(id, qty), c.getName(), c.isStackable())
|
||||||
|
};
|
||||||
|
if (!c.isStackable() && (qty > 1 || qty < -1))
|
||||||
|
{
|
||||||
|
items = new InventoryItem[Math.abs(qty)];
|
||||||
|
for (int i = 0; i < Math.abs(qty); i++)
|
||||||
|
{
|
||||||
|
final Item item = new Item(id, Integer.signum(qty));
|
||||||
|
items[i] = new InventoryItem(-1, item, c.getName(), c.isStackable());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Arrays.stream(items);
|
||||||
|
})
|
||||||
|
.collect(Collectors.partitioningBy(item -> item.getItem().getQuantity() > 0));
|
||||||
|
|
||||||
|
final InventoryItem[] added = result.get(true).toArray(new InventoryItem[0]);
|
||||||
|
final InventoryItem[] removed = result.get(false).stream()
|
||||||
|
// Make quantities positive now that its been sorted.
|
||||||
|
.peek(i -> i.setItem(new Item(i.getItem().getId(), -i.getItem().getQuantity())))
|
||||||
|
.toArray(InventoryItem[]::new);
|
||||||
|
|
||||||
|
return new InventoryItem[][]{
|
||||||
|
added, removed
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static String getNameForInventoryID(final int id)
|
||||||
|
{
|
||||||
|
for (final InventoryID inv : InventoryID.values())
|
||||||
|
{
|
||||||
|
if (inv.getId() == id)
|
||||||
|
{
|
||||||
|
return inv.name();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020, TheStonedTurtle <https://github.com/TheStonedTurtle>
|
||||||
|
* 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 lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import net.runelite.api.Item;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
class InventoryItem
|
||||||
|
{
|
||||||
|
private final int slot;
|
||||||
|
private Item item;
|
||||||
|
private final String name;
|
||||||
|
private final boolean stackable;
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020, TheStonedTurtle <https://github.com/TheStonedTurtle>
|
||||||
|
* 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 javax.annotation.Nullable;
|
||||||
|
import lombok.Value;
|
||||||
|
import net.runelite.api.Item;
|
||||||
|
|
||||||
|
@Value
|
||||||
|
class InventoryLog
|
||||||
|
{
|
||||||
|
int containerId;
|
||||||
|
@Nullable
|
||||||
|
String containerName;
|
||||||
|
Item[] items;
|
||||||
|
int tick;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020, TheStonedTurtle <https://github.com/TheStonedTurtle>
|
||||||
|
* 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 javax.swing.tree.DefaultMutableTreeNode;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
class InventoryLogNode extends DefaultMutableTreeNode
|
||||||
|
{
|
||||||
|
private final InventoryLog log;
|
||||||
|
|
||||||
|
InventoryLogNode(final InventoryLog log)
|
||||||
|
{
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.log = log;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "Tick: " + log.getTick();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020, TheStonedTurtle <https://github.com/TheStonedTurtle>
|
||||||
|
* 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 javax.annotation.Nullable;
|
||||||
|
import javax.swing.tree.DefaultMutableTreeNode;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
class InventoryTreeNode extends DefaultMutableTreeNode
|
||||||
|
{
|
||||||
|
final int id;
|
||||||
|
@Nullable
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
InventoryTreeNode(final int id, @Nullable final String name)
|
||||||
|
{
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return id + (name == null ? "" : " - " + name);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user