This commit is contained in:
therealunull
2020-12-14 09:08:42 -05:00
parent aa44329202
commit a55f9502e2
115 changed files with 5376 additions and 267 deletions

View File

@@ -0,0 +1,93 @@
/*
* Copyright (c) 2018, Matthew Steglinski <https://github.com/sainttx>
* 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.Dimension;
import java.awt.Graphics2D;
import javax.inject.Inject;
import net.runelite.api.Client;
import net.runelite.client.ui.overlay.OverlayPanel;
import net.runelite.client.ui.overlay.OverlayPosition;
import net.runelite.client.ui.overlay.components.LineComponent;
import net.runelite.client.ui.overlay.components.TitleComponent;
public class CameraOverlay extends OverlayPanel
{
private final Client client;
private final DevToolsPlugin plugin;
@Inject
CameraOverlay(Client client, DevToolsPlugin plugin)
{
this.client = client;
this.plugin = plugin;
setPosition(OverlayPosition.TOP_LEFT);
}
@Override
public Dimension render(Graphics2D graphics)
{
if (!plugin.getCameraPosition().isActive())
{
return null;
}
panelComponent.getChildren().add(TitleComponent.builder()
.text("Camera")
.build());
panelComponent.getChildren().add(LineComponent.builder()
.left("X")
.right("" + client.getCameraX())
.build());
panelComponent.getChildren().add(LineComponent.builder()
.left("Y")
.right("" + client.getCameraY())
.build());
panelComponent.getChildren().add(LineComponent.builder()
.left("Z")
.right("" + client.getCameraZ())
.build());
panelComponent.getChildren().add(LineComponent.builder()
.left("Pitch")
.right("" + client.getCameraPitch())
.build());
panelComponent.getChildren().add(LineComponent.builder()
.left("Yaw")
.right("" + client.getCameraYaw())
.build());
panelComponent.getChildren().add(LineComponent.builder()
.left("Scale")
.right("" + client.getScale())
.build());
return super.render(graphics);
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright (c) 2018, Tomas Slusny <slusnucky@gmail.com>
* 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 javax.swing.JButton;
import lombok.Getter;
class DevToolsButton extends JButton
{
@Getter
private boolean active;
DevToolsButton(String title)
{
super(title);
addActionListener((ev) -> setActive(!active));
this.setToolTipText(title);
}
void setActive(boolean active)
{
this.active = active;
if (active)
{
setBackground(Color.GREEN);
}
else
{
setBackground(null);
}
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (c) 2018 Abex
* 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 net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
@ConfigGroup("devtools")
public interface DevToolsConfig extends Config
{
@ConfigItem(
keyName = "inspectorAlwaysOnTop",
name = "",
description = "",
hidden = true
)
default boolean inspectorAlwaysOnTop()
{
return false;
}
@ConfigItem(
keyName = "inspectorAlwaysOnTop",
name = "",
description = ""
)
void inspectorAlwaysOnTop(boolean value);
}

View File

@@ -0,0 +1,468 @@
/*
* Copyright (c) 2017, Kronos <https://github.com/KronosDesign>
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.Rectangle2D;
import java.util.List;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Singleton;
import net.runelite.api.Client;
import net.runelite.api.Constants;
import net.runelite.api.DecorativeObject;
import net.runelite.api.GameObject;
import net.runelite.api.GraphicsObject;
import net.runelite.api.TileItem;
import net.runelite.api.GroundObject;
import net.runelite.api.ItemLayer;
import net.runelite.api.NPC;
import net.runelite.api.NPCComposition;
import net.runelite.api.Node;
import net.runelite.api.Perspective;
import net.runelite.api.Player;
import net.runelite.api.Point;
import net.runelite.api.Projectile;
import net.runelite.api.Scene;
import net.runelite.api.Tile;
import net.runelite.api.WallObject;
import net.runelite.api.coords.LocalPoint;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.api.widgets.WidgetItem;
import net.runelite.client.ui.FontManager;
import net.runelite.client.ui.overlay.Overlay;
import net.runelite.client.ui.overlay.OverlayLayer;
import net.runelite.client.ui.overlay.OverlayPosition;
import net.runelite.client.ui.overlay.OverlayPriority;
import net.runelite.client.ui.overlay.OverlayUtil;
import net.runelite.client.ui.overlay.tooltip.Tooltip;
import net.runelite.client.ui.overlay.tooltip.TooltipManager;
@Singleton
class DevToolsOverlay extends Overlay
{
private static final Font FONT = FontManager.getRunescapeFont().deriveFont(Font.BOLD, 16);
private static final Color RED = new Color(221, 44, 0);
private static final Color GREEN = new Color(0, 200, 83);
private static final Color ORANGE = new Color(255, 109, 0);
private static final Color YELLOW = new Color(255, 214, 0);
private static final Color CYAN = new Color(0, 184, 212);
private static final Color BLUE = new Color(41, 98, 255);
private static final Color DEEP_PURPLE = new Color(98, 0, 234);
private static final Color PURPLE = new Color(170, 0, 255);
private static final Color GRAY = new Color(158, 158, 158);
private static final int MAX_DISTANCE = 2400;
private final Client client;
private final DevToolsPlugin plugin;
private final TooltipManager toolTipManager;
@Inject
private DevToolsOverlay(Client client, DevToolsPlugin plugin, TooltipManager toolTipManager)
{
setPosition(OverlayPosition.DYNAMIC);
setLayer(OverlayLayer.ABOVE_WIDGETS);
setPriority(OverlayPriority.HIGHEST);
this.client = client;
this.plugin = plugin;
this.toolTipManager = toolTipManager;
}
@Override
public Dimension render(Graphics2D graphics)
{
graphics.setFont(FONT);
if (plugin.getPlayers().isActive())
{
renderPlayers(graphics);
}
if (plugin.getNpcs().isActive())
{
renderNpcs(graphics);
}
if (plugin.getGroundItems().isActive() || plugin.getGroundObjects().isActive() || plugin.getGameObjects().isActive() || plugin.getWalls().isActive() || plugin.getDecorations().isActive() || plugin.getTileLocation().isActive() || plugin.getMovementFlags().isActive())
{
renderTileObjects(graphics);
}
if (plugin.getInventory().isActive())
{
renderInventory(graphics);
}
if (plugin.getProjectiles().isActive())
{
renderProjectiles(graphics);
}
if (plugin.getGraphicsObjects().isActive())
{
renderGraphicsObjects(graphics);
}
return null;
}
private void renderPlayers(Graphics2D graphics)
{
List<Player> players = client.getPlayers();
Player local = client.getLocalPlayer();
for (Player p : players)
{
if (p != local)
{
String text = p.getName() + " (A: " + p.getAnimation() + ") (P: " + p.getPoseAnimation() + ") (G: " + p.getGraphic() + ")";
OverlayUtil.renderActorOverlay(graphics, p, text, BLUE);
}
}
String text = local.getName() + " (A: " + local.getAnimation() + ") (P: " + local.getPoseAnimation() + ") (G: " + local.getGraphic() + ")";
OverlayUtil.renderActorOverlay(graphics, local, text, CYAN);
renderPlayerWireframe(graphics, local, CYAN);
}
private void renderNpcs(Graphics2D graphics)
{
List<NPC> npcs = client.getNpcs();
for (NPC npc : npcs)
{
NPCComposition composition = npc.getComposition();
Color color = composition.getCombatLevel() > 1 ? YELLOW : ORANGE;
if (composition.getConfigs() != null)
{
NPCComposition transformedComposition = composition.transform();
if (transformedComposition == null)
{
color = GRAY;
}
else
{
composition = transformedComposition;
}
}
String text = composition.getName() + " (ID:" + composition.getId() + ")" +
" (A: " + npc.getAnimation() + ") (P: " + npc.getPoseAnimation() + ") (G: " + npc.getGraphic() + ")";
OverlayUtil.renderActorOverlay(graphics, npc, text, color);
}
}
private void renderTileObjects(Graphics2D graphics)
{
Scene scene = client.getScene();
Tile[][][] tiles = scene.getTiles();
int z = client.getPlane();
for (int x = 0; x < Constants.SCENE_SIZE; ++x)
{
for (int y = 0; y < Constants.SCENE_SIZE; ++y)
{
Tile tile = tiles[z][x][y];
if (tile == null)
{
continue;
}
Player player = client.getLocalPlayer();
if (player == null)
{
continue;
}
if (plugin.getGroundItems().isActive())
{
renderGroundItems(graphics, tile, player);
}
if (plugin.getGroundObjects().isActive())
{
renderGroundObject(graphics, tile, player);
}
if (plugin.getGameObjects().isActive())
{
renderGameObjects(graphics, tile, player);
}
if (plugin.getWalls().isActive())
{
renderWallObject(graphics, tile, player);
}
if (plugin.getDecorations().isActive())
{
renderDecorObject(graphics, tile, player);
}
if (plugin.getTileLocation().isActive())
{
renderTileTooltip(graphics, tile);
}
if (plugin.getMovementFlags().isActive())
{
renderMovementInfo(graphics, tile);
}
}
}
}
private void renderTileTooltip(Graphics2D graphics, Tile tile)
{
Polygon poly = Perspective.getCanvasTilePoly(client, tile.getLocalLocation());
if (poly != null && poly.contains(client.getMouseCanvasPosition().getX(), client.getMouseCanvasPosition().getY()))
{
toolTipManager.add(new Tooltip("World Location: " + tile.getWorldLocation().getX() + ", " + tile.getWorldLocation().getY() + ", " + client.getPlane()));
OverlayUtil.renderPolygon(graphics, poly, GREEN);
}
}
private void renderMovementInfo(Graphics2D graphics, Tile tile)
{
Polygon poly = Perspective.getCanvasTilePoly(client, tile.getLocalLocation());
if (poly == null || !poly.contains(client.getMouseCanvasPosition().getX(), client.getMouseCanvasPosition().getY()))
{
return;
}
if (client.getCollisionMaps() != null)
{
int[][] flags = client.getCollisionMaps()[client.getPlane()].getFlags();
int data = flags[tile.getSceneLocation().getX()][tile.getSceneLocation().getY()];
Set<MovementFlag> movementFlags = MovementFlag.getSetFlags(data);
if (movementFlags.isEmpty())
{
toolTipManager.add(new Tooltip("No movement flags"));
}
else
{
movementFlags.forEach(flag -> toolTipManager.add(new Tooltip(flag.toString())));
}
OverlayUtil.renderPolygon(graphics, poly, GREEN);
}
}
private void renderGroundItems(Graphics2D graphics, Tile tile, Player player)
{
ItemLayer itemLayer = tile.getItemLayer();
if (itemLayer != null)
{
if (player.getLocalLocation().distanceTo(itemLayer.getLocalLocation()) <= MAX_DISTANCE)
{
Node current = itemLayer.getBottom();
while (current instanceof TileItem)
{
TileItem item = (TileItem) current;
OverlayUtil.renderTileOverlay(graphics, itemLayer, "ID: " + item.getId() + " Qty:" + item.getQuantity(), RED);
current = current.getNext();
}
}
}
}
private void renderGameObjects(Graphics2D graphics, Tile tile, Player player)
{
GameObject[] gameObjects = tile.getGameObjects();
if (gameObjects != null)
{
for (GameObject gameObject : gameObjects)
{
if (gameObject != null)
{
if (player.getLocalLocation().distanceTo(gameObject.getLocalLocation()) <= MAX_DISTANCE)
{
OverlayUtil.renderTileOverlay(graphics, gameObject, "ID: " + gameObject.getId(), GREEN);
}
// Draw a polygon around the convex hull
// of the model vertices
Shape p = gameObject.getConvexHull();
if (p != null)
{
graphics.draw(p);
}
}
}
}
}
private void renderGroundObject(Graphics2D graphics, Tile tile, Player player)
{
GroundObject groundObject = tile.getGroundObject();
if (groundObject != null)
{
if (player.getLocalLocation().distanceTo(groundObject.getLocalLocation()) <= MAX_DISTANCE)
{
OverlayUtil.renderTileOverlay(graphics, groundObject, "ID: " + groundObject.getId(), PURPLE);
}
}
}
private void renderWallObject(Graphics2D graphics, Tile tile, Player player)
{
WallObject wallObject = tile.getWallObject();
if (wallObject != null)
{
if (player.getLocalLocation().distanceTo(wallObject.getLocalLocation()) <= MAX_DISTANCE)
{
OverlayUtil.renderTileOverlay(graphics, wallObject, "ID: " + wallObject.getId(), GRAY);
}
}
}
private void renderDecorObject(Graphics2D graphics, Tile tile, Player player)
{
DecorativeObject decorObject = tile.getDecorativeObject();
if (decorObject != null)
{
if (player.getLocalLocation().distanceTo(decorObject.getLocalLocation()) <= MAX_DISTANCE)
{
OverlayUtil.renderTileOverlay(graphics, decorObject, "ID: " + decorObject.getId(), DEEP_PURPLE);
}
Shape p = decorObject.getConvexHull();
if (p != null)
{
graphics.draw(p);
}
p = decorObject.getConvexHull2();
if (p != null)
{
graphics.draw(p);
}
}
}
private void renderInventory(Graphics2D graphics)
{
Widget inventoryWidget = client.getWidget(WidgetInfo.INVENTORY);
if (inventoryWidget == null || inventoryWidget.isHidden())
{
return;
}
for (WidgetItem item : inventoryWidget.getWidgetItems())
{
Rectangle slotBounds = item.getCanvasBounds();
String idText = "" + item.getId();
FontMetrics fm = graphics.getFontMetrics();
Rectangle2D textBounds = fm.getStringBounds(idText, graphics);
int textX = (int) (slotBounds.getX() + (slotBounds.getWidth() / 2) - (textBounds.getWidth() / 2));
int textY = (int) (slotBounds.getY() + (slotBounds.getHeight() / 2) + (textBounds.getHeight() / 2));
graphics.setColor(new Color(255, 255, 255, 65));
graphics.fill(slotBounds);
graphics.setColor(Color.BLACK);
graphics.drawString(idText, textX + 1, textY + 1);
graphics.setColor(YELLOW);
graphics.drawString(idText, textX, textY);
}
}
private void renderProjectiles(Graphics2D graphics)
{
List<Projectile> projectiles = client.getProjectiles();
for (Projectile projectile : projectiles)
{
int projectileId = projectile.getId();
String text = "(ID: " + projectileId + ")";
int x = (int) projectile.getX();
int y = (int) projectile.getY();
LocalPoint projectilePoint = new LocalPoint(x, y);
Point textLocation = Perspective.getCanvasTextLocation(client, graphics, projectilePoint, text, 0);
if (textLocation != null)
{
OverlayUtil.renderTextLocation(graphics, textLocation, text, Color.RED);
}
}
}
private void renderGraphicsObjects(Graphics2D graphics)
{
List<GraphicsObject> graphicsObjects = client.getGraphicsObjects();
for (GraphicsObject graphicsObject : graphicsObjects)
{
LocalPoint lp = graphicsObject.getLocation();
Polygon poly = Perspective.getCanvasTilePoly(client, lp);
if (poly != null)
{
OverlayUtil.renderPolygon(graphics, poly, Color.MAGENTA);
}
String infoString = "(ID: " + graphicsObject.getId() + ")";
Point textLocation = Perspective.getCanvasTextLocation(
client, graphics, lp, infoString, 0);
if (textLocation != null)
{
OverlayUtil.renderTextLocation(graphics, textLocation, infoString, Color.WHITE);
}
}
}
private void renderPlayerWireframe(Graphics2D graphics, Player player, Color color)
{
Polygon[] polys = player.getPolygons();
if (polys == null)
{
return;
}
graphics.setColor(color);
for (Polygon p : polys)
{
graphics.drawPolygon(p);
}
}
}

View File

@@ -0,0 +1,210 @@
/*
* Copyright (c) 2017, Kronos <https://github.com/KronosDesign>
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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.GridLayout;
import java.awt.TrayIcon;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.swing.JButton;
import javax.swing.JPanel;
import net.runelite.api.Client;
import net.runelite.api.MenuAction;
import net.runelite.client.Notifier;
import net.runelite.client.ui.ColorScheme;
import net.runelite.client.ui.PluginPanel;
import net.runelite.client.ui.overlay.OverlayMenuEntry;
import net.runelite.client.ui.overlay.infobox.Counter;
import net.runelite.client.ui.overlay.infobox.InfoBoxManager;
import net.runelite.client.util.ImageUtil;
class DevToolsPanel extends PluginPanel
{
private final Client client;
private final Notifier notifier;
private final DevToolsPlugin plugin;
private final WidgetInspector widgetInspector;
private final VarInspector varInspector;
private final ScriptInspector scriptInspector;
private final InventoryInspector inventoryInspector;
private final InfoBoxManager infoBoxManager;
private final ScheduledExecutorService scheduledExecutorService;
@Inject
private DevToolsPanel(
Client client,
DevToolsPlugin plugin,
WidgetInspector widgetInspector,
VarInspector varInspector,
ScriptInspector scriptInspector,
InventoryInspector inventoryInspector,
Notifier notifier,
InfoBoxManager infoBoxManager,
ScheduledExecutorService scheduledExecutorService)
{
super();
this.client = client;
this.plugin = plugin;
this.widgetInspector = widgetInspector;
this.varInspector = varInspector;
this.inventoryInspector = inventoryInspector;
this.scriptInspector = scriptInspector;
this.notifier = notifier;
this.infoBoxManager = infoBoxManager;
this.scheduledExecutorService = scheduledExecutorService;
setBackground(ColorScheme.DARK_GRAY_COLOR);
add(createOptionsPanel());
}
private JPanel createOptionsPanel()
{
final JPanel container = new JPanel();
container.setBackground(ColorScheme.DARK_GRAY_COLOR);
container.setLayout(new GridLayout(0, 2, 3, 3));
container.add(plugin.getPlayers());
container.add(plugin.getNpcs());
container.add(plugin.getGroundItems());
container.add(plugin.getGroundObjects());
container.add(plugin.getGameObjects());
container.add(plugin.getGraphicsObjects());
container.add(plugin.getWalls());
container.add(plugin.getDecorations());
container.add(plugin.getInventory());
container.add(plugin.getProjectiles());
container.add(plugin.getLocation());
container.add(plugin.getWorldMapLocation());
container.add(plugin.getTileLocation());
container.add(plugin.getCameraPosition());
container.add(plugin.getChunkBorders());
container.add(plugin.getMapSquares());
container.add(plugin.getLineOfSight());
container.add(plugin.getValidMovement());
container.add(plugin.getMovementFlags());
container.add(plugin.getInteracting());
container.add(plugin.getExamine());
container.add(plugin.getDetachedCamera());
plugin.getDetachedCamera().addActionListener((ev) ->
{
client.setOculusOrbState(!plugin.getDetachedCamera().isActive() ? 1 : 0);
client.setOculusOrbNormalSpeed(!plugin.getDetachedCamera().isActive() ? 36 : 12);
});
container.add(plugin.getWidgetInspector());
plugin.getWidgetInspector().addActionListener((ev) ->
{
if (plugin.getWidgetInspector().isActive())
{
widgetInspector.close();
}
else
{
widgetInspector.open();
}
});
container.add(plugin.getVarInspector());
plugin.getVarInspector().addActionListener((ev) ->
{
if (plugin.getVarInspector().isActive())
{
varInspector.close();
}
else
{
varInspector.open();
}
});
container.add(plugin.getSoundEffects());
final JButton notificationBtn = new JButton("Notification");
notificationBtn.addActionListener(e ->
{
scheduledExecutorService.schedule(() -> notifier.notify("Wow!", TrayIcon.MessageType.ERROR), 3, TimeUnit.SECONDS);
});
container.add(notificationBtn);
container.add(plugin.getScriptInspector());
plugin.getScriptInspector().addActionListener((ev) ->
{
if (plugin.getScriptInspector().isActive())
{
scriptInspector.close();
}
else
{
scriptInspector.open();
}
});
final JButton newInfoboxBtn = new JButton("Infobox");
newInfoboxBtn.addActionListener(e ->
{
Counter counter = new Counter(ImageUtil.getResourceStreamFromClass(getClass(), "devtools_icon.png"), plugin, 42)
{
@Override
public String getName()
{
// Give the infobox a unique name to test infobox splitting
return "devtools-" + hashCode();
}
};
counter.getMenuEntries().add(new OverlayMenuEntry(MenuAction.RUNELITE_INFOBOX, "Test", "DevTools"));
infoBoxManager.addInfoBox(counter);
});
container.add(newInfoboxBtn);
final JButton clearInfoboxBtn = new JButton("Clear Infobox");
clearInfoboxBtn.addActionListener(e -> infoBoxManager.removeIf(i -> true));
container.add(clearInfoboxBtn);
container.add(plugin.getInventoryInspector());
plugin.getInventoryInspector().addActionListener((ev) ->
{
if (plugin.getInventoryInspector().isActive())
{
inventoryInspector.close();
}
else
{
inventoryInspector.open();
}
});
return container;
}
}

View File

@@ -0,0 +1,470 @@
/*
* Copyright (c) 2017, Kronos <https://github.com/KronosDesign>
* 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 ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import com.google.common.collect.ImmutableList;
import com.google.common.primitives.Ints;
import com.google.inject.Provides;
import java.awt.image.BufferedImage;
import static java.lang.Math.min;
import java.util.Arrays;
import java.util.List;
import javax.inject.Inject;
import lombok.Getter;
import net.runelite.api.ChatMessageType;
import net.runelite.api.Client;
import net.runelite.api.Experience;
import net.runelite.api.MenuAction;
import net.runelite.api.MenuEntry;
import net.runelite.api.NPC;
import net.runelite.api.Player;
import net.runelite.api.Skill;
import net.runelite.api.VarbitComposition;
import net.runelite.api.coords.WorldPoint;
import net.runelite.api.events.CommandExecuted;
import net.runelite.api.events.MenuEntryAdded;
import net.runelite.api.events.StatChanged;
import net.runelite.api.events.VarbitChanged;
import net.runelite.api.kit.KitType;
import net.runelite.client.chat.ChatMessageBuilder;
import net.runelite.client.chat.ChatMessageManager;
import net.runelite.client.chat.QueuedMessage;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.EventBus;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.ui.ClientToolbar;
import net.runelite.client.ui.JagexColors;
import net.runelite.client.ui.NavigationButton;
import net.runelite.client.ui.overlay.OverlayManager;
import net.runelite.client.util.ColorUtil;
import net.runelite.client.util.ImageUtil;
import org.slf4j.LoggerFactory;
@PluginDescriptor(
name = "Developer Tools",
tags = {"panel"},
developerPlugin = true
)
@Getter
public class DevToolsPlugin extends Plugin
{
private static final List<MenuAction> EXAMINE_MENU_ACTIONS = ImmutableList.of(MenuAction.EXAMINE_ITEM,
MenuAction.EXAMINE_ITEM_GROUND, MenuAction.EXAMINE_NPC, MenuAction.EXAMINE_OBJECT);
@Inject
private Client client;
@Inject
private ClientToolbar clientToolbar;
@Inject
private OverlayManager overlayManager;
@Inject
private DevToolsOverlay overlay;
@Inject
private LocationOverlay locationOverlay;
@Inject
private SceneOverlay sceneOverlay;
@Inject
private CameraOverlay cameraOverlay;
@Inject
private WorldMapLocationOverlay worldMapLocationOverlay;
@Inject
private WorldMapRegionOverlay mapRegionOverlay;
@Inject
private SoundEffectOverlay soundEffectOverlay;
@Inject
private EventBus eventBus;
@Inject
private ConfigManager configManager;
@Inject
private ChatMessageManager chatMessageManager;
private DevToolsButton players;
private DevToolsButton npcs;
private DevToolsButton groundItems;
private DevToolsButton groundObjects;
private DevToolsButton gameObjects;
private DevToolsButton graphicsObjects;
private DevToolsButton walls;
private DevToolsButton decorations;
private DevToolsButton inventory;
private DevToolsButton projectiles;
private DevToolsButton location;
private DevToolsButton chunkBorders;
private DevToolsButton mapSquares;
private DevToolsButton validMovement;
private DevToolsButton movementFlags;
private DevToolsButton lineOfSight;
private DevToolsButton cameraPosition;
private DevToolsButton worldMapLocation;
private DevToolsButton tileLocation;
private DevToolsButton interacting;
private DevToolsButton examine;
private DevToolsButton detachedCamera;
private DevToolsButton widgetInspector;
private DevToolsButton varInspector;
private DevToolsButton soundEffects;
private DevToolsButton scriptInspector;
private DevToolsButton inventoryInspector;
private NavigationButton navButton;
@Provides
DevToolsConfig provideConfig(ConfigManager configManager)
{
return configManager.getConfig(DevToolsConfig.class);
}
@Override
protected void startUp() throws Exception
{
players = new DevToolsButton("Players");
npcs = new DevToolsButton("NPCs");
groundItems = new DevToolsButton("Ground Items");
groundObjects = new DevToolsButton("Ground Objects");
gameObjects = new DevToolsButton("Game Objects");
graphicsObjects = new DevToolsButton("Graphics Objects");
walls = new DevToolsButton("Walls");
decorations = new DevToolsButton("Decorations");
inventory = new DevToolsButton("Inventory");
projectiles = new DevToolsButton("Projectiles");
location = new DevToolsButton("Location");
worldMapLocation = new DevToolsButton("World Map Location");
tileLocation = new DevToolsButton("Tile Location");
cameraPosition = new DevToolsButton("Camera Position");
chunkBorders = new DevToolsButton("Chunk Borders");
mapSquares = new DevToolsButton("Map Squares");
lineOfSight = new DevToolsButton("Line Of Sight");
validMovement = new DevToolsButton("Valid Movement");
movementFlags = new DevToolsButton("Movement Flags");
interacting = new DevToolsButton("Interacting");
examine = new DevToolsButton("Examine");
detachedCamera = new DevToolsButton("Detached Camera");
widgetInspector = new DevToolsButton("Widget Inspector");
varInspector = new DevToolsButton("Var Inspector");
soundEffects = new DevToolsButton("Sound Effects");
scriptInspector = new DevToolsButton("Script Inspector");
inventoryInspector = new DevToolsButton("Inventory Inspector");
overlayManager.add(overlay);
overlayManager.add(locationOverlay);
overlayManager.add(sceneOverlay);
overlayManager.add(cameraOverlay);
overlayManager.add(worldMapLocationOverlay);
overlayManager.add(mapRegionOverlay);
overlayManager.add(soundEffectOverlay);
final DevToolsPanel panel = injector.getInstance(DevToolsPanel.class);
final BufferedImage icon = ImageUtil.getResourceStreamFromClass(getClass(), "devtools_icon.png");
navButton = NavigationButton.builder()
.tooltip("Developer Tools")
.icon(icon)
.priority(1)
.panel(panel)
.build();
clientToolbar.addNavigation(navButton);
eventBus.register(soundEffectOverlay);
}
@Override
protected void shutDown() throws Exception
{
eventBus.unregister(soundEffectOverlay);
overlayManager.remove(overlay);
overlayManager.remove(locationOverlay);
overlayManager.remove(sceneOverlay);
overlayManager.remove(cameraOverlay);
overlayManager.remove(worldMapLocationOverlay);
overlayManager.remove(mapRegionOverlay);
overlayManager.remove(soundEffectOverlay);
clientToolbar.removeNavigation(navButton);
}
@Subscribe
public void onCommandExecuted(CommandExecuted commandExecuted)
{
String[] args = commandExecuted.getArguments();
switch (commandExecuted.getCommand())
{
case "logger":
{
final Logger logger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
String message;
Level currentLoggerLevel = logger.getLevel();
if (args.length < 1)
{
message = "Logger level is currently set to " + currentLoggerLevel;
}
else
{
Level newLoggerLevel = Level.toLevel(args[0], currentLoggerLevel);
logger.setLevel(newLoggerLevel);
message = "Logger level has been set to " + newLoggerLevel;
}
client.addChatMessage(ChatMessageType.GAMEMESSAGE, "", message, null);
break;
}
case "getvarp":
{
int varp = Integer.parseInt(args[0]);
int[] varps = client.getVarps();
int value = varps[varp];
client.addChatMessage(ChatMessageType.GAMEMESSAGE, "", "VarPlayer " + varp + ": " + value, null);
break;
}
case "setvarp":
{
int varp = Integer.parseInt(args[0]);
int value = Integer.parseInt(args[1]);
int[] varps = client.getVarps();
varps[varp] = value;
client.queueChangedVarp(varp);
client.addChatMessage(ChatMessageType.GAMEMESSAGE, "", "Set VarPlayer " + varp + " to " + value, null);
VarbitChanged varbitChanged = new VarbitChanged();
varbitChanged.setIndex(varp);
eventBus.post(varbitChanged); // fake event
break;
}
case "getvarb":
{
int varbit = Integer.parseInt(args[0]);
int value = client.getVarbitValue(client.getVarps(), varbit);
client.addChatMessage(ChatMessageType.GAMEMESSAGE, "", "Varbit " + varbit + ": " + value, null);
break;
}
case "setvarb":
{
int varbit = Integer.parseInt(args[0]);
int value = Integer.parseInt(args[1]);
client.setVarbitValue(client.getVarps(), varbit, value);
VarbitComposition varbitComposition = client.getVarbit(varbit);
client.queueChangedVarp(varbitComposition.getIndex());
client.addChatMessage(ChatMessageType.GAMEMESSAGE, "", "Set varbit " + varbit + " to " + value, null);
eventBus.post(new VarbitChanged()); // fake event
break;
}
case "addxp":
{
Skill skill = Skill.valueOf(args[0].toUpperCase());
int xp = Integer.parseInt(args[1]);
int totalXp = client.getSkillExperience(skill) + xp;
int level = min(Experience.getLevelForXp(totalXp), 99);
client.getBoostedSkillLevels()[skill.ordinal()] = level;
client.getRealSkillLevels()[skill.ordinal()] = level;
client.getSkillExperiences()[skill.ordinal()] = totalXp;
client.queueChangedSkill(skill);
StatChanged statChanged = new StatChanged(
skill,
totalXp,
level,
level
);
eventBus.post(statChanged);
break;
}
case "setstat":
{
Skill skill = Skill.valueOf(args[0].toUpperCase());
int level = Integer.parseInt(args[1]);
level = Ints.constrainToRange(level, 1, Experience.MAX_REAL_LEVEL);
int xp = Experience.getXpForLevel(level);
client.getBoostedSkillLevels()[skill.ordinal()] = level;
client.getRealSkillLevels()[skill.ordinal()] = level;
client.getSkillExperiences()[skill.ordinal()] = xp;
client.queueChangedSkill(skill);
StatChanged statChanged = new StatChanged(
skill,
xp,
level,
level
);
eventBus.post(statChanged);
break;
}
case "anim":
{
int id = Integer.parseInt(args[0]);
Player localPlayer = client.getLocalPlayer();
localPlayer.setAnimation(id);
localPlayer.setActionFrame(0);
break;
}
case "gfx":
{
int id = Integer.parseInt(args[0]);
Player localPlayer = client.getLocalPlayer();
localPlayer.setGraphic(id);
localPlayer.setSpotAnimFrame(0);
break;
}
case "transform":
{
int id = Integer.parseInt(args[0]);
Player player = client.getLocalPlayer();
player.getPlayerComposition().setTransformedNpcId(id);
player.setIdlePoseAnimation(-1);
player.setPoseAnimation(-1);
break;
}
case "cape":
{
int id = Integer.parseInt(args[0]);
Player player = client.getLocalPlayer();
player.getPlayerComposition().getEquipmentIds()[KitType.CAPE.getIndex()] = id + 512;
player.getPlayerComposition().setHash();
break;
}
case "sound":
{
int id = Integer.parseInt(args[0]);
client.playSoundEffect(id);
break;
}
case "msg":
{
client.addChatMessage(ChatMessageType.GAMEMESSAGE, "", String.join(" ", args), "");
break;
}
case "setconf":
{
// setconf group.key name = value
String group = args[0];
String key = args[1], value = "";
for (int i = 2; i < args.length; ++i)
{
if (args[i].equals("="))
{
value = String.join(" ", Arrays.copyOfRange(args, i + 1, args.length));
break;
}
key += " " + args[i];
}
String current = configManager.getConfiguration(group, key);
final String message;
if (value.isEmpty())
{
configManager.unsetConfiguration(group, key);
message = String.format("Unset configuration %s.%s (was: %s)", group, key, current);
}
else
{
configManager.setConfiguration(group, key, value);
message = String.format("Set configuration %s.%s to %s (was: %s)", group, key, value, current);
}
chatMessageManager.queue(QueuedMessage.builder()
.type(ChatMessageType.GAMEMESSAGE)
.runeLiteFormattedMessage(new ChatMessageBuilder().append(message).build())
.build());
break;
}
case "getconf":
{
String group = args[0], key = String.join(" ", Arrays.copyOfRange(args, 1, args.length));
String value = configManager.getConfiguration(group, key);
final String message = String.format("%s.%s = %s", group, key, value);
chatMessageManager.queue(QueuedMessage.builder()
.type(ChatMessageType.GAMEMESSAGE)
.runeLiteFormattedMessage(new ChatMessageBuilder().append(message).build())
.build());
break;
}
}
}
@Subscribe
public void onMenuEntryAdded(MenuEntryAdded event)
{
if (!examine.isActive())
{
return;
}
MenuAction action = MenuAction.of(event.getType());
if (EXAMINE_MENU_ACTIONS.contains(action))
{
MenuEntry[] entries = client.getMenuEntries();
MenuEntry entry = entries[entries.length - 1];
final int identifier = event.getIdentifier();
String info = "ID: ";
if (action == MenuAction.EXAMINE_NPC)
{
NPC npc = client.getCachedNPCs()[identifier];
info += npc.getId();
}
else
{
info += identifier;
if (action == MenuAction.EXAMINE_OBJECT)
{
WorldPoint point = WorldPoint.fromScene(client, entry.getParam0(), entry.getParam1(), client.getPlane());
info += " X: " + point.getX() + " Y: " + point.getY();
}
}
entry.setTarget(entry.getTarget() + " " + ColorUtil.prependColorTag("(" + info + ")", JagexColors.MENU_TARGET));
client.setMenuEntries(entries);
}
}
}

View File

@@ -0,0 +1,224 @@
/*
* 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()
{
@Override
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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,118 @@
/*
* Copyright (c) 2018, Seth <https://github.com/sethtroll>
* 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.Graphics2D;
import javax.inject.Inject;
import net.runelite.api.Client;
import static net.runelite.api.Constants.CHUNK_SIZE;
import net.runelite.api.coords.LocalPoint;
import net.runelite.api.coords.WorldPoint;
import net.runelite.client.ui.overlay.OverlayPanel;
import net.runelite.client.ui.overlay.OverlayPosition;
import net.runelite.client.ui.overlay.components.LineComponent;
public class LocationOverlay extends OverlayPanel
{
private final Client client;
private final DevToolsPlugin plugin;
@Inject
LocationOverlay(Client client, DevToolsPlugin plugin)
{
this.client = client;
this.plugin = plugin;
setPosition(OverlayPosition.TOP_LEFT);
}
@Override
public Dimension render(Graphics2D graphics)
{
if (!plugin.getLocation().isActive())
{
return null;
}
WorldPoint localWorld = client.getLocalPlayer().getWorldLocation();
LocalPoint localPoint = client.getLocalPlayer().getLocalLocation();
int regionID = localWorld.getRegionID();
if (client.isInInstancedRegion())
{
panelComponent.getChildren().add(LineComponent.builder()
.left("Instance")
.build());
int[][][] instanceTemplateChunks = client.getInstanceTemplateChunks();
int z = client.getPlane();
int chunkData = instanceTemplateChunks[z][localPoint.getSceneX() / CHUNK_SIZE][localPoint.getSceneY() / CHUNK_SIZE];
int rotation = chunkData >> 1 & 0x3;
int chunkY = (chunkData >> 3 & 0x7FF) * CHUNK_SIZE;
int chunkX = (chunkData >> 14 & 0x3FF) * CHUNK_SIZE;
panelComponent.getChildren().add(LineComponent.builder()
.left("Chunk " + localPoint.getSceneX() / CHUNK_SIZE + "," + localPoint.getSceneY() / CHUNK_SIZE)
.right(rotation + " " + chunkX + " " + chunkY)
.build());
}
panelComponent.getChildren().add(LineComponent.builder()
.left("Base")
.right(client.getBaseX() + ", " + client.getBaseY())
.build());
panelComponent.getChildren().add(LineComponent.builder()
.left("Local")
.right(localPoint.getX() + ", " + localPoint.getY())
.build());
panelComponent.getChildren().add(LineComponent.builder()
.left("Scene")
.right(localPoint.getSceneX() + ", " + localPoint.getSceneY())
.build());
panelComponent.getChildren().add(LineComponent.builder()
.left("Tile")
.right(localWorld.getX() + ", " + localWorld.getY() + ", " + client.getPlane())
.build());
for (int i = 0; i < client.getMapRegions().length; i++)
{
int region = client.getMapRegions()[i];
panelComponent.getChildren().add(LineComponent.builder()
.left((i == 0) ? "Map regions" : " ")
.right(String.valueOf(region))
.rightColor((region == regionID) ? Color.GREEN : Color.WHITE)
.build());
}
return super.render(graphics);
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright (c) 2020, Pratted <https://github.com/Pratted>
* 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.Getter;
import net.runelite.api.CollisionDataFlag;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
/**
* An enum that binds a name to each movement flag.
*
* @see CollisionDataFlag
*/
@AllArgsConstructor
enum MovementFlag
{
BLOCK_MOVEMENT_NORTH_WEST(CollisionDataFlag.BLOCK_MOVEMENT_NORTH_WEST),
BLOCK_MOVEMENT_NORTH(CollisionDataFlag.BLOCK_MOVEMENT_NORTH),
BLOCK_MOVEMENT_NORTH_EAST(CollisionDataFlag.BLOCK_MOVEMENT_NORTH_EAST),
BLOCK_MOVEMENT_EAST(CollisionDataFlag.BLOCK_MOVEMENT_EAST),
BLOCK_MOVEMENT_SOUTH_EAST(CollisionDataFlag.BLOCK_MOVEMENT_SOUTH_EAST),
BLOCK_MOVEMENT_SOUTH(CollisionDataFlag.BLOCK_MOVEMENT_SOUTH),
BLOCK_MOVEMENT_SOUTH_WEST(CollisionDataFlag.BLOCK_MOVEMENT_SOUTH_WEST),
BLOCK_MOVEMENT_WEST(CollisionDataFlag.BLOCK_MOVEMENT_WEST),
BLOCK_MOVEMENT_OBJECT(CollisionDataFlag.BLOCK_MOVEMENT_OBJECT),
BLOCK_MOVEMENT_FLOOR_DECORATION(CollisionDataFlag.BLOCK_MOVEMENT_FLOOR_DECORATION),
BLOCK_MOVEMENT_FLOOR(CollisionDataFlag.BLOCK_MOVEMENT_FLOOR),
BLOCK_MOVEMENT_FULL(CollisionDataFlag.BLOCK_MOVEMENT_FULL);
@Getter
private int flag;
/**
* @param collisionData The tile collision flags.
* @return The set of {@link MovementFlag}s that have been set.
*/
public static Set<MovementFlag> getSetFlags(int collisionData)
{
return Arrays.stream(values())
.filter(movementFlag -> (movementFlag.flag & collisionData) != 0)
.collect(Collectors.toSet());
}
}

View File

@@ -0,0 +1,406 @@
/*
* Copyright (c) 2018, Woox <https://github.com/wooxsolo>
* 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.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.util.List;
import java.util.stream.Stream;
import javax.inject.Inject;
import net.runelite.api.Actor;
import net.runelite.api.Client;
import net.runelite.api.NPC;
import net.runelite.api.Perspective;
import net.runelite.api.Player;
import net.runelite.api.Point;
import net.runelite.api.coords.LocalPoint;
import net.runelite.api.coords.WorldArea;
import net.runelite.api.coords.WorldPoint;
import net.runelite.client.ui.overlay.Overlay;
import net.runelite.client.ui.overlay.OverlayLayer;
import net.runelite.client.ui.overlay.OverlayPosition;
import net.runelite.client.ui.overlay.OverlayUtil;
public class SceneOverlay extends Overlay
{
private static final Color MAP_SQUARE_COLOR = Color.GREEN;
private static final Color CHUNK_BORDER_COLOR = Color.BLUE;
private static final Color LOCAL_VALID_MOVEMENT_COLOR = new Color(141, 220, 26);
private static final Color VALID_MOVEMENT_COLOR = new Color(73, 122, 18);
private static final Color LINE_OF_SIGHT_COLOR = new Color(204, 42, 219);
private static final Color INTERACTING_COLOR = Color.CYAN;
private static final int LOCAL_TILE_SIZE = Perspective.LOCAL_TILE_SIZE;
private static final int CHUNK_SIZE = 8;
private static final int MAP_SQUARE_SIZE = CHUNK_SIZE * CHUNK_SIZE; // 64
private static final int CULL_CHUNK_BORDERS_RANGE = 16;
private static final int STROKE_WIDTH = 4;
private static final int CULL_LINE_OF_SIGHT_RANGE = 10;
private static final int INTERACTING_SHIFT = -16;
private static final Polygon ARROW_HEAD = new Polygon(
new int[]{0, -3, 3},
new int[]{0, -5, -5},
3
);
private final Client client;
private final DevToolsPlugin plugin;
@Inject
public SceneOverlay(Client client, DevToolsPlugin plugin)
{
setPosition(OverlayPosition.DYNAMIC);
setLayer(OverlayLayer.ABOVE_SCENE);
this.client = client;
this.plugin = plugin;
}
@Override
public Dimension render(Graphics2D graphics)
{
if (plugin.getChunkBorders().isActive())
{
renderChunkBorders(graphics);
}
if (plugin.getMapSquares().isActive())
{
renderMapSquares(graphics);
}
if (plugin.getLineOfSight().isActive())
{
renderLineOfSight(graphics);
}
if (plugin.getValidMovement().isActive())
{
renderValidMovement(graphics);
}
if (plugin.getInteracting().isActive())
{
renderInteracting(graphics);
}
return null;
}
private void renderChunkBorders(Graphics2D graphics)
{
WorldPoint wp = client.getLocalPlayer().getWorldLocation();
int startX = (wp.getX() - CULL_CHUNK_BORDERS_RANGE + CHUNK_SIZE - 1) / CHUNK_SIZE * CHUNK_SIZE;
int startY = (wp.getY() - CULL_CHUNK_BORDERS_RANGE + CHUNK_SIZE - 1) / CHUNK_SIZE * CHUNK_SIZE;
int endX = (wp.getX() + CULL_CHUNK_BORDERS_RANGE) / CHUNK_SIZE * CHUNK_SIZE;
int endY = (wp.getY() + CULL_CHUNK_BORDERS_RANGE) / CHUNK_SIZE * CHUNK_SIZE;
graphics.setStroke(new BasicStroke(STROKE_WIDTH));
graphics.setColor(CHUNK_BORDER_COLOR);
GeneralPath path = new GeneralPath();
for (int x = startX; x <= endX; x += CHUNK_SIZE)
{
LocalPoint lp1 = LocalPoint.fromWorld(client, x, wp.getY() - CULL_CHUNK_BORDERS_RANGE);
LocalPoint lp2 = LocalPoint.fromWorld(client, x, wp.getY() + CULL_CHUNK_BORDERS_RANGE);
boolean first = true;
for (int y = lp1.getY(); y <= lp2.getY(); y += LOCAL_TILE_SIZE)
{
Point p = Perspective.localToCanvas(client,
new LocalPoint(lp1.getX() - LOCAL_TILE_SIZE / 2, y - LOCAL_TILE_SIZE / 2),
client.getPlane());
if (p != null)
{
if (first)
{
path.moveTo(p.getX(), p.getY());
first = false;
}
else
{
path.lineTo(p.getX(), p.getY());
}
}
}
}
for (int y = startY; y <= endY; y += CHUNK_SIZE)
{
LocalPoint lp1 = LocalPoint.fromWorld(client, wp.getX() - CULL_CHUNK_BORDERS_RANGE, y);
LocalPoint lp2 = LocalPoint.fromWorld(client, wp.getX() + CULL_CHUNK_BORDERS_RANGE, y);
boolean first = true;
for (int x = lp1.getX(); x <= lp2.getX(); x += LOCAL_TILE_SIZE)
{
Point p = Perspective.localToCanvas(client,
new LocalPoint(x - LOCAL_TILE_SIZE / 2, lp1.getY() - LOCAL_TILE_SIZE / 2),
client.getPlane());
if (p != null)
{
if (first)
{
path.moveTo(p.getX(), p.getY());
first = false;
}
else
{
path.lineTo(p.getX(), p.getY());
}
}
}
}
graphics.draw(path);
}
private void renderMapSquares(Graphics2D graphics)
{
WorldPoint wp = client.getLocalPlayer().getWorldLocation();
int startX = (wp.getX() - CULL_CHUNK_BORDERS_RANGE + MAP_SQUARE_SIZE - 1) / MAP_SQUARE_SIZE * MAP_SQUARE_SIZE;
int startY = (wp.getY() - CULL_CHUNK_BORDERS_RANGE + MAP_SQUARE_SIZE - 1) / MAP_SQUARE_SIZE * MAP_SQUARE_SIZE;
int endX = (wp.getX() + CULL_CHUNK_BORDERS_RANGE) / MAP_SQUARE_SIZE * MAP_SQUARE_SIZE;
int endY = (wp.getY() + CULL_CHUNK_BORDERS_RANGE) / MAP_SQUARE_SIZE * MAP_SQUARE_SIZE;
graphics.setStroke(new BasicStroke(STROKE_WIDTH));
graphics.setColor(MAP_SQUARE_COLOR);
GeneralPath path = new GeneralPath();
for (int x = startX; x <= endX; x += MAP_SQUARE_SIZE)
{
LocalPoint lp1 = LocalPoint.fromWorld(client, x, wp.getY() - CULL_CHUNK_BORDERS_RANGE);
LocalPoint lp2 = LocalPoint.fromWorld(client, x, wp.getY() + CULL_CHUNK_BORDERS_RANGE);
boolean first = true;
for (int y = lp1.getY(); y <= lp2.getY(); y += LOCAL_TILE_SIZE)
{
Point p = Perspective.localToCanvas(client,
new LocalPoint(lp1.getX() - LOCAL_TILE_SIZE / 2, y - LOCAL_TILE_SIZE / 2),
client.getPlane());
if (p != null)
{
if (first)
{
path.moveTo(p.getX(), p.getY());
first = false;
}
else
{
path.lineTo(p.getX(), p.getY());
}
}
}
}
for (int y = startY; y <= endY; y += MAP_SQUARE_SIZE)
{
LocalPoint lp1 = LocalPoint.fromWorld(client, wp.getX() - CULL_CHUNK_BORDERS_RANGE, y);
LocalPoint lp2 = LocalPoint.fromWorld(client, wp.getX() + CULL_CHUNK_BORDERS_RANGE, y);
boolean first = true;
for (int x = lp1.getX(); x <= lp2.getX(); x += LOCAL_TILE_SIZE)
{
Point p = Perspective.localToCanvas(client,
new LocalPoint(x - LOCAL_TILE_SIZE / 2, lp1.getY() - LOCAL_TILE_SIZE / 2),
client.getPlane());
if (p != null)
{
if (first)
{
path.moveTo(p.getX(), p.getY());
first = false;
}
else
{
path.lineTo(p.getX(), p.getY());
}
}
}
}
graphics.draw(path);
}
private void renderTileIfValidForMovement(Graphics2D graphics, Actor actor, int dx, int dy)
{
WorldArea area = actor.getWorldArea();
if (area == null)
{
return;
}
if (area.canTravelInDirection(client, dx, dy))
{
LocalPoint lp = actor.getLocalLocation();
if (lp == null)
{
return;
}
lp = new LocalPoint(
lp.getX() + dx * Perspective.LOCAL_TILE_SIZE + dx * Perspective.LOCAL_TILE_SIZE * (area.getWidth() - 1) / 2,
lp.getY() + dy * Perspective.LOCAL_TILE_SIZE + dy * Perspective.LOCAL_TILE_SIZE * (area.getHeight() - 1) / 2);
Polygon poly = Perspective.getCanvasTilePoly(client, lp);
if (poly == null)
{
return;
}
if (actor == client.getLocalPlayer())
{
OverlayUtil.renderPolygon(graphics, poly, LOCAL_VALID_MOVEMENT_COLOR);
}
else
{
OverlayUtil.renderPolygon(graphics, poly, VALID_MOVEMENT_COLOR);
}
}
}
private void renderValidMovement(Graphics2D graphics)
{
Player player = client.getLocalPlayer();
List<NPC> npcs = client.getNpcs();
for (NPC npc : npcs)
{
if (player.getInteracting() != npc && npc.getInteracting() != player)
{
continue;
}
for (int dx = -1; dx <= 1; dx++)
{
for (int dy = -1; dy <= 1; dy++)
{
if (dx == 0 && dy == 0)
{
continue;
}
renderTileIfValidForMovement(graphics, npc, dx, dy);
}
}
}
for (int dx = -1; dx <= 1; dx++)
{
for (int dy = -1; dy <= 1; dy++)
{
if (dx == 0 && dy == 0)
{
continue;
}
renderTileIfValidForMovement(graphics, player, dx, dy);
}
}
}
private void renderTileIfHasLineOfSight(Graphics2D graphics, WorldArea start, int targetX, int targetY)
{
WorldPoint targetLocation = new WorldPoint(targetX, targetY, start.getPlane());
// Running the line of sight algorithm 100 times per frame doesn't
// seem to use much CPU time, however rendering 100 tiles does
if (start.hasLineOfSightTo(client, targetLocation))
{
LocalPoint lp = LocalPoint.fromWorld(client, targetLocation);
if (lp == null)
{
return;
}
Polygon poly = Perspective.getCanvasTilePoly(client, lp);
if (poly == null)
{
return;
}
OverlayUtil.renderPolygon(graphics, poly, LINE_OF_SIGHT_COLOR);
}
}
private void renderLineOfSight(Graphics2D graphics)
{
WorldArea area = client.getLocalPlayer().getWorldArea();
for (int x = area.getX() - CULL_LINE_OF_SIGHT_RANGE; x <= area.getX() + CULL_LINE_OF_SIGHT_RANGE; x++)
{
for (int y = area.getY() - CULL_LINE_OF_SIGHT_RANGE; y <= area.getY() + CULL_LINE_OF_SIGHT_RANGE; y++)
{
if (x == area.getX() && y == area.getY())
{
continue;
}
renderTileIfHasLineOfSight(graphics, area, x, y);
}
}
}
private void renderInteracting(Graphics2D graphics)
{
Stream.concat(
client.getPlayers().stream(),
client.getNpcs().stream()
).forEach(fa ->
{
Actor ta = fa.getInteracting();
if (ta == null)
{
return;
}
LocalPoint fl = fa.getLocalLocation();
Point fs = Perspective.localToCanvas(client, fl, client.getPlane(), fa.getLogicalHeight() / 2);
if (fs == null)
{
return;
}
int fsx = fs.getX();
int fsy = fs.getY() - INTERACTING_SHIFT;
LocalPoint tl = ta.getLocalLocation();
Point ts = Perspective.localToCanvas(client, tl, client.getPlane(), ta.getLogicalHeight() / 2);
if (ts == null)
{
return;
}
int tsx = ts.getX();
int tsy = ts.getY() - INTERACTING_SHIFT;
graphics.setColor(INTERACTING_COLOR);
graphics.drawLine(fsx, fsy, tsx, tsy);
AffineTransform t = new AffineTransform();
t.translate(tsx, tsy);
t.rotate(tsx - fsx, tsy - fsy);
t.rotate(Math.PI / -2);
AffineTransform ot = graphics.getTransform();
graphics.setTransform(t);
graphics.fill(ARROW_HEAD);
graphics.setTransform(ot);
});
}
}

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 final JList jList;
private final 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
while (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;
}
}

View File

@@ -0,0 +1,144 @@
/*
* Copyright (c) 2018, WooxSolo <https://github.com/WooxSolo>
* 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.Graphics2D;
import javax.inject.Inject;
import net.runelite.api.Client;
import net.runelite.api.Player;
import net.runelite.api.coords.LocalPoint;
import net.runelite.api.events.AreaSoundEffectPlayed;
import net.runelite.api.events.SoundEffectPlayed;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.ui.overlay.OverlayPanel;
import net.runelite.client.ui.overlay.OverlayPosition;
import net.runelite.client.ui.overlay.components.LineComponent;
class SoundEffectOverlay extends OverlayPanel
{
private final static int MAX_LINES = 16;
private final static Color COLOR_SOUND_EFFECT = Color.WHITE;
private final static Color COLOR_AREA_SOUND_EFFECT = Color.YELLOW;
private final static Color COLOR_SILENT_SOUND_EFFECT = Color.GRAY;
private final Client client;
private final DevToolsPlugin plugin;
@Inject
SoundEffectOverlay(Client client, DevToolsPlugin plugin)
{
this.client = client;
this.plugin = plugin;
panelComponent.getChildren().add(LineComponent.builder()
.left("Sound Effects")
.leftColor(Color.CYAN)
.build());
setClearChildren(false);
setPosition(OverlayPosition.TOP_LEFT);
}
@Override
public Dimension render(Graphics2D graphics)
{
if (!plugin.getSoundEffects().isActive())
{
return null;
}
return super.render(graphics);
}
@Subscribe
public void onSoundEffectPlayed(SoundEffectPlayed event)
{
if (!plugin.getSoundEffects().isActive())
{
return;
}
String text =
"Id: " + event.getSoundId() +
" - D: " + event.getDelay();
panelComponent.getChildren().add(LineComponent.builder()
.left(text)
.leftColor(COLOR_SOUND_EFFECT)
.build());
checkMaxLines();
}
@Subscribe
public void onAreaSoundEffectPlayed(AreaSoundEffectPlayed event)
{
if (!plugin.getSoundEffects().isActive())
{
return;
}
Color textColor = COLOR_AREA_SOUND_EFFECT;
// Check if the player is within range to hear the sound
Player localPlayer = client.getLocalPlayer();
if (localPlayer != null)
{
LocalPoint lp = localPlayer.getLocalLocation();
if (lp != null)
{
int sceneX = lp.getSceneX();
int sceneY = lp.getSceneY();
int distance = Math.abs(sceneX - event.getSceneX()) + Math.abs(sceneY - event.getSceneY());
if (distance > event.getRange())
{
textColor = COLOR_SILENT_SOUND_EFFECT;
}
}
}
String text =
"Id: " + event.getSoundId() +
" - S: " + (event.getSource() != null ? event.getSource().getName() : "<none>") +
" - L: " + event.getSceneX() + "," + event.getSceneY() +
" - R: " + event.getRange() +
" - D: " + event.getDelay();
panelComponent.getChildren().add(LineComponent.builder()
.left(text)
.leftColor(textColor)
.build());
checkMaxLines();
}
private void checkMaxLines()
{
while (panelComponent.getChildren().size() > MAX_LINES)
{
panelComponent.getChildren().remove(1);
}
}
}

View File

@@ -0,0 +1,377 @@
/*
* Copyright (c) 2018 Abex
* 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.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.inject.Inject;
import java.awt.BorderLayout;
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.HashMap;
import java.util.Map;
import java.util.Objects;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.border.CompoundBorder;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.IndexDataBase;
import net.runelite.api.VarClientInt;
import net.runelite.api.VarClientStr;
import net.runelite.api.VarPlayer;
import net.runelite.api.VarbitComposition;
import net.runelite.api.Varbits;
import net.runelite.api.events.VarClientIntChanged;
import net.runelite.api.events.VarClientStrChanged;
import net.runelite.api.events.VarbitChanged;
import net.runelite.client.callback.ClientThread;
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;
@Slf4j
class VarInspector extends JFrame
{
@Getter
private enum VarType
{
VARBIT("Varbit"),
VARP("VarPlayer"),
VARCINT("VarClientInt"),
VARCSTR("VarClientStr");
private final String name;
private final JCheckBox checkBox;
VarType(String name)
{
this.name = name;
checkBox = new JCheckBox(name, true);
}
}
private final static int MAX_LOG_ENTRIES = 10_000;
private static final int VARBITS_ARCHIVE_ID = 14;
private final Client client;
private final ClientThread clientThread;
private final EventBus eventBus;
private final JPanel tracker = new JPanel();
private int lastTick = 0;
private int[] oldVarps = null;
private int[] oldVarps2 = null;
private Multimap<Integer, Integer> varbits;
private Map<Integer, Object> varcs = null;
@Inject
VarInspector(Client client, ClientThread clientThread, EventBus eventBus, DevToolsPlugin plugin)
{
this.client = client;
this.clientThread = clientThread;
this.eventBus = eventBus;
setTitle("RuneLite Var Inspector");
setIconImage(ClientUI.ICON);
setLayout(new BorderLayout());
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
addWindowListener(new WindowAdapter()
{
@Override
public void windowClosing(WindowEvent e)
{
close();
plugin.getVarInspector().setActive(false);
}
});
tracker.setLayout(new DynamicGridLayout(0, 1, 0, 3));
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();
}
});
add(trackerScroller, BorderLayout.CENTER);
final JPanel trackerOpts = new JPanel();
trackerOpts.setLayout(new FlowLayout());
for (VarType cb : VarType.values())
{
trackerOpts.add(cb.getCheckBox());
}
final JButton clearBtn = new JButton("Clear");
clearBtn.addActionListener(e ->
{
tracker.removeAll();
tracker.revalidate();
});
trackerOpts.add(clearBtn);
add(trackerOpts, BorderLayout.SOUTH);
pack();
}
private void addVarLog(VarType type, String name, int old, int neew)
{
addVarLog(type, name, Integer.toString(old), Integer.toString(neew));
}
private void addVarLog(VarType type, String name, String old, String neew)
{
if (!type.getCheckBox().isSelected())
{
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);
}
tracker.add(new JLabel(String.format("%s %s changed: %s -> %s", type.getName(), name, old, neew)));
// Cull very old stuff
while (tracker.getComponentCount() > MAX_LOG_ENTRIES)
{
tracker.remove(0);
}
tracker.revalidate();
});
}
@Subscribe
public void onVarbitChanged(VarbitChanged varbitChanged)
{
int index = varbitChanged.getIndex();
int[] varps = client.getVarps();
// Check varbits
for (int i : varbits.get(index))
{
int old = client.getVarbitValue(oldVarps, i);
int neew = client.getVarbitValue(varps, i);
if (old != neew)
{
// Set the varbit so it doesn't show in the varp changes
// However, some varbits share common bits, so we only do it in oldVarps2
// Example: 4101 collides with 4104-4129
client.setVarbitValue(oldVarps2, i, neew);
String name = Integer.toString(i);
for (Varbits varbit : Varbits.values())
{
if (varbit.getId() == i)
{
name = String.format("%s(%d)", varbit.name(), i);
break;
}
}
addVarLog(VarType.VARBIT, name, old, neew);
}
}
// Check varps
int old = oldVarps2[index];
int neew = varps[index];
if (old != neew)
{
String name = Integer.toString(index);
for (VarPlayer varp : VarPlayer.values())
{
if (varp.getId() == index)
{
name = String.format("%s(%d)", varp.name(), index);
break;
}
}
addVarLog(VarType.VARP, name, old, neew);
}
System.arraycopy(client.getVarps(), 0, oldVarps, 0, oldVarps.length);
System.arraycopy(client.getVarps(), 0, oldVarps2, 0, oldVarps2.length);
}
@Subscribe
public void onVarClientIntChanged(VarClientIntChanged e)
{
int idx = e.getIndex();
int neew = (Integer) client.getVarcMap().getOrDefault(idx, 0);
int old = (Integer) varcs.getOrDefault(idx, 0);
varcs.put(idx, neew);
if (old != neew)
{
String name = String.format("%d", idx);
for (VarClientInt varc : VarClientInt.values())
{
if (varc.getIndex() == idx)
{
name = String.format("%s(%d)", varc.name(), idx);
break;
}
}
addVarLog(VarType.VARCINT, name, old, neew);
}
}
@Subscribe
public void onVarClientStrChanged(VarClientStrChanged e)
{
int idx = e.getIndex();
String neew = (String) client.getVarcMap().getOrDefault(idx, "");
String old = (String) varcs.getOrDefault(idx, "");
varcs.put(idx, neew);
if (!Objects.equals(old, neew))
{
String name = String.format("%d", idx);
for (VarClientStr varc : VarClientStr.values())
{
if (varc.getIndex() == idx)
{
name = String.format("%s(%d)", varc.name(), idx);
break;
}
}
if (old != null)
{
old = "\"" + old + "\"";
}
else
{
old = "null";
}
if (neew != null)
{
neew = "\"" + neew + "\"";
}
else
{
neew = "null";
}
addVarLog(VarType.VARCSTR, name, old, neew);
}
}
public void open()
{
if (oldVarps == null)
{
oldVarps = new int[client.getVarps().length];
oldVarps2 = new int[client.getVarps().length];
}
System.arraycopy(client.getVarps(), 0, oldVarps, 0, oldVarps.length);
System.arraycopy(client.getVarps(), 0, oldVarps2, 0, oldVarps2.length);
varcs = new HashMap<>(client.getVarcMap());
varbits = HashMultimap.create();
clientThread.invoke(() ->
{
// Build varp index -> varbit id map
IndexDataBase indexVarbits = client.getIndexConfig();
final int[] varbitIds = indexVarbits.getFileIds(VARBITS_ARCHIVE_ID);
for (int id : varbitIds)
{
VarbitComposition varbit = client.getVarbit(id);
if (varbit != null)
{
varbits.put(varbit.getIndex(), id);
}
}
});
eventBus.register(this);
setVisible(true);
toFront();
repaint();
}
public void close()
{
tracker.removeAll();
eventBus.unregister(this);
setVisible(false);
varcs = null;
varbits = null;
}
}

View File

@@ -0,0 +1,104 @@
/*
* Copyright (c) 2018 Abex
* 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.util.function.BiConsumer;
import java.util.function.Function;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.widgets.Widget;
import org.slf4j.helpers.MessageFormatter;
@Slf4j
public class WidgetField<T>
{
@Getter
private final String name;
private final Function<Widget, T> getter;
private final BiConsumer<Widget, T> setter;
private final Class<T> type;
WidgetField(String name, Function<Widget, T> getter)
{
this(name, getter, null, null);
}
WidgetField(String name, Function<Widget, T> getter, BiConsumer<Widget, T> setter, Class<T> type)
{
this.name = name;
this.getter = getter;
this.setter = setter;
this.type = type;
}
Object getValue(Widget widget)
{
Object value = getter.apply(widget);
// These types are handled by the JTable automatically
if (value instanceof Boolean || value instanceof Number || value instanceof String)
{
return value;
}
if (value instanceof Widget)
{
return WidgetInspector.getWidgetIdentifier((Widget) value);
}
return MessageFormatter.format("{}", value).getMessage();
}
void setValue(Widget widget, Object inValue)
{
Object value = null;
if ("null".equals(inValue))
{
value = null;
}
if (type.isAssignableFrom(inValue.getClass()))
{
value = inValue;
}
else if (type == Boolean.class)
{
value = Boolean.valueOf((String) inValue);
}
else if (type == Integer.class)
{
value = Integer.valueOf((String) inValue);
}
else
{
log.warn("Type {} is not supported for editing", type);
}
setter.accept(widget, (T) value);
}
boolean isSettable()
{
return setter != null;
}
}

View File

@@ -0,0 +1,200 @@
/*
* Copyright (c) 2018 Abex
* 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.ImmutableMap;
import com.google.inject.Inject;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
import net.runelite.api.widgets.Widget;
import net.runelite.client.callback.ClientThread;
public class WidgetInfoTableModel extends AbstractTableModel
{
@Inject
private ClientThread clientThread;
private static final int COL_FIELD = 0;
private static final int COL_VALUE = 1;
private static final List<WidgetField> fields = populateWidgetFields();
private Widget widget = null;
private Map<WidgetField, Object> values = null;
public void setWidget(Widget w)
{
clientThread.invoke(() ->
{
Map<WidgetField, Object> newValues = w == null ? null : fields.stream().collect(ImmutableMap.toImmutableMap(
Function.identity(),
i -> i.getValue(w)
));
SwingUtilities.invokeLater(() ->
{
widget = w;
values = newValues;
fireTableStructureChanged();
});
});
}
@Override
public String getColumnName(int col)
{
switch (col)
{
case COL_FIELD:
return "Field";
case COL_VALUE:
return "Value";
default:
return null;
}
}
@Override
public int getColumnCount()
{
return 2;
}
@Override
public int getRowCount()
{
if (values == null)
{
return 0;
}
return values.size();
}
@Override
public Object getValueAt(int rowIndex, int columnIndex)
{
WidgetField<?> field = fields.get(rowIndex);
switch (columnIndex)
{
case COL_FIELD:
return field.getName();
case COL_VALUE:
return values.get(field);
default:
return null;
}
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex)
{
if (columnIndex == COL_VALUE)
{
WidgetField<?> field = fields.get(rowIndex);
return field.isSettable();
}
return false;
}
@Override
public void setValueAt(Object value, int rowIndex, int columnIndex)
{
WidgetField<?> field = fields.get(rowIndex);
clientThread.invoke(() ->
{
field.setValue(widget, value);
setWidget(widget);
});
}
private static List<WidgetField> populateWidgetFields()
{
List<WidgetField> out = new ArrayList<>();
out.add(new WidgetField<>("Id", Widget::getId));
out.add(new WidgetField<>("Type", Widget::getType, Widget::setType, Integer.class));
out.add(new WidgetField<>("ContentType", Widget::getContentType, Widget::setContentType, Integer.class));
out.add(new WidgetField<>("ParentId", Widget::getParentId));
out.add(new WidgetField<>("SelfHidden", Widget::isSelfHidden, Widget::setHidden, Boolean.class));
out.add(new WidgetField<>("Hidden", Widget::isHidden));
out.add(new WidgetField<>("Text", Widget::getText, Widget::setText, String.class));
out.add(new WidgetField<>("TextColor",
w -> Integer.toString(w.getTextColor(), 16),
(w, str) -> w.setTextColor(Integer.parseInt(str, 16)),
String.class
));
out.add(new WidgetField<>("Opacity", Widget::getOpacity, Widget::setOpacity, Integer.class));
out.add(new WidgetField<>("FontId", Widget::getFontId, Widget::setFontId, Integer.class));
out.add(new WidgetField<>("TextShadowed", Widget::getTextShadowed, Widget::setTextShadowed, Boolean.class));
out.add(new WidgetField<>("Name", w -> w.getName().trim(), Widget::setName, String.class));
out.add(new WidgetField<>("ItemId", Widget::getItemId, Widget::setItemId, Integer.class));
out.add(new WidgetField<>("ItemQuantity", Widget::getItemQuantity, Widget::setItemQuantity, Integer.class));
out.add(new WidgetField<>("ItemQuantityMode", Widget::getItemQuantityMode, Widget::setItemQuantityMode, Integer.class));
out.add(new WidgetField<>("ModelId", Widget::getModelId, Widget::setModelId, Integer.class));
out.add(new WidgetField<>("ModelType", Widget::getModelType, Widget::setModelType, Integer.class));
out.add(new WidgetField<>("AnimationId", Widget::getAnimationId, Widget::setAnimationId, Integer.class));
out.add(new WidgetField<>("RotationX", Widget::getRotationX, Widget::setRotationX, Integer.class));
out.add(new WidgetField<>("RotationY", Widget::getRotationY, Widget::setRotationY, Integer.class));
out.add(new WidgetField<>("RotationZ", Widget::getRotationZ, Widget::setRotationZ, Integer.class));
out.add(new WidgetField<>("ModelZoom", Widget::getModelZoom, Widget::setModelZoom, Integer.class));
out.add(new WidgetField<>("SpriteId", Widget::getSpriteId, Widget::setSpriteId, Integer.class));
out.add(new WidgetField<>("SpriteTiling", Widget::getSpriteTiling, Widget::setSpriteTiling, Boolean.class));
out.add(new WidgetField<>("BorderType", Widget::getBorderType, Widget::setBorderType, Integer.class));
out.add(new WidgetField<>("IsIf3", Widget::isIf3));
out.add(new WidgetField<>("HasListener", Widget::hasListener, Widget::setHasListener, Boolean.class));
out.add(new WidgetField<>("Filled", Widget::isFilled, Widget::setFilled, Boolean.class));
out.add(new WidgetField<>("OriginalX", Widget::getOriginalX, Widget::setOriginalX, Integer.class));
out.add(new WidgetField<>("OriginalY", Widget::getOriginalY, Widget::setOriginalY, Integer.class));
out.add(new WidgetField<>("OriginalWidth", Widget::getOriginalWidth, Widget::setOriginalWidth, Integer.class));
out.add(new WidgetField<>("OriginalHeight", Widget::getOriginalHeight, Widget::setOriginalHeight, Integer.class));
out.add(new WidgetField<>("XPositionMode", Widget::getXPositionMode, Widget::setXPositionMode, Integer.class));
out.add(new WidgetField<>("YPositionMode", Widget::getYPositionMode, Widget::setYPositionMode, Integer.class));
out.add(new WidgetField<>("WidthMode", Widget::getWidthMode, Widget::setWidthMode, Integer.class));
out.add(new WidgetField<>("HeightMode", Widget::getHeightMode, Widget::setHeightMode, Integer.class));
out.add(new WidgetField<>("XTextAlignment", Widget::getXTextAlignment, Widget::setXTextAlignment, Integer.class));
out.add(new WidgetField<>("YTextAlignment", Widget::getYTextAlignment, Widget::setYTextAlignment, Integer.class));
out.add(new WidgetField<>("RelativeX", Widget::getRelativeX, Widget::setRelativeX, Integer.class));
out.add(new WidgetField<>("RelativeY", Widget::getRelativeY, Widget::setRelativeY, Integer.class));
out.add(new WidgetField<>("Width", Widget::getWidth, Widget::setWidth, Integer.class));
out.add(new WidgetField<>("Height", Widget::getHeight, Widget::setHeight, Integer.class));
out.add(new WidgetField<>("CanvasLocation", Widget::getCanvasLocation));
out.add(new WidgetField<>("Bounds", Widget::getBounds));
out.add(new WidgetField<>("ScrollX", Widget::getScrollX, Widget::setScrollX, Integer.class));
out.add(new WidgetField<>("ScrollY", Widget::getScrollY, Widget::setScrollY, Integer.class));
out.add(new WidgetField<>("ScrollWidth", Widget::getScrollWidth, Widget::setScrollWidth, Integer.class));
out.add(new WidgetField<>("ScrollHeight", Widget::getScrollHeight, Widget::setScrollHeight, Integer.class));
out.add(new WidgetField<>("DragDeadZone", Widget::getDragDeadZone, Widget::setDragDeadZone, Integer.class));
out.add(new WidgetField<>("DragDeadTime", Widget::getDragDeadTime, Widget::setDragDeadTime, Integer.class));
out.add(new WidgetField<>("NoClickThrough", Widget::getNoClickThrough, Widget::setNoClickThrough, Boolean.class));
out.add(new WidgetField<>("NoScrollThrough", Widget::getNoScrollThrough, Widget::setNoScrollThrough, Boolean.class));
out.add(new WidgetField<>("TargetVerb", Widget::getTargetVerb, Widget::setTargetVerb, String.class));
out.add(new WidgetField<>("DragParent", Widget::getDragParent));
return out;
}
}

View File

@@ -0,0 +1,602 @@
/*
* Copyright (c) 2018 Abex
* Copyright (c) 2017, Kronos <https://github.com/KronosDesign>
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Collection;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
import java.util.stream.Stream;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.MenuAction;
import net.runelite.api.MenuEntry;
import net.runelite.api.SpriteID;
import static net.runelite.api.widgets.WidgetInfo.TO_CHILD;
import static net.runelite.api.widgets.WidgetInfo.TO_GROUP;
import net.runelite.client.events.ConfigChanged;
import net.runelite.api.events.MenuEntryAdded;
import net.runelite.api.events.MenuOptionClicked;
import net.runelite.api.widgets.JavaScriptCallback;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetConfig;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.api.widgets.WidgetItem;
import net.runelite.api.widgets.WidgetType;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.eventbus.EventBus;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.ui.ClientUI;
import net.runelite.client.ui.overlay.OverlayManager;
import net.runelite.client.util.ColorUtil;
@Slf4j
@Singleton
class WidgetInspector extends JFrame
{
private static final Map<Integer, WidgetInfo> widgetIdMap = new HashMap<>();
static final Color SELECTED_WIDGET_COLOR = Color.CYAN;
private static final float SELECTED_WIDGET_HUE;
static
{
float[] hsb = new float[3];
Color.RGBtoHSB(SELECTED_WIDGET_COLOR.getRed(), SELECTED_WIDGET_COLOR.getGreen(), SELECTED_WIDGET_COLOR.getBlue(), hsb);
SELECTED_WIDGET_HUE = hsb[0];
}
private final Client client;
private final ClientThread clientThread;
private final DevToolsConfig config;
private final Provider<WidgetInspectorOverlay> overlay;
private final OverlayManager overlayManager;
private final JTree widgetTree;
private final WidgetInfoTableModel infoTableModel;
private final JCheckBox alwaysOnTop;
private final JCheckBox hideHidden;
private DefaultMutableTreeNode root;
@Getter
private Widget selectedWidget;
@Getter
private int selectedItem;
private Widget picker = null;
@Getter
private boolean pickerSelected = false;
@Inject
private WidgetInspector(
Client client,
ClientThread clientThread,
WidgetInfoTableModel infoTableModel,
DevToolsConfig config,
DevToolsPlugin plugin,
EventBus eventBus,
Provider<WidgetInspectorOverlay> overlay,
OverlayManager overlayManager)
{
this.client = client;
this.clientThread = clientThread;
this.infoTableModel = infoTableModel;
this.config = config;
this.overlay = overlay;
this.overlayManager = overlayManager;
eventBus.register(this);
setTitle("RuneLite Widget Inspector");
setIconImage(ClientUI.ICON);
// Reset highlight on close
addWindowListener(new WindowAdapter()
{
@Override
public void windowClosing(WindowEvent e)
{
close();
plugin.getWidgetInspector().setActive(false);
}
});
setLayout(new BorderLayout());
widgetTree = new JTree(new DefaultMutableTreeNode());
widgetTree.setRootVisible(false);
widgetTree.setShowsRootHandles(true);
widgetTree.getSelectionModel().addTreeSelectionListener(e ->
{
Object selected = widgetTree.getLastSelectedPathComponent();
if (selected instanceof WidgetTreeNode)
{
WidgetTreeNode node = (WidgetTreeNode) selected;
Widget widget = node.getWidget();
setSelectedWidget(widget, -1, false);
}
else if (selected instanceof WidgetItemNode)
{
WidgetItemNode node = (WidgetItemNode) selected;
setSelectedWidget(node.getWidgetItem().getWidget(), node.getWidgetItem().getIndex(), false);
}
});
final JScrollPane treeScrollPane = new JScrollPane(widgetTree);
treeScrollPane.setPreferredSize(new Dimension(200, 400));
final JTable widgetInfo = new JTable(infoTableModel);
final JScrollPane infoScrollPane = new JScrollPane(widgetInfo);
infoScrollPane.setPreferredSize(new Dimension(400, 400));
final JPanel bottomPanel = new JPanel();
add(bottomPanel, BorderLayout.SOUTH);
final JButton refreshWidgetsBtn = new JButton("Refresh");
refreshWidgetsBtn.addActionListener(e -> refreshWidgets());
bottomPanel.add(refreshWidgetsBtn);
alwaysOnTop = new JCheckBox("Always on top");
alwaysOnTop.addItemListener(ev -> config.inspectorAlwaysOnTop(alwaysOnTop.isSelected()));
onConfigChanged(null);
bottomPanel.add(alwaysOnTop);
hideHidden = new JCheckBox("Hide hidden");
hideHidden.setSelected(true);
hideHidden.addItemListener(ev -> refreshWidgets());
bottomPanel.add(hideHidden);
final JButton revalidateWidget = new JButton("Revalidate");
revalidateWidget.addActionListener(ev -> clientThread.invokeLater(() ->
{
if (selectedWidget == null)
{
return;
}
selectedWidget.revalidate();
}));
bottomPanel.add(revalidateWidget);
final JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, treeScrollPane, infoScrollPane);
add(split, BorderLayout.CENTER);
pack();
}
@Subscribe
private void onConfigChanged(ConfigChanged ev)
{
boolean onTop = config.inspectorAlwaysOnTop();
setAlwaysOnTop(onTop);
alwaysOnTop.setSelected(onTop);
}
private void refreshWidgets()
{
clientThread.invokeLater(() ->
{
Widget[] rootWidgets = client.getWidgetRoots();
root = new DefaultMutableTreeNode();
Widget wasSelectedWidget = selectedWidget;
int wasSelectedItem = selectedItem;
selectedWidget = null;
selectedItem = -1;
for (Widget widget : rootWidgets)
{
DefaultMutableTreeNode childNode = addWidget("R", widget);
if (childNode != null)
{
root.add(childNode);
}
}
SwingUtilities.invokeLater(() ->
{
widgetTree.setModel(new DefaultTreeModel(root));
setSelectedWidget(wasSelectedWidget, wasSelectedItem, true);
});
});
}
private DefaultMutableTreeNode addWidget(String type, Widget widget)
{
if (widget == null || (hideHidden.isSelected() && widget.isHidden()))
{
return null;
}
DefaultMutableTreeNode node = new WidgetTreeNode(type, widget);
Widget[] childComponents = widget.getDynamicChildren();
if (childComponents != null)
{
for (Widget component : childComponents)
{
DefaultMutableTreeNode childNode = addWidget("D", component);
if (childNode != null)
{
node.add(childNode);
}
}
}
childComponents = widget.getStaticChildren();
if (childComponents != null)
{
for (Widget component : childComponents)
{
DefaultMutableTreeNode childNode = addWidget("S", component);
if (childNode != null)
{
node.add(childNode);
}
}
}
childComponents = widget.getNestedChildren();
if (childComponents != null)
{
for (Widget component : childComponents)
{
DefaultMutableTreeNode childNode = addWidget("N", component);
if (childNode != null)
{
node.add(childNode);
}
}
}
Collection<WidgetItem> items = widget.getWidgetItems();
if (items != null)
{
for (WidgetItem item : items)
{
if (item == null)
{
continue;
}
node.add(new WidgetItemNode(item));
}
}
return node;
}
private void setSelectedWidget(Widget widget, int item, boolean updateTree)
{
infoTableModel.setWidget(widget);
if (this.selectedWidget == widget && this.selectedItem == item)
{
return;
}
this.selectedWidget = widget;
this.selectedItem = item;
if (root == null || !updateTree)
{
return;
}
clientThread.invoke(() ->
{
Stack<Widget> treePath = new Stack<>();
for (Widget w = widget; w != null; w = w.getParent())
{
treePath.push(w);
}
DefaultMutableTreeNode node = root;
deeper:
while (!treePath.empty())
{
Widget w = treePath.pop();
for (Enumeration<?> it = node.children(); it.hasMoreElements(); )
{
WidgetTreeNode inner = (WidgetTreeNode) it.nextElement();
if (inner.getWidget().getId() == w.getId() && inner.getWidget().getIndex() == w.getIndex())
{
node = inner;
continue deeper;
}
}
}
if (selectedItem != -1)
{
for (Enumeration<?> it = node.children(); it.hasMoreElements(); )
{
Object wiw = it.nextElement();
if (wiw instanceof WidgetItemNode)
{
WidgetItemNode inner = (WidgetItemNode) wiw;
if (inner.getWidgetItem().getIndex() == selectedItem)
{
node = inner;
break;
}
}
}
}
final DefaultMutableTreeNode fnode = node;
SwingUtilities.invokeLater(() ->
{
widgetTree.getSelectionModel().clearSelection();
widgetTree.getSelectionModel().addSelectionPath(new TreePath(fnode.getPath()));
});
});
}
static WidgetInfo getWidgetInfo(int packedId)
{
if (widgetIdMap.isEmpty())
{
//Initialize map here so it doesn't create the index
//until it's actually needed.
WidgetInfo[] widgets = WidgetInfo.values();
for (WidgetInfo w : widgets)
{
widgetIdMap.put(w.getPackedId(), w);
}
}
return widgetIdMap.get(packedId);
}
public void open()
{
setVisible(true);
toFront();
repaint();
overlayManager.add(this.overlay.get());
clientThread.invokeLater(this::addPickerWidget);
}
public void close()
{
overlayManager.remove(this.overlay.get());
clientThread.invokeLater(this::removePickerWidget);
setSelectedWidget(null, -1, false);
setVisible(false);
}
private void removePickerWidget()
{
if (picker == null)
{
return;
}
Widget parent = picker.getParent();
if (parent == null)
{
return;
}
Widget[] children = parent.getChildren();
if (children == null || children.length <= picker.getIndex() || children[picker.getIndex()] != picker)
{
return;
}
children[picker.getIndex()] = null;
}
private void addPickerWidget()
{
removePickerWidget();
int x = 10, y = 2;
Widget parent = client.getWidget(WidgetInfo.MINIMAP_ORBS);
if (parent == null)
{
Widget[] roots = client.getWidgetRoots();
parent = Stream.of(roots)
.filter(w -> w.getType() == WidgetType.LAYER && w.getContentType() == 0 && !w.isSelfHidden())
.sorted(Comparator.comparing((Widget w) -> w.getRelativeX() + w.getRelativeY())
.reversed()
.thenComparing(Widget::getId)
.reversed())
.findFirst().get();
x = 4;
y = 4;
}
picker = parent.createChild(-1, WidgetType.GRAPHIC);
log.info("Picker is {}.{} [{}]", WidgetInfo.TO_GROUP(picker.getId()), WidgetInfo.TO_CHILD(picker.getId()), picker.getIndex());
picker.setSpriteId(SpriteID.MOBILE_FINGER_ON_INTERFACE);
picker.setOriginalWidth(15);
picker.setOriginalHeight(17);
picker.setOriginalX(x);
picker.setOriginalY(y);
picker.revalidate();
picker.setTargetVerb("Select");
picker.setName("Pick");
picker.setClickMask(WidgetConfig.USE_WIDGET | WidgetConfig.USE_ITEM);
picker.setNoClickThrough(true);
picker.setOnTargetEnterListener((JavaScriptCallback) ev ->
{
pickerSelected = true;
picker.setOpacity(30);
client.setAllWidgetsAreOpTargetable(true);
});
picker.setOnTargetLeaveListener((JavaScriptCallback) ev -> onPickerDeselect());
}
private void onPickerDeselect()
{
client.setAllWidgetsAreOpTargetable(false);
picker.setOpacity(0);
pickerSelected = false;
}
@Subscribe
private void onMenuOptionClicked(MenuOptionClicked ev)
{
if (!pickerSelected)
{
return;
}
onPickerDeselect();
client.setSpellSelected(false);
ev.consume();
Object target = getWidgetOrWidgetItemForMenuOption(ev.getMenuAction().getId(), ev.getActionParam(), ev.getWidgetId());
if (target == null)
{
return;
}
if (target instanceof WidgetItem)
{
WidgetItem iw = (WidgetItem) target;
setSelectedWidget(iw.getWidget(), iw.getIndex(), true);
}
else
{
setSelectedWidget((Widget) target, -1, true);
}
}
@Subscribe
private void onMenuEntryAdded(MenuEntryAdded event)
{
if (!pickerSelected)
{
return;
}
MenuEntry[] menuEntries = client.getMenuEntries();
for (int i = 0; i < menuEntries.length; i++)
{
MenuEntry entry = menuEntries[i];
if (entry.getType() != MenuAction.ITEM_USE_ON_WIDGET.getId()
&& entry.getType() != MenuAction.SPELL_CAST_ON_WIDGET.getId())
{
continue;
}
String name = WidgetInfo.TO_GROUP(entry.getParam1()) + "." + WidgetInfo.TO_CHILD(entry.getParam1());
if (entry.getParam0() != -1)
{
name += " [" + entry.getParam0() + "]";
}
Color color = colorForWidget(i, menuEntries.length);
entry.setTarget(ColorUtil.wrapWithColorTag(name, color));
}
client.setMenuEntries(menuEntries);
}
Color colorForWidget(int index, int length)
{
float h = SELECTED_WIDGET_HUE + .1f + (.8f / length) * index;
return Color.getHSBColor(h, 1, 1);
}
Object getWidgetOrWidgetItemForMenuOption(int type, int param0, int param1)
{
if (type == MenuAction.SPELL_CAST_ON_WIDGET.getId())
{
Widget w = client.getWidget(param1);
if (param0 != -1)
{
w = w.getChild(param0);
}
return w;
}
else if (type == MenuAction.ITEM_USE_ON_WIDGET.getId())
{
Widget w = client.getWidget(param1);
return w.getWidgetItem(param0);
}
return null;
}
public static String getWidgetIdentifier(Widget widget)
{
int id = widget.getId();
String str = TO_GROUP(id) + "." + TO_CHILD(id);
if (widget.getIndex() != -1)
{
str += "[" + widget.getIndex() + "]";
}
WidgetInfo info = WidgetInspector.getWidgetInfo(id);
if (info != null)
{
str += " " + info.name();
}
return str;
}
}

View File

@@ -0,0 +1,132 @@
/*
* Copyright (c) 2018 Abex
* Copyright (c) 2017, Kronos <https://github.com/KronosDesign>
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;
import javax.inject.Inject;
import javax.inject.Singleton;
import net.runelite.api.Client;
import net.runelite.api.MenuEntry;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetItem;
import net.runelite.client.ui.overlay.Overlay;
import net.runelite.client.ui.overlay.OverlayLayer;
import net.runelite.client.ui.overlay.OverlayPosition;
import net.runelite.client.ui.overlay.OverlayPriority;
@Singleton
public class WidgetInspectorOverlay extends Overlay
{
private final Client client;
private final WidgetInspector inspector;
@Inject
public WidgetInspectorOverlay(
Client client,
WidgetInspector inspector
)
{
this.client = client;
this.inspector = inspector;
setPosition(OverlayPosition.DYNAMIC);
setLayer(OverlayLayer.ABOVE_WIDGETS);
setPriority(OverlayPriority.HIGHEST);
}
@Override
public Dimension render(Graphics2D g)
{
Widget w = inspector.getSelectedWidget();
if (w != null)
{
Object wiw = w;
if (inspector.getSelectedItem() != -1)
{
wiw = w.getWidgetItem(inspector.getSelectedItem());
}
renderWiw(g, wiw, WidgetInspector.SELECTED_WIDGET_COLOR);
}
if (inspector.isPickerSelected())
{
boolean menuOpen = client.isMenuOpen();
MenuEntry[] entries = client.getMenuEntries();
for (int i = menuOpen ? 0 : entries.length - 1; i < entries.length; i++)
{
MenuEntry e = entries[i];
Object wiw = inspector.getWidgetOrWidgetItemForMenuOption(e.getType(), e.getParam0(), e.getParam1());
if (wiw == null)
{
continue;
}
Color color = inspector.colorForWidget(i, entries.length);
renderWiw(g, wiw, color);
}
}
return null;
}
private void renderWiw(Graphics2D g, Object wiw, Color color)
{
g.setColor(color);
if (wiw instanceof WidgetItem)
{
WidgetItem wi = (WidgetItem) wiw;
Rectangle bounds = wi.getCanvasBounds();
g.draw(bounds);
String text = wi.getId() + "";
FontMetrics fm = g.getFontMetrics();
Rectangle2D textBounds = fm.getStringBounds(text, g);
int textX = (int) (bounds.getX() + (bounds.getWidth() / 2) - (textBounds.getWidth() / 2));
int textY = (int) (bounds.getY() + (bounds.getHeight() / 2) + (textBounds.getHeight() / 2));
g.setColor(Color.BLACK);
g.drawString(text, textX + 1, textY + 1);
g.setColor(Color.ORANGE);
g.drawString(text, textX, textY);
}
else
{
Widget w = (Widget) wiw;
g.draw(w.getBounds());
}
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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 net.runelite.api.widgets.WidgetItem;
class WidgetItemNode extends DefaultMutableTreeNode
{
private final WidgetItem widgetItem;
public WidgetItemNode(WidgetItem widgetItem)
{
super(widgetItem);
this.widgetItem = widgetItem;
}
public WidgetItem getWidgetItem()
{
return widgetItem;
}
@Override
public String toString()
{
return "I " + widgetItem.getIndex();
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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 net.runelite.api.widgets.Widget;
class WidgetTreeNode extends DefaultMutableTreeNode
{
private final String type;
public WidgetTreeNode(String type, Widget widget)
{
super(widget);
this.type = type;
}
public Widget getWidget()
{
return (Widget) getUserObject();
}
@Override
public String toString()
{
return type + " " + WidgetInspector.getWidgetIdentifier(getWidget());
}
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright (c) 2018, Morgan Lewis <https://github.com/MESLewis>
* 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.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import javax.inject.Inject;
import net.runelite.api.Client;
import net.runelite.api.Point;
import net.runelite.api.RenderOverview;
import net.runelite.api.coords.WorldPoint;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.client.ui.overlay.Overlay;
import net.runelite.client.ui.overlay.OverlayLayer;
import net.runelite.client.ui.overlay.OverlayPosition;
import net.runelite.client.ui.overlay.OverlayPriority;
import net.runelite.client.ui.overlay.worldmap.WorldMapOverlay;
public class WorldMapLocationOverlay extends Overlay
{
private final Client client;
private final WorldMapOverlay worldMapOverlay;
private final DevToolsPlugin plugin;
@Inject
private WorldMapLocationOverlay(Client client, WorldMapOverlay worldMapOverlay, DevToolsPlugin plugin)
{
this.client = client;
this.worldMapOverlay = worldMapOverlay;
this.plugin = plugin;
setPosition(OverlayPosition.DYNAMIC);
setPriority(OverlayPriority.HIGHEST);
setLayer(OverlayLayer.ABOVE_MAP);
}
@Override
public Dimension render(Graphics2D graphics)
{
if (!plugin.getWorldMapLocation().isActive())
{
return null;
}
RenderOverview ro = client.getRenderOverview();
Widget worldMapWidget = client.getWidget(WidgetInfo.WORLD_MAP_VIEW);
if (ro == null || worldMapWidget == null)
{
return null;
}
Rectangle worldMapRectangle = worldMapWidget.getBounds();
graphics.setClip(worldMapRectangle);
graphics.setColor(Color.CYAN);
WorldPoint mapCenterPoint = new WorldPoint(ro.getWorldMapPosition().getX(), ro.getWorldMapPosition().getY(), 0);
Point middle = worldMapOverlay.mapWorldPointToGraphicsPoint(mapCenterPoint);
if (middle == null)
{
return null;
}
graphics.drawLine(middle.getX(), worldMapRectangle.y, middle.getX(), worldMapRectangle.y + worldMapRectangle.height);
graphics.drawLine(worldMapRectangle.x, middle.getY(), worldMapRectangle.x + worldMapRectangle.width, middle.getY());
String output = "Center: " + mapCenterPoint.getX() + ", " + mapCenterPoint.getY();
graphics.setColor(Color.white);
FontMetrics fm = graphics.getFontMetrics();
int height = fm.getHeight();
int width = fm.stringWidth(output);
graphics.fillRect((int)worldMapRectangle.getX(), (int)worldMapRectangle.getY() + worldMapRectangle.height - height, (int)worldMapRectangle.getX() + width, (int)worldMapRectangle.getY() + worldMapRectangle.height);
graphics.setColor(Color.BLACK);
graphics.drawString(output, (int) worldMapRectangle.getX(), (int) worldMapRectangle.getY() + worldMapRectangle.height);
return null;
}
}

View File

@@ -0,0 +1,131 @@
/*
* Copyright (c) 2018, Alex Kolpa <https://github.com/AlexKolpa>
* 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.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;
import javax.inject.Inject;
import net.runelite.api.Client;
import net.runelite.api.Point;
import net.runelite.api.RenderOverview;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.client.ui.overlay.Overlay;
import net.runelite.client.ui.overlay.OverlayLayer;
import net.runelite.client.ui.overlay.OverlayPosition;
import net.runelite.client.ui.overlay.OverlayPriority;
class WorldMapRegionOverlay extends Overlay
{
private static final Color WHITE_TRANSLUCENT = new Color(255, 255, 255, 127);
private static final int LABEL_PADDING = 4;
private static final int REGION_SIZE = 1 << 6;
// Bitmask to return first coordinate in region
private static final int REGION_TRUNCATE = ~((1 << 6) - 1);
private final Client client;
private final DevToolsPlugin plugin;
@Inject
private WorldMapRegionOverlay(Client client, DevToolsPlugin plugin)
{
setPosition(OverlayPosition.DYNAMIC);
setPriority(OverlayPriority.HIGH);
setLayer(OverlayLayer.ABOVE_MAP);
this.client = client;
this.plugin = plugin;
}
@Override
public Dimension render(Graphics2D graphics)
{
if (!plugin.getWorldMapLocation().isActive())
{
return null;
}
drawRegionOverlay(graphics);
return null;
}
private void drawRegionOverlay(Graphics2D graphics)
{
RenderOverview ro = client.getRenderOverview();
Widget map = client.getWidget(WidgetInfo.WORLD_MAP_VIEW);
Float pixelsPerTile = ro.getWorldMapZoom();
if (map == null)
{
return;
}
Rectangle worldMapRect = map.getBounds();
graphics.setClip(worldMapRect);
int widthInTiles = (int) Math.ceil(worldMapRect.getWidth() / pixelsPerTile);
int heightInTiles = (int) Math.ceil(worldMapRect.getHeight() / pixelsPerTile);
Point worldMapPosition = ro.getWorldMapPosition();
// Offset in tiles from anchor sides
int yTileMin = worldMapPosition.getY() - heightInTiles / 2;
int xRegionMin = (worldMapPosition.getX() - widthInTiles / 2) & REGION_TRUNCATE;
int xRegionMax = ((worldMapPosition.getX() + widthInTiles / 2) & REGION_TRUNCATE) + REGION_SIZE;
int yRegionMin = (yTileMin & REGION_TRUNCATE);
int yRegionMax = ((worldMapPosition.getY() + heightInTiles / 2) & REGION_TRUNCATE) + REGION_SIZE;
int regionPixelSize = (int) Math.ceil(REGION_SIZE * pixelsPerTile);
for (int x = xRegionMin; x < xRegionMax; x += REGION_SIZE)
{
for (int y = yRegionMin; y < yRegionMax; y += REGION_SIZE)
{
graphics.setColor(WHITE_TRANSLUCENT);
int yTileOffset = -(yTileMin - y);
int xTileOffset = x + widthInTiles / 2 - worldMapPosition.getX();
int xPos = ((int) (xTileOffset * pixelsPerTile)) + (int) worldMapRect.getX();
int yPos = (worldMapRect.height - (int) (yTileOffset * pixelsPerTile)) + (int) worldMapRect.getY();
// Offset y-position by a single region to correct for drawRect starting from the top
yPos -= regionPixelSize;
graphics.drawRect(xPos, yPos, regionPixelSize, regionPixelSize);
int regionId = ((x >> 6) << 8) | (y >> 6);
String regionText = String.valueOf(regionId);
FontMetrics fm = graphics.getFontMetrics();
Rectangle2D textBounds = fm.getStringBounds(regionText, graphics);
int labelWidth = (int) textBounds.getWidth() + 2 * LABEL_PADDING;
int labelHeight = (int) textBounds.getHeight() + 2 * LABEL_PADDING;
graphics.fillRect(xPos, yPos, labelWidth, labelHeight);
graphics.setColor(Color.BLACK);
graphics.drawString(regionText, xPos + LABEL_PADDING, yPos + (int) textBounds.getHeight() + LABEL_PADDING);
}
}
}
}