From 53f97ed90ebd9eb4a75dd4095ee2d6c5978dfb29 Mon Sep 17 00:00:00 2001 From: RobinWeymans Date: Tue, 18 Apr 2017 02:22:51 +0200 Subject: [PATCH] Hiscore plugin: lookup menu item (#22) Hiscore plugin: lookup menu item --- .../main/java/net/runelite/api/Client.java | 20 +++ .../java/net/runelite/client/RuneLite.java | 9 ++ .../client/events/MenuOptionClicked.java | 67 ++++++++ .../events/PlayerMenuOptionClicked.java | 51 ++++++ .../events/PlayerMenuOptionsChanged.java | 44 ++++++ .../runelite/client/menus/MenuManager.java | 149 ++++++++++++++++++ .../client/plugins/hiscore/Hiscore.java | 20 ++- .../client/plugins/hiscore/HiscorePanel.java | 6 + .../net/runelite/inject/callbacks/Hooks.java | 33 ++++ .../main/java/net/runelite/rs/api/Client.java | 10 +- 10 files changed, 406 insertions(+), 3 deletions(-) create mode 100644 runelite-client/src/main/java/net/runelite/client/events/MenuOptionClicked.java create mode 100644 runelite-client/src/main/java/net/runelite/client/events/PlayerMenuOptionClicked.java create mode 100644 runelite-client/src/main/java/net/runelite/client/events/PlayerMenuOptionsChanged.java create mode 100644 runelite-client/src/main/java/net/runelite/client/menus/MenuManager.java diff --git a/runelite-api/src/main/java/net/runelite/api/Client.java b/runelite-api/src/main/java/net/runelite/api/Client.java index 286f86f1c6..ac4510b29d 100644 --- a/runelite-api/src/main/java/net/runelite/api/Client.java +++ b/runelite-api/src/main/java/net/runelite/api/Client.java @@ -204,6 +204,26 @@ public class Client return client.getWidgetPositionsY(); } + public String[] getPlayerOptions() + { + return client.getPlayerOptions(); + } + + public boolean[] getPlayerOptionsPriorities() + { + return client.getPlayerOptionsPriorities(); + } + + public int[] getPlayerMenuType() + { + return client.getPlayerMenuTypes(); + } + + public String[] getMenuOptions() + { + return client.getMenuOptions(); + } + public int getMapScale() { return client.getMapScale(); diff --git a/runelite-client/src/main/java/net/runelite/client/RuneLite.java b/runelite-client/src/main/java/net/runelite/client/RuneLite.java index af61e85739..143321254a 100644 --- a/runelite-client/src/main/java/net/runelite/client/RuneLite.java +++ b/runelite-client/src/main/java/net/runelite/client/RuneLite.java @@ -37,6 +37,7 @@ import javax.imageio.ImageIO; import joptsimple.OptionParser; import joptsimple.OptionSet; import net.runelite.api.Client; +import net.runelite.client.menus.MenuManager; import net.runelite.client.plugins.PluginManager; import net.runelite.client.ui.ClientUI; import net.runelite.client.ui.overlay.OverlayRenderer; @@ -59,6 +60,7 @@ public class RuneLite private ClientUI gui; private PluginManager pluginManager; + private MenuManager menuManager = new MenuManager(this); private OverlayRenderer renderer; private EventBus eventBus = new EventBus(this::eventExceptionHandler); private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(4); @@ -89,6 +91,8 @@ public class RuneLite { gui = new ClientUI(); + eventBus.register(menuManager); + if (SystemTray.isSupported()) { SystemTray systemTray = SystemTray.getSystemTray(); @@ -134,6 +138,11 @@ public class RuneLite return pluginManager; } + public MenuManager getMenuManager() + { + return menuManager; + } + public OverlayRenderer getRenderer() { return renderer; diff --git a/runelite-client/src/main/java/net/runelite/client/events/MenuOptionClicked.java b/runelite-client/src/main/java/net/runelite/client/events/MenuOptionClicked.java new file mode 100644 index 0000000000..a25750a1b9 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/events/MenuOptionClicked.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2017, Robin + * 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.events; + +/** + * + * @author robin + */ +public class MenuOptionClicked +{ + private String menuOption; + private String menuTarget; + private int menuAction; + + public String getMenuOption() + { + return menuOption; + } + + public void setMenuOption(String menuOption) + { + this.menuOption = menuOption; + } + + public String getMenuTarget() + { + return menuTarget; + } + + public void setMenuTarget(String menuTarget) + { + this.menuTarget = menuTarget; + } + + public int getMenuAction() + { + return menuAction; + } + + public void setMenuAction(int menuAction) + { + this.menuAction = menuAction; + } + +} diff --git a/runelite-client/src/main/java/net/runelite/client/events/PlayerMenuOptionClicked.java b/runelite-client/src/main/java/net/runelite/client/events/PlayerMenuOptionClicked.java new file mode 100644 index 0000000000..3192000689 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/events/PlayerMenuOptionClicked.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2016-2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.events; + +public class PlayerMenuOptionClicked +{ + private String menuOption; + private String menuTarget; + + public String getMenuOption() + { + return menuOption; + } + + public void setMenuOption(String menuOption) + { + this.menuOption = menuOption; + } + + public String getMenuTarget() + { + return menuTarget; + } + + public void setMenuTarget(String menuTarget) + { + this.menuTarget = menuTarget; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/events/PlayerMenuOptionsChanged.java b/runelite-client/src/main/java/net/runelite/client/events/PlayerMenuOptionsChanged.java new file mode 100644 index 0000000000..9a97b8b3f0 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/events/PlayerMenuOptionsChanged.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2017, Robin + * 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.events; + +public class PlayerMenuOptionsChanged +{ + /** + * Index in playerOptions which changed + */ + private int index; + + public int getIndex() + { + return index; + } + + public void setIndex(int index) + { + this.index = index; + } + +} 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 new file mode 100644 index 0000000000..7eb5a2c7be --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/menus/MenuManager.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2017, Robin + * 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.menus; + +import com.google.common.base.Preconditions; +import com.google.common.eventbus.Subscribe; +import java.util.HashMap; +import java.util.Map; +import net.runelite.api.Client; +import net.runelite.client.RuneLite; +import net.runelite.client.events.MenuOptionClicked; +import net.runelite.client.events.PlayerMenuOptionClicked; +import net.runelite.client.events.PlayerMenuOptionsChanged; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MenuManager +{ + private static final Logger logger = LoggerFactory.getLogger(MenuManager.class); + + /* 1007 is the highest number the rs client uses for actions. There is no way to see which ones are used, + * so im starting from 1500. Its just a number well over their maximum, so if a new action gets added, chances are little + * it interferes with the action the MenuManager uses. + */ + private static final int MENU_ACTION = 1500; + + /* + * The index needs to be between 4 and 7, + */ + private static final int IDX_LOWER = 4; + private static final int IDX_UPPER = 8; + + private final RuneLite runeLite; + + //Maps the indexes that are being used to the menu option. + private final Map playerMenuIndexMap = new HashMap<>(); + + public MenuManager(RuneLite runeLite) + { + this.runeLite = runeLite; + } + + public void addPlayerMenuItem(String menuText) + { + Preconditions.checkNotNull(menuText); + + int playerMenuIndex = findEmptyPlayerMenuIndex(); + if (playerMenuIndex == IDX_UPPER) + { + return; // no more slots + } + + addPlayerMenuItem(playerMenuIndex, menuText); + } + + @Subscribe + public void onPlayerMenuOptionsChanged(PlayerMenuOptionsChanged event) + { + int idx = event.getIndex(); + + String menuText = playerMenuIndexMap.get(idx); + if (menuText == null) + { + return; // not our menu + } + + // find new index for this option + int newIdx = findEmptyPlayerMenuIndex(); + if (newIdx == IDX_UPPER) + { + logger.debug("Client has updated player menu index {} where option {} was, and there are no more free slots available", idx, menuText); + return; + } + + logger.debug("Client has updated player menu index {} where option {} was, moving to index {}", idx, menuText, newIdx); + + playerMenuIndexMap.remove(idx); + addPlayerMenuItem(newIdx, menuText); + } + + @Subscribe + public void onMenuOptionClicked(MenuOptionClicked event) + { + if (event.getMenuAction() != MENU_ACTION) + { + return; // not a player menu + } + + String target = event.getMenuTarget(); + String username = target.split("[<>]")[2]; // username (level-42) + + PlayerMenuOptionClicked playerMenuOptionClicked = new PlayerMenuOptionClicked(); + playerMenuOptionClicked.setMenuOption(event.getMenuOption()); + playerMenuOptionClicked.setMenuTarget(username); + + runeLite.getEventBus().post(playerMenuOptionClicked); + } + + private void addPlayerMenuItem(int playerOptionIndex, String menuText) + { + Client client = RuneLite.getClient(); + + client.getPlayerOptions()[playerOptionIndex] = menuText; + client.getPlayerOptionsPriorities()[playerOptionIndex] = true; + client.getPlayerMenuType()[playerOptionIndex] = MENU_ACTION; + + playerMenuIndexMap.put(playerOptionIndex, menuText); + } + + /** + * Find the next empty player menu slot index + * + * @return + */ + private int findEmptyPlayerMenuIndex() + { + int index = IDX_LOWER; + + String[] playerOptions = RuneLite.getClient().getPlayerOptions(); + while (index < IDX_UPPER && playerOptions[index] != null) + { + index++; + } + + return index; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/Hiscore.java b/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/Hiscore.java index b3a6ff3067..340f02ea37 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/Hiscore.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/Hiscore.java @@ -24,12 +24,15 @@ */ package net.runelite.client.plugins.hiscore; +import com.google.common.eventbus.Subscribe; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.IOException; +import java.util.concurrent.ScheduledExecutorService; import javax.imageio.ImageIO; import javax.swing.ImageIcon; import net.runelite.client.RuneLite; +import net.runelite.client.events.PlayerMenuOptionClicked; import net.runelite.client.plugins.Plugin; import net.runelite.client.ui.ClientUI; import net.runelite.client.ui.NavigationButton; @@ -41,10 +44,13 @@ public class Hiscore extends Plugin implements ActionListener { private static final Logger logger = LoggerFactory.getLogger(Hiscore.class); + private static final String LOOKUP = "Lookup"; + private final NavigationButton navButton = new NavigationButton("Hiscore"); private final HiscorePanel hiscorePanel = new HiscorePanel(); - private final ClientUI ui = RuneLite.getRunelite().getGui(); + private final RuneLite runeLite = RuneLite.getRunelite(); + private final ClientUI ui = runeLite.getGui(); public Hiscore() { @@ -61,6 +67,8 @@ public class Hiscore extends Plugin implements ActionListener } ui.getNavigationPanel().addNavigation(navButton); + + runeLite.getMenuManager().addPlayerMenuItem(LOOKUP); } @Override @@ -76,4 +84,14 @@ public class Hiscore extends Plugin implements ActionListener ui.expand(); } + @Subscribe + public void onLookupMenuClicked(PlayerMenuOptionClicked event) + { + if (event.getMenuOption().equals(LOOKUP)) + { + ScheduledExecutorService executor = runeLite.getExecutor(); + executor.execute(() -> hiscorePanel.lookup(event.getMenuTarget())); + } + } + } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/HiscorePanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/HiscorePanel.java index 0119d2d807..507f793b3f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/HiscorePanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/hiscore/HiscorePanel.java @@ -150,6 +150,12 @@ public class HiscorePanel extends PluginPanel return iconLevel; } + public void lookup(String username) + { + input.setText(username); + lookup(); + } + private void lookup() { String lookup = input.getText(); diff --git a/runelite-client/src/main/java/net/runelite/inject/callbacks/Hooks.java b/runelite-client/src/main/java/net/runelite/inject/callbacks/Hooks.java index 9670c1a539..f27bd2129c 100644 --- a/runelite-client/src/main/java/net/runelite/inject/callbacks/Hooks.java +++ b/runelite-client/src/main/java/net/runelite/inject/callbacks/Hooks.java @@ -27,6 +27,8 @@ package net.runelite.inject.callbacks; import net.runelite.client.RuneLite; import net.runelite.client.events.ExperienceChanged; import net.runelite.client.events.MapRegionChanged; +import net.runelite.client.events.MenuOptionClicked; +import net.runelite.client.events.PlayerMenuOptionsChanged; import net.runelite.client.events.AnimationChanged; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -61,6 +63,13 @@ public class Hooks runelite.getEventBus().post(regionChanged); break; } + case "playerMenuOptionsChanged": + { + PlayerMenuOptionsChanged optionsChanged = new PlayerMenuOptionsChanged(); + optionsChanged.setIndex(idx); + runelite.getEventBus().post(optionsChanged); + break; + } case "animationChanged": { AnimationChanged animationChange = new AnimationChanged(); @@ -74,8 +83,32 @@ public class Hooks } if (object != null) + { logger.trace("Event {} (idx {}) triggered on {}", name, idx, object); + } else + { logger.trace("Event {} (idx {}) triggered", name, idx); + } + } + + public static void menuActionHook(int var0, int var1, int menuAction, int var3, String menuOption, String menuTarget, int var6, int var7) + { + /* Along the way, the RuneScape client may change a menuAction by incrementing it with 2000. + * I have no idea why, but it does. Their code contains the same conditional statement. + */ + if (menuAction >= 2000) + { + menuAction -= 2000; + } + + logger.debug("Menu action clicked: {} ({}) on {}", menuOption, menuAction, menuTarget.isEmpty() ? "" : menuTarget); + + MenuOptionClicked playerMenuOptionClicked = new MenuOptionClicked(); + playerMenuOptionClicked.setMenuOption(menuOption); + playerMenuOptionClicked.setMenuTarget(menuTarget); + playerMenuOptionClicked.setMenuAction(menuAction); + + runelite.getEventBus().post(playerMenuOptionClicked); } } diff --git a/runescape-api/src/main/java/net/runelite/rs/api/Client.java b/runescape-api/src/main/java/net/runelite/rs/api/Client.java index b4a550801a..477743764c 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/Client.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/Client.java @@ -125,8 +125,14 @@ public interface Client extends GameEngine @Import(value = "username", setter = true) void setUsername(String username); - @Import("menuActions") - String[] getMenuActions(); + @Import("playerOptions") + String[] getPlayerOptions(); + + @Import("playerOptionsPriorities") + boolean[] getPlayerOptionsPriorities(); + + @Import("playerMenuTypes") + int[] getPlayerMenuTypes(); @Import("menuTargets") String[] getMenuTargets();