diff --git a/runelite-api/src/main/java/net/runelite/api/ItemComposition.java b/runelite-api/src/main/java/net/runelite/api/ItemComposition.java index f4ee757ad6..b314cd7dac 100644 --- a/runelite-api/src/main/java/net/runelite/api/ItemComposition.java +++ b/runelite-api/src/main/java/net/runelite/api/ItemComposition.java @@ -81,4 +81,22 @@ public interface ItemComposition * @return true if stackable, false otherwise */ boolean isStackable(); + + /** + * Returns the menu actions the item has in a players' inventory + * + * @return the inventory menu actions + */ + String[] getInventoryActions(); + + /** + * Returns the menu action index of the shift-click action + * + * @return menu index of the shift-click action + */ + int getShiftClickActionIndex(); + + void setShiftClickActionIndex(int shiftclickActionIndex); + + void resetShiftClickActionIndex(); } diff --git a/runelite-api/src/main/java/net/runelite/api/events/PostItemComposition.java b/runelite-api/src/main/java/net/runelite/api/events/PostItemComposition.java new file mode 100644 index 0000000000..82d05fc53e --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/events/PostItemComposition.java @@ -0,0 +1,34 @@ +/* + * 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.ItemComposition; + +@Data +public class PostItemComposition +{ + private ItemComposition itemComposition; +} diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java index f46a3607bf..d1b3a34b05 100644 --- a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java +++ b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java @@ -207,16 +207,19 @@ public class WidgetID static class FixedViewport { + static final int INVENTORY_TAB = 51; static final int PRAYER_TAB = 53; } static class ResizableViewport { + static final int INVENTORY_TAB = 54; static final int PRAYER_TAB = 56; } static class ResizableViewportBottomLine { + static final int INVENTORY_TAB = 51; static final int PRAYER_TAB = 53; } diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java index 112b63ebc5..d40c6680b5 100644 --- a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java +++ b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java @@ -114,11 +114,14 @@ public enum WidgetInfo LOGIN_CLICK_TO_PLAY_SCREEN(WidgetID.LOGIN_CLICK_TO_PLAY_GROUP_ID, 0), FIXED_VIEWPORT(WidgetID.FIXED_VIEWPORT_GROUP_ID, WidgetID.Viewport.FIXED_VIEWPORT), + FIXED_VIEWPORT_INVENTORY_TAB(WidgetID.FIXED_VIEWPORT_GROUP_ID, WidgetID.FixedViewport.INVENTORY_TAB), FIXED_VIEWPORT_PRAYER_TAB(WidgetID.FIXED_VIEWPORT_GROUP_ID, WidgetID.FixedViewport.PRAYER_TAB), MINIMAP_WIDGET(WidgetID.RESIZABLE_VIEWPORT_OLD_SCHOOL_BOX_GROUP_ID, WidgetID.Viewport.MINIMAP_WIDGET), RESIZABLE_VIEWPORT_OLD_SCHOOL_BOX(WidgetID.RESIZABLE_VIEWPORT_OLD_SCHOOL_BOX_GROUP_ID, WidgetID.Viewport.RESIZABLE_VIEWPORT_OLD_SCHOOL_BOX), + RESIZABLE_VIEWPORT_INVENTORY_TAB(WidgetID.RESIZABLE_VIEWPORT_OLD_SCHOOL_BOX_GROUP_ID, WidgetID.ResizableViewport.INVENTORY_TAB), RESIZABLE_VIEWPORT_PRAYER_TAB(WidgetID.RESIZABLE_VIEWPORT_OLD_SCHOOL_BOX_GROUP_ID, WidgetID.ResizableViewport.PRAYER_TAB), RESIZABLE_VIEWPORT_BOTTOM_LINE(WidgetID.RESIZABLE_VIEWPORT_BOTTOM_LINE_GROUP_ID, WidgetID.Viewport.RESIZABLE_VIEWPORT_BOTTOM_LINE), + RESIZABLE_VIEWPORT_BOTTOM_LINE_INVENTORY_TAB(WidgetID.RESIZABLE_VIEWPORT_BOTTOM_LINE_GROUP_ID, WidgetID.ResizableViewportBottomLine.INVENTORY_TAB), RESIZABLE_VIEWPORT_BOTTOM_LINE_PRAYER_TAB(WidgetID.RESIZABLE_VIEWPORT_BOTTOM_LINE_GROUP_ID, WidgetID.ResizableViewportBottomLine.PRAYER_TAB), PRAYER_THICK_SKIN(WidgetID.PRAYER_GROUP_ID, WidgetID.Prayer.THICK_SKIN), diff --git a/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java b/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java index 9902b48439..459ac6e3d6 100644 --- a/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java +++ b/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java @@ -38,6 +38,7 @@ import java.awt.image.BufferedImage; import net.runelite.api.Actor; import net.runelite.api.ChatMessageType; import net.runelite.api.Client; +import net.runelite.api.ItemComposition; import net.runelite.api.KeyFocusListener; import net.runelite.api.MainBufferProvider; import net.runelite.api.MenuAction; @@ -54,6 +55,7 @@ import net.runelite.api.events.ChatMessage; import net.runelite.api.events.FocusChanged; import net.runelite.api.events.GameTick; import net.runelite.api.events.MenuOptionClicked; +import net.runelite.api.events.PostItemComposition; import net.runelite.api.events.ProjectileMoved; import net.runelite.api.events.SetMessage; import net.runelite.api.widgets.Widget; @@ -413,4 +415,11 @@ public class Hooks eventBus.post(death); } } + + public static void postItemComposition(ItemComposition itemComposition) + { + PostItemComposition event = new PostItemComposition(); + event.setItemComposition(itemComposition); + eventBus.post(event); + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperConfig.java index c80beeaee3..9d8436535a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperConfig.java @@ -37,6 +37,17 @@ public interface MenuEntrySwapperConfig extends Config { @ConfigItem( position = 0, + keyName = "shiftClickCustomization", + name = "Customizable shift-click", + description = "Allows customization of shift-clicks on items" + ) + default boolean shiftClickCustomization() + { + return true; + } + + @ConfigItem( + position = 1, keyName = "swapPickpocket", name = "Pickpocket", description = "Swap Talk-to with Pickpocket on NPC
Example: Man, Woman" @@ -47,7 +58,7 @@ public interface MenuEntrySwapperConfig extends Config } @ConfigItem( - position = 1, + position = 2, keyName = "swapBanker", name = "Bank", description = "Swap Talk-to with Bank on Bank NPC
Example: Banker" @@ -58,7 +69,7 @@ public interface MenuEntrySwapperConfig extends Config } @ConfigItem( - position = 2, + position = 3, keyName = "swapExchange", name = "Exchange", description = "Swap Talk-to with Exchange on NPC
Example: Grand Exchange Clerk, Tool Leprechaun, Void Knight" @@ -69,7 +80,7 @@ public interface MenuEntrySwapperConfig extends Config } @ConfigItem( - position = 3, + position = 4, keyName = "swapHarpoon", name = "Harpoon", description = "Swap Cage, Big Net with Harpoon on Fishing spot" @@ -80,7 +91,7 @@ public interface MenuEntrySwapperConfig extends Config } @ConfigItem( - position = 4, + position = 5, keyName = "swapTrade", name = "Trade", description = "Swap Talk-to with Trade on NPC
Example: Shop keeper, Shop assistant" @@ -91,7 +102,7 @@ public interface MenuEntrySwapperConfig extends Config } @ConfigItem( - position = 5, + position = 6, keyName = "swapTravel", name = "Travel", description = "Swap Talk-to with Travel, Take-boat, Pay-fare, Charter on NPC
Example: Squire, Monk of Entrana, Customs officer, Trader Crewmember" @@ -102,7 +113,7 @@ public interface MenuEntrySwapperConfig extends Config } @ConfigItem( - position = 6, + position = 7, keyName = "swapPay", name = "Pay", description = "Swap Talk-to with Pay on NPC
Example: Elstan, Heskel, Fayeth" @@ -113,7 +124,7 @@ public interface MenuEntrySwapperConfig extends Config } @ConfigItem( - position = 7, + position = 8, keyName = "swapHome", name = "Home", description = "Swap Enter with Home on Portal" @@ -124,7 +135,7 @@ public interface MenuEntrySwapperConfig extends Config } @ConfigItem( - position = 8, + position = 9, keyName = "swapLastDestination", name = "Last-destination (XXX)", description = "Swap Zanaris with Last-destination on Fairy ring" @@ -135,7 +146,7 @@ public interface MenuEntrySwapperConfig extends Config } @ConfigItem( - position = 9, + position = 10, keyName = "swapBoxTrap", name = "Reset", description = "Swap Check with Reset on box trap" @@ -146,7 +157,7 @@ public interface MenuEntrySwapperConfig extends Config } @ConfigItem( - position = 10, + position = 11, keyName = "swapCatacombEntrance", name = "Catacomb entrance", description = "Swap Read with Investigate on Catacombs of Kourend entrance" @@ -157,7 +168,7 @@ public interface MenuEntrySwapperConfig extends Config } @ConfigItem( - position = 11, + position = 12, keyName = "swapTeleportItem", name = "Teleport item", description = "Swap Wear, Wield with Rub, Teleport on teleport item
Example: Amulet of glory, Ardougne cloak, Chronicle" @@ -168,7 +179,7 @@ public interface MenuEntrySwapperConfig extends Config } @ConfigItem( - position = 12, + position = 13, keyName = "swapSilverSickle", name = "Silver sickle(b)", description = "Swap Wield with Cast Bloom on Silver sickle(b)" @@ -179,7 +190,7 @@ public interface MenuEntrySwapperConfig extends Config } @ConfigItem( - position = 13, + position = 14, keyName = "swapBones", name = "Bury", description = "Swap Bury with Use on Bones" diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPlugin.java index fc30693030..9701049eb6 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPlugin.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2018, Adam + * Copyright (c) 2018, Kamiel * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -26,14 +27,32 @@ package net.runelite.client.plugins.menuentryswapper; import com.google.common.eventbus.Subscribe; import com.google.inject.Provides; +import java.awt.event.MouseEvent; +import java.util.Collection; +import java.util.Collections; import javax.inject.Inject; +import lombok.Getter; +import lombok.Setter; import net.runelite.api.Client; import net.runelite.api.GameState; +import net.runelite.api.ItemComposition; import net.runelite.api.MenuEntry; +import net.runelite.api.events.ConfigChanged; import net.runelite.api.events.MenuEntryAdded; +import net.runelite.api.events.PostItemComposition; +import net.runelite.api.events.WidgetMenuOptionClicked; +import net.runelite.api.widgets.WidgetInfo; +import net.runelite.api.widgets.WidgetItem; import net.runelite.client.config.ConfigManager; +import net.runelite.client.game.ItemManager; +import net.runelite.client.input.KeyManager; +import net.runelite.client.input.MouseManager; +import net.runelite.client.menus.MenuManager; +import net.runelite.client.menus.WidgetMenuOption; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.util.Text; @PluginDescriptor( name = "Menu Entry Swapper", @@ -41,18 +60,163 @@ import net.runelite.client.plugins.PluginDescriptor; ) public class MenuEntrySwapperPlugin extends Plugin { + private static final String CONFIGURE = "Configure"; + private static final String SAVE = "Save"; + private static final String MENU_TARGET = "Shift-click"; + + private static final String CONFIG_GROUP = "shiftclick"; + private static final String ITEM_KEY_PREFIX = "item_"; + + private static final WidgetMenuOption FIXED_INVENTORY_TAB_CONFIGURE = new WidgetMenuOption(CONFIGURE, + MENU_TARGET, WidgetInfo.FIXED_VIEWPORT_INVENTORY_TAB); + + private static final WidgetMenuOption FIXED_INVENTORY_TAB_SAVE = new WidgetMenuOption(SAVE, + MENU_TARGET, WidgetInfo.FIXED_VIEWPORT_INVENTORY_TAB); + + private static final WidgetMenuOption RESIZABLE_INVENTORY_TAB_CONFIGURE = new WidgetMenuOption(CONFIGURE, + MENU_TARGET, WidgetInfo.RESIZABLE_VIEWPORT_INVENTORY_TAB); + + private static final WidgetMenuOption RESIZABLE_INVENTORY_TAB_SAVE = new WidgetMenuOption(SAVE, + MENU_TARGET, WidgetInfo.RESIZABLE_VIEWPORT_INVENTORY_TAB); + + private static final WidgetMenuOption RESIZABLE_BOTTOM_LINE_INVENTORY_TAB_CONFIGURE = new WidgetMenuOption(CONFIGURE, + MENU_TARGET, WidgetInfo.RESIZABLE_VIEWPORT_BOTTOM_LINE_INVENTORY_TAB); + + private static final WidgetMenuOption RESIZABLE_BOTTOM_LINE_INVENTORY_TAB_SAVE = new WidgetMenuOption(SAVE, + MENU_TARGET, WidgetInfo.RESIZABLE_VIEWPORT_BOTTOM_LINE_INVENTORY_TAB); + @Inject private Client client; @Inject private MenuEntrySwapperConfig config; + @Inject + private ShiftClickConfigurationOverlay overlay; + + @Inject + private ShiftClickInputListener inputListener; + + @Inject + private ConfigManager configManager; + + @Inject + private ItemManager itemManager; + + @Inject + private MouseManager mouseManager; + + @Inject + private KeyManager keyManager; + + @Inject + private MenuManager menuManager; + + @Getter + private boolean configuringShiftClick = false; + + @Setter + private boolean shiftModifier = false; + @Provides MenuEntrySwapperConfig provideConfig(ConfigManager configManager) { return configManager.getConfig(MenuEntrySwapperConfig.class); } + @Override + public Overlay getOverlay() + { + return overlay; + } + + @Override + public void startUp() + { + if (config.shiftClickCustomization()) + { + enableCustomization(); + } + } + + @Override + public void shutDown() + { + disableCustomization(); + } + + @Subscribe + public void onConfigChanged(ConfigChanged event) + { + if (event.getKey().equals("shiftClickCustomization")) + { + if (config.shiftClickCustomization()) + { + enableCustomization(); + } + else + { + disableCustomization(); + } + } + } + + private Integer getSwapConfig(int itemId) + { + String config = configManager.getConfiguration(CONFIG_GROUP, ITEM_KEY_PREFIX + itemId); + if (config == null || config.isEmpty()) + { + return null; + } + + return Integer.parseInt(config); + } + + private void setSwapConfig(int itemId, int index) + { + configManager.setConfiguration(CONFIG_GROUP, ITEM_KEY_PREFIX + itemId, index); + } + + private void unsetSwapConfig(int itemId) + { + configManager.unsetConfiguration(CONFIG_GROUP, ITEM_KEY_PREFIX + itemId); + } + + private void enableCustomization() + { + keyManager.registerKeyListener(inputListener); + refreshShiftClickCustomizationMenus(); + } + + private void disableCustomization() + { + keyManager.unregisterKeyListener(inputListener); + mouseManager.unregisterMouseListener(inputListener); + removeShiftClickCustomizationMenus(); + configuringShiftClick = false; + } + + @Subscribe + public void onWidgetMenuOptionClicked(WidgetMenuOptionClicked event) + { + if (event.getWidget() == WidgetInfo.FIXED_VIEWPORT_INVENTORY_TAB + || event.getWidget() == WidgetInfo.RESIZABLE_VIEWPORT_INVENTORY_TAB + || event.getWidget() == WidgetInfo.RESIZABLE_VIEWPORT_BOTTOM_LINE_INVENTORY_TAB) + { + configuringShiftClick = event.getMenuOption().equals(CONFIGURE); + refreshShiftClickCustomizationMenus(); + + if (configuringShiftClick) + { + mouseManager.registerMouseListener(inputListener); + } + else + { + mouseManager.unregisterMouseListener(inputListener); + } + } + } + @Subscribe public void onMenuEntryAdded(MenuEntryAdded event) { @@ -61,8 +225,9 @@ public class MenuEntrySwapperPlugin extends Plugin return; } - String option = event.getOption().toLowerCase(); - String target = event.getTarget(); + int itemId = event.getIdentifier(); + String option = Text.removeTags(event.getOption()).toLowerCase(); + String target = Text.removeTags(event.getTarget()).toLowerCase(); if (option.equals("talk-to")) { @@ -144,6 +309,31 @@ public class MenuEntrySwapperPlugin extends Plugin { swap("use", option, target, true); } + else if (config.shiftClickCustomization() && shiftModifier && !option.equals("use")) + { + Integer customOption = getSwapConfig(itemId); + + if (customOption != null && customOption == -1) + { + swap("use", option, target, true); + } + } + } + + @Subscribe + public void onPostItemComposition(PostItemComposition event) + { + ItemComposition itemComposition = event.getItemComposition(); + Integer option = getSwapConfig(itemComposition.getId()); + + if (option != null) + { + itemComposition.setShiftClickActionIndex(option); + + // Update our cached item composition too + ItemComposition ourItemComposition = itemManager.getItemComposition(itemComposition.getId()); + ourItemComposition.setShiftClickActionIndex(option); + } } private int searchIndex(MenuEntry[] entries, String option, String target, boolean strict) @@ -151,21 +341,25 @@ public class MenuEntrySwapperPlugin extends Plugin for (int i = entries.length - 1; i >= 0; i--) { MenuEntry entry = entries[i]; + String entryOption = Text.removeTags(entry.getOption()).toLowerCase(); + String entryTarget = Text.removeTags(entry.getTarget()).toLowerCase(); + if (strict) { - if (entry.getOption().toLowerCase().equals(option) && entry.getTarget().equals(target)) + if (entryOption.equals(option) && entryTarget.equals(target)) { return i; } } else { - if (entry.getOption().toLowerCase().contains(option) && entry.getTarget().equals(target)) + if (entryOption.contains(option.toLowerCase()) && entryTarget.equals(target)) { return i; } } } + return -1; } @@ -185,4 +379,81 @@ public class MenuEntrySwapperPlugin extends Plugin client.setMenuEntries(entries); } } + + public void cycleItemShiftClickAction(WidgetItem item, int mouseClick) + { + ItemComposition itemComposition = client.getItemDefinition(item.getId()); + + if (mouseClick == MouseEvent.BUTTON1) + { + String[] actions = itemComposition.getInventoryActions(); + int shiftClickActionIndex = itemComposition.getShiftClickActionIndex(); + int nextActionIndex = getNextActionIndex(actions, shiftClickActionIndex); + setSwapConfig(item.getId(), nextActionIndex); + itemComposition.setShiftClickActionIndex(nextActionIndex); + } + else if (mouseClick == MouseEvent.BUTTON3) + { + unsetSwapConfig(item.getId()); + itemComposition.resetShiftClickActionIndex(); + } + } + + private int getNextActionIndex(String[] actions, int currentIndex) + { + int size = actions.length; + int index = currentIndex + 1; + + for (int i = index; i < index + size; i++) + { + if (i == size) + { + // -1 is used for "Use" which is not in actions + return -1; + } + + index = i % size; + + if (actions[index] == null) + { + continue; + } + + break; + } + + return index; + } + + private void removeShiftClickCustomizationMenus() + { + menuManager.removeManagedCustomMenu(FIXED_INVENTORY_TAB_CONFIGURE); + menuManager.removeManagedCustomMenu(FIXED_INVENTORY_TAB_SAVE); + menuManager.removeManagedCustomMenu(RESIZABLE_BOTTOM_LINE_INVENTORY_TAB_CONFIGURE); + menuManager.removeManagedCustomMenu(RESIZABLE_BOTTOM_LINE_INVENTORY_TAB_SAVE); + menuManager.removeManagedCustomMenu(RESIZABLE_INVENTORY_TAB_CONFIGURE); + menuManager.removeManagedCustomMenu(RESIZABLE_INVENTORY_TAB_SAVE); + } + + private void refreshShiftClickCustomizationMenus() + { + removeShiftClickCustomizationMenus(); + if (configuringShiftClick) + { + menuManager.addManagedCustomMenu(FIXED_INVENTORY_TAB_SAVE); + menuManager.addManagedCustomMenu(RESIZABLE_BOTTOM_LINE_INVENTORY_TAB_SAVE); + menuManager.addManagedCustomMenu(RESIZABLE_INVENTORY_TAB_SAVE); + } + else + { + menuManager.addManagedCustomMenu(FIXED_INVENTORY_TAB_CONFIGURE); + menuManager.addManagedCustomMenu(RESIZABLE_BOTTOM_LINE_INVENTORY_TAB_CONFIGURE); + menuManager.addManagedCustomMenu(RESIZABLE_INVENTORY_TAB_CONFIGURE); + } + } + + Collection getInventoryItems() + { + return Collections.unmodifiableCollection(client.getWidget(WidgetInfo.INVENTORY).getWidgetItems()); + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/ShiftClickConfigurationOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/ShiftClickConfigurationOverlay.java new file mode 100644 index 0000000000..ec9db0e938 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/ShiftClickConfigurationOverlay.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2018, Kamiel + * 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.menuentryswapper; + +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Rectangle; +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.api.ItemComposition; +import net.runelite.api.widgets.WidgetInfo; +import net.runelite.api.widgets.WidgetItem; +import net.runelite.client.ui.FontManager; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayLayer; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.components.TextComponent; + +public class ShiftClickConfigurationOverlay extends Overlay +{ + @Inject + private Client client; + + @Inject + private MenuEntrySwapperPlugin plugin; + + @Inject + public ShiftClickConfigurationOverlay() + { + setPosition(OverlayPosition.DYNAMIC); + setLayer(OverlayLayer.ABOVE_WIDGETS); + } + + @Override + public Dimension render(Graphics2D graphics) + { + if (!plugin.isConfiguringShiftClick() || client.isMenuOpen() || client.getWidget(WidgetInfo.INVENTORY).isHidden()) + { + return null; + } + + Font font = FontManager.getRunescapeSmallFont(); + graphics.setFont(font); + + net.runelite.api.Point mouseCanvasPosition = client.getMouseCanvasPosition(); + Point mousePoint = new Point(mouseCanvasPosition.getX(), mouseCanvasPosition.getY()); + + for (WidgetItem item : plugin.getInventoryItems()) + { + final Rectangle bounds = item.getCanvasBounds(); + + if (!bounds.contains(mousePoint)) + { + continue; + } + + ItemComposition itemComposition = client.getItemDefinition(item.getId()); + String[] actions = itemComposition.getInventoryActions(); + int index = itemComposition.getShiftClickActionIndex(); + + if (index >= 0 && actions[index] == null) + { + continue; + } + + String action = index == -1 ? "Use" : actions[index]; + int textWidth = graphics.getFontMetrics().stringWidth(action); + int textLocationX = (int) (bounds.x + bounds.getWidth() / 2 - textWidth / 2); + int textLocationY = bounds.y + 28; + final TextComponent textComponent = new TextComponent(); + textComponent.setPosition(new Point(textLocationX, textLocationY)); + textComponent.setText(action); + textComponent.render(graphics); + } + + return null; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/ShiftClickInputListener.java b/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/ShiftClickInputListener.java new file mode 100644 index 0000000000..dddf78b696 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/ShiftClickInputListener.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2018, Kamiel + * 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.menuentryswapper; + +import java.awt.Point; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetInfo; +import net.runelite.api.widgets.WidgetItem; +import net.runelite.client.callback.ClientThread; +import net.runelite.client.input.KeyListener; +import net.runelite.client.input.MouseListener; + +public class ShiftClickInputListener extends MouseListener implements KeyListener +{ + @Inject + private ClientThread clientThread; + + @Inject + private Client client; + + @Inject + private MenuEntrySwapperPlugin plugin; + + @Inject + private MenuEntrySwapperConfig config; + + private WidgetItem getClickedItem(Point point) + { + for (WidgetItem item : plugin.getInventoryItems()) + { + if (item.getCanvasBounds().contains(point)) + { + return item; + } + } + + return null; + } + + @Override + public MouseEvent mouseClicked(MouseEvent event) + { + if (!config.shiftClickCustomization() || !isValidInventoryClick(event.getPoint())) + { + return event; + } + + final WidgetItem item = getClickedItem(event.getPoint()); + final int button = event.getButton(); + + if (item != null) + { + clientThread.invokeLater(() -> plugin.cycleItemShiftClickAction(item, button)); + event.consume(); + } + + return event; + } + + @Override + public MouseEvent mousePressed(MouseEvent event) + { + if (!config.shiftClickCustomization() || !isValidInventoryClick(event.getPoint())) + { + return event; + } + + event.consume(); + return event; + } + + private boolean isValidInventoryClick(Point point) + { + Widget widget = client.getWidget(WidgetInfo.INVENTORY).getParent(); + return !widget.isHidden() && !client.isMenuOpen() && widget.getBounds().contains(point); + } + + @Override + public void keyTyped(KeyEvent event) + { + + } + + @Override + public void keyPressed(KeyEvent event) + { + if (event.getKeyCode() == KeyEvent.VK_SHIFT) + { + plugin.setShiftModifier(true); + } + } + + @Override + public void keyReleased(KeyEvent event) + { + if (event.getKeyCode() == KeyEvent.VK_SHIFT) + { + plugin.setShiftModifier(false); + } + } +} diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSItemCompositionMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSItemCompositionMixin.java index 766fb30735..6d8a7a6d61 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSItemCompositionMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSItemCompositionMixin.java @@ -25,17 +25,52 @@ package net.runelite.mixins; +import net.runelite.api.mixins.Copy; import net.runelite.api.mixins.Inject; import net.runelite.api.mixins.Mixin; +import net.runelite.api.mixins.Replace; import net.runelite.rs.api.RSItemComposition; @Mixin(RSItemComposition.class) public abstract class RSItemCompositionMixin implements RSItemComposition { + private static final int DEFAULT_CUSTOM_SHIFT_CLICK_INDEX = -2; + + @Inject + private int shiftClickActionIndex = DEFAULT_CUSTOM_SHIFT_CLICK_INDEX; + + @Inject + RSItemCompositionMixin() + { + } + @Inject @Override public boolean isStackable() { return getIsStackable() != 0; } + + @Inject + @Override + public void setShiftClickActionIndex(int shiftClickActionIndex) + { + this.shiftClickActionIndex = shiftClickActionIndex; + } + + @Copy("getShiftClickActionIndex") + abstract int rs$getShiftClickActionIndex(); + + @Replace("getShiftClickActionIndex") + public int getShiftClickActionIndex() + { + return shiftClickActionIndex == DEFAULT_CUSTOM_SHIFT_CLICK_INDEX ? rs$getShiftClickActionIndex() : shiftClickActionIndex; + } + + @Inject + @Override + public void resetShiftClickActionIndex() + { + shiftClickActionIndex = DEFAULT_CUSTOM_SHIFT_CLICK_INDEX; + } } diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSItemComposition.java b/runescape-api/src/main/java/net/runelite/rs/api/RSItemComposition.java index 7a738f671f..8d48957b1a 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSItemComposition.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSItemComposition.java @@ -69,4 +69,12 @@ public interface RSItemComposition extends ItemComposition @Import("maleModel") int getMaleModel(); + + @Import("inventoryActions") + @Override + String[] getInventoryActions(); + + @Import("getShiftClickActionIndex") + @Override + int getShiftClickActionIndex(); }