From af7e76502b24ec2b81bb6db025304c0c9ece1166 Mon Sep 17 00:00:00 2001 From: Tomas Slusny Date: Sun, 20 May 2018 00:29:40 +0200 Subject: [PATCH 1/2] Add new toBufferedOutline method to SpritePixels Add method that returns item image with outline to SpritePixels. Signed-off-by: Tomas Slusny --- .../java/net/runelite/api/SpritePixels.java | 16 +++++ .../runelite/mixins/RSSpritePixelsMixin.java | 65 +++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/runelite-api/src/main/java/net/runelite/api/SpritePixels.java b/runelite-api/src/main/java/net/runelite/api/SpritePixels.java index 9eec87c833..4ebdbdba1c 100644 --- a/runelite-api/src/main/java/net/runelite/api/SpritePixels.java +++ b/runelite-api/src/main/java/net/runelite/api/SpritePixels.java @@ -24,6 +24,7 @@ */ package net.runelite.api; +import java.awt.Color; import java.awt.image.BufferedImage; /** @@ -76,4 +77,19 @@ public interface SpritePixels * @throws IllegalArgumentException if the width or height do not match */ void toBufferedImage(BufferedImage img) throws IllegalArgumentException; + + /** + * Writes the contents of the SpritePixels with chosen outline to the BufferedImage + * + * @param color target color + */ + BufferedImage toBufferedOutline(Color color); + + /** + * Writes the contents of the SpritePixels with chosen outline to the BufferedImage + * + * @param img target image + * @param color target color + */ + void toBufferedOutline(BufferedImage img, int color); } diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSSpritePixelsMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSSpritePixelsMixin.java index 7511df70d7..b351a3d768 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSSpritePixelsMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSSpritePixelsMixin.java @@ -24,6 +24,7 @@ */ package net.runelite.mixins; +import java.awt.Color; import java.awt.image.BufferedImage; import net.runelite.api.mixins.Inject; import net.runelite.api.mixins.Mixin; @@ -68,4 +69,68 @@ public abstract class RSSpritePixelsMixin implements RSSpritePixels img.setRGB(0, 0, width, height, transPixels, 0, width); } + + @Inject + @Override + public BufferedImage toBufferedOutline(Color color) + { + BufferedImage img = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB); + + toBufferedOutline(img, color.getRGB()); + + return img; + } + + @Inject + @Override + public void toBufferedOutline(BufferedImage img, int color) + { + int width = getWidth(); + int height = getHeight(); + + if (img.getWidth() != width || img.getHeight() != height) + { + throw new IllegalArgumentException("Image bounds do not match SpritePixels"); + } + + int[] pixels = getPixels(); + int[] newPixels = new int[width * height]; + int pixelIndex = 0; + + for (int y = 0; y < height; ++y) + { + for (int x = 0; x < width; ++x) + { + int pixel = pixels[pixelIndex]; + if (pixel == 16777215 || pixel == 0) + { + // W + if (x > 0 && pixels[pixelIndex - 1] != 0) + { + pixel = color; + } + // N + else if (y > 0 && pixels[pixelIndex - width] != 0) + { + pixel = color; + } + // E + else if (x < width - 1 && pixels[pixelIndex + 1] != 0) + { + pixel = color; + } + // S + else if (y < height - 1 && pixels[pixelIndex + width] != 0) + { + pixel = color; + } + newPixels[pixelIndex] = pixel; + } + + pixelIndex++; + } + } + + img.setRGB(0, 0, width, height, newPixels, 0, width); + } } From 49c3f538efff24ebfc7c1a325d8577d9efce1189 Mon Sep 17 00:00:00 2001 From: Tomas Slusny Date: Sun, 20 May 2018 00:31:19 +0200 Subject: [PATCH 2/2] Add inventory tagging plugin Add plugin that will tag items in inventory with 4 different categories and draw outlines for tagged items. Signed-off-by: Tomas Slusny --- .../inventorytags/InventoryTagsConfig.java | 84 +++++ .../inventorytags/InventoryTagsOverlay.java | 118 +++++++ .../inventorytags/InventoryTagsPlugin.java | 314 ++++++++++++++++++ 3 files changed, 516 insertions(+) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/inventorytags/InventoryTagsConfig.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/inventorytags/InventoryTagsOverlay.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/inventorytags/InventoryTagsPlugin.java diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventorytags/InventoryTagsConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventorytags/InventoryTagsConfig.java new file mode 100644 index 0000000000..aa436769eb --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventorytags/InventoryTagsConfig.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2018 kulers + * 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.inventorytags; + +import java.awt.Color; +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup( + keyName = "inventorytags", + name = "Inventory Tags", + description = "Configuration for the Inventory Item Tagging plugin" +) +public interface InventoryTagsConfig extends Config +{ + String GROUP = "inventorytags"; + + @ConfigItem( + position = 0, + keyName = "groupColor1", + name = "Group 1 Color", + description = "Color of the Tag" + ) + default Color getGroup1Color() + { + return new Color(255, 0, 0); + } + + @ConfigItem( + position = 1, + keyName = "groupColor2", + name = "Group 2 Color", + description = "Color of the Tag" + ) + default Color getGroup2Color() + { + return new Color(0, 255, 0); + } + + @ConfigItem( + position = 2, + keyName = "groupColor3", + name = "Group 3 Color", + description = "Color of the Tag" + ) + default Color getGroup3Color() + { + return new Color(0, 0, 255); + } + + @ConfigItem( + position = 3, + keyName = "groupColor4", + name = "Group 4 Color", + description = "Color of the Tag" + ) + default Color getGroup4Color() + { + return new Color(255, 0, 255); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventorytags/InventoryTagsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventorytags/InventoryTagsOverlay.java new file mode 100644 index 0000000000..d9e5c0301d --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventorytags/InventoryTagsOverlay.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2018 kulers + * 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.inventorytags; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.util.HashMap; +import java.util.Map; +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.api.Query; +import net.runelite.api.SpritePixels; +import net.runelite.api.queries.InventoryWidgetItemQuery; +import net.runelite.api.widgets.WidgetItem; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayLayer; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.OverlayPriority; +import net.runelite.client.util.QueryRunner; + +public class InventoryTagsOverlay extends Overlay +{ + private final Map storedOutlines = new HashMap<>(); + private final QueryRunner queryRunner; + private final Client client; + private final InventoryTagsPlugin plugin; + + @Inject + private InventoryTagsOverlay(QueryRunner queryRunner, Client client, InventoryTagsPlugin plugin) + { + setPosition(OverlayPosition.DYNAMIC); + setPriority(OverlayPriority.LOW); + setLayer(OverlayLayer.ABOVE_WIDGETS); + this.queryRunner = queryRunner; + this.client = client; + this.plugin = plugin; + } + + @Override + public Dimension render(Graphics2D graphics) + { + if (!plugin.isHasTaggedItems()) + { + return null; + } + + // Now query the inventory for the tagged item ids + final Query query = new InventoryWidgetItemQuery(); + final WidgetItem[] widgetItems = queryRunner.runQuery(query); + + // Iterate through all found items and draw the outlines + for (final WidgetItem item : widgetItems) + { + final String group = plugin.getTag(item.getId()); + + if (group != null) + { + final Color color = plugin.getGroupNameColor(group); + if (color != null) + { + final BufferedImage outline = getOutline(item.getId(), color); + graphics.drawImage(outline, item.getCanvasLocation().getX() + 1, item.getCanvasLocation().getY() + 1, null); + } + } + } + + return null; + } + + void clearStoredOutlines() + { + storedOutlines.clear(); + } + + private BufferedImage getOutline(final int id, final Color color) + { + final String key = getStringGeneratedId(id, color); + BufferedImage stored = storedOutlines.get(key); + if (stored != null) + { + return stored; + } + + final SpritePixels itemSprite = client.createItemSprite(id, 1, 1, 0, 0, true, 710); + final BufferedImage generatedPicture = itemSprite.toBufferedOutline(color); + storedOutlines.put(key, generatedPicture); + return generatedPicture; + } + + private String getStringGeneratedId(final int id, final Color color) + { + return id + "." + color.getRGB(); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventorytags/InventoryTagsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventorytags/InventoryTagsPlugin.java new file mode 100644 index 0000000000..0a50ec633c --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventorytags/InventoryTagsPlugin.java @@ -0,0 +1,314 @@ +/* + * Copyright (c) 2018 kulers + * 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.inventorytags; + +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableList; +import com.google.common.eventbus.Subscribe; +import com.google.inject.Provides; +import java.awt.Color; +import java.util.List; +import javax.inject.Inject; +import lombok.AccessLevel; +import lombok.Getter; +import net.runelite.api.Client; +import net.runelite.api.InventoryID; +import net.runelite.api.Item; +import net.runelite.api.ItemContainer; +import net.runelite.api.MenuAction; +import net.runelite.api.MenuEntry; +import net.runelite.api.events.ConfigChanged; +import net.runelite.api.events.ItemContainerChanged; +import net.runelite.api.events.MenuOpened; +import net.runelite.api.events.MenuOptionClicked; +import net.runelite.api.widgets.WidgetInfo; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.overlay.OverlayManager; +import net.runelite.client.util.Text; +import org.apache.commons.lang3.ArrayUtils; + +@PluginDescriptor( + name = "Inventory Tags", + enabledByDefault = false +) +public class InventoryTagsPlugin extends Plugin +{ + private static final String ITEM_KEY_PREFIX = "item_"; + + private static final String SETNAME_GROUP_1 = "Group 1"; + private static final String SETNAME_GROUP_2 = "Group 2"; + private static final String SETNAME_GROUP_3 = "Group 3"; + private static final String SETNAME_GROUP_4 = "Group 4"; + + private static final String CONFIGURE = "Configure"; + private static final String SAVE = "Save"; + private static final String MENU_TARGET = "Inventory Tags"; + private static final String MENU_SET = "Mark"; + private static final String MENU_REMOVE = "Remove"; + + private static final List GROUPS = ImmutableList.of(SETNAME_GROUP_4, SETNAME_GROUP_3, SETNAME_GROUP_2, SETNAME_GROUP_1); + + @Inject + private Client client; + + @Inject + private ConfigManager configManager; + + @Inject + private InventoryTagsConfig config; + + @Inject + private InventoryTagsOverlay overlay; + + @Inject + private OverlayManager overlayManager; + + @Getter(AccessLevel.PACKAGE) + private boolean hasTaggedItems; + + private boolean editorMode = false; + + @Provides + InventoryTagsConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(InventoryTagsConfig.class); + } + + String getTag(int itemId) + { + String tag = configManager.getConfiguration(InventoryTagsConfig.GROUP, ITEM_KEY_PREFIX + itemId); + if (tag == null || tag.isEmpty()) + { + return null; + } + + return tag; + } + + void setTag(int itemId, String tag) + { + configManager.setConfiguration(InventoryTagsConfig.GROUP, ITEM_KEY_PREFIX + itemId, tag); + } + + void unsetTag(int itemId) + { + configManager.unsetConfiguration(InventoryTagsConfig.GROUP, ITEM_KEY_PREFIX + itemId); + } + + @Override + protected void startUp() throws Exception + { + overlayManager.add(overlay); + } + + @Override + protected void shutDown() throws Exception + { + overlayManager.remove(overlay); + hasTaggedItems = editorMode = false; + } + + @Subscribe + public void onConfigChanged(ConfigChanged configChanged) + { + if (!configChanged.getGroup().equals("inventorytags")) + { + return; + } + + overlay.clearStoredOutlines(); + } + + @Subscribe + public void onClickMenu(final MenuOptionClicked event) + { + if (event.getMenuAction() != MenuAction.RUNELITE) + { + return; + } + + if (MENU_TARGET.equals(event.getMenuTarget())) + { + switch (event.getMenuOption()) + { + case CONFIGURE: + editorMode = true; + break; + case SAVE: + editorMode = false; + break; + } + + return; + } + + final String selectedMenu = Text.removeTags(event.getMenuTarget()); + + if (event.getMenuOption().equals(MENU_SET)) + { + setTag(event.getId(), selectedMenu); + + hasTaggedItems = true; + } + else if (event.getMenuOption().equals(MENU_REMOVE)) + { + unsetTag(event.getId()); + + checkForTags(client.getItemContainer(InventoryID.INVENTORY)); + } + } + + @Subscribe + public void onMenuOpened(final MenuOpened event) + { + final MenuEntry firstEntry = event.getFirstEntry(); + + if (firstEntry == null) + { + return; + } + + final int widgetId = firstEntry.getParam1(); + + // Inventory button menu + if (widgetId == WidgetInfo.FIXED_VIEWPORT_INVENTORY_TAB.getId() || + widgetId == WidgetInfo.RESIZABLE_VIEWPORT_INVENTORY_TAB.getId() || + widgetId == WidgetInfo.RESIZABLE_VIEWPORT_BOTTOM_LINE_INVENTORY_TAB.getId()) + { + final int itemId = firstEntry.getIdentifier(); + + if (itemId == -1) + { + return; + } + + MenuEntry[] entries = event.getMenuEntries(); + + final MenuEntry configureOption = new MenuEntry(); + configureOption.setOption(editorMode ? SAVE : CONFIGURE); + configureOption.setTarget(MENU_TARGET); + configureOption.setIdentifier(itemId); + configureOption.setParam1(widgetId); + configureOption.setType(MenuAction.RUNELITE.getId()); + entries = ArrayUtils.addAll(entries, configureOption); + + client.setMenuEntries(entries); + } + + // Inventory item menu + if (widgetId == WidgetInfo.INVENTORY.getId() && editorMode) + { + int itemId = firstEntry.getIdentifier(); + + if (itemId == -1) + { + return; + } + + MenuEntry[] menuList = new MenuEntry[GROUPS.size()]; + int num = 0; + + for (final String groupName : GROUPS) + { + final String group = getTag(itemId); + final MenuEntry newMenu = new MenuEntry(); + final Color color = getGroupNameColor(groupName); + newMenu.setOption(group != null && groupName.equals(group) ? MENU_REMOVE : MENU_SET); + newMenu.setTarget("" + groupName); + newMenu.setIdentifier(itemId); + newMenu.setParam1(widgetId); + newMenu.setType(MenuAction.RUNELITE.getId()); + menuList[num++] = newMenu; + } + + client.setMenuEntries(menuList); + } + } + + @Subscribe + public void onItemContainerChanged(ItemContainerChanged itemContainerChanged) + { + ItemContainer itemContainer = itemContainerChanged.getItemContainer(); + if (itemContainer == client.getItemContainer(InventoryID.INVENTORY)) + { + checkForTags(itemContainer); + } + } + + private void checkForTags(ItemContainer itemContainer) + { + hasTaggedItems = false; + + if (itemContainer == null) + { + return; + } + + Item[] items = itemContainer.getItems(); + if (items != null) + { + for (Item item : items) + { + if (item == null) + { + continue; + } + + String tag = getTag(item.getId()); + if (tag == null) + { + continue; + } + + hasTaggedItems = true; + return; + } + } + } + + private String getHexColor(final Color color) + { + return String.format("%02x%02x%02x", color.getRed(), color.getGreen(), color.getBlue()); + } + + Color getGroupNameColor(final String name) + { + switch (name) + { + case SETNAME_GROUP_1: + return config.getGroup1Color(); + case SETNAME_GROUP_2: + return config.getGroup2Color(); + case SETNAME_GROUP_3: + return config.getGroup3Color(); + case SETNAME_GROUP_4: + return config.getGroup4Color(); + } + + return null; + } +}