menu swapper: add custom npc left click swap
This commit is contained in:
@@ -149,6 +149,18 @@ public interface MenuEntrySwapperConfig extends Config
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ConfigItem(
|
||||||
|
position = -2,
|
||||||
|
keyName = "npcLeftClickCustomization",
|
||||||
|
name = "Customizable left-click",
|
||||||
|
description = "Allows customization of left-clicks on NPCs",
|
||||||
|
section = npcSection
|
||||||
|
)
|
||||||
|
default boolean npcLeftClickCustomization()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@ConfigItem(
|
@ConfigItem(
|
||||||
keyName = "swapAdmire",
|
keyName = "swapAdmire",
|
||||||
name = "Admire",
|
name = "Admire",
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ import net.runelite.api.KeyCode;
|
|||||||
import net.runelite.api.MenuAction;
|
import net.runelite.api.MenuAction;
|
||||||
import net.runelite.api.MenuEntry;
|
import net.runelite.api.MenuEntry;
|
||||||
import net.runelite.api.NPC;
|
import net.runelite.api.NPC;
|
||||||
|
import net.runelite.api.NPCComposition;
|
||||||
import net.runelite.api.ObjectComposition;
|
import net.runelite.api.ObjectComposition;
|
||||||
import net.runelite.api.events.ClientTick;
|
import net.runelite.api.events.ClientTick;
|
||||||
import net.runelite.api.events.MenuEntryAdded;
|
import net.runelite.api.events.MenuEntryAdded;
|
||||||
@@ -98,6 +99,7 @@ public class MenuEntrySwapperPlugin extends Plugin
|
|||||||
private static final String SHIFTCLICK_CONFIG_GROUP = "shiftclick";
|
private static final String SHIFTCLICK_CONFIG_GROUP = "shiftclick";
|
||||||
private static final String ITEM_KEY_PREFIX = "item_";
|
private static final String ITEM_KEY_PREFIX = "item_";
|
||||||
private static final String OBJECT_KEY_PREFIX = "object_";
|
private static final String OBJECT_KEY_PREFIX = "object_";
|
||||||
|
private static final String NPC_KEY_PREFIX = "npc_";
|
||||||
|
|
||||||
// Shift click
|
// Shift click
|
||||||
private static final WidgetMenuOption FIXED_INVENTORY_TAB_CONFIGURE_SC = new WidgetMenuOption(CONFIGURE,
|
private static final WidgetMenuOption FIXED_INVENTORY_TAB_CONFIGURE_SC = new WidgetMenuOption(CONFIGURE,
|
||||||
@@ -140,13 +142,13 @@ public class MenuEntrySwapperPlugin extends Plugin
|
|||||||
MenuAction.ITEM_USE
|
MenuAction.ITEM_USE
|
||||||
);
|
);
|
||||||
|
|
||||||
private static final Set<MenuAction> NPC_MENU_TYPES = ImmutableSet.of(
|
private static final List<MenuAction> NPC_MENU_TYPES = ImmutableList.of(
|
||||||
MenuAction.NPC_FIRST_OPTION,
|
MenuAction.NPC_FIRST_OPTION,
|
||||||
MenuAction.NPC_SECOND_OPTION,
|
MenuAction.NPC_SECOND_OPTION,
|
||||||
MenuAction.NPC_THIRD_OPTION,
|
MenuAction.NPC_THIRD_OPTION,
|
||||||
MenuAction.NPC_FOURTH_OPTION,
|
MenuAction.NPC_FOURTH_OPTION,
|
||||||
MenuAction.NPC_FIFTH_OPTION,
|
MenuAction.NPC_FIFTH_OPTION
|
||||||
MenuAction.EXAMINE_NPC);
|
);
|
||||||
|
|
||||||
private static final List<MenuAction> OBJECT_MENU_TYPES = ImmutableList.of(
|
private static final List<MenuAction> OBJECT_MENU_TYPES = ImmutableList.of(
|
||||||
MenuAction.GAME_OBJECT_FIRST_OPTION,
|
MenuAction.GAME_OBJECT_FIRST_OPTION,
|
||||||
@@ -533,6 +535,7 @@ public class MenuEntrySwapperPlugin extends Plugin
|
|||||||
if (!configuringShiftClick && !configuringLeftClick)
|
if (!configuringShiftClick && !configuringLeftClick)
|
||||||
{
|
{
|
||||||
configureObjectClick(event);
|
configureObjectClick(event);
|
||||||
|
configureNpcClick(event);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -636,16 +639,9 @@ public class MenuEntrySwapperPlugin extends Plugin
|
|||||||
final ObjectComposition composition = client.getObjectDefinition(entry.getIdentifier());
|
final ObjectComposition composition = client.getObjectDefinition(entry.getIdentifier());
|
||||||
final String[] actions = composition.getActions();
|
final String[] actions = composition.getActions();
|
||||||
|
|
||||||
final MenuAction currentAction;
|
final Integer swapConfig = getObjectSwapConfig(composition.getId());
|
||||||
Integer swapConfig = getObjectSwapConfig(composition.getId());
|
final MenuAction currentAction = swapConfig != null ? OBJECT_MENU_TYPES.get(swapConfig) :
|
||||||
if (swapConfig != null)
|
defaultAction(composition);
|
||||||
{
|
|
||||||
currentAction = OBJECT_MENU_TYPES.get(swapConfig);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
currentAction = defaultAction(composition);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int actionIdx = 0; actionIdx < OBJECT_MENU_TYPES.size(); ++actionIdx)
|
for (int actionIdx = 0; actionIdx < OBJECT_MENU_TYPES.size(); ++actionIdx)
|
||||||
{
|
{
|
||||||
@@ -694,6 +690,111 @@ public class MenuEntrySwapperPlugin extends Plugin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void configureNpcClick(MenuOpened event)
|
||||||
|
{
|
||||||
|
if (!shiftModifier() || !config.npcLeftClickCustomization())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuEntry[] entries = event.getMenuEntries();
|
||||||
|
for (int idx = entries.length - 1; idx >= 0; --idx)
|
||||||
|
{
|
||||||
|
final MenuEntry entry = entries[idx];
|
||||||
|
final MenuAction type = entry.getType();
|
||||||
|
final int id = entry.getIdentifier();
|
||||||
|
|
||||||
|
if (type == MenuAction.EXAMINE_NPC)
|
||||||
|
{
|
||||||
|
final NPC npc = client.getCachedNPCs()[id];
|
||||||
|
final NPCComposition composition = npc.getTransformedComposition();
|
||||||
|
final String[] actions = composition.getActions();
|
||||||
|
|
||||||
|
final Integer swapConfig = getNpcSwapConfig(composition.getId());
|
||||||
|
final boolean hasAttack = Arrays.stream(composition.getActions()).anyMatch("Attack"::equalsIgnoreCase);
|
||||||
|
final MenuAction currentAction = swapConfig != null ? NPC_MENU_TYPES.get(swapConfig) :
|
||||||
|
// Attackable NPCs always have Attack as the first, last (deprioritized), or when hidden, no, option.
|
||||||
|
// Due to this the default action would be either Attack or the first non-Attack option, based on
|
||||||
|
// the game settings. Since it may be valid to swap an option up to override Attack, even when Attack
|
||||||
|
// is left-click, we cannot assume any default currentAction on attackable NPCs.
|
||||||
|
// Non-attackable NPCS have a predictable default action which we can prevent a swap to if no swap
|
||||||
|
// config is set, which just avoids showing a Swap option on a 1-op NPC, which looks odd.
|
||||||
|
(hasAttack ? null : defaultAction(composition));
|
||||||
|
|
||||||
|
for (int actionIdx = 0; actionIdx < NPC_MENU_TYPES.size(); ++actionIdx)
|
||||||
|
{
|
||||||
|
// Attack can be swapped with the in-game settings, and this becomes very confusing if we try
|
||||||
|
// to swap Attack and the game also tries to swap it (by deprioritizing), so just use the in-game
|
||||||
|
// setting.
|
||||||
|
if (Strings.isNullOrEmpty(actions[actionIdx]) || "Attack".equalsIgnoreCase(actions[actionIdx]))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int menuIdx = actionIdx;
|
||||||
|
final MenuAction menuAction = NPC_MENU_TYPES.get(actionIdx);
|
||||||
|
if (currentAction == menuAction)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("Pickpocket".equals(actions[actionIdx])
|
||||||
|
|| "Knock-Out".equals(actions[actionIdx])
|
||||||
|
|| "Lure".equals(actions[actionIdx]))
|
||||||
|
{
|
||||||
|
// https://secure.runescape.com/m=news/another-message-about-unofficial-clients?oldschool=1
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
client.createMenuEntry(idx)
|
||||||
|
.setOption("Swap " + actions[actionIdx])
|
||||||
|
.setTarget(entry.getTarget())
|
||||||
|
.setType(MenuAction.RUNELITE)
|
||||||
|
.onClick(e ->
|
||||||
|
{
|
||||||
|
final String message = new ChatMessageBuilder()
|
||||||
|
.append("The default left click option for '").append(composition.getName()).append("' ")
|
||||||
|
.append("has been set to '").append(actions[menuIdx]).append("'.")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
chatMessageManager.queue(QueuedMessage.builder()
|
||||||
|
.type(ChatMessageType.CONSOLE)
|
||||||
|
.runeLiteFormattedMessage(message)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
log.debug("Set npc swap for {} to {}", composition.getId(), menuAction);
|
||||||
|
|
||||||
|
setNpcSwapConfig(composition.getId(), menuIdx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getNpcSwapConfig(composition.getId()) != null)
|
||||||
|
{
|
||||||
|
// Reset
|
||||||
|
client.createMenuEntry(idx)
|
||||||
|
.setOption("Reset swap")
|
||||||
|
.setTarget(entry.getTarget())
|
||||||
|
.setType(MenuAction.RUNELITE)
|
||||||
|
.onClick(e ->
|
||||||
|
{
|
||||||
|
final String message = new ChatMessageBuilder()
|
||||||
|
.append("The default left click option for '").append(composition.getName()).append("' ")
|
||||||
|
.append("has been reset.")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
chatMessageManager.queue(QueuedMessage.builder()
|
||||||
|
.type(ChatMessageType.CONSOLE)
|
||||||
|
.runeLiteFormattedMessage(message)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
log.debug("Unset npc swap for {}", composition.getId());
|
||||||
|
unsetNpcSwapConfig(composition.getId());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void onMenuEntryAdded(MenuEntryAdded menuEntryAdded)
|
public void onMenuEntryAdded(MenuEntryAdded menuEntryAdded)
|
||||||
{
|
{
|
||||||
@@ -850,6 +951,33 @@ public class MenuEntrySwapperPlugin extends Plugin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (NPC_MENU_TYPES.contains(menuAction))
|
||||||
|
{
|
||||||
|
final NPC npc = client.getCachedNPCs()[eventId];
|
||||||
|
final NPCComposition composition = npc.getTransformedComposition();
|
||||||
|
|
||||||
|
Integer customOption = getNpcSwapConfig(composition.getId());
|
||||||
|
if (customOption != null)
|
||||||
|
{
|
||||||
|
MenuAction swapAction = NPC_MENU_TYPES.get(customOption);
|
||||||
|
if (swapAction == menuAction)
|
||||||
|
{
|
||||||
|
// Advance to the top-most op for this NPC. Normally menuEntries.length - 1 is examine, and swapping
|
||||||
|
// with that works due to it being sorted later, but if other plugins like NPC indicators add additional
|
||||||
|
// menus before examine that are also >1000, like RUNELITE menus, that would result in the >1000 menus being
|
||||||
|
// reordered relative to each other.
|
||||||
|
int i = index;
|
||||||
|
while (i < menuEntries.length - 1 && NPC_MENU_TYPES.contains(menuEntries[i + 1].getType()))
|
||||||
|
{
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
|
||||||
|
swap(optionIndexes, menuEntries, index, i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Built-in swaps
|
// Built-in swaps
|
||||||
Collection<Swap> swaps = this.swaps.get(option);
|
Collection<Swap> swaps = this.swaps.get(option);
|
||||||
for (Swap swap : swaps)
|
for (Swap swap : swaps)
|
||||||
@@ -969,6 +1097,11 @@ public class MenuEntrySwapperPlugin extends Plugin
|
|||||||
|
|
||||||
private void swap(ArrayListMultimap<String, Integer> optionIndexes, MenuEntry[] entries, int index1, int index2)
|
private void swap(ArrayListMultimap<String, Integer> optionIndexes, MenuEntry[] entries, int index1, int index2)
|
||||||
{
|
{
|
||||||
|
if (index1 == index2)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
MenuEntry entry1 = entries[index1],
|
MenuEntry entry1 = entries[index1],
|
||||||
entry2 = entries[index2];
|
entry2 = entries[index2];
|
||||||
|
|
||||||
@@ -1102,4 +1235,38 @@ public class MenuEntrySwapperPlugin extends Plugin
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Integer getNpcSwapConfig(int npcId)
|
||||||
|
{
|
||||||
|
String config = configManager.getConfiguration(MenuEntrySwapperConfig.GROUP, NPC_KEY_PREFIX + npcId);
|
||||||
|
if (config == null || config.isEmpty())
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Integer.parseInt(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setNpcSwapConfig(int npcId, int index)
|
||||||
|
{
|
||||||
|
configManager.setConfiguration(MenuEntrySwapperConfig.GROUP, NPC_KEY_PREFIX + npcId, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unsetNpcSwapConfig(int npcId)
|
||||||
|
{
|
||||||
|
configManager.unsetConfiguration(MenuEntrySwapperConfig.GROUP, NPC_KEY_PREFIX + npcId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MenuAction defaultAction(NPCComposition composition)
|
||||||
|
{
|
||||||
|
String[] actions = composition.getActions();
|
||||||
|
for (int i = 0; i < NPC_MENU_TYPES.size(); ++i)
|
||||||
|
{
|
||||||
|
if (!Strings.isNullOrEmpty(actions[i]) && !actions[i].equalsIgnoreCase("Attack"))
|
||||||
|
{
|
||||||
|
return NPC_MENU_TYPES.get(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ import net.runelite.api.GameState;
|
|||||||
import net.runelite.api.KeyCode;
|
import net.runelite.api.KeyCode;
|
||||||
import net.runelite.api.MenuAction;
|
import net.runelite.api.MenuAction;
|
||||||
import net.runelite.api.MenuEntry;
|
import net.runelite.api.MenuEntry;
|
||||||
|
import net.runelite.api.NPC;
|
||||||
|
import net.runelite.api.NPCComposition;
|
||||||
import net.runelite.api.ObjectComposition;
|
import net.runelite.api.ObjectComposition;
|
||||||
import net.runelite.api.events.ClientTick;
|
import net.runelite.api.events.ClientTick;
|
||||||
import net.runelite.api.events.MenuEntryAdded;
|
import net.runelite.api.events.MenuEntryAdded;
|
||||||
@@ -95,6 +97,11 @@ public class MenuEntrySwapperPluginTest
|
|||||||
when(client.getGameState()).thenReturn(GameState.LOGGED_IN);
|
when(client.getGameState()).thenReturn(GameState.LOGGED_IN);
|
||||||
when(client.getObjectDefinition(anyInt())).thenReturn(mock(ObjectComposition.class));
|
when(client.getObjectDefinition(anyInt())).thenReturn(mock(ObjectComposition.class));
|
||||||
|
|
||||||
|
NPC npc = mock(NPC.class);
|
||||||
|
NPCComposition composition = mock(NPCComposition.class);
|
||||||
|
when(npc.getTransformedComposition()).thenReturn(composition);
|
||||||
|
when(client.getCachedNPCs()).thenReturn(new NPC[] { npc });
|
||||||
|
|
||||||
when(client.getMenuEntries()).thenAnswer((Answer<MenuEntry[]>) invocationOnMock ->
|
when(client.getMenuEntries()).thenAnswer((Answer<MenuEntry[]>) invocationOnMock ->
|
||||||
{
|
{
|
||||||
// The menu implementation returns a copy of the array, which causes swap() to not
|
// The menu implementation returns a copy of the array, which causes swap() to not
|
||||||
|
|||||||
Reference in New Issue
Block a user