From 0cfc1cdc94008912b2920540774a81f48f01e5d9 Mon Sep 17 00:00:00 2001 From: krulvis Date: Sun, 5 Oct 2025 16:31:25 +0200 Subject: [PATCH 1/3] feat: Allow interacting with Rs2ItemModel through sub-menu without the manual lookup of identifier --- .../microbot/util/inventory/Rs2Inventory.java | 15 +- .../microbot/util/inventory/Rs2ItemModel.java | 223 ++++++++++-------- 2 files changed, 138 insertions(+), 100 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/inventory/Rs2Inventory.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/inventory/Rs2Inventory.java index e06669b634e..f04eca5bc57 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/inventory/Rs2Inventory.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/inventory/Rs2Inventory.java @@ -1892,13 +1892,13 @@ public static boolean hasRunePouch() { */ private static void invokeMenu(Rs2ItemModel rs2Item, String action, int providedIdentifier) { if (rs2Item == null) return; - Rs2Tab.switchToInventoryTab(); Microbot.status = action + " " + rs2Item.getName(); int param0; int param1; int identifier = -1; + String target = rs2Item.getName(); MenuAction menuAction = MenuAction.CC_OP; Widget[] inventoryWidgets; param0 = rs2Item.getSlot(); @@ -1939,7 +1939,16 @@ private static void invokeMenu(Rs2ItemModel rs2Item, String action, int provided itemWidget.getActions() : rs2Item.getInventoryActions(); - identifier = providedIdentifier == -1 ? indexOfIgnoreCase(stripColTags(actions), action) + 1 : providedIdentifier; + int simpleIndex = indexOfIgnoreCase(stripColTags(actions), action); + if (simpleIndex != -1) { + identifier = simpleIndex + 1; + } else { + Map.Entry subActionMap = rs2Item.getIndexOfSubAction(action); + // The main menu index depends on the inventory interface from which this item is interacted with + int mainMenuIndex = java.util.Arrays.asList(actions).indexOf(subActionMap.getKey()) + 1; + identifier = NewMenuEntry.findIdentifier(subActionMap.getValue() + 1, mainMenuIndex); + target = ""; + } } @@ -1951,7 +1960,7 @@ private static void invokeMenu(Rs2ItemModel rs2Item, String action, int provided menuAction = MenuAction.WIDGET_TARGET_ON_WIDGET; } - Microbot.doInvoke(new NewMenuEntry(action, param0, param1, menuAction.getId(), identifier, rs2Item.getId(), rs2Item.getName()), (itemBounds(rs2Item) == null) ? new Rectangle(1, 1) : itemBounds(rs2Item)); + Microbot.doInvoke(new NewMenuEntry(action, param0, param1, menuAction.getId(), identifier, rs2Item.getId(), target), (itemBounds(rs2Item) == null) ? new Rectangle(1, 1) : itemBounds(rs2Item)); if (action.equalsIgnoreCase("destroy")) { sleepUntil(() -> Rs2Widget.isWidgetVisible(584, 0)); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/inventory/Rs2ItemModel.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/inventory/Rs2ItemModel.java index 2dba3e2e466..c7eaa4c4a1d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/inventory/Rs2ItemModel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/inventory/Rs2ItemModel.java @@ -6,14 +6,11 @@ import net.runelite.api.EquipmentInventorySlot; import net.runelite.api.Item; import net.runelite.api.ItemComposition; -import net.runelite.api.gameval.ItemID; import net.runelite.api.ParamID; +import net.runelite.api.gameval.ItemID; import net.runelite.client.plugins.microbot.Microbot; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; +import java.util.*; import java.util.function.BiPredicate; import java.util.function.Predicate; @@ -23,18 +20,19 @@ public class Rs2ItemModel { private int id; @Getter @Setter - private int quantity; + private int quantity; @Getter - private int slot = -1; - private String name; - private String[] inventoryActions; + private int slot = -1; + private String name; + private String[] inventoryActions; + private String[][] subops; @Getter - private List equipmentActions = new ArrayList<>(); - private boolean isStackable; - private boolean isNoted; - private boolean isTradeable; - private ItemComposition itemComposition; - private int[] wearableActionIndexes = new int[]{ + private List equipmentActions = new ArrayList<>(); + private boolean isStackable; + private boolean isNoted; + private boolean isTradeable; + private ItemComposition itemComposition; + private int[] wearableActionIndexes = new int[]{ ParamID.OC_ITEM_OP1, ParamID.OC_ITEM_OP2, ParamID.OC_ITEM_OP3, @@ -43,7 +41,7 @@ public class Rs2ItemModel { ParamID.OC_ITEM_OP6, ParamID.OC_ITEM_OP7, ParamID.OC_ITEM_OP8 - }; + }; public Rs2ItemModel(Item item, ItemComposition itemComposition, int slot) { @@ -57,10 +55,10 @@ public Rs2ItemModel(Item item, ItemComposition itemComposition, int slot) { * Creates an Rs2ItemModel from cached data (ID, quantity, slot). * This is used when loading bank data from config where we don't have the full ItemComposition. * ItemComposition data will be loaded lazily when needed. - * - * @param id Item ID + * + * @param id Item ID * @param quantity Item quantity - * @param slot Item slot position + * @param slot Item slot position * @return Rs2ItemModel with basic data, ItemComposition loaded lazily */ public static Rs2ItemModel createFromCache(int id, int quantity, int slot) { @@ -69,12 +67,12 @@ public static Rs2ItemModel createFromCache(int id, int quantity, int slot) { /** - * Constructor for creating Rs2ItemModel with explicit ItemComposition. + * Constructor for creating Rs2ItemModel with explicit ItemComposition. */ public Rs2ItemModel(int id, int quantity, int slot, ItemComposition itemComposition) { this.id = id; this.quantity = quantity; - this.slot = slot; + this.slot = slot; if (itemComposition == null) { //lazy loading will handle this initializeDefaults(); @@ -82,6 +80,7 @@ public Rs2ItemModel(int id, int quantity, int slot, ItemComposition itemComposit initializeFromComposition(itemComposition); } } + /** * Private constructor for creating Rs2ItemModel from cached data. * ItemComposition will be loaded lazily when needed. @@ -91,12 +90,12 @@ public Rs2ItemModel(int id, int quantity, int slot) { this.quantity = quantity; this.slot = slot; ItemComposition itemComposition = Microbot.getClientThread().runOnClientThreadOptional(() -> - Microbot.getClient().getItemDefinition(id)).orElse(null); - if (itemComposition == null) { + Microbot.getClient().getItemDefinition(id)).orElse(null); + if (itemComposition == null) { initializeDefaults(); - }else{ + } else { initializeFromComposition(itemComposition); - } + } } /** @@ -104,29 +103,8 @@ public Rs2ItemModel(int id, int quantity, int slot) { * This ensures we can work with cached items while minimizing performance impact. */ private void ensureCompositionLoaded() { - if (itemComposition == null && id > 0) { - this.itemComposition = Microbot.getClientThread().runOnClientThreadOptional(()-> Microbot.getClient().getItemDefinition(id)).orElse(null); - if (itemComposition != null) { - this.name = itemComposition.getName(); - this.isStackable = itemComposition.isStackable(); - this.isNoted = itemComposition.getNote() == 799; - if (this.isNoted) { - Microbot.getClientThread().runOnClientThreadOptional(() -> - Microbot.getClient().getItemDefinition(itemComposition.getLinkedNoteId())).ifPresent(itemDefinition -> this.isTradeable = itemDefinition.isTradeable()); - } else { - this.isTradeable = itemComposition.isTradeable(); - } - this.inventoryActions = itemComposition.getInventoryActions(); - Microbot.getClientThread().runOnClientThreadOptional(() -> { - addEquipmentActions(itemComposition); - return true; - }); - } - else { - // If we can't load the ItemComposition, set defaults - log.warn("Failed to load ItemComposition for id: {}, setting defaults", id); - } + Microbot.getClientThread().runOnClientThreadOptional(() -> Microbot.getClient().getItemDefinition(id)).ifPresent(this::initializeFromComposition); } } @@ -179,6 +157,55 @@ public String[] getInventoryActions() { } return inventoryActions; } + + /** + * Gets the sub-actions, loading composition if needed. + * This returns a list of sub-menu actions that can be performed on the item. + * The first index corresponds to the main menu's index, the second index is the sub-menu index. + * + * @return A list of sub-actions, or null if none are available + */ + public String[][] getSubops() { + if (itemComposition == null) { + ensureCompositionLoaded(); + } + return subops; + } + + /** + * Retrieves the index of a sub-action from the sub-actions list, matching the given action. + * + * This method searches through the sub-actions of the item, attempting to find a match + * for the specified action. If a match is found, it returns the corresponding main action + * and index of the sub-action. If no match is found or if the sub-actions are unavailable, + * it returns a default result with null and -1. + * + * @param action The action to search for in the sub-actions list. Case-insensitive comparison is used. + * @return A Map.Entry containing the inventory action (String) and the index of the sub-action (Integer) + * if a match is found; otherwise, returns a Map.Entry with null and -1. + */ + public Map.Entry getIndexOfSubAction(String action) { + if (action == null) return Map.entry(null, -1); + String alc = action.toLowerCase(); + + String[][] subOps = getSubops(); + if (subOps == null) { + return Map.entry(null, -1); + } + for (int i = 0; i < subOps.length; i++) { + String[] subOpsActions = subOps[i]; + if (subOpsActions == null) continue; + for (int j = 0; j < subOpsActions.length; j++) { + String subObsAction = subOpsActions[j]; + if (subObsAction != null && subObsAction.toLowerCase().contains(alc)) { + return Map.entry(inventoryActions[i], j); + } + } + } + + return Map.entry(null, -1); + } + /** * Gets the equipment actions, loading composition if needed. * This returns a list of actions that can be performed on the item when equipped. @@ -200,15 +227,15 @@ public ItemComposition getItemComposition() { return itemComposition; } - public boolean isFood() { - if (isNoted()) return false; + public boolean isFood() { + if (isNoted()) return false; - String lowerName = getName().toLowerCase(); + String lowerName = getName().toLowerCase(); - boolean isEdible = Arrays.stream(getInventoryActions()).anyMatch(action -> action != null && action.equalsIgnoreCase("eat")); + boolean isEdible = Arrays.stream(getInventoryActions()).anyMatch(action -> action != null && action.equalsIgnoreCase("eat")); - return (isEdible || lowerName.contains("jug of wine")) && !lowerName.contains("rock cake"); - } + return (isEdible || lowerName.contains("jug of wine")) && !lowerName.contains("rock cake"); + } private void addEquipmentActions(ItemComposition itemComposition) { for (int i = 0; i < wearableActionIndexes.length; i++) { @@ -235,7 +262,7 @@ public int getHaPrice() { public boolean isHaProfitable() { int natureRunePrice = Microbot.getClientThread().runOnClientThreadOptional(() -> Microbot.getItemManager().getItemPrice(ItemID.NATURERUNE)).orElse(0); - return (getHaPrice() - natureRunePrice) > (getPrice()/quantity) && isTradeable; + return (getHaPrice() - natureRunePrice) > (getPrice() / quantity) && isTradeable; } @@ -271,21 +298,21 @@ public String toString() { sb.append("\tisNoted: ").append(isNoted()).append("\n"); sb.append("\tisTradeable: ").append(isTradeable()).append("\n"); sb.append("\tisFood: ").append(isFood()).append("\n"); - + // Price information int price = getPrice(); sb.append("\tprice: ").append(price).append(" gp (total)\n"); if (quantity > 0) { sb.append("\tunitPrice: ").append(price / quantity).append(" gp (each)\n"); } - + // High Alchemy information if (itemComposition != null) { int haPrice = getHaPrice(); sb.append("\thaPrice: ").append(haPrice).append(" gp\n"); sb.append("\tisHaProfitable: ").append(isHaProfitable()).append("\n"); } - + // Actions String[] invActions = getInventoryActions(); if (invActions != null && invActions.length > 0) { @@ -298,7 +325,7 @@ public String toString() { } sb.append("]\n"); } - + // Equipment actions if (!equipmentActions.isEmpty()) { sb.append("\tequipmentActions: ["); @@ -312,10 +339,10 @@ public String toString() { } sb.append("]\n"); } - + // Composition status sb.append("\tcompositionLoaded: ").append(itemComposition != null).append("\n"); - + sb.append("}"); return sb.toString(); } @@ -336,7 +363,7 @@ public static Predicate matches(int... ids) { public static Predicate matches(EquipmentInventorySlot... slots) { return matches(slots, (item, slot) -> item.getSlot() == slot.getSlotIdx()); } - + /** * Initialize default values when ItemComposition is not available. */ @@ -348,18 +375,18 @@ private void initializeDefaults() { this.inventoryActions = new String[0]; this.itemComposition = null; } - + /** * Gets the noted variant of this item if it exists and is stackable. * Returns this item's ID if the item is already noted or has no noted variant. - * + * * @return The noted item ID if available, otherwise the original item ID */ public int getNotedId() { if (itemComposition == null) { ensureCompositionLoaded(); } - + if (itemComposition == null) { return id; // fallback to original ID } @@ -368,18 +395,18 @@ public int getNotedId() { } return getNotedItemId(itemComposition); } - + /** * Gets the unnoted variant of this item if it exists. * Returns this item's ID if the item is already unnoted or has no unnoted variant. - * + * * @return The unnoted item ID if available, otherwise the original item ID */ public int getUnNotedId() { if (itemComposition == null) { ensureCompositionLoaded(); } - + if (itemComposition == null) { return id; // fallback to original ID } @@ -389,27 +416,27 @@ public int getUnNotedId() { } return getUnNotedId(itemComposition); } - + /** * Gets the linked item ID (noted/unnoted counterpart) of this item. - * + * * @return The linked item ID */ public int getLinkedId() { if (itemComposition == null) { ensureCompositionLoaded(); } - + if (itemComposition == null) { return id; // fallback to original ID } - + return getLinkedItemId(itemComposition); } - + /** * Static method to get the noted variant of an item ID. - * + * * @param itemId The original item ID * @return The noted item ID if available, otherwise the original item ID */ @@ -417,13 +444,13 @@ public static int getNotedId(int itemId) { ItemComposition composition = Microbot.getClientThread().runOnClientThreadOptional(() -> Microbot.getClient().getItemDefinition(itemId) ).orElse(null); - + return getNotedItemId(composition); } - + /** * Static method to get the unnoted variant of an item ID. - * + * * @param itemId The original item ID * @return The unnoted item ID if available, otherwise the original item ID */ @@ -431,14 +458,14 @@ public static int getUnNotedId(int itemId) { ItemComposition composition = Microbot.getClientThread().runOnClientThreadOptional(() -> Microbot.getClient().getItemDefinition(itemId) ).orElse(null); - + return getUnNotedId(composition); } - + /** * Helper method to get the noted variant from ItemComposition. * Returns the noted ID if the item has a stackable noted variant. - * + * * @param composition The ItemComposition to check * @return The noted item ID if available, otherwise the original item ID */ @@ -447,26 +474,26 @@ private static int getNotedItemId(ItemComposition composition) { if (composition == null) { return -1; } - + int itemId = composition.getId(); boolean isNoted = composition.getNote() == 799; int linkedNoteId = composition.getLinkedNoteId(); // if already stackable, return original ID - if ( (isNoted && composition.isStackable()) || linkedNoteId == - 1) { + if ((isNoted && composition.isStackable()) || linkedNoteId == -1) { return itemId; - } - + } + return linkedNoteId; - + } catch (Exception e) { return -1; // fall back to original on error } } - + /** * Helper method to get the unnoted variant from ItemComposition. * Returns the unnoted ID if the item has an unnoted variant. - * + * * @param composition The ItemComposition to check * @return The unnoted item ID if available, otherwise the original item ID */ @@ -475,25 +502,25 @@ private static int getUnNotedId(ItemComposition composition) { if (composition == null) { log.warn("Could not get item composition for item ID, returning original ID"); return -1; - } + } int itemId = composition.getId(); boolean isNoted = composition.getNote() == 799; int linkedNoteId = composition.getLinkedNoteId(); // if already stackable, return original ID - if ( (!isNoted && !composition.isStackable()) || linkedNoteId == - 1) { + if ((!isNoted && !composition.isStackable()) || linkedNoteId == -1) { return itemId; - } + } return linkedNoteId; - + } catch (Exception e) { log.error("Error getting unnoted item ID: {}", e.getMessage()); return -1; // fall back to original on error } } - + /** * Helper method to get the linked item ID from ItemComposition. - * + * * @param composition The ItemComposition to check * @return The linked item ID */ @@ -503,10 +530,10 @@ private static int getLinkedItemId(ItemComposition composition) { log.warn("no item composition for item ID, returning -1"); return -1; } - + // check if this item has a noted variant return composition.getLinkedNoteId(); - + } catch (Exception e) { log.error("Error getting linked item ID: {}", e.getMessage()); return -1; // fall back on error @@ -520,7 +547,7 @@ private void initializeFromComposition(ItemComposition itemComposition) { this.name = itemComposition.getName(); this.isStackable = itemComposition.isStackable(); this.isNoted = itemComposition.getNote() == 799; - + // Handle noted item tradeable status if (this.isNoted) { Microbot.getClientThread().runOnClientThreadOptional(() -> @@ -529,10 +556,12 @@ private void initializeFromComposition(ItemComposition itemComposition) { } else { this.isTradeable = itemComposition.isTradeable(); } - + this.inventoryActions = itemComposition.getInventoryActions(); + // This has to be run in microbot's client thread + this.subops = Microbot.getClientThread().runOnClientThreadOptional(itemComposition::getSubops).orElse(null); this.itemComposition = itemComposition; - + // Add equipment actions asynchronously Microbot.getClientThread().runOnClientThreadOptional(() -> { addEquipmentActions(itemComposition); From 0fbfd8a222a9becfb750823083b1010ed3aea5e5 Mon Sep 17 00:00:00 2001 From: krulvis Date: Mon, 6 Oct 2025 20:22:50 +0200 Subject: [PATCH 2/3] feat(Rs2Equipment): Allow interaction with through sub-menu actions --- .../microbot/util/equipment/Rs2Equipment.java | 25 ++++++++----- .../microbot/util/inventory/Rs2Inventory.java | 36 +++++-------------- 2 files changed, 24 insertions(+), 37 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/equipment/Rs2Equipment.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/equipment/Rs2Equipment.java index 580362819fe..65532a0db14 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/equipment/Rs2Equipment.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/equipment/Rs2Equipment.java @@ -15,12 +15,7 @@ import org.slf4j.event.Level; import java.awt.Rectangle; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.Set; +import java.util.*; import java.util.function.Predicate; import java.util.stream.Stream; @@ -313,6 +308,7 @@ public static void invokeMenu(Rs2ItemModel rs2Item, String action) { int param0 = -1; int param1 = -1; int identifier; + String target = rs2Item.getName(); MenuAction menuAction = MenuAction.CC_OP; if (action.equalsIgnoreCase("remove")) { identifier = 1; @@ -325,9 +321,20 @@ public static void invokeMenu(Rs2ItemModel rs2Item, String action) { break; } } + // We could not find the action in the equipment actions, so we try to find it in the sub-menu actions if (identifier == -1) { - Microbot.log("Item=" + rs2Item.getName() + " does not have action=" + action + ". Actions=" + Arrays.toString(actions.stream().filter(Objects::nonNull).toArray()), Level.ERROR); - return; + Map.Entry subMenuEntry = rs2Item.getIndexOfSubAction(action); + if (subMenuEntry == null) { + Microbot.log("Item=" + rs2Item.getName() + " does not have a subaction=" + action, Level.ERROR); + return; + } + int mainMenuIndex = actions.indexOf(subMenuEntry.getKey()); + if (mainMenuIndex < 0) { + Microbot.log("Cannot find action=%s, in main actions=%s, mainMenuIndex=%s", subMenuEntry.getKey(), String.join(", ", actions), mainMenuIndex); + return; + } + target = ""; + identifier = NewMenuEntry.findIdentifier(subMenuEntry.getValue() + 1, mainMenuIndex + 2); } } @@ -355,7 +362,7 @@ public static void invokeMenu(Rs2ItemModel rs2Item, String action) { param1 = 25362456; } - Microbot.doInvoke(new NewMenuEntry(param0, param1, menuAction.getId(), identifier, -1, rs2Item.getName()), new Rectangle(1, 1, Microbot.getClient().getCanvasWidth(), Microbot.getClient().getCanvasHeight())); + Microbot.doInvoke(new NewMenuEntry(param0, param1, menuAction.getId(), identifier, -1, target), new Rectangle(1, 1, Microbot.getClient().getCanvasWidth(), Microbot.getClient().getCanvasHeight())); //Rs2Reflection.invokeMenu(param0, param1, menuAction.getId(), identifier, rs2Item.id, action, target, -1, -1); } } \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/inventory/Rs2Inventory.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/inventory/Rs2Inventory.java index f04eca5bc57..cfe623a2f72 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/inventory/Rs2Inventory.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/inventory/Rs2Inventory.java @@ -1206,20 +1206,6 @@ public static boolean interact(int id) { public static boolean interact(int id, String action) { return interact(get(id), action); } - /** - * Interacts with an item with the specified ID in the inventory using the specified action. - * - * @param id The ID of the item to interact with. - * @param action The action to perform on the item. - * - * @return True if the interaction was successful, false otherwise. - */ - public static boolean interact(int id, String action, int identifier) { - final Rs2ItemModel rs2Item = get(id); - if (rs2Item == null) return false; - invokeMenu(rs2Item, action, identifier); - return true; - } /** * Interacts with an item with the specified name in the inventory using the first available action. @@ -1888,9 +1874,8 @@ public static boolean hasRunePouch() { * * @param rs2Item The current item to interact with. * @param action The action to be used on the item. - * @param providedIdentifier The identifier to use; if -1, compute using the old logic. */ - private static void invokeMenu(Rs2ItemModel rs2Item, String action, int providedIdentifier) { + private static void invokeMenu(Rs2ItemModel rs2Item, String action) { if (rs2Item == null) return; Rs2Tab.switchToInventoryTab(); Microbot.status = action + " " + rs2Item.getName(); @@ -1943,10 +1928,15 @@ private static void invokeMenu(Rs2ItemModel rs2Item, String action, int provided if (simpleIndex != -1) { identifier = simpleIndex + 1; } else { + // We could not find the action in the item widget's actions, so we try to find it in the sub-menu actions Map.Entry subActionMap = rs2Item.getIndexOfSubAction(action); + if (subActionMap == null) { + Microbot.log("Item=" + rs2Item.getName() + " does not have action=" + action, Level.ERROR); + return; + } // The main menu index depends on the inventory interface from which this item is interacted with - int mainMenuIndex = java.util.Arrays.asList(actions).indexOf(subActionMap.getKey()) + 1; - identifier = NewMenuEntry.findIdentifier(subActionMap.getValue() + 1, mainMenuIndex); + int mainMenuIndex = java.util.Arrays.asList(actions).indexOf(subActionMap.getKey()); + identifier = NewMenuEntry.findIdentifier(subActionMap.getValue() + 1, mainMenuIndex + 1); target = ""; } } @@ -1969,16 +1959,6 @@ private static void invokeMenu(Rs2ItemModel rs2Item, String action, int provided } - /** - * Method executes menu actions - * - * @param rs2Item Current item to interact with - * @param action Action used on the item - */ - private static void invokeMenu(Rs2ItemModel rs2Item, String action) { - invokeMenu(rs2Item, action, -1); - } - public static Widget getInventory() { final int BANK_PIN_INVENTORY_ITEM_CONTAINER = 17563648; final int SHOP_INVENTORY_ITEM_CONTAINER = 19726336; From e2cb049b430f1080b4979e44fc969602138d478d Mon Sep 17 00:00:00 2001 From: krulvis Date: Mon, 6 Oct 2025 23:17:49 +0200 Subject: [PATCH 3/3] feat(Rs2Walker): Allow use of sub-menu interaction and further code cleanup and efficiency improvements --- .../microbot/util/dialogues/Rs2Dialogue.java | 49 +++- .../microbot/util/equipment/Rs2Equipment.java | 3 +- .../microbot/util/inventory/Rs2ItemModel.java | 38 ++- .../microbot/util/walker/Rs2Walker.java | 252 ++++++------------ 4 files changed, 160 insertions(+), 182 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/dialogues/Rs2Dialogue.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/dialogues/Rs2Dialogue.java index 4d5a0568a1b..93981b2c736 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/dialogues/Rs2Dialogue.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/dialogues/Rs2Dialogue.java @@ -9,6 +9,7 @@ import java.awt.event.KeyEvent; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -84,7 +85,7 @@ private static boolean hasDeathContinue() { * * @return true if the "Continue" option is visible in either sprite-based dialogue, false otherwise. */ - + private static boolean hasSpriteContinue() { return Rs2Widget.isWidgetVisible(InterfaceID.DIALOG_SPRITE, 0) || Rs2Widget.isWidgetVisible(InterfaceID.DIALOG_SPRITE, 3) || Rs2Widget.isWidgetVisible(InterfaceID.DIALOG_DOUBLE_SPRITE, 4); } @@ -138,10 +139,10 @@ private static boolean hasSpellFilterContinue() { public static boolean hasSelectAnOption() { boolean isWidgetVisible = Rs2Widget.isWidgetVisible(InterfaceID.DIALOG_OPTION, 1); if (!isWidgetVisible) return false; - + Widget widget = Rs2Widget.getWidget(InterfaceID.DIALOG_OPTION, 1); if (widget == null) return false; - + return widget.getDynamicChildren() != null; } @@ -337,6 +338,36 @@ public static boolean keyPressForDialogueOption(int index) { return true; } + /** + * Attempts to click on a dialogue option based on the specified text(s). The method + * performs a partial matching depending on the provided parameter and will return + * whether the operation was successful. + * + * @param texts varargs parameter representing the*/ + public static boolean clickOption(String... texts){ + return clickOption(false, texts); + } + + /** + * Attempts to click on a dialogue option based on the specified text(s). The method can + * perform an exact or partial matching depending on the provided parameter and will return + * whether the operation was successful. + * + * @param exact specifies whether the matching should be exact (true) or partial (false). + * @param texts varargs parameter representing the*/ + public static boolean clickOption(boolean exact, String... texts){ + if (!hasSelectAnOption()) return false; + List options = getDialogueOptions(); + if(options.isEmpty()) return false; + + Widget dialogueOption = options.stream() + .filter(dialop -> exact ? Arrays.stream(texts).anyMatch(t -> dialop.getText().equalsIgnoreCase(t)) : Arrays.stream(texts).anyMatch(t -> dialop.getText().toLowerCase().contains(t.toLowerCase()))) + .findFirst() + .orElse(null); + if (dialogueOption == null) return false; + return Rs2Widget.clickWidget(dialogueOption); + } + /** * Attempts to click on a dialogue option widget with the specified text. * @@ -449,7 +480,7 @@ public static boolean sleepUntilHasQuestion(String text, boolean exact) { public static boolean sleepUntilHasQuestion(String text) { return sleepUntilHasQuestion(text, false); } - + /** * Checks if the combination dialogue widget is currently visible. * @@ -597,11 +628,11 @@ public static boolean clickCombinationOption(String text, boolean exact) { if (!hasCombinationDialogue()) return false; Widget option = getCombinationOption(text, exact); - + if (option == null) return false; - + return Rs2Widget.clickWidget(option); - + } /** @@ -627,7 +658,7 @@ public static boolean sleepUntilHasCombinationDialogue() { * Pauses the current thread until a specific combination dialogue option becomes available. * *

This method continuously checks for a combination dialogue option that matches the specified - * text. If an exact match is required, it will search for an option that exactly matches the text; + * text. If an exact match is required, it will search for an option that exactly matches the text; * otherwise, it will look for an option containing the text. * * @param text the text to search for within the combination dialogue options. @@ -649,7 +680,7 @@ public static boolean sleepUntilHasCombinationOption(String text, boolean exact) public static boolean sleepUntilHasCombinationOption(String text) { return sleepUntilHasCombinationOption(text, false); } - + /** * Determines whether the game is currently in a cutscene. *

diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/equipment/Rs2Equipment.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/equipment/Rs2Equipment.java index 65532a0db14..b6a64928f13 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/equipment/Rs2Equipment.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/equipment/Rs2Equipment.java @@ -330,7 +330,7 @@ public static void invokeMenu(Rs2ItemModel rs2Item, String action) { } int mainMenuIndex = actions.indexOf(subMenuEntry.getKey()); if (mainMenuIndex < 0) { - Microbot.log("Cannot find action=%s, in main actions=%s, mainMenuIndex=%s", subMenuEntry.getKey(), String.join(", ", actions), mainMenuIndex); + Microbot.log("Cannot find action=%s, in main actions=%s, mainMenuIndex=%s", subMenuEntry.getKey(), String.join(", ", actions), mainMenuIndex, Level.ERROR); return; } target = ""; @@ -363,6 +363,5 @@ public static void invokeMenu(Rs2ItemModel rs2Item, String action) { } Microbot.doInvoke(new NewMenuEntry(param0, param1, menuAction.getId(), identifier, -1, target), new Rectangle(1, 1, Microbot.getClient().getCanvasWidth(), Microbot.getClient().getCanvasHeight())); - //Rs2Reflection.invokeMenu(param0, param1, menuAction.getId(), identifier, rs2Item.id, action, target, -1, -1); } } \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/inventory/Rs2ItemModel.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/inventory/Rs2ItemModel.java index c7eaa4c4a1d..1863095be4b 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/inventory/Rs2ItemModel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/inventory/Rs2ItemModel.java @@ -174,7 +174,7 @@ public String[][] getSubops() { /** * Retrieves the index of a sub-action from the sub-actions list, matching the given action. - * + *

* This method searches through the sub-actions of the item, attempting to find a match * for the specified action. If a match is found, it returns the corresponding main action * and index of the sub-action. If no match is found or if the sub-actions are unavailable, @@ -182,7 +182,7 @@ public String[][] getSubops() { * * @param action The action to search for in the sub-actions list. Case-insensitive comparison is used. * @return A Map.Entry containing the inventory action (String) and the index of the sub-action (Integer) - * if a match is found; otherwise, returns a Map.Entry with null and -1. + * if a match is found; otherwise, returns a Map.Entry with null and -1. */ public Map.Entry getIndexOfSubAction(String action) { if (action == null) return Map.entry(null, -1); @@ -568,4 +568,38 @@ private void initializeFromComposition(ItemComposition itemComposition) { return true; }); } + + /** + * Retrieves an inventory action that contains the specified substring, ignoring case sensitivity. + * Searches through all non-null inventory actions and returns the first match. + * + * @param partOfAction The substring to search for within the inventory actions. Case-insensitive comparison is used. + * @return The first matching inventory action that contains the specified substring, or null if no match is found. + */ + public String getAction(String partOfAction) { + return Arrays.stream(getInventoryActions()) + .filter(Objects::nonNull) + .filter(x -> x.toLowerCase().contains(partOfAction)) + .findFirst().orElse(null); + } + + /** + * Retrieves the most relevant action from a list of possible actions by matching them against + * the inventory actions of the item. The relevance is determined by the order of the given actions + * and their occurrence within the inventory actions. + * + * @param actions The list of actions to search for, provided as varargs. Null values will be ignored. + * @return The most relevant matching action from the inventory actions, or null if no match is found. + */ + public String getActionFromList(List actions) { + return Arrays.stream(getInventoryActions()) + .filter(action -> action != null && actions.stream().anyMatch(keyword -> action.toLowerCase().contains(keyword.toLowerCase()))) + .min(Comparator.comparingInt(action -> + actions.stream() + .filter(keyword -> action.toLowerCase().contains(keyword.toLowerCase())) + .mapToInt(actions::indexOf) + .findFirst() + .orElse(Integer.MAX_VALUE) + )).orElse(null); + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/Rs2Walker.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/Rs2Walker.java index f92dece79c8..2571464b3e5 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/Rs2Walker.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/Rs2Walker.java @@ -3,26 +3,24 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder; import lombok.Setter; import lombok.extern.slf4j.Slf4j; -import net.runelite.api.Point; import net.runelite.api.*; +import net.runelite.api.Point; import net.runelite.api.annotations.Component; import net.runelite.api.coords.LocalPoint; import net.runelite.api.coords.WorldArea; import net.runelite.api.coords.WorldPoint; +import net.runelite.api.gameval.*; import net.runelite.api.gameval.ItemID; import net.runelite.api.gameval.NpcID; import net.runelite.api.gameval.ObjectID; -import net.runelite.api.gameval.*; import net.runelite.api.widgets.ComponentID; import net.runelite.api.widgets.Widget; import net.runelite.client.plugins.devtools.MovementFlag; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.globval.enums.InterfaceTab; -import net.runelite.client.plugins.microbot.shortestpath.ShortestPathConfig; -import net.runelite.client.plugins.microbot.shortestpath.ShortestPathPlugin; -import net.runelite.client.plugins.microbot.shortestpath.Transport; -import net.runelite.client.plugins.microbot.shortestpath.TransportType; +import net.runelite.client.plugins.microbot.shortestpath.*; import net.runelite.client.plugins.microbot.shortestpath.pathfinder.Pathfinder; +import net.runelite.client.plugins.microbot.shortestpath.pathfinder.PathfinderConfig; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; @@ -55,12 +53,10 @@ import javax.inject.Named; import java.awt.*; -import java.util.List; import java.util.*; +import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -1836,7 +1832,8 @@ private static boolean handleTeleportSpell(Transport transport) { } private static boolean handleTeleportItem(Transport transport) { - if (Rs2Pvp.isInWilderness() && (Rs2Pvp.getWildernessLevelFrom(Rs2Player.getWorldLocation()) > (transport.getMaxWildernessLevel() + 1))) return false; + if (Rs2Pvp.isInWilderness() && (Rs2Pvp.getWildernessLevelFrom(Rs2Player.getWorldLocation()) > (transport.getMaxWildernessLevel() + 1))) + return false; boolean succesfullAction = false; for (Set itemIds : transport.getItemIdRequirements()) { if (succesfullAction) @@ -1848,127 +1845,94 @@ private static boolean handleTeleportItem(Transport transport) { if (succesfullAction) break; //If an action is succesfully we break out of the loop - succesfullAction = handleInventoryTeleports(transport, itemId) || handleWearableTeleports(transport, itemId); - + succesfullAction = handleWearableTeleports(transport, itemId) || handleInventoryTeleports(transport, itemId); } } return succesfullAction; } - private static boolean handleInventoryTeleports(Transport transport, int itemId) { + private static boolean handleInventoryTeleports(Transport transport, int itemId) { Rs2ItemModel rs2Item = Rs2Inventory.get(itemId); if (rs2Item == null) return false; - List locationKeyWords = Arrays.asList("farm", "monastery", "lletya", "prifddinas", "rellekka", "waterbirth island", "neitiznot", "jatiszo", - "ver sinhaza", "darkmeyer", "slepe", "troll stronghold", "weiss", "ecto", "burgh", "duradel", "gem mine", "nardah", "kalphite cave", "Tele to POH", "inside", "outside", - "kourend woodland", "mount karuulm", "outside", "fishing guild", "otto's grotto", "stronghold slayer cave", "slayer tower", "fremennik", "tarn's lair", "dark beasts"); List genericKeyWords = Arrays.asList("invoke", "empty", "consume", "open", "teleport", "rub", "break", "reminisce", "signal", "play", "commune", "squash"); + // Return true when the item can be used to teleport to multiple places boolean hasMultipleDestination = transport.getDisplayInfo().contains(":"); String destination = hasMultipleDestination ? transport.getDisplayInfo().split(":")[1].trim().toLowerCase() : transport.getDisplayInfo().trim().toLowerCase(); - // Check location keywords based on multiple destinations - String itemAction = hasMultipleDestination - ? Arrays.stream(rs2Item.getInventoryActions()) - .filter(action -> action != null && locationKeyWords.stream().anyMatch(keyword -> - destination.contains(keyword.toLowerCase()) && action.toLowerCase().contains(keyword.toLowerCase()))) - .findFirst() - .orElse(null) - : Arrays.stream(rs2Item.getInventoryActions()) - .filter(action -> action != null && locationKeyWords.stream().anyMatch(keyword -> action.toLowerCase().contains(keyword.toLowerCase()))) - .findFirst() - .orElse(null); + boolean wildernessTransport = PathfinderConfig.isInWilderness(WorldPointUtil.packWorldPoint(transport.getDestination())); - // If no location-based action found, try generic actions - if (itemAction == null) { + log.debug("Trying to find action for destination={}", destination); + // Check if item has destination as direct action + String itemAction = rs2Item.getAction(destination); - itemAction = Arrays.stream(rs2Item.getInventoryActions()) - .filter(action -> action != null && genericKeyWords.stream().anyMatch(keyword -> action.toLowerCase().contains(keyword.toLowerCase()))) - .min(Comparator.comparingInt(action -> - genericKeyWords.stream() - .filter(keyword -> action.toLowerCase().contains(keyword.toLowerCase())) - .mapToInt(genericKeyWords::indexOf) - .findFirst() - .orElse(Integer.MAX_VALUE) - )) - .orElse(null); + // Check if item has destination as sub-menu action + if (itemAction == null && rs2Item.getIndexOfSubAction(destination).getKey() != null) { + itemAction = destination; } - if (itemAction == null) return false; - - // Check the first character of the item name, if it is a number return true - boolean hasMenuOption = !transport.getDisplayInfo().isEmpty() && Character.isDigit(transport.getDisplayInfo().charAt(0)); - - if (!hasMenuOption) { - if (Rs2Inventory.interact(itemId, itemAction)) { - if (itemAction.equalsIgnoreCase("rub") && (itemId == ItemID.XERIC_TALISMAN || transport.getDisplayInfo().toLowerCase().contains("skills necklace"))) { - return interactWithAdventureLog(transport); - } - - if (itemAction.equalsIgnoreCase("rub") && transport.getDisplayInfo().toLowerCase().contains("burning amulet")) { - Rs2Dialogue.sleepUntilInDialogue(); - Rs2Dialogue.clickOption(destination); - Rs2Dialogue.sleepUntilHasDialogueOption("Okay, teleport to level"); - Rs2Dialogue.clickOption("Okay, teleport to level"); - } - - if (itemAction.equalsIgnoreCase("teleport") && transport.getDisplayInfo().toLowerCase().contains("revenant cave teleport")) { - Rs2Dialogue.sleepUntilHasDialogueOption("Yes, teleport me now"); - Rs2Dialogue.clickOption("Yes, teleport me now"); - } + // If there's only one destination with the item possible, a generic action will also work + if (itemAction == null && !hasMultipleDestination) { + itemAction = rs2Item.getActionFromList(genericKeyWords); + } - if (itemAction.equalsIgnoreCase("break") && itemId == ItemID.LUNAR_TABLET_ICE_PLATEAU_TELEPORT) { - Rs2Dialogue.sleepUntilHasQuestion("Teleport into the DEEP wilderness?"); - Rs2Dialogue.clickOption("Yes"); - } + if (itemAction != null) { + boolean interaction = Rs2Inventory.interact(rs2Item, itemAction); + if (!interaction) { + return false; + } else if (wildernessTransport) { + Rs2Dialogue.sleepUntilInDialogue(); + return Rs2Dialogue.clickOption("Yes", "Okay"); + } + return true; + } - if (itemAction.equalsIgnoreCase("teleport") && transport.getDisplayInfo().toLowerCase().contains("slayer ring")) { - Rs2Dialogue.sleepUntilSelectAnOption(); - Rs2Dialogue.clickOption(destination); - } + // If no location-based action found, try generic actions + itemAction = rs2Item.getActionFromList(genericKeyWords); - if (itemAction.equalsIgnoreCase("rub") || itemAction.equalsIgnoreCase("reminisce")) { - Rs2Dialogue.sleepUntilSelectAnOption(); - Rs2Dialogue.clickOption(destination); - } + if (itemAction == null) { + log.debug("No generic keyword found for={}, genericKeywords={}", itemAction, String.join(",", genericKeyWords)); + return false; + } - if (itemAction.equalsIgnoreCase("open") && itemId == ItemID.BOOKOFSCROLLS_CHARGED) { - handleMasterScrollBook(destination); - } + if (Rs2Inventory.interact(itemId, itemAction)) { + log.debug("Traveling with genericAction={}, to {} - ({})", itemAction, transport.getDisplayInfo(), transport.getDestination()); - log.info("Traveling to {} - ({})", transport.getDisplayInfo(), transport.getDestination()); - return sleepUntilTrue(() -> Rs2Player.getWorldLocation().distanceTo2D(transport.getDestination()) < OFFSET, 100, 8000); + if (itemAction.equalsIgnoreCase("open") && itemId == ItemID.BOOKOFSCROLLS_CHARGED) { + return handleMasterScrollBook(destination); + } else if (wildernessTransport) { + Rs2Dialogue.sleepUntilInDialogue(); + return Rs2Dialogue.clickOption("Yes", "Okay"); + } else { + log.info("Unsure how to handle this itemTransport={} action={}", transport, itemAction); } } - else { - return interactWithNewRuneliteMenu(transport,itemId); - } - return false; } private static boolean handleWearableTeleports(Transport transport, int itemId) { - if (Rs2Equipment.isWearing(itemId)) { - if (transport.getDisplayInfo().contains(":")) { - String[] values = transport.getDisplayInfo().split(":"); - String destination = values[1].trim().toLowerCase(); - Rs2ItemModel rs2Item = Rs2Equipment.get(itemId); - if (transport.getDisplayInfo().toLowerCase().contains("slayer ring")) { - Rs2Equipment.invokeMenu(rs2Item, "teleport"); - Rs2Dialogue.sleepUntilSelectAnOption(); - Rs2Dialogue.clickOption(destination); - } else { - Rs2Equipment.invokeMenu(rs2Item, destination); - if (transport.getDisplayInfo().toLowerCase().contains("burning amulet")) { - Rs2Dialogue.sleepUntilInDialogue(); - Rs2Dialogue.clickOption("Okay, teleport to level"); - } + Rs2ItemModel rs2Item = Rs2Equipment.get(itemId); + if (rs2Item == null) return false; + if (transport.getDisplayInfo().contains(":")) { + String[] values = transport.getDisplayInfo().split(":"); + String destination = values[1].trim().toLowerCase(); + + if (transport.getDisplayInfo().toLowerCase().contains("slayer ring")) { + Rs2Equipment.invokeMenu(rs2Item, "teleport"); + Rs2Dialogue.sleepUntilSelectAnOption(); + Rs2Dialogue.clickOption(destination); + } else { + Rs2Equipment.invokeMenu(rs2Item, destination); + if (transport.getDisplayInfo().toLowerCase().contains("burning amulet")) { + Rs2Dialogue.sleepUntilInDialogue(); + Rs2Dialogue.clickOption("Okay, teleport to level"); } - log.info("Traveling to {} - ({})", transport.getDisplayInfo(), transport.getDestination()); - return sleepUntilTrue(() -> Rs2Player.getWorldLocation().distanceTo2D(transport.getDestination()) < OFFSET, 100, 8000); } + log.info("Traveling to {} - ({})", transport.getDisplayInfo(), transport.getDestination()); + return true; } return false; } @@ -2322,27 +2286,27 @@ private static boolean handleQuetzal(Transport transport) { return false; } - private static void handleMasterScrollBook(String destination) { - boolean isMasterScrollBookOpen = sleepUntilTrue(() -> Rs2Widget.isWidgetVisible(InterfaceID.Bookofscrolls.CONTENTS), 100, 10000); - if (!isMasterScrollBookOpen) { - log.error("Master Scroll Book did not open within timeout period"); - return; - } + private static boolean handleMasterScrollBook(String destination) { + boolean isMasterScrollBookOpen = sleepUntilTrue(() -> Rs2Widget.isWidgetVisible(InterfaceID.Bookofscrolls.CONTENTS), 100, 10000); + if (!isMasterScrollBookOpen) { + log.error("Master Scroll Book did not open within timeout period"); + return false; + } - Widget bookOfScrollsWidget = Rs2Widget.getWidget(InterfaceID.Bookofscrolls.CONTENTS); - List bookOfScrollsChildren = Arrays.stream(bookOfScrollsWidget.getStaticChildren()) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - - Widget destinationWidget = Rs2Widget.findWidget(destination, bookOfScrollsChildren, false); - if (destinationWidget == null) return; - Rs2Widget.clickWidget(destinationWidget); - if (destination.equalsIgnoreCase("Revenant cave")) { - Rs2Dialogue.sleepUntilInDialogue(); - Rs2Dialogue.clickOption("Yes, teleport me now"); - } - Rs2Player.waitForAnimation(); - } + Widget bookOfScrollsWidget = Rs2Widget.getWidget(InterfaceID.Bookofscrolls.CONTENTS); + List bookOfScrollsChildren = Arrays.stream(bookOfScrollsWidget.getStaticChildren()) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + Widget destinationWidget = Rs2Widget.findWidget(destination, bookOfScrollsChildren, false); + if (destinationWidget == null) return false; + boolean interaction = Rs2Widget.clickWidget(destinationWidget); + if (interaction && destination.equalsIgnoreCase("Revenant cave")) { + Rs2Dialogue.sleepUntilInDialogue(); + return Rs2Dialogue.clickOption("Yes, teleport me now"); + } + return interaction; + } private static boolean handleMagicCarpet(Transport transport) { final int flyingPoseAnimation = 6936; @@ -2408,56 +2372,6 @@ private static boolean interactWithAdventureLog(Transport transport) { return sleepUntilTrue(() -> Rs2Player.getWorldLocation().distanceTo2D(transport.getDestination()) < OFFSET, 100, 5000); } - private static boolean interactWithNewRuneliteMenu(Transport transport,int itemId) { - if (transport.getDisplayInfo() == null || transport.getDisplayInfo().isEmpty()) return false; - - Pattern pattern = Pattern.compile("^(\\d+)\\."); - Matcher matcher = pattern.matcher(transport.getDisplayInfo()); - if (matcher.find()) { - int menuOption = Integer.parseInt(matcher.group(1)); - String[] values = transport.getDisplayInfo().split(":"); - String destination = values[1].trim(); - int identifier = NewMenuEntry.findIdentifier(menuOption, getIdentifierOffset(transport.getDisplayInfo())); - Rs2Inventory.interact(itemId, destination, identifier); - if (transport.getDisplayInfo().toLowerCase().contains("burning amulet")) { - Rs2Dialogue.sleepUntilHasDialogueOption("Okay, teleport to level"); - Rs2Dialogue.clickOption("Okay, teleport to level"); - } - log.info("Traveling to {} - ({})", transport.getDisplayInfo(), transport.getDestination()); - return sleepUntilTrue(() -> Rs2Player.getWorldLocation().distanceTo2D(transport.getDestination()) < OFFSET, 100, 5000); - } - return false; - } - - private static int getIdentifierOffset(String itemName) { - String lowerCaseItemName = itemName.toLowerCase(); - if (lowerCaseItemName.contains("ring of dueling") || - lowerCaseItemName.contains("games necklace") || - lowerCaseItemName.contains("skills necklace") || - lowerCaseItemName.contains("amulet of glory") || - lowerCaseItemName.contains("ring of wealth") || - lowerCaseItemName.contains("combat bracelet") || - lowerCaseItemName.contains("digsite pendant") || - lowerCaseItemName.contains("necklace of passage") || - lowerCaseItemName.contains("camulet") || - lowerCaseItemName.contains("burning amulet") || - lowerCaseItemName.contains("giantsoul amulet")) { - return 6; - } else if (lowerCaseItemName.contains("xeric's talisman") || - lowerCaseItemName.contains("slayer ring") || - lowerCaseItemName.contains("construct. cape") || - lowerCaseItemName.contains("pendant of ates")) { - return 4; - } else if (lowerCaseItemName.contains("book of the dead")) { - return 3; - } else if (lowerCaseItemName.contains("kharedst's memoirs") || - lowerCaseItemName.contains("enchanted lyre")) { - return 2; - } else { - return 4; // Default offset if no match is found - } - } - private static boolean handleGlider(Transport transport) { int TA_QUIR_PRIW = 9043972; int SINDARPOS = 9043975;