From 1923671bb1eaec16e2c2bdd06e4495caf6ce37f3 Mon Sep 17 00:00:00 2001 From: Adam Date: Tue, 20 Mar 2018 17:47:01 -0400 Subject: [PATCH] runelite-client: add npc highlight plugin --- .../npchighlight/NpcClickboxOverlay.java | 101 +++++++++ .../npchighlight/NpcHighlightConfig.java | 82 +++++++ .../npchighlight/NpcHighlightPlugin.java | 211 ++++++++++++++++++ .../npchighlight/NpcMinimapOverlay.java | 75 +++++++ 4 files changed, 469 insertions(+) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcClickboxOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcHighlightConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcHighlightPlugin.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcMinimapOverlay.java diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcClickboxOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcClickboxOverlay.java new file mode 100644 index 0000000000..e03c138793 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcClickboxOverlay.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2018, James Swindle + * Copyright (c) 2018, 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.npchighlight; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Polygon; +import java.util.Map; +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.api.NPC; +import net.runelite.api.NPCComposition; +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 NpcClickboxOverlay extends Overlay +{ + private final Client client; + private final NpcHighlightConfig config; + private final NpcHighlightPlugin plugin; + + @Inject + NpcClickboxOverlay(Client client, NpcHighlightConfig config, NpcHighlightPlugin plugin) + { + this.client = client; + this.config = config; + this.plugin = plugin; + setPosition(OverlayPosition.DYNAMIC); + setLayer(OverlayLayer.ABOVE_SCENE); + } + + @Override + public Dimension render(Graphics2D graphics, Point parent) + { + Map npcMap = plugin.getHighlightedNpcs(); + for (NPC npc : npcMap.keySet()) + { + renderNpcOverlay(graphics, npc, npcMap.get(npc), config.getNpcColor()); + } + + for (NPC npc : plugin.getTaggedNpcs()) + { + NPCComposition composition = plugin.getComposition(npc); + if (composition == null || composition.getName() == null) + continue; + + String name = composition.getName().replace('\u00A0', ' '); + renderNpcOverlay(graphics, npc, name, config.getTagColor()); + } + + return null; + } + + private void renderNpcOverlay(Graphics2D graphics, NPC actor, String name, Color color) + { + Polygon objectClickbox = actor.getConvexHull(); + if (objectClickbox != null) + { + graphics.setColor(color); + graphics.setStroke(new BasicStroke(2)); + graphics.draw(objectClickbox); + graphics.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), 20)); + graphics.fill(objectClickbox); + } + + net.runelite.api.Point textLocation = actor.getCanvasTextLocation(graphics, name, actor.getLogicalHeight() + 40); + + if (textLocation != null) + { + OverlayUtil.renderTextLocation(graphics, textLocation, name, color); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcHighlightConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcHighlightConfig.java new file mode 100644 index 0000000000..f49fe029fa --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcHighlightConfig.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2018, Tomas Slusny + * 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.npchighlight; + +import java.awt.Color; +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup( + keyName = "npchighlight", + name = "NPC Highlight", + description = "Configuration for the NPC highlight plugin" +) +public interface NpcHighlightConfig extends Config +{ + @ConfigItem( + position = 0, + keyName = "npcToHighlight", + name = "NPCs to Highlight", + description = "List of NPC names to highlight" + ) + default String getNpcToHighlight() + { + return ""; + } + + @ConfigItem( + position = 1, + keyName = "npcColor", + name = "Highlight Color", + description = "Color of the NPC highlight" + ) + default Color getNpcColor() + { + return Color.CYAN; + } + + @ConfigItem( + position = 2, + keyName = "enableTag", + name = "Enable Tag Option", + description = "Enable the NPC tag menu option" + ) + default boolean isTagEnabled() + { + return false; + } + + @ConfigItem( + position = 3, + keyName = "tagColor", + name = "Tag Color", + description = "Color of the NPC tag highlight" + ) + default Color getTagColor() + { + return Color.CYAN; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcHighlightPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcHighlightPlugin.java new file mode 100644 index 0000000000..55dc9e0168 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcHighlightPlugin.java @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2018, James Swindle + * Copyright (c) 2018, 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.npchighlight; + +import com.google.common.eventbus.Subscribe; +import com.google.inject.Provides; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.inject.Inject; +import lombok.AccessLevel; +import lombok.Getter; +import net.runelite.api.Client; +import net.runelite.api.NPC; +import net.runelite.api.NPCComposition; +import net.runelite.api.events.ConfigChanged; +import net.runelite.api.events.GameTick; +import net.runelite.api.events.MenuOptionClicked; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.menus.MenuManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.util.WildcardMatcher; + +@PluginDescriptor(name = "NPC Highlight") +public class NpcHighlightPlugin extends Plugin +{ + // Option added to NPC menu + private static final String TAG = "Tag"; + + // Regex for splitting the hidden items in the config. + private static final String DELIMITER_REGEX = "\\s*,\\s*"; + + @Inject + private Client client; + + @Inject + private MenuManager menuManager; + + @Inject + private NpcHighlightConfig config; + + @Inject + private NpcClickboxOverlay npcClickboxOverlay; + + @Inject + private NpcMinimapOverlay npcMinimapOverlay; + + @Getter(AccessLevel.PACKAGE) + private final Set npcTags = new HashSet<>(); + + @Getter(AccessLevel.PACKAGE) + private final List taggedNpcs = new ArrayList<>(); + + @Getter(AccessLevel.PACKAGE) + private Map highlightedNpcs = new HashMap<>(); + + private void toggleTag(int npcId) + { + boolean removed = npcTags.remove(npcId); + if (!removed) + npcTags.add(npcId); + } + + @Provides + NpcHighlightConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(NpcHighlightConfig.class); + } + + @Override + protected void startUp() throws Exception + { + if (config.isTagEnabled()) + menuManager.addNpcMenuOption(TAG); + } + + @Override + protected void shutDown() throws Exception + { + npcTags.clear(); + taggedNpcs.clear(); + menuManager.removeNpcMenuOption(TAG); + } + + @Subscribe + public void updateConfig(ConfigChanged event) + { + if (!event.getGroup().equals("npchighlight")) + return; + + if (config.isTagEnabled()) + menuManager.addNpcMenuOption(TAG); + else + menuManager.removeNpcMenuOption(TAG); + } + + @Subscribe + public void onMenuObjectClicked(MenuOptionClicked click) + { + if (click.getMenuOption().equals(TAG)) + toggleTag(click.getId()); + } + + @Subscribe + public void onGameTick(GameTick tick) + { + highlightedNpcs = buildNpcsToHighlight(); + taggedNpcs.clear(); + if (npcTags.isEmpty() || !config.isTagEnabled()) + { + return; + } + for (NPC npc : client.getNpcs()) + { + if (npcTags.contains(npc.getIndex())) + { + NPCComposition composition = getComposition(npc); + if (composition == null || composition.getName() == null) + continue; + + taggedNpcs.add(npc); + } + } + } + + @Override + public Collection getOverlays() + { + return Arrays.asList(npcClickboxOverlay, npcMinimapOverlay); + } + + private Map buildNpcsToHighlight() + { + String configNpcs = config.getNpcToHighlight().toLowerCase(); + if (configNpcs.isEmpty()) + return Collections.EMPTY_MAP; + + Map npcMap = new HashMap<>(); + List highlightedNpcs = Arrays.asList(configNpcs.split(DELIMITER_REGEX)); + + for (NPC npc : client.getNpcs()) + { + NPCComposition composition = getComposition(npc); + + if (npc == null || composition == null || composition.getName() == null) + continue; + + for (String highlight : highlightedNpcs) + { + String name = composition.getName().replace('\u00A0', ' '); + if (WildcardMatcher.matches(highlight, name)) + { + npcMap.put(npc, name); + } + } + } + + return npcMap; + } + + /** + * Get npc composition, account for imposters + * + * @param npc + * @return + */ + protected NPCComposition getComposition(NPC npc) + { + if (npc == null) + return null; + + NPCComposition composition = npc.getComposition(); + if (composition != null && composition.getConfigs() != null) + { + composition = composition.transform(); + } + + return composition; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcMinimapOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcMinimapOverlay.java new file mode 100644 index 0000000000..3a185127c3 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcMinimapOverlay.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2018, James Swindle + * Copyright (c) 2018, 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.npchighlight; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Point; +import java.util.Map; +import javax.inject.Inject; +import net.runelite.api.NPC; +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 NpcMinimapOverlay extends Overlay +{ + private final NpcHighlightConfig config; + private final NpcHighlightPlugin plugin; + + @Inject + NpcMinimapOverlay(NpcHighlightConfig config, NpcHighlightPlugin plugin) + { + this.config = config; + this.plugin = plugin; + setPosition(OverlayPosition.DYNAMIC); + setLayer(OverlayLayer.ABOVE_WIDGETS); + } + + @Override + public Dimension render(Graphics2D graphics, Point parent) + { + Map npcMap = plugin.getHighlightedNpcs(); + for (NPC npc : npcMap.keySet()) + { + renderNpcOverlay(graphics, npc, npcMap.get(npc), config.getNpcColor()); + } + + return null; + } + + private void renderNpcOverlay(Graphics2D graphics, NPC actor, String name, Color color) + { + net.runelite.api.Point minimapLocation = actor.getMinimapLocation(); + if (minimapLocation != null) + { + OverlayUtil.renderMinimapLocation(graphics, minimapLocation, color.darker()); + OverlayUtil.renderTextLocation(graphics, minimapLocation, name, color); + } + } +}