Skip to content

Comments

quest-helper: sync to version 4.10.0+ (commit 55396262)#1442

Merged
chsami merged 51 commits intochsami:developmentfrom
Voxsylvae:quest-helper-update-20250829_063754
Aug 30, 2025
Merged

quest-helper: sync to version 4.10.0+ (commit 55396262)#1442
chsami merged 51 commits intochsami:developmentfrom
Voxsylvae:quest-helper-update-20250829_063754

Conversation

@Voxsylvae
Copy link
Contributor

@Voxsylvae Voxsylvae commented Aug 29, 2025

Updates:

  • Applied "Polish Enakhras Lament" improvements
  • Enhanced code organization and brazier puzzle fixes
  • Moved sync tracking to questhelper directory
  • Created comprehensive analysis scripts for future updates

Latest external commit: 5539626281a502ba8c44fe59b8b57bce32fe4f16

Summary by CodeRabbit

  • New Features

    • Added quest helpers: The Final Dawn, Shadows of Custodia, Scrambled, Vale Totems (miniquest), plus multiple new mini/quest helpers and an Egg puzzle solver.
    • Developer option: show debug overlay on launch and a "Reload quest" button that preserves scroll position.
    • Config toggles: show/hide world map point and control mini-map arrow/highlighting behavior.
  • Improvements

    • Many quest guides refactored for clearer, state-aware steps, puzzle wrapping, dynamic item tracking, and better item/NPC detection.
  • UI

    • Click-to-toggle individual step panels; removed global collapse. README updated with integration docs and help badge.

pajlada and others added 30 commits August 28, 2025 16:24
* fix: Allow npcs to highlight based on composition id

* fix: Use composite optionally for NpcRequirement and NpcStep
* feat: implement Vale Totems miniquest

* Double check the "can i build toten base" varbit

Thanks to cooper for report in Discord

* clarify that bows work too

* feat: Added new method for interfaceID + checkChildren for WidgetHighlight

* fix: Remove unused zone check from Vale Totems

* feat: Added tooltip for knife in Vale Totems

---------

Co-authored-by: Zoinkwiz <zoinkwiz@hotmail.co.uk>
Applied first 4 patches successfully:
- 0001: chore: prepare Varlamore Part 3 quests (#2188)
- 0002: fix: Allow npcs to highlight based on composition id (#2192)
- 0003: feat: implement Vale Totems miniquest (#2190)
- 0004: fix: Ensure rune pouch checks don't have invalid null items (#2196)

Status: 4/80 patches applied, compilation successful
Remaining: 76 patches available for future incremental updates
- Complete implementation of Shadows of Custodia quest guide
- Added .editorconfig for code formatting
- Includes all quest steps, requirements, and rewards
- Features combat with Strange creatures (level 93)
- Rewards: Slayer XP, access to Custodia Pass Slayer Dungeon

Applied patch 0005 manually due to conflicts.
* refactor(QuestOverviewPanel): remove unused collapseBtn

* unrelated: make header a JTextPane

this allows sections to span multiple lines which is cool for the Scrambled! quest imo but can be skipped if you want to
since it _does_ look a bit different. I don't know how to grapple Swing into doing proper vertical alignment

* feat: implement Scrambled! quest

* feat: implement Scrambled! quest

* clean up egg solver & some texts

* fix: Scrambled fix run up to puzzle

* fix: Updated ordering in egg puzzle

---------

Co-authored-by: Zoinkwiz <zoinkwiz@hotmail.co.uk>
* refactor(QuestOverviewPanel): remove unused collapseBtn

* unrelated: make header a JTextPane

this allows sections to span multiple lines which is cool for the Scrambled! quest imo but can be skipped if you want to
since it _does_ look a bit different. I don't know how to grapple Swing into doing proper vertical alignment

Slightly reduce bottom border of queststeppanel header

this makes it look more similar to when we used a JLabel
* feat: Added boilerplate requirements and items for The Final Dawn

* feat: Implemented first part of The Final Dawn

* feat: Implemented hideout section of The Final Dawn

* feat: Added changes up to Cam Torum battle

* feat: Added select item tile highlighting

* feat: Final Dawn up to and including Sun Puzzle

* feat: Added additional puzzle wrap step in QuestStep

* feat: Completed Final Dawn up to the moon puzzle

* feat: Completed Final Dawn

* fix: revert rl version

* fix: Add puzzlewrapper to final dawn password

* feat: Added more puzzlewrapper methods to QuestStep

* fix: Added puzzlewrapper for keystone in Final Dawn

* fix: Update streambound hint + sun altar direction in The Final Dawn

* fix: Correct spelling of Civitas illa in Final Dawn

Co-authored-by: Hamish Dickson <38864213+Hamish-Dickson@users.noreply.github.com>

* fix: Add clarification of where you're going in The Final Dawn

Co-authored-by: Hamish Dickson <38864213+Hamish-Dickson@users.noreply.github.com>

* fix: Added extra movement info for enforcer fight in The Final Dawn

Co-authored-by: Hamish Dickson <38864213+Hamish-Dickson@users.noreply.github.com>

* fix: Corrected ranul + ralos pillar positions in The Final Dawn

* feat: Added widget highlight for quetzal for The Final Dawn start

* feat: Added withModelRequirement method to WidgetHighlight

* fix: Added quetzal highlight to The Final Dawn

* fix: Copy in ItemRequirements correctly copies equip

* feat: Highlighted robes in inv to equip for The Final Dawn

* fix: Add inv slot empty hint to potted fan step in The Final Dawn

* fix: Correct direction of dwarf hole in The Final Dawn

* fix: Corrected sidebar for ancient prison step in The Final Dawn

* fix: Removed colour mention from circles in boss fight during The Final Dawn

* fix: Add missing sidebar step for rope climb in The Final Dawn

* fix: Improved guidance to Temple area in The Final Dawn

* fix: Teleports highlight correctly inventory when over a certain distance from goal

* fix: Avoid setting itemreq used in itemreqs to equipped as part of itemreqs setEquip

* fix: Add more prayer restore items for PRAYER_POTIONS in ItemCollections

* fix: Avoid duplicate text in noSolveStep for PuzzleWrapperStep

* fix: Improvements to The Final Dawn

New steps to fill in gaps, added civitas teleport, added puzzle version 1 solution for sun puzzle.

---------

Co-authored-by: Hamish Dickson <38864213+Hamish-Dickson@users.noreply.github.com>
Co-authored-by: Cooper Morris <coopermorris96@yahoo.com>
* chore: update readme to mention both donation options

* chore: mention our other plugin integrations

* aesthetic
hopefully not too merge-conflict-heavy with unmerged helpers, should be reasonable
Co-authored-by: Blake Ziolkowski <blake@xonaspace.com>
* fix: Allow for pure essence in ESSENCE_LOW ItemCollection

* Update ItemCollections.java
* nit: reformat

* fix: highlight correct step to start the quest

* nit: modernize

* nit: untitlecase "Ghostspeak amulet", and remove explicit quantity

* nit: replace hardcoded varbit with gameval varbit

* nit: update directions to Father Urhney

* nit: remove unneccessary dialog steps on open & search of coffin

you only talk to the NPC now.
Only the first dialog step of speaking to the ghost was used, but not doing too much cleanup at the same time

* nit: add some directions to the Wizard's Tower.

* nit: consistent usage of "the Lumbridge graveyhard"

* modernize: remove setupConditions (merge it with setupRequirements)

* modernize: use var, and use the `and()` helper function instead of raw new Conditions

* reorganize method order

* nit: update phrasing for the last part of the quest
* fix: don't show arrow on itemstep if showMiniMapArrow is disabled

* bikeshedder: add item step
…#2208)

* fix: The Great Brain Robbery correctly requires a disposable hammer

ItemID.IMCANDO_HAMMER was allowed for the quest, but a real hammer is needed to hand in the hammer to frakenstein.

* Hammer is consumed when talking to Dr. FrakenStein
Only tested in the "Scrambled!" quest, but unless Jagex spaghetti has taken over it should work everywhere where the
mainhand version works.
* refactor: modernize

 - Java 11, single-line declarations
 - reorder method order
   order I used:
   ordered logically:
   setupZones
   setupRequirements
   setupSteps
   loadSteps
   ordered based on order in the sidebar:
   getItemRequirements
   getItemRecommended
   getEnemiesToDefeat
   getQuestPointReward
   getExperienceRewards
   getItemRewards
   getUnlockRewards
   getPanels
 - use LogicHelper instead of raw Conditions

* fix: use gameval VarbitID for mill state

* java11

* feat: add WidgetHighlight builder for highlighting an item in a shop interface

* fix(gameval): make use of new createShopItemHighlight for highlighting items in the shop interface

* todo

* polish: reorder steps

* refactor: reorganize steps to match rough quest flow

* fix: coin quantity

* fix: highlight pot in general store interface

* chore: add myself to copyright

* polish: make use of dialog requirements to clean up the finishing step

it's not perfect since we can't track the state through relogs, but this makes the steps not jump around frantically
when turning in the ingredients

* check the "you've turned in everything!" message too

* remove unused import
Corrected Apothecary location from south east to south west
It can take more than 6 nails to repair the cart
* chore: remove unused item predicate check

* feat: add withCustomSpadeRequirement builder

this allows the helper to provide a custom spade to use, with a custom tooltip

* feat: add a "when to highlight" enum, allowing exact-tile highlight of item

* reformat

* chore: license
* refactor: modernize

* fix: "Yes." dialog option to start the quest
* sheep shearer refactor: modernize

* refactor: reformat

* Define lumbridge south staircase in QHObjectID

* feat: add function to create widget highlight multiskill-by-id

* polish(Sheep Shearer): Gameval, use new convenience-functions, add final step

Previously, if the user turned in 19 balls of wool, the helper would close.

---------

Co-authored-by: Zoinkwiz <zoinkwiz@hotmail.co.uk>
pajlada and others added 19 commits August 28, 2025 16:53
* https://oldschool.runescape.wiki/w/Costume_needle

* Update runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/elementalworkshopi/ElementalWorkshopI.java

Co-authored-by: pajlada <rasmus.karlsson+github@pajlada.com>

* Update runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/spiritsoftheelid/SpiritsOfTheElid.java

Co-authored-by: pajlada <rasmus.karlsson+github@pajlada.com>

* Update runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/spiritsoftheelid/SpiritsOfTheElid.java

Co-authored-by: pajlada <rasmus.karlsson+github@pajlada.com>

---------

Co-authored-by: pajlada <rasmus.karlsson+github@pajlada.com>
Co-authored-by: Zoinkwiz <zoinkwiz@hotmail.co.uk>
* refactor: modernize

also add combat gear to general requirement, and food to general recommended

* fix: start quest "Yes." dialog highlight

* montai directions

* refactor: and() instead of new Conditions(LogicType.AND)

* refactor: gameval

* nit: north-west/south-west instead of northwest/southwest

* nit: more flavour text & direction

* add first orb as a requirement to elkoy skip step

* add dialog highlight to elkoy skip step

* fix: dupe message in overlay

* nit: reword orb of protection step

* nit: update name of warlord

* elkoy skip when returning the last time

* fix: add item reward

* refactor: final polish pass

* chore: license
Before this change, nothing happens after you get the Jester outfit from the chest. Only after equipping the Jester
outfit does it show the next step, but this is not explained to the player.

The `hasJesterOutfit` `ItemRequirement` now does not require the jester outfit to be equipped, and so now the next steps
will show correctly even when the Jester outfit is in your inventory.
* fix: add antifire shield and combat gear to Barbarian Training item requirements

* fix: add Mithril dragon to Barbarian Training combat requirements
essentially just saves me from typing ::questhelperdebug enable every time i restart the client
* feat(PanelDetails): diary creator

* nit: rename from `diary` to `lockedPanel`
* refactor: modernize

* fix: sidebar steps

* polish rum smuggling steps

some sidebar steps missing,

* nit: key requirement & step order

* kill gardener step

* last reformat
* fix: hide mourner outfit req for ardy hard if SOTE completed

* fix: Hide new key if SOTE done for Ardy hard
Successfully applied 76/80 patches from external quest-helper repository:
- New quests: Shadows of Custodia, Scrambled!, The Final Dawn
- New miniquest: Vale Totems
- Quest polish: UI improvements, bug fixes, requirement updates
- Integration improvements: README updates, plugin compatibility

Skipped patches (4): build files, CI workflows, test infrastructure
not applicable to Microbot integration.

External sync point: 6329d81ee15a07a4b4fb53217bd4305e978c7a0e
✅ Compilation: SUCCESSFUL
✅ Integration: COMPLETE
- Enhanced code organization with better variable declarations
- Fixed Shadow Room brazier puzzle sequence and locations
- Added missing chisel icon for cutOffLimb step
- Improved requirement categorization structure

Synced from quest-helper commit 5539626281a502ba8c44fe59b8b57bce32fe4f16
- Moved .quest-helper-sync from project root to questhelper directory
- Updated sync file with structured format and comprehensive history
- Enhanced sync position tracking for automated scripts
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 29, 2025

Walkthrough

Adds four new quest helpers and many quest refactors; introduces developer-mode tooling (reload, debug overlay), config toggles, UI interaction changes, world-map/item highlight controls, API/requirement adjustments, widget/step utilities, a sync tracking file, and numerous tooltip/text tweaks across quest helpers.

Changes

Cohort / File(s) Summary
Documentation
README.md
Adds "Quest Helper Integration" (Not Enough Runes, Shortest Path) usage instructions and a "Help and discussion" section with a Buy Me a Coffee badge and images.
Sync tracking
.../questhelper/.quest-helper-sync
New persistent sync metadata file recording external quest-helper commit position, history, and integration status.
Config & plugin
.../QuestHelperConfig.java, .../QuestHelperPlugin.java
New config items: showWorldMapPoint (hints) and developmentSection with devShowOverlayOnLaunch; plugin adds/removes debug overlay on startup/shutdown/config changes; getSelectedQuest() annotated @nullable.
Quest API & helpers
.../questhelpers/QuestHelper.java, .../questinfo/QuestHelperQuest.java, .../questinfo/QuestVarbits.java
Adds uninitializeRequirements(); registers four new quest entries and varbits (THE_FINAL_DAWN, SHADOWS_OF_CUSTODIA, SCRAMBLED, VALE_TOTEMS).
Managers
.../managers/ItemAndLastUpdated.java, .../managers/QuestManager.java, .../managers/QuestContainerManager.java
getItems() now @nonnull returning empty array; selectedQuest marked @nullable; rune pouch iteration now uses per-item null check (outer null guard removed).
Panel UI
.../panel/QuestHelperPanel.java, .../panel/QuestOverviewPanel.java, .../panel/QuestStepPanel.java, .../panel/PanelDetails.java
Adds developer "reload quest" button (preserves scroll), restores saved scroll on add; removes overview collapse button/handler; makes step panels clickable to toggle collapse; adds PanelDetails.lockedPanel factory.
Collections & requirements
.../collections/ItemCollections.java, .../requirements/item/ItemRequirement.java, .../requirements/item/ItemRequirements.java, .../requirements/npc/NpcRequirement.java
Adds item entries (e.g., wearable saw offhand, prayer potion variants); canBeObtainedDuringQuest() becomes fluent; ItemRequirements no longer propagate equip to nested items on set/copy; NPC matching broadened to composition ID and fixed zone plane calc.
Widget & highlight utilities
.../steps/widget/WidgetHighlight.java, .../util/QHObjectID.java
WidgetHighlight: new name/shop/model helpers, public highlightWidget, child-traversal ctor, fluent model constraint; adds LUMBRIDGE_CASTLE_F0_SOUTH_STAIRCASE object ID.
Step & rendering core
.../steps/QuestStep.java, .../steps/PuzzleWrapperStep.java, .../steps/DetailedQuestStep.java, .../steps/ItemStep.java, .../steps/DigStep.java, .../steps/NpcStep.java
Puzzle wrapper API expanded (new overloads, DEFAULT_TEXT), isValidRenderRequirementInInventory promoted protected, world-map point and item-highlight gated by showWorldMapPoint, minimap arrow gating added, DigStep supports custom spade and highlight modes, NPC checks include composition ID.
New quests/features
.../valetotems/ValeTotems.java, .../scrambled/Scrambled.java, .../scrambled/EggSolver.java, .../shadowsofcustodia/ShadowsOfCustodia.java, .../thefinaldawn/TheFinalDawn.java
Adds full quest helper implementations: Vale Totems (miniquest), Scrambled (+ EggSolver), Shadows of Custodia, The Final Dawn — steps, zones, varbits, items, panels, rewards.
Major quest refactors
.../enakhraslament/EnakhrasLament.java, .../cooksassistant/CooksAssistant.java, .../misthalinmystery/MisthalinMystery.java, .../piratestreasure/..., .../sheepshearer/SheepShearer.java, .../treegnomevillage/TreeGnomeVillage.java, .../therestlessghost/TheRestlessGhost.java
Large restructures: split setupZones/setupRequirements/setupSteps, varbit/zone-driven flows, puzzle wrappers, dynamic inventory/varp tracking, new step types and panel reorganizations.
Quest tweaks (text/logic/tooltips)
Multiple files under .../helpers/...
Numerous small changes: tooltip/text updates, new/hide-conditioned requirements, additional recommended items/quantities, UI text and widget ID fixes, and minor step additions across many quest helpers.
Quest ordering lists
.../panel/questorders/*.java
Inserts new quests into ordering lists (IronmanOptimalQuestGuide, OptimalQuestGuide, ReleaseDate); note duplicated insertions in OptimalQuestGuide.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Plugin as QuestHelperPlugin
  participant Config as QuestHelperConfig
  participant Overlay as QuestOverlayManager

  Note over Plugin,Overlay: Developer overlay lifecycle
  User->>Plugin: start plugin
  Plugin->>Config: read developerMode + devShowOverlayOnLaunch
  alt developerMode && devShowOverlayOnLaunch
    Plugin->>Overlay: addDebugOverlay()
  end
  User->>Config: toggle devShowOverlayOnLaunch
  Plugin->>Plugin: onConfigChanged()
  alt developerMode && enabled
    Plugin->>Overlay: addDebugOverlay()
  else developerMode && disabled
    Plugin->>Overlay: removeDebugOverlay()
  end
  User->>Plugin: stop plugin
  Plugin->>Overlay: removeDebugOverlay()
Loading
sequenceDiagram
  autonumber
  actor Dev
  participant Panel as QuestHelperPanel
  participant Quest as QuestHelper
  participant Scroll as ScrollPane

  Note over Panel,Scroll: Developer "reload quest" preserves scroll position
  Dev->>Panel: click "reload quest"
  Panel->>Scroll: getVerticalScroll()
  Panel->>Panel: nextDesiredScrollValue = current
  Panel->>Quest: uninitializeRequirements()
  Panel->>Panel: reselect same quest
  Panel->>Scroll: scrollTo(nextDesiredScrollValue)
  Panel->>Panel: nextDesiredScrollValue = 0
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120+ minutes

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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: 42

Caution

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

⚠️ Outside diff range comments (25)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/thecurseofarrav/MetalDoorSolver.java (2)

338-346: Guard against null/empty widget text before regex match.

getText() can be null; calling matcher on it risks NPE. Add a small guard.

-		var textWidget = client.getWidget(InterfaceID.Messagebox.TEXT);
-		if (textWidget == null)
-		{
-			return;
-		}
-
-		var matcher = CODE_PATTERN.matcher(textWidget.getText());
+		var textWidget = client.getWidget(InterfaceID.Messagebox.TEXT);
+		if (textWidget == null)
+		{
+			return;
+		}
+		final String msg = textWidget.getText();
+		if (msg == null || msg.isEmpty())
+		{
+			return;
+		}
+		var matcher = CODE_PATTERN.matcher(msg);

423-423: Overlay for “Up” distance targets the Down button widget.

drawDistanceUp should anchor to PUZZLE_BTN_UP_CHILD_ID; both Up/Down overlays currently point at the down widget.

-		var arrow = client.getWidget(PUZZLE_GROUP_ID, PUZZLE_BTN_DOWN_CHILD_ID);
+		var arrow = client.getWidget(PUZZLE_GROUP_ID, PUZZLE_BTN_UP_CHILD_ID);
runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/managers/ItemAndLastUpdated.java (2)

45-60: Maintain the non-null invariant and improve visibility under concurrency.

update(..) can assign null to items, violating the new @Nonnull contract; also reads/writes may be cross-thread. Normalize to an empty array and make fields volatile.

Apply:

-    private int lastUpdated = -1;
-    private Item[] items = new Item[0];
+    private volatile int lastUpdated = -1;
+    private static final Item[] EMPTY = new Item[0];
+    private volatile @Nonnull Item[] items = EMPTY;
@@
-    public void update(int updateTick, Item[] items)
+    public void update(int updateTick, @Nullable Item[] items)
     {
         this.lastUpdated = updateTick;
-        this.items = items;
+        this.items = (items != null) ? items : EMPTY;
     }

Optional: if aliasing from external mutations is a concern, assign a copy: this.items = items != null ? java.util.Arrays.copyOf(items, items.length) : EMPTY;


69-83: Honor @nonnull at return: guard supplier nulls and log the cause.

If specialMethodToObtainItems.call() returns null, getItems() would violate @Nonnull. Guard and fall back to the internal array; include the exception in logs.

Apply:

     public @Nonnull Item[] getItems()
     {
         if (specialMethodToObtainItems != null)
         {
             try
             {
-                return specialMethodToObtainItems.call();
+                final Item[] computed = specialMethodToObtainItems.call();
+                if (computed != null)
+                {
+                    return computed;
+                }
+                log.debug("specialMethodToObtainItems returned null for {}", containerType);
             } catch (Exception e)
             {
-                log.warn("Failed to load container from method");
+                log.warn("Failed to load container from method for {}", containerType, e);
             }
         }
 
         return items;
     }

Optional: return a defensive copy if you need to prevent external mutation of internal state.

runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/clocktower/ClockTower.java (2)

140-147: Polish player-facing text (names/grammar).

Improves clarity and item names (“Ice gloves”, “Smiths’ gloves (i)”), and fixes minor grammar.

-		bucketOfWater = new ItemRequirement("Bucket of Water or a pair of ice gloves or smiths gloves(i)", ItemID.BUCKET_WATER);
+		bucketOfWater = new ItemRequirement("Bucket of water, Ice gloves, or Smiths' gloves (i)", ItemID.BUCKET_WATER);
@@
-		bucketOfWater.setTooltip("There is a bucket spawn next to the well east of the Clocktower. You can fill it on" +
-			" the well");
+		bucketOfWater.setTooltip("There is a bucket spawn next to the well east of the Clocktower. You can fill it at the well.");
@@
-		noteAboutWater = new ItemRequirement("There's a bucket and a well and just next to brother cedric for the black cog", -1, -1);
+		noteAboutWater = new ItemRequirement("There's a bucket spawn and a well next to Brother Cedric (east of the Clocktower) for cooling the black cog.", -1, -1);
@@
-		pickupBlackCog = new DetailedQuestStep(this, new WorldPoint(2613, 9639, 0), "Enter the north east door, and " +
-			"pick up the black cog with a bucket of water, alternatively you can equip ice gloves or smith gloves(i).", bucketOfWater, blackCog);
+		pickupBlackCog = new DetailedQuestStep(this, new WorldPoint(2613, 9639, 0), "Enter the north-east door and pick up the black cog. Use a bucket of water to cool it, or equip Ice gloves or Smiths' gloves (i).", bucketOfWater, blackCog);

Also applies to: 142-143, 145-145, 233-235


165-165: Fix secretPath4 X-coordinate typo
The end X value 9595 is clearly out of range—should be 2595.

-        secretPath4 = new Zone(new WorldPoint(2580, 9600, 0), new WorldPoint(9595, 9603, 0));
+        secretPath4 = new Zone(new WorldPoint(2580, 9600, 0), new WorldPoint(2595, 9603, 0));
runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/panel/QuestStepPanel.java (4)

89-107: Layout mismatch: BoxLayout + BorderLayout constraints causes misplacement of controls

You’re using BoxLayout but passing BorderLayout constraints to add(). This breaks right-alignment of viewControls and is fragile.

Prefer BorderLayout here and remove the redundant add(lockStep) that’s immediately reparented into viewControls.

- headerPanel.setLayout(new BoxLayout(headerPanel, BoxLayout.X_AXIS));
- headerPanel.setBorder(new EmptyBorder(7, 7, 3, 7));
- headerPanel.add(Box.createRigidArea(new Dimension(TITLE_PADDING, 0)));
- leftTitleContainer.add(headerLabel, BorderLayout.CENTER);
- headerPanel.add(leftTitleContainer, BorderLayout.WEST);
+ headerPanel.setLayout(new BorderLayout());
+ headerPanel.setBorder(new EmptyBorder(7, 7, 3, 7));
+ leftTitleContainer.add(headerLabel, BorderLayout.CENTER);
+ headerPanel.add(leftTitleContainer, BorderLayout.CENTER);
@@
- headerPanel.add(lockStep, BorderLayout.EAST);
@@
  viewControls.add(lockStep);
 
  headerPanel.add(viewControls, BorderLayout.EAST);

If you prefer BoxLayout, insert Box.createHorizontalGlue() before adding viewControls to push it right, and don’t pass BorderLayout constraints.


155-159: Incorrect alignment constants for BoxLayout

setAlignmentX/Y expect floats in [0.0, 1.0]. Using SwingConstants.* (ints) can cause erratic placement under BoxLayout.

-questStepLabel.setAlignmentX(SwingConstants.LEFT);
-questStepLabel.setAlignmentY(SwingConstants.TOP);
+questStepLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
+questStepLabel.setAlignmentY(Component.TOP_ALIGNMENT);

342-350: Collapse: avoid cumulative color drift from applyDimmer

Repeated brighten/darken mutations won’t round-trip exactly and don’t reach nested components (e.g., headerLabel inside leftTitleContainer).

Either remove the dimmer here and set explicit colors in highlight/removeHighlight, or make dimming idempotent and recursive.

Minimal fix (remove dimming here):

   if (!isCollapsed())
   {
     bodyPanel.setVisible(false);
-    applyDimmer(false, headerPanel);
   }

Optional: I can provide a small setHeaderState(expanded) helper that applies explicit foreground/background consistently.


351-359: Expand: same color-dimming issue as collapse

See prior comment.

   if (isCollapsed())
   {
     bodyPanel.setVisible(true);
-    applyDimmer(true, headerPanel);
   }
runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/achievementdiaries/ardougne/ArdougneHard.java (1)

401-404: Bug: locking step set on the wrong panel variable.

You create yanSteps but call setLockingStep on yan2Steps. This likely prevents the lock from working on the second panel.

Apply:

-    yan2Steps.setLockingStep(yanHouseTask);
+    yanSteps.setLockingStep(yanHouseTask);
runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/requirements/item/ItemRequirements.java (1)

394-397: Propagate equip flag to nested requirements in ItemRequirements.copy()
In ItemRequirements.copy() (ItemRequirements.java:338), newItem.setEquip(equip) no longer cascades to its child requirements—replace with explicit propagation (e.g., loop through getItemRequirements() and call setEquip(equip) on each or use equipped()), deprecate setEquip() on composites, and audit all .setEquip( call-sites.

runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/NpcStep.java (1)

295-309: onNpcChanged does not honor composition-ID matching.

You remove and re-add based only on newNpcId, bypassing the new composition-aware logic in npcPassesChecks(). Transformed NPCs that only match by composition ID will drop highlights.

Apply:

-  int newNpcId = npcChanged.getNpc().getId();
-  npcs.remove(npcChanged.getNpc());
-  if (allIds().contains(newNpcId))
-  {
-    if (npcs.size() == 0 || allowMultipleHighlights)
-    {
-      npcs.add(npcChanged.getNpc());
-    }
-  }
+  NPC changed = npcChanged.getNpc();
+  npcs.remove(changed);
+  if (npcPassesChecks(changed))
+  {
+    if (npcs.isEmpty() || allowMultipleHighlights)
+    {
+      npcs.add(changed);
+    }
+  }

Optional: mirror composition-aware checks in passesFocusChecks() when mustBeFocusedOnNpcs is used.

runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/miniquests/barbariantraining/BarbarianTraining.java (1)

599-599: Fix typo in user-facing text (“succesful” → “successful”).

Minor polish for the Pyremaking completion step text.

Proposed correction:

talkToOttoAfterPyre = new NpcStep(this, NpcID.BRUT_OTTO, new WorldPoint(2500, 3488, 0),
	"Return to Otto and tell him about the successful pyre-making.");
runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/collections/ItemCollections.java (1)

485-497: Remove non-prayer items from PRAYER_POTIONS.

HUNTER_MIX_MOONMOTH_* and BUTTERFLY_JAR_MOONMOTH aren’t prayer-restoring consumables; including them will cause incorrect matches.

 	PRAYER_POTIONS(ImmutableList.of(
 		ItemID._4DOSEPRAYERRESTORE,
 		ItemID._3DOSEPRAYERRESTORE,
 		ItemID._2DOSEPRAYERRESTORE,
 		ItemID._1DOSEPRAYERRESTORE,
-		ItemID._4DOSE2RESTORE,
-		ItemID._3DOSE2RESTORE,
-		ItemID._2DOSE2RESTORE,
-		ItemID._1DOSE2RESTORE,
-		ItemID.HUNTER_MIX_MOONMOTH_2DOSE,
-		ItemID.HUNTER_MIX_MOONMOTH_1DOSE,
-		ItemID.BUTTERFLY_JAR_MOONMOTH
+		// Super restores can substitute for prayer; keep them in SUPER_RESTORE_POTIONS instead.
 	)),
runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/QuestHelperPlugin.java (1)

491-495: Guard all getSelectedQuest() usages with null checks
The newly exposed @Nullable return must be handled at every call site:

  • Quest logic classes (e.g. RomeoAndJuliet.java:19, RuneMysteries.java:23, PiratesTreasure.java:49) invoke getSelectedQuest().… without checking for null—wrap these in if (getSelectedQuest() != null) or return early.
  • UI overlays and panels (e.g. QuestHelperTooltipOverlay.java:56, QuestHelperWorldOverlay.java:64, QuestStepPanel.java:74) assume a non-null quest—add null guards before dereferencing.
  • The helper method in QuestUpdateStrategy (private static QuestHelper getSelectedQuestHelper()) returns null but isn’t annotated; mark it @Nullable.
runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/PuzzleWrapperStep.java (1)

262-272: Substeps lost when solvePuzzles() is true

addSubSteps(...) only updates noSolvingStep, so substeps won’t appear when puzzle solving is enabled. Add to both branches.

@Override
public void addSubSteps(QuestStep... substep)
{
-  noSolvingStep.addSubSteps(Arrays.asList(substep));
+  noSolvingStep.addSubSteps(Arrays.asList(substep));
+  steps.get(null).addSubSteps(Arrays.asList(substep));
}
@Override
public void addSubSteps(Collection<QuestStep> substeps)
{
-  noSolvingStep.addSubSteps(substeps);
+  noSolvingStep.addSubSteps(substeps);
+  steps.get(null).addSubSteps(substeps);
}
runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/thefremennikisles/TheFremennikIsles.java (4)

389-390: Fix Jatizso zone Y-coordinate (likely typo 2825 → 3825)

Current zone spans Y=2825..3842, probably unintended and overly large. Use 3825 to match the islands’ area.

- jatizso2 = new Zone(new WorldPoint(2402, 2825, 0), new WorldPoint(2434, 3842, 0));
+ jatizso2 = new Zone(new WorldPoint(2402, 3825, 0), new WorldPoint(2434, 3842, 0));

399-401: Zone union duplicates neitiznot1 and omits neitiznot2

inNeitiznotOrTrollLands should include neitiznot2.

- inNeitiznotOrTrollLands = new ZoneRequirement(neitiznot1, neitiznot1, trollLands);
+ inNeitiznotOrTrollLands = new ZoneRequirement(neitiznot1, neitiznot2, trollLands);

438-442: “Nothing but your jester outfit” is not enforced by requirements

The step text claims only jester outfit should be worn, but requirements only assert the outfit is equipped (not exclusivity). Either add an explicit “no other equipment” requirement if available, or soften the wording.

Option A (preferred, if available): add a “no other equipment” requirement.

Option B (text tweak):

- "Talk to Slug Hemligssen wearing nothing but your Silly Jester outfit."
+ "Talk to Slug Hemligssen while wearing the Silly Jester outfit."

537-539: Require the yak-hide armour to be worn (matches step text and later cave gate)

The text says “with full yak-hide armour”, but the requirements are the inventory versions. Use the worn variants to prevent confusion and align with enterCave.

- getYakArmour = new NpcStep(this, NpcID.FRIS_R_BURGHER_CROWN, new WorldPoint(2335, 3800, 0), "Talk to Mawnis with full yak-hide armour.", yakTop, yakBottom);
+ getYakArmour = new NpcStep(this, NpcID.FRIS_R_BURGHER_CROWN, new WorldPoint(2335, 3800, 0), "Talk to Mawnis with full yak-hide armour equipped.", yakTopWorn, yakBottomWorn);
runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/widget/WidgetHighlight.java (2)

158-166: Guard against index out of bounds when targeting a child

childChildId access should verify bounds.

-		if (childChildId != -1 && widgets != null)
-		{
-			highlightChoices(widgets[childChildId], graphics, questHelper);
-			return;
-		}
+		if (childChildId != -1 && widgets != null && childChildId >= 0 && childChildId < widgets.length)
+		{
+			highlightChoices(widgets[childChildId], graphics, questHelper);
+			return;
+		}

170-174: Null-check staticChildren to avoid NPE

parentWidget.getStaticChildren() can be null.

-			for (Widget widget : staticWidgets)
-			{
-				highlightChoices(widget, graphics, questHelper);
-			}
+			if (staticWidgets != null)
+			{
+				for (Widget widget : staticWidgets)
+				{
+					highlightChoices(widget, graphics, questHelper);
+				}
+			}
runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/piratestreasure/RumSmugglingStep.java (1)

179-183: NPC ID typo: ZAMBO, not ZEMBO

The constant should be NpcID.ZAMBO. Using a non-existent ID will prevent the step from highlighting or compiling (depending on enum contents).

-		talkToZambo = new NpcStep(getQuestHelper(), NpcID.ZEMBO, new WorldPoint(2929, 3145, 0),
+		talkToZambo = new NpcStep(getQuestHelper(), NpcID.ZAMBO, new WorldPoint(2929, 3145, 0),
 			"Talk to Zembo in the Karamja Wines, Spirits and Beers bar. Buy one Karamjan rum.", new ItemRequirement("Coins", ItemCollections.COINS, 30));
runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/enakhraslament/EnakhrasLament.java (1)

292-296: Allow Earth staff for Crumble Undead and fix mislabeled item; align panel items

  • Item label says “Air staff” for an earth staff.
  • Step requires exactly two earth runes, ignoring an Earth staff.
  • Panel lists raw runes; prefer OR requirements for staff/runes.

Apply:

-		earthStaff = new ItemRequirement("Air staff", ItemCollections.EARTH_STAFF, 1, true);
+		earthStaff = new ItemRequirement("Earth staff", ItemCollections.EARTH_STAFF, 1, true);
-		castCrumbleUndead = new NpcStep(this, NpcID.ENAKH_BONEGUARD, new WorldPoint(3104, 9307, 2), "Cast crumble undead on the Boneguard.", earth2,
-			airRuneOrStaff, chaos, onNormals);
+		castCrumbleUndead = new NpcStep(this, NpcID.ENAKH_BONEGUARD, new WorldPoint(3104, 9307, 2), "Cast crumble undead on the Boneguard.", earthRuneOrStaff,
+			airRuneOrStaff, chaos, onNormals);
-		), List.of(pickaxe, chiselHighlighted, softClay, breadOrCake, tinderbox, log, oakLog, willowLog, mapleLog, candle, coal, fireSpellRunes,
-			airSpellRunes, earth2, air2, chaos
+		), List.of(pickaxe, chiselHighlighted, softClay, breadOrCake, tinderbox, log, oakLog, willowLog, mapleLog, candle, coal, fireSpellRunes,
+			airSpellRunes, earthRuneOrStaff, airRuneOrStaff, chaos
 		)));

Also applies to: 469-471, 662-667

🧹 Nitpick comments (51)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/creatureoffenkenstrain/CreatureOfFenkenstrain.java (9)

266-269: Pluralization fix: “amulet” → “amulets”

Minor copy nit to read naturally.

- combineAmulet = new ItemStep(this, "Combine the two amulet by using one on the other.",
+ combineAmulet = new ItemStep(this, "Combine the two amulets by using one on the other.",
   marbleAmulet.highlighted(),
   obsidianAmulet.highlighted());

283-286: Grammar: “go in the caves” → “go into the caves”

Smoother phrasing.

- "Use the Star Amulet on the memorial and push it to go in the caves.", starAmulet.highlighted());
+ "Use the Star Amulet on the memorial and push it to go into the caves.", starAmulet.highlighted());

331-331: Grammar: add conjunction

Reads better with “and”.

- makeLightningRod = new DetailedQuestStep(this, "Go to any furnace make a lightning rod.", silverBar, conductorMould);
+ makeLightningRod = new DetailedQuestStep(this, "Go to any furnace and make a lightning rod.", silverBar, conductorMould);

337-338: Capitalization consistency

“Lightning conductor” isn’t a proper noun here.

- "Repair the lightning Conductor.");
+ "Repair the lightning conductor.");

155-156: User-facing label: clarify minimum coins

Make the requirement explicit.

- coins = new ItemRequirement("Coins at least", ItemCollections.COINS, 100);
+ coins = new ItemRequirement("Coins (at least 100)", ItemCollections.COINS, 100);

263-265: Title consistency with dialog step

Aligns with “Gravedigging” used below.

- "Search the west bookcase for The Joy of Grave Digging.");
+ "Search the west bookcase for The Joy of Gravedigging.");

162-164: Copy tweak: missing “to”

Improves readability of combat gear note.

- armor = new ItemRequirement("Armour and weapons defeat a level 51 monster and run past level 72 monsters", -1, -1).isNotConsumed();
+ armor = new ItemRequirement("Armour and weapons to defeat a level 51 monster and run past level 72 monsters", -1, -1).isNotConsumed();

212-214: Typos in identifier: inExperiementCave → inExperimentCave

Correct spelling improves readability; local rename is safe.

- inExperiementCave = new ZoneRequirement(experimentCave);
+ inExperimentCave = new ZoneRequirement(experimentCave);
- gatherBodyParts.addStep(new Conditions(inExperiementCave, hasDecapitatedHeadWithBrain, hasCavernKey), leaveExperimentCave);
- gatherBodyParts.addStep(new Conditions(inExperiementCave, hasDecapitatedHeadWithBrain, keyNearby), pickupKey);
- gatherBodyParts.addStep(new Conditions(inExperiementCave, hasDecapitatedHeadWithBrain), killExperiment);
+ gatherBodyParts.addStep(new Conditions(inExperimentCave, hasDecapitatedHeadWithBrain, hasCavernKey), leaveExperimentCave);
+ gatherBodyParts.addStep(new Conditions(inExperimentCave, hasDecapitatedHeadWithBrain, keyNearby), pickupKey);
+ gatherBodyParts.addStep(new Conditions(inExperimentCave, hasDecapitatedHeadWithBrain), killExperiment);

Also applies to: 92-95


119-121: Typos in identifier: talkToFenkentrain → talkToFenkenstrain

Fixes misspelling of NPC name in variable.

- ConditionalStep talkToFenkentrain = new ConditionalStep(this, goBackToFirstFloor);
- talkToFenkentrain.addStep(inCastleFloor0, talkToFenkenstrainAfterFixingRod);
+ ConditionalStep talkToFenkenstrain = new ConditionalStep(this, goBackToFirstFloor);
+ talkToFenkenstrain.addStep(inCastleFloor0, talkToFenkenstrainAfterFixingRod);
- steps.put(4, talkToFenkentrain);
+ steps.put(4, talkToFenkenstrain);

Also applies to: 130-131

runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/thecurseofarrav/MetalDoorSolver.java (2)

304-307: Use cached widget text variables to avoid repeated reads.

Slightly safer and avoids inconsistent reads between calls.

-				if (Objects.equals(input1Text, "-") || Integer.parseInt(input1.getText()) != this.doorPassword[0])
+				if (Objects.equals(input1Text, "-") || Integer.parseInt(input1Text) != this.doorPassword[0])
 ...
-				else if (Objects.equals(input2Text, "-") || Integer.parseInt(input2.getText()) != this.doorPassword[1])
+				else if (Objects.equals(input2Text, "-") || Integer.parseInt(input2Text) != this.doorPassword[1])
 ...
-				else if (Objects.equals(input3Text, "-") || Integer.parseInt(input3.getText()) != this.doorPassword[2])
+				else if (Objects.equals(input3Text, "-") || Integer.parseInt(input3Text) != this.doorPassword[2])
 ...
-				else if (Objects.equals(input4Text, "-") || Integer.parseInt(input4.getText()) != this.doorPassword[3])
+				else if (Objects.equals(input4Text, "-") || Integer.parseInt(input4Text) != this.doorPassword[3])

Also applies to: 308-311, 312-315, 316-319


154-154: Fix typo in variable: shouldClickDownInteadOfUp → shouldClickDownInsteadOfUp.

Improves readability; local rename only.

-	private ManualRequirement shouldClickDownInteadOfUp;
+	private ManualRequirement shouldClickDownInsteadOfUp;
@@
-					this.shouldClickDownInteadOfUp.setShouldPass(this.distanceDown < this.distanceUp);
+					this.shouldClickDownInsteadOfUp.setShouldPass(this.distanceDown < this.distanceUp);
@@
-		clickUpOrDown.addStep(shouldClickDownInteadOfUp, clickDown);
+		clickUpOrDown.addStep(shouldClickDownInsteadOfUp, clickDown);

Also applies to: 332-333, 406-407

runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/managers/QuestContainerManager.java (1)

104-109: Optional: use Map.merge (+ filter invalid ids/qty) to simplify and reduce lookups

computeIfPresent + putIfAbsent duplicates work. merge expresses “add or insert” in one call and avoids two hash lookups. Also consider skipping invalid entries (id <= 0 or qty <= 0).

Example for the rune pouch block (this replaces the two map ops inside the loop):

-                    inventoryMap.computeIfPresent(runePouchItem.getId(), (currentVal, existingItem) -> new Item(currentVal, existingItem.getQuantity() + runePouchItem.getQuantity()));
-                    inventoryMap.putIfAbsent(runePouchItem.getId(), runePouchItem);
+                    if (runePouchItem.getId() > 0 && runePouchItem.getQuantity() > 0) {
+                        inventoryMap.merge(
+                            runePouchItem.getId(),
+                            runePouchItem,
+                            (existing, incoming) -> new Item(existing.getId(), existing.getQuantity() + incoming.getQuantity())
+                        );
+                    }

Apply the same pattern to the main inventory loop above for consistency.

runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/clocktower/ClockTower.java (1)

216-216: Match helper text to the new overview sync flow.

Minor copy tweak to direct players to the Overview pane explicitly.

-		syncStep = new DetailedQuestStep(this, "Open your Quest Journal to sync your current state.");
+		syncStep = new DetailedQuestStep(this, "Open the Quest Journal Overview to sync your current state.");
runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/panel/QuestStepPanel.java (5)

41-43: Use MouseAdapter instead of implementing MouseListener

Avoid no-op overrides and simplify by using a MouseAdapter listener instance.

Apply this diff to adjust imports:

-import java.awt.event.MouseEvent;
-import java.awt.event.MouseListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;

48-48: Drop class-level MouseListener implementation

Keep the panel focused on layout/state; attach a MouseAdapter instance instead.

-public class QuestStepPanel extends JPanel implements MouseListener
+public class QuestStepPanel extends JPanel

413-427: Toggle handler: consume event and keep left-click guard

Functionally fine; consider consuming to prevent downstream handlers from double-toggling if more listeners are added later.

   if (e.getButton() == MouseEvent.BUTTON1)
   {
     if (isCollapsed())
     {
       expand();
     }
     else
     {
       collapse();
     }
+    e.consume();
   }

429-447: Remove empty MouseListener overrides; attach a MouseAdapter instead

The no-ops add noise. Use a small MouseAdapter.

-@Override
-public void mousePressed(MouseEvent mouseEvent) {}
-
-@Override
-public void mouseReleased(MouseEvent mouseEvent) {}
-
-@Override
-public void mouseEntered(MouseEvent mouseEvent) {}
-
-@Override
-public void mouseExited(MouseEvent mouseEvent) {}

Outside this hunk, add the adapter and register it (replacing “this”):

// Field (optional) or local in constructor
private final MouseAdapter toggleListener = new MouseAdapter() {
  @Override public void mouseClicked(MouseEvent e) {
    if (e.getButton() == MouseEvent.BUTTON1) {
      if (isCollapsed()) expand(); else collapse();
      e.consume();
    }
  }
};

// In constructor:
headerLabel.addMouseListener(toggleListener);
headerPanel.addMouseListener(toggleListener);
leftTitleContainer.addMouseListener(toggleListener);

365-372: applyDimmer only affects direct children; headerLabel won’t be dimmed

Since headerLabel sits inside leftTitleContainer, iterate recursively or target specific components.

Example recursive variant:

private void applyDimmerRecursive(boolean brighten, Container c) {
  for (Component comp : c.getComponents()) {
    final Color color = comp.getForeground();
    if (color != null) comp.setForeground(brighten ? color.brighter() : color.darker());
    if (comp instanceof Container) applyDimmerRecursive(brighten, (Container) comp);
  }
}

Then call applyDimmerRecursive(..., headerPanel).

runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/romeoandjuliet/RomeoAndJuliet.java (1)

120-120: Keep directions consistent with cadava berry tooltip.

Berry tooltip (Line 90) says “south east of Varrock” while this step uses “south west Varrock.” Consider aligning wording to avoid confusing players.

runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/spiritsoftheelid/SpiritsOfTheElid.java (1)

136-139: Clarify tooltips; ignore rune rename

  • Update needle tooltip to "The Costume Needle cannot be used as a substitute."
  • Update thread tooltip to "The Costume Needle does not replace Thread; bring normal Thread."
  • ItemID.AIRRUNE and ItemID.LAWRUNE are still valid constants—no underscore variants exist in ItemID.
runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/DetailedQuestStep.java (1)

838-842: Inventory highlight rule — LGTM; consider identity vs. equivalence.

Using teleport.contains(requirement) relies on instance identity, which is fine if the same instances are passed around. If copies can exist, matching by equality or ID set may be safer.

-  return (teleport.contains(requirement) || requirement.shouldHighlightInInventory(client)) && requirement.getAllIds().contains(item.getItemId());
+  boolean isTeleportReq = teleport.stream()
+    .filter(ItemRequirement.class::isInstance)
+    .map(ItemRequirement.class::cast)
+    .anyMatch(r -> r == requirement || r.getAllIds().equals(requirement.getAllIds()));
+  return (isTeleportReq || requirement.shouldHighlightInInventory(client))
+    && requirement.getAllIds().contains(item.getItemId());
runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/.quest-helper-sync (1)

1-51: Sync metadata file — OK; add trailing newline and brief schema note.

  • Add a newline at EOF to placate linters.
  • Consider prefixing the commit line with a key (e.g., CURRENT=...) to make parsing more robust if comments shift.

Apply:

-# Sync file location: CORRECT (moved from project root)
+# Sync file location: CORRECT (moved from project root)
+
runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/panel/questorders/OptimalQuestGuide.java (1)

262-266: Unsorted entries added — track follow-up to place them.

Good placeholder. Add a short TODO with a wiki link or issue reference so these get moved to their correct locations once the OSRS Optimal Quest Guide reflects them.

runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/deviousminds/DeviousMinds.java (1)

115-116: Prefer .quantity(n) and consistent naming.

For consistency with the rest of the codebase (e.g., seed.quantity(9)), prefer chaining .quantity(...) and singular “teleport”.

-		fallyTele = new ItemRequirement("Falador Teleports", ItemID.POH_TABLET_FALADORTELEPORT, 2);
-		lumberTele = new ItemRequirement("Lumberyard Teleports", ItemID.TELEPORTSCROLL_LUMBERYARD, 3);
+		fallyTele = new ItemRequirement("Falador teleport", ItemID.POH_TABLET_FALADORTELEPORT).quantity(2);
+		lumberTele = new ItemRequirement("Lumberyard teleport", ItemID.TELEPORTSCROLL_LUMBERYARD).quantity(3);
runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/miniquests/barbariantraining/BarbarianTraining.java (1)

670-672: Scope required items to the pyre segment (optional).

Listing antifireShield/combatGear as global “required” can overwhelm users doing earlier tasks. Consider surfacing them only in the Pyremaking panel (already present) or mark them as recommended globally.

-			hammer, bronzeBar.quantity(2), logs.quantity(3),
-			attackPotion, roe,
-			antifireShield, combatGear);
+			hammer, bronzeBar.quantity(2), logs.quantity(3),
+			attackPotion, roe);

Alternative: keep here, but add a tooltip clarifying “Only needed for Pyremaking”.

runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/managers/QuestManager.java (1)

97-99: Propagate @nullable to the Lombok getter.

Field annotation doesn’t guarantee the generated getter is annotated. Add onMethod_ to prevent null-safety tool false positives.

-	@Getter
+	@Getter(onMethod_ = @Nullable)
 	@Inject
 	@Named("developerMode")
 	private boolean developerMode;
@@
-	@Getter
-	@Inject
-	@Nullable
-	private QuestHelper selectedQuest;
+	@Getter(onMethod_ = @Nullable)
+	@Inject
+	private QuestHelper selectedQuest;
runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/playerquests/bikeshedder/BikeShedder.java (1)

191-193: Leverage fluent API and add icon for consistency.

Use canBeObtainedDuringQuest() and add a coin icon, mirroring other steps.

-		anyCoins = new ItemRequirement("Coins", ItemCollections.COINS);
-		getCoins = new ItemStep(this, new WorldPoint(3224, 3215, 0), "Get coins", anyCoins);
+		anyCoins = new ItemRequirement("Coins", ItemCollections.COINS).canBeObtainedDuringQuest();
+		getCoins = new ItemStep(this, new WorldPoint(3224, 3215, 0), "Get coins", anyCoins);
+		getCoins.addIcon(ItemID.COINS);
runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/collections/ItemCollections.java (1)

1234-1237: Prefer accessor over direct field; clarify ordering.

Use getItems() to avoid tight coupling and keep top-to-bottom ordering comment accurate.

-	ESSENCE_LOW(new ImmutableList.Builder<Integer>()
-			.addAll(ESSENCE_HIGH.items).add(
-			ItemID.BLANKRUNE).build()
+	ESSENCE_LOW(new ImmutableList.Builder<Integer>()
+			.addAll(ESSENCE_HIGH.getItems())
+			.add(ItemID.BLANKRUNE)
+			.build()
 	),
runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/panel/PanelDetails.java (1)

101-109: Good convenience factory; consider a varargs overload for steps

lockedPanel reads well and matches setDisplayCondition semantics. As a minor QoL, offer an overload that accepts QuestStep... steps to mirror other constructors.

- public static PanelDetails lockedPanel(String header, Requirement displayCondition, QuestStep lockingStep, List<QuestStep> steps, Requirement... requirements)
+ public static PanelDetails lockedPanel(String header, Requirement displayCondition, QuestStep lockingStep, List<QuestStep> steps, Requirement... requirements)
 {
   var section = new PanelDetails(header, steps, requirements);
   section.setDisplayCondition(displayCondition);
   section.setLockingStep(lockingStep);
   return section;
 }
+
+public static PanelDetails lockedPanel(String header, Requirement displayCondition, QuestStep lockingStep, Requirement... requirements) {
+  var section = new PanelDetails(header);
+  section.setDisplayCondition(displayCondition);
+  section.setLockingStep(lockingStep);
+  section.addSteps(); // intentionally empty; caller can add later
+  if (requirements != null && requirements.length > 0) section.requirements = Arrays.asList(requirements);
+  return section;
+}
runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/panel/QuestHelperPanel.java (1)

556-559: Scroll restore timing is good

Using invokeLater avoids fighting layout; optional: only set when nextDesiredScrollValue > 0.

- SwingUtilities.invokeLater(() -> {
-   scrollableContainer.getVerticalScrollBar().setValue(nextDesiredScrollValue);
-   nextDesiredScrollValue = 0;
- });
+ SwingUtilities.invokeLater(() -> {
+   if (nextDesiredScrollValue > 0) {
+     scrollableContainer.getVerticalScrollBar().setValue(nextDesiredScrollValue);
+     nextDesiredScrollValue = 0;
+   }
+ });
runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/deserttreasureii/VardorvisSteps.java (1)

142-142: Use a representative icon and alternates for generic “food”

-1 display can render poorly. Suggest adding alternates and a display icon (e.g., shark), or reuse a food collection if available.

- food = new ItemRequirement("Bring high healing food to tank the infected.", -1);
+ food = new ItemRequirement("Bring high healing food to tank the infected.", -1);
+ food.addAlternates(ItemCollections.FOOD); // or a HIGH_HEALING_FOODS collection if present
+ food.setDisplayItemId(ItemID.SHARK);
runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/PuzzleWrapperStep.java (1)

48-48: Make DEFAULT_TEXT a constant

Declare as final.

- public static String DEFAULT_TEXT = "If you want help with this, enable 'Show Puzzle Solutions' in the Quest Helper configuration settings.";
+ public static final String DEFAULT_TEXT = "If you want help with this, enable 'Show Puzzle Solutions' in the Quest Helper configuration settings.";
runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/witchspotion/WitchsPotion.java (1)

72-78: Dialog sequence: keep both lines or prefer the updated “Yes.”?

You now include both “I am in search of a quest.” and “Yes.”. If game dialog changed, consider removing the obsolete line; otherwise keeping both is fine.

- talkToWitch.addDialogStep("I am in search of a quest.");
- talkToWitch.addDialogStep("Yes.");
+ talkToWitch.addDialogStep("Yes.");

Please confirm the current in-game dialog to avoid misleading highlights.

runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/scrambled/Scrambled.java (1)

387-389: Empty instruction text for enterDragonCave

The step has an empty string, so the sidebar shows no guidance.

-		var enterDragonCave = new ObjectStep(this, ObjectID.TLATI_DRAGON_NEST_CAVE_ENTRY, new WorldPoint(1288, 3133, 0), "", List.of(), List.of(antifireShield));
+		var enterDragonCave = new ObjectStep(this, ObjectID.TLATI_DRAGON_NEST_CAVE_ENTRY, new WorldPoint(1288, 3133, 0), "Enter the dragon cave south‑east of the temple.", List.of(), List.of(antifireShield));
runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/DigStep.java (1)

114-117: Use the configured spade’s display item for the overlay

Always rendering SPADE ignores custom display icons.

-	private BufferedImage getSpadeImage()
-	{
-		return itemManager.getImage(ItemID.SPADE);
-	}
+	private BufferedImage getSpadeImage()
+	{
+		int iconId = spade.getDisplayItemId() != null ? spade.getDisplayItemId() : spade.getId();
+		if (iconId <= 0) iconId = ItemID.SPADE;
+		return itemManager.getImage(iconId);
+	}
runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/treegnomevillage/TreeGnomeVillage.java (2)

248-253: Variable naming typo (“Balista” → “Ballista”)

Minor consistency/naming nit; rename to avoid confusion with step texts/constants.

-		fireBalistaConditional = new ConditionalStep(this, fireBallista, "Fire the ballista at the tower.");
-		fireBalistaConditional.addStep(shouldFireBallista1, fireBallista1);
-		fireBalistaConditional.addStep(shouldFireBallista2, fireBallista2);
-		fireBalistaConditional.addStep(shouldFireBallista3, fireBallista3);
-		fireBalistaConditional.addStep(shouldFireBallista4, fireBallista4);
+		fireBallistaConditional = new ConditionalStep(this, fireBallista, "Fire the ballista at the tower.");
+		fireBallistaConditional.addStep(shouldFireBallista1, fireBallista1);
+		fireBallistaConditional.addStep(shouldFireBallista2, fireBallista2);
+		fireBallistaConditional.addStep(shouldFireBallista3, fireBallista3);
+		fireBallistaConditional.addStep(shouldFireBallista4, fireBallista4);
@@
-		var cTalkToTrackers = new ConditionalStep(this, fireBalistaConditional);
+		var cTalkToTrackers = new ConditionalStep(this, fireBallistaConditional);
@@
-			fireBalistaConditional
+			fireBallistaConditional

Also applies to: 311-315, 401-401


256-261: Clarify player-facing text

Tiny copy edit for clarity/grammar.

-		var getOrbFromChest = new ObjectStep(this, ObjectID.CHESTCLOSED_KHAZARD, new WorldPoint(2506, 3259, 1), "Retrieve the first orb from chest.");
+		var getOrbFromChest = new ObjectStep(this, ObjectID.CHESTCLOSED_KHAZARD, new WorldPoint(2506, 3259, 1), "Retrieve the first orb from the chest.");
runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/QuestStep.java (1)

667-670: Expose hook method but document/align naming for clarity

Method access widened to protected is good for subclassing. Please add a short Javadoc stating when to override and align the name with the private dispatcher isValidRequirementForRenderInInventory(...) (the current pair is easy to confuse). If you keep both, consider renaming this to isValidItemRequirementForInventoryHighlight(...) for precision.

-	protected boolean isValidRenderRequirementInInventory(ItemRequirement requirement, Widget item)
+	/**
+	 * Extension hook for subclasses to decide whether an ItemRequirement should be highlighted
+	 * for a specific inventory widget item.
+	 */
+	protected boolean isValidItemRequirementForInventoryHighlight(ItemRequirement requirement, Widget item)
 	{
-		return (requirement.shouldHighlightInInventory(client)) && requirement.getAllIds().contains(item.getItemId());
+		return requirement.shouldHighlightInInventory(client) && requirement.getAllIds().contains(item.getItemId());
 	}

And update the private dispatcher accordingly.

runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/shadowsofcustodia/ShadowsOfCustodia.java (1)

183-188: Remove speculative note or turn into actionable check

The hammer comment is speculative and will linger. Either confirm supported hammers and encode via ItemCollections, or remove the note to avoid confusion.

-		// NOTE: I have _not_ confirmed you can use any other hammer, this is an educated guess.
 		hammer = new ItemRequirement("Hammer", ItemCollections.HAMMER).showConditioned(needToReinforceWall);
runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/sheepshearer/SheepShearer.java (1)

127-131: Fix typos and naming: “getShears” and “Pick up”

Minor UX polish.

-getSheers = new ItemStep(this, new WorldPoint(3190, 3273, 0),
-  "Pickup the shears in Fred's house.", shears);
+getSheers = new ItemStep(this, new WorldPoint(3190, 3273, 0),
+  "Pick up the shears in Fred's house.", shears);

Optional: rename variable getSheers → getShears throughout.

runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/misthalinmystery/MisthalinMystery.java (3)

191-191: Avoid magic numbers for widget group IDs

Replace 554 with a named constant to improve maintainability (it equals the MistmystPiano group). If a constant for the group ID isn’t available, add one.

Example:

// Prefer a dedicated constant, e.g. InterfaceGroup.MISTMYST_PIANO = 554;
inPianoWidget = new WidgetTextRequirement(InterfaceGroup.MISTMYST_PIANO, 20, "C");

257-262: Empty step text leads to blank overlays

playPiano/searchFireplace steps have empty descriptions; provide concise guidance to avoid blank panels/hints.

-playPiano = new ObjectStep(this, ObjectID.MISTMYST_PIANO, new WorldPoint(1647, 4841, 0), "");
+playPiano = new ObjectStep(this, ObjectID.MISTMYST_PIANO, new WorldPoint(1647, 4841, 0), "Interact with the piano.");

193-199: Spelling: “Sapphire”

Variable and comments use “Saphire”; prefer “Sapphire” for consistency (selectedSapphire).

Would you like me to prepare a small rename patch across this file?

runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/therestlessghost/TheRestlessGhost.java (1)

123-128: Dialogue punctuation clean-up

Minor grammar fixes for on-screen text.

 speakToGhost.addDialogStep("Yep, now tell me what the problem is.");
-speakToGhost.addDialogStep("Yep, clever aren't I?.");
-speakToGhost.addDialogStep("Yes, ok. Do you know WHY you're a ghost?");
-speakToGhost.addDialogStep("Yes, ok. Do you know why you're a ghost?");
+speakToGhost.addDialogStep("Yep, clever aren't I?");
+speakToGhost.addDialogStep("Yes, OK. Do you know WHY you're a ghost?");
+speakToGhost.addDialogStep("Yes, OK. Do you know why you're a ghost?");
runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/thefinaldawn/TheFinalDawn.java (4)

585-606: ItemRequirement names: align display vs. actual items

“Ranged/Magic Combat gear” and “Melee Combat gear” use -1 display with bank icons. Consider adding tooltips for clarity in the sidebar.

Example:

rangedGear.setTooltip("Any decent ranged or magic setup suitable for lvl 160–396 foes.");
combatGear.setTooltip("Any decent melee setup suitable for lvl 160–396 foes.");

755-776: Widget model requirements: keep constants centralized

The magic numbers 54541/54546/54539 are fragile. Consider centralizing these model IDs (e.g., PendantOfAtes/Quetzal icon models) into a constants class.


1070-1086: Sun puzzle substep wiring: duplicate addSubSteps

solveSunPuzzleNoPuzzleWrap and solveSunPuzzle both add the same substeps; pick one to avoid redundant sidebar duplication.


1187-1194: Combat requirements: add brief tactics per encounter

Optional UX improvement: short one-liners (e.g., prayer suggestion) increase clarity in the sidebar summary.

runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/enakhraslament/EnakhrasLament.java (3)

400-404: Tighten step copy: fix apostrophe and clarify ladder action

Helps avoid player confusion when already inside the entrance chamber.

Apply:

-		enterTemple = new ObjectStep(this, ObjectID.ENAKH_SECRET_BOULDER_MULTILOC_E, new WorldPoint(3194, 2925, 0), "Enter the temple south of the Bandit's " +
-			"Camp.");
+		enterTemple = new ObjectStep(this, ObjectID.ENAKH_SECRET_BOULDER_MULTILOC_E, new WorldPoint(3194, 2925, 0), "Enter the temple south of the Bandit Camp.");
-		enterTempleDownLadder = new ObjectStep(this, ObjectID.ENAKH_TEMPLE_LADDERDOWN, new WorldPoint(3127, 9329, 1), "Enter the temple south of the Bandit's" +
-			" Camp.");
+		enterTempleDownLadder = new ObjectStep(this, ObjectID.ENAKH_TEMPLE_LADDERDOWN, new WorldPoint(3127, 9329, 1), "Climb down the ladder in the temple entrance chamber.");

330-334: Either wire letter-door steps/varbits into progression or drop them

You define zPlaced/mPlaced/rPlaced/kPlaced and enterK/R/M/ZDoor, but they’re never surfaced by any ConditionalStep. This is dead weight or missed guidance for opening the central room.

Options:

  • Hook: after collecting all four sigils, surface the corresponding enterXDoor steps until doors are opened; or
  • Remove the unused varbits/steps to reduce noise.

If desired, I can propose a precise sequencing using the placed varbits; confirm intended UX.

Also applies to: 428-432, 519-537


488-490: Rename typo: talkToAkthankos → talkToAkthanakos

Keeps naming consistent with the NPC IDs and reward text.

Outside-range change (illustrative):

// rename the field and its usages across this file
private NpcStep talkToAkthanakos;
// ...and update all references where talkToAkthankos is used

Comment on lines +36 to +38
If you have the [Shortest Path](https://runelite.net/plugin-hub/show/shortest-path) plugin installed, you can enable the
the "Use 'Shortest Path' plugin" in the Quest Helper config.

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

Fix duplicate word.

Apply:

-If you have the [Shortest Path](https://runelite.net/plugin-hub/show/shortest-path) plugin installed, you can enable the
-the "Use 'Shortest Path' plugin" in the Quest Helper config.
+If you have the [Shortest Path](https://runelite.net/plugin-hub/show/shortest-path) plugin installed, you can enable
+the "Use 'Shortest Path' plugin" in the Quest Helper config.
📝 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 you have the [Shortest Path](https://runelite.net/plugin-hub/show/shortest-path) plugin installed, you can enable the
the "Use 'Shortest Path' plugin" in the Quest Helper config.
If you have the [Shortest Path](https://runelite.net/plugin-hub/show/shortest-path) plugin installed, you can enable
the "Use 'Shortest Path' plugin" in the Quest Helper config.
🧰 Tools
🪛 LanguageTool

[grammar] ~36-~36: There might be a mistake here.
Context: ...th) plugin installed, you can enable the the "Use 'Shortest Path' plugin" in the Que...

(QB_NEW_EN)

🤖 Prompt for AI Agents
In README.md around lines 36 to 38, there is a duplicated word "the the" in the
sentence about the Shortest Path plugin; remove the extra "the" so the sentence
reads "...you can enable the "Use 'Shortest Path' plugin" in the Quest Helper
config." Ensure spacing and punctuation remain correct after deletion.


![](./images/shortest-path-02.png)

You can configure what teleportation methods, or the aesthetic of the path in the Shortest Path config.
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

Clarify sentence.

Apply:

-You can configure what teleportation methods, or the aesthetic of the path in the Shortest Path config.
+You can configure which teleportation methods to use and the path’s aesthetic in the Shortest Path config.
📝 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
You can configure what teleportation methods, or the aesthetic of the path in the Shortest Path config.
You can configure which teleportation methods to use and the path’s aesthetic in the Shortest Path config.
🤖 Prompt for AI Agents
In README.md around line 45, the sentence "You can configure what teleportation
methods, or the aesthetic of the path in the Shortest Path config." is unclear
and grammatically incorrect; replace it with a clear, concise sentence such as
"You can configure teleportation methods and the aesthetic of the path in the
Shortest Path configuration." to fix the grammar, remove the stray comma, and
use "configuration" for clarity.

Comment on lines +135 to 137
.filter(npc -> npc.getId() == npcID || npc.getComposition().getId() == npcID)
.filter(npc -> npcName == null || (npc.getName() != null && npc.getName().equals(npcName)))
.collect(Collectors.toList());
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

Harden ID and location checks to avoid NPEs.

npc.getComposition() and npc.getWorldLocation() can be null transiently. Current code risks NPEs during scene rebuilds/instancing.

Apply:

- .filter(npc -> npc.getId() == npcID || npc.getComposition().getId() == npcID)
+ .filter(npc -> npc.getId() == npcID || (npc.getComposition() != null && npc.getComposition().getId() == npcID))
- WorldPoint npcLocation = WorldPoint.fromLocalInstance(client,  npc.getLocalLocation(), npc.getWorldLocation().getPlane());
+ WorldPoint worldLoc = npc.getWorldLocation();
+ WorldPoint npcLocation = worldLoc != null
+   ? WorldPoint.fromLocalInstance(client, npc.getLocalLocation(), worldLoc.getPlane())
+   : null;

Optionally, extract a shared "matchesNpcId" helper used here and in NpcStep to keep matching rules consistent.

Also applies to: 145-147

Comment on lines +128 to +130
@Setter
private boolean onlyHighlightItemsOnTile;

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

New toggle onlyHighlightItemsOnTile — add null-safety for worldPoint.

When enabled and worldPoint is null, downstream calls will NPE. Guard in consumers.

Apply:

@@
-  private boolean onlyHighlightItemsOnTile;
+  private boolean onlyHighlightItemsOnTile;

And update usages below (see suggested diffs on Lines 574 and 624).

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +574 to 575
if (onlyHighlightItemsOnTile && !QuestPerspective.getInstanceLocalPointFromReal(client, worldPoint).contains(tile.getLocalLocation())) return;
for (Requirement requirement : requirements)
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

Guard against NPE when restricting highlights to the step tile.

worldPoint can be null; also precompute local points once.

Apply:

-    if (onlyHighlightItemsOnTile && !QuestPerspective.getInstanceLocalPointFromReal(client, worldPoint).contains(tile.getLocalLocation())) return;
+    if (onlyHighlightItemsOnTile) {
+      if (worldPoint == null) return;
+      List<LocalPoint> stepPoints = QuestPerspective.getInstanceLocalPointFromReal(client, worldPoint);
+      if (stepPoints == null || stepPoints.isEmpty()) return;
+      if (!stepPoints.contains(tile.getLocalLocation())) return;
+    }
📝 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 (onlyHighlightItemsOnTile && !QuestPerspective.getInstanceLocalPointFromReal(client, worldPoint).contains(tile.getLocalLocation())) return;
for (Requirement requirement : requirements)
if (onlyHighlightItemsOnTile) {
if (worldPoint == null) {
return;
}
List<LocalPoint> stepPoints = QuestPerspective.getInstanceLocalPointFromReal(client, worldPoint);
if (stepPoints == null || stepPoints.isEmpty()) {
return;
}
if (!stepPoints.contains(tile.getLocalLocation())) {
return;
}
}
for (Requirement requirement : requirements)
🤖 Prompt for AI Agents
In
runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/DetailedQuestStep.java
around lines 574-575, guard against a NullPointerException by checking if
worldPoint is null before using it, and precompute the local point once (e.g.,
compute QuestPerspective.getInstanceLocalPointFromReal(client, worldPoint) into
a local variable) instead of calling it repeatedly; if worldPoint is null skip
the tile-restriction logic (return or continue as appropriate) and use the
cached local point for comparisons to avoid repeated expensive calls.

Comment on lines +624 to 625
if (onlyHighlightItemsOnTile && !QuestPerspective.getInstanceLocalPointFromReal(client, worldPoint).contains(tile.getLocalLocation())) continue;
for (Requirement requirement : requirements)
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

Same NPE risk in bulk scan; apply the same guard.

Mirror the worldPoint checks to avoid crashes during area scans.

Apply:

-          if (onlyHighlightItemsOnTile && !QuestPerspective.getInstanceLocalPointFromReal(client, worldPoint).contains(tile.getLocalLocation())) continue;
+          if (onlyHighlightItemsOnTile) {
+            if (worldPoint == null) continue;
+            List<LocalPoint> stepPoints = QuestPerspective.getInstanceLocalPointFromReal(client, worldPoint);
+            if (stepPoints == null || stepPoints.isEmpty()) continue;
+            if (!stepPoints.contains(tile.getLocalLocation())) 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
if (onlyHighlightItemsOnTile && !QuestPerspective.getInstanceLocalPointFromReal(client, worldPoint).contains(tile.getLocalLocation())) continue;
for (Requirement requirement : requirements)
if (onlyHighlightItemsOnTile) {
if (worldPoint == null) continue;
List<LocalPoint> stepPoints = QuestPerspective.getInstanceLocalPointFromReal(client, worldPoint);
if (stepPoints == null || stepPoints.isEmpty()) continue;
if (!stepPoints.contains(tile.getLocalLocation())) continue;
}
for (Requirement requirement : requirements)
🤖 Prompt for AI Agents
In
runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/DetailedQuestStep.java
around lines 624-625, the bulk scan loop calls
QuestPerspective.getInstanceLocalPointFromReal(client, worldPoint) without
guarding against a null worldPoint which can cause an NPE; add the same
null/valid-worldPoint check used elsewhere (e.g., verify worldPoint != null
and/or worldPoint.getX()/getY() are valid) before calling
getInstanceLocalPointFromReal so the continue check only runs when worldPoint is
non-null/valid.

Comment on lines +63 to 67
/// Creates a DigStep with a custom spade requirement, allowing you to pass through custom tooltips / tips to the player
public static DigStep withCustomSpadeRequirement(QuestHelper questHelper, WorldPoint worldPoint, String text, ItemRequirement spade, Requirement... requirements)
{
this.expectedItemPredicate = predicate == null ? i -> true : predicate;
return new DigStep(questHelper, worldPoint, text, spade, requirements);
}
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

Avoid mutating caller-supplied ItemRequirement

When a custom spade requirement is passed, toggling highlight will mutate the shared instance. Copy it locally to prevent side effects.

-	public static DigStep withCustomSpadeRequirement(QuestHelper questHelper, WorldPoint worldPoint, String text, ItemRequirement spade, Requirement... requirements)
+	public static DigStep withCustomSpadeRequirement(QuestHelper questHelper, WorldPoint worldPoint, String text, ItemRequirement spade, Requirement... requirements)
 	{
-		return new DigStep(questHelper, worldPoint, text, spade, requirements);
+		return new DigStep(questHelper, worldPoint, text, spade.copy(), requirements);
 	}
📝 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
/// Creates a DigStep with a custom spade requirement, allowing you to pass through custom tooltips / tips to the player
public static DigStep withCustomSpadeRequirement(QuestHelper questHelper, WorldPoint worldPoint, String text, ItemRequirement spade, Requirement... requirements)
{
this.expectedItemPredicate = predicate == null ? i -> true : predicate;
return new DigStep(questHelper, worldPoint, text, spade, requirements);
}
/// Creates a DigStep with a custom spade requirement, allowing you to pass through custom tooltips / tips to the player
public static DigStep withCustomSpadeRequirement(QuestHelper questHelper, WorldPoint worldPoint, String text, ItemRequirement spade, Requirement... requirements)
{
return new DigStep(questHelper, worldPoint, text, spade.copy(), requirements);
}
🤖 Prompt for AI Agents
In
runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/steps/DigStep.java
around lines 63-67, avoid mutating the caller-supplied ItemRequirement by making
a defensive copy before storing/using it; change withCustomSpadeRequirement to
pass a copy of spade (e.g. spade.copy() or new ItemRequirement constructed from
spade's properties if copy() isn't available) into the DigStep constructor so
toggling highlight mutates only the local instance.

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.

Review continued from previous batch...

Comment on lines +212 to +214
northPuzzleRoom = new Zone(new WorldPoint(2095, 9319, 1), new WorldPoint(3112, 9335, 1));
topRoom = new Zone(new WorldPoint(3097, 9299, 2), new WorldPoint(3113, 9334, 2));
lastRoom = new Zone(new WorldPoint(3096, 9291, 1), new WorldPoint(3112, 9302, 1));
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

northPuzzleRoom zone min-X looks like a typo (2095 → 3095); current bounds are enormous and will mis-trigger steps

Using 2095 makes the zone span ~1k tiles westward. This will cause false positives for inNorthPuzzleRoom and any steps gated by it.

Apply:

-		northPuzzleRoom = new Zone(new WorldPoint(2095, 9319, 1), new WorldPoint(3112, 9335, 1));
+		northPuzzleRoom = new Zone(new WorldPoint(3095, 9319, 1), new WorldPoint(3112, 9335, 1));
📝 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
northPuzzleRoom = new Zone(new WorldPoint(2095, 9319, 1), new WorldPoint(3112, 9335, 1));
topRoom = new Zone(new WorldPoint(3097, 9299, 2), new WorldPoint(3113, 9334, 2));
lastRoom = new Zone(new WorldPoint(3096, 9291, 1), new WorldPoint(3112, 9302, 1));
northPuzzleRoom = new Zone(new WorldPoint(3095, 9319, 1), new WorldPoint(3112, 9335, 1));
topRoom = new Zone(new WorldPoint(3097, 9299, 2), new WorldPoint(3113, 9334, 2));
lastRoom = new Zone(new WorldPoint(3096, 9291, 1), new WorldPoint(3112, 9302, 1));
🤖 Prompt for AI Agents
In
runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/enakhraslament/EnakhrasLament.java
around lines 212 to 214, the northPuzzleRoom Zone was created with an incorrect
min-X of 2095 which makes the zone span ~1k tiles west and will mis-trigger
location checks; change the first WorldPoint X from 2095 to 3095 so the Zone
reads new WorldPoint(3095, 9319, 1) to correctly bound the north puzzle room.

Comment on lines +499 to +535
private int getSumOfLitBraziers()
{
Tile[][] tiles;
if (client.getTopLevelWorldView().getScene() == null) return -1;

List<WorldPoint> wps = List.of(
new WorldPoint(1296, 9457, 1),
new WorldPoint(1289, 9457, 1),
new WorldPoint(1286, 9454, 1),
new WorldPoint(1282, 9449, 1),
new WorldPoint(1282, 9443, 1),
new WorldPoint(1286, 9438, 1),
new WorldPoint(1289, 9435, 1),
new WorldPoint(1296, 9435, 1)

);

int total = 0;

for (WorldPoint wp : wps)
{
List<LocalPoint> localPoints = QuestPerspective.getInstanceLocalPointFromReal(client, wp);

if (localPoints.isEmpty()) return -1;

tiles = client.getTopLevelWorldView().getScene().getTiles()[client.getTopLevelWorldView().getPlane()];

for (LocalPoint localPoint : localPoints)
{
Tile b1Tile = tiles[localPoint.getSceneX()][localPoint.getSceneY()];
boolean isMatch = Arrays.stream(b1Tile.getGameObjects()).anyMatch((obj) -> obj != null && obj.getId() == ObjectID.VMQ4_MOON_PUZZLE_BRAZIER_LIT);
if (isMatch) total++;
}
}

return total;
}
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

⚠️ Potential issue

Null/bounds safety and minor perf in getSumOfLitBraziers

Guard null tiles and out-of-bounds scene coords; hoist tiles outside the inner loop.

 private int getSumOfLitBraziers()
 {
-  Tile[][] tiles;
-  if (client.getTopLevelWorldView().getScene() == null) return -1;
+  if (client.getTopLevelWorldView().getScene() == null) return -1;
+  final var scene = client.getTopLevelWorldView().getScene();
+  final int plane = client.getTopLevelWorldView().getPlane();
+  final Tile[][] tiles = scene.getTiles()[plane];

   List<WorldPoint> wps = List.of(
     new WorldPoint(1296, 9457, 1),
     new WorldPoint(1289, 9457, 1),
     new WorldPoint(1286, 9454, 1),
     new WorldPoint(1282, 9449, 1),
     new WorldPoint(1282, 9443, 1),
     new WorldPoint(1286, 9438, 1),
     new WorldPoint(1289, 9435, 1),
     new WorldPoint(1296, 9435, 1)
   );

   int total = 0;

   for (WorldPoint wp : wps)
   {
     List<LocalPoint> localPoints = QuestPerspective.getInstanceLocalPointFromReal(client, wp);

     if (localPoints.isEmpty()) return -1;

-    tiles = client.getTopLevelWorldView().getScene().getTiles()[client.getTopLevelWorldView().getPlane()];
-
     for (LocalPoint localPoint : localPoints)
     {
-      Tile b1Tile = tiles[localPoint.getSceneX()][localPoint.getSceneY()];
-      boolean isMatch = Arrays.stream(b1Tile.getGameObjects()).anyMatch((obj) -> obj != null && obj.getId() == ObjectID.VMQ4_MOON_PUZZLE_BRAZIER_LIT);
+      int sx = localPoint.getSceneX(), sy = localPoint.getSceneY();
+      if (sx < 0 || sy < 0 || sx >= tiles.length || sy >= tiles[0].length) continue;
+      Tile b1Tile = tiles[sx][sy];
+      if (b1Tile == null) continue;
+      boolean isMatch = Arrays.stream(b1Tile.getGameObjects())
+        .filter(Objects::nonNull)
+        .anyMatch(obj -> obj.getId() == ObjectID.VMQ4_MOON_PUZZLE_BRAZIER_LIT);
       if (isMatch) total++;
     }
   }

   return total;
 }
📝 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
private int getSumOfLitBraziers()
{
Tile[][] tiles;
if (client.getTopLevelWorldView().getScene() == null) return -1;
List<WorldPoint> wps = List.of(
new WorldPoint(1296, 9457, 1),
new WorldPoint(1289, 9457, 1),
new WorldPoint(1286, 9454, 1),
new WorldPoint(1282, 9449, 1),
new WorldPoint(1282, 9443, 1),
new WorldPoint(1286, 9438, 1),
new WorldPoint(1289, 9435, 1),
new WorldPoint(1296, 9435, 1)
);
int total = 0;
for (WorldPoint wp : wps)
{
List<LocalPoint> localPoints = QuestPerspective.getInstanceLocalPointFromReal(client, wp);
if (localPoints.isEmpty()) return -1;
tiles = client.getTopLevelWorldView().getScene().getTiles()[client.getTopLevelWorldView().getPlane()];
for (LocalPoint localPoint : localPoints)
{
Tile b1Tile = tiles[localPoint.getSceneX()][localPoint.getSceneY()];
boolean isMatch = Arrays.stream(b1Tile.getGameObjects()).anyMatch((obj) -> obj != null && obj.getId() == ObjectID.VMQ4_MOON_PUZZLE_BRAZIER_LIT);
if (isMatch) total++;
}
}
return total;
}
private int getSumOfLitBraziers()
{
if (client.getTopLevelWorldView().getScene() == null)
{
return -1;
}
final var scene = client.getTopLevelWorldView().getScene();
final int plane = client.getTopLevelWorldView().getPlane();
final Tile[][] tiles = scene.getTiles()[plane];
List<WorldPoint> wps = List.of(
new WorldPoint(1296, 9457, 1),
new WorldPoint(1289, 9457, 1),
new WorldPoint(1286, 9454, 1),
new WorldPoint(1282, 9449, 1),
new WorldPoint(1282, 9443, 1),
new WorldPoint(1286, 9438, 1),
new WorldPoint(1289, 9435, 1),
new WorldPoint(1296, 9435, 1)
);
int total = 0;
for (WorldPoint wp : wps)
{
List<LocalPoint> localPoints = QuestPerspective.getInstanceLocalPointFromReal(client, wp);
if (localPoints.isEmpty())
{
return -1;
}
for (LocalPoint localPoint : localPoints)
{
int sx = localPoint.getSceneX(), sy = localPoint.getSceneY();
if (sx < 0 || sy < 0 || sx >= tiles.length || sy >= tiles[0].length)
{
continue;
}
Tile b1Tile = tiles[sx][sy];
if (b1Tile == null)
{
continue;
}
boolean isMatch = Arrays.stream(b1Tile.getGameObjects())
.filter(Objects::nonNull)
.anyMatch(obj -> obj.getId() == ObjectID.VMQ4_MOON_PUZZLE_BRAZIER_LIT);
if (isMatch)
{
total++;
}
}
}
return total;
}
🤖 Prompt for AI Agents
In
runelite-client/src/main/java/net/runelite/client/plugins/microbot/questhelper/helpers/quests/thefinaldawn/TheFinalDawn.java
around lines 499 to 535, the method should guard against null tiles and
out-of-bounds scene coordinates and hoist the tiles lookup outside the inner
loop: after obtaining localPoints, fetch the scene tiles once (check
getTopLevelWorldView().getScene() and tiles for null), get the plane and
validate tiles[plane] exists, then inside the localPoints loop validate each
localPoint's sceneX/sceneY are within tiles[plane] bounds before indexing, skip
or return -1 on invalid coords as appropriate, and null-check the returned Tile
and its getGameObjects() before streaming to avoid NPEs; this both fixes safety
and removes repeated tiles lookups for minor perf improvement.

chsami and others added 2 commits August 30, 2025 02:00
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@chsami chsami merged commit 9a9cf66 into chsami:development Aug 30, 2025
1 of 2 checks passed
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: 0

♻️ Duplicate comments (3)
README.md (3)

36-37: Fix duplicated “the” and streamline sentence.
Same issue flagged earlier; it still appears here.

-If you have the [Shortest Path](https://runelite.net/plugin-hub/show/shortest-path) plugin installed, you can enable the
-the "Use 'Shortest Path' plugin" in the Quest Helper config.
+If you have the [Shortest Path](https://runelite.net/plugin-hub/show/shortest-path) plugin installed, enable the "Use 'Shortest Path' plugin" option in the Quest Helper config.

39-39: Add alt text for accessibility (MD045).
Same as prior comment; add concise alt text.

-![](./images/shortest-path-01.png)
+![Shortest Path option in config](./images/shortest-path-01.png)

45-45: Clarify grammar and remove stray comma.
Same suggestion as earlier review.

-You can configure what teleportation methods, or the aesthetic of the path in the Shortest Path config.
+You can configure which teleportation methods to use and the path’s aesthetic in the Shortest Path config.
🧹 Nitpick comments (1)
README.md (1)

29-31: Tighten phrasing (“look up”) and avoid hard line break mid‑sentence.
Use “look up” and keep the sentence on one line for readability.

-If you have the [Not Enough Runes](https://runelite.net/plugin-hub/show/not-enough-runes) plugin installed, you can
-right-click item requirements and click `Go to NER...` to look that item up in Not Enough Runes.
+If you have the [Not Enough Runes](https://runelite.net/plugin-hub/show/not-enough-runes) plugin installed, you can right-click item requirements and click `Go to NER...` to look up that item in Not Enough Runes.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a787b6f and 1f5790b.

📒 Files selected for processing (1)
  • README.md (1 hunks)
🧰 Additional context used
🪛 LanguageTool
README.md

[grammar] ~29-~29: There might be a mistake here.
Context: ...-enough-runes) plugin installed, you can right-click item requirements and click ...

(QB_NEW_EN)


[grammar] ~36-~36: There might be a mistake here.
Context: ...th) plugin installed, you can enable the the "Use 'Shortest Path' plugin" in the Que...

(QB_NEW_EN)

🪛 markdownlint-cli2 (0.17.2)
README.md

39-39: Images should have alternate text (alt text)

(MD045, no-alt-text)

🔇 Additional comments (3)
README.md (3)

23-26: Nice addition: clear integration intro.
Section reads well and orients users to the combined plugin experience.


32-32: Alt text looks good.
Descriptive and accessible.


43-43: Alt text provided—good.
Keeps consistency with other images.

@chsami chsami mentioned this pull request Sep 3, 2025
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.