menu swapper: add custom npc left click swap

This commit is contained in:
Adam
2022-04-07 16:44:46 -04:00
parent f54b7ffc44
commit dadf3661e1
3 changed files with 199 additions and 13 deletions

View File

@@ -149,6 +149,18 @@ public interface MenuEntrySwapperConfig extends Config
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(
keyName = "swapAdmire",
name = "Admire",

View File

@@ -53,6 +53,7 @@ import net.runelite.api.KeyCode;
import net.runelite.api.MenuAction;
import net.runelite.api.MenuEntry;
import net.runelite.api.NPC;
import net.runelite.api.NPCComposition;
import net.runelite.api.ObjectComposition;
import net.runelite.api.events.ClientTick;
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 ITEM_KEY_PREFIX = "item_";
private static final String OBJECT_KEY_PREFIX = "object_";
private static final String NPC_KEY_PREFIX = "npc_";
// Shift click
private static final WidgetMenuOption FIXED_INVENTORY_TAB_CONFIGURE_SC = new WidgetMenuOption(CONFIGURE,
@@ -140,13 +142,13 @@ public class MenuEntrySwapperPlugin extends Plugin
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_SECOND_OPTION,
MenuAction.NPC_THIRD_OPTION,
MenuAction.NPC_FOURTH_OPTION,
MenuAction.NPC_FIFTH_OPTION,
MenuAction.EXAMINE_NPC);
MenuAction.NPC_FIFTH_OPTION
);
private static final List<MenuAction> OBJECT_MENU_TYPES = ImmutableList.of(
MenuAction.GAME_OBJECT_FIRST_OPTION,
@@ -533,6 +535,7 @@ public class MenuEntrySwapperPlugin extends Plugin
if (!configuringShiftClick && !configuringLeftClick)
{
configureObjectClick(event);
configureNpcClick(event);
return;
}
@@ -636,16 +639,9 @@ public class MenuEntrySwapperPlugin extends Plugin
final ObjectComposition composition = client.getObjectDefinition(entry.getIdentifier());
final String[] actions = composition.getActions();
final MenuAction currentAction;
Integer swapConfig = getObjectSwapConfig(composition.getId());
if (swapConfig != null)
{
currentAction = OBJECT_MENU_TYPES.get(swapConfig);
}
else
{
currentAction = defaultAction(composition);
}
final Integer swapConfig = getObjectSwapConfig(composition.getId());
final MenuAction currentAction = swapConfig != null ? OBJECT_MENU_TYPES.get(swapConfig) :
defaultAction(composition);
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
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
Collection<Swap> swaps = this.swaps.get(option);
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)
{
if (index1 == index2)
{
return;
}
MenuEntry entry1 = entries[index1],
entry2 = entries[index2];
@@ -1102,4 +1235,38 @@ public class MenuEntrySwapperPlugin extends Plugin
}
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;
}
}

View File

@@ -34,6 +34,8 @@ import net.runelite.api.GameState;
import net.runelite.api.KeyCode;
import net.runelite.api.MenuAction;
import net.runelite.api.MenuEntry;
import net.runelite.api.NPC;
import net.runelite.api.NPCComposition;
import net.runelite.api.ObjectComposition;
import net.runelite.api.events.ClientTick;
import net.runelite.api.events.MenuEntryAdded;
@@ -95,6 +97,11 @@ public class MenuEntrySwapperPluginTest
when(client.getGameState()).thenReturn(GameState.LOGGED_IN);
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 ->
{
// The menu implementation returns a copy of the array, which causes swap() to not