From 0cfc1cdc94008912b2920540774a81f48f01e5d9 Mon Sep 17 00:00:00 2001 From: krulvis Date: Sun, 5 Oct 2025 16:31:25 +0200 Subject: [PATCH 01/25] 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 02/25] 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 03/25] 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; From 69e8ed2d8c9f4f6a10180c8c727ab8f6b7d7204d Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 9 Oct 2025 19:27:03 -0700 Subject: [PATCH 04/25] - Rs2Equipment - Added bounds for equipment slot widget for un equipping --- .../microbot/util/equipment/Rs2Equipment.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 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..106d272d825 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 @@ -6,12 +6,15 @@ import net.runelite.api.ItemContainer; import net.runelite.api.MenuAction; import net.runelite.api.events.ItemContainerChanged; +import net.runelite.api.gameval.InterfaceID; import net.runelite.api.gameval.InventoryID; +import net.runelite.api.widgets.Widget; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.globval.enums.InterfaceTab; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; import net.runelite.client.plugins.microbot.util.menu.NewMenuEntry; import net.runelite.client.plugins.microbot.util.tabs.Rs2Tab; +import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; import org.slf4j.event.Level; import java.awt.Rectangle; @@ -330,32 +333,43 @@ public static void invokeMenu(Rs2ItemModel rs2Item, String action) { return; } } - + Rectangle rectangle = new Rectangle(1, 1, Microbot.getClient().getCanvasWidth(), Microbot.getClient().getCanvasHeight()); if (rs2Item.getSlot() == EquipmentInventorySlot.CAPE.getSlotIdx()) { param1 = 25362448; + rectangle = Rs2Widget.getWidget(InterfaceID.WORNITEMS,16).getBounds(); } else if (rs2Item.getSlot() == EquipmentInventorySlot.HEAD.getSlotIdx()) { param1 = 25362447; + rectangle = Rs2Widget.getWidget(InterfaceID.WORNITEMS,15).getBounds(); } else if (rs2Item.getSlot() == EquipmentInventorySlot.AMMO.getSlotIdx()) { param1 = 25362457; + rectangle = Rs2Widget.getWidget(InterfaceID.WORNITEMS,25).getBounds(); } else if (rs2Item.getSlot() == EquipmentInventorySlot.AMULET.getSlotIdx()) { param1 = 25362449; + rectangle = Rs2Widget.getWidget(InterfaceID.WORNITEMS,17).getBounds(); } else if (rs2Item.getSlot() == EquipmentInventorySlot.WEAPON.getSlotIdx()) { param1 = 25362450; + rectangle = Rs2Widget.getWidget(InterfaceID.WORNITEMS,18).getBounds(); } else if (rs2Item.getSlot() == EquipmentInventorySlot.BODY.getSlotIdx()) { param1 = 25362451; + rectangle = Rs2Widget.getWidget(InterfaceID.WORNITEMS,19).getBounds(); } else if (rs2Item.getSlot() == EquipmentInventorySlot.SHIELD.getSlotIdx()) { param1 = 25362452; + rectangle = Rs2Widget.getWidget(InterfaceID.WORNITEMS,20).getBounds(); } else if (rs2Item.getSlot() == EquipmentInventorySlot.LEGS.getSlotIdx()) { param1 = 25362453; + rectangle = Rs2Widget.getWidget(InterfaceID.WORNITEMS,21).getBounds(); } else if (rs2Item.getSlot() == EquipmentInventorySlot.GLOVES.getSlotIdx()) { param1 = 25362454; + rectangle = Rs2Widget.getWidget(InterfaceID.WORNITEMS,22).getBounds(); } else if (rs2Item.getSlot() == EquipmentInventorySlot.BOOTS.getSlotIdx()) { param1 = 25362455; + rectangle = Rs2Widget.getWidget(InterfaceID.WORNITEMS,23).getBounds(); } else if (rs2Item.getSlot() == EquipmentInventorySlot.RING.getSlotIdx()) { param1 = 25362456; + rectangle = Rs2Widget.getWidget(InterfaceID.WORNITEMS,24).getBounds(); } - 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, rs2Item.getName()), rectangle); //Rs2Reflection.invokeMenu(param0, param1, menuAction.getId(), identifier, rs2Item.id, action, target, -1, -1); } } \ No newline at end of file From 51508f477a603c8dbd16d3f5df3581945da3282d Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 11 Oct 2025 10:51:59 -0400 Subject: [PATCH 05/25] worldarea: fix plane passed to extraCondition --- .../src/main/java/net/runelite/api/coords/WorldArea.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/runelite-api/src/main/java/net/runelite/api/coords/WorldArea.java b/runelite-api/src/main/java/net/runelite/api/coords/WorldArea.java index 6ed585fcd09..beaf63b79fd 100644 --- a/runelite-api/src/main/java/net/runelite/api/coords/WorldArea.java +++ b/runelite-api/src/main/java/net/runelite/api/coords/WorldArea.java @@ -380,7 +380,7 @@ public boolean canTravelInDirection(WorldView wv, int dx, int dy, for (int x = startX; x <= endX; x++) { if ((collisionDataFlags[x][checkY] & yFlags) != 0 || - !extraCondition.test(WorldPoint.fromScene(wv, x, checkY, wv.getPlane()))) + !extraCondition.test(WorldPoint.fromScene(wv, x, checkY, plane))) { // Collision while attempting to travel along the y axis return false; @@ -408,7 +408,7 @@ public boolean canTravelInDirection(WorldView wv, int dx, int dy, if (dx != 0 && dy != 0) { if ((collisionDataFlags[checkX][checkY] & xyFlags) != 0 || - !extraCondition.test(WorldPoint.fromScene(wv, checkX, checkY, wv.getPlane()))) + !extraCondition.test(WorldPoint.fromScene(wv, checkX, checkY, plane))) { // Collision while attempting to travel diagonally return false; @@ -420,7 +420,7 @@ public boolean canTravelInDirection(WorldView wv, int dx, int dy, if (width == 1) { if ((collisionDataFlags[checkX][checkY - dy] & xFlags) != 0 && - extraCondition.test(WorldPoint.fromScene(wv, checkX, startY, wv.getPlane()))) + extraCondition.test(WorldPoint.fromScene(wv, checkX, startY, plane))) { return false; } @@ -428,7 +428,7 @@ public boolean canTravelInDirection(WorldView wv, int dx, int dy, if (height == 1) { if ((collisionDataFlags[checkX - dx][checkY] & yFlags) != 0 && - extraCondition.test(WorldPoint.fromScene(wv, startX, checkY, wv.getPlane()))) + extraCondition.test(WorldPoint.fromScene(wv, startX, checkY, plane))) { return false; } From ef001ea75666b968bc87f0b81451fbd00bb354f4 Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 11 Oct 2025 10:58:42 -0400 Subject: [PATCH 06/25] npc highlight: fix untagging npcs in instances Clicking tag on a tagged npc in instances wouldn't work because they aren't added to npcTags --- .../client/plugins/npchighlight/NpcIndicatorsPlugin.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcIndicatorsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcIndicatorsPlugin.java index bfff67e53c0..ab027363954 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcIndicatorsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcIndicatorsPlugin.java @@ -508,10 +508,11 @@ private void tag(MenuEntry entry) if (entry.getOption().equals(TAG) || entry.getOption().equals(UNTAG)) { - final boolean removed = npcTags.remove(id); + final boolean exists = highlightedNpcs.containsKey(npc); - if (removed) + if (exists) { + npcTags.remove(id); if (!highlightMatchesNPCName(npc.getName())) { highlightedNpcs.remove(npc); From 54bb840bf735d4afabaada6850f5b53a1e1c00e1 Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 11 Oct 2025 11:20:48 -0400 Subject: [PATCH 07/25] client: add worldview support to overlays --- .../src/main/java/net/runelite/api/Actor.java | 2 +- .../runelite/api/CameraFocusableEntity.java | 34 ++ .../main/java/net/runelite/api/Client.java | 15 + .../java/net/runelite/api/GraphicsObject.java | 6 + .../java/net/runelite/api/Perspective.java | 325 +++++++++++++----- .../java/net/runelite/api/WorldEntity.java | 17 +- .../main/java/net/runelite/api/WorldView.java | 34 ++ .../net/runelite/api/coords/LocalPoint.java | 8 +- .../api/events/WorldEntityDespawned.java | 37 ++ .../api/events/WorldEntitySpawned.java | 40 +++ .../runelite/api/events/WorldViewLoaded.java | 37 ++ .../api/events/WorldViewUnloaded.java | 37 ++ .../game/npcoverlay/NpcOverlayService.java | 30 +- .../plugins/grounditems/GroundItem.java | 5 +- .../grounditems/GroundItemsOverlay.java | 17 +- .../grounditems/GroundItemsPlugin.java | 29 +- .../client/plugins/grounditems/Lootbeam.java | 5 + .../GroundMarkerMinimapOverlay.java | 54 +-- .../groundmarkers/GroundMarkerOverlay.java | 47 +-- .../groundmarkers/GroundMarkerPlugin.java | 74 ++-- .../InteractHighlightOverlay.java | 3 +- .../InteractHighlightPlugin.java | 16 +- .../npchighlight/NpcIndicatorsPlugin.java | 46 ++- .../ObjectIndicatorsOverlay.java | 9 +- .../ObjectIndicatorsPlugin.java | 85 +++-- .../PlayerIndicatorsService.java | 17 +- .../tileindicators/TileIndicatorsOverlay.java | 8 +- .../overlay/outline/ModelOutlineRenderer.java | 48 +-- .../client/util/GameEventManager.java | 138 ++++---- .../loottracker/LootTrackerPluginTest.java | 2 +- .../farming/CompostTrackerTest.java | 1 + 31 files changed, 878 insertions(+), 348 deletions(-) create mode 100644 runelite-api/src/main/java/net/runelite/api/CameraFocusableEntity.java create mode 100644 runelite-api/src/main/java/net/runelite/api/events/WorldEntityDespawned.java create mode 100644 runelite-api/src/main/java/net/runelite/api/events/WorldEntitySpawned.java create mode 100644 runelite-api/src/main/java/net/runelite/api/events/WorldViewLoaded.java create mode 100644 runelite-api/src/main/java/net/runelite/api/events/WorldViewUnloaded.java diff --git a/runelite-api/src/main/java/net/runelite/api/Actor.java b/runelite-api/src/main/java/net/runelite/api/Actor.java index afd06f3f8c9..794075e51e8 100644 --- a/runelite-api/src/main/java/net/runelite/api/Actor.java +++ b/runelite-api/src/main/java/net/runelite/api/Actor.java @@ -37,7 +37,7 @@ /** * Represents a RuneScape actor/entity. */ -public interface Actor extends Renderable +public interface Actor extends Renderable, CameraFocusableEntity { /** * Get the {@link WorldView} this actor belongs to diff --git a/runelite-api/src/main/java/net/runelite/api/CameraFocusableEntity.java b/runelite-api/src/main/java/net/runelite/api/CameraFocusableEntity.java new file mode 100644 index 00000000000..95bee2a332c --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/CameraFocusableEntity.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2025, 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.api; + +import net.runelite.api.coords.LocalPoint; + +public interface CameraFocusableEntity +{ + WorldView getWorldView(); + + LocalPoint getCameraFocus(); +} 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 d9a63b72c9d..6f5cc2fcc48 100644 --- a/runelite-api/src/main/java/net/runelite/api/Client.java +++ b/runelite-api/src/main/java/net/runelite/api/Client.java @@ -2417,4 +2417,19 @@ default Tile getSelectedSceneTile() * @return the newly created SceneTilePaint */ SceneTilePaint createSceneTilePaint(int swColor, int seColor, int neColor, int nwColor, int texture, int minimapRgb, boolean flatShade); + + /** + * Get the entity that the camera is focused on + * + * @return + */ + CameraFocusableEntity getCameraFocusEntity(); + + /** + * Find the worldview a given worldpoint belongs in + * @param point + * @return + */ + @Nonnull + WorldView findWorldViewFromWorldPoint(WorldPoint point); } diff --git a/runelite-api/src/main/java/net/runelite/api/GraphicsObject.java b/runelite-api/src/main/java/net/runelite/api/GraphicsObject.java index cc094248e0e..f24d9a4114b 100644 --- a/runelite-api/src/main/java/net/runelite/api/GraphicsObject.java +++ b/runelite-api/src/main/java/net/runelite/api/GraphicsObject.java @@ -32,6 +32,12 @@ */ public interface GraphicsObject extends Renderable { + /** + * Get the {@link WorldEntity} this spotanim is on. + * @return + */ + WorldView getWorldView(); + /** * The graphics object ID. * diff --git a/runelite-api/src/main/java/net/runelite/api/Perspective.java b/runelite-api/src/main/java/net/runelite/api/Perspective.java index 0d5e31cf517..8a6d915b91e 100644 --- a/runelite-api/src/main/java/net/runelite/api/Perspective.java +++ b/runelite-api/src/main/java/net/runelite/api/Perspective.java @@ -34,7 +34,6 @@ import java.util.List; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import static net.runelite.api.Constants.EXTENDED_SCENE_SIZE; import static net.runelite.api.Constants.TILE_FLAG_BRIDGE; import net.runelite.api.coords.LocalPoint; import net.runelite.api.gameval.VarbitID; @@ -105,15 +104,42 @@ public static Point localToCanvas(@Nonnull Client client, @Nonnull LocalPoint po * @param client the game client * @param point ground coordinate * @param plane ground plane on the z axis - * @param zOffset distance from ground on the z axis + * @param heightOffset distance from ground on the z axis * @return a {@link Point} on screen corresponding to the position in * 3D-space */ @Nullable - public static Point localToCanvas(@Nonnull Client client, @Nonnull LocalPoint point, int plane, int zOffset) + public static Point localToCanvas(@Nonnull Client client, @Nonnull LocalPoint point, int plane, int heightOffset) { - final int tileHeight = getTileHeight(client, point, plane); - return localToCanvas(client, point.getX(), point.getY(), tileHeight - zOffset); + if (point.getWorldView() > -1) + { + WorldView wv = client.getTopLevelWorldView(); + WorldEntity we = wv.worldEntities().byIndex(point.getWorldView()); + if (we == null) + { + return null; + } + + LocalPoint entityLocation = we.getLocalLocation(); + int height = getTileHeight(we.getWorldView(), point.getX(), point.getY(), plane); // height in wv + height += getTileHeight(wv, entityLocation.getX(), entityLocation.getY(), wv.getPlane()); // height of we + height -= heightOffset; + + WorldView subWv = we.getWorldView(); + Projection projection = subWv.getCanvasProjection(); + float[] p = projection.project(point.getX(), height, point.getY()); + float x0 = p[0], y0 = p[1], z0 = p[2]; + final int scale = client.getScale(); + final float pointX = client.getViewportWidth() / 2f + x0 * scale / z0; + final float pointY = client.getViewportHeight() / 2f + y0 * scale / z0; + return new Point( + (int) pointX + client.getViewportXOffset(), + (int) pointY + client.getViewportYOffset() + ); + } + + int tileHeight = getTileHeight(client, point, plane); + return localToCanvas(client, point.getX(), point.getY(), tileHeight - heightOffset); } /** @@ -132,6 +158,32 @@ public static Point localToCanvas(@Nonnull Client client, int x, int y, int z) return client.isGpu() ? localToCanvasGpu(client, x, y, z) : localToCanvasCpu(client, x, y, z); } + public static Point localToCanvas(@Nonnull Client client, int worldId, int x, int y, int z) + { + if (worldId > -1) + { + WorldView wv = client.getTopLevelWorldView(); + WorldEntity we = wv.worldEntities().byIndex(worldId); + if (we == null) + { + return null; + } + + WorldView subWv = we.getWorldView(); + Projection projection = subWv.getCanvasProjection(); + float[] p = projection.project(x, z, y); + float x0 = p[0], y0 = p[1], z0 = p[2]; + final int scale = client.getScale(); + final float pointX = client.getViewportWidth() / 2f + x0 * scale / z0; + final float pointY = client.getViewportHeight() / 2f + y0 * scale / z0; + return new Point( + (int) pointX + client.getViewportXOffset(), + (int) pointY + client.getViewportYOffset() + ); + } + return client.isGpu() ? localToCanvasGpu(client, x, y, z) : localToCanvasCpu(client, x, y, z); + } + private static Point localToCanvasCpu(Client client, int x, int y, int z) { if (x >= -ESCENE_OFFSET << LOCAL_COORD_BITS && y >= -ESCENE_OFFSET << LOCAL_COORD_BITS && @@ -209,14 +261,24 @@ private static Point localToCanvasGpu(Client client, int x, int y, int z) return null; } + @Deprecated + public static void modelToCanvas(Client client, int end, int x3dCenter, int y3dCenter, int z3dCenter, int rotate, float[] x3d, float[] y3d, float[] z3d, int[] x2d, int[] y2d) + { + modelToCanvas(client, client.getTopLevelWorldView(), end, x3dCenter, y3dCenter, z3dCenter, rotate, x3d, y3d, z3d, x2d, y2d); + } + /** * Translates a model's vertices into 2d space. */ - public static void modelToCanvas(Client client, int end, int x3dCenter, int y3dCenter, int z3dCenter, int rotate, float[] x3d, float[] y3d, float[] z3d, int[] x2d, int[] y2d) + public static void modelToCanvas(Client client, WorldView wv, int end, int x3dCenter, int y3dCenter, int z3dCenter, int rotate, float[] x3d, float[] y3d, float[] z3d, int[] x2d, int[] y2d) { + if (!wv.isTopLevel()) + { + modelToCanvasProjection(client, wv, end, x3dCenter, y3dCenter, z3dCenter, rotate, x3d, y3d, z3d, x2d, y2d); + } // There is a separate implementation for GPU since GPU uses a slightly more precise projection that can // cause features like model outlines being noticeably off otherwise. - if (client.isGpu()) + else if (client.isGpu()) { modelToCanvasGpu(client, end, x3dCenter, y3dCenter, z3dCenter, rotate, x3d, y3d, z3d, x2d, y2d); } @@ -226,6 +288,58 @@ public static void modelToCanvas(Client client, int end, int x3dCenter, int y3dC } } + private static void modelToCanvasProjection(Client client, WorldView wv, int end, int x3dCenter, int y3dCenter, int z3dCenter, int rotate, float[] x3d, float[] y3d, float[] z3d, int[] x2d, int[] y2d) + { + final float + rotateSin = SINE[rotate] / 65536.0f, + rotateCos = COSINE[rotate] / 65536.0f, + + viewportXMiddle = client.getViewportWidth() / 2f, + viewportYMiddle = client.getViewportHeight() / 2f, + viewportXOffset = client.getViewportXOffset(), + viewportYOffset = client.getViewportYOffset(), + + zoom3d = client.getScale(); + + Projection proj = wv.getCanvasProjection(); + for (int i = 0; i < end; ++i) + { + float x = x3d[i]; + float y = y3d[i]; + float z = z3d[i]; + + if (rotate != 0) + { + float x0 = x; + x = x0 * rotateCos + y * rotateSin; + y = y * rotateCos - x0 * rotateSin; + } + + x += x3dCenter; + y += y3dCenter; + z += z3dCenter; + + float[] p = proj.project(x, z, y); + float x1 = p[0], y1 = p[1], z1 = p[2]; + + int viewX, viewY; + + if (z1 < 50) + { + viewX = Integer.MIN_VALUE; + viewY = Integer.MIN_VALUE; + } + else + { + viewX = Math.round((viewportXMiddle + x1 * zoom3d / z1) + viewportXOffset); + viewY = Math.round((viewportYMiddle + y1 * zoom3d / z1) + viewportYOffset); + } + + x2d[i] = viewX; + y2d[i] = viewY; + } + } + private static void modelToCanvasGpu(Client client, int end, int x3dCenter, int y3dCenter, int z3dCenter, int rotate, float[] x3d, float[] y3d, float[] z3d, int[] x2d, int[] y2d) { final double @@ -391,53 +505,77 @@ public static Point localToMinimap(@Nonnull Client client, @Nonnull LocalPoint p @Nullable public static Point localToMinimap(@Nonnull Client client, @Nonnull LocalPoint point, int distance) { - LocalPoint localLocation = client.getLocalPlayer().getLocalLocation(); - final int dx = point.getX() - localLocation.getX(); - final int dy = point.getY() - localLocation.getY(); - - if (dx * dx + dy * dy < distance * distance) + if (point.getWorldView() > -1) { - Widget minimapDrawWidget; - if (client.isResized()) + WorldView toplevel = client.getTopLevelWorldView(); + WorldEntity we = toplevel.worldEntities().byIndex(point.getWorldView()); + if (we == null) { - if (client.getVarbitValue(VarbitID.RESIZABLE_STONE_ARRANGEMENT) == 1) - { - minimapDrawWidget = client.getWidget(WidgetInfo.RESIZABLE_MINIMAP_DRAW_AREA); - } - else - { - minimapDrawWidget = client.getWidget(WidgetInfo.RESIZABLE_MINIMAP_STONES_DRAW_AREA); - } + return null; } - else + + point = we.transformToMainWorld(point); + } + + CameraFocusableEntity cameraFocus = client.getCameraFocusEntity(); + LocalPoint cameraFocusPoint = cameraFocus.getCameraFocus(); + if (cameraFocusPoint.getWorldView() > -1) + { + WorldView toplevel = client.getTopLevelWorldView(); + WorldView wv = cameraFocus.getWorldView(); + WorldEntity we = toplevel.worldEntities().byIndex(wv.getId()); + if (we != null) { - minimapDrawWidget = client.getWidget(WidgetInfo.FIXED_VIEWPORT_MINIMAP_DRAW_AREA); + cameraFocusPoint = we.transformToMainWorld(cameraFocusPoint); } + } - if (minimapDrawWidget == null || minimapDrawWidget.isHidden()) + final int dx = point.getX() - cameraFocusPoint.getX(); + final int dy = point.getY() - cameraFocusPoint.getY(); + if (dx * dx + dy * dy >= distance * distance) + { + return null; + } + + Widget minimapDrawWidget; + if (client.isResized()) + { + if (client.getVarbitValue(VarbitID.RESIZABLE_STONE_ARRANGEMENT) == 1) { - return null; + minimapDrawWidget = client.getWidget(WidgetInfo.RESIZABLE_MINIMAP_DRAW_AREA); + } + else + { + minimapDrawWidget = client.getWidget(WidgetInfo.RESIZABLE_MINIMAP_STONES_DRAW_AREA); } + } + else + { + minimapDrawWidget = client.getWidget(WidgetInfo.FIXED_VIEWPORT_MINIMAP_DRAW_AREA); + } - final double zoom = client.getMinimapZoom() / LOCAL_TILE_SIZE; - final int x = (int) (dx * zoom); - final int y = (int) (dy * zoom); + if (minimapDrawWidget == null || minimapDrawWidget.isHidden()) + { + return null; + } - final int angle = client.getCameraYawTarget() & 0x7FF; + final double zoom = client.getMinimapZoom() / LOCAL_TILE_SIZE; + final int x = (int) (dx * zoom); + final int y = (int) (dy * zoom); - final int sin = SINE[angle]; - final int cos = COSINE[angle]; + final int angle = client.getCameraYawTarget() & 0x7FF; - final int rx = cos * x + sin * y >> 16; - final int ry = sin * x - cos * y >> 16; + final int sin = SINE[angle]; + final int cos = COSINE[angle]; - Point loc = minimapDrawWidget.getCanvasLocation(); - int miniMapX = loc.getX() + minimapDrawWidget.getWidth() / 2 + rx; - int miniMapY = loc.getY() + minimapDrawWidget.getHeight() / 2 + ry; - return new Point(miniMapX, miniMapY); - } + final int rx = cos * x + sin * y >> 16; + final int ry = sin * x - cos * y >> 16; + + Point loc = minimapDrawWidget.getCanvasLocation(); + int miniMapX = loc.getX() + minimapDrawWidget.getWidth() / 2 + rx; + int miniMapY = loc.getY() + minimapDrawWidget.getHeight() / 2 + ry; + return new Point(miniMapX, miniMapY); - return null; } /** @@ -452,26 +590,26 @@ public static int getTileHeight(@Nonnull Client client, @Nonnull LocalPoint poin { int sceneX = point.getSceneX(); int sceneY = point.getSceneY(); - if (sceneX >= 0 && sceneY >= 0 && sceneX < SCENE_SIZE && sceneY < SCENE_SIZE) + var wv = client.getWorldView(point.getWorldView()); + if (wv == null || sceneX < 0 || sceneY < 0 || sceneX >= wv.getSizeX() || sceneY >= wv.getSizeY()) { - var wv = client.getWorldView(point.getWorldView()); - byte[][][] tileSettings = wv.getTileSettings(); - int[][][] tileHeights = wv.getTileHeights(); + return 0; + } - int z1 = plane; - if (plane < Constants.MAX_Z - 1 && (tileSettings[1][sceneX][sceneY] & TILE_FLAG_BRIDGE) == TILE_FLAG_BRIDGE) - { - z1 = plane + 1; - } + byte[][][] tileSettings = wv.getTileSettings(); + int[][][] tileHeights = wv.getTileHeights(); - int x = point.getX() & (LOCAL_TILE_SIZE - 1); - int y = point.getY() & (LOCAL_TILE_SIZE - 1); - int var8 = x * tileHeights[z1][sceneX + 1][sceneY] + (LOCAL_TILE_SIZE - x) * tileHeights[z1][sceneX][sceneY] >> LOCAL_COORD_BITS; - int var9 = tileHeights[z1][sceneX][sceneY + 1] * (LOCAL_TILE_SIZE - x) + x * tileHeights[z1][sceneX + 1][sceneY + 1] >> LOCAL_COORD_BITS; - return (LOCAL_TILE_SIZE - y) * var8 + y * var9 >> LOCAL_COORD_BITS; + int z1 = plane; + if (plane < Constants.MAX_Z - 1 && (tileSettings[1][sceneX][sceneY] & TILE_FLAG_BRIDGE) == TILE_FLAG_BRIDGE) + { + z1 = plane + 1; } - return 0; + int x = point.getX() & (LOCAL_TILE_SIZE - 1); + int y = point.getY() & (LOCAL_TILE_SIZE - 1); + int var8 = x * tileHeights[z1][sceneX + 1][sceneY] + (LOCAL_TILE_SIZE - x) * tileHeights[z1][sceneX][sceneY] >> LOCAL_COORD_BITS; + int var9 = tileHeights[z1][sceneX][sceneY + 1] * (LOCAL_TILE_SIZE - x) + x * tileHeights[z1][sceneX + 1][sceneY + 1] >> LOCAL_COORD_BITS; + return (LOCAL_TILE_SIZE - y) * var8 + y * var9 >> LOCAL_COORD_BITS; } public static int getFootprintTileHeight(@Nonnull Client client, @Nonnull LocalPoint p, int level, int footprintSize) @@ -510,13 +648,14 @@ public static int getFootprintTileHeight(@Nonnull Client client, @Nonnull LocalP * Does not account for bridges. * @return */ - private static int getHeight(@Nonnull Scene scene, int localX, int localY, int plane) + private static int getTileHeight(@Nonnull WorldView wv, int localX, int localY, int plane) { - int sceneX = (localX >> LOCAL_COORD_BITS) + ESCENE_OFFSET; - int sceneY = (localY >> LOCAL_COORD_BITS) + ESCENE_OFFSET; - if (sceneX >= 0 && sceneY >= 0 && sceneX < Constants.EXTENDED_SCENE_SIZE && sceneY < Constants.EXTENDED_SCENE_SIZE) + int offset = wv.isTopLevel() ? ESCENE_OFFSET : 0; + int sceneX = (localX >> LOCAL_COORD_BITS) + offset; + int sceneY = (localY >> LOCAL_COORD_BITS) + offset; + if (sceneX >= 0 && sceneY >= 0 && sceneX < wv.getSizeX() + offset && sceneY < wv.getSizeY() + offset) { - int[][][] tileHeights = scene.getTileHeights(); + int[][][] tileHeights = wv.getScene().getTileHeights(); int x = localX & (LOCAL_TILE_SIZE - 1); int y = localY & (LOCAL_TILE_SIZE - 1); @@ -574,9 +713,9 @@ public static Polygon getCanvasTileAreaPoly(@Nonnull Client client, @Nonnull Loc * @param client the game client * @param localLocation the center location of the AoE * @param sizeX the size of the area in tiles on the x axis - * @param sizeY the size of the area in tiles on the y axis - * @param plane the plane of the area - * @param zOffset offset from ground plane + * @param sizeY the size of the area in tiles on the z axis + * @param level the level of the area + * @param heightOffset offset from ground level * @return a polygon representing the tiles in the area */ public static Polygon getCanvasTileAreaPoly( @@ -584,31 +723,37 @@ public static Polygon getCanvasTileAreaPoly( @Nonnull LocalPoint localLocation, int sizeX, int sizeY, - int plane, - int zOffset) + int level, + int heightOffset) { - final int msx = localLocation.getSceneX() + ESCENE_OFFSET; - final int msy = localLocation.getSceneY() + ESCENE_OFFSET; - final var wv = client.getWorldView(localLocation.getWorldView()); + final WorldView wv = client.getWorldView(localLocation.getWorldView()); + if (wv == null) + { + return null; + } + + int offset = wv.isTopLevel() ? ESCENE_OFFSET : 0; + final int msx = localLocation.getSceneX() + offset; + final int msy = localLocation.getSceneY() + offset; - if (msx < 0 || msy < 0 || msx >= EXTENDED_SCENE_SIZE || msy >= EXTENDED_SCENE_SIZE || wv == null) + if (msx < 0 || msy < 0 || msx >= wv.getSizeX() + offset || msy >= wv.getSizeY() + offset) { // out of scene return null; } - if (plane == -1) + if (level == -1) { - plane = wv.getPlane(); + level = wv.getPlane(); } var scene = wv.getScene(); final byte[][][] tileSettings = scene.getExtendedTileSettings(); - int tilePlane = plane; - if (plane < Constants.MAX_Z - 1 && (tileSettings[1][msx][msy] & TILE_FLAG_BRIDGE) == TILE_FLAG_BRIDGE) + int tilePlane = level; + if (level < Constants.MAX_Z - 1 && (tileSettings[1][msx][msy] & TILE_FLAG_BRIDGE) == TILE_FLAG_BRIDGE) { - tilePlane = plane + 1; + tilePlane = level + 1; } final int swX = localLocation.getX() - (sizeX * LOCAL_TILE_SIZE / 2); @@ -623,15 +768,15 @@ public static Polygon getCanvasTileAreaPoly( final int nwX = neX; final int nwY = swY; - final int swHeight = getHeight(scene, swX, swY, tilePlane) - zOffset; - final int nwHeight = getHeight(scene, nwX, nwY, tilePlane) - zOffset; - final int neHeight = getHeight(scene, neX, neY, tilePlane) - zOffset; - final int seHeight = getHeight(scene, seX, seY, tilePlane) - zOffset; + final int swHeight = getTileHeight(wv, swX, swY, tilePlane) - heightOffset; + final int nwHeight = getTileHeight(wv, nwX, nwY, tilePlane) - heightOffset; + final int neHeight = getTileHeight(wv, neX, neY, tilePlane) - heightOffset; + final int seHeight = getTileHeight(wv, seX, seY, tilePlane) - heightOffset; - Point p1 = localToCanvas(client, swX, swY, swHeight); - Point p2 = localToCanvas(client, nwX, nwY, nwHeight); - Point p3 = localToCanvas(client, neX, neY, neHeight); - Point p4 = localToCanvas(client, seX, seY, seHeight); + Point p1 = localToCanvas(client, wv.getId(), swX, swY, swHeight); + Point p2 = localToCanvas(client, wv.getId(), nwX, nwY, nwHeight); + Point p3 = localToCanvas(client, wv.getId(), neX, neY, neHeight); + Point p4 = localToCanvas(client, wv.getId(), seX, seY, seHeight); if (p1 == null || p2 == null || p3 == null || p4 == null) { @@ -799,6 +944,7 @@ public static Point getCanvasSpriteLocation( * object on the tile at ({@code localX}, {@code localY}) and rotated to * angle {@code orientation}. * @param client the game client + * @param wv the worldview * @param model the model to calculate a clickbox for * @param orientation the orientation of the model (0-2048, where 0 is north) * @param x x coord in local space @@ -807,15 +953,14 @@ public static Point getCanvasSpriteLocation( */ @Nullable @ApiStatus.Internal - public static Shape getClickbox(@Nonnull Client client, Model model, int orientation, int x, int y, int z) + public static Shape getClickbox(@Nonnull Client client, WorldView wv, Model model, int orientation, int x, int y, int z) { if (model == null) { return null; } - SimplePolygon bounds = calculateAABB(client, model, orientation, x, y, z); - + SimplePolygon bounds = calculateAABB(client, wv, model, orientation, x, y, z); if (bounds == null) { return null; @@ -826,7 +971,7 @@ public static Shape getClickbox(@Nonnull Client client, Model model, int orienta return bounds; } - Shapes bounds2d = calculate2DBounds(client, model, orientation, x, y, z); + Shapes bounds2d = calculate2DBounds(client, wv, model, orientation, x, y, z); if (bounds2d == null) { return null; @@ -840,7 +985,7 @@ public static Shape getClickbox(@Nonnull Client client, Model model, int orienta return bounds2d; } - private static SimplePolygon calculateAABB(Client client, Model m, int jauOrient, int x, int y, int z) + private static SimplePolygon calculateAABB(Client client, WorldView wv, Model m, int jauOrient, int x, int y, int z) { AABB aabb = m.getAABB(jauOrient); @@ -876,18 +1021,18 @@ private static SimplePolygon calculateAABB(Client client, Model m, int jauOrient int[] x2d = new int[8]; int[] y2d = new int[8]; - modelToCanvasCpu(client, 8, x, y, z, 0, xa, ya, za, x2d, y2d); + modelToCanvas(client, wv, 8, x, y, z, 0, xa, ya, za, x2d, y2d); return Jarvis.convexHull(x2d, y2d); } - private static Shapes calculate2DBounds(Client client, Model m, int jauOrient, int x, int y, int z) + private static Shapes calculate2DBounds(Client client, WorldView wv, Model m, int jauOrient, int x, int y, int z) { int[] x2d = new int[m.getVerticesCount()]; int[] y2d = new int[m.getVerticesCount()]; final int[] faceColors3 = m.getFaceColors3(); - Perspective.modelToCanvasCpu(client, + Perspective.modelToCanvas(client, wv, m.getVerticesCount(), x, y, z, jauOrient, diff --git a/runelite-api/src/main/java/net/runelite/api/WorldEntity.java b/runelite-api/src/main/java/net/runelite/api/WorldEntity.java index 2e747e9023b..45684b3502c 100644 --- a/runelite-api/src/main/java/net/runelite/api/WorldEntity.java +++ b/runelite-api/src/main/java/net/runelite/api/WorldEntity.java @@ -24,7 +24,22 @@ */ package net.runelite.api; -public interface WorldEntity +import net.runelite.api.coords.LocalPoint; + +public interface WorldEntity extends CameraFocusableEntity { WorldView getWorldView(); + + /** + * Get the location of this world entity in the top level world. + * @return + */ + LocalPoint getLocalLocation(); + + /** + * Transform a point within the world entity to the overworld + * @param point + * @return + */ + LocalPoint transformToMainWorld(LocalPoint point); } diff --git a/runelite-api/src/main/java/net/runelite/api/WorldView.java b/runelite-api/src/main/java/net/runelite/api/WorldView.java index 6261e9a5708..8924d45134e 100644 --- a/runelite-api/src/main/java/net/runelite/api/WorldView.java +++ b/runelite-api/src/main/java/net/runelite/api/WorldView.java @@ -25,6 +25,8 @@ package net.runelite.api; import javax.annotation.Nullable; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldPoint; public interface WorldView { @@ -62,6 +64,12 @@ public interface WorldView */ IndexedObjectSet worldEntities(); + /** + * Get the worldviews of each worldentity in this worldview. + * @return + */ + IndexedObjectSet worldViews(); + /** * Gets an array of tile collision data. *

@@ -205,4 +213,30 @@ Projectile createProjectile(int id, int plane, int startX, int startY, int start * @return the map regions */ int[] getMapRegions(); + + /** + * Test if this worldview contains the given point + * @param point + * @return + */ + boolean contains(WorldPoint point); + + /** + * Test if this worldview contains the given point + * @param point + * @return + */ + boolean contains(LocalPoint point); + + /** + * Returns a {@link Projection} to translate from this world view to the main world + * @return + */ + Projection getMainWorldProjection(); + + /** + * Returns a {@link Projection} to translate from this world view to the canvas + * @return + */ + Projection getCanvasProjection(); } diff --git a/runelite-api/src/main/java/net/runelite/api/coords/LocalPoint.java b/runelite-api/src/main/java/net/runelite/api/coords/LocalPoint.java index caf1bd68193..e9cc84ea8e7 100644 --- a/runelite-api/src/main/java/net/runelite/api/coords/LocalPoint.java +++ b/runelite-api/src/main/java/net/runelite/api/coords/LocalPoint.java @@ -63,10 +63,10 @@ public LocalPoint(int x, int y) } @Nullable - @Deprecated - public static LocalPoint fromWorld(Client client, WorldPoint world) + public static LocalPoint fromWorld(Client client, WorldPoint point) { - return fromWorld(client.getTopLevelWorldView(), world); + WorldView wv = client.findWorldViewFromWorldPoint(point); + return fromWorld(wv, point); } /** @@ -145,7 +145,6 @@ public int distanceTo(LocalPoint other) { if (worldView != other.worldView) { - assert false; return Integer.MAX_VALUE; } @@ -156,6 +155,7 @@ public int distanceTo(LocalPoint other) * Test if this point is in the basic 104x104 tile scene. * @return */ + @Deprecated public boolean isInScene() { return x >= 0 && x < Perspective.SCENE_SIZE << Perspective.LOCAL_COORD_BITS diff --git a/runelite-api/src/main/java/net/runelite/api/events/WorldEntityDespawned.java b/runelite-api/src/main/java/net/runelite/api/events/WorldEntityDespawned.java new file mode 100644 index 00000000000..288f995d204 --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/events/WorldEntityDespawned.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2025, 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.api.events; + +import lombok.Value; +import net.runelite.api.WorldEntity; + +/** + * Called when a world entity despawns + */ +@Value +public class WorldEntityDespawned +{ + WorldEntity worldEntity; +} diff --git a/runelite-api/src/main/java/net/runelite/api/events/WorldEntitySpawned.java b/runelite-api/src/main/java/net/runelite/api/events/WorldEntitySpawned.java new file mode 100644 index 00000000000..6cb7d55decc --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/events/WorldEntitySpawned.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2025, 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.api.events; + +import lombok.Value; +import net.runelite.api.WorldEntity; + +/** + * Called when a world entity spawns + * + * This is called when the world entity spawns, but before the WorldView has been loaded. + * @see WorldViewLoaded + */ +@Value +public class WorldEntitySpawned +{ + WorldEntity worldEntity; +} diff --git a/runelite-api/src/main/java/net/runelite/api/events/WorldViewLoaded.java b/runelite-api/src/main/java/net/runelite/api/events/WorldViewLoaded.java new file mode 100644 index 00000000000..017ea118f51 --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/events/WorldViewLoaded.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2025, 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.api.events; + +import lombok.Value; +import net.runelite.api.WorldView; + +/** + * Called when a worldview has been loaded + */ +@Value +public class WorldViewLoaded +{ + WorldView worldView; +} diff --git a/runelite-api/src/main/java/net/runelite/api/events/WorldViewUnloaded.java b/runelite-api/src/main/java/net/runelite/api/events/WorldViewUnloaded.java new file mode 100644 index 00000000000..b6c286c69ca --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/events/WorldViewUnloaded.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2025, 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.api.events; + +import lombok.Value; +import net.runelite.api.WorldView; + +/** + * Called when a worldview has been unloaded + */ +@Value +public class WorldViewUnloaded +{ + WorldView worldView; +} diff --git a/runelite-client/src/main/java/net/runelite/client/game/npcoverlay/NpcOverlayService.java b/runelite-client/src/main/java/net/runelite/client/game/npcoverlay/NpcOverlayService.java index db96bc91a52..f7942ddf0ac 100644 --- a/runelite-client/src/main/java/net/runelite/client/game/npcoverlay/NpcOverlayService.java +++ b/runelite-client/src/main/java/net/runelite/client/game/npcoverlay/NpcOverlayService.java @@ -35,6 +35,7 @@ import net.runelite.api.Client; import net.runelite.api.GameState; import net.runelite.api.NPC; +import net.runelite.api.WorldView; import net.runelite.api.events.GameStateChanged; import net.runelite.api.events.NpcChanged; import net.runelite.api.events.NpcDespawned; @@ -127,21 +128,30 @@ public void rebuild() clientThread.invoke(() -> { highlightedNpcs.clear(); + rebuild(client.getTopLevelWorldView()); + }); + } - outer: - for (NPC npc : client.getNpcs()) + private void rebuild(WorldView wv) + { + outer: + for (NPC npc : wv.npcs()) + { + for (Function f : highlightFunctions) { - for (Function f : highlightFunctions) + HighlightedNpc highlightedNpc = f.apply(npc); + if (highlightedNpc != null) { - HighlightedNpc highlightedNpc = f.apply(npc); - if (highlightedNpc != null) - { - highlightedNpcs.put(npc, highlightedNpc); - continue outer; - } + highlightedNpcs.put(npc, highlightedNpc); + continue outer; } } - }); + } + + for (WorldView sub : wv.worldViews()) + { + rebuild(sub); + } } public void registerHighlighter(Function p) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItem.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItem.java index d736f48c867..63fde1f8f98 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItem.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItem.java @@ -30,11 +30,11 @@ import javax.annotation.Nullable; import lombok.Builder; import lombok.Data; +import net.runelite.api.ItemLayer; import static net.runelite.api.TileItem.OWNERSHIP_GROUP; import static net.runelite.api.TileItem.OWNERSHIP_NONE; import static net.runelite.api.TileItem.OWNERSHIP_OTHER; import static net.runelite.api.TileItem.OWNERSHIP_SELF; -import net.runelite.api.coords.WorldPoint; import org.intellij.lang.annotations.MagicConstant; @Data @@ -45,8 +45,7 @@ class GroundItem private int itemId; private String name; private int quantity; - private WorldPoint location; - private int height; + private ItemLayer itemLayer; private int haPrice; private int gePrice; private int offset; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java index a4b3486bb2c..ba0bdc8bf67 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java @@ -42,6 +42,7 @@ import net.runelite.api.Perspective; import net.runelite.api.Player; import net.runelite.api.Point; +import net.runelite.api.WorldView; import net.runelite.api.coords.LocalPoint; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.ItemID; @@ -113,7 +114,6 @@ public Dimension render(Graphics2D graphics) } offsetMap.clear(); - final LocalPoint localLocation = player.getLocalLocation(); final Point mousePos = client.getMouseCanvasPosition(); Collection groundItemList = plugin.getCollectedGroundItems().values(); GroundItem topGroundItem = null; @@ -128,7 +128,7 @@ public Dimension render(Graphics2D graphics) for (GroundItem item : groundItemList) { - item.setOffset(offsetMap.compute(item.getLocation(), (k, v) -> v != null ? v + 1 : 0)); + item.setOffset(offsetMap.compute(item.getItemLayer().getWorldLocation(), (k, v) -> v != null ? v + 1 : 0)); if (groundItem != null) { @@ -171,6 +171,7 @@ public Dimension render(Graphics2D graphics) plugin.setHiddenBoxBounds(null); plugin.setHighlightBoxBounds(null); + final LocalPoint localLocation = player.getLocalLocation(); final DespawnTimerMode groundItemTimers = config.groundItemTimers(); final boolean outline = config.textOutline(); final OwnershipFilterMode ownershipFilterMode = config.ownershipFilterMode(); @@ -178,9 +179,11 @@ public Dimension render(Graphics2D graphics) for (GroundItem item : groundItemList) { - final LocalPoint groundPoint = LocalPoint.fromWorld(client, item.getLocation()); + final WorldView wv = item.getItemLayer().getWorldView(); + final LocalPoint groundPoint = LocalPoint.fromWorld(wv, item.getItemLayer().getWorldLocation()); - if (groundPoint == null || localLocation.distanceTo(groundPoint) > MAX_DISTANCE + if (groundPoint == null + || (groundPoint.getWorldView() == WorldView.TOPLEVEL && localLocation.distanceTo(groundPoint) > MAX_DISTANCE) || !plugin.shouldDisplayItem(ownershipFilterMode, item.getOwnership(), accountType)) { continue; @@ -207,7 +210,7 @@ public Dimension render(Graphics2D graphics) if (config.highlightTiles()) { - final Polygon poly = Perspective.getCanvasTilePoly(client, groundPoint, item.getHeight()); + final Polygon poly = Perspective.getCanvasTilePoly(client, groundPoint, item.getItemLayer().getHeight()); if (poly != null) { @@ -271,7 +274,7 @@ else if (displayMode != PriceDisplayMode.OFF) graphics, groundPoint, itemString, - item.getHeight() + OFFSET_Z); + item.getItemLayer().getHeight() + OFFSET_Z); if (textPoint == null) { @@ -280,7 +283,7 @@ else if (displayMode != PriceDisplayMode.OFF) final int offset = plugin.isHotKeyPressed() ? item.getOffset() - : offsetMap.compute(item.getLocation(), (k, v) -> v != null ? v + 1 : 0); + : offsetMap.compute(item.getItemLayer().getWorldLocation(), (k, v) -> v != null ? v + 1 : 0); final int textX = textPoint.getX(); final int textY = textPoint.getY() - (STRING_GAP * offset); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java index 6163d81bb62..ed3010254c6 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java @@ -57,8 +57,8 @@ import lombok.Setter; import lombok.Value; import net.runelite.api.Client; -import net.runelite.api.GameState; import net.runelite.api.ItemComposition; +import net.runelite.api.ItemLayer; import net.runelite.api.KeyCode; import net.runelite.api.Menu; import net.runelite.api.MenuAction; @@ -68,14 +68,15 @@ import static net.runelite.api.TileItem.OWNERSHIP_GROUP; import static net.runelite.api.TileItem.OWNERSHIP_OTHER; import static net.runelite.api.TileItem.OWNERSHIP_SELF; +import net.runelite.api.WorldView; import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.ClientTick; import net.runelite.api.events.FocusChanged; -import net.runelite.api.events.GameStateChanged; import net.runelite.api.events.ItemDespawned; import net.runelite.api.events.ItemQuantityChanged; import net.runelite.api.events.ItemSpawned; import net.runelite.api.events.MenuEntryAdded; +import net.runelite.api.events.WorldViewUnloaded; import net.runelite.api.gameval.ItemID; import net.runelite.api.gameval.VarbitID; import net.runelite.client.Notifier; @@ -258,13 +259,11 @@ private void migrate() } @Subscribe - public void onGameStateChanged(final GameStateChanged event) + public void onWorldViewUnloaded(WorldViewUnloaded event) { - if (event.getGameState() == GameState.LOADING) - { - collectedGroundItems.clear(); - lootbeams.clear(); - } + var wv = event.getWorldView(); + collectedGroundItems.values().removeIf(g -> g.getItemLayer().getWorldView() == wv); + lootbeams.values().removeIf(l -> l.getWorldView() == wv.getId()); } @Subscribe @@ -272,8 +271,9 @@ public void onItemSpawned(ItemSpawned itemSpawned) { TileItem item = itemSpawned.getItem(); Tile tile = itemSpawned.getTile(); + ItemLayer layer = tile.getItemLayer(); - GroundItem groundItem = buildGroundItem(tile, item); + GroundItem groundItem = buildGroundItem(layer, item); GroundItem existing = collectedGroundItems.get(tile.getWorldLocation(), item.getId()); if (existing != null) { @@ -406,7 +406,7 @@ public void onClientTick(ClientTick event) }).toArray(MenuEntry[]::new)); } - private GroundItem buildGroundItem(final Tile tile, final TileItem item) + private GroundItem buildGroundItem(final ItemLayer layer, final TileItem item) { // Collect the data for the item final int itemId = item.getId(); @@ -418,12 +418,11 @@ private GroundItem buildGroundItem(final Tile tile, final TileItem item) final GroundItem groundItem = GroundItem.builder() .id(itemId) - .location(tile.getWorldLocation()) .itemId(realItemId) .quantity(item.getQuantity()) + .itemLayer(layer) .name(itemComposition.getName()) .haPrice(alchPrice) - .height(tile.getItemLayer().getHeight()) .tradeable(itemComposition.isTradeable()) .ownership(item.getOwnership()) .isPrivate(item.isPrivate()) @@ -507,10 +506,10 @@ public void onMenuEntryAdded(MenuEntryAdded event) final int sceneX = event.getActionParam0(); final int sceneY = event.getActionParam1(); - MenuEntry[] menuEntries = client.getMenuEntries(); - MenuEntry lastEntry = menuEntries[menuEntries.length - 1]; + MenuEntry lastEntry = event.getMenuEntry(); + WorldView wv = client.getWorldView(lastEntry.getWorldViewId()); - final WorldPoint worldPoint = WorldPoint.fromScene(client, sceneX, sceneY, client.getPlane()); + final WorldPoint worldPoint = WorldPoint.fromScene(wv, sceneX, sceneY, wv.getPlane()); GroundItem groundItem = collectedGroundItems.get(worldPoint, itemId); updateItemColor(groundItem); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/Lootbeam.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/Lootbeam.java index 0fe13514971..4f49ca843b5 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/Lootbeam.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/Lootbeam.java @@ -102,6 +102,11 @@ public Lootbeam(Client client, ClientThread clientThread, WorldPoint worldPoint, runeLiteObject.setActive(true); } + int getWorldView() + { + return runeLiteObject.getWorldView(); + } + public void setColor(Color color) { if (this.color != null && this.color.equals(color)) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerMinimapOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerMinimapOverlay.java index 0a808fdd36a..0c05513e7d1 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerMinimapOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerMinimapOverlay.java @@ -24,17 +24,18 @@ */ package net.runelite.client.plugins.groundmarkers; +import com.google.common.collect.Multimap; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Polygon; import java.awt.Stroke; -import java.util.Collection; import javax.inject.Inject; import net.runelite.api.Client; import net.runelite.api.Perspective; import net.runelite.api.Point; +import net.runelite.api.WorldView; import net.runelite.api.coords.LocalPoint; import net.runelite.api.coords.WorldPoint; import net.runelite.client.ui.overlay.Overlay; @@ -61,50 +62,51 @@ private GroundMarkerMinimapOverlay(Client client, GroundMarkerConfig config, Gro @Override public Dimension render(Graphics2D graphics) { - if (!config.drawTileOnMinimmap()) + final Multimap points = plugin.getPoints(); + if (points.isEmpty() || !config.drawTileOnMinimmap()) { return null; } - final Collection points = plugin.getPoints(); - for (final ColorTileMarker point : points) + for (WorldView wv : points.keySet()) { - WorldPoint worldPoint = point.getWorldPoint(); - if (worldPoint.getPlane() != client.getPlane()) + for (final ColorTileMarker point : points.get(wv)) { - continue; - } + WorldPoint worldPoint = point.getWorldPoint(); + if (worldPoint.getPlane() != wv.getPlane()) + { + continue; + } - Color tileColor = point.getColor(); - if (tileColor == null) - { - // If this is an old tile which has no color, use marker color - tileColor = config.markerColor(); - } + Color tileColor = point.getColor(); + if (tileColor == null) + { + // If this is an old tile which has no color, use marker color + tileColor = config.markerColor(); + } - drawOnMinimap(graphics, worldPoint, tileColor); + drawOnMinimap(graphics, wv, worldPoint, tileColor); + } } return null; } - private void drawOnMinimap(Graphics2D graphics, WorldPoint point, Color color) + private void drawOnMinimap(Graphics2D graphics, WorldView wv, WorldPoint point, Color color) { - if (!point.isInScene(client)) + LocalPoint lp = LocalPoint.fromWorld(wv, point); + if (lp == null) { return; } - int x = point.getX() - client.getBaseX(); - int y = point.getY() - client.getBaseY(); - - x <<= Perspective.LOCAL_COORD_BITS; - y <<= Perspective.LOCAL_COORD_BITS; + int x = lp.getX() & -Perspective.LOCAL_TILE_SIZE; + int y = lp.getY() & -Perspective.LOCAL_TILE_SIZE; - Point mp1 = Perspective.localToMinimap(client, new LocalPoint(x, y)); - Point mp2 = Perspective.localToMinimap(client, new LocalPoint(x, y + Perspective.LOCAL_TILE_SIZE)); - Point mp3 = Perspective.localToMinimap(client, new LocalPoint(x + Perspective.LOCAL_TILE_SIZE, y + Perspective.LOCAL_TILE_SIZE)); - Point mp4 = Perspective.localToMinimap(client, new LocalPoint(x + Perspective.LOCAL_TILE_SIZE, y)); + Point mp1 = Perspective.localToMinimap(client, new LocalPoint(x, y, wv.getId())); + Point mp2 = Perspective.localToMinimap(client, new LocalPoint(x, y + Perspective.LOCAL_TILE_SIZE, wv.getId())); + Point mp3 = Perspective.localToMinimap(client, new LocalPoint(x + Perspective.LOCAL_TILE_SIZE, y + Perspective.LOCAL_TILE_SIZE, wv.getId())); + Point mp4 = Perspective.localToMinimap(client, new LocalPoint(x + Perspective.LOCAL_TILE_SIZE, y, wv.getId())); if (mp1 == null || mp2 == null || mp3 == null || mp4 == null) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerOverlay.java index b0243cf67f6..2ab8a888eb9 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerOverlay.java @@ -26,18 +26,19 @@ package net.runelite.client.plugins.groundmarkers; import com.google.common.base.Strings; +import com.google.common.collect.Multimap; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Polygon; import java.awt.Stroke; -import java.util.Collection; import javax.annotation.Nullable; import javax.inject.Inject; import net.runelite.api.Client; import net.runelite.api.Perspective; import net.runelite.api.Point; +import net.runelite.api.WorldView; import net.runelite.api.coords.LocalPoint; import net.runelite.api.coords.WorldPoint; import net.runelite.client.ui.overlay.Overlay; @@ -67,44 +68,50 @@ private GroundMarkerOverlay(Client client, GroundMarkerConfig config, GroundMark @Override public Dimension render(Graphics2D graphics) { - final Collection points = plugin.getPoints(); + final Multimap points = plugin.getPoints(); if (points.isEmpty()) { return null; } Stroke stroke = new BasicStroke((float) config.borderWidth()); - for (final ColorTileMarker point : points) + for (WorldView wv : points.keySet()) { - WorldPoint worldPoint = point.getWorldPoint(); - if (worldPoint.getPlane() != client.getPlane()) + for (final ColorTileMarker point : points.get(wv)) { - continue; - } + WorldPoint worldPoint = point.getWorldPoint(); + if (worldPoint.getPlane() != wv.getPlane()) + { + continue; + } - Color tileColor = point.getColor(); - if (tileColor == null) - { - // If this is an old tile which has no color, use marker color - tileColor = config.markerColor(); - } + Color tileColor = point.getColor(); + if (tileColor == null) + { + // If this is an old tile which has no color, use marker color + tileColor = config.markerColor(); + } - drawTile(graphics, worldPoint, tileColor, point.getLabel(), stroke); + drawTile(graphics, wv, worldPoint, tileColor, point.getLabel(), stroke); + } } return null; } - private void drawTile(Graphics2D graphics, WorldPoint point, Color color, @Nullable String label, Stroke borderStroke) + private void drawTile(Graphics2D graphics, WorldView wv, WorldPoint point, Color color, @Nullable String label, Stroke borderStroke) { - WorldPoint playerLocation = client.getLocalPlayer().getWorldLocation(); - - if (point.distanceTo(playerLocation) >= MAX_DRAW_DISTANCE) + if (wv.isTopLevel()) { - return; + WorldPoint playerLocation = client.getLocalPlayer().getWorldLocation(); + + if (point.distanceTo(playerLocation) >= MAX_DRAW_DISTANCE) + { + return; + } } - LocalPoint lp = LocalPoint.fromWorld(client, point); + LocalPoint lp = LocalPoint.fromWorld(wv, point); if (lp == null) { return; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerPlugin.java index 8c093e193ae..1057059d431 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerPlugin.java @@ -26,6 +26,8 @@ package net.runelite.client.plugins.groundmarkers; import com.google.common.base.Strings; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ListMultimap; import com.google.common.util.concurrent.Runnables; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; @@ -43,16 +45,17 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; -import net.runelite.api.GameState; import net.runelite.api.KeyCode; import net.runelite.api.Menu; import net.runelite.api.MenuAction; import net.runelite.api.MenuEntry; import net.runelite.api.Tile; -import net.runelite.api.coords.LocalPoint; +import net.runelite.api.WorldEntity; +import net.runelite.api.WorldView; import net.runelite.api.coords.WorldPoint; -import net.runelite.api.events.GameStateChanged; import net.runelite.api.events.MenuEntryAdded; +import net.runelite.api.events.WorldViewLoaded; +import net.runelite.api.events.WorldViewUnloaded; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.EventBus; import net.runelite.client.eventbus.Subscribe; @@ -79,7 +82,7 @@ public class GroundMarkerPlugin extends Plugin private static final String REGION_PREFIX = "region_"; @Getter(AccessLevel.PACKAGE) - private final List points = new ArrayList<>(); + private final ListMultimap points = ArrayListMultimap.create(); @Inject private Client client; @@ -149,8 +152,20 @@ void loadPoints() { points.clear(); - int[] regions = client.getMapRegions(); + WorldView wv = client.getTopLevelWorldView(); + loadPoints(wv); + for (WorldEntity we : wv.worldEntities()) + { + loadPoints(we.getWorldView()); + } + } + + void loadPoints(WorldView wv) + { + points.removeAll(wv); + + int[] regions = wv.getMapRegions(); if (regions == null) { return; @@ -161,8 +176,8 @@ void loadPoints() // load points for region log.debug("Loading points for region {}", regionId); Collection regionPoints = getPoints(regionId); - Collection colorTileMarkers = translateToColorTileMarker(regionPoints); - points.addAll(colorTileMarkers); + Collection colorTileMarkers = translateToColorTileMarker(wv, regionPoints); + points.putAll(wv, colorTileMarkers); } } @@ -173,7 +188,7 @@ void loadPoints() * @return A collection of color tile markers, converted from the passed ground marker points, accounting for local * instance points. See {@link WorldPoint#toLocalInstance(Client, WorldPoint)} */ - private Collection translateToColorTileMarker(Collection points) + private Collection translateToColorTileMarker(WorldView wv, Collection points) { if (points.isEmpty()) { @@ -186,7 +201,7 @@ private Collection translateToColorTileMarker(Collection { - final Collection localWorldPoints = WorldPoint.toLocalInstance(client, colorTile.getWorldPoint()); + final Collection localWorldPoints = WorldPoint.toLocalInstance(wv, colorTile.getWorldPoint()); return localWorldPoints.stream().map(wp -> new ColorTileMarker(wp, colorTile.getColor(), colorTile.getLabel())); }) .collect(Collectors.toList()); @@ -223,15 +238,15 @@ public void onProfileChanged(ProfileChanged profileChanged) } @Subscribe - public void onGameStateChanged(GameStateChanged gameStateChanged) + public void onWorldViewLoaded(WorldViewLoaded event) { - if (gameStateChanged.getGameState() != GameState.LOGGED_IN) - { - return; - } + loadPoints(event.getWorldView()); + } - // map region has just been updated - loadPoints(); + @Subscribe + public void onWorldViewUnloaded(WorldViewUnloaded event) + { + points.removeAll(event.getWorldView()); } @Subscribe @@ -240,8 +255,14 @@ public void onMenuEntryAdded(MenuEntryAdded event) final boolean hotKeyPressed = client.isKeyPressed(KeyCode.KC_SHIFT); if (hotKeyPressed && event.getOption().equals(WALK_HERE)) { - final Tile selectedSceneTile = client.getSelectedSceneTile(); + int worldId = event.getMenuEntry().getWorldViewId(); + WorldView wv = client.getWorldView(worldId); + if (wv == null) + { + return; + } + final Tile selectedSceneTile = wv.getSelectedSceneTile(); if (selectedSceneTile == null) { return; @@ -259,13 +280,7 @@ public void onMenuEntryAdded(MenuEntryAdded event) .setTarget("Tile") .setType(MenuAction.RUNELITE) .onClick(e -> - { - Tile target = client.getSelectedSceneTile(); - if (target != null) - { - markTile(target.getLocalLocation()); - } - }); + markTile(worldPoint)); if (existingOpt.isPresent()) { @@ -317,7 +332,7 @@ public void onMenuEntryAdded(MenuEntryAdded event) }); }); - var existingColors = points.stream() + var existingColors = points.values().stream() .map(ColorTileMarker::getColor) .distinct() .collect(Collectors.toList()); @@ -351,15 +366,8 @@ public void onConfigChanged(ConfigChanged event) } } - private void markTile(LocalPoint localPoint) + private void markTile(WorldPoint worldPoint) { - if (localPoint == null) - { - return; - } - - WorldPoint worldPoint = WorldPoint.fromLocalInstance(client, localPoint); - int regionId = worldPoint.getRegionID(); GroundMarkerPoint point = new GroundMarkerPoint(regionId, worldPoint.getRegionX(), worldPoint.getRegionY(), worldPoint.getPlane(), config.markerColor(), null); log.debug("Updating point: {} - {}", point, worldPoint); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/interacthighlight/InteractHighlightOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/interacthighlight/InteractHighlightOverlay.java index 7e4b2f3ba7d..d4e03546a20 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/interacthighlight/InteractHighlightOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/interacthighlight/InteractHighlightOverlay.java @@ -93,10 +93,11 @@ private void renderMouseover() case GAME_OBJECT_FIFTH_OPTION: case EXAMINE_OBJECT: { + int worldId = entry.getWorldViewId(); int x = entry.getParam0(); int y = entry.getParam1(); int id = entry.getIdentifier(); - TileObject tileObject = plugin.findTileObject(x, y, id); + TileObject tileObject = plugin.findTileObject(worldId, x, y, id); if (tileObject != null && config.objectShowHover() && (tileObject != plugin.getInteractedObject() || !config.objectShowInteract())) { modelOutlineRenderer.drawOutline(tileObject, config.borderWidth(), config.objectHoverHighlightColor(), config.outlineFeather()); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/interacthighlight/InteractHighlightPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/interacthighlight/InteractHighlightPlugin.java index d27eb527902..02083f38584 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/interacthighlight/InteractHighlightPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/interacthighlight/InteractHighlightPlugin.java @@ -42,6 +42,7 @@ import net.runelite.api.Tile; import net.runelite.api.TileObject; import net.runelite.api.WallObject; +import net.runelite.api.WorldView; import net.runelite.api.events.GameStateChanged; import net.runelite.api.events.GameTick; import net.runelite.api.events.InteractingChanged; @@ -150,10 +151,11 @@ public void onMenuOptionClicked(MenuOptionClicked menuOptionClicked) case GAME_OBJECT_FOURTH_OPTION: case GAME_OBJECT_FIFTH_OPTION: { + int worldId = menuOptionClicked.getMenuEntry().getWorldViewId(); int x = menuOptionClicked.getParam0(); int y = menuOptionClicked.getParam1(); int id = menuOptionClicked.getId(); - interactedObject = findTileObject(x, y, id); + interactedObject = findTileObject(worldId, x, y, id); interactedNpc = null; clickTick = client.getTickCount(); gameCycle = client.getGameCycle(); @@ -198,13 +200,15 @@ public void onMenuOptionClicked(MenuOptionClicked menuOptionClicked) } } - TileObject findTileObject(int x, int y, int id) + TileObject findTileObject(int worldId, int x, int y, int id) { - x += (Constants.EXTENDED_SCENE_SIZE - Constants.SCENE_SIZE) / 2; - y += (Constants.EXTENDED_SCENE_SIZE - Constants.SCENE_SIZE) / 2; - Scene scene = client.getScene(); + WorldView wv = client.getWorldView(worldId); + int offset = worldId == WorldView.TOPLEVEL ? (Constants.EXTENDED_SCENE_SIZE - Constants.SCENE_SIZE) / 2 : 0; + x += offset; + y += offset; + Scene scene = wv.getScene(); Tile[][][] tiles = scene.getExtendedTiles(); - Tile tile = tiles[client.getPlane()][x][y]; + Tile tile = tiles[wv.getPlane()][x][y]; if (tile != null) { for (GameObject gameObject : tile.getGameObjects()) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcIndicatorsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcIndicatorsPlugin.java index ab027363954..7d6465bb88c 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcIndicatorsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcIndicatorsPlugin.java @@ -41,6 +41,7 @@ import javax.inject.Inject; import javax.swing.SwingUtilities; import lombok.AccessLevel; +import lombok.AllArgsConstructor; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; @@ -175,7 +176,14 @@ public class NpcIndicatorsPlugin extends Plugin * Tagged NPCs that despawned this tick, which need to be verified that * they actually spawned and didn't just walk into view range. */ - private final List despawnedNpcsThisTick = new ArrayList<>(); + private final List despawnedNpcsThisTick = new ArrayList<>(); + + @AllArgsConstructor + private static class DespawnedNpc + { + WorldPoint coord; + int index; + } /** * World locations of graphics object which indicate that an @@ -300,6 +308,7 @@ public void onMenuEntryAdded(MenuEntryAdded event) client.createMenuEntry(idx--) .setOption(idMatch ? UNTAG : TAG) .setTarget(event.getTarget()) + .setWorldViewId(menuEntry.getWorldViewId()) .setIdentifier(event.getIdentifier()) .setType(MenuAction.RUNELITE) .onClick(this::tag); @@ -310,6 +319,7 @@ public void onMenuEntryAdded(MenuEntryAdded event) client.createMenuEntry(idx--) .setOption(nameMatch ? UNTAG_ALL : TAG_ALL) .setTarget(event.getTarget()) + .setWorldViewId(menuEntry.getWorldViewId()) .setIdentifier(event.getIdentifier()) .setType(MenuAction.RUNELITE) .onClick(this::tag); @@ -497,10 +507,14 @@ private int createTagStyleMenu(int idx, String target, NPC npc) private void tag(MenuEntry entry) { + WorldView wv = client.getWorldView(entry.getWorldViewId()); + if (wv == null) + { + return; + } + final int id = entry.getIdentifier(); - WorldView wv = client.getTopLevelWorldView(); final NPC npc = wv.npcs().byIndex(id); - if (npc == null || npc.getName() == null) { return; @@ -583,7 +597,7 @@ public void onNpcDespawned(NpcDespawned npcDespawned) if (memorizedNpcs.containsKey(npc.getIndex())) { - despawnedNpcsThisTick.add(npc); + despawnedNpcsThisTick.add(new DespawnedNpc(npc.getWorldLocation(), npc.getIndex())); } highlightedNpcs.remove(npc); @@ -713,7 +727,14 @@ void rebuild() return; } - for (NPC npc : client.getNpcs()) + rebuildWorldview(client.getTopLevelWorldView()); + + npcOverlayService.rebuild(); + } + + private void rebuildWorldview(WorldView wv) + { + for (NPC npc : wv.npcs()) { final String npcName = npc.getName(); @@ -730,7 +751,7 @@ void rebuild() if (highlightMatchesNPCName(npcName)) { - if (!client.isInInstancedRegion()) + if (!wv.isInstance()) { memorizeNpc(npc); } @@ -742,7 +763,10 @@ void rebuild() memorizedNpcs.remove(npc.getIndex()); } - npcOverlayService.rebuild(); + for (WorldView sub : wv.worldViews()) + { + rebuildWorldview(sub); + } } private boolean highlightMatchesNPCName(String npcName) @@ -766,20 +790,20 @@ private void validateSpawnedNpcs() } else { - for (NPC npc : despawnedNpcsThisTick) + for (DespawnedNpc npc : despawnedNpcsThisTick) { if (!teleportGraphicsObjectSpawnedThisTick.isEmpty()) { - if (teleportGraphicsObjectSpawnedThisTick.contains(npc.getWorldLocation())) + if (teleportGraphicsObjectSpawnedThisTick.contains(npc.coord)) { // NPC teleported away, so we don't want to add the respawn timer continue; } } - if (isInViewRange(client.getLocalPlayer().getWorldLocation(), npc.getWorldLocation())) + if (isInViewRange(client.getLocalPlayer().getWorldLocation(), npc.coord)) { - final MemorizedNpc mn = memorizedNpcs.get(npc.getIndex()); + final MemorizedNpc mn = memorizedNpcs.get(npc.index); if (mn != null) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java index d97c0794a4f..e26e888a0da 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java @@ -34,13 +34,13 @@ import java.awt.Shape; import java.awt.Stroke; import javax.inject.Inject; -import net.runelite.api.Client; import net.runelite.api.DecorativeObject; import net.runelite.api.GameObject; import net.runelite.api.GroundObject; import net.runelite.api.ObjectComposition; import net.runelite.api.TileObject; import net.runelite.api.WallObject; +import net.runelite.api.WorldView; import static net.runelite.client.plugins.objectindicators.ColorTileObject.HF_CLICKBOX; import static net.runelite.client.plugins.objectindicators.ColorTileObject.HF_HULL; import static net.runelite.client.plugins.objectindicators.ColorTileObject.HF_OUTLINE; @@ -54,16 +54,14 @@ class ObjectIndicatorsOverlay extends Overlay { - private final Client client; private final ObjectIndicatorsConfig config; private final ObjectIndicatorsPlugin plugin; private final ModelOutlineRenderer modelOutlineRenderer; @Inject - private ObjectIndicatorsOverlay(Client client, ObjectIndicatorsConfig config, ObjectIndicatorsPlugin plugin, + private ObjectIndicatorsOverlay(ObjectIndicatorsConfig config, ObjectIndicatorsPlugin plugin, ModelOutlineRenderer modelOutlineRenderer) { - this.client = client; this.config = config; this.plugin = plugin; this.modelOutlineRenderer = modelOutlineRenderer; @@ -90,8 +88,9 @@ public Dimension render(Graphics2D graphics) for (ColorTileObject obj : objects) { TileObject object = obj.getTileObject(); + WorldView wv = object.getWorldView(); - if (object.getPlane() != client.getPlane()) + if (wv == null || object.getPlane() != wv.getPlane()) { continue; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java index 4cc2fcca02a..765e4e306ef 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java @@ -50,7 +50,6 @@ import net.runelite.api.Client; import net.runelite.api.DecorativeObject; import net.runelite.api.GameObject; -import net.runelite.api.GameState; import net.runelite.api.GroundObject; import net.runelite.api.KeyCode; import net.runelite.api.Menu; @@ -61,17 +60,20 @@ import net.runelite.api.Tile; import net.runelite.api.TileObject; import net.runelite.api.WallObject; +import net.runelite.api.WorldEntity; +import net.runelite.api.WorldView; import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.DecorativeObjectDespawned; import net.runelite.api.events.DecorativeObjectSpawned; import net.runelite.api.events.GameObjectDespawned; import net.runelite.api.events.GameObjectSpawned; -import net.runelite.api.events.GameStateChanged; import net.runelite.api.events.GroundObjectDespawned; import net.runelite.api.events.GroundObjectSpawned; import net.runelite.api.events.MenuEntryAdded; import net.runelite.api.events.WallObjectDespawned; import net.runelite.api.events.WallObjectSpawned; +import net.runelite.api.events.WorldViewLoaded; +import net.runelite.api.events.WorldViewUnloaded; import net.runelite.client.callback.ClientThread; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; @@ -138,7 +140,7 @@ ObjectIndicatorsConfig provideConfig(ConfigManager configManager) protected void startUp() { overlayManager.add(overlay); - clientThread.invokeLater(this::reloadPoints); + clientThread.invokeLater(() -> loadPoints()); } @Override @@ -152,7 +154,7 @@ protected void shutDown() @Subscribe public void onProfileChanged(ProfileChanged e) { - clientThread.invokeLater(this::reloadPoints); + clientThread.invokeLater(() -> loadPoints()); } @Subscribe @@ -203,33 +205,49 @@ public void onGroundObjectDespawned(GroundObjectDespawned event) objects.removeIf(o -> o.getTileObject() == event.getGroundObject()); } - private void reloadPoints() + private void loadPoints() { points.clear(); - if (client.getMapRegions() != null) + + WorldView wv = client.getTopLevelWorldView(); + loadPoints(wv); + + for (WorldEntity we : wv.worldEntities()) + { + loadPoints(we.getWorldView()); + } + } + + private void loadPoints(WorldView wv) + { + int[] regions = wv.getMapRegions(); + if (regions == null) { - for (int regionId : client.getMapRegions()) + return; + } + + for (int regionId : regions) + { + // load points for region + final Set regionPoints = loadPoints(regionId); + if (regionPoints != null) { - // load points for region - final Set regionPoints = loadPoints(regionId); - if (regionPoints != null) - { - points.put(regionId, regionPoints); - } + points.put(regionId, regionPoints); } } } @Subscribe - public void onGameStateChanged(GameStateChanged gameStateChanged) + public void onWorldViewLoaded(WorldViewLoaded event) { - GameState gameState = gameStateChanged.getGameState(); - if (gameState == GameState.LOADING) - { - // Reload points with new map regions - objects.clear(); - reloadPoints(); - } + loadPoints(event.getWorldView()); + } + + @Subscribe + public void onWorldViewUnloaded(WorldViewUnloaded event) + { + var wv = event.getWorldView(); + objects.removeIf(c -> c.getTileObject().getWorldView() == wv); } @Subscribe @@ -240,7 +258,14 @@ public void onMenuEntryAdded(MenuEntryAdded event) return; } - final TileObject tileObject = findTileObject(client.getPlane(), event.getActionParam0(), event.getActionParam1(), event.getIdentifier()); + int worldId = event.getMenuEntry().getWorldViewId(); + WorldView wv = client.getWorldView(worldId); + if (wv == null) + { + return; + } + + final TileObject tileObject = findTileObject(wv, event.getActionParam0(), event.getActionParam1(), event.getIdentifier()); if (tileObject == null) { return; @@ -251,6 +276,7 @@ public void onMenuEntryAdded(MenuEntryAdded event) client.createMenuEntry(idx--) .setOption(marked.isPresent() ? UNMARK : MARK) .setTarget(event.getTarget()) + .setWorldViewId(worldId) .setParam0(event.getActionParam0()) .setParam1(event.getActionParam1()) .setIdentifier(event.getIdentifier()) @@ -403,7 +429,13 @@ private int createTagStyleMenu(int idx, String target, TileObject object) private void markObject(MenuEntry entry) { - TileObject object = findTileObject(client.getPlane(), entry.getParam0(), entry.getParam1(), entry.getIdentifier()); + WorldView wv = client.getWorldView(entry.getWorldViewId()); + if (wv == null) + { + return; + } + + TileObject object = findTileObject(wv, entry.getParam0(), entry.getParam1(), entry.getIdentifier()); if (object == null) { return; @@ -503,11 +535,12 @@ private void checkObjectPoints(TileObject object) } } - private TileObject findTileObject(int z, int x, int y, int id) + private TileObject findTileObject(WorldView wv, int x, int y, int id) { - Scene scene = client.getScene(); + int level = wv.getPlane(); + Scene scene = wv.getScene(); Tile[][][] tiles = scene.getTiles(); - final Tile tile = tiles[z][x][y]; + final Tile tile = tiles[level][x][y]; if (tile == null) { return null; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/playerindicators/PlayerIndicatorsService.java b/runelite-client/src/main/java/net/runelite/client/plugins/playerindicators/PlayerIndicatorsService.java index 069100b8be9..b5f6b691887 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/playerindicators/PlayerIndicatorsService.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/playerindicators/PlayerIndicatorsService.java @@ -36,11 +36,13 @@ import net.runelite.api.FriendsChatRank; import static net.runelite.api.FriendsChatRank.UNRANKED; import net.runelite.api.Player; +import net.runelite.api.WorldView; import net.runelite.api.clan.ClanChannel; import net.runelite.api.clan.ClanChannelMember; import net.runelite.api.clan.ClanRank; import net.runelite.api.clan.ClanSettings; import net.runelite.api.clan.ClanTitle; +import net.runelite.api.coords.LocalPoint; import net.runelite.api.gameval.VarbitID; import net.runelite.client.party.PartyService; import net.runelite.client.util.Text; @@ -62,9 +64,20 @@ private PlayerIndicatorsService(Client client, PlayerIndicatorsConfig config, Pa void forEachPlayer(final BiConsumer consumer) { - for (Player player : client.getPlayers()) + WorldView wv = client.getTopLevelWorldView(); + forEachPlayer(consumer, wv); + for (WorldView sub : wv.worldViews()) { - if (player == null || player.getName() == null) + forEachPlayer(consumer, sub); + } + } + + private void forEachPlayer(BiConsumer consumer, WorldView wv) + { + for (Player player : wv.players()) + { + LocalPoint lp = player.getLocalLocation(); + if (!wv.contains(lp)) { continue; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/tileindicators/TileIndicatorsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/tileindicators/TileIndicatorsOverlay.java index 23b45e9c83b..04acf99e3fa 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/tileindicators/TileIndicatorsOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/tileindicators/TileIndicatorsOverlay.java @@ -32,6 +32,8 @@ import javax.inject.Inject; import net.runelite.api.Client; import net.runelite.api.Perspective; +import net.runelite.api.Tile; +import net.runelite.api.WorldView; import net.runelite.api.coords.LocalPoint; import net.runelite.api.coords.WorldPoint; import net.runelite.client.ui.overlay.Overlay; @@ -59,10 +61,12 @@ public Dimension render(Graphics2D graphics) { if (config.highlightHoveredTile()) { + WorldView wv = client.getLocalPlayer().getWorldView(); + Tile tile = wv.getSelectedSceneTile(); // If we have tile "selected" render it - if (client.getSelectedSceneTile() != null) + if (tile != null) { - renderTile(graphics, client.getSelectedSceneTile().getLocalLocation(), config.highlightHoveredColor(), config.hoveredTileBorderWidth(), config.hoveredTileFillColor()); + renderTile(graphics, tile.getLocalLocation(), config.highlightHoveredColor(), config.hoveredTileBorderWidth(), config.hoveredTileFillColor()); } } diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/outline/ModelOutlineRenderer.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/outline/ModelOutlineRenderer.java index a7160963502..8af834178c9 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/outline/ModelOutlineRenderer.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/outline/ModelOutlineRenderer.java @@ -51,6 +51,7 @@ import net.runelite.api.RuneLiteObject; import net.runelite.api.TileObject; import net.runelite.api.WallObject; +import net.runelite.api.WorldView; import net.runelite.api.coords.LocalPoint; @Singleton @@ -521,10 +522,10 @@ private void simulateTriangleRasterizationForOutline( * @param vertexOrientation The orientation of the vertices. * @return Returns true if any of them are inside the clip area, otherwise false. */ - private boolean projectVertices(Model model, int localX, int localY, int localZ, final int vertexOrientation) + private boolean projectVertices(WorldView wv, Model model, int localX, int localY, int localZ, final int vertexOrientation) { final int vertexCount = model.getVerticesCount(); - Perspective.modelToCanvas(client, + Perspective.modelToCanvas(client, wv, vertexCount, localX, localY, localZ, vertexOrientation, @@ -894,7 +895,7 @@ private void processOutlinePixelQueue(int outlineWidth, Color color, int feather * @param outlineWidth The width of the outline * @param color The color of the outline */ - private void drawModelOutline(Model model, + private void drawModelOutline(WorldView wv, Model model, int localX, int localY, int localZ, int orientation, int outlineWidth, Color color, int feather) { @@ -927,7 +928,7 @@ else if (feather > MAX_FEATHER) clipX2 = client.getViewportWidth() + clipX1; clipY2 = client.getViewportHeight() + clipY1; - if (!projectVertices(model, localX, localY, localZ, orientation)) + if (!projectVertices(wv, model, localX, localY, localZ, orientation)) { // No vertex of the model is visible on the screen, so we can // assume there are no parts of the model to outline. @@ -985,8 +986,9 @@ public void drawOutline(NPC npc, int outlineWidth, Color color, int feather) LocalPoint lp = npc.getLocalLocation(); if (lp != null) { - drawModelOutline(npc.getModel(), lp.getX(), lp.getY(), - Perspective.getFootprintTileHeight(client, lp, client.getPlane(), npc.getComposition().getFootprintSize()) - npc.getAnimationHeightOffset(), + WorldView wv = npc.getWorldView(); + drawModelOutline(wv, npc.getModel(), lp.getX(), lp.getY(), + Perspective.getFootprintTileHeight(client, lp, wv.getPlane(), npc.getComposition().getFootprintSize()) - npc.getAnimationHeightOffset(), npc.getCurrentOrientation(), outlineWidth, color, feather); } } @@ -996,8 +998,9 @@ public void drawOutline(Player player, int outlineWidth, Color color, int feathe LocalPoint lp = player.getLocalLocation(); if (lp != null) { - drawModelOutline(player.getModel(), lp.getX(), lp.getY(), - Perspective.getFootprintTileHeight(client, lp, client.getPlane(), player.getFootprintSize()) - player.getAnimationHeightOffset(), + WorldView wv = player.getWorldView(); + drawModelOutline(wv, player.getModel(), lp.getX(), lp.getY(), + Perspective.getFootprintTileHeight(client, lp, wv.getPlane(), player.getFootprintSize()) - player.getAnimationHeightOffset(), player.getCurrentOrientation(), outlineWidth, color, feather); } } @@ -1010,7 +1013,7 @@ private void drawOutline(GameObject gameObject, int outlineWidth, Color color, i Model model = renderable instanceof Model ? (Model) renderable : renderable.getModel(); if (model != null) { - drawModelOutline(model, gameObject.getX(), gameObject.getY(), gameObject.getZ(), + drawModelOutline(gameObject.getWorldView(), model, gameObject.getX(), gameObject.getY(), gameObject.getZ(), gameObject.getModelOrientation(), outlineWidth, color, feather); } } @@ -1024,7 +1027,7 @@ private void drawOutline(GroundObject groundObject, int outlineWidth, Color colo Model model = renderable instanceof Model ? (Model) renderable : renderable.getModel(); if (model != null) { - drawModelOutline(model, groundObject.getX(), groundObject.getY(), groundObject.getZ(), + drawModelOutline(groundObject.getWorldView(), model, groundObject.getX(), groundObject.getY(), groundObject.getZ(), 0, outlineWidth, color, feather); } } @@ -1038,7 +1041,7 @@ private void drawOutline(ItemLayer itemLayer, int outlineWidth, Color color, int Model model = bottomRenderable instanceof Model ? (Model) bottomRenderable : bottomRenderable.getModel(); if (model != null) { - drawModelOutline(model, itemLayer.getX(), itemLayer.getY(), itemLayer.getZ() - itemLayer.getHeight(), + drawModelOutline(itemLayer.getWorldView(), model, itemLayer.getX(), itemLayer.getY(), itemLayer.getZ() - itemLayer.getHeight(), 0, outlineWidth, color, feather); } } @@ -1049,7 +1052,7 @@ private void drawOutline(ItemLayer itemLayer, int outlineWidth, Color color, int Model model = middleRenderable instanceof Model ? (Model) middleRenderable : middleRenderable.getModel(); if (model != null) { - drawModelOutline(model, itemLayer.getX(), itemLayer.getY(), itemLayer.getZ() - itemLayer.getHeight(), + drawModelOutline(itemLayer.getWorldView(), model, itemLayer.getX(), itemLayer.getY(), itemLayer.getZ() - itemLayer.getHeight(), 0, outlineWidth, color, feather); } } @@ -1060,7 +1063,7 @@ private void drawOutline(ItemLayer itemLayer, int outlineWidth, Color color, int Model model = topRenderable instanceof Model ? (Model) topRenderable : topRenderable.getModel(); if (model != null) { - drawModelOutline(model, itemLayer.getX(), itemLayer.getY(), itemLayer.getZ() - itemLayer.getHeight(), + drawModelOutline(itemLayer.getWorldView(), model, itemLayer.getX(), itemLayer.getY(), itemLayer.getZ() - itemLayer.getHeight(), 0, outlineWidth, color, feather); } } @@ -1074,7 +1077,7 @@ private void drawOutline(DecorativeObject decorativeObject, int outlineWidth, Co Model model = renderable1 instanceof Model ? (Model) renderable1 : renderable1.getModel(); if (model != null) { - drawModelOutline(model, + drawModelOutline(decorativeObject.getWorldView(), model, decorativeObject.getX() + decorativeObject.getXOffset(), decorativeObject.getY() + decorativeObject.getYOffset(), decorativeObject.getZ(), @@ -1089,7 +1092,7 @@ private void drawOutline(DecorativeObject decorativeObject, int outlineWidth, Co if (model != null) { // Offset is not used for the second model - drawModelOutline(model, decorativeObject.getX(), decorativeObject.getY(), decorativeObject.getZ(), + drawModelOutline(decorativeObject.getWorldView(), model, decorativeObject.getX(), decorativeObject.getY(), decorativeObject.getZ(), 0, outlineWidth, color, feather); } } @@ -1103,7 +1106,7 @@ private void drawOutline(WallObject wallObject, int outlineWidth, Color color, i Model model = renderable1 instanceof Model ? (Model) renderable1 : renderable1.getModel(); if (model != null) { - drawModelOutline(model, wallObject.getX(), wallObject.getY(), wallObject.getZ(), + drawModelOutline(wallObject.getWorldView(), model, wallObject.getX(), wallObject.getY(), wallObject.getZ(), 0, outlineWidth, color, feather); } } @@ -1114,7 +1117,7 @@ private void drawOutline(WallObject wallObject, int outlineWidth, Color color, i Model model = renderable2 instanceof Model ? (Model) renderable2 : renderable2.getModel(); if (model != null) { - drawModelOutline(model, wallObject.getX(), wallObject.getY(), wallObject.getZ(), + drawModelOutline(wallObject.getWorldView(), model, wallObject.getX(), wallObject.getY(), wallObject.getZ(), 0, outlineWidth, color, feather); } } @@ -1152,7 +1155,7 @@ public void drawOutline(GraphicsObject graphicsObject, int outlineWidth, Color c Model model = graphicsObject.getModel(); if (model != null) { - drawModelOutline(model, lp.getX(), lp.getY(), graphicsObject.getZ(), + drawModelOutline(graphicsObject.getWorldView(), model, lp.getX(), lp.getY(), graphicsObject.getZ(), 0, outlineWidth, color, feather); } } @@ -1166,8 +1169,13 @@ public void drawOutline(RuneLiteObject runeLiteObject, int outlineWidth, Color c Model model = runeLiteObject.getModel(); if (model != null) { - drawModelOutline(model, lp.getX(), lp.getY(), runeLiteObject.getZ(), - runeLiteObject.getOrientation(), outlineWidth, color, feather); + int worldView = runeLiteObject.getWorldView(); + WorldView wv = client.getWorldView(worldView); + if (wv != null) + { + drawModelOutline(wv, model, lp.getX(), lp.getY(), runeLiteObject.getZ(), + runeLiteObject.getOrientation(), outlineWidth, color, feather); + } } } } diff --git a/runelite-client/src/main/java/net/runelite/client/util/GameEventManager.java b/runelite-client/src/main/java/net/runelite/client/util/GameEventManager.java index 5246b6db09d..b72e97913ed 100644 --- a/runelite-client/src/main/java/net/runelite/client/util/GameEventManager.java +++ b/runelite-client/src/main/java/net/runelite/client/util/GameEventManager.java @@ -42,6 +42,7 @@ import net.runelite.api.Tile; import net.runelite.api.TileItem; import net.runelite.api.WallObject; +import net.runelite.api.WorldView; import net.runelite.api.events.DecorativeObjectSpawned; import net.runelite.api.events.GameObjectSpawned; import net.runelite.api.events.GroundObjectSpawned; @@ -72,19 +73,18 @@ private GameEventManager(Client client, ClientThread clientThread) * * @param consumer consumer accepting tile as parameter */ - private void forEachTile(Consumer consumer) + private void forEachTile(WorldView wv, Consumer consumer) { - final Scene scene = client.getScene(); + final Scene scene = wv.getScene(); final Tile[][][] tiles = scene.getTiles(); for (int z = 0; z < Constants.MAX_Z; ++z) { - for (int x = 0; x < Constants.SCENE_SIZE; ++x) + for (int x = 0; x < wv.getSizeX(); ++x) { - for (int y = 0; y < Constants.SCENE_SIZE; ++y) + for (int y = 0; y < wv.getSizeY(); ++y) { Tile tile = tiles[z][x][y]; - if (tile == null) { continue; @@ -122,84 +122,94 @@ public void simulateGameEvents(Object subscriber) eventBus.post(new ItemContainerChanged(itemContainer.getId(), itemContainer)); } - for (NPC npc : client.getTopLevelWorldView().npcs()) + simulateGameEvents(client.getTopLevelWorldView()); + + eventBus.unregister(subscriber); + }); + } + + private void simulateGameEvents(WorldView wv) + { + for (NPC npc : wv.npcs()) + { + if (npc != null) { - if (npc != null) - { - final NpcSpawned npcSpawned = new NpcSpawned(npc); - eventBus.post(npcSpawned); - } + final NpcSpawned npcSpawned = new NpcSpawned(npc); + eventBus.post(npcSpawned); } + } - for (Player player : client.getTopLevelWorldView().players()) + for (Player player : wv.players()) + { + if (player != null) { - if (player != null) - { - final PlayerSpawned playerSpawned = new PlayerSpawned(player); - eventBus.post(playerSpawned); - } + final PlayerSpawned playerSpawned = new PlayerSpawned(player); + eventBus.post(playerSpawned); } + } - forEachTile((tile) -> + forEachTile(wv, (tile) -> + { + WallObject wallObject = tile.getWallObject(); + if (wallObject != null) { - WallObject wallObject = tile.getWallObject(); - if (wallObject != null) - { - final WallObjectSpawned objectSpawned = new WallObjectSpawned(); - objectSpawned.setTile(tile); - objectSpawned.setWallObject(wallObject); - eventBus.post(objectSpawned); - } + final WallObjectSpawned objectSpawned = new WallObjectSpawned(); + objectSpawned.setTile(tile); + objectSpawned.setWallObject(wallObject); + eventBus.post(objectSpawned); + } - DecorativeObject decorativeObject = tile.getDecorativeObject(); - if (decorativeObject != null) - { - final DecorativeObjectSpawned objectSpawned = new DecorativeObjectSpawned(); - objectSpawned.setTile(tile); - objectSpawned.setDecorativeObject(decorativeObject); - eventBus.post(objectSpawned); - } + DecorativeObject decorativeObject = tile.getDecorativeObject(); + if (decorativeObject != null) + { + final DecorativeObjectSpawned objectSpawned = new DecorativeObjectSpawned(); + objectSpawned.setTile(tile); + objectSpawned.setDecorativeObject(decorativeObject); + eventBus.post(objectSpawned); + } - GroundObject groundObject = tile.getGroundObject(); - if (groundObject != null) - { - final GroundObjectSpawned objectSpawned = new GroundObjectSpawned(); - objectSpawned.setTile(tile); - objectSpawned.setGroundObject(groundObject); - eventBus.post(objectSpawned); - } + GroundObject groundObject = tile.getGroundObject(); + if (groundObject != null) + { + final GroundObjectSpawned objectSpawned = new GroundObjectSpawned(); + objectSpawned.setTile(tile); + objectSpawned.setGroundObject(groundObject); + eventBus.post(objectSpawned); + } - for (GameObject object : tile.getGameObjects()) + for (GameObject object : tile.getGameObjects()) + { + if (object != null) { - if (object != null) + if (object.getSceneMinLocation().equals(tile.getSceneLocation())) { - if (object.getSceneMinLocation().equals(tile.getSceneLocation())) - { - final GameObjectSpawned objectSpawned = new GameObjectSpawned(); - objectSpawned.setTile(tile); - objectSpawned.setGameObject(object); - eventBus.post(objectSpawned); - } + final GameObjectSpawned objectSpawned = new GameObjectSpawned(); + objectSpawned.setTile(tile); + objectSpawned.setGameObject(object); + eventBus.post(objectSpawned); } } + } - ItemLayer itemLayer = tile.getItemLayer(); - if (itemLayer != null) + ItemLayer itemLayer = tile.getItemLayer(); + if (itemLayer != null) + { + Node current = itemLayer.getTop(); + while (current instanceof TileItem) { - Node current = itemLayer.getTop(); - while (current instanceof TileItem) - { - final TileItem item = (TileItem) current; + final TileItem item = (TileItem) current; - current = current.getNext(); + current = current.getNext(); - final ItemSpawned itemSpawned = new ItemSpawned(tile, item); - eventBus.post(itemSpawned); - } + final ItemSpawned itemSpawned = new ItemSpawned(tile, item); + eventBus.post(itemSpawned); } - }); - - eventBus.unregister(subscriber); + } }); + + for (WorldView sub : wv.worldViews()) + { + simulateGameEvents(sub); + } } } \ No newline at end of file diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/loottracker/LootTrackerPluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/loottracker/LootTrackerPluginTest.java index c51bb9f8c6c..88c948c5188 100644 --- a/runelite-client/src/test/java/net/runelite/client/plugins/loottracker/LootTrackerPluginTest.java +++ b/runelite-client/src/test/java/net/runelite/client/plugins/loottracker/LootTrackerPluginTest.java @@ -162,8 +162,8 @@ public void setUp() wv = mock(WorldView.class); when(client.getTopLevelWorldView()).thenReturn(wv); - when(client.getWorldView(anyInt())).thenReturn(wv); + when(client.findWorldViewFromWorldPoint(any(WorldPoint.class))).thenReturn(wv); lootTrackerPlugin = spy(lootTrackerPlugin); doNothing().when(lootTrackerPlugin).addLoot(any(), anyInt(), any(), any(), any(Collection.class)); diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/timetracking/farming/CompostTrackerTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/timetracking/farming/CompostTrackerTest.java index 0116d69b54e..cd8fc990935 100644 --- a/runelite-client/src/test/java/net/runelite/client/plugins/timetracking/farming/CompostTrackerTest.java +++ b/runelite-client/src/test/java/net/runelite/client/plugins/timetracking/farming/CompostTrackerTest.java @@ -125,6 +125,7 @@ public void before() when(wv.getSizeX()).thenReturn(104); when(wv.getSizeY()).thenReturn(104); when(client.getTopLevelWorldView()).thenReturn(wv); + when(client.findWorldViewFromWorldPoint(any(WorldPoint.class))).thenReturn(wv); when(client.getLocalPlayer()).thenReturn(player); when(player.getWorldLocation()).thenReturn(worldPoint); From d020719dc837e9022650fd4bafcd4f45b6d1c7ab Mon Sep 17 00:00:00 2001 From: Tim Date: Sat, 11 Oct 2025 11:06:11 -0700 Subject: [PATCH 08/25] chores(Rs2Equipment): added null check for widget and default back to rectangle --- .../microbot/util/equipment/Rs2Equipment.java | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 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 106d272d825..81304b09dd5 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 @@ -336,40 +336,49 @@ public static void invokeMenu(Rs2ItemModel rs2Item, String action) { Rectangle rectangle = new Rectangle(1, 1, Microbot.getClient().getCanvasWidth(), Microbot.getClient().getCanvasHeight()); if (rs2Item.getSlot() == EquipmentInventorySlot.CAPE.getSlotIdx()) { param1 = 25362448; - rectangle = Rs2Widget.getWidget(InterfaceID.WORNITEMS,16).getBounds(); + rectangle = getSafeBounds(InterfaceID.WORNITEMS,16); } else if (rs2Item.getSlot() == EquipmentInventorySlot.HEAD.getSlotIdx()) { param1 = 25362447; - rectangle = Rs2Widget.getWidget(InterfaceID.WORNITEMS,15).getBounds(); + rectangle = getSafeBounds(InterfaceID.WORNITEMS,15); } else if (rs2Item.getSlot() == EquipmentInventorySlot.AMMO.getSlotIdx()) { param1 = 25362457; - rectangle = Rs2Widget.getWidget(InterfaceID.WORNITEMS,25).getBounds(); + rectangle = getSafeBounds(InterfaceID.WORNITEMS,25); } else if (rs2Item.getSlot() == EquipmentInventorySlot.AMULET.getSlotIdx()) { param1 = 25362449; - rectangle = Rs2Widget.getWidget(InterfaceID.WORNITEMS,17).getBounds(); + rectangle = getSafeBounds(InterfaceID.WORNITEMS,17); } else if (rs2Item.getSlot() == EquipmentInventorySlot.WEAPON.getSlotIdx()) { param1 = 25362450; - rectangle = Rs2Widget.getWidget(InterfaceID.WORNITEMS,18).getBounds(); + rectangle = getSafeBounds(InterfaceID.WORNITEMS,18); } else if (rs2Item.getSlot() == EquipmentInventorySlot.BODY.getSlotIdx()) { param1 = 25362451; - rectangle = Rs2Widget.getWidget(InterfaceID.WORNITEMS,19).getBounds(); + rectangle = getSafeBounds(InterfaceID.WORNITEMS,19); } else if (rs2Item.getSlot() == EquipmentInventorySlot.SHIELD.getSlotIdx()) { param1 = 25362452; - rectangle = Rs2Widget.getWidget(InterfaceID.WORNITEMS,20).getBounds(); + rectangle = getSafeBounds(InterfaceID.WORNITEMS,20); } else if (rs2Item.getSlot() == EquipmentInventorySlot.LEGS.getSlotIdx()) { param1 = 25362453; - rectangle = Rs2Widget.getWidget(InterfaceID.WORNITEMS,21).getBounds(); + rectangle = getSafeBounds(InterfaceID.WORNITEMS,21); } else if (rs2Item.getSlot() == EquipmentInventorySlot.GLOVES.getSlotIdx()) { param1 = 25362454; - rectangle = Rs2Widget.getWidget(InterfaceID.WORNITEMS,22).getBounds(); + rectangle = getSafeBounds(InterfaceID.WORNITEMS,22); } else if (rs2Item.getSlot() == EquipmentInventorySlot.BOOTS.getSlotIdx()) { param1 = 25362455; - rectangle = Rs2Widget.getWidget(InterfaceID.WORNITEMS,23).getBounds(); + rectangle = getSafeBounds(InterfaceID.WORNITEMS,23); } else if (rs2Item.getSlot() == EquipmentInventorySlot.RING.getSlotIdx()) { param1 = 25362456; - rectangle = Rs2Widget.getWidget(InterfaceID.WORNITEMS,24).getBounds(); + rectangle = getSafeBounds(InterfaceID.WORNITEMS,24); } - Microbot.doInvoke(new NewMenuEntry(param0, param1, menuAction.getId(), identifier, -1, rs2Item.getName()), rectangle); //Rs2Reflection.invokeMenu(param0, param1, menuAction.getId(), identifier, rs2Item.id, action, target, -1, -1); } + + private static Rectangle getSafeBounds(int interfaceId, int childId) { + Widget widget = Rs2Widget.getWidget(interfaceId, childId); + if (widget != null && widget.getBounds() != null) { + return widget.getBounds(); + } + return new Rectangle(1, 1, + Microbot.getClient().getCanvasWidth(), + Microbot.getClient().getCanvasHeight()); + } } \ No newline at end of file From 88f24572c127e27fba97ddf4c6130f85a02c9c50 Mon Sep 17 00:00:00 2001 From: Julian Pettit Date: Sat, 11 Oct 2025 14:26:48 -0400 Subject: [PATCH 09/25] item identification: add surge potion --- .../client/plugins/itemidentification/ItemIdentification.java | 1 + 1 file changed, 1 insertion(+) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentification.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentification.java index 4e9d3f4160a..fee3fe1bcb6 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentification.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentification.java @@ -325,6 +325,7 @@ enum ItemIdentification ANCIENT_BREW(Type.POTION, "AncBr", "A.Br", ItemID._4DOSEANCIENTBREW, ItemID._3DOSEANCIENTBREW, ItemID._2DOSEANCIENTBREW, ItemID._1DOSEANCIENTBREW), ANCIENT_MIX(Type.POTION, "AncBr", "Anc", ItemID.BRUTAL_1DOSEANCIENTBREW, ItemID.BRUTAL_2DOSEANCIENTBREW), FORGOTTEN_BREW(Type.POTION, "ForgBr", "F.Br", ItemID._4DOSEFORGOTTENBREW, ItemID._3DOSEFORGOTTENBREW, ItemID._2DOSEFORGOTTENBREW, ItemID._1DOSEFORGOTTENBREW), + SURGE_POTION(Type.POTION, "Surge", "Sur", ItemID._4DOSESURGE, ItemID._3DOSESURGE, ItemID._2DOSESURGE, ItemID._1DOSESURGE), ANTIPOISON(Type.POTION, "AntiP", "AP", ItemID._4DOSEANTIPOISON, ItemID._3DOSEANTIPOISON, ItemID._2DOSEANTIPOISON, ItemID._1DOSEANTIPOISON), ANTIPOISON_MIX(Type.POTION, "AntiP", "AP", ItemID.BRUTAL_1DOSEANTIPOISON, ItemID.BRUTAL_2DOSEANTIPOISON), From 6931bbeecee261810a28eabfe26e956bf72bef4d Mon Sep 17 00:00:00 2001 From: coopermor Date: Wed, 17 Sep 2025 11:19:38 -0700 Subject: [PATCH 10/25] worldmap: Add Darkmeyer wall rocks, Fossil island zipline, Mausoleum bridge, and slayer tower shortcuts --- .../runelite/client/game/AgilityShortcut.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/runelite-client/src/main/java/net/runelite/client/game/AgilityShortcut.java b/runelite-client/src/main/java/net/runelite/client/game/AgilityShortcut.java index b1962d22a05..2cab92eeb80 100644 --- a/runelite-client/src/main/java/net/runelite/client/game/AgilityShortcut.java +++ b/runelite-client/src/main/java/net/runelite/client/game/AgilityShortcut.java @@ -32,6 +32,7 @@ import net.runelite.api.TileObject; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.ObjectID; +import net.runelite.api.gameval.VarbitID; @Getter public enum AgilityShortcut @@ -117,6 +118,7 @@ public boolean matches(Client client, TileObject object) CRABCLAW_CAVES_CREVICE(18, "Crevice", new WorldPoint(1710, 9822, 0), ObjectID.HOSIDIUSQUEST_CRACKIN, ObjectID.HOSIDIUSQUEST_CRACKOUT), CRABCLAW_CAVES_ROCKS(18, "Rocks", new WorldPoint(1687, 9802, 0), ObjectID.HOSIDIUSQUEST_ROCK), CRABCLAW_CAVES_STEPPING_STONES(18, "Stepping Stones", new WorldPoint(1704, 9800, 0), ObjectID.HOSIDIUSQUEST_STONE), + SLAYER_TOWER_GROUND_WINDOW(18, "Window", new WorldPoint(3442, 3532, 0), ObjectID.SLAYERTOWER_WINDOW_SHORTCUT_THROUGH), YANILLE_WATCHTOWER_TRELLIS(18, "Trellis", null, ObjectID.QIP_WATCHTOWER_TRELLIS_BASE), COAL_TRUCKS_LOG_BALANCE(20, "Log Balance", new WorldPoint(2598, 3475, 0), ObjectID.MINE_LOG_BALANCE1), GRAND_EXCHANGE_UNDERWALL_TUNNEL(21, "Underwall Tunnel", new WorldPoint(3139, 3515, 0), ObjectID.VARROCK_SC_TUNNEL_WEST, ObjectID.VARROCK_SC_TUNNEL_EAST), @@ -242,6 +244,14 @@ public boolean matches(Client client, TileObject object) WEISS_OBSTACLES(68, "Shortcut", null, ObjectID.MY2ARM_CLIFF_SHORTCUT_1, ObjectID.MY2ARM_CLIFF_SHORTCUT_2, ObjectID.MY2ARM_CLIFF_SHORTCUT_3, ObjectID.MY2ARM_CLIFF_SHORTCUT_3_ROPETRAIL_MULTI, ObjectID.MY2ARM_CLIFF_SHORTCUT_4, ObjectID.MY2ARM_CLIFF_SHORTCUT_5, ObjectID.MY2ARM_CLIFF_SHORTCUT_6), WEISS_FARMING_PATCH_BOULDER(0, "Shortcut", null, ObjectID.MY2ARM_HERBPATCH_ACCESS), ARCEUUS_ESSENSE_NORTH(69, "Rock Climb", new WorldPoint(1759, 3873, 0), ObjectID.ARCHEUUS_RUNESTONE_SHORTCUT_GREY_SHORTCUT_NORTH), + FENKENSTRAIN_MAUSOLEUM_BRIDGE(69, "Bridge Jump", new WorldPoint(3504, 3560, 0), ObjectID.FENK_BRIDGE_MULTI_NORTH, ObjectID.FENK_BRIDGE_MULTI_NORTH_MIRROR, ObjectID.FENK_BRIDGE_MULTI_SOUTH, ObjectID.FENK_BRIDGE_MULTI_SOUTH_MIRROR) + { + @Override + public boolean matches(Client client, TileObject object) + { + return client.getVarbitValue(VarbitID.FENK_BUILT_BRIDGE_NORTH) == 2 && client.getVarbitValue(VarbitID.FENK_BUILT_BRIDGE_SOUTH) == 2; + } + }, TAVERLEY_DUNGEON_PIPE_BLUE_DRAGON(70, "Pipe Squeeze", new WorldPoint(2886, 9798, 0), ObjectID.TAVERLY_DUNGEON_PIPE_SC), TAVERLEY_DUNGEON_ROCKS_NORTH(70, "Rocks", new WorldPoint(2887, 9823, 0), ObjectID.TAVERLEY_DRAGON_JUMPUP, ObjectID.TAVERLEY_DRAGON_JUMPDOWN), TAVERLEY_DUNGEON_ROCKS_SOUTH(70, "Rocks", new WorldPoint(2887, 9631, 0), ObjectID.TAVERLEY_DRAGON_JUMPUP, ObjectID.TAVERLEY_DRAGON_JUMPDOWN), @@ -275,6 +285,7 @@ public boolean matches(Client client, TileObject object) LAVA_DRAGON_ISLE_JUMP(74, "Stepping Stone", new WorldPoint(3200, 3807, 0), ObjectID.WILDERNESS_LAVA_DRAGONS_SHORTCUT), MEIYERDITCH_LAB_TUNNELS_NORTH(74, "Cave", new WorldPoint(3623, 9747, 0), ObjectID.MYQ5_WALL_CAVE_SHORTCUT_1, ObjectID.MYQ5_WALL_CAVE_SHORTCUT_2), MEIYERDITCH_LAB_TUNNELS_SOUTH(74, "Cave", new WorldPoint(3618, 9722, 0), ObjectID.MYQ5_WALL_CAVE_SHORTCUT_3, ObjectID.MYQ5_WALL_CAVE_SHORTCUT_4), + FOSSIL_ISLAND_ZIPLINE(74, "Zipline", new WorldPoint(3764, 3883, 0), ObjectID.FOSSIL_ZIPWIRE_START), MOKHAIOTL_PIT_JUMP(75, "Jump", null, ObjectID.MOKI_AGIL_SHORTCUT_OP), REVENANT_CAVES_DEMONS_JUMP(75, "Jump", new WorldPoint(3199, 10135, 0), ObjectID.WILD_CAVE_AGILITY_JUMP), REVENANT_CAVES_ANKOU_EAST(75, "Jump", new WorldPoint(3201, 10195, 0), ObjectID.WILD_CAVE_AGILITY_JUMP), @@ -290,6 +301,8 @@ public boolean matches(Client client, TileObject object) SHILO_VILLAGE_ROCKS(79, "Rocks", new WorldPoint(2870, 3003, 0), ObjectID.SHORTCUT_SHILO_ROCKS_TOP, ObjectID.SHORTCUT_SHILO_ROCKS_BOTTOM), KHARAZI_JUNGLE_VINE_CLIMB(79, "Vine", new WorldPoint(2897, 2939, 0), ObjectID.KHARAZI_SHORTCUT_VINE_END, ObjectID.KHARAZI_SHORTCUT_VINE_DIAG1), TAVERLEY_DUNGEON_SPIKED_BLADES(80, "Strange Floor", new WorldPoint(2877, 9813, 0), ObjectID.TAVERLY_DUNGEON_FLOOR_SPIKES_SC), + SLAYER_TOWER_IVY(81, "Ivy", new WorldPoint(3417, 3533, 0), ObjectID.SLAYERTOWER_SHORTCUT_UP), + SLAYER_TOWER_TOP_WINDOW(81, "Window", new WorldPoint(3419, 3534, 0), ObjectID.SLAYERTOWER_WINDOW_SHORTCUT_DOWN), WATERBIRTH_DUNGEON_CREVICE(81, "Crevice", new WorldPoint(2604, 10070, 0), ObjectID.DAGANNOTH_CREVICE), LAVA_MAZE_NORTH_JUMP(82, "Stepping Stone", new WorldPoint(3092, 3880, 0), ObjectID.WILDERNESS_LAVA_MAZE_NORTHERN_SHORTCUT), ASGARNIA_ICE_DUNGEON_ADEPT_WEST(82, "Tunnel", new WorldPoint(3012, 9549, 0), ObjectID.CAVEWALL_SHORTCUT_WYVERN_WEST), @@ -301,6 +314,14 @@ public boolean matches(Client client, TileObject object) ELVEN_ADVANCED_CLIFF_SCRAMBLE(85, "Rocks", new WorldPoint(2337, 3253, 0), ObjectID.ELVES_OVERPASS_SC_ROCKS_TOP, ObjectID.ELVES_OVERPASS_SC_ROCKS_BOTTOM), ELVEN_ADVANCED_CLIFF_SCRAMBLE_PRIFDDINAS(85, "Rocks", new WorldPoint(3361, 6005, 0), ObjectID.ELVES_OVERPASS_SC_ROCKS_TOP, ObjectID.ELVES_OVERPASS_SC_ROCKS_BOTTOM), WATERBIRTH_ISLAND_ROCKS(85, "Rocks", new WorldPoint(2546, 3750, 0), ObjectID.DAGANNOTH_WATERBIRTH_ROCK_CLIMB_AGILITY_SHORTCUT_BOTTOM, ObjectID.DAGANNOTH_WATERBIRTH_ROCK_CLIMB_AGILITY_SHORTCUT_TOP), + DARKMEYER_WALL_ROCKS(86, "Rocks", new WorldPoint(3563, 3380, 0), ObjectID.HH_MASTER005, ObjectID.DARKM_WALL_ROCK_SHORTCUT) + { + @Override + public boolean matches(Client client, TileObject object) + { + return Quest.SINS_OF_THE_FATHER.getState(client) == QuestState.FINISHED; + } + }, KALPHITE_WALL(86, "Crevice", new WorldPoint(3214, 9508, 0), ObjectID.KALPHITE_WALL_SHORTCUT), BRIMHAVEN_DUNGEON_VINE_EAST(87, "Vine", new WorldPoint(2672, 9582, 0), ObjectID.KARAM_CAVEWALL_VINE_CLIMBABLE_BOTTOM, ObjectID.KARAM_CAVEWALL_VINE_CLIMBABLE_TOP), BRIMHAVEN_DUNGEON_VINE_WEST(87, "Vine", new WorldPoint(2606, 9584, 0), ObjectID.KARAM_CAVEWALL_VINE_CLIMBABLE_BOTTOM, ObjectID.KARAM_CAVEWALL_VINE_CLIMBABLE_TOP), From 60c68c7749ca8806fd1dbf7eb27a439057a9bc59 Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 11 Oct 2025 14:59:35 -0400 Subject: [PATCH 11/25] xpupdater: remove runetracker --- .../plugins/xpupdater/XpUpdaterConfig.java | 11 -------- .../plugins/xpupdater/XpUpdaterPlugin.java | 26 ------------------- 2 files changed, 37 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/xpupdater/XpUpdaterConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/xpupdater/XpUpdaterConfig.java index 992d84a211b..40a0113413f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/xpupdater/XpUpdaterConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/xpupdater/XpUpdaterConfig.java @@ -42,17 +42,6 @@ default boolean cml() return false; } - @ConfigItem( - position = 2, - keyName = "runetracker", - name = "RuneTracker", - description = "Automatically updates your stats on runetracker.org when you log out." - ) - default boolean runetracker() - { - return false; - } - @ConfigItem( position = 3, keyName = "templeosrs", diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/xpupdater/XpUpdaterPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/xpupdater/XpUpdaterPlugin.java index bb7087ca003..991b1da6c98 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/xpupdater/XpUpdaterPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/xpupdater/XpUpdaterPlugin.java @@ -136,7 +136,6 @@ private void update(long accountHash, String username) EnumSet worldTypes = client.getWorldType(); username = username.replace(" ", "_"); updateCml(username, worldTypes); - updateRunetracker(username, worldTypes); updateTempleosrs(accountHash, username, worldTypes); updateWom(accountHash, username, worldTypes); } @@ -167,31 +166,6 @@ private void updateCml(String username, EnumSet worldTypes) } } - private void updateRunetracker(String username, EnumSet worldTypes) - { - if (config.runetracker() - && !worldTypes.contains(WorldType.SEASONAL) - && !worldTypes.contains(WorldType.DEADMAN) - && !worldTypes.contains(WorldType.NOSAVE_MODE) - && !worldTypes.contains(WorldType.FRESH_START_WORLD)) - { - HttpUrl url = new HttpUrl.Builder() - .scheme("https") - .host("rscript.org") - .addPathSegment("lookup.php") - .addQueryParameter("type", "stats07") - .addQueryParameter("user", username) - .build(); - - Request request = new Request.Builder() - .header("User-Agent", "RuneLite") - .url(url) - .build(); - - sendRequest("RuneTracker", request); - } - } - private void updateTempleosrs(long accountHash, String username, EnumSet worldTypes) { if (config.templeosrs() From 30919eb43742fec55e9f53d99b0a025b6a362cb8 Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 11 Oct 2025 15:04:31 -0400 Subject: [PATCH 12/25] xpupdater: exclude tournament worlds --- .../client/plugins/xpupdater/XpUpdaterPlugin.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/xpupdater/XpUpdaterPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/xpupdater/XpUpdaterPlugin.java index 991b1da6c98..d6bc9548651 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/xpupdater/XpUpdaterPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/xpupdater/XpUpdaterPlugin.java @@ -146,7 +146,8 @@ private void updateCml(String username, EnumSet worldTypes) && !worldTypes.contains(WorldType.SEASONAL) && !worldTypes.contains(WorldType.DEADMAN) && !worldTypes.contains(WorldType.NOSAVE_MODE) - && !worldTypes.contains(WorldType.FRESH_START_WORLD)) + && !worldTypes.contains(WorldType.FRESH_START_WORLD) + && !worldTypes.contains(WorldType.TOURNAMENT_WORLD)) { HttpUrl url = new HttpUrl.Builder() .scheme("https") @@ -171,7 +172,8 @@ private void updateTempleosrs(long accountHash, String username, EnumSet wor { if (config.wiseoldman() && !worldTypes.contains(WorldType.DEADMAN) - && !worldTypes.contains(WorldType.NOSAVE_MODE)) + && !worldTypes.contains(WorldType.NOSAVE_MODE) + && !worldTypes.contains(WorldType.TOURNAMENT_WORLD)) { HttpUrl url = new HttpUrl.Builder() .scheme("https") From 8b44fca4d818cd3e78ca0e525448e59768334e4c Mon Sep 17 00:00:00 2001 From: chsami Date: Sat, 11 Oct 2025 22:44:55 +0200 Subject: [PATCH 13/25] feat: add search functionality to MicrobotConfigPanel --- .../microbot/ui/MicrobotConfigPanel.java | 127 +++++++++++++----- 1 file changed, 94 insertions(+), 33 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/ui/MicrobotConfigPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/ui/MicrobotConfigPanel.java index 365c7f4fe84..83ed9de9439 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/ui/MicrobotConfigPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/ui/MicrobotConfigPanel.java @@ -107,6 +107,11 @@ class MicrobotConfigPanel extends MicrobotPluginPanel { private final MicrobotPluginToggleButton pluginToggle; private MicrobotPluginConfigurationDescriptor pluginConfig = null; + // add near other fields + private final JTextField searchField = new JTextField(); + private final Map itemIndex = new HashMap<>(); // item panel -> lowercased name + private final Map sectionContentByKey = new HashMap<>(); // section key -> contents panel + private final Map sectionPanelByDesc = new HashMap<>(); // whole section panel @Inject private MicrobotConfigPanel( @@ -129,11 +134,7 @@ private MicrobotConfigPanel( setLayout(new BorderLayout()); setBackground(ColorScheme.DARK_GRAY_COLOR); - JPanel topPanel = new JPanel(); - topPanel.setBorder(new EmptyBorder(10, 10, 10, 10)); - topPanel.setLayout(new BorderLayout(0, BORDER_OFFSET)); - add(topPanel, BorderLayout.NORTH); - + // --- initialize mainPanel first --- mainPanel = new MicrobotFixedWidthPanel(); mainPanel.setBorder(new EmptyBorder(8, 10, 10, 10)); mainPanel.setLayout(new DynamicGridLayout(0, 1, 0, 5)); @@ -147,21 +148,83 @@ private MicrobotConfigPanel( scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); add(scrollPane, BorderLayout.CENTER); - JButton topPanelBackButton = new JButton(BACK_ICON); - SwingUtil.removeButtonDecorations(topPanelBackButton); - topPanelBackButton.setPreferredSize(new Dimension(22, 0)); - topPanelBackButton.setBorder(new EmptyBorder(0, 0, 0, 5)); - topPanelBackButton.addActionListener(e -> pluginList.getMuxer().popState()); - topPanelBackButton.setToolTipText("Back"); - topPanel.add(topPanelBackButton, BorderLayout.WEST); + // --- topPanel construction --- + JPanel topPanel = new JPanel(new BorderLayout(0, BORDER_OFFSET)); + topPanel.setBorder(new EmptyBorder(10, 10, 10, 10)); + add(topPanel, BorderLayout.NORTH); + + JPanel header = new JPanel(new BorderLayout()); + JButton backBtn = new JButton(BACK_ICON); + SwingUtil.removeButtonDecorations(backBtn); + backBtn.setPreferredSize(new Dimension(22, 0)); + backBtn.setBorder(new EmptyBorder(0, 0, 0, 5)); + backBtn.addActionListener(e -> pluginList.getMuxer().popState()); + backBtn.setToolTipText("Back"); pluginToggle = new MicrobotPluginToggleButton(); - topPanel.add(pluginToggle, BorderLayout.EAST); title = new JLabel(); title.setForeground(Color.WHITE); - topPanel.add(title); + header.add(backBtn, BorderLayout.WEST); + header.add(title, BorderLayout.CENTER); + header.add(pluginToggle, BorderLayout.EAST); + topPanel.add(header, BorderLayout.NORTH); + + // --- search field --- + searchField.setToolTipText("Filter settings"); + searchField.putClientProperty("JTextField.placeholderText", "Search settings..."); + searchField.getDocument().addDocumentListener(new javax.swing.event.DocumentListener() { + public void insertUpdate(javax.swing.event.DocumentEvent e) { + applyFilter(); + } + + public void removeUpdate(javax.swing.event.DocumentEvent e) { + applyFilter(); + } + + public void changedUpdate(javax.swing.event.DocumentEvent e) { + applyFilter(); + } + }); + searchField.setPreferredSize(new Dimension(10, 26)); + topPanel.add(searchField, BorderLayout.CENTER); + } + + + private void applyFilter() { + String q = searchField.getText(); + String needle = q == null ? "" : q.trim().toLowerCase(Locale.ROOT); + // show all if empty + boolean noFilter = needle.isEmpty(); + + // toggle each item + for (Map.Entry e : itemIndex.entrySet()) { + boolean match = noFilter || e.getValue().contains(needle); + e.getKey().setVisible(match); + } + + // hide section contents with no visible children + for (Map.Entry e : sectionContentByKey.entrySet()) { + JPanel contents = e.getValue(); + boolean anyVisible = false; + for (Component c : contents.getComponents()) { + if (c.isVisible()) { + anyVisible = true; + break; + } + } + contents.getParent().setVisible(anyVisible); // whole section panel + contents.setVisible(anyVisible); // keep contents open when filtering + + // auto-expand sections with matches + if (!anyVisible) continue; + // ensure expanded when filtering + if (!noFilter && !contents.isVisible()) contents.setVisible(true); + } + + mainPanel.revalidate(); + mainPanel.repaint(); } void init(MicrobotPluginConfigurationDescriptor pluginConfig) { @@ -211,16 +274,15 @@ private void toggleSection(ConfigSectionDescriptor csd, JButton button, JPanel c private void rebuild() { mainPanel.removeAll(); - + itemIndex.clear(); + sectionContentByKey.clear(); + sectionPanelByDesc.clear(); ConfigDescriptor cd = pluginConfig.getConfigDescriptor(); final Map sectionWidgets = new HashMap<>(); final Map topLevelPanels = new TreeMap<>((a, b) -> - ComparisonChain.start() - .compare(a.position(), b.position()) - .compare(a.name(), b.name()) - .result()); + ComparisonChain.start().compare(a.position(), b.position()).compare(a.name(), b.name()).result()); if (cd.getInformation() != null) { buildInformationPanel(cd.getInformation()); @@ -228,23 +290,20 @@ private void rebuild() { for (ConfigSectionDescriptor csd : cd.getSections()) { ConfigSection cs = csd.getSection(); - final boolean isOpen = sectionExpandStates.getOrDefault(csd, !cs.closedByDefault()); + boolean isOpen = sectionExpandStates.getOrDefault(csd, !cs.closedByDefault()); - final JPanel section = new JPanel(); + JPanel section = new JPanel(); section.setLayout(new BoxLayout(section, BoxLayout.Y_AXIS)); section.setMinimumSize(new Dimension(PANEL_WIDTH, 0)); - final JPanel sectionHeader = new JPanel(); - sectionHeader.setLayout(new BorderLayout()); + JPanel sectionHeader = new JPanel(new BorderLayout()); sectionHeader.setMinimumSize(new Dimension(PANEL_WIDTH, 0)); - // For whatever reason, the header extends out by a single pixel when closed. Adding a single pixel of - // border on the right only affects the width when closed, fixing the issue. sectionHeader.setBorder(new CompoundBorder( new MatteBorder(0, 0, 1, 0, ColorScheme.MEDIUM_GRAY_COLOR), new EmptyBorder(0, 0, 3, 1))); section.add(sectionHeader, BorderLayout.NORTH); - final JButton sectionToggle = new JButton(isOpen ? SECTION_RETRACT_ICON : SECTION_EXPAND_ICON); + JButton sectionToggle = new JButton(isOpen ? SECTION_RETRACT_ICON : SECTION_EXPAND_ICON); sectionToggle.setPreferredSize(new Dimension(18, 0)); sectionToggle.setBorder(new EmptyBorder(0, 0, 0, 5)); sectionToggle.setToolTipText(isOpen ? "Retract" : "Expand"); @@ -252,14 +311,13 @@ private void rebuild() { sectionHeader.add(sectionToggle, BorderLayout.WEST); String name = cs.name(); - final JLabel sectionName = new JLabel(name); + JLabel sectionName = new JLabel(name); sectionName.setForeground(ColorScheme.BRAND_ORANGE); sectionName.setFont(FontManager.getRunescapeBoldFont()); sectionName.setToolTipText("" + name + ":
" + cs.description() + ""); sectionHeader.add(sectionName, BorderLayout.CENTER); - final JPanel sectionContents = new JPanel(); - sectionContents.setLayout(new DynamicGridLayout(0, 1, 0, 5)); + JPanel sectionContents = new JPanel(new DynamicGridLayout(0, 1, 0, 5)); sectionContents.setMinimumSize(new Dimension(PANEL_WIDTH, 0)); sectionContents.setBorder(new CompoundBorder( new MatteBorder(0, 0, 1, 0, ColorScheme.MEDIUM_GRAY_COLOR), @@ -267,22 +325,23 @@ private void rebuild() { sectionContents.setVisible(isOpen); section.add(sectionContents, BorderLayout.SOUTH); - // Add listeners to each part of the header so that it's easier to toggle them - final MouseAdapter adapter = new MouseAdapter() { + MouseAdapter adapter = new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { toggleSection(csd, sectionToggle, sectionContents); } }; - sectionToggle.addActionListener(actionEvent -> toggleSection(csd, sectionToggle, sectionContents)); + + sectionToggle.addActionListener(ev -> toggleSection(csd, sectionToggle, sectionContents)); sectionName.addMouseListener(adapter); sectionHeader.addMouseListener(adapter); sectionWidgets.put(csd.getKey(), sectionContents); + sectionPanelByDesc.put(csd, section); + sectionContentByKey.put(csd.getKey(), sectionContents); topLevelPanels.put(csd, section); } - for (ConfigItemDescriptor cid : cd.getItems()) { if (cid.getItem().hidden()) { continue; @@ -334,6 +393,7 @@ public void mouseClicked(MouseEvent e) { } else { section.add(item); } + itemIndex.put(item, name.toLowerCase(Locale.ROOT)); } topLevelPanels.values().forEach(mainPanel::add); @@ -364,6 +424,7 @@ public void mouseClicked(MouseEvent e) { mainPanel.add(backButton); revalidate(); + applyFilter(); } private void buildInformationPanel(ConfigInformation ci) { From 688541567b3a65b832311fd4c8473d3b8efac0c1 Mon Sep 17 00:00:00 2001 From: chsami Date: Sat, 11 Oct 2025 22:45:11 +0200 Subject: [PATCH 14/25] feat: bump 2.0.22 --- runelite-client/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runelite-client/pom.xml b/runelite-client/pom.xml index 9541576a854..a13bcea92af 100644 --- a/runelite-client/pom.xml +++ b/runelite-client/pom.xml @@ -41,7 +41,7 @@ nogit false false - 2.0.22 + 2.0.23 nogit From 5d6deafa28d5376cec0c7a81ada62e74ab35cd35 Mon Sep 17 00:00:00 2001 From: RuneLite updater Date: Sun, 12 Oct 2025 14:03:13 +0000 Subject: [PATCH 15/25] Release 1.11.21 --- cache/pom.xml | 2 +- pom.xml | 4 ++-- runelite-api/pom.xml | 2 +- runelite-client/pom.xml | 2 +- runelite-jshell/pom.xml | 2 +- runelite-maven-plugin/pom.xml | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cache/pom.xml b/cache/pom.xml index 8f08363a43a..8ef299d3e99 100644 --- a/cache/pom.xml +++ b/cache/pom.xml @@ -29,7 +29,7 @@ net.runelite runelite-parent - 1.11.21-SNAPSHOT + 1.11.21 cache diff --git a/pom.xml b/pom.xml index 14de6625976..8919b964855 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,7 @@ net.runelite runelite-parent - 1.11.21-SNAPSHOT + 1.11.21 pom RuneLite @@ -61,7 +61,7 @@ https://github.com/runelite/runelite scm:git:git://github.com/runelite/runelite scm:git:git@github.com:runelite/runelite - HEAD + runelite-parent-1.11.21 diff --git a/runelite-api/pom.xml b/runelite-api/pom.xml index 579beeec477..0e89b6a3c95 100644 --- a/runelite-api/pom.xml +++ b/runelite-api/pom.xml @@ -29,7 +29,7 @@ net.runelite runelite-parent - 1.11.21-SNAPSHOT + 1.11.21 runelite-api diff --git a/runelite-client/pom.xml b/runelite-client/pom.xml index d61715e1ca0..fa15445dd0f 100644 --- a/runelite-client/pom.xml +++ b/runelite-client/pom.xml @@ -29,7 +29,7 @@ net.runelite runelite-parent - 1.11.21-SNAPSHOT + 1.11.21 client diff --git a/runelite-jshell/pom.xml b/runelite-jshell/pom.xml index 26b27ae731b..443ce0edf1d 100644 --- a/runelite-jshell/pom.xml +++ b/runelite-jshell/pom.xml @@ -30,7 +30,7 @@ net.runelite runelite-parent - 1.11.21-SNAPSHOT + 1.11.21 jshell diff --git a/runelite-maven-plugin/pom.xml b/runelite-maven-plugin/pom.xml index f0abdd956fc..45e3a1ed1b9 100644 --- a/runelite-maven-plugin/pom.xml +++ b/runelite-maven-plugin/pom.xml @@ -29,7 +29,7 @@ net.runelite runelite-parent - 1.11.21-SNAPSHOT + 1.11.21 runelite-maven-plugin From 451ead4e405b6cd1906819b5b0d43dde27649626 Mon Sep 17 00:00:00 2001 From: RuneLite updater Date: Sun, 12 Oct 2025 14:03:18 +0000 Subject: [PATCH 16/25] Bump for 1.11.22-SNAPSHOT [ci skip] --- cache/pom.xml | 2 +- pom.xml | 4 ++-- runelite-api/pom.xml | 2 +- runelite-client/pom.xml | 2 +- runelite-jshell/pom.xml | 2 +- runelite-maven-plugin/pom.xml | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cache/pom.xml b/cache/pom.xml index 8ef299d3e99..34468da2cf0 100644 --- a/cache/pom.xml +++ b/cache/pom.xml @@ -29,7 +29,7 @@ net.runelite runelite-parent - 1.11.21 + 1.11.22-SNAPSHOT cache diff --git a/pom.xml b/pom.xml index 8919b964855..acb45e60638 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,7 @@ net.runelite runelite-parent - 1.11.21 + 1.11.22-SNAPSHOT pom RuneLite @@ -61,7 +61,7 @@ https://github.com/runelite/runelite scm:git:git://github.com/runelite/runelite scm:git:git@github.com:runelite/runelite - runelite-parent-1.11.21 + HEAD diff --git a/runelite-api/pom.xml b/runelite-api/pom.xml index 0e89b6a3c95..e4769ed1044 100644 --- a/runelite-api/pom.xml +++ b/runelite-api/pom.xml @@ -29,7 +29,7 @@ net.runelite runelite-parent - 1.11.21 + 1.11.22-SNAPSHOT runelite-api diff --git a/runelite-client/pom.xml b/runelite-client/pom.xml index fa15445dd0f..c57775bfa99 100644 --- a/runelite-client/pom.xml +++ b/runelite-client/pom.xml @@ -29,7 +29,7 @@ net.runelite runelite-parent - 1.11.21 + 1.11.22-SNAPSHOT client diff --git a/runelite-jshell/pom.xml b/runelite-jshell/pom.xml index 443ce0edf1d..e908fde1713 100644 --- a/runelite-jshell/pom.xml +++ b/runelite-jshell/pom.xml @@ -30,7 +30,7 @@ net.runelite runelite-parent - 1.11.21 + 1.11.22-SNAPSHOT jshell diff --git a/runelite-maven-plugin/pom.xml b/runelite-maven-plugin/pom.xml index 45e3a1ed1b9..756ae5a1ff4 100644 --- a/runelite-maven-plugin/pom.xml +++ b/runelite-maven-plugin/pom.xml @@ -29,7 +29,7 @@ net.runelite runelite-parent - 1.11.21 + 1.11.22-SNAPSHOT runelite-maven-plugin From 0cb4392ad00baa6d576547df83c1bc28f5ca2dd7 Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 12 Oct 2025 17:20:48 -0400 Subject: [PATCH 17/25] api: add WorldEntity.isHiddenForOverlap() --- .../src/main/java/net/runelite/api/WorldEntity.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/runelite-api/src/main/java/net/runelite/api/WorldEntity.java b/runelite-api/src/main/java/net/runelite/api/WorldEntity.java index 45684b3502c..c8c93ca1972 100644 --- a/runelite-api/src/main/java/net/runelite/api/WorldEntity.java +++ b/runelite-api/src/main/java/net/runelite/api/WorldEntity.java @@ -42,4 +42,10 @@ public interface WorldEntity extends CameraFocusableEntity * @return */ LocalPoint transformToMainWorld(LocalPoint point); + + /** + * Return true if this worldentity is overlapped + * @return + */ + boolean isHiddenForOverlap(); } From 3e808e85f8d4e42c506e25129ff66204844602fd Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 12 Oct 2025 17:28:06 -0400 Subject: [PATCH 18/25] api: make toLocalInstance not return points outside of the scene --- .../java/net/runelite/api/coords/WorldPoint.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/runelite-api/src/main/java/net/runelite/api/coords/WorldPoint.java b/runelite-api/src/main/java/net/runelite/api/coords/WorldPoint.java index d984597833a..cefa4b1be08 100644 --- a/runelite-api/src/main/java/net/runelite/api/coords/WorldPoint.java +++ b/runelite-api/src/main/java/net/runelite/api/coords/WorldPoint.java @@ -334,26 +334,35 @@ public static Collection toLocalInstance(WorldView wv, WorldPoint wo { return toLocalInstance(wv.getInstanceTemplateChunks(), wv.getBaseX(), wv.getBaseY(), worldPoint); } - else + else if (wv.contains(worldPoint)) { return Collections.singleton(worldPoint); } + else + { + return Collections.emptyList(); + } } /** * Get occurrences of a tile on the scene, accounting for instances. There may be * more than one if the same template chunk occurs more than once on the scene. */ + @Deprecated public static Collection toLocalInstance(Scene scene, WorldPoint worldPoint) { if (scene.isInstance()) { return toLocalInstance(scene.getInstanceTemplateChunks(), scene.getBaseX(), scene.getBaseY(), worldPoint); } - else + else if (isInScene(scene, worldPoint.getX(), worldPoint.getY())) { return Collections.singleton(worldPoint); } + else + { + return Collections.emptyList(); + } } private static Collection toLocalInstance(int[][][] instanceTemplateChunks, int baseX, int baseY, WorldPoint worldPoint) From d36c56af99e903bc9715e2eb09c3622d0923486c Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 12 Oct 2025 17:29:08 -0400 Subject: [PATCH 19/25] object indicators: hide overlays on hidden worldentities --- .../objectindicators/ObjectIndicatorsOverlay.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java index e26e888a0da..24be5178da5 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java @@ -34,12 +34,14 @@ import java.awt.Shape; import java.awt.Stroke; import javax.inject.Inject; +import net.runelite.api.Client; import net.runelite.api.DecorativeObject; import net.runelite.api.GameObject; import net.runelite.api.GroundObject; import net.runelite.api.ObjectComposition; import net.runelite.api.TileObject; import net.runelite.api.WallObject; +import net.runelite.api.WorldEntity; import net.runelite.api.WorldView; import static net.runelite.client.plugins.objectindicators.ColorTileObject.HF_CLICKBOX; import static net.runelite.client.plugins.objectindicators.ColorTileObject.HF_HULL; @@ -54,14 +56,16 @@ class ObjectIndicatorsOverlay extends Overlay { + private final Client client; private final ObjectIndicatorsConfig config; private final ObjectIndicatorsPlugin plugin; private final ModelOutlineRenderer modelOutlineRenderer; @Inject - private ObjectIndicatorsOverlay(ObjectIndicatorsConfig config, ObjectIndicatorsPlugin plugin, + private ObjectIndicatorsOverlay(Client client, ObjectIndicatorsConfig config, ObjectIndicatorsPlugin plugin, ModelOutlineRenderer modelOutlineRenderer) { + this.client = client; this.config = config; this.plugin = plugin; this.modelOutlineRenderer = modelOutlineRenderer; @@ -79,6 +83,7 @@ public Dimension render(Graphics2D graphics) return null; } + WorldView toplevel = client.getTopLevelWorldView(); Stroke stroke = new BasicStroke((float) config.borderWidth()); final var defaultFlags = (config.highlightHull() ? HF_HULL : 0) | @@ -95,6 +100,12 @@ public Dimension render(Graphics2D graphics) continue; } + WorldEntity we = toplevel.worldEntities().byIndex(wv.getId()); + if (we != null && we.isHiddenForOverlap()) + { + continue; + } + ObjectComposition composition = obj.getComposition(); if (composition.getImpostorIds() != null) { From 8d32c4fb12b24bec34ae4841d45334dad8895be7 Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 12 Oct 2025 17:29:54 -0400 Subject: [PATCH 20/25] object indicators: remove points on wv unload --- .../plugins/objectindicators/ObjectIndicatorsPlugin.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java index 765e4e306ef..c43efad96bb 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java @@ -32,6 +32,7 @@ import com.google.inject.Provides; import java.awt.Color; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -248,6 +249,11 @@ public void onWorldViewUnloaded(WorldViewUnloaded event) { var wv = event.getWorldView(); objects.removeIf(c -> c.getTileObject().getWorldView() == wv); + // TODO remove points when the last boat using it despawns? + if (wv.isTopLevel()) + { + Arrays.stream(wv.getMapRegions()).forEach(points::remove); + } } @Subscribe From 38a3ae705dac3f8468dbcbf104b2968b6df78c11 Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 12 Oct 2025 17:33:51 -0400 Subject: [PATCH 21/25] object indicators: fix (un)marking objects which occur multiple times --- .../ObjectIndicatorsPlugin.java | 112 +++++++++++------- 1 file changed, 70 insertions(+), 42 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java index c43efad96bb..4acd0053c5a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java @@ -458,7 +458,37 @@ private void markObject(MenuEntry entry) return; } - markObject(objectDefinition, name, object); + final WorldPoint worldPoint = WorldPoint.fromLocalInstance(client, object.getLocalLocation()); + final int regionId = worldPoint.getRegionID(); + final Color borderColor = config.markerColor(); + final Color fillColor = config.fillColor(); + final ObjectPoint point = new ObjectPoint( + object.getId(), + name, + regionId, + worldPoint.getRegionX(), + worldPoint.getRegionY(), + worldPoint.getPlane(), + borderColor, + fillColor, + // use the default config values + null, null, null, null); + + Set objectPoints = points.computeIfAbsent(regionId, k -> new HashSet<>()); + + if (objectPoints.removeIf(findObjectPredicate(objectDefinition, object, worldPoint))) + { + unmarkObjects(client.getTopLevelWorldView(), worldPoint, objectDefinition); + log.debug("Unmarking object: {}", point); + } + else + { + objectPoints.add(point); + markObjects(client.getTopLevelWorldView(), worldPoint, objectDefinition); + log.debug("Marking object: {}", point); + } + + savePoints(regionId, objectPoints); } private void updateObjectConfig(TileObject object, Consumer c) @@ -483,9 +513,13 @@ private void updateObjectConfig(TileObject object, Consumer c) savePoints(regionId, objectPoints); // rebuild the ColorTileObject from the new config - if (objects.removeIf(o -> o.getTileObject() == object)) + for (ColorTileObject o : new ArrayList<>(objects)) { - checkObjectPoints(object); + if (o.getTileObject().getId() == object.getId()) + { + objects.remove(o); + checkObjectPoints(o.getTileObject()); + } } } @@ -613,54 +647,48 @@ private boolean objectIdEquals(TileObject tileObject, int id) return false; } - /** mark or unmark an object - * - * @param objectComposition transformed composition of object based on vars - * @param name name of objectComposition - * @param object tile object, for multilocs object.getId() is the base id - */ - private void markObject(ObjectComposition objectComposition, String name, final TileObject object) + private void markObjects(WorldView wv, WorldPoint p, ObjectComposition objectConfig) { - final WorldPoint worldPoint = WorldPoint.fromLocalInstance(client, object.getLocalLocation()); - final int regionId = worldPoint.getRegionID(); - final Color borderColor = config.markerColor(); - final Color fillColor = config.fillColor(); - final ObjectPoint point = new ObjectPoint( - object.getId(), - name, - regionId, - worldPoint.getRegionX(), - worldPoint.getRegionY(), - worldPoint.getPlane(), - borderColor, - fillColor, - // use the default config values - null, null, null, null); - - Set objectPoints = points.computeIfAbsent(regionId, k -> new HashSet<>()); - - if (objects.removeIf(o -> o.getTileObject() == object)) + for (WorldPoint sp : WorldPoint.toLocalInstance(wv, p)) { - if (!objectPoints.removeIf(findObjectPredicate(objectComposition, object, worldPoint))) + int x = sp.getX() - wv.getBaseX(), y = sp.getY() - wv.getBaseY(); + TileObject object = findTileObject(wv, x, y, objectConfig.getId()); + if (object != null) { - log.warn("unable to find object point for unmarked object {}", object.getId()); + objects.add(new ColorTileObject(object, + client.getObjectDefinition(object.getId()), + objectConfig.getName(), + config.markerColor(), + config.fillColor(), + (byte) 0)); } + } - log.debug("Unmarking object: {}", point); + for (WorldView sub : wv.worldViews()) + { + markObjects(sub, p, objectConfig); } - else + } + + private void unmarkObjects(WorldView wv, WorldPoint p, ObjectComposition objectConfig) + { + for (WorldPoint sp : WorldPoint.toLocalInstance(wv, p)) { - objectPoints.add(point); - objects.add(new ColorTileObject(object, - client.getObjectDefinition(object.getId()), - name, - borderColor, - fillColor, - (byte) 0)); - log.debug("Marking object: {}", point); + int x = sp.getX() - wv.getBaseX(), y = sp.getY() - wv.getBaseY(); + TileObject object = findTileObject(wv, x, y, objectConfig.getId()); + if (object != null) + { + if (!objects.removeIf(o -> o.getTileObject() == object)) + { + log.warn("unable to find object point for unmarked object {}", object.getId()); + } + } } - savePoints(regionId, objectPoints); + for (WorldView sub : wv.worldViews()) + { + unmarkObjects(sub, p, objectConfig); + } } private static Predicate findObjectPredicate(ObjectComposition objectComposition, TileObject object, WorldPoint worldPoint) From 1afab68d59f94cc5e2216f927582c192c9e6e6d8 Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 13 Oct 2025 20:46:40 -0400 Subject: [PATCH 22/25] interface styles: set wv id on condensed menus --- .../client/plugins/interfacestyles/InterfaceStylesPlugin.java | 1 + 1 file changed, 1 insertion(+) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/InterfaceStylesPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/InterfaceStylesPlugin.java index 9b0a814aa37..2490bc94fad 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/InterfaceStylesPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/InterfaceStylesPlugin.java @@ -185,6 +185,7 @@ private void condensePlayerOptions() .setType(type) .setParam0(menuEntry.getParam0()) .setParam1(menuEntry.getParam1()) + .setWorldViewId(menuEntry.getWorldViewId()) .setDeprioritized(deprioritized); changed = true; } From 49d527295e428a5e24b71eeb398dd3e756d72500 Mon Sep 17 00:00:00 2001 From: chsami Date: Tue, 14 Oct 2025 12:37:11 +0200 Subject: [PATCH 23/25] refactor(Rs2ItemModel): simplify getIndexOfSubAction return values --- .../plugins/microbot/util/inventory/Rs2ItemModel.java | 6 +++--- .../client/plugins/microbot/util/walker/Rs2Walker.java | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) 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 1863095be4b..5a39cdca884 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 @@ -185,12 +185,12 @@ public String[][] getSubops() { * 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); + if (action == null) return null; String alc = action.toLowerCase(); String[][] subOps = getSubops(); if (subOps == null) { - return Map.entry(null, -1); + return null; } for (int i = 0; i < subOps.length; i++) { String[] subOpsActions = subOps[i]; @@ -203,7 +203,7 @@ public Map.Entry getIndexOfSubAction(String action) { } } - return Map.entry(null, -1); + return 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 87357eada4a..cdd8c7ebb88 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 @@ -1870,7 +1870,8 @@ private static boolean handleInventoryTeleports(Transport transport, int itemId) String itemAction = rs2Item.getAction(destination); // Check if item has destination as sub-menu action - if (itemAction == null && rs2Item.getIndexOfSubAction(destination).getKey() != null) { + Map.Entry sub = rs2Item.getIndexOfSubAction(destination); + if (itemAction == null && sub != null && sub.getKey() != null) { itemAction = destination; } From 2aaeed601c4043ba4bc1b5386bcd331085719c50 Mon Sep 17 00:00:00 2001 From: chsami Date: Tue, 14 Oct 2025 12:47:18 +0200 Subject: [PATCH 24/25] feat(actor): add method to retrieve camera focus --- .../runelite/client/plugins/microbot/util/ActorModel.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/ActorModel.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/ActorModel.java index 146e50243f6..ddfc8d29cdc 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/ActorModel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/ActorModel.java @@ -26,6 +26,11 @@ public WorldView getWorldView() return actor.getWorldView(); } + @Override + public LocalPoint getCameraFocus() { + return Microbot.getClientThread().runOnClientThreadOptional(actor::getCameraFocus).orElse(null); + } + @Override public int getCombatLevel() { From 2f391fb2117359d209ca64a224cd4b4f2c990aef Mon Sep 17 00:00:00 2001 From: chsami Date: Tue, 14 Oct 2025 12:52:27 +0200 Subject: [PATCH 25/25] refactor(perspective): update clickbox method to use top-level world view --- .../extendedruneliteobjects/ExtendedRuneliteObject.java | 1 + .../runeliteobjects/extendedruneliteobjects/ReplacedNpc.java | 2 +- .../runelite/client/plugins/microbot/util/misc/Rs2UiHelper.java | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/runeliteobjects/extendedruneliteobjects/ExtendedRuneliteObject.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/runeliteobjects/extendedruneliteobjects/ExtendedRuneliteObject.java index f233ef79559..6275094d6ad 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/runeliteobjects/extendedruneliteobjects/ExtendedRuneliteObject.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/runeliteobjects/extendedruneliteobjects/ExtendedRuneliteObject.java @@ -191,6 +191,7 @@ public Shape getClickbox() if (QuestPerspective.getInstanceLocalPointFromReal(client, worldPoint).isEmpty()) return null; return Perspective.getClickbox(client, + client.getTopLevelWorldView(), getRuneliteObject().getModel(), getRuneliteObject().getOrientation(), getRuneliteObject().getLocation().getX(), diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/runeliteobjects/extendedruneliteobjects/ReplacedNpc.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/runeliteobjects/extendedruneliteobjects/ReplacedNpc.java index 904e99baf2f..f966b4b6d9f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/runeliteobjects/extendedruneliteobjects/ReplacedNpc.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/runeliteobjects/extendedruneliteobjects/ReplacedNpc.java @@ -96,7 +96,7 @@ public void addMenuEntry(MenuEntryWrapper menuEntry) public Shape getClickbox() { if (npc == null) return null; - return Perspective.getClickbox(client, npc.getModel(), npc.getOrientation(), npc.getLocalLocation().getX(), npc.getLocalLocation().getY(), + return Perspective.getClickbox(client, client.getTopLevelWorldView(), npc.getModel(), npc.getOrientation(), npc.getLocalLocation().getX(), npc.getLocalLocation().getY(), Perspective.getTileHeight(client, npc.getLocalLocation(), getWorldPoint().getPlane())); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/misc/Rs2UiHelper.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/misc/Rs2UiHelper.java index 72c4789283c..0d77a6b9029 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/misc/Rs2UiHelper.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/misc/Rs2UiHelper.java @@ -76,7 +76,7 @@ public static Rectangle getActorClickbox(Actor actor) { } - Shape clickbox = Microbot.getClientThread().runOnClientThreadOptional(() -> Perspective.getClickbox(Microbot.getClient(), actor.getModel(), actor.getCurrentOrientation(), lp.getX(), lp.getY(), + Shape clickbox = Microbot.getClientThread().runOnClientThreadOptional(() -> Perspective.getClickbox(Microbot.getClient(), Microbot.getClient().getTopLevelWorldView(), actor.getModel(), actor.getCurrentOrientation(), lp.getX(), lp.getY(), Perspective.getTileHeight(Microbot.getClient(), lp, actor.getWorldLocation().getPlane()))) .orElse(null);