diff --git a/.gitignore b/.gitignore index 28ddc69bf1..a4f1ba7d02 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ nb-configuration.xml project.properties *.iml .idea/ +.project +.settings/ +.classpath \ No newline at end of file diff --git a/runelite-api/src/main/java/net/runelite/api/Actor.java b/runelite-api/src/main/java/net/runelite/api/Actor.java index 54ea822326..4c021a2249 100644 --- a/runelite-api/src/main/java/net/runelite/api/Actor.java +++ b/runelite-api/src/main/java/net/runelite/api/Actor.java @@ -68,4 +68,6 @@ public interface Actor extends Renderable * Returns the logical height of the actor's model. This is roughly where the health bar is drawn. */ int getLogicalHeight(); + + Polygon getConvexHull(); } diff --git a/runelite-api/src/main/java/net/runelite/api/TileObject.java b/runelite-api/src/main/java/net/runelite/api/TileObject.java index abf2e36bd9..abc580c92e 100644 --- a/runelite-api/src/main/java/net/runelite/api/TileObject.java +++ b/runelite-api/src/main/java/net/runelite/api/TileObject.java @@ -56,8 +56,6 @@ public interface TileObject Point getMinimapLocation(); - Polygon getConvexHull(Model model, int orientation); - /** * Get the on-screen clickable area of {@code object} * diff --git a/runelite-api/src/main/java/net/runelite/api/events/NpcActionChanged.java b/runelite-api/src/main/java/net/runelite/api/events/NpcActionChanged.java new file mode 100644 index 0000000000..5d105547ff --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/events/NpcActionChanged.java @@ -0,0 +1,35 @@ +/* + * 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.api.events; + +import lombok.Data; +import net.runelite.api.NPCComposition; + +@Data +public class NpcActionChanged +{ + private NPCComposition npcComposition; + private int idx; +} diff --git a/runelite-client/src/main/java/net/runelite/client/menus/MenuManager.java b/runelite-client/src/main/java/net/runelite/client/menus/MenuManager.java index 7502a7b883..6abceb6264 100644 --- a/runelite-client/src/main/java/net/runelite/client/menus/MenuManager.java +++ b/runelite-client/src/main/java/net/runelite/client/menus/MenuManager.java @@ -32,7 +32,9 @@ import com.google.common.eventbus.Subscribe; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import javax.inject.Inject; import javax.inject.Provider; import javax.inject.Singleton; @@ -40,8 +42,11 @@ import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; import net.runelite.api.MenuAction; import net.runelite.api.MenuEntry; +import net.runelite.api.NPC; +import net.runelite.api.NPCComposition; import net.runelite.api.events.MenuEntryAdded; import net.runelite.api.events.MenuOptionClicked; +import net.runelite.api.events.NpcActionChanged; import net.runelite.api.events.PlayerMenuOptionClicked; import net.runelite.api.events.PlayerMenuOptionsChanged; import net.runelite.api.events.WidgetMenuOptionClicked; @@ -64,6 +69,7 @@ public class MenuManager private final Map playerMenuIndexMap = new HashMap<>(); //Used to manage custom non-player menu options private final Multimap managedMenuOptions = HashMultimap.create(); + private final Set npcMenuOptions = new HashSet<>(); @Inject public MenuManager(Provider clientProvider, EventBus eventBus) @@ -72,6 +78,32 @@ public class MenuManager this.eventBus = eventBus; } + public void addNpcMenuOption(String option) + { + npcMenuOptions.add(option); + + // add to surrounding npcs + Client client = clientProvider.get(); + for (NPC npc : client.getNpcs()) + { + NPCComposition composition = npc.getComposition(); + addNpcOption(composition, option); + } + } + + public void removeNpcMenuOption(String option) + { + npcMenuOptions.remove(npcMenuOptions); + + // remove this option from all npc compositions + Client client = clientProvider.get(); + for (NPC npc : client.getNpcs()) + { + NPCComposition composition = npc.getComposition(); + removeNpcOption(composition, option); + } + } + /** * Adds a CustomMenuOption to the list of managed menu options. * @@ -186,6 +218,56 @@ public class MenuManager addPlayerMenuItem(newIdx, menuText); } + @Subscribe + public void onNpcActionChanged(NpcActionChanged event) + { + NPCComposition composition = event.getNpcComposition(); + for (String npcOption : npcMenuOptions) + { + addNpcOption(composition, npcOption); + } + } + + private void addNpcOption(NPCComposition composition, String npcOption) + { + String[] actions = composition.getActions(); + int unused = -1; + for (int i = 0; i < actions.length; ++i) + { + if (actions[i] == null && unused == -1) + { + unused = i; + } + else if (actions[i] != null && actions[i].equals(npcOption)) + { + return; + } + } + if (unused == -1) + { + return; + } + actions[unused] = npcOption; + } + + private void removeNpcOption(NPCComposition composition, String npcOption) + { + String[] actions = composition.getActions(); + + if (composition.getActions() == null) + { + return; + } + + for (int i = 0; i < actions.length; ++i) + { + if (actions[i] != null && actions[i].equals(npcOption)) + { + actions[i] = null; + } + } + } + @Subscribe public void onMenuOptionClicked(MenuOptionClicked event) { 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 8d9fc9ebf1..8073ea296f 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 @@ -155,7 +155,7 @@ public class DevToolsOverlay extends Overlay for (NPC npc : npcs) { NPCComposition composition = npc.getComposition(); - if (composition.getConfigs() != null && composition.transform() != null) + if (composition.getConfigs() != null) { composition = composition.transform(); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/WildcardMatchLoader.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/WildcardMatchLoader.java index ab6da416bd..ff7e94102e 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/WildcardMatchLoader.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/WildcardMatchLoader.java @@ -27,15 +27,11 @@ package net.runelite.client.plugins.grounditems; import com.google.common.base.Strings; import com.google.common.cache.CacheLoader; import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import javax.annotation.Nonnull; +import net.runelite.client.util.WildcardMatcher; class WildcardMatchLoader extends CacheLoader { - // Regex used for matching item names with others - private static final Pattern WILDCARD_PATTERN = Pattern.compile("(?i)[^*]+|(\\*)"); - private final List nameFilters; WildcardMatchLoader(List nameFilters) @@ -55,26 +51,7 @@ class WildcardMatchLoader extends CacheLoader for (final String filter : nameFilters) { - final Matcher matcher = WILDCARD_PATTERN.matcher(filter); - final StringBuffer buffer = new StringBuffer(); - - buffer.append("(?i)"); - while (matcher.find()) - { - if (matcher.group(1) != null) - { - matcher.appendReplacement(buffer, ".*"); - } - else - { - matcher.appendReplacement(buffer, "\\\\Q" + matcher.group(0) + "\\\\E"); - } - } - - matcher.appendTail(buffer); - final String replaced = buffer.toString(); - - if (filteredName.matches(replaced)) + if (WildcardMatcher.matches(filter, filteredName)) { return true; } 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); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/util/WildcardMatcher.java b/runelite-client/src/main/java/net/runelite/client/util/WildcardMatcher.java new file mode 100644 index 0000000000..354005a048 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/util/WildcardMatcher.java @@ -0,0 +1,57 @@ +/* + * 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.util; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class WildcardMatcher +{ + private static final Pattern WILDCARD_PATTERN = Pattern.compile("(?i)[^*]+|(\\*)"); + + public static boolean matches(String pattern, String text) + { + final Matcher matcher = WILDCARD_PATTERN.matcher(pattern); + final StringBuffer buffer = new StringBuffer(); + + buffer.append("(?i)"); + while (matcher.find()) + { + if (matcher.group(1) != null) + { + matcher.appendReplacement(buffer, ".*"); + } + else + { + matcher.appendReplacement(buffer, "\\\\Q" + matcher.group(0) + "\\\\E"); + } + } + + matcher.appendTail(buffer); + final String replaced = buffer.toString(); + + return text.matches(replaced); + } +} diff --git a/runelite-client/src/test/java/net/runelite/client/util/WildcardMatcherTest.java b/runelite-client/src/test/java/net/runelite/client/util/WildcardMatcherTest.java new file mode 100644 index 0000000000..53b48a0755 --- /dev/null +++ b/runelite-client/src/test/java/net/runelite/client/util/WildcardMatcherTest.java @@ -0,0 +1,43 @@ +/* + * 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.util; + +import static junit.framework.TestCase.assertTrue; +import static net.runelite.client.util.WildcardMatcher.matches; +import static org.junit.Assert.assertFalse; +import org.junit.Test; + +public class WildcardMatcherTest +{ + @Test + public void testMatches() + { + assertTrue(matches("rune*", "rune pouch")); + assertTrue(matches("rune*", "Rune pouch")); + assertFalse(matches("Abyssal whip", "Adamant dagger")); + assertTrue(matches("rune*", "Runeite Ore")); + assertTrue(matches("Abyssal whip", "Abyssal whip")); + } +} \ No newline at end of file diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSActorMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSActorMixin.java index 1a664fe05c..ef35c2c80f 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSActorMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSActorMixin.java @@ -35,12 +35,12 @@ import net.runelite.api.Point; import net.runelite.api.SpritePixels; import net.runelite.api.coords.LocalPoint; import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.AnimationChanged; import net.runelite.api.events.GraphicChanged; import net.runelite.api.mixins.FieldHook; import net.runelite.api.mixins.Inject; import net.runelite.api.mixins.Mixin; import net.runelite.api.mixins.Shadow; -import net.runelite.api.events.AnimationChanged; import static net.runelite.client.callback.Hooks.eventBus; import net.runelite.rs.api.RSActor; import net.runelite.rs.api.RSClient; @@ -48,6 +48,7 @@ import net.runelite.rs.api.RSCombatInfo1; import net.runelite.rs.api.RSCombatInfo2; import net.runelite.rs.api.RSCombatInfoList; import net.runelite.rs.api.RSCombatInfoListHolder; +import net.runelite.rs.api.RSModel; import net.runelite.rs.api.RSNode; @Mixin(RSActor.class) @@ -188,4 +189,16 @@ public abstract class RSActorMixin implements RSActor graphicChanged.setActor(this); eventBus.post(graphicChanged); } + + @Inject + @Override + public Polygon getConvexHull() + { + RSModel model = getModel(); + if (model == null) + { + return null; + } + return model.getConvexHull(getX(), getY(), getOrientation()); + } } diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSDecorativeObjectMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSDecorativeObjectMixin.java index f16b39f81b..f6a7f23064 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSDecorativeObjectMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSDecorativeObjectMixin.java @@ -28,12 +28,13 @@ import java.awt.Polygon; import java.awt.geom.Area; import net.runelite.api.Model; import net.runelite.api.Perspective; -import net.runelite.api.Renderable; import net.runelite.api.mixins.Inject; import net.runelite.api.mixins.Mixin; import net.runelite.api.mixins.Shadow; import net.runelite.rs.api.RSClient; import net.runelite.rs.api.RSDecorativeObject; +import net.runelite.rs.api.RSModel; +import net.runelite.rs.api.RSRenderable; @Mixin(RSDecorativeObject.class) public abstract class RSDecorativeObjectMixin implements RSDecorativeObject @@ -59,19 +60,19 @@ public abstract class RSDecorativeObjectMixin implements RSDecorativeObject } @Inject - private Model getModel() + private RSModel getModel() { - Renderable renderable = getRenderable(); + RSRenderable renderable = getRenderable(); if (renderable == null) { return null; } - Model model; + RSModel model; if (renderable instanceof Model) { - model = (Model) renderable; + model = (RSModel) renderable; } else { @@ -92,13 +93,13 @@ public abstract class RSDecorativeObjectMixin implements RSDecorativeObject @Override public Polygon getConvexHull() { - Model model = getModel(); + RSModel model = getModel(); if (model == null) { return null; } - return getConvexHull(model, getOrientation()); + return model.getConvexHull(getX(), getY(), getOrientation()); } } diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSGameObjectMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSGameObjectMixin.java index 0ff8ffaed1..ec8695b03d 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSGameObjectMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSGameObjectMixin.java @@ -26,15 +26,15 @@ package net.runelite.mixins; import java.awt.Polygon; import java.awt.geom.Area; -import net.runelite.api.Model; import net.runelite.api.Perspective; import net.runelite.api.Point; -import net.runelite.api.Renderable; import net.runelite.api.mixins.Inject; import net.runelite.api.mixins.Mixin; import net.runelite.api.mixins.Shadow; import net.runelite.rs.api.RSClient; import net.runelite.rs.api.RSGameObject; +import net.runelite.rs.api.RSModel; +import net.runelite.rs.api.RSRenderable; @Mixin(RSGameObject.class) public abstract class RSGameObjectMixin implements RSGameObject @@ -57,17 +57,17 @@ public abstract class RSGameObjectMixin implements RSGameObject } @Inject - private Model getModel() + private RSModel getModel() { - Renderable renderable = getRenderable(); + RSRenderable renderable = getRenderable(); if (renderable == null) { return null; } - if (renderable instanceof Model) + if (renderable instanceof RSModel) { - return (Model) renderable; + return (RSModel) renderable; } else { @@ -86,13 +86,13 @@ public abstract class RSGameObjectMixin implements RSGameObject @Override public Polygon getConvexHull() { - Model model = getModel(); + RSModel model = getModel(); if (model == null) { return null; } - return getConvexHull(model, getOrientation()); + return model.getConvexHull(getX(), getY(), getOrientation()); } } diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSModelMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSModelMixin.java index 10e2b089c0..003591e200 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSModelMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSModelMixin.java @@ -24,17 +24,26 @@ */ package net.runelite.mixins; +import java.awt.Polygon; import java.util.ArrayList; import java.util.List; -import net.runelite.api.model.Triangle; -import net.runelite.api.model.Vertex; +import net.runelite.api.Perspective; +import net.runelite.api.Point; import net.runelite.api.mixins.Inject; import net.runelite.api.mixins.Mixin; +import net.runelite.api.mixins.Shadow; +import net.runelite.api.model.Jarvis; +import net.runelite.api.model.Triangle; +import net.runelite.api.model.Vertex; +import net.runelite.rs.api.RSClient; import net.runelite.rs.api.RSModel; @Mixin(RSModel.class) public abstract class RSModelMixin implements RSModel { + @Shadow("clientInstance") + private static RSClient client; + @Override @Inject public List getVertices() @@ -85,4 +94,49 @@ public abstract class RSModelMixin implements RSModel return triangles; } + + @Override + @Inject + public Polygon getConvexHull(int localX, int localY, int orientation) + { + List vertices = getVertices(); + + // rotate vertices + for (int i = 0; i < vertices.size(); ++i) + { + Vertex v = vertices.get(i); + vertices.set(i, v.rotate(orientation)); + } + + List points = new ArrayList(); + + for (Vertex v : vertices) + { + // Compute canvas location of vertex + Point p = Perspective.worldToCanvas(client, + localX - v.getX(), + localY - v.getZ(), + -v.getY()); + if (p != null) + { + points.add(p); + } + } + + // Run Jarvis march algorithm + points = Jarvis.convexHull(points); + if (points == null) + { + return null; + } + + // Convert to a polygon + Polygon p = new Polygon(); + for (Point point : points) + { + p.addPoint(point.getX(), point.getY()); + } + + return p; + } } diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSNpcCompositionMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSNpcCompositionMixin.java new file mode 100644 index 0000000000..ce3decf2bc --- /dev/null +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSNpcCompositionMixin.java @@ -0,0 +1,46 @@ +/* + * 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.mixins; + +import net.runelite.api.events.NpcActionChanged; +import net.runelite.api.mixins.FieldHook; +import net.runelite.api.mixins.Inject; +import net.runelite.api.mixins.Mixin; +import static net.runelite.client.callback.Hooks.eventBus; +import net.runelite.rs.api.RSNPCComposition; + +@Mixin(RSNPCComposition.class) +public abstract class RSNpcCompositionMixin implements RSNPCComposition +{ + @FieldHook("actions") + @Inject + public void actionsHook(int idx) + { + NpcActionChanged npcActionChanged = new NpcActionChanged(); + npcActionChanged.setNpcComposition(this); + npcActionChanged.setIdx(idx); + eventBus.post(npcActionChanged); + } +} diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/TileObjectMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/TileObjectMixin.java index 539e389beb..3ee9d46268 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/TileObjectMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/TileObjectMixin.java @@ -26,9 +26,6 @@ package net.runelite.mixins; import java.awt.Graphics2D; import java.awt.Polygon; -import java.util.ArrayList; -import java.util.List; -import net.runelite.api.Model; import net.runelite.api.Perspective; import net.runelite.api.Point; import net.runelite.api.TileObject; @@ -38,8 +35,6 @@ import net.runelite.api.mixins.Inject; import net.runelite.api.mixins.Mixin; import net.runelite.api.mixins.Mixins; import net.runelite.api.mixins.Shadow; -import net.runelite.api.model.Jarvis; -import net.runelite.api.model.Vertex; import net.runelite.rs.api.RSClient; import net.runelite.rs.api.RSDecorativeObject; import net.runelite.rs.api.RSGameObject; @@ -115,52 +110,4 @@ public abstract class TileObjectMixin implements TileObject { return Perspective.worldToMiniMap(client, getX(), getY()); } - - @Override - @Inject - public Polygon getConvexHull(Model model, int orientation) - { - int localX = getX(); - int localY = getY(); - - List vertices = model.getVertices(); - - // rotate vertices - for (int i = 0; i < vertices.size(); ++i) - { - Vertex v = vertices.get(i); - vertices.set(i, v.rotate(orientation)); - } - - List points = new ArrayList(); - - for (Vertex v : vertices) - { - // Compute canvas location of vertex - Point p = Perspective.worldToCanvas(client, - localX - v.getX(), - localY - v.getZ(), - -v.getY()); - if (p != null) - { - points.add(p); - } - } - - // Run Jarvis march algorithm - points = Jarvis.convexHull(points); - if (points == null) - { - return null; - } - - // Convert to a polygon - Polygon p = new Polygon(); - for (Point point : points) - { - p.addPoint(point.getX(), point.getY()); - } - - return p; - } } diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSDecorativeObject.java b/runescape-api/src/main/java/net/runelite/rs/api/RSDecorativeObject.java index c2e539e7be..8263a676d0 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSDecorativeObject.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSDecorativeObject.java @@ -25,7 +25,6 @@ package net.runelite.rs.api; import net.runelite.api.DecorativeObject; -import net.runelite.api.Renderable; import net.runelite.mapping.Import; public interface RSDecorativeObject extends DecorativeObject @@ -44,7 +43,7 @@ public interface RSDecorativeObject extends DecorativeObject int getOrientation(); @Import("renderable1") - Renderable getRenderable(); + RSRenderable getRenderable(); void setPlane(int plane); } diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSGameObject.java b/runescape-api/src/main/java/net/runelite/rs/api/RSGameObject.java index 3595beda6b..f62b24e8ed 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSGameObject.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSGameObject.java @@ -25,13 +25,12 @@ package net.runelite.rs.api; import net.runelite.api.GameObject; -import net.runelite.api.Renderable; import net.runelite.mapping.Import; public interface RSGameObject extends GameObject { @Import("renderable") - Renderable getRenderable(); + RSRenderable getRenderable(); @Import("plane") int getPlane(); diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSModel.java b/runescape-api/src/main/java/net/runelite/rs/api/RSModel.java index a1273f0cdb..7e10c56061 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSModel.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSModel.java @@ -24,6 +24,7 @@ */ package net.runelite.rs.api; +import java.awt.Polygon; import net.runelite.api.Model; import net.runelite.mapping.Import; @@ -52,4 +53,13 @@ public interface RSModel extends RSRenderable, Model @Import("indices3") int[] getTrianglesZ(); + + /** + * Compute the convex hull of this model + * @param localX + * @param localY + * @param orientation + * @return + */ + Polygon getConvexHull(int localX, int localY, int orientation); } diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSRenderable.java b/runescape-api/src/main/java/net/runelite/rs/api/RSRenderable.java index 04b2e077f2..d9bff9eb5b 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSRenderable.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSRenderable.java @@ -24,7 +24,6 @@ */ package net.runelite.rs.api; -import net.runelite.api.Model; import net.runelite.api.Renderable; import net.runelite.mapping.Import; @@ -35,5 +34,5 @@ public interface RSRenderable extends RSNode, Renderable @Import("getModel") @Override - Model getModel(); + RSModel getModel(); }