From 0d0a938499d343fc1e296cd8f71fab34d4dd3f84 Mon Sep 17 00:00:00 2001 From: Kronos Date: Wed, 19 Apr 2017 07:39:38 +1000 Subject: [PATCH] Updated DevTools Plugin (#25) Updated DevTools and consolidated developer plugins --- .../src/main/java/net/runelite/api/Point.java | 4 +- .../client/plugins/PluginManager.java | 5 - .../runelite/client/plugins/debug/Debug.java | 40 -- .../client/plugins/debug/DebugOverlay.java | 197 ---------- .../client/plugins/devtools/DevTools.java | 150 +++++++- .../plugins/devtools/DevToolsOverlay.java | 346 ++++++++++++++++-- .../plugins/devtools/DevToolsPanel.java | 125 +++++++ .../plugins/gronditems/GroundItems.java | 40 -- .../gronditems/GroundItemsOverlay.java | 114 ------ .../client/plugins/devtools/devtools_icon.png | Bin 0 -> 966 bytes .../client/plugins/devtools/runescape.ttf | Bin 0 -> 36400 bytes 11 files changed, 581 insertions(+), 440 deletions(-) delete mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/debug/Debug.java delete mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/debug/DebugOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPanel.java delete mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/gronditems/GroundItems.java delete mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/gronditems/GroundItemsOverlay.java create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/devtools/devtools_icon.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/devtools/runescape.ttf diff --git a/runelite-api/src/main/java/net/runelite/api/Point.java b/runelite-api/src/main/java/net/runelite/api/Point.java index fa92f88d33..d027fd3451 100644 --- a/runelite-api/src/main/java/net/runelite/api/Point.java +++ b/runelite-api/src/main/java/net/runelite/api/Point.java @@ -59,9 +59,7 @@ public class Point */ public int distanceTo(Point other) { - int dx = x - other.x; - int dy = y - other.y; - return (int) Math.sqrt(dx * dx + dy * dy); + return (int) Math.hypot(getX() - other.getX(), getY() - other.getY()); } @Override diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java index c8d6babb15..4ffccb7417 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java @@ -30,10 +30,8 @@ import java.util.List; import net.runelite.client.RuneLite; import net.runelite.client.plugins.boosts.Boosts; import net.runelite.client.plugins.bosstimer.BossTimers; -import net.runelite.client.plugins.debug.Debug; import net.runelite.client.plugins.devtools.DevTools; import net.runelite.client.plugins.fpsinfo.FPS; -import net.runelite.client.plugins.gronditems.GroundItems; import net.runelite.client.plugins.hiscore.Hiscore; import net.runelite.client.plugins.idlenotifier.IdleNotifier; import net.runelite.client.plugins.opponentinfo.OpponentInfo; @@ -66,9 +64,6 @@ public class PluginManager if (RuneLite.getOptions().has("developer-mode")) { logger.info("Loading developer plugins"); - - load(new Debug()); - load(new GroundItems()); load(new DevTools()); } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/debug/Debug.java b/runelite-client/src/main/java/net/runelite/client/plugins/debug/Debug.java deleted file mode 100644 index f67a382734..0000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/debug/Debug.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2017, Adam - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.runelite.client.plugins.debug; - -import net.runelite.client.plugins.Plugin; -import net.runelite.client.ui.overlay.Overlay; - -public class Debug extends Plugin -{ - private final DebugOverlay debugOverlay = new DebugOverlay(); - - @Override - public Overlay getOverlay() - { - return debugOverlay; - } - -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/debug/DebugOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/debug/DebugOverlay.java deleted file mode 100644 index bc4a827054..0000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/debug/DebugOverlay.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright (c) 2017, Adam - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.runelite.client.plugins.debug; - -import java.awt.Color; -import java.awt.Dimension; -import java.awt.Graphics2D; -import java.awt.Rectangle; -import net.runelite.api.Client; -import net.runelite.api.DecorativeObject; -import net.runelite.api.GameObject; -import net.runelite.api.GameState; -import net.runelite.api.GroundObject; -import net.runelite.api.Point; -import net.runelite.api.Region; -import net.runelite.api.Tile; -import net.runelite.api.TileObject; -import net.runelite.api.WallObject; -import net.runelite.api.widgets.Widget; -import net.runelite.api.widgets.WidgetID; -import net.runelite.api.widgets.WidgetItem; -import net.runelite.client.RuneLite; -import net.runelite.client.ui.overlay.Overlay; -import net.runelite.client.ui.overlay.OverlayPosition; - -public class DebugOverlay extends Overlay -{ - private static final Client client = RuneLite.getClient(); - - private static final int REGION_SIZE = 104; - - public DebugOverlay() - { - super(OverlayPosition.DYNAMIC); - } - - @Override - public Dimension render(Graphics2D graphics) - { - if (client.getGameState() != GameState.LOGGED_IN) - { - return null; - } - - renderTiles(graphics); - renderInventory(graphics); - - return null; - } - - private void renderTiles(Graphics2D graphics) - { - Region region = client.getRegion(); - Tile[][][] tiles = region.getTiles(); - - int z = client.getPlane(); - - for (int x = 0; x < REGION_SIZE; ++x) - { - for (int y = 0; y < REGION_SIZE; ++y) - { - Tile tile = tiles[z][x][y]; - - if (tile == null) - { - continue; - } - - render(graphics, tile); - } - } - } - - private void renderInventory(Graphics2D graphics) - { - Widget inventoryWidget = client.getWidget(WidgetID.INVENTORY_GROUP_ID, WidgetID.INVENTORY_CHILD_ID); - if (inventoryWidget == null) - { - return; - } - - for (WidgetItem item : inventoryWidget.getWidgetItems()) - { - Rectangle bounds = item.getCanvasBounds(); - - Color[] colors = new Color[] - { - Color.RED, Color.GREEN, Color.BLUE - }; - graphics.setColor(colors[item.getIndex() % 3]); - if (bounds != null) - { - graphics.draw(bounds); - } - } - } - - private void render(Graphics2D graphics, Tile tile) - { - renderDecorativeObject(graphics, tile); - renderWallObject(graphics, tile); - renderGroundObject(graphics, tile); - renderGameObjects(graphics, tile); - } - - private void renderDecorativeObject(Graphics2D graphics, Tile tile) - { - DecorativeObject decorativeObject = tile.getDecorativeObject(); - if (decorativeObject == null) - { - return; - } - - int id = decorativeObject.getId(); - renderTileObject(graphics, decorativeObject, Color.RED, "D" + id); - } - - private void renderWallObject(Graphics2D graphics, Tile tile) - { - WallObject wallObject = tile.getWallObject(); - if (wallObject == null) - { - return; - - } - - int id = wallObject.getId(); - renderTileObject(graphics, wallObject, Color.GREEN, "W" + id); - } - - private void renderGroundObject(Graphics2D graphics, Tile tile) - { - GroundObject groundObject = tile.getGroundObject(); - if (groundObject == null) - { - return; - - } - - int id = groundObject.getId(); - renderTileObject(graphics, groundObject, Color.BLUE, "G" + id); - } - - private void renderGameObjects(Graphics2D graphics, Tile tile) - { - GameObject[] gameObjects = tile.getGameObjects(); - if (gameObjects == null) - { - return; - } - - for (GameObject gameObject : gameObjects) - { - if (gameObject == null) - { - continue; - } - - renderTileObject(graphics, gameObject, Color.YELLOW, "GA" + gameObject.getId()); - } - } - - private void renderTileObject(Graphics2D graphics, TileObject tileObject, Color color, String text) - { - Point canvasLocation = tileObject.getCanvasLocation(); - - if (canvasLocation == null) - { - return; - } - - graphics.setColor(color); - graphics.drawString(text, canvasLocation.getX(), canvasLocation.getY()); - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevTools.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevTools.java index 73a31996e0..927c160f10 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevTools.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevTools.java @@ -24,12 +24,70 @@ */ package net.runelite.client.plugins.devtools; +import java.awt.Font; +import java.awt.FontFormatException; +import java.awt.GraphicsEnvironment; +import java.awt.event.ActionEvent; +import java.io.IOException; +import javax.imageio.ImageIO; +import javax.swing.ImageIcon; +import net.runelite.client.RuneLite; import net.runelite.client.plugins.Plugin; +import net.runelite.client.ui.ClientUI; +import net.runelite.client.ui.NavigationButton; import net.runelite.client.ui.overlay.Overlay; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class DevTools extends Plugin { - private final DevToolsOverlay overlay = new DevToolsOverlay(); + private static final Logger logger = LoggerFactory.getLogger(DevTools.class); + + private final DevToolsOverlay overlay = new DevToolsOverlay(this); + private final DevToolsPanel panel = new DevToolsPanel(this); + private final NavigationButton navButton = new NavigationButton("DevTools"); + private final ClientUI ui = RuneLite.getRunelite().getGui(); + + private boolean togglePlayers; + private boolean toggleNpcs; + private boolean toggleGroundItems; + private boolean toggleGroundObjects; + private boolean toggleGameObjects; + private boolean toggleWalls; + private boolean toggleDecor; + private boolean toggleInventory; + + private Font font; + + public DevTools() + { + navButton.getButton().addActionListener(this::expandPanel); + + try + { + ImageIcon icon = new ImageIcon(ImageIO.read(getClass().getResourceAsStream("devtools_icon.png"))); + navButton.getButton().setIcon(icon); + } + catch (IOException ex) + { + logger.warn("Unable to load devtools icon", ex); + } + + ui.getNavigationPanel().addNavigation(navButton); + + try + { + font = Font.createFont(Font.TRUETYPE_FONT, getClass().getResourceAsStream("runescape.ttf")); + + font = font.deriveFont(Font.PLAIN, 16); + GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); + ge.registerFont(font); + } + catch (FontFormatException | IOException ex) + { + logger.warn("Unable to load font", ex); + } + } @Override public Overlay getOverlay() @@ -37,4 +95,94 @@ public class DevTools extends Plugin return overlay; } + private void expandPanel(ActionEvent e) + { + ui.setPluginPanel(panel); + ui.expand(); + } + + public Font getFont() + { + return font; + } + + public void togglePlayers() + { + togglePlayers = !togglePlayers; + } + + public void toggleNpcs() + { + toggleNpcs = !toggleNpcs; + } + + public void toggleGroundItems() + { + toggleGroundItems = !toggleGroundItems; + } + + public void toggleGroundObjects() + { + toggleGroundObjects = !toggleGroundObjects; + } + + public void toggleGameObjects() + { + toggleGameObjects = !toggleGameObjects; + } + + public void toggleWalls() + { + toggleWalls = !toggleWalls; + } + + public void toggleDecor() + { + toggleDecor = !toggleDecor; + } + + public void toggleInventory() + { + toggleInventory = !toggleInventory; + } + + public boolean isTogglePlayers() + { + return togglePlayers; + } + + public boolean isToggleNpcs() + { + return toggleNpcs; + } + + public boolean isToggleGroundItems() + { + return toggleGroundItems; + } + + public boolean isToggleGroundObjects() + { + return toggleGroundObjects; + } + + public boolean isToggleGameObjects() + { + return toggleGameObjects; + } + + public boolean isToggleWalls() + { + return toggleWalls; + } + + public boolean isToggleDecor() + { + return toggleDecor; + } + + public boolean isToggleInventory() + { + return toggleInventory; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsOverlay.java index e560ecb218..a6e2a34dc6 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsOverlay.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2017, Kronos + * Copyright (c) 2017, Adam * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -27,26 +28,55 @@ package net.runelite.client.plugins.devtools; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Dimension; +import java.awt.Font; import java.awt.Graphics2D; import java.awt.Polygon; +import java.awt.Rectangle; import net.runelite.api.Actor; import net.runelite.api.Client; +import net.runelite.api.DecorativeObject; +import net.runelite.api.GameObject; import net.runelite.api.GameState; +import net.runelite.api.GroundObject; +import net.runelite.api.Item; +import net.runelite.api.ItemLayer; import net.runelite.api.NPC; +import net.runelite.api.Node; import net.runelite.api.Player; import net.runelite.api.Point; +import net.runelite.api.Region; +import net.runelite.api.Tile; +import net.runelite.api.TileObject; +import net.runelite.api.WallObject; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetID; +import net.runelite.api.widgets.WidgetItem; import net.runelite.client.RuneLite; import net.runelite.client.ui.overlay.Overlay; import net.runelite.client.ui.overlay.OverlayPosition; public class DevToolsOverlay extends Overlay { + 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 Client client = RuneLite.getClient(); + private static final int REGION_SIZE = 104; + private static final int MAX_DISTANCE = 2400; - public DevToolsOverlay() + private final DevTools tools; + private final Client client = RuneLite.getClient(); + + public DevToolsOverlay(DevTools tools) { super(OverlayPosition.DYNAMIC); + this.tools = tools; } @Override @@ -57,6 +87,139 @@ public class DevToolsOverlay extends Overlay return null; } + if (tools.isTogglePlayers()) + { + renderPlayers(graphics); + } + + if (tools.isToggleNpcs()) + { + renderNpcs(graphics); + } + + if (tools.isToggleGroundItems() || tools.isToggleGroundObjects() || tools.isToggleGameObjects() || tools.isToggleWalls() || tools.isToggleDecor()) + { + renderTileObjects(graphics); + } + + if (tools.isToggleInventory()) + { + renderInventory(graphics); + } + + return null; + } + + private void renderActorOverlay(Graphics2D graphics, Actor actor, String text, Color color) + { + Polygon poly = actor.getCanvasTilePoly(); + if (poly != null) + { + graphics.setColor(color); + graphics.setStroke(new BasicStroke(2)); + graphics.drawPolygon(poly); + graphics.setColor(new Color(0, 0, 0, 50)); + graphics.fillPolygon(poly); + } + + Point minimapLocation = actor.getMinimapLocation(); + if (minimapLocation != null) + { + graphics.setColor(color); + graphics.fillOval(minimapLocation.getX(), minimapLocation.getY(), 5, 5); + graphics.setColor(Color.WHITE); + graphics.setStroke(new BasicStroke(1)); + graphics.drawOval(minimapLocation.getX(), minimapLocation.getY(), 5, 5); + } + + Point textLocation = actor.getCanvasTextLocation(graphics, text, actor.getModelHeight()); + if (textLocation != null) + { + int x = textLocation.getX(); + int y = textLocation.getY(); + + Font font = tools.getFont(); + if (font != null) + { + graphics.setFont(font); + } + + graphics.setColor(Color.BLACK); + graphics.drawString(text, x + 1, y + 1); + + graphics.setColor(color); + graphics.drawString(text, x, y); + } + } + + private void renderTileOverlay(Graphics2D graphics, TileObject tileObject, String text, Color color) + { + Polygon poly = tileObject.getCanvasTilePoly(); + if (poly != null) + { + graphics.setColor(color); + graphics.setStroke(new BasicStroke(2)); + graphics.drawPolygon(poly); + graphics.setColor(new Color(0, 0, 0, 50)); + graphics.fillPolygon(poly); + } + + Point minimapLocation = tileObject.getMinimapLocation(); + if (minimapLocation != null) + { + graphics.setColor(color); + graphics.fillOval(minimapLocation.getX(), minimapLocation.getY(), 5, 5); + graphics.setColor(Color.WHITE); + graphics.setStroke(new BasicStroke(1)); + graphics.drawOval(minimapLocation.getX(), minimapLocation.getY(), 5, 5); + } + + Point textLocation = tileObject.getCanvasTextLocation(graphics, text, 0); + if (textLocation != null) + { + int x = textLocation.getX(); + int y = textLocation.getY(); + + Font font = tools.getFont(); + if (font != null) + { + graphics.setFont(font); + } + + graphics.setColor(Color.BLACK); + graphics.drawString(text, x + 1, y + 1); + + graphics.setColor(color); + graphics.drawString(text, x, y); + } + } + + private void renderPlayers(Graphics2D graphics) + { + Player[] players = client.getPlayers(); + Player local = client.getLocalPlayer(); + + if (players != null && (players.length - 1) > 0) + { + for (Player p : players) + { + if (p != null) + { + if (!p.getName().equals(local.getName())) + { + String text = p.getName() + " (A: " + p.getAnimation() + ")"; + renderActorOverlay(graphics, p, text, BLUE); + } + } + } + } + + String text = local.getName() + " (A: " + local.getAnimation() + ")"; + renderActorOverlay(graphics, local, text, CYAN); + } + + private void renderNpcs(Graphics2D graphics) + { NPC[] npcs = client.getNpcs(); if (npcs != null && (npcs.length - 1) > 0) { @@ -67,59 +230,162 @@ public class DevToolsOverlay extends Overlay String text = npc.getName() + " (A: " + npc.getAnimation() + ")"; if (npc.getCombatLevel() > 1) { - render(graphics, npc, text, new Color(230, 74, 25)); + renderActorOverlay(graphics, npc, text, YELLOW); } else { - render(graphics, npc, text, Color.ORANGE); + renderActorOverlay(graphics, npc, text, ORANGE); } } } } - - Player[] players = client.getPlayers(); - if (players != null && (players.length - 1) > 0) - { - for (Player p : players) - { - if (p != null) - { - if (!p.getName().equals(client.getLocalPlayer().getName())) - { - String text = p.getName() + " (A: " + p.getAnimation() + ")"; - render(graphics, p, text, Color.BLUE); - } - } - } - } - - Player local = client.getLocalPlayer(); - String text = local.getName() + " (A: " + local.getAnimation() + ")"; - render(graphics, local, text, Color.CYAN); - - return null; } - private void render(Graphics2D graphics, Actor actor, String text, Color color) + private void renderTileObjects(Graphics2D graphics) { - Polygon poly = actor.getCanvasTilePoly(); - if (poly == null) + Region region = client.getRegion(); + Tile[][][] tiles = region.getTiles(); + + int z = client.getPlane(); + + for (int x = 0; x < REGION_SIZE; ++x) + { + for (int y = 0; y < REGION_SIZE; ++y) + { + Tile tile = tiles[z][x][y]; + + if (tile == null) + { + continue; + } + + Player player = client.getLocalPlayer(); + if (player == null) + { + continue; + } + + if (tools.isToggleGroundItems()) + { + renderGroundItems(graphics, tile, player); + } + + if (tools.isToggleGroundObjects()) + { + renderGroundObject(graphics, tile, player); + } + + if (tools.isToggleGameObjects()) + { + renderGameObjects(graphics, tile, player); + } + + if (tools.isToggleWalls()) + { + renderWallObject(graphics, tile, player); + } + + if (tools.isToggleDecor()) + { + renderDecorObject(graphics, tile, player); + } + } + } + } + + 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 Item) + { + Item item = (Item) current; + 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) + { + renderTileOverlay(graphics, gameObject, "ID: " + gameObject.getId(), GREEN); + } + } + } + } + } + + private void renderGroundObject(Graphics2D graphics, Tile tile, Player player) + { + GroundObject groundObject = tile.getGroundObject(); + if (groundObject != null) + { + if (player.getLocalLocation().distanceTo(groundObject.getLocalLocation()) <= MAX_DISTANCE) + { + 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) + { + 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) + { + renderTileOverlay(graphics, decorObject, "ID: " + decorObject.getId(), DEEP_PURPLE); + } + } + } + + private void renderInventory(Graphics2D graphics) + { + Widget inventoryWidget = client.getWidget(WidgetID.INVENTORY_GROUP_ID, WidgetID.INVENTORY_CHILD_ID); + if (inventoryWidget == null) { return; } - Point textLocation = actor.getCanvasTextLocation(graphics, text, actor.getModelHeight()); - - graphics.setColor(color); - graphics.setStroke(new BasicStroke(2)); - graphics.drawPolygon(poly); - graphics.setColor(new Color(0, 0, 0, 50)); - graphics.fillPolygon(poly); - - if (textLocation != null) + for (WidgetItem item : inventoryWidget.getWidgetItems()) { - graphics.setColor(Color.WHITE); - graphics.drawString(text, textLocation.getX(), textLocation.getY()); + Rectangle bounds = item.getCanvasBounds(); + + Color[] colors = new Color[] + { + Color.RED, Color.GREEN, Color.BLUE + }; + graphics.setColor(colors[item.getIndex() % 3]); + if (bounds != null) + { + graphics.draw(bounds); + } } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPanel.java new file mode 100644 index 0000000000..8bb9ab129c --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPanel.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2017, Kronos + * 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 javax.swing.JButton; +import javax.swing.JPanel; +import net.runelite.client.ui.PluginPanel; + +public class DevToolsPanel extends PluginPanel +{ + private JButton renderPlayersBtn = new JButton(); + private JButton renderNpcsBtn = new JButton(); + private JButton renderGroundItemsBtn = new JButton(); + private JButton renderGroundObjectsBtn = new JButton(); + private JButton renderGameObjectsBtn = new JButton(); + private JButton renderWallsBtn = new JButton(); + private JButton renderDecorBtn = new JButton(); + private JButton renderInventoryBtn = new JButton(); + + public DevToolsPanel(DevTools tools) + { + setMinimumSize(new Dimension(PANEL_WIDTH, PANEL_HEIGHT)); + setPreferredSize(new Dimension(PANEL_WIDTH, PANEL_HEIGHT)); + setSize(PANEL_WIDTH, PANEL_HEIGHT); + setVisible(true); + + JPanel container = new JPanel(); + container.setLayout(new GridLayout(4, 2, 3, 3)); + add(container); + + renderPlayersBtn = new JButton("Players"); + renderPlayersBtn.addActionListener(e -> { + highlightButton(renderPlayersBtn); + tools.togglePlayers(); + }); + container.add(renderPlayersBtn); + + renderNpcsBtn = new JButton("NPCs"); + renderNpcsBtn.addActionListener(e -> { + highlightButton(renderNpcsBtn); + tools.toggleNpcs(); + }); + container.add(renderNpcsBtn); + + renderGroundItemsBtn = new JButton("Ground Items"); + renderGroundItemsBtn.addActionListener(e -> { + highlightButton(renderGroundItemsBtn); + tools.toggleGroundItems(); + }); + container.add(renderGroundItemsBtn); + + renderGroundObjectsBtn = new JButton("Ground Objects"); + renderGroundObjectsBtn.addActionListener(e -> { + highlightButton(renderGroundObjectsBtn); + tools.toggleGroundObjects(); + }); + container.add(renderGroundObjectsBtn); + + renderGameObjectsBtn = new JButton("Game Objects"); + renderGameObjectsBtn.addActionListener(e -> { + highlightButton(renderGameObjectsBtn); + tools.toggleGameObjects(); + }); + container.add(renderGameObjectsBtn); + + renderWallsBtn = new JButton("Walls"); + renderWallsBtn.addActionListener(e -> { + highlightButton(renderWallsBtn); + tools.toggleWalls(); + }); + container.add(renderWallsBtn); + + renderDecorBtn = new JButton("Decorations"); + renderDecorBtn.addActionListener(e -> { + highlightButton(renderDecorBtn); + tools.toggleDecor(); + }); + container.add(renderDecorBtn); + + renderInventoryBtn = new JButton("Inventory"); + renderInventoryBtn.addActionListener(e -> { + highlightButton(renderInventoryBtn); + tools.toggleInventory(); + }); + container.add(renderInventoryBtn); + } + + private void highlightButton(JButton button) + { + if (button.getBackground().equals(Color.GREEN)) + { + button.setBackground(null); + } + else + { + button.setBackground(Color.GREEN); + } + } + +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gronditems/GroundItems.java b/runelite-client/src/main/java/net/runelite/client/plugins/gronditems/GroundItems.java deleted file mode 100644 index 1cd013c6fc..0000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/gronditems/GroundItems.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2017, Adam - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.runelite.client.plugins.gronditems; - -import net.runelite.client.plugins.Plugin; -import net.runelite.client.ui.overlay.Overlay; - -public class GroundItems extends Plugin -{ - private final GroundItemsOverlay groundItemsOverlay = new GroundItemsOverlay(); - - @Override - public Overlay getOverlay() - { - return groundItemsOverlay; - } - -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gronditems/GroundItemsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/gronditems/GroundItemsOverlay.java deleted file mode 100644 index ae69f47a7b..0000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/gronditems/GroundItemsOverlay.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (c) 2017, Adam - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.runelite.client.plugins.gronditems; - -import java.awt.Color; -import java.awt.Dimension; -import java.awt.Graphics2D; -import net.runelite.api.Client; -import net.runelite.api.GameState; -import net.runelite.api.Item; -import net.runelite.api.ItemLayer; -import net.runelite.api.Node; -import net.runelite.api.Point; -import net.runelite.api.Region; -import net.runelite.api.Tile; -import net.runelite.client.RuneLite; -import net.runelite.client.ui.overlay.Overlay; -import net.runelite.client.ui.overlay.OverlayPosition; - -public class GroundItemsOverlay extends Overlay -{ - private static final Client client = RuneLite.getClient(); - - private static final int REGION_SIZE = 104; - - public GroundItemsOverlay() - { - super(OverlayPosition.DYNAMIC); - } - - @Override - public Dimension render(Graphics2D graphics) - { - if (client.getGameState() != GameState.LOGGED_IN) - { - return null; - } - - Region region = client.getRegion(); - Tile[][][] tiles = region.getTiles(); - - int z = client.getPlane(); - - for (int x = 0; x < REGION_SIZE; ++x) - { - for (int y = 0; y < REGION_SIZE; ++y) - { - Tile tile = tiles[z][x][y]; - - if (tile == null) - { - continue; - } - - render(graphics, tile); - } - } - - return null; - } - - private void render(Graphics2D graphics, Tile tile) - { - ItemLayer itemLayer = tile.getItemLayer(); - - if (itemLayer == null) - { - return; - } - - Point canvasLocation = itemLayer.getCanvasLocation(); - if (canvasLocation == null) - { - return; - } - - Node current = itemLayer.getBottom(); - - while (current instanceof Item) - { - Item item = (Item) current; - render(graphics, canvasLocation, item); - current = current.getNext(); - } - } - - private void render(Graphics2D graphics, Point canvasLocation, Item item) - { - graphics.setColor(Color.ORANGE); - graphics.drawString("I" + item.getId(), canvasLocation.getX(), canvasLocation.getY()); - } -} diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/devtools/devtools_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/devtools/devtools_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..2dbd69a8ee58b04b412a3eaeb4d74b2b7943cf33 GIT binary patch literal 966 zcmV;%13CPOP)UbZL)2t~m@0W+{ug2kBkXC#JP;0|+590`}Y0;fqvSIyarE0_OS;^=RYKqpiTf>1=j5 z`*1-a<$>C@tL*UVS~QUPJ7s77oc=gHl{#5bs`P-Jz%k&Ocmv3-wG>(a; zslA=H?OQRPu();h3Tn|nm@4oQ+xO1C( z_wHkwCPIicfTdb?bdCX=l~ikeUz7Z6id-&7IBZi@UBfeVbvzliNsNzkXL1rLB}yrP zQJ{NC<5!ndz78a6 zs#eTbR)tAt<`SOk#L}}fwv*3em}W*P^=>E>x@Z^%j^lg@%sn2{^7I@#vC4`v*>Zlv>XI3y{oBzQ#zC{{R3007*qoM6N<$g4@)|3IG5A literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/devtools/runescape.ttf b/runelite-client/src/main/resources/net/runelite/client/plugins/devtools/runescape.ttf new file mode 100644 index 0000000000000000000000000000000000000000..0d76cf55024a32b44052e3f93422a8e8fae60ed8 GIT binary patch literal 36400 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 DJ;nT5 literal 0 HcmV?d00001