Skip to content

Comments

2.0.24#1564

Merged
chsami merged 30 commits intomainfrom
development
Oct 14, 2025
Merged

2.0.24#1564
chsami merged 30 commits intomainfrom
development

Conversation

@chsami
Copy link
Owner

@chsami chsami commented Oct 14, 2025

fix on rswalker with sub menu items
update with latest runelite

Krulvis and others added 28 commits October 5, 2025 16:31
Clicking tag on a tagged npc in instances wouldn't work because they aren't added to npcTags
chore(Rs2Equipment): Added equipment slot bounds when doInvoking
Allow interacting with Rs2ItemModel's submenu
# Conflicts:
#	cache/pom.xml
#	pom.xml
#	runelite-api/pom.xml
#	runelite-api/src/main/java/net/runelite/api/Perspective.java
#	runelite-client/pom.xml
#	runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItem.java
#	runelite-jshell/pom.xml
#	runelite-maven-plugin/pom.xml
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 14, 2025

Walkthrough

This PR introduces WorldView- and camera-focus-aware APIs to runelite-api (new interfaces, methods, and events), extends Perspective with WorldView projections, and updates Actor/WorldEntity contracts. Client and numerous plugins are refactored to operate per-WorldView (ground items/markers, object and NPC indicators, player indicators, interact highlight, tile indicators), and ModelOutlineRenderer/GameEventManager propagate WorldView context. Microbot changes add a settings search UI, dialogue option helpers, camera focus on ActorModel, equipment safe-bounds interaction, inventory submenu action resolution, Rs2ItemModel subops, and walker refactors. Adds agility shortcuts and Surge potion. Removes RuneTracker config/path. Parent and microbot versions are bumped. Tests stub new WorldView lookup.

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 34.97% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title Check ❓ Inconclusive The title “2.0.24” is just a version number and does not describe the substantive changes in the pull request, making it too generic to inform reviewers about the fixes to rswalker and the RuneLite update. Please use a concise, descriptive title such as “Fix rswalker submenu handling and bump to RuneLite 2.0.24” to clearly convey the main changes.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed The description mentions the rswalker submenu fix and the update to the latest RuneLite version, which aligns with the core changes in the diff and is therefore sufficiently related to the changeset.
✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (8)
runelite-client/pom.xml (1)

450-476: Blocker: malformed XML and duplicate in shade plugin

  • Extraneous characters "§y" on Line 472 break XML.
  • Two sibling blocks; schema expects a single containing all entries.

Merge the filters and remove the stray characters:

-                            <filters>
-                                <filter>
-                                    <artifact>*:*</artifact>
-                                    <excludes>
-                                        <exclude>META-INF/versions/**/module-info.class</exclude>
-                                    </excludes>
-                                </filter>
-                            </filters>
@@
-                            <filters>
+                            <filters>
                                 <filter>
                                     <artifact>*:*</artifact>
                                     <excludes>
+                                        <exclude>META-INF/versions/**/module-info.class</exclude>
                                         <exclude>META-INF/*.SF</exclude>
-                                        <exclude>META-INF/*.DSA</exclude>§y
+                                        <exclude>META-INF/*.DSA</exclude>
                                         <exclude>META-INF/*.RSA</exclude>
                                     </excludes>
                                 </filter>
                             </filters>
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/Rs2Walker.java (1)

1650-1669: Trapdoor open/closed ID mapping used in reverse

OPEN_TO_CLOSED_MAPPINGS is open→closed, but variables are assigned as closed=open and open=closed, inverting the logic and comparisons.

-        for (Map.Entry<Integer, Integer> entry : OPEN_TO_CLOSED_MAPPINGS.entrySet()) {
-            final int closedTrapdoorId = entry.getKey();
-            final int openTrapdoorId = entry.getValue();
-
-            if (transport.getObjectId() == openTrapdoorId) {
-                if (tileObject.getId() == closedTrapdoorId) {
+        for (Map.Entry<Integer, Integer> entry : OPEN_TO_CLOSED_MAPPINGS.entrySet()) {
+            final int openTrapdoorId = entry.getKey();
+            final int closedTrapdoorId = entry.getValue();
+
+            if (transport.getObjectId() == openTrapdoorId) {
+                if (tileObject.getId() == closedTrapdoorId) {
                     Rs2GameObject.interact(tileObject, "Open");
                     sleepUntil(() -> Rs2GameObject.exists(openTrapdoorId));
                     TileObject openTrapdoor = Rs2GameObject.getAll(o -> o.getId() == openTrapdoorId, tileObject.getWorldLocation(), 10).stream().findFirst().orElse(null);
                     if (openTrapdoor != null) {
                         Rs2GameObject.interact(openTrapdoor, transport.getAction());
                     }
-                } else if (tileObject.getId() == openTrapdoorId) {
+                } else if (tileObject.getId() == openTrapdoorId) {
                     Rs2GameObject.interact(tileObject, transport.getAction());
                 }
                 sleepUntil(() -> !Rs2Player.isAnimating());
                 sleepUntilTrue(() -> Rs2Player.getWorldLocation().distanceTo(transport.getDestination()) < OFFSET);
                 return true;
             }
         }
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/dialogues/Rs2Dialogue.java (1)

516-545: Doc/behavior mismatch: getDialogueText promises null, returns empty string

Javadoc says “returns null if no dialogue is active,” but the implementation returns "". Align one of them.

Apply one of:

-     * or the relevant widget is not visible, the method returns {@code null}.
+     * or the relevant widget is not visible, the method returns an empty string.
@@
-    public static String getDialogueText() {
-        if (!isInDialogue()) return "";
+    public static String getDialogueText() {
+        if (!isInDialogue()) return "";

Or change code to return null and update downstream checks accordingly.

runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/inventory/Rs2ItemModel.java (2)

500-519: Unnoted-ID logic is inverted for stackable unnoted items

If an item is already unnoted, you should return its own ID regardless of stackability. Current condition can return the noted variant for stackable unnoted items.

 private static int getUnNotedId(ItemComposition composition) {
   try {
     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) {
-        return itemId;
-    }
-    return linkedNoteId;
+    // If already unnoted, return original ID
+    if (!isNoted || linkedNoteId == -1) {
+      return itemId;
+    }
+    // If noted, return unnoted counterpart
+    return linkedNoteId;
   } catch (Exception e) {
     log.error("Error getting unnoted item ID: {}", e.getMessage());
     return -1; // fall back to original on error
   }
 }

472-491: Fallback for noted-ID helper should not be -1

For parity with Javadoc and callers, return original ID when composition is null or link is missing.

 private static int getNotedItemId(ItemComposition composition) {
   try {
     if (composition == null) {
-      return -1;
+      return -1; // keep sentinel here
     }
     int itemId = composition.getId();
     boolean isNoted = composition.getNote() == 799;
     int linkedNoteId = composition.getLinkedNoteId();
-    // if already stackable, return original ID
+    // if already noted & stackable, or no noted variant, return original ID
     if ((isNoted && composition.isStackable()) || linkedNoteId == -1) {
       return itemId;
     }
     return linkedNoteId;
   } catch (Exception e) {
-    return -1; // fall back to original on error
+    return -1; // let callers translate -1 to original id
   }
 }

And ensure the public static wrappers translate -1 to original ID (see previous comment).

runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java (1)

578-618: Tile lookup inconsistent with extended scene (likely miss on TOPLEVEL).

This uses Scene.getTiles()[level][x][y] with no TOPLEVEL offset. InteractHighlightPlugin switched to getExtendedTiles() and adds the extended-scene offset for TOPLEVEL menu coords. Without matching here, SHIFT‑examine lookups can fail on TOPLEVEL.

Align with extended scene:

 	private TileObject findTileObject(WorldView wv, int x, int y, int id)
 	{
-		int level = wv.getPlane();
-		Scene scene = wv.getScene();
-		Tile[][][] tiles = scene.getTiles();
-		final Tile tile = tiles[level][x][y];
+		int level = wv.getPlane();
+		Scene scene = wv.getScene();
+		final int offset = wv.isTopLevel()
+			? (net.runelite.api.Constants.EXTENDED_SCENE_SIZE - net.runelite.api.Constants.SCENE_SIZE) / 2
+			: 0;
+		Tile[][][] tiles = scene.getExtendedTiles();
+		final Tile tile = tiles[level][x + offset][y + offset];

This matches the extended-scene handling used in InteractHighlightPlugin.findTileObject(worldId, x, y, id). Based on Perspective/Constants usage shown in the PR.

runelite-api/src/main/java/net/runelite/api/Perspective.java (1)

1060-1064: Viewport Y offset bug (uses X offset for Y).

vpY1 should use getViewportYOffset(); current code culls triangles incorrectly.

Apply this one-line fix:

- int vpY1 = client.getViewportXOffset();
+ int vpY1 = client.getViewportYOffset();
runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcIndicatorsPlugin.java (1)

592-600: Unify instanced-region check
Replace if (!client.isInInstancedRegion()) at NpcIndicatorsPlugin.java:595 with:

WorldView wv = client.findWorldViewFromWorldPoint(npc.getWorldLocation());
if (wv != null && !wv.isInstance())
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 003a3d2 and 92c1506.

📒 Files selected for processing (50)
  • cache/pom.xml (1 hunks)
  • pom.xml (1 hunks)
  • runelite-api/pom.xml (1 hunks)
  • runelite-api/src/main/java/net/runelite/api/Actor.java (1 hunks)
  • runelite-api/src/main/java/net/runelite/api/CameraFocusableEntity.java (1 hunks)
  • runelite-api/src/main/java/net/runelite/api/Client.java (1 hunks)
  • runelite-api/src/main/java/net/runelite/api/GraphicsObject.java (1 hunks)
  • runelite-api/src/main/java/net/runelite/api/Perspective.java (14 hunks)
  • runelite-api/src/main/java/net/runelite/api/WorldEntity.java (1 hunks)
  • runelite-api/src/main/java/net/runelite/api/WorldView.java (3 hunks)
  • runelite-api/src/main/java/net/runelite/api/coords/LocalPoint.java (2 hunks)
  • runelite-api/src/main/java/net/runelite/api/coords/WorldArea.java (3 hunks)
  • runelite-api/src/main/java/net/runelite/api/coords/WorldPoint.java (1 hunks)
  • runelite-api/src/main/java/net/runelite/api/events/WorldEntityDespawned.java (1 hunks)
  • runelite-api/src/main/java/net/runelite/api/events/WorldEntitySpawned.java (1 hunks)
  • runelite-api/src/main/java/net/runelite/api/events/WorldViewLoaded.java (1 hunks)
  • runelite-api/src/main/java/net/runelite/api/events/WorldViewUnloaded.java (1 hunks)
  • runelite-client/pom.xml (2 hunks)
  • runelite-client/src/main/java/net/runelite/client/game/AgilityShortcut.java (6 hunks)
  • runelite-client/src/main/java/net/runelite/client/game/npcoverlay/NpcOverlayService.java (2 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItem.java (2 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java (6 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java (6 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/grounditems/Lootbeam.java (1 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerMinimapOverlay.java (2 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerOverlay.java (2 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerPlugin.java (12 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/interacthighlight/InteractHighlightOverlay.java (1 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/interacthighlight/InteractHighlightPlugin.java (3 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/InterfaceStylesPlugin.java (1 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentification.java (1 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/ui/MicrobotConfigPanel.java (6 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/dialogues/Rs2Dialogue.java (8 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/equipment/Rs2Equipment.java (3 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/inventory/Rs2Inventory.java (3 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/inventory/Rs2ItemModel.java (21 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/Rs2Walker.java (5 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcIndicatorsPlugin.java (10 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java (3 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java (12 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/playerindicators/PlayerIndicatorsService.java (2 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/tileindicators/TileIndicatorsOverlay.java (2 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/xpupdater/XpUpdaterConfig.java (0 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/xpupdater/XpUpdaterPlugin.java (3 hunks)
  • runelite-client/src/main/java/net/runelite/client/ui/overlay/outline/ModelOutlineRenderer.java (17 hunks)
  • runelite-client/src/main/java/net/runelite/client/util/GameEventManager.java (3 hunks)
  • runelite-client/src/test/java/net/runelite/client/plugins/loottracker/LootTrackerPluginTest.java (1 hunks)
  • runelite-client/src/test/java/net/runelite/client/plugins/timetracking/farming/CompostTrackerTest.java (1 hunks)
  • runelite-jshell/pom.xml (1 hunks)
  • runelite-maven-plugin/pom.xml (1 hunks)
💤 Files with no reviewable changes (1)
  • runelite-client/src/main/java/net/runelite/client/plugins/xpupdater/XpUpdaterConfig.java
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-25T15:51:39.272Z
Learnt from: runsonmypc
PR: chsami/Microbot#1417
File: runelite-client/src/main/java/net/runelite/client/plugins/microbot/agility/AgilityScript.java:13-13
Timestamp: 2025-08-25T15:51:39.272Z
Learning: Both net.runelite.api.ItemID and net.runelite.api.gameval.ItemID are valid import paths in the RuneLite codebase. The microbot plugins consistently use net.runelite.api.ItemID as their established pattern.

Applied to files:

  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/inventory/Rs2ItemModel.java
📚 Learning: 2025-08-26T13:22:16.518Z
Learnt from: Voxsylvae
PR: chsami/Microbot#1415
File: runelite-client/src/main/java/net/runelite/client/plugins/microbot/pluginscheduler/tasks/requirements/PrePostScheduleRequirements.java:1215-1221
Timestamp: 2025-08-26T13:22:16.518Z
Learning: The Rs2Equipment.get(slot) method in the Microbot framework returns Rs2ItemModel instances with slot indices that correspond to valid EquipmentInventorySlot values, as items are retrieved by iterating through the enum values themselves. When processing equipment items that were originally obtained via Rs2Equipment.get(slot), the slot indices are guaranteed to be valid for EquipmentInventorySlot.values()[index] access.

Applied to files:

  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/inventory/Rs2ItemModel.java
🧬 Code graph analysis (13)
runelite-api/src/main/java/net/runelite/api/events/WorldViewLoaded.java (3)
runelite-api/src/main/java/net/runelite/api/events/WorldEntityDespawned.java (1)
  • Value (33-37)
runelite-api/src/main/java/net/runelite/api/events/WorldEntitySpawned.java (1)
  • Value (36-40)
runelite-api/src/main/java/net/runelite/api/events/WorldViewUnloaded.java (1)
  • Value (33-37)
runelite-api/src/main/java/net/runelite/api/events/WorldViewUnloaded.java (3)
runelite-api/src/main/java/net/runelite/api/events/WorldEntityDespawned.java (1)
  • Value (33-37)
runelite-api/src/main/java/net/runelite/api/events/WorldEntitySpawned.java (1)
  • Value (36-40)
runelite-api/src/main/java/net/runelite/api/events/WorldViewLoaded.java (1)
  • Value (33-37)
runelite-api/src/main/java/net/runelite/api/events/WorldEntityDespawned.java (1)
runelite-api/src/main/java/net/runelite/api/events/WorldEntitySpawned.java (1)
  • Value (36-40)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/equipment/Rs2Equipment.java (1)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/menu/NewMenuEntry.java (1)
  • NewMenuEntry (11-317)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/inventory/Rs2Inventory.java (1)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/menu/NewMenuEntry.java (1)
  • NewMenuEntry (11-317)
runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerMinimapOverlay.java (1)
runelite-api/src/main/java/net/runelite/api/Perspective.java (1)
  • Perspective (62-1159)
runelite-client/src/main/java/net/runelite/client/game/AgilityShortcut.java (1)
runelite-api/src/main/java/net/runelite/api/gameval/DBTableID.java (1)
  • Quest (8-404)
runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcIndicatorsPlugin.java (1)
runelite-client/src/main/java/net/runelite/client/ui/overlay/outline/ModelOutlineRenderer.java (2)
  • AllArgsConstructor (60-65)
  • AllArgsConstructor (67-74)
runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerOverlay.java (1)
runelite-api/src/main/java/net/runelite/api/Perspective.java (1)
  • Perspective (62-1159)
runelite-api/src/main/java/net/runelite/api/events/WorldEntitySpawned.java (3)
runelite-api/src/main/java/net/runelite/api/events/WorldEntityDespawned.java (1)
  • Value (33-37)
runelite-api/src/main/java/net/runelite/api/events/WorldViewLoaded.java (1)
  • Value (33-37)
runelite-api/src/main/java/net/runelite/api/events/WorldViewUnloaded.java (1)
  • Value (33-37)
runelite-client/src/main/java/net/runelite/client/ui/overlay/outline/ModelOutlineRenderer.java (1)
runelite-api/src/main/java/net/runelite/api/Perspective.java (1)
  • Perspective (62-1159)
runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java (1)
runelite-api/src/main/java/net/runelite/api/Perspective.java (1)
  • Perspective (62-1159)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/Rs2Walker.java (4)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/inventory/Rs2Inventory.java (1)
  • Rs2Inventory (46-2313)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/WorldPointUtil.java (1)
  • WorldPointUtil (15-274)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/dialogues/Rs2Dialogue.java (1)
  • Rs2Dialogue (19-756)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/equipment/Rs2Equipment.java (1)
  • Rs2Equipment (25-392)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build
  • GitHub Check: Build
🔇 Additional comments (3)
runelite-client/src/main/java/net/runelite/client/plugins/grounditems/Lootbeam.java (1)

105-108: LGTM!

The getter correctly delegates to runeLiteObject.getWorldView() and is appropriately package-private for internal plugin use.

runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentification.java (1)

328-328: LGTM!

The new SURGE_POTION constant follows the established pattern for potion definitions, with the correct constructor signature (Type.POTION, display name, shorthand, and four dose-variant ItemIDs). The placement within the POTION section is appropriate, and the shorthand "Sur" is unique.

runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItem.java (1)

51-53: No action needed: GroundItem.itemLayer is always set via the builder
Builder usages consistently call .itemLayer(...) and there are no direct new GroundItem(...) calls, so the NPE risk is unfounded.

Likely an incorrect or invalid review comment.

Comment on lines +35 to +39
/**
* Get the {@link WorldEntity} this spotanim is on.
* @return
*/
WorldView getWorldView();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Correct the javadoc to reference WorldView instead of WorldEntity.

The javadoc incorrectly references {@link WorldEntity} when the method returns WorldView. Additionally, the @return tag is empty and should describe what is returned.

Apply this diff:

 	/**
-	 * Get the {@link WorldEntity} this spotanim is on.
-	 * @return
+	 * Get the {@link WorldView} this spotanim is on.
+	 * @return the world view
 	 */
 	WorldView getWorldView();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Get the {@link WorldEntity} this spotanim is on.
* @return
*/
WorldView getWorldView();
/**
* Get the {@link WorldView} this spotanim is on.
* @return the world view
*/
WorldView getWorldView();
🤖 Prompt for AI Agents
In runelite-api/src/main/java/net/runelite/api/GraphicsObject.java around lines
35 to 39, the Javadoc incorrectly references {@link WorldEntity} and has an
empty @return; update the Javadoc to reference {@link WorldView} and provide a
concise description for the @return (e.g., "the WorldView this spotanim is on"),
keeping formatting consistent with surrounding comments.

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));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Stacking offset key ignores WorldView (offsets can collide across views).

offsetMap keys by WorldPoint only. WorldPoint lacks WorldView identity, so items in different WorldViews at identical world coords share the same offset, causing inconsistent text stacking.

Recommend keying by (worldViewId, WorldPoint).

-	private final Map<WorldPoint, Integer> offsetMap = new HashMap<>();
+	private final Map<java.util.AbstractMap.SimpleEntry<Integer, WorldPoint>, Integer> offsetMap = new HashMap<>();
-				item.setOffset(offsetMap.compute(item.getItemLayer().getWorldLocation(), (k, v) -> v != null ? v + 1 : 0));
+				var key = new java.util.AbstractMap.SimpleEntry<>(wv.getId(), item.getItemLayer().getWorldLocation());
+				item.setOffset(offsetMap.compute(key, (k, v) -> v != null ? v + 1 : 0));
-				: offsetMap.compute(item.getItemLayer().getWorldLocation(), (k, v) -> v != null ? v + 1 : 0);
+				: offsetMap.compute(new java.util.AbstractMap.SimpleEntry<>(wv.getId(), item.getItemLayer().getWorldLocation()),
+					(k, v) -> v != null ? v + 1 : 0);

Also applies to: 286-286

🤖 Prompt for AI Agents
In
runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java
around lines 131 and 286, the offsetMap currently keys by WorldPoint only which
allows collisions across different WorldViews; change the map key to include the
WorldView identity (e.g., use a composite key of worldViewId and WorldPoint or a
small ValueObject/record like (int worldViewId, WorldPoint point)) and update
compute(...) calls to use that composite key when setting and reading offsets so
offsets are isolated per WorldView.

Comment on lines +182 to 187
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))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Distance filter mixes world views (can compute wrong distances).

You compare player LocalPoint (in their current view) to a TOPLEVEL groundPoint. If the player is in a sub-WorldView, distanceTo is meaningless and may hide/show items incorrectly. Prefer a world-point distance check gated by the item’s WorldView.

Apply this diff:

-			if (groundPoint == null
-				|| (groundPoint.getWorldView() == WorldView.TOPLEVEL && localLocation.distanceTo(groundPoint) > MAX_DISTANCE)
-				|| !plugin.shouldDisplayItem(ownershipFilterMode, item.getOwnership(), accountType))
+			if (groundPoint == null
+				|| !plugin.shouldDisplayItem(ownershipFilterMode, item.getOwnership(), accountType))
 			{
 				continue;
 			}
+			// Only apply distance gating in TOPLEVEL; compare in world tiles to avoid cross-WorldView mismatch
+			if (wv.isTopLevel())
+			{
+				WorldPoint playerWp = player.getWorldLocation();
+				if (playerWp == null || item.getItemLayer().getWorldLocation().distanceTo(playerWp) > 20) // ~20 tiles
+				{
+					continue;
+				}
+			}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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))
final WorldView wv = item.getItemLayer().getWorldView();
final LocalPoint groundPoint = LocalPoint.fromWorld(wv, item.getItemLayer().getWorldLocation());
if (groundPoint == null
|| !plugin.shouldDisplayItem(ownershipFilterMode, item.getOwnership(), accountType))
{
continue;
}
// Only apply distance gating in TOPLEVEL; compare in world tiles to avoid cross-WorldView mismatch
if (wv.isTopLevel())
{
WorldPoint playerWp = player.getWorldLocation();
if (playerWp == null || item.getItemLayer().getWorldLocation().distanceTo(playerWp) > 20) // ~20 tiles
{
continue;
}
}

Comment on lines +270 to +271
collectedGroundItems.values().removeIf(g -> g.getItemLayer().getWorldView() == wv);
lootbeams.values().removeIf(l -> l.getWorldView() == wv.getId());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Inconsistent WorldView comparison approach.

Line 270 compares WorldView objects (getItemLayer().getWorldView() == wv), while Line 271 compares what appears to be WorldView IDs (getWorldView() == wv.getId()). For consistency, both should use the same comparison approach—either compare objects throughout or compare IDs throughout.

🤖 Prompt for AI Agents
In
runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java
around lines 270-271, the code inconsistently compares WorldView by object on
line 270 and by ID on line 271; make the comparisons consistent by comparing
WorldView IDs for both removals (e.g., use getItemLayer().getWorldView().getId()
== wv.getId()), and add null-safety where needed (ensure getItemLayer() and
getWorldView() are non-null before calling getId()).

// auto-expand sections with matches
if (!anyVisible) continue;
// ensure expanded when filtering
if (!noFilter && !contents.isVisible()) contents.setVisible(true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Dead code: condition is always false at this point.

Line 223 checks !contents.isVisible() after line 218 sets contents.setVisible(anyVisible) where anyVisible is guaranteed to be true (line 221 would have continued otherwise). Since the parent was also made visible on line 217, contents.isVisible() will return true, making the condition !contents.isVisible() always false. The body never executes.

-            // auto-expand sections with matches
-            if (!noFilter && !contents.isVisible()) contents.setVisible(true);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!noFilter && !contents.isVisible()) contents.setVisible(true);
🤖 Prompt for AI Agents
In
runelite-client/src/main/java/net/runelite/client/plugins/microbot/ui/MicrobotConfigPanel.java
around line 223, the condition `if (!noFilter && !contents.isVisible())
contents.setVisible(true);` is dead because `contents.setVisible(anyVisible)`
just set it visible when anyVisible is true; remove this redundant if-statement
(or, if the intent was to set visibility before the parent change, move the
check earlier) so you either delete the line or adjust the ordering so the
visibility check is meaningful.

Comment on lines 443 to 451
public static int getNotedId(int itemId) {
ItemComposition composition = Microbot.getClientThread().runOnClientThreadOptional(() ->
Microbot.getClient().getItemDefinition(itemId)
).orElse(null);

return getNotedItemId(composition);
}

/**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Static noted/unnoted helpers violate their own contract

Javadoc says “otherwise the original item ID,” but both return -1 when composition is null or on error. Return the original ID as fallback.

 public static int getNotedId(int itemId) {
   ItemComposition composition = Microbot.getClientThread().runOnClientThreadOptional(() ->
           Microbot.getClient().getItemDefinition(itemId)
   ).orElse(null);
-
-  return getNotedItemId(composition);
+  int res = getNotedItemId(composition);
+  return res == -1 ? itemId : res;
 }
@@
 public static int getUnNotedId(int itemId) {
   ItemComposition composition = Microbot.getClientThread().runOnClientThreadOptional(() ->
           Microbot.getClient().getItemDefinition(itemId)
   ).orElse(null);
-
-  return getUnNotedId(composition);
+  int res = getUnNotedId(composition);
+  return res == -1 ? itemId : res;
 }

Also applies to: 457-464

Comment on lines +579 to +584
public String getAction(String partOfAction) {
return Arrays.stream(getInventoryActions())
.filter(Objects::nonNull)
.filter(x -> x.toLowerCase().contains(partOfAction))
.findFirst().orElse(null);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Case-insensitive search isn’t symmetric

Javadoc promises case-insensitive, but only action is lowercased; partOfAction isn’t. Lower both.

 public String getAction(String partOfAction) {
-    return Arrays.stream(getInventoryActions())
-            .filter(Objects::nonNull)
-            .filter(x -> x.toLowerCase().contains(partOfAction))
+    final String needle = partOfAction == null ? "" : partOfAction.toLowerCase();
+    return Arrays.stream(getInventoryActions())
+            .filter(Objects::nonNull)
+            .filter(x -> x.toLowerCase().contains(needle))
             .findFirst().orElse(null);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public String getAction(String partOfAction) {
return Arrays.stream(getInventoryActions())
.filter(Objects::nonNull)
.filter(x -> x.toLowerCase().contains(partOfAction))
.findFirst().orElse(null);
}
public String getAction(String partOfAction) {
final String needle = partOfAction == null ? "" : partOfAction.toLowerCase();
return Arrays.stream(getInventoryActions())
.filter(Objects::nonNull)
.filter(x -> x.toLowerCase().contains(needle))
.findFirst().orElse(null);
}
🤖 Prompt for AI Agents
In
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/inventory/Rs2ItemModel.java
around lines 579 to 584, the case-insensitive search lowercases only the
inventory action but not the input partOfAction causing asymmetric matching;
update the code to lowercase partOfAction (and guard against null) before the
stream filter so both sides are compared in the same case (e.g., compute String
needle = partOfAction == null ? "" : partOfAction.toLowerCase() and use
x.toLowerCase().contains(needle)).

Comment on lines +594 to 605
public String getActionFromList(List<String> 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);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

getActionFromList claims to ignore nulls but doesn’t

Null keywords in actions will NPE. Filter nulls up front.

 public String getActionFromList(List<String> actions) {
-    return Arrays.stream(getInventoryActions())
-            .filter(action -> action != null && actions.stream().anyMatch(keyword -> action.toLowerCase().contains(keyword.toLowerCase())))
+    final List<String> needles = actions == null ? List.of() :
+            actions.stream().filter(Objects::nonNull).collect(Collectors.toList());
+    return Arrays.stream(getInventoryActions())
+            .filter(action -> action != null && needles.stream().anyMatch(keyword -> action.toLowerCase().contains(keyword.toLowerCase())))
             .min(Comparator.comparingInt(action ->
-                    actions.stream()
-                            .filter(keyword -> action.toLowerCase().contains(keyword.toLowerCase()))
-                            .mapToInt(actions::indexOf)
+                    needles.stream()
+                            .filter(keyword -> action.toLowerCase().contains(keyword.toLowerCase()))
+                            .mapToInt(needles::indexOf)
                             .findFirst()
                             .orElse(Integer.MAX_VALUE)
             )).orElse(null);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public String getActionFromList(List<String> 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);
}
}
public String getActionFromList(List<String> actions) {
// Filter out null keywords (and handle a null actions list)
final List<String> needles = actions == null
? List.of()
: actions.stream()
.filter(Objects::nonNull)
.collect(Collectors.toList());
return Arrays.stream(getInventoryActions())
.filter(action ->
action != null &&
needles.stream()
.anyMatch(keyword -> action.toLowerCase().contains(keyword.toLowerCase()))
)
.min(Comparator.comparingInt(action ->
needles.stream()
.filter(keyword -> action.toLowerCase().contains(keyword.toLowerCase()))
.mapToInt(needles::indexOf)
.findFirst()
.orElse(Integer.MAX_VALUE)
))
.orElse(null);
}
🤖 Prompt for AI Agents
In
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/inventory/Rs2ItemModel.java
around lines 594 to 605, the stream logic assumes keywords in the passed-in
actions List are non-null and will NPE when they are; update the predicate and
comparator to filter out null keywords before calling toLowerCase by adding
keyword != null checks (e.g., change anyMatch to
actions.stream().filter(Objects::nonNull).anyMatch(...) and similarly filter
keywords in the comparator's inner stream) so null keywords are ignored and
won’t cause a NullPointerException.

Comment on lines +1835 to +1836
if (Rs2Pvp.isInWilderness() && (Rs2Pvp.getWildernessLevelFrom(Rs2Player.getWorldLocation()) > (transport.getMaxWildernessLevel() + 1)))
return false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Off-by-one in wilderness teleport restriction check

Using > (maxWildernessLevel + 1) permits teleports at exactly max+1. Should block when current level exceeds max, not max+1.

Please align similar checks elsewhere (e.g., spells) for consistency.

-        if (Rs2Pvp.isInWilderness() && (Rs2Pvp.getWildernessLevelFrom(Rs2Player.getWorldLocation()) > (transport.getMaxWildernessLevel() + 1)))
+        if (Rs2Pvp.isInWilderness() && Rs2Pvp.getWildernessLevelFrom(Rs2Player.getWorldLocation()) > transport.getMaxWildernessLevel())
             return false;

🏁 Script executed:

#!/bin/bash
rg -n --type java "getMaxWildernessLevel" -C3
rg -n --type java "getWildernessLevelFrom" -C3

Length of output: 10601


Fix off-by-one in wilderness teleport restriction
In both handleTeleportSpell (line 1812) and handleTeleportItem (line 1835), the check

if (isInWilderness() && getWildernessLevelFrom(...) > (transport.getMaxWildernessLevel() + 1))

allows teleports at exactly max+1. It should block any level above max:

- … > (transport.getMaxWildernessLevel() + 1)
+ … > transport.getMaxWildernessLevel()
🤖 Prompt for AI Agents
In
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/Rs2Walker.java
around lines 1835-1836, the wilderness teleport restriction uses a comparison
that allows teleports at exactly max+1; change the condition so it blocks any
wilderness level greater than the transport's max (i.e., replace the current >
(transport.getMaxWildernessLevel() + 1) check with a comparison against
transport.getMaxWildernessLevel() so teleports are disallowed when wilderness
level > transport.getMaxWildernessLevel()).

Comment on lines +189 to +196
private final List<DespawnedNpc> despawnedNpcsThisTick = new ArrayList<>();

@AllArgsConstructor
private static class DespawnedNpc
{
WorldPoint coord;
int index;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

WorldView-aware tagging/memorization still keyed by NPC index only (collisions across views).

Index-only keys (npcTags.contains(npc.getIndex()), memorizedNpcs.get(npc.getIndex())) can collide between worldviews. Result: wrong NPCs get (un)tagged or respawn timers mis-assigned when multiple worldviews are active. Despawn records also lack worldViewId.

Scope keys by worldviewId+index and carry worldviewId through DespawnedNpc.

Apply targeted diffs to changed hunks:

@@
- private final List<DespawnedNpc> despawnedNpcsThisTick = new ArrayList<>();
+ private final List<DespawnedNpc> despawnedNpcsThisTick = new ArrayList<>();
@@
- @AllArgsConstructor
- private static class DespawnedNpc
+ @AllArgsConstructor
+ private static class DespawnedNpc
 {
-   WorldPoint coord;
-   int index;
+   WorldPoint coord;
+   int worldViewId;
+   int index;
 }
@@
- final boolean idMatch = npcTags.contains(npc.getIndex());
+ final boolean idMatch = npcTags.contains(key(menuEntry.getWorldViewId(), npc.getIndex()));
@@
- if (npcTags.contains(npc.getIndex()))
+ if (npcTags.contains(key(wv.getId(), npc.getIndex())))
 {
   memorizeNpc(npc);
   highlightedNpcs.put(npc, highlightedNpc(npc));
   spawnedNpcsThisTick.add(npc);
   return;
 }
@@
- if (memorizedNpcs.containsKey(npc.getIndex()))
+ if (memorizedNpcs.containsKey(key(client.findWorldViewFromWorldPoint(npc.getWorldLocation()).getId(), npc.getIndex())))
 {
-   despawnedNpcsThisTick.add(new DespawnedNpc(npc.getWorldLocation(), npc.getIndex()));
+   WorldView wv = client.findWorldViewFromWorldPoint(npc.getWorldLocation());
+   despawnedNpcsThisTick.add(new DespawnedNpc(npc.getWorldLocation(), wv != null ? wv.getId() : -1, npc.getIndex()));
 }
@@
- for (DespawnedNpc npc : despawnedNpcsThisTick)
+ for (DespawnedNpc npc : despawnedNpcsThisTick)
 {
   ...
-  final MemorizedNpc mn = memorizedNpcs.get(npc.index);
+  final MemorizedNpc mn = memorizedNpcs.get(key(npc.worldViewId, npc.index));
   ...
 }

Add helpers and update key types (outside these hunks):

// Helper
private static long key(int worldViewId, int index) {
  return ((long) worldViewId << 32) | (index & 0xFFFFFFFFL);
}

// Change types:
private final Set<Long> npcTags = new HashSet<>();
private final Map<Long, MemorizedNpc> memorizedNpcs = new HashMap<>();
@Getter(AccessLevel.PACKAGE)
private final Map<Long, MemorizedNpc> deadNpcsToDisplay = new HashMap<>();

// Update all .add/.remove/.contains/.put/.get calls to use key(wvId, index)

Note: Use the actual API you have to resolve a WorldView for an NPC (e.g., via world point). If method names differ, adapt accordingly.

Also applies to: 312-314, 756-769, 610-611, 803-817


🏁 Script executed:

#!/bin/bash
set -e
FILE="runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcIndicatorsPlugin.java"
echo "=== DespawnedNpc class definition ==="
rg -n -C2 "class DespawnedNpc" "$FILE"
echo "=== npcTags.contains usages ==="
rg -n -C2 "npcTags\.contains" "$FILE"
echo "=== memorizedNpcs.get/.contains usages ==="
rg -n -C2 "memorizedNpcs\.(get|contains)" "$FILE"
echo "=== worldViewId references ==="
rg -n "worldViewId" "$FILE"

Length of output: 1514


The review comment is correct: keys lack worldview scoping, risking collisions across worldviews.

🤖 Prompt for AI Agents
In
runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcIndicatorsPlugin.java
around lines 189-196, the DespawnedNpc entries do not include worldview scoping
which can cause collisions across different worldviews; add a worldview
identifier field (e.g. int world or UUID worldviewId) to the DespawnedNpc class,
populate it when creating/despawning NPC entries using the current worldview
value from the client/context, and update any places that compare or look up
DespawnedNpc instances to include the worldview in equality/identity checks so
entries are only considered equal within the same worldview.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (8)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/equipment/Rs2Equipment.java (1)

314-341: Inconsistent usage of the target variable.

The target variable is initialized to rs2Item.getName() on line 314, then conditionally set to an empty string on line 339 for sub-menu actions. However, line 379 always passes rs2Item.getName() directly to the NewMenuEntry constructor instead of using the target variable.

Apply this diff to use the target variable consistently:

-        Microbot.doInvoke(new NewMenuEntry(param0, param1, menuAction.getId(), identifier, -1, rs2Item.getName()), rectangle);
+        Microbot.doInvoke(new NewMenuEntry(param0, param1, menuAction.getId(), identifier, -1, target), rectangle);

Also applies to: 379-379

runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/inventory/Rs2Inventory.java (1)

1921-1921: Three logic issues in invokeMenu: NPE risk, default action not resolved, and fragile sub-action main index lookup

  • Line 1921: Optional.orElseGet(null) will NPE. Use orElse(null).
  • Lines 1920-1936 (inside the action handling): When action is empty, no fallback is chosen. This contradicts interact(int id)’s javadoc (“first available action”) and yields identifier=-1 with option="". Resolve the first non-null inventory action and set identifier accordingly.
  • Lines 1938-1941: mainMenuIndex is derived with Arrays.asList(actions).indexOf(subActionMap.getKey()) without stripping color tags or case-insensitive matching. If not found, mainMenuIndex becomes -1, leading to an invalid identifier. Normalize actions before matching, and hard-fail if unresolved.

Apply this diff:

-            var itemWidget = Arrays.stream(inventoryWidgets).filter(x -> x != null && x.getIndex() == rs2Item.getSlot()).findFirst().orElseGet(null);
+            var itemWidget = Arrays.stream(inventoryWidgets)
+                    .filter(x -> x != null && x.getIndex() == rs2Item.getSlot())
+                    .findFirst()
+                    .orElse(null);
@@
-            String[] actions = itemWidget != null && itemWidget.getActions() != null ?
-                    itemWidget.getActions() :
-                    rs2Item.getInventoryActions();
+            String[] actions = (itemWidget != null && itemWidget.getActions() != null)
+                    ? itemWidget.getActions()
+                    : rs2Item.getInventoryActions();
+            String[] actionsStripped = stripColTags(actions);
@@
-            int simpleIndex = indexOfIgnoreCase(stripColTags(actions), action);
-            if (simpleIndex != -1) {
-                identifier = simpleIndex + 1;
-            } else {
+            if (action.isEmpty()) {
+                // Resolve first available action as default
+                for (int i = 0; i < actionsStripped.length; i++) {
+                    if (actionsStripped[i] != null && !actionsStripped[i].isEmpty()) {
+                        action = actions[i];
+                        identifier = i + 1;
+                        break;
+                    }
+                }
+                if (identifier == -1) {
+                    Microbot.log("No available actions for item=" + rs2Item.getName(), Level.ERROR);
+                    return;
+                }
+            } else {
+                int simpleIndex = indexOfIgnoreCase(actionsStripped, action);
+                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<String, Integer> 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());
+                // Resolve main menu index robustly (strip color tags, ignore case)
+                int mainMenuIndex = -1;
+                for (int i = 0; i < actionsStripped.length; i++) {
+                    if (actionsStripped[i] != null && actionsStripped[i].equalsIgnoreCase(subActionMap.getKey())) {
+                        mainMenuIndex = i;
+                        break;
+                    }
+                }
+                if (mainMenuIndex == -1) {
+                    Microbot.log("Could not resolve main menu index for sub-action '" + subActionMap.getKey() + "' on item=" + rs2Item.getName(), Level.ERROR);
+                    return;
+                }
                 identifier = NewMenuEntry.findIdentifier(subActionMap.getValue() + 1, mainMenuIndex + 1);
                 target = "";
+                }
             }

Also applies to: 1927-1941, 1878-1887

runelite-api/src/main/java/net/runelite/api/coords/WorldArea.java (1)

420-439: Fix inverted extraCondition logic in diagonal movement checks

Diagonal checks should mirror the x/y branches: block when there is a collision OR when extraCondition fails. Current code returns false only if both collision and extraCondition are true, letting movement pass when extraCondition fails. Align with earlier checks.

-			if (width == 1)
-			{
-				if ((collisionDataFlags[checkX][checkY - dy] & xFlags) != 0 &&
-					extraCondition.test(WorldPoint.fromScene(wv, checkX, startY, plane)))
-				{
-					return false;
-				}
-			}
+			if (width == 1)
+			{
+				if ((collisionDataFlags[checkX][checkY - dy] & xFlags) != 0 ||
+					!extraCondition.test(WorldPoint.fromScene(wv, checkX, startY, plane)))
+				{
+					return false;
+				}
+			}
 			if (height == 1)
 			{
-				if ((collisionDataFlags[checkX - dx][checkY] & yFlags) != 0 &&
-					extraCondition.test(WorldPoint.fromScene(wv, startX, checkY, plane)))
+				if ((collisionDataFlags[checkX - dx][checkY] & yFlags) != 0 ||
+					!extraCondition.test(WorldPoint.fromScene(wv, startX, checkY, plane)))
 				{
 					return false;
 				}
 			}
runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java (1)

194-201: WorldView collisions: keys are WorldPoint-only (items and lootbeams), causing cross-view conflation.

You’ve made handling WorldView-aware, but both:

  • collectedGroundItems is keyed by WorldPoint only, and
  • lootbeams map key is WorldPoint only.

When identical WorldPoints exist in multiple WorldViews, items/lootbeams collide: lookups return the wrong GroundItem, and only one lootbeam survives per WorldPoint. The unload filter cannot prevent conflicts while multiple views are loaded.

Refactor to include WorldView identity in the key.

Apply targeted changes within the touched areas:

- private static final Table<WorldPoint, Integer, GroundItem> collectedGroundItems = Tables.synchronizedTable(HashBasedTable.create());
+ private static final Table<WorldKey, Integer, GroundItem> collectedGroundItems = Tables.synchronizedTable(HashBasedTable.create());
@@ public void onItemSpawned(ItemSpawned itemSpawned)
- ItemLayer layer = tile.getItemLayer();
+ ItemLayer layer = tile.getItemLayer();
@@
- GroundItem groundItem = buildGroundItem(layer, item);
- GroundItem existing = collectedGroundItems.get(tile.getWorldLocation(), item.getId());
+ GroundItem groundItem = buildGroundItem(layer, item);
+ var key = new WorldKey(layer.getWorldView().getId(), tile.getWorldLocation());
+ GroundItem existing = collectedGroundItems.get(key, item.getId());
@@
-   collectedGroundItems.put(tile.getWorldLocation(), item.getId(), groundItem);
+   collectedGroundItems.put(key, item.getId(), groundItem);
@@ public void onMenuEntryAdded(MenuEntryAdded event)
- final WorldPoint worldPoint = WorldPoint.fromScene(wv, sceneX, sceneY, wv.getPlane());
- GroundItem groundItem = collectedGroundItems.get(worldPoint, itemId);
+ final WorldPoint worldPoint = WorldPoint.fromScene(wv, sceneX, sceneY, wv.getPlane());
+ var key = new WorldKey(wv.getId(), worldPoint);
+ GroundItem groundItem = collectedGroundItems.get(key, itemId);
- private final Map<WorldPoint, Lootbeam> lootbeams = new HashMap<>();
+ private final Map<WorldKey, Lootbeam> lootbeams = new HashMap<>();
@@ private void handleLootbeam(WorldPoint worldPoint)
- removeLootbeam(worldPoint);
+ // supply the view id along with the point when calling this method
+ removeLootbeam(key);
@@ private void addLootbeam(WorldPoint worldPoint, Color color)
- Lootbeam lootbeam = lootbeams.get(worldPoint);
+ Lootbeam lootbeam = lootbeams.get(key);
@@
-   lootbeam = new Lootbeam(client, clientThread, worldPoint, color, config.lootbeamStyle());
-   lootbeams.put(worldPoint, lootbeam);
+   lootbeam = new Lootbeam(client, clientThread, worldPoint, color, config.lootbeamStyle());
+   lootbeams.put(key, lootbeam);
@@
-   lootbeam.setColor(color);
+   lootbeam.setColor(color);
@@ private void removeLootbeam(WorldPoint worldPoint)
- Lootbeam lootbeam = lootbeams.remove(worldPoint);
+ Lootbeam lootbeam = lootbeams.remove(key);

Add a lightweight key type (outside the shown ranges):

// Helper key to disambiguate the same WorldPoint across multiple WorldViews
private static final class WorldKey {
  final int worldViewId;
  final WorldPoint point;
  WorldKey(int worldViewId, WorldPoint point) { this.worldViewId = worldViewId; this.point = point; }
  @Override public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof WorldKey)) return false;
    WorldKey k = (WorldKey) o;
    return worldViewId == k.worldViewId && point.equals(k.point);
  }
  @Override public int hashCode() { return 31 * worldViewId + point.hashCode(); }
}

Also update other call sites (despawn/quantity change/handleLootbeam callers) to construct and pass WorldKey consistently (use the item’s ItemLayer.getWorldView().getId() or the event/menu-derived WorldView).

Also applies to: 266-272, 514-519, 757-824, 843-866

runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcIndicatorsPlugin.java (3)

533-557: Tag/Un-tag toggles against “highlighted” state, not id-tag state (wrong behavior when highlight comes from name/wildcard).

Clicking “Tag” on an NPC already highlighted via name/wildcard can untag/no-op instead of adding an id tag. Toggle should be based on npcTags.contains(id), not highlightedNpcs.containsKey(npc).

Apply this fix:

 		if (entry.getOption().equals(TAG) || entry.getOption().equals(UNTAG))
 		{
-			final boolean exists = highlightedNpcs.containsKey(npc);
+			final boolean idTagged = npcTags.contains(id);
 
-			if (exists)
+			if (idTagged)
 			{
 				npcTags.remove(id);
 				if (!highlightMatchesNPCName(npc.getName()))
 				{
 					highlightedNpcs.remove(npc);
 					memorizedNpcs.remove(npc.getIndex());
 				}
 			}
 			else
 			{
-				if (!wv.isInstance())
-				{
-					memorizeNpc(npc);
-					npcTags.add(id);
-				}
+				if (!wv.isInstance())
+				{
+					memorizeNpc(npc);
+					npcTags.add(id);
+				}
 				highlightedNpcs.put(npc, highlightedNpc(npc));
 			}
 
 			npcOverlayService.rebuild();
 		}

803-829: Respawn validation ignores WorldView membership.

Despawn checks compare NPC coord vs player location without ensuring the coord is in the player’s WorldView, potentially starting timers for NPCs in other views.

Gate processing by player WV containment:

-			for (DespawnedNpc npc : despawnedNpcsThisTick)
+			for (DespawnedNpc npc : despawnedNpcsThisTick)
 			{
+				// Only consider despawns in the player's current world view
+				if (!client.getTopLevelWorldView().contains(npc.coord))
+				{
+					continue;
+				}
 				if (!teleportGraphicsObjectSpawnedThisTick.isEmpty())
 				{
 					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.coord))
 				{
 					final MemorizedNpc mn = memorizedNpcs.get(npc.index);

If contains(WorldPoint) isn’t available, add an equivalent WV check here.


592-600: Align instance checks between spawn and rebuild paths
Spawn handler in NpcIndicatorsPlugin (lines 595–600) uses client.isInInstancedRegion(), while rebuildWorldview filters via WorldView.isInstance(), leading to instanced child views being processed incorrectly. Replace

- if (!client.isInInstancedRegion())
+ if (!client.getTopLevelWorldView().isInstance())

or, if available, use each NPC’s own getWorldView().isInstance().

runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/Rs2Walker.java (1)

3344-3355: Bank withdrawal ignores current inventory count (over-withdraws)

amountToWithdraw uses amountNeeded, but the subsequent wait condition expects a delta (currentCount + amountToWithdraw). This over-withdraws and mismatches the wait.

Fix to withdraw the missing amount only:

-                    int currentCount = Rs2Inventory.count(itemId);
-                    int amountToWithdraw = Math.max(0, amountNeeded );
+                    int currentCount = Rs2Inventory.count(itemId);
+                    int amountToWithdraw = Math.max(0, amountNeeded - currentCount);
🧹 Nitpick comments (1)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/Rs2Walker.java (1)

1918-1939: Wearable teleports only handled when displayInfo contains “:”; single-destination wearables are ignored

Current logic returns false for wearables that don’t require a destination (e.g., single “Teleport”/“Operate” actions). This skips valid teleports.

Extend the method to handle single-destination/operate wearables:

-    private static boolean handleWearableTeleports(Transport transport, int itemId) {
+    private static boolean handleWearableTeleports(Transport transport, int itemId) {
         Rs2ItemModel rs2Item = Rs2Equipment.get(itemId);
         if (rs2Item == null) return false;
-        if (transport.getDisplayInfo().contains(":")) {
+        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 true;
-        }
-        return false;
+        } else {
+            // Handle single-destination/operate actions on worn items
+            String action = Optional.ofNullable(rs2Item.getAction("teleport"))
+                    .orElse(rs2Item.getActionFromList(Arrays.asList("operate", "invoke", "teleport")));
+            if (action != null && Rs2Equipment.invokeMenu(rs2Item, action)) {
+                // Confirm wilderness warning if applicable
+                boolean wilderness = PathfinderConfig.isInWilderness(WorldPointUtil.packWorldPoint(transport.getDestination()));
+                if (wilderness) {
+                    Rs2Dialogue.sleepUntilInDialogue();
+                    return Rs2Dialogue.clickOption("Yes", "Okay");
+                }
+                log.info("Traveling to {} - ({})", transport.getDisplayInfo(), transport.getDestination());
+                return true;
+            }
+        }
+        return false;
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 003a3d2 and 2aaeed6.

📒 Files selected for processing (51)
  • cache/pom.xml (1 hunks)
  • pom.xml (1 hunks)
  • runelite-api/pom.xml (1 hunks)
  • runelite-api/src/main/java/net/runelite/api/Actor.java (1 hunks)
  • runelite-api/src/main/java/net/runelite/api/CameraFocusableEntity.java (1 hunks)
  • runelite-api/src/main/java/net/runelite/api/Client.java (1 hunks)
  • runelite-api/src/main/java/net/runelite/api/GraphicsObject.java (1 hunks)
  • runelite-api/src/main/java/net/runelite/api/Perspective.java (14 hunks)
  • runelite-api/src/main/java/net/runelite/api/WorldEntity.java (1 hunks)
  • runelite-api/src/main/java/net/runelite/api/WorldView.java (3 hunks)
  • runelite-api/src/main/java/net/runelite/api/coords/LocalPoint.java (2 hunks)
  • runelite-api/src/main/java/net/runelite/api/coords/WorldArea.java (3 hunks)
  • runelite-api/src/main/java/net/runelite/api/coords/WorldPoint.java (1 hunks)
  • runelite-api/src/main/java/net/runelite/api/events/WorldEntityDespawned.java (1 hunks)
  • runelite-api/src/main/java/net/runelite/api/events/WorldEntitySpawned.java (1 hunks)
  • runelite-api/src/main/java/net/runelite/api/events/WorldViewLoaded.java (1 hunks)
  • runelite-api/src/main/java/net/runelite/api/events/WorldViewUnloaded.java (1 hunks)
  • runelite-client/pom.xml (2 hunks)
  • runelite-client/src/main/java/net/runelite/client/game/AgilityShortcut.java (6 hunks)
  • runelite-client/src/main/java/net/runelite/client/game/npcoverlay/NpcOverlayService.java (2 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItem.java (2 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java (6 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java (6 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/grounditems/Lootbeam.java (1 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerMinimapOverlay.java (2 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerOverlay.java (2 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerPlugin.java (12 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/interacthighlight/InteractHighlightOverlay.java (1 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/interacthighlight/InteractHighlightPlugin.java (3 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/InterfaceStylesPlugin.java (1 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentification.java (1 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/ui/MicrobotConfigPanel.java (6 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/ActorModel.java (1 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/dialogues/Rs2Dialogue.java (8 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/equipment/Rs2Equipment.java (3 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/inventory/Rs2Inventory.java (3 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/inventory/Rs2ItemModel.java (21 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/Rs2Walker.java (5 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcIndicatorsPlugin.java (10 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java (3 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java (12 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/playerindicators/PlayerIndicatorsService.java (2 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/tileindicators/TileIndicatorsOverlay.java (2 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/xpupdater/XpUpdaterConfig.java (0 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/xpupdater/XpUpdaterPlugin.java (3 hunks)
  • runelite-client/src/main/java/net/runelite/client/ui/overlay/outline/ModelOutlineRenderer.java (17 hunks)
  • runelite-client/src/main/java/net/runelite/client/util/GameEventManager.java (3 hunks)
  • runelite-client/src/test/java/net/runelite/client/plugins/loottracker/LootTrackerPluginTest.java (1 hunks)
  • runelite-client/src/test/java/net/runelite/client/plugins/timetracking/farming/CompostTrackerTest.java (1 hunks)
  • runelite-jshell/pom.xml (1 hunks)
  • runelite-maven-plugin/pom.xml (1 hunks)
💤 Files with no reviewable changes (1)
  • runelite-client/src/main/java/net/runelite/client/plugins/xpupdater/XpUpdaterConfig.java
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-25T15:51:39.272Z
Learnt from: runsonmypc
PR: chsami/Microbot#1417
File: runelite-client/src/main/java/net/runelite/client/plugins/microbot/agility/AgilityScript.java:13-13
Timestamp: 2025-08-25T15:51:39.272Z
Learning: Both net.runelite.api.ItemID and net.runelite.api.gameval.ItemID are valid import paths in the RuneLite codebase. The microbot plugins consistently use net.runelite.api.ItemID as their established pattern.

Applied to files:

  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/inventory/Rs2ItemModel.java
📚 Learning: 2025-08-26T13:22:16.518Z
Learnt from: Voxsylvae
PR: chsami/Microbot#1415
File: runelite-client/src/main/java/net/runelite/client/plugins/microbot/pluginscheduler/tasks/requirements/PrePostScheduleRequirements.java:1215-1221
Timestamp: 2025-08-26T13:22:16.518Z
Learning: The Rs2Equipment.get(slot) method in the Microbot framework returns Rs2ItemModel instances with slot indices that correspond to valid EquipmentInventorySlot values, as items are retrieved by iterating through the enum values themselves. When processing equipment items that were originally obtained via Rs2Equipment.get(slot), the slot indices are guaranteed to be valid for EquipmentInventorySlot.values()[index] access.

Applied to files:

  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/inventory/Rs2ItemModel.java
🧬 Code graph analysis (13)
runelite-api/src/main/java/net/runelite/api/events/WorldEntityDespawned.java (3)
runelite-api/src/main/java/net/runelite/api/events/WorldEntitySpawned.java (1)
  • Value (36-40)
runelite-api/src/main/java/net/runelite/api/events/WorldViewLoaded.java (1)
  • Value (33-37)
runelite-api/src/main/java/net/runelite/api/events/WorldViewUnloaded.java (1)
  • Value (33-37)
runelite-api/src/main/java/net/runelite/api/events/WorldViewLoaded.java (3)
runelite-api/src/main/java/net/runelite/api/events/WorldEntityDespawned.java (1)
  • Value (33-37)
runelite-api/src/main/java/net/runelite/api/events/WorldEntitySpawned.java (1)
  • Value (36-40)
runelite-api/src/main/java/net/runelite/api/events/WorldViewUnloaded.java (1)
  • Value (33-37)
runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerMinimapOverlay.java (1)
runelite-api/src/main/java/net/runelite/api/Perspective.java (1)
  • Perspective (62-1159)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/equipment/Rs2Equipment.java (1)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/menu/NewMenuEntry.java (1)
  • NewMenuEntry (11-317)
runelite-api/src/main/java/net/runelite/api/events/WorldViewUnloaded.java (3)
runelite-api/src/main/java/net/runelite/api/events/WorldEntityDespawned.java (1)
  • Value (33-37)
runelite-api/src/main/java/net/runelite/api/events/WorldEntitySpawned.java (1)
  • Value (36-40)
runelite-api/src/main/java/net/runelite/api/events/WorldViewLoaded.java (1)
  • Value (33-37)
runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcIndicatorsPlugin.java (1)
runelite-client/src/main/java/net/runelite/client/ui/overlay/outline/ModelOutlineRenderer.java (2)
  • AllArgsConstructor (60-65)
  • AllArgsConstructor (67-74)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/inventory/Rs2Inventory.java (1)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/menu/NewMenuEntry.java (1)
  • NewMenuEntry (11-317)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/Rs2Walker.java (6)
runelite-api/src/main/java/net/runelite/api/coords/WorldArea.java (1)
  • WorldArea (45-784)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/inventory/Rs2Inventory.java (1)
  • Rs2Inventory (46-2313)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/WorldPointUtil.java (1)
  • WorldPointUtil (15-274)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/dialogues/Rs2Dialogue.java (1)
  • Rs2Dialogue (19-756)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/equipment/Rs2Equipment.java (1)
  • Rs2Equipment (25-392)
runelite-api/src/main/java/net/runelite/api/gameval/InterfaceID.java (1)
  • Bookofscrolls (21155-21238)
runelite-api/src/main/java/net/runelite/api/events/WorldEntitySpawned.java (3)
runelite-api/src/main/java/net/runelite/api/events/WorldEntityDespawned.java (1)
  • Value (33-37)
runelite-api/src/main/java/net/runelite/api/events/WorldViewLoaded.java (1)
  • Value (33-37)
runelite-api/src/main/java/net/runelite/api/events/WorldViewUnloaded.java (1)
  • Value (33-37)
runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java (1)
runelite-api/src/main/java/net/runelite/api/Perspective.java (1)
  • Perspective (62-1159)
runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerOverlay.java (1)
runelite-api/src/main/java/net/runelite/api/Perspective.java (1)
  • Perspective (62-1159)
runelite-client/src/main/java/net/runelite/client/ui/overlay/outline/ModelOutlineRenderer.java (1)
runelite-api/src/main/java/net/runelite/api/Perspective.java (1)
  • Perspective (62-1159)
runelite-client/src/main/java/net/runelite/client/game/AgilityShortcut.java (2)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/gameobject/ObjectID.java (1)
  • ObjectID (10-33)
runelite-api/src/main/java/net/runelite/api/gameval/DBTableID.java (1)
  • Quest (8-404)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build
  • GitHub Check: Build
🔇 Additional comments (11)
runelite-client/src/main/java/net/runelite/client/plugins/interfacestyles/InterfaceStylesPlugin.java (1)

181-189: LGTM!

The worldViewId propagation is consistent with how other menu entry properties are being copied to the submenu entry. The logic correctly preserves the WorldViewId context when condensing player options into submenus.

runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/equipment/Rs2Equipment.java (2)

383-391: LGTM!

The getSafeBounds helper method correctly retrieves widget bounds and provides a sensible fallback to the full canvas rectangle when bounds are unavailable.


343-377: LGTM!

The safe bounds computation is consistently applied across all equipment slots, replacing the previous static full-canvas rectangle approach.

runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java (1)

86-107: LGTM!

The WorldView-aware filtering logic is well-structured with proper null guards and plane checks before rendering. The overlap detection via WorldEntity.isHiddenForOverlap() appropriately prevents rendering objects that should be hidden.

runelite-api/src/main/java/net/runelite/api/Client.java (1)

2419-2432: LGTM!

Clean API method declarations extending the Client interface to support camera focus retrieval and WorldView lookup from WorldPoint.

runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentification.java (1)

328-328: LGTM!

The SURGE_POTION entry follows the established pattern for potion identification, consistent with other potion types in the enum.

runelite-client/src/main/java/net/runelite/client/plugins/interacthighlight/InteractHighlightOverlay.java (1)

96-100: LGTM!

The worldId extraction and parameter addition correctly extends the tile object lookup to be WorldView-aware.

runelite-api/src/main/java/net/runelite/api/WorldEntity.java (1)

27-50: LGTM!

The interface extension to CameraFocusableEntity and the new location/transform methods are well-documented and align with the WorldView-aware architecture introduced in this PR.

runelite-api/src/main/java/net/runelite/api/events/WorldViewLoaded.java (1)

30-37: LGTM!

Standard Lombok event class following the established pattern used for other WorldView/WorldEntity lifecycle events.

runelite-api/src/main/java/net/runelite/api/events/WorldEntitySpawned.java (1)

30-40: LGTM!

Standard event class with helpful documentation clarifying that this fires before the WorldView is loaded, setting clear expectations for event ordering.

runelite-api/src/main/java/net/runelite/api/events/WorldViewUnloaded.java (1)

30-37: LGTM!

Standard event class completing the WorldView lifecycle event pair with WorldViewLoaded.

Comment on lines +68 to 72
public static LocalPoint fromWorld(Client client, WorldPoint point)
{
return fromWorld(client.getTopLevelWorldView(), world);
WorldView wv = client.findWorldViewFromWorldPoint(point);
return fromWorld(wv, point);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Handle null WorldView lookup before delegating

client.findWorldViewFromWorldPoint(point) returns null when the tile isn’t backed by a loaded WorldView (eg, outside the scene). Calling fromWorld(null, …) then dereferences wv and will throw. Add a null guard and return null instead.

 		WorldView wv = client.findWorldViewFromWorldPoint(point);
-		return fromWorld(wv, point);
+		if (wv == null)
+		{
+			return null;
+		}
+		return fromWorld(wv, point);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public static LocalPoint fromWorld(Client client, WorldPoint point)
{
return fromWorld(client.getTopLevelWorldView(), world);
WorldView wv = client.findWorldViewFromWorldPoint(point);
return fromWorld(wv, point);
}
public static LocalPoint fromWorld(Client client, WorldPoint point)
{
WorldView wv = client.findWorldViewFromWorldPoint(point);
if (wv == null)
{
return null;
}
return fromWorld(wv, point);
}
🤖 Prompt for AI Agents
In runelite-api/src/main/java/net/runelite/api/coords/LocalPoint.java around
lines 68 to 72, the method calls client.findWorldViewFromWorldPoint(point) and
passes the result into fromWorld(...) without checking for null; add a null
guard: if the WorldView lookup returns null, return null immediately instead of
delegating, otherwise call fromWorld(wv, point). Ensure the method signature and
callers handle a possible null return value.

private final JTextField searchField = new JTextField();
private final Map<JPanel, String> itemIndex = new HashMap<>(); // item panel -> lowercased name
private final Map<String, JPanel> sectionContentByKey = new HashMap<>(); // section key -> contents panel
private final Map<ConfigSectionDescriptor, JPanel> sectionPanelByDesc = new HashMap<>(); // whole section panel
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Unused map: sectionPanelByDesc is never read.

The map is populated during rebuild but never queried. The applyFilter method uses contents.getParent() directly at line 217 instead of looking up the section panel via this map.

If the map is not needed for future functionality, remove it:

-    private final Map<ConfigSectionDescriptor, JPanel> sectionPanelByDesc = new HashMap<>();
         mainPanel.removeAll();
         itemIndex.clear();
         sectionContentByKey.clear();
-        sectionPanelByDesc.clear();
             sectionWidgets.put(csd.getKey(), sectionContents);
-            sectionPanelByDesc.put(csd, section);
             sectionContentByKey.put(csd.getKey(), sectionContents);

Also applies to: 279-279, 340-340

🤖 Prompt for AI Agents
In
runelite-client/src/main/java/net/runelite/client/plugins/microbot/ui/MicrobotConfigPanel.java
around lines 114 (and also references at ~279 and ~340), the field
sectionPanelByDesc is populated but never read; remove the unused Map
declaration and any code that only exists to populate it (e.g., put calls during
rebuild) and update applyFilter to continue using contents.getParent() as it
already does; also remove any imports or references that become unused after
deleting the field to keep the file clean.

Comment on lines +217 to +223
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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Dead code: redundant visibility check.

Line 218 sets contents.setVisible(anyVisible). When anyVisible is true (the only case that reaches line 223 due to the continue at line 221), contents is already visible. The condition at line 223 !contents.isVisible() can never be true.

Apply this diff to remove the dead code:

             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);
+            if (!anyVisible) continue;
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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);
contents.getParent().setVisible(anyVisible); // whole section panel
contents.setVisible(anyVisible); // keep contents open when filtering
if (!anyVisible) continue;
🤖 Prompt for AI Agents
In
runelite-client/src/main/java/net/runelite/client/plugins/microbot/ui/MicrobotConfigPanel.java
around lines 217-223, remove the redundant visibility check by replacing the
final conditional so it no longer tests contents.isVisible(); specifically,
change the dead branch `if (!noFilter && !contents.isVisible())
contents.setVisible(true);` to `if (!noFilter) contents.setVisible(true);` (or
remove the conditional entirely if you prefer the call to be unconditional when
filtering) so the code no longer relies on a condition that can never be true.

}
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, Level.ERROR);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Inconsistent logging pattern.

Line 336 uses a format-string style with placeholders (%s), while other logging calls in the file (lines 306, 331) use string concatenation. This inconsistency may cause issues if Microbot.log does not support this format-string signature with the Level parameter at the end.

Apply this diff to match the logging pattern used elsewhere in the file:

-                    Microbot.log("Cannot find action=%s, in main actions=%s, mainMenuIndex=%s", subMenuEntry.getKey(), String.join(", ", actions), mainMenuIndex, Level.ERROR);
+                    Microbot.log("Cannot find action=" + subMenuEntry.getKey() + ", in main actions=" + String.join(", ", actions) + ", mainMenuIndex=" + mainMenuIndex, Level.ERROR);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Microbot.log("Cannot find action=%s, in main actions=%s, mainMenuIndex=%s", subMenuEntry.getKey(), String.join(", ", actions), mainMenuIndex, Level.ERROR);
Microbot.log("Cannot find action=" + subMenuEntry.getKey() + ", in main actions=" + String.join(", ", actions) + ", mainMenuIndex=" + mainMenuIndex, Level.ERROR);
🤖 Prompt for AI Agents
In
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/equipment/Rs2Equipment.java
around line 336, the Microbot.log call uses printf-style placeholders with Level
passed as the last argument which is inconsistent with surrounding calls that
use string concatenation and pass Level explicitly; replace the call with the
same concatenation pattern used on lines ~306 and ~331 (build the message by
concatenating subMenuEntry.getKey(), the joined actions string, and
mainMenuIndex) and pass Level.ERROR as the explicit level argument to match the
file's logging pattern.

Comment on lines 443 to 449
public static int getNotedId(int itemId) {
ItemComposition composition = Microbot.getClientThread().runOnClientThreadOptional(() ->
Microbot.getClient().getItemDefinition(itemId)
).orElse(null);

return getNotedItemId(composition);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Static getNotedId/getUnNotedId can return -1; contradicts method contract

Javadoc promises “otherwise the original item ID,” but when composition is null helpers return -1. This leaks -1 to callers.

 public static int getNotedId(int itemId) {
     ItemComposition composition = Microbot.getClientThread().runOnClientThreadOptional(() ->
             Microbot.getClient().getItemDefinition(itemId)
     ).orElse(null);
-
-    return getNotedItemId(composition);
+    int noted = getNotedItemId(composition);
+    return noted == -1 ? itemId : noted;
 }
@@
 public static int getUnNotedId(int itemId) {
     ItemComposition composition = Microbot.getClientThread().runOnClientThreadOptional(() ->
             Microbot.getClient().getItemDefinition(itemId)
     ).orElse(null);
-
-    return getUnNotedId(composition);
+    int unnoted = getUnNotedId(composition);
+    return unnoted == -1 ? itemId : unnoted;
 }

Also applies to: 457-463, 472-491, 500-519

Comment on lines +579 to +584
public String getAction(String partOfAction) {
return Arrays.stream(getInventoryActions())
.filter(Objects::nonNull)
.filter(x -> x.toLowerCase().contains(partOfAction))
.findFirst().orElse(null);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Case-insensitive search not actually case-insensitive

Javadoc says “ignoring case sensitivity,” but partOfAction isn’t lower-cased. Uppercase input can fail.

-    public String getAction(String partOfAction) {
-        return Arrays.stream(getInventoryActions())
-                .filter(Objects::nonNull)
-                .filter(x -> x.toLowerCase().contains(partOfAction))
-                .findFirst().orElse(null);
-    }
+    public String getAction(String partOfAction) {
+        if (partOfAction == null) return null;
+        final String needle = partOfAction.toLowerCase();
+        return Arrays.stream(getInventoryActions())
+                .filter(Objects::nonNull)
+                .filter(x -> x.toLowerCase().contains(needle))
+                .findFirst()
+                .orElse(null);
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public String getAction(String partOfAction) {
return Arrays.stream(getInventoryActions())
.filter(Objects::nonNull)
.filter(x -> x.toLowerCase().contains(partOfAction))
.findFirst().orElse(null);
}
public String getAction(String partOfAction) {
if (partOfAction == null) return null;
final String needle = partOfAction.toLowerCase();
return Arrays.stream(getInventoryActions())
.filter(Objects::nonNull)
.filter(x -> x.toLowerCase().contains(needle))
.findFirst()
.orElse(null);
}
🤖 Prompt for AI Agents
In
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/inventory/Rs2ItemModel.java
around lines 579 to 584, the method claims to perform a case-insensitive search
but only lower-cases the inventory action strings; lower-case the incoming
partOfAction (preferably using partOfAction.toLowerCase(Locale.ROOT)) before the
stream filter so both sides are compared in the same case, or alternatively use
String::toLowerCase on both operands inside the filter to ensure true
case-insensitive contains matching.

Comment on lines +1902 to 1913
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);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Inventory teleports with multiple destinations don’t select the destination after generic “rub/teleport/break”

When itemAction falls back to a generic keyword on multi-destination items, the code doesn’t pick the target option, unlike wearable teleports. This can leave the teleport unresolved and return false, even though the submenu is open.

Apply this diff to select the destination when hasMultipleDestination is true and handle wilderness confirmation afterward:

-        if (Rs2Inventory.interact(itemId, itemAction)) {
+        if (Rs2Inventory.interact(itemId, itemAction)) {
             log.debug("Traveling with genericAction={}, to {} - ({})", itemAction, transport.getDisplayInfo(), transport.getDestination());
 
-            if (itemAction.equalsIgnoreCase("open") && itemId == ItemID.BOOKOFSCROLLS_CHARGED) {
+            if (itemAction.equalsIgnoreCase("open") && itemId == ItemID.BOOKOFSCROLLS_CHARGED) {
                 return handleMasterScrollBook(destination);
-            } else if (wildernessTransport) {
+            }
+            // Multi-destination inventory items (e.g., games/skills necklace): select the destination
+            else if (hasMultipleDestination) {
+                Rs2Dialogue.sleepUntilSelectAnOption();
+                boolean selected = Rs2Dialogue.clickOption(destination, true);
+                if (!selected) return false;
+                if (wildernessTransport) {
+                    Rs2Dialogue.sleepUntilInDialogue();
+                    return Rs2Dialogue.clickOption("Yes", "Okay");
+                }
+                return true;
+            } else if (wildernessTransport) {
                 Rs2Dialogue.sleepUntilInDialogue();
                 return Rs2Dialogue.clickOption("Yes", "Okay");
             } else {
                 log.info("Unsure how to handle this itemTransport={} action={}", transport, itemAction);
             }
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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);
}
}
if (Rs2Inventory.interact(itemId, itemAction)) {
log.debug("Traveling with genericAction={}, to {} - ({})", itemAction, transport.getDisplayInfo(), transport.getDestination());
if (itemAction.equalsIgnoreCase("open") && itemId == ItemID.BOOKOFSCROLLS_CHARGED) {
return handleMasterScrollBook(destination);
}
// Multi-destination inventory items (e.g., games/skills necklace): select the destination
else if (hasMultipleDestination) {
Rs2Dialogue.sleepUntilSelectAnOption();
boolean selected = Rs2Dialogue.clickOption(destination, true);
if (!selected) {
return false;
}
if (wildernessTransport) {
Rs2Dialogue.sleepUntilInDialogue();
return Rs2Dialogue.clickOption("Yes", "Okay");
}
return true;
}
else if (wildernessTransport) {
Rs2Dialogue.sleepUntilInDialogue();
return Rs2Dialogue.clickOption("Yes", "Okay");
} else {
log.info("Unsure how to handle this itemTransport={} action={}", transport, itemAction);
}
}

Comment on lines +247 to 256
@Subscribe
public void onWorldViewUnloaded(WorldViewUnloaded event)
{
GameState gameState = gameStateChanged.getGameState();
if (gameState == GameState.LOADING)
var wv = event.getWorldView();
objects.removeIf(c -> c.getTileObject().getWorldView() == wv);
// TODO remove points when the last boat using it despawns?
if (wv.isTopLevel())
{
// Reload points with new map regions
objects.clear();
reloadPoints();
Arrays.stream(wv.getMapRegions()).forEach(points::remove);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Null-safety: guard getMapRegions() on unload.

onWorldViewUnloaded assumes wv.getMapRegions() is non-null; at unload time it can be null, causing an NPE.

- if (wv.isTopLevel())
- {
-   Arrays.stream(wv.getMapRegions()).forEach(points::remove);
- }
+ if (wv.isTopLevel())
+ {
+   int[] regions = wv.getMapRegions();
+   if (regions != null)
+   {
+     Arrays.stream(regions).forEach(points::remove);
+   }
+ }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Subscribe
public void onWorldViewUnloaded(WorldViewUnloaded event)
{
GameState gameState = gameStateChanged.getGameState();
if (gameState == GameState.LOADING)
var wv = event.getWorldView();
objects.removeIf(c -> c.getTileObject().getWorldView() == wv);
// TODO remove points when the last boat using it despawns?
if (wv.isTopLevel())
{
// Reload points with new map regions
objects.clear();
reloadPoints();
Arrays.stream(wv.getMapRegions()).forEach(points::remove);
}
@@ -247,10 +247,15 @@ public void onWorldViewUnloaded(WorldViewUnloaded event)
- if (wv.isTopLevel())
- {
- Arrays.stream(wv.getMapRegions()).forEach(points::remove);
if (wv.isTopLevel())
{
int[] regions = wv.getMapRegions();
if (regions != null)
{
Arrays.stream(regions).forEach(points::remove);
}
}
🤖 Prompt for AI Agents
In
runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java
around lines 247 to 256, onWorldViewUnloaded calls wv.getMapRegions() without
guarding for null which can NPE during unload; fix by checking if
wv.getMapRegions() != null (or using a null-safe guard) before streaming/forEach
to remove points so the removal is skipped when getMapRegions() returns null.

@chsami chsami merged commit 1abefa8 into main Oct 14, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants