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 6c674c1d14..4e4f9fb847 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 @@ -27,28 +27,34 @@ package net.runelite.client.menus; import com.google.common.base.Preconditions; import com.google.common.collect.HashMultimap; +import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; + import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.Pattern; import javax.inject.Inject; import javax.inject.Singleton; -import lombok.AllArgsConstructor; + +import com.google.common.collect.Sets; import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; import net.runelite.api.MenuAction; -import static net.runelite.api.MenuAction.GAME_OBJECT_FIRST_OPTION; -import static net.runelite.api.MenuAction.WIDGET_DEFAULT; +import static net.runelite.api.MenuAction.MENU_ACTION_DEPRIORITIZE_OFFSET; import net.runelite.api.MenuEntry; import net.runelite.api.NPCDefinition; -import net.runelite.api.events.ClientTick; +import net.runelite.api.events.BeforeRender; import net.runelite.api.events.MenuEntryAdded; +import net.runelite.api.events.MenuOpened; import net.runelite.api.events.MenuOptionClicked; import net.runelite.api.events.NpcActionChanged; import net.runelite.api.events.PlayerMenuOptionClicked; @@ -70,21 +76,9 @@ public class MenuManager private static final int IDX_UPPER = 8; static final Pattern LEVEL_PATTERN = Pattern.compile("\\(level-[0-9]*\\)"); - private static MenuEntry CANCEL() - { - MenuEntry cancel = new MenuEntry(); - cancel.setOption("Cancel"); - cancel.setTarget(""); - cancel.setIdentifier(0); - cancel.setType(MenuAction.CANCEL.getId()); - cancel.setParam0(0); - cancel.setParam1(0); - - return cancel; - } - private final Client client; private final EventBus eventBus; + private final Prioritizer prioritizer; //Maps the indexes that are being used to the menu option. private final Map playerMenuIndexMap = new HashMap<>(); @@ -95,15 +89,21 @@ public class MenuManager private final Set priorityEntries = new HashSet<>(); private final Set currentPriorityEntries = new HashSet<>(); private final Set hiddenEntries = new HashSet<>(); - + private final Set currentHiddenEntries = new HashSet<>(); private final Map swaps = new HashMap<>(); - private EntryTypeMapping originalType; + private final Map currentSwaps = new HashMap<>(); + + private final LinkedHashSet entries = Sets.newLinkedHashSet(); + + private MenuEntry leftClickEntry = null; + private int leftClickType = -1; @Inject private MenuManager(Client client, EventBus eventBus) { this.client = client; this.eventBus = eventBus; + this.prioritizer = new Prioritizer(); } /** @@ -143,6 +143,121 @@ public class MenuManager return false; } + @Subscribe + public void onMenuOpened(MenuOpened event) + { + currentPriorityEntries.clear(); + currentHiddenEntries.clear(); + + // Need to reorder the list to normal, then rebuild with swaps + MenuEntry[] oldEntries = event.getMenuEntries(); + + for (MenuEntry entry : oldEntries) + { + if (entry == leftClickEntry) + { + entry.setType(leftClickType); + break; + } + } + + leftClickEntry = null; + leftClickType = -1; + + client.sortMenuEntries(); + + List newEntries = Lists.newArrayList(oldEntries); + + boolean shouldDeprioritize = false; + + prioritizer: for (MenuEntry entry : oldEntries) + { + // Remove hidden entries from menus + for (ComparableEntry p : hiddenEntries) + { + if (p.matches(entry)) + { + newEntries.remove(entry); + continue prioritizer; + } + } + + for (ComparableEntry p : priorityEntries) + { + // Create list of priority entries, and remove from menus + if (p.matches(entry)) + { + // Other entries need to be deprioritized if their types are lower than 1000 + if (entry.getType() >= 1000 && !shouldDeprioritize) + { + shouldDeprioritize = true; + } + currentPriorityEntries.add(entry); + newEntries.remove(entry); + continue prioritizer; + } + } + + if (newEntries.size() > 0) + { + // Swap first matching entry to top + for (ComparableEntry src : swaps.keySet()) + { + if (!src.matches(entry)) + { + continue; + } + + MenuEntry swapFrom = null; + + ComparableEntry from = swaps.get(src); + + for (MenuEntry e : newEntries) + { + if (from.matches(e)) + { + swapFrom = e; + break; + } + } + + // Do not need to swap with itself + if (swapFrom != null && swapFrom != entry) + { + // Deprioritize entries if the swaps are not in similar type groups + if ((swapFrom.getType() >= 1000 && entry.getType() < 1000) || (entry.getType() >= 1000 && swapFrom.getType() < 1000) && !shouldDeprioritize) + { + shouldDeprioritize = true; + } + + int indexFrom = newEntries.indexOf(swapFrom); + int indexTo = newEntries.indexOf(entry); + + Collections.swap(newEntries, indexFrom, indexTo); + } + } + } + } + + if (shouldDeprioritize) + { + for (MenuEntry entry : newEntries) + { + if (entry.getType() <= MENU_ACTION_DEPRIORITIZE_OFFSET) + { + entry.setType(entry.getType() + MENU_ACTION_DEPRIORITIZE_OFFSET); + } + } + } + + if (!priorityEntries.isEmpty()) + { + newEntries.addAll(currentPriorityEntries); + } + + event.setMenuEntries(newEntries.toArray(new MenuEntry[0])); + } + @Subscribe public void onMenuEntryAdded(MenuEntryAdded event) { @@ -167,116 +282,78 @@ public class MenuManager } } + + @Subscribe - private void onClientTick(ClientTick event) + public void onBeforeRender(BeforeRender event) { - originalType = null; + leftClickEntry = null; + leftClickType = -1; + + if (client.isMenuOpen()) + { + return; + } + + entries.clear(); + + entries.addAll(Arrays.asList(client.getMenuEntries())); + + if (entries.size() < 2) + { + return; + } + currentPriorityEntries.clear(); - client.sortMenuEntries(); + currentHiddenEntries.clear(); + currentSwaps.clear(); - MenuEntry[] oldEntries = client.getMenuEntries(); - List newEntries = Lists.newArrayList(oldEntries); + prioritizer.prioritize(); - for (MenuEntry entry : oldEntries) + while (prioritizer.isRunning()) { - for (ComparableEntry p : priorityEntries) + // wait + } + + entries.removeAll(currentHiddenEntries); + + + for (MenuEntry entry : currentPriorityEntries) + { + if (entries.contains(entry)) { - if (p.matches(entry)) - { - currentPriorityEntries.add(entry); - } - } - - // If there are entries we want to prioritize, we have to remove the rest - if (!currentPriorityEntries.isEmpty() && !client.isMenuOpen()) - { - newEntries.retainAll(currentPriorityEntries); - - // This is because players existing changes walk-here target - // so without this we lose track of em - if (newEntries.size() != currentPriorityEntries.size()) - { - for (MenuEntry e : currentPriorityEntries) - { - if (newEntries.contains(e)) - { - continue; - } - - for (MenuEntry e2 : client.getMenuEntries()) - { - if (e.getType() == e2.getType()) - { - e.setTarget(e2.getTarget()); - newEntries.add(e); - } - } - } - } - } - - boolean isHidden = false; - for (ComparableEntry p : hiddenEntries) - { - if (p.matches(entry)) - { - isHidden = true; - break; - } - } - - if (isHidden) - { - newEntries.remove(entry); + leftClickEntry = entry; + leftClickType = entry.getType(); + entries.remove(leftClickEntry); + leftClickEntry.setType(MenuAction.WIDGET_DEFAULT.getId()); + entries.add(leftClickEntry); + break; } } - if (!currentPriorityEntries.isEmpty() && !client.isMenuOpen()) + + if (leftClickEntry == null) { - newEntries.add(0, CANCEL()); - } + MenuEntry first = Iterables.getLast(entries); - MenuEntry leftClickEntry = newEntries.get(newEntries.size() - 1); - - for (ComparableEntry src : swaps.keySet()) - { - if (!src.matches(leftClickEntry)) + for (ComparableEntry swap : currentSwaps.keySet()) { - continue; - } - - ComparableEntry tgt = swaps.get(src); - - for (int i = newEntries.size() - 2; i > 0; i--) - { - MenuEntry e = newEntries.get(i); - - if (tgt.matches(e)) + if (swap.matches(first)) { - newEntries.set(newEntries.size() - 1, e); - newEntries.set(i, leftClickEntry); - - int type = e.getType(); - - if (type >= 1000) - { - int newType = getLeftClickType(type); - if (newType != -1 && newType != type) - { - MenuEntry original = MenuEntry.copy(e); - e.setType(newType); - originalType = new EntryTypeMapping(new ComparableEntry(leftClickEntry), original); - } - } - + leftClickEntry = currentSwaps.get(swap); + leftClickType = leftClickEntry.getType(); + entries.remove(leftClickEntry); + leftClickEntry.setType(MenuAction.WIDGET_DEFAULT.getId()); + entries.add(leftClickEntry); break; } } } - client.setMenuEntries(newEntries.toArray(new MenuEntry[0])); + client.setMenuEntries(entries.toArray(new MenuEntry[0])); } + public void addPlayerMenuItem(String menuText) { Preconditions.checkNotNull(menuText); @@ -338,24 +415,6 @@ public class MenuManager } } - private int getLeftClickType(int oldType) - { - if (oldType > 2000) - { - oldType -= 2000; - } - - switch (MenuAction.of(oldType)) - { - case GAME_OBJECT_FIFTH_OPTION: - return GAME_OBJECT_FIRST_OPTION.getId(); - case EXAMINE_ITEM_BANK_EQ: - return WIDGET_DEFAULT.getId(); - default: - return oldType; - } - } - private void addNpcOption(NPCDefinition composition, String npcOption) { String[] actions = composition.getActions(); @@ -399,21 +458,11 @@ public class MenuManager @Subscribe public void onMenuOptionClicked(MenuOptionClicked event) { - // Type is changed in check - if (originalType != null && originalType.check(event)) + if (leftClickEntry != null && leftClickType != -1) { - event.consume(); - - client.invokeMenuAction( - event.getActionParam0(), - event.getActionParam1(), - event.getType(), - event.getIdentifier(), - "do not edit", - event.getTarget(), - client.getMouseCanvasPosition().getX(), - client.getMouseCanvasPosition().getY() - ); + leftClickEntry.setType(leftClickType); + event.setMenuEntry(leftClickEntry); + leftClickEntry = null; } if (event.getMenuAction() != MenuAction.RUNELITE) @@ -740,23 +789,115 @@ public class MenuManager hiddenEntries.remove(entry); } - @AllArgsConstructor - private class EntryTypeMapping + private class Prioritizer { - private final ComparableEntry comparable; - private final MenuEntry target; + private MenuEntry[] entries; + private AtomicInteger state = new AtomicInteger(0); - private boolean check(MenuOptionClicked event) + boolean isRunning() { - MenuEntry entry = event.getMenuEntry(); + return state.get() != 0; + } - if (event.getTarget().equals("do not edit") || !comparable.matches(entry)) + void prioritize() + { + if (state.get() != 0) { - return false; + return; } - event.setMenuEntry(target); - return true; + entries = client.getMenuEntries(); + + state.set(3); + + if (!hiddenEntries.isEmpty()) + { + hiddenFinder.run(); + } + else + { + state.decrementAndGet(); + } + + if (!priorityEntries.isEmpty()) + { + priorityFinder.run(); + } + else + { + state.decrementAndGet(); + } + + if (!swaps.isEmpty()) + { + swapFinder.run(); + } + else + { + state.decrementAndGet(); + } } + + private Thread hiddenFinder = new Thread() + { + @Override + public void run() + { + Arrays.stream(entries).parallel().forEach(entry -> + { + for (ComparableEntry p : hiddenEntries) + { + if (p.matches(entry)) + { + currentHiddenEntries.add(entry); + return; + } + } + }); + state.decrementAndGet(); + } + }; + + private Thread priorityFinder = new Thread() + { + @Override + public void run() + { + Arrays.stream(entries).parallel().forEach(entry -> + { + for (ComparableEntry p : priorityEntries) + { + if (p.matches(entry)) + { + currentPriorityEntries.add(entry); + return; + } + } + }); + + state.decrementAndGet(); + } + }; + + private Thread swapFinder = new Thread() + { + @Override + public void run() + { + Arrays.stream(entries).parallel().forEach(entry -> + { + for (Map.Entry p : swaps.entrySet()) + { + if (p.getValue().matches(entry)) + { + currentSwaps.put(p.getKey(), entry); + return; + } + } + }); + + state.decrementAndGet(); + } + }; } } diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java index 069c9a99d4..ee569abca0 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java @@ -1264,7 +1264,8 @@ public abstract class RSClientMixin implements RSClient return; } - rs$menuAction(actionParam, widgetId, menuAction, id, menuOption, menuTarget, var6, var7); + rs$menuAction(menuOptionClicked.getActionParam0(), menuOptionClicked.getActionParam1(), menuOptionClicked.getType(), + menuOptionClicked.getIdentifier(), menuOptionClicked.getOption(), menuOptionClicked.getTarget(), var6, var7); } @FieldHook("Login_username") @@ -1303,6 +1304,7 @@ public abstract class RSClientMixin implements RSClient final MenuOpened event = new MenuOpened(); event.setMenuEntries(getMenuEntries()); callbacks.post(event); + client.setMenuEntries(event.getMenuEntries()); } @Inject