devtools
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 2020, TheStonedTurtle <https://github.com/TheStonedTurtle>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.runelite.client.plugins.devtools;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import net.runelite.api.Item;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
class InventoryItem
|
||||
{
|
||||
private final int slot;
|
||||
private Item item;
|
||||
private final String name;
|
||||
private final boolean stackable;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2020, TheStonedTurtle <https://github.com/TheStonedTurtle>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.runelite.client.plugins.devtools;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import lombok.Value;
|
||||
import net.runelite.api.Item;
|
||||
|
||||
@Value
|
||||
class InventoryLog
|
||||
{
|
||||
int containerId;
|
||||
@Nullable
|
||||
String containerName;
|
||||
Item[] items;
|
||||
int tick;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (c) 2020, TheStonedTurtle <https://github.com/TheStonedTurtle>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.runelite.client.plugins.devtools;
|
||||
|
||||
import javax.swing.tree.DefaultMutableTreeNode;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
class InventoryLogNode extends DefaultMutableTreeNode
|
||||
{
|
||||
private final InventoryLog log;
|
||||
|
||||
InventoryLogNode(final InventoryLog log)
|
||||
{
|
||||
super();
|
||||
|
||||
this.log = log;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "Tick: " + log.getTick();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) 2020, TheStonedTurtle <https://github.com/TheStonedTurtle>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package net.runelite.client.plugins.devtools;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.swing.tree.DefaultMutableTreeNode;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
class InventoryTreeNode extends DefaultMutableTreeNode
|
||||
{
|
||||
final int id;
|
||||
@Nullable
|
||||
final String name;
|
||||
|
||||
InventoryTreeNode(final int id, @Nullable final String name)
|
||||
{
|
||||
super();
|
||||
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return id + (name == null ? "" : " - " + name);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user