Skip to content

Comments

1.9.8.8#1426

Merged
chsami merged 28 commits intomainfrom
development
Aug 24, 2025
Merged

1.9.8.8#1426
chsami merged 28 commits intomainfrom
development

Conversation

@chsami
Copy link
Owner

@chsami chsami commented Aug 23, 2025

Summary by CodeRabbit

  • New Features

    • Forestry automation: entlings, roots, fox traps, hives, leprechaun, ritual circles, pheasant eggs, struggling saplings (flowers placeholder).
    • Forestry events UI and integration; plugin hub auto-refresh and sync.
    • Woodcutting overlay: live stats (XP/hr, level delta, logs, runtime) and reset stats.
    • New bank action: empty log basket.
  • Improvements

    • Woodcutting v1.7.0: state-driven flow, banking/looting, firemaking, forestry toggles and many new config options.
    • Plugin install/load resilience and version compatibility checks.
    • NPC animation/head-icon resolution improved; plugin icons scaled in hub.
  • Chores

    • Version bump.

Steffen Carlsen and others added 28 commits July 5, 2025 01:49
Implemented detection and handling for the Struggling Sapling forestry event in AutoWoodcuttingScript. Updated overlay to display current forestry event. Extended items to bank in config. Improved event detection and handling logic for forestry events, and refactored related code for clarity and maintainability.
Implemented detection and handling for the Struggling Sapling forestry event in AutoWoodcuttingScript. Updated overlay to display current forestry event. Extended items to bank in config. Improved event detection and handling logic for forestry events, and refactored related code for clarity and maintainability.
…stry-

# Conflicts:
#	runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingScript.java
test and fix events:
- all events work somewhat
- for some events the interaction speed is abysmal, but that can't easily be changed without rewriting the core plugin loop
- fix burning logs on existing campfires (not getting stuck in last animation anymore)
…oodcutting-and-Forestry-

# Conflicts:
#	runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingScript.java
#	runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/enums/WoodcuttingTree.java
- improve some events
- add tracking of wrong ingredients for sapling
…instead of trying to re-load all sideloaded plugins
Adjust Install workflow of MicrobotPluginManager
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 23, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Bumps microbot.version and implements extensive plugin-management, forestry/woodcutting features, GUI updates, utilities tweaks, and many new forestry event classes. Changes include manifest caching/refresh, sideload install/load/sync, a state-machine woodcutting script, UI/overlay updates, reflection-to-API refactors, and a new bank helper.

Changes

Cohort / File(s) Summary
Build & versioning
runelite-client/pom.xml
Bumps microbot.version from 1.9.8.6 → 1.9.8.8 (affects shaded artifact name).
External plugins system
.../externalplugins/MicrobotPluginManager.java, .../ui/MicrobotPluginHubPanel.java
Adds manifestMap cache with scheduled refresh (10m), getManifestMap() accessor, manifest change detection and event posting, robust install/load/sync flows (SHA-256 verification, sideload loader, dependency-aware loading), improved error handling and UI integration; PluginHubPanel now reads manifests from manager and refactors reload/build logic; icons scaled.
Woodcutting core (config, overlay, plugin, script, enums)
.../woodcutting/AutoWoodcuttingConfig.java, .../AutoWoodcuttingOverlay.java, .../AutoWoodcuttingPlugin.java, .../AutoWoodcuttingScript.java, .../enums/ForestryEvents.java, .../enums/WoodcuttingScriptState.java
Adds AutoWoodcutting config group/sections and many new options (forestry, loot/reset), injects Client into overlay and shows runtime stats, exposes forestry state in plugin and dynamic event registration, converts script to state-machine with banking/looting/return-point logic, introduces FORESTRY_DISTANCE and version bump, and adds enums for forestry events and script states.
Forestry events
.../woodcutting/Forestry/EggEvent.java, EntlingsEvent.java, FlowersEvent.java, FoxEvent.java, HivesEvent.java, LeprechaunEvent.java, RitualEvent.java, StrugglingSaplingEvent.java
Adds multiple BlockingEvent implementations for forestry: EggEvent, EntlingsEvent, FlowersEvent (placeholder), FoxEvent, HivesEvent, LeprechaunEvent, RitualEvent, StrugglingSaplingEvent. Each supplies validate/execute/priority and updates plugin forestry state.
Utilities
.../util/reflection/Rs2Reflection.java, .../util/bank/Rs2Bank.java
Rs2Reflection: replaces reflection-based NPC animation/head-icon resolution with direct API (signature change: getAnimation(NPC), new getHeadIcon), removes legacy reflection helpers. Rs2Bank: adds public emptyLogBasket() helper.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Manager as MicrobotPluginManager
  participant Client as MicrobotPluginClient
  participant FS as FileSystem
  participant Bus as EventBus

  Note over Manager: Initialization
  User->>Manager: instantiate
  Manager->>Client: download manifests
  Client-->>Manager: manifests
  Manager->>Manager: build manifestMap, compare (keys+SHA256)
  alt manifests changed
    Manager->>Bus: post ExternalPluginsChanged
  end
  Manager->>Manager: schedule periodic refresh (10m)

  Note over Manager,FS: Install path
  User->>Manager: install(manifest)
  Manager->>Client: fetch jar URL & bytes
  Client-->>Manager: jar bytes
  Manager->>Manager: verify SHA-256
  Manager->>FS: write jar, update installed list
  Manager->>Manager: loadSideLoadPlugin(internalName)
  Manager->>Bus: post ExternalPluginsChanged
Loading
sequenceDiagram
  autonumber
  participant Plugin as AutoWoodcuttingPlugin
  participant Script as AutoWoodcuttingScript
  participant Events as ForestryEvents*
  participant Overlay as AutoWoodcuttingOverlay
  participant Config

  Note over Plugin: Startup
  Plugin->>Plugin: addEvents() if config.enableForestry
  Plugin->>Script: start main loop
  loop main
    Script->>Script: preFlightChecks
    alt WOODCUTTING
      Script->>Script: handleWoodcutting / handleLooting
    else FIREMAKING
      Script->>Script: handleFiremaking
    else RESETTING
      Script->>Script: handleBanking/resetInventory
    end
    Script->>Overlay: update stats (xp, logs, time)
    alt forestry triggered
      Plugin->>Events: execute selected blocking event
      Events-->>Plugin: update currentForestryEvent
    end
  end
Loading
sequenceDiagram
  autonumber
  participant Panel as MicrobotPluginHubPanel
  participant Manager as MicrobotPluginManager
  participant Counts as Plugin Count Service
  participant UI

  Panel->>Manager: getManifestMap().values()
  Panel->>Counts: fetch pluginCounts
  alt counts OK
    Panel->>UI: build list from manifests + counts
  else counts error
    Panel->>UI: show Retry button
  end
  Manager-->>Panel: ExternalPluginsChanged event
  Panel->>Panel: reload using cached manifests
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

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.


📜 Recent 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 0d2a385 and c5ba4f9.

📒 Files selected for processing (20)
  • runelite-client/pom.xml (1 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/externalplugins/MicrobotPluginManager.java (6 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/ui/MicrobotPluginHubPanel.java (3 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/bank/Rs2Bank.java (1 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/reflection/Rs2Reflection.java (2 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingConfig.java (7 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingOverlay.java (1 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingPlugin.java (2 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingScript.java (7 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/EggEvent.java (1 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/EntlingsEvent.java (1 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/FlowersEvent.java (1 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/FoxEvent.java (1 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/HivesEvent.java (1 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/LeprechaunEvent.java (1 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/RitualEvent.java (1 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/RootEvent.java (1 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/StrugglingSaplingEvent.java (1 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/enums/ForestryEvents.java (1 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/enums/WoodcuttingScriptState.java (1 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 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 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.

@chsami
Copy link
Owner Author

chsami commented Aug 23, 2025

@CodeRabbit summary

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 23, 2025

✅ Actions performed

Summary regeneration triggered.

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

Caution

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

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

472-481: Stray characters in shade plugin block will break the POM.

There’s a literal "§y" appended after the closing DSA exclude on Line 477. This will cause XML parsing errors during build.

Apply this diff to remove the stray characters:

-                                        <exclude>META-INF/*.DSA</exclude>§y
+                                        <exclude>META-INF/*.DSA</exclude>
runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingConfig.java (1)

133-141: User-facing typo: “Comma seperated” → “Comma separated”.

Visible in the configuration UI; fix spelling.

-            name = "Items to bank (Comma seperated)",
+            name = "Items to bank (Comma separated)",
🧹 Nitpick comments (34)
runelite-client/pom.xml (1)

49-53: Consider using HTTPS for distributionManagement snapshotRepository.

The snapshot repo URL is plain HTTP. If your Nexus/Artifactory supports TLS, switching to HTTPS avoids MITM risks during deploys.

-            <url>http://138.201.81.246:8081/repository/microbot-snapshot/</url>
+            <url>https://138.201.81.246:8081/repository/microbot-snapshot/</url>
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/reflection/Rs2Reflection.java (2)

106-118: Javadoc says “by using reflection” but method is a direct accessor; adjust docs and consider removing @SneakyThrows.

Method body is fine and the deprecation makes sense. Update the summary to reflect direct API usage, and drop @SneakyThrows since nothing here throws.

-    /**
-     * Gets the animation of an NPC by using reflection.
-     * @param npc
-     * @return
-     */
+    /**
+     * Returns the current animation id of the given NPC via the public API.
+     * Returns -1 when npc is null.
+     */
@@
-    @SneakyThrows
     @Deprecated(since="1.9.8.7 - Runelite exposes all animations", forRemoval=true)
     public static int getAnimation(NPC npc) {
         if (npc == null) {
             return -1;
         }
         return npc.getAnimation();
     }

120-148: Bound-check overheadSpriteId to avoid IndexOutOfBounds; prefer direct API where available.

Mapping sprite ids to HeadIcon via ordinal is brittle; unexpected sprite ids can throw. Add a bounds check and consider a future replacement with NPC.getOverheadIcon().

-    @SneakyThrows
-    @Deprecated(since="1.9.8.7 - Runelite exposes overheads on npcs", forRemoval = true)
+    @Deprecated(since="1.9.8.7 - Runelite exposes overheads on npcs", forRemoval = true)
     public static HeadIcon getHeadIcon(Rs2NpcModel npc) {
         if (npc == null) {
             return null;
         }
 
         if (npc.getOverheadSpriteIds() == null) {
             Microbot.log("Failed to find the correct overhead prayer.");
             return null;
         }
 
         for (int i = 0; i < npc.getOverheadSpriteIds().length; i++) {
             int overheadSpriteId = npc.getOverheadSpriteIds()[i];
 
             if (overheadSpriteId == -1) continue;
 
-            return HeadIcon.values()[overheadSpriteId];
+            HeadIcon[] icons = HeadIcon.values();
+            if (overheadSpriteId >= 0 && overheadSpriteId < icons.length) {
+                return icons[overheadSpriteId];
+            } else {
+                Microbot.log("Unexpected overheadSpriteId: " + overheadSpriteId);
+            }
         }
 
         Microbot.log("Found overheadSpriteIds: " + Arrays.toString(npc.getOverheadSpriteIds()) + " but failed to find valid overhead prayer.");
 
         return null;
     }

Optionally, introduce a non-deprecated overload:

public static HeadIcon getHeadIcon(NPC npc) {
    return npc == null ? null : npc.getOverheadIcon();
}
runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/FoxEvent.java (2)

38-40: Wait for trap state change to avoid re-click spam

After invoking Disarm, immediately looping can spam interactions. Wait until the trap despawns/changes before retrying.

             Rs2Npc.interact(trap, "Disarm");
-            Rs2Player.waitForAnimation(1000);
+            Rs2Player.waitForAnimation(1000);
+            // Wait up to 4s for the trap to disappear or become non-interactable
+            net.runelite.client.plugins.microbot.util.Global.sleepUntil(
+                () -> net.runelite.client.plugins.microbot.util.npc.Rs2Npc.getNpc(
+                        net.runelite.api.gameval.NpcID.GATHERING_EVENT_POACHERS_TRAP) == null, 4000);

41-43: Optionally reset forestry state on exit

Other flows may expect currentForestryEvent to clear once handling is done.

-        Microbot.log("FoxEvent: Finished executing the Fox event.", Level.INFO);
-        return true;
+        Microbot.log("FoxEvent: Finished executing the Fox event.", Level.INFO);
+        plugin.currentForestryEvent = net.runelite.client.plugins.microbot.woodcutting.enums.ForestryEvents.NONE;
+        return true;
runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/LeprechaunEvent.java (2)

43-47: Use distance-based arrival check and a more forgiving timeout

Exact WorldPoint equality can be brittle (e.g., pathing to adjacent tile). Prefer a distance check and give walking a bit more time.

-            if (!Rs2Player.getWorldLocation().equals(location)) {
+            if (net.runelite.client.plugins.microbot.util.player.Rs2Player.distanceTo(location) > 0) {
                 Microbot.log("LeprechaunEvent: Walking to the end of the rainbow at " + location, Level.INFO);
                 Rs2Walker.walkFastCanvas(location);
-                Global.sleepUntil(() -> Rs2Player.getWorldLocation().equals(location), 5000);
+                Global.sleepUntil(
+                    () -> net.runelite.client.plugins.microbot.util.player.Rs2Player.distanceTo(location) == 0
+                          || !this.validate(),
+                    10000
+                );
             }

49-52: Relocate TODO above the return (unreachable comment)

The TODO appears after return true;, making it effectively unreachable context. Move it above the return or to the class-level Javadoc.

runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/EggEvent.java (3)

74-77: Add backoff when no nests or pheasants are present

This continue can tight-loop. Sleep briefly to reduce CPU churn.

-            if (nests.isEmpty() || pheasants.isEmpty()) {
+            if (nests.isEmpty() || pheasants.isEmpty()) {
                 Microbot.log("EggEvent: No pheasant nests found, cannot proceed with egg event.");
-                continue;
+                net.runelite.client.plugins.microbot.util.Global.sleep(200);
+                continue;
             }

86-96: Guard null closestNest before interacting and add small delay

closestNest can be null if emptyNests is empty, leading to repeated failed interacts and a hot loop.

-            var closestNest = emptyNests.stream()
+            var closestNest = emptyNests.stream()
                     .min(Comparator.comparingInt(o -> o.getWorldLocation().distanceTo(Rs2Player.getWorldLocation())))
                     .orElse(null);
 
-            var interact = Rs2GameObject.interact(closestNest);
-            if (!interact) {
-                Microbot.log("EggEvent: Failed to interact with the pheasant nest.");
-                Microbot.log("EggEvent: Closest nest is null? " + (closestNest == null));
-            }
+            if (closestNest == null) {
+                Microbot.log("EggEvent: No reachable empty nests found; waiting...");
+                net.runelite.client.plugins.microbot.util.Global.sleep(300);
+                continue;
+            }
+
+            boolean interact = Rs2GameObject.interact(closestNest);
+            if (!interact) {
+                Microbot.log("EggEvent: Failed to interact with the pheasant nest at " + closestNest.getWorldLocation());
+            }
             Rs2Player.waitForAnimation();

48-57: Inventory full path: confirm fallback when no logs are present

If the inventory is full and there’s no tree log to drop, the event cannot progress. Consider an alternate drop strategy (e.g., least valuable log), or abort with a clear log message so the script can recover.

I can propose a safe fallback (e.g., drop a single low-value item from a configurable allowlist) if you want.

runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/RootEvent.java (2)

58-64: Interacting-with-same-root detection is unreliable

The “already interacting” check uses actor interaction and compares an actor’s location with an object’s location; for object chopping this generally won’t match. If the intent is to avoid re-clicking while already chopping, a simpler and reliable check is to skip when the player is currently animating.

-                if (Rs2Player.isInteracting() && Rs2Player.getInteracting() != null) {
-                    Actor interactingNpc = Microbot.getClient().getLocalPlayer().getInteracting();
-                    if (interactingNpc.getWorldLocation().equals(specialRoot.getWorldLocation())) {
-                        continue;
-                    }
-                }
+                if (Rs2Player.isAnimating()) {
+                    continue; // Already chopping; avoid redundant clicks
+                }

Repeat the same replacement in the regular root branch.

Also applies to: 74-80


88-89: Align execute() return with other events

Other events return true on completion; this one returns false. If the framework interprets the boolean, this inconsistency may affect control flow.

-        return false;
+        return true;
runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/EntlingsEvent.java (2)

35-40: Sort then act is good; consider broadening ID filter

Only NpcID.GATHERING_EVENT_ENTLINGS_NPC_01 is included. If other variant IDs (e.g., _02, _03) exist, the handler will miss them.

I can provide a small helper to match a Set of entling IDs if you confirm the constants in your codebase.


45-59: Handle unrecognized/no overhead text with a short backoff

When all entlings have null/unrecognized overheads, the loop spins hot. Add a tiny sleep before the next iteration if nothing actionable was found.

             for (Rs2NpcModel entling : entlings) {
                 String request = entling.getOverheadText();
                 String action;
 
                 if (request == null || request.isEmpty()) {
-                    continue; // Skip if no overhead text is present
+                    continue; // Skip if no overhead text is present
                 }
                 switch (request) {
                     case "Breezy at the back!":
                     case "Short back and sides!":
                         action = "Prune-back";
                         break;
                     case "A leafy mullet!":
                     case "Short on top!":
                         action = "Prune-top";
                         break;
                     default:
-                        continue;
+                        continue;
                 }
 
                 Microbot.log("EntlingsEvent: Interacting with entling: with action: " + action);
                 Rs2Npc.interact(entling, action);
                 Rs2Player.waitForAnimation(1000); // Wait for the pruning animation to finish
             }
+            // Avoid busy-spin when no actionable entlings were found
+            net.runelite.client.plugins.microbot.util.Global.sleep(150);
runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/RitualEvent.java (3)

40-42: Remove unnecessary continue statement

The continue statement on line 42 is redundant since there's no code after it in the loop. The loop will naturally continue to the next iteration.

 if (targetCircle == null) {
     Microbot.log("RitualEvent: No target circle found, cannot proceed with the ritual.", Level.INFO);
-    continue; // If no target circle is found, we cannot proceed with the event
 }

44-48: Consider improving wait condition logic

The TODO comment indicates there's a better condition to wait for when already at the target circle. Consider waiting for a specific game state change (e.g., circle activation or ritual progress) rather than using a fixed sleep duration.

Would you like me to help implement a better wait condition based on ritual circle state changes?


64-89: Document the ritual circle solving algorithm

The XOR-based algorithm for solving ritual circles lacks documentation. Adding comments would improve maintainability by explaining the bit manipulation logic and the pattern matching approach.

 private NPC solveCircles(List<NPC> ritualCircles) {
     if (ritualCircles.size() != 5) {
         return null;
     }

+    // Build a bitmask representing all circles using XOR
+    // Each circle is encoded as: (16 << shape) | (1 << color)
+    // where shape is derived from dividing the NPC offset by 4
+    // and color is the remainder when dividing by 4
     int s = 0;
     for (NPC npc : ritualCircles) {
         int off = npc.getId() - NpcID.GATHERING_EVENT_ENCHANTED_RITUAL_A_1;
         int shape = off / 4;
         int color = off % 4;
         int id = (16 << shape) | (1 << color);
         s = s ^ id; // XOR operation
     }

+    // Find the circle whose ID is a subset of the XOR mask
+    // This identifies the unique/odd circle that needs to be stood on
     for (NPC npc : ritualCircles) {
         int off = npc.getId() - NpcID.GATHERING_EVENT_ENCHANTED_RITUAL_A_1;
         int shape = off / 4;
         int color = off % 4;
         int id = (16 << shape) | (1 << color);
         if ((id & s) == id) { // Bitwise AND
             return npc;
         }
     }

     return null;
 }
runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/FlowersEvent.java (2)

29-34: Implement the execute() method

The execute() method is currently a stub that always returns false. This needs implementation to handle the flowers event properly.

Would you like me to help implement the flowers event execution logic based on the pattern used in other forestry events?


41-51: Consider using a Set for O(1) lookup performance

The isFloweringBush method performs multiple equality checks. For better performance, consider using a Set for O(1) lookup time.

+private static final Set<Integer> FLOWERING_BUSH_IDS = Set.of(
+    NpcID.GATHERING_EVENT_FLOWERING_TREE_BUSH_COL01,
+    NpcID.GATHERING_EVENT_FLOWERING_TREE_BUSH_COL02,
+    NpcID.GATHERING_EVENT_FLOWERING_TREE_BUSH_COL03,
+    NpcID.GATHERING_EVENT_FLOWERING_TREE_BUSH_COL04,
+    NpcID.GATHERING_EVENT_FLOWERING_TREE_BUSH_COL05,
+    NpcID.GATHERING_EVENT_FLOWERING_TREE_BUSH_COL06,
+    NpcID.GATHERING_EVENT_FLOWERING_TREE_BUSH_COL07,
+    NpcID.GATHERING_EVENT_FLOWERING_TREE_BUSH_COL08
+);
+
 private static boolean isFloweringBush(int npcId)
 {
-    return npcId == NpcID.GATHERING_EVENT_FLOWERING_TREE_BUSH_COL01 ||
-            npcId == NpcID.GATHERING_EVENT_FLOWERING_TREE_BUSH_COL02 ||
-            npcId == NpcID.GATHERING_EVENT_FLOWERING_TREE_BUSH_COL03 ||
-            npcId == NpcID.GATHERING_EVENT_FLOWERING_TREE_BUSH_COL04 ||
-            npcId == NpcID.GATHERING_EVENT_FLOWERING_TREE_BUSH_COL05 ||
-            npcId == NpcID.GATHERING_EVENT_FLOWERING_TREE_BUSH_COL06 ||
-            npcId == NpcID.GATHERING_EVENT_FLOWERING_TREE_BUSH_COL07 ||
-            npcId == NpcID.GATHERING_EVENT_FLOWERING_TREE_BUSH_COL08;
+    return FLOWERING_BUSH_IDS.contains(npcId);
 }
runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/HivesEvent.java (2)

29-30: Simplify stream usage for beehive validation

The stream operation can be simplified using anyMatch instead of findAny().isPresent().

 var beehives = Rs2Npc.getNpcs(x -> x.getId() == net.runelite.api.gameval.NpcID.GATHERING_EVENT_BEES_BEEBOX_1 || x.getId() == net.runelite.api.gameval.NpcID.GATHERING_EVENT_BEES_BEEBOX_2);
-return beehives.findAny().isPresent() && Rs2Inventory.count(plugin.config.TREE().getLogID()) > 1;
+return beehives.anyMatch(x -> true) && Rs2Inventory.count(plugin.config.TREE().getLogID()) > 1;

52-53: Implement TODO: Handle sturdy Beehive parts

The TODO comment indicates that sturdy Beehive parts should be dropped from inventory if present. This functionality should be implemented for complete event handling.

Would you like me to generate code to drop sturdy Beehive parts from the inventory after building beehives?

runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/StrugglingSaplingEvent.java (4)

104-110: Add null check before accessing ingredient ID

The code accesses ingredient.getId() without verifying that ingredient is not null. While unlikely in this context, adding a null check would make the code more defensive.

 // Look for matching ingredient in our available ingredients
 for (GameObject ingredient : ingredients) {
+    if (ingredient == null) continue;
     if (ingredient.getId() == correctIngredient.getId()) {
         // Collect this ingredient as it's known to be correct
         Microbot.log("StrugglingSaplingEvent: Collecting known correct ingredient: " + ingredient.getWorldLocation());
         Rs2GameObject.interact(ingredient, "Collect");
         Rs2Player.waitForAnimation();
     }
 }

129-130: Use secure random for better randomness

Using Math.random() for randomization is less ideal than using a proper random generator. Consider using ThreadLocalRandom for better randomness.

+import java.util.concurrent.ThreadLocalRandom;

 Microbot.log("StrugglingSaplingEvent: No known correct ingredient, collecting a random one.");
-var randomIngredient = availableIngredients.get((int) (Math.random() * availableIngredients.size()));
+var randomIngredient = availableIngredients.get(ThreadLocalRandom.current().nextInt(availableIngredients.size()));

136-138: Consider using Arrays.fill for cleaner array reset

Instead of individually setting each element to null, consider using Arrays.fill for a cleaner approach.

 Microbot.log("StrugglingSaplingEvent: Finished processing struggling sapling.");
-plugin.saplingOrder[0] = null; // Reset the sapling order after processing
-plugin.saplingOrder[1] = null;
-plugin.saplingOrder[2] = null;
+Arrays.fill(plugin.saplingOrder, null); // Reset the sapling order after processing

141-144: Improve exception logging

The current exception logging concatenates the stack trace with the message using string concatenation. Consider using the logger's built-in exception handling for better formatting.

 catch (Exception e) {
-    Microbot.log("StrugglingSaplingEvent: Error during execution: " + e.getMessage() + Arrays.toString(e.getStackTrace()));
+    Microbot.log("StrugglingSaplingEvent: Error during execution", e);
     return false;
 }
runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingScript.java (4)

82-87: Nit: prefer enum identity (==) over equals for readability and safety.

Comparing enums via == is idiomatic and null-safe. Current code uses .equals(...) for walkBack() and TREE() checks.

-        if (config.walkBack().equals(WoodcuttingWalkBack.LAST_LOCATION)) {
+        if (config.walkBack() == WoodcuttingWalkBack.LAST_LOCATION) {

-            tree = Rs2GameObject.findReachableObject(config.TREE().getName(), true, config.distanceToStray(), getInitialPlayerLocation(), config.TREE().equals(WoodcuttingTree.REDWOOD), config.TREE().getAction());
+            tree = Rs2GameObject.findReachableObject(
+                config.TREE().getName(),
+                true,
+                config.distanceToStray(),
+                getInitialPlayerLocation(),
+                config.TREE() == WoodcuttingTree.REDWOOD,
+                config.TREE().getAction());

-                if (config.walkBack().equals(WoodcuttingWalkBack.LAST_LOCATION)) {
+                if (config.walkBack() == WoodcuttingWalkBack.LAST_LOCATION) {

Also applies to: 129-131, 137-140


297-339: Reduce chances of hangs and double-wait in burnLog.

Two issues:

  • sleepUntil(() -> !isFiremake()) has no timeout; if conditions never change, the script thread can stall.
  • Wrapping Rs2Player.waitForXpDrop(...) (which itself waits) inside sleepUntil adds redundant waiting.
-        sleepUntil(() -> !isFiremake());
+        // Avoid indefinite waits; bail out if no state change within 6 seconds
+        sleepUntil(() -> !isFiremake(), 6000);

-        if (!cannotLightFire && isFiremake()) {
-            sleepUntil(() -> Rs2Player.waitForXpDrop(Skill.FIREMAKING, 40000), 40000);
-        }
+        if (!cannotLightFire && isFiremake()) {
+            // Block until the next FM XP drop or timeout
+            Rs2Player.waitForXpDrop(Skill.FIREMAKING, 40000);
+        }

Also consider adding a recursion cap in fireSpot(int distance) to prevent deep recursion in extreme cases (e.g., max distance 30) and returning to a simple nearby tile if exceeded.


64-79: Bank session may remain open after withdrawing tinderbox.

When tinderbox is missing, you open the bank and withdraw it but never explicitly close the bank if logs are already present. This leaves the bank UI open unnecessarily.

         if (!Rs2Inventory.hasItem(TINDERBOX)) {
             Rs2Bank.openBank();
             sleepUntil(Rs2Bank::isOpen, 20000);
             Rs2Bank.withdrawItem(true, "Tinderbox");
+            // Close if we don't also need to withdraw logs
+            if (Rs2Inventory.hasItem(config.TREE().getLog())) {
+                Rs2Bank.closeBank();
+                sleep(300, 700);
+            }
         }

371-381: Rename typo in isFlectching method name

The method isFlectching and its single call site (sleepUntil(() -> !isFlectching(), 5000);) both contain a minor spelling error. Renaming it to isFletching will improve readability and consistency.

• Location:

  • Definition at AutoWoodcuttingScript.java:379
  • Call site at AutoWoodcuttingScript.java:376

Apply this optional refactor:

--- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingScript.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingScript.java
@@ -373,9 +373,9 @@
     private void fletchArrowShaft(AutoWoodcuttingConfig config) {
         Rs2Inventory.combineClosest("knife", config.TREE().getLog());
         sleepUntil(Rs2Widget::isProductionWidgetOpen, 5000);
         Rs2Widget.clickWidget("arrow shafts");
         Rs2Player.waitForAnimation();
-        sleepUntil(() -> !isFlectching(), 5000);
+        sleepUntil(() -> !isFletching(), 5000);
     }

-    private boolean isFlectching() {
+    private boolean isFletching() {
         return Rs2Player.isAnimating(3000) && Rs2Player.getLastAnimationID() == AnimationID.FLETCHING_BOW_CUTTING;
     }
runelite-client/src/main/java/net/runelite/client/plugins/microbot/externalplugins/MicrobotPluginManager.java (1)

100-116: Optional: atomic manifest swap to avoid transient empty reads.

Clearing and repopulating a shared map can briefly expose an empty state to readers. Swap the reference atomically to a new ConcurrentHashMap to avoid transient inconsistencies.

-            if (changed)
-            {
-                manifestMap.clear();
-                manifestMap.putAll(next);
+            if (changed)
+            {
+                var replacement = new ConcurrentHashMap<>(next);
+                manifestMap.clear();
+                manifestMap.putAll(replacement);

Alternatively, wrap the map in an AtomicReference<Map<String,MicrobotPluginManifest>> and replace the reference.

runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingConfig.java (2)

154-163: User-facing typo: “Comma separated” fix and minor clarity.

Same spelling issue and a chance to clarify phrasing.

-            name = "Items to keep when dropping (Comma separated)",
-            description = "Items to keep in inventory",
+            name = "Items to keep when dropping (comma separated)",
+            description = "Items to keep in your inventory while dropping",

68-71: Consistency: config method names are verbs; consider prefixing booleans with is/enable.

Optional style note: HardwoodTreePatch()/lootBirdNests()/lootSeeds() could follow a consistent boolean naming convention (e.g., hardwoodTreePatch() or isHardwoodTreePatchEnabled()).

Also applies to: 79-82, 90-93

runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingPlugin.java (2)

37-41: Reduce surface area: avoid public field + Lombok getter for the script.

autoWoodcuttingScript is public and also annotated with @Getter(AccessLevel.MODULE). Prefer one approach. Making the field private with a package/module-level getter reduces accidental external mutation.

-    @Getter(AccessLevel.MODULE)
-    public AutoWoodcuttingScript autoWoodcuttingScript;
+    @Getter(AccessLevel.MODULE)
+    private AutoWoodcuttingScript autoWoodcuttingScript;

276-295: Simplify config boolean toggles in onConfigChanged.

Comparing strings "true"/"false" is brittle. Use Boolean.parseBoolean(value).

-                var value = ev.getNewValue();
-                if (value != null && value.equals("true")) {
+                var value = ev.getNewValue();
+                if (Boolean.parseBoolean(value)) {
                     this.addEvent(key);
-                }
-                else if (value != null && value.equals("false")) {
+                } else if (value != null && !Boolean.parseBoolean(value)) {
                     this.removeEvent(key);
                 }
📜 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 0d2a385 and c5ba4f9.

📒 Files selected for processing (20)
  • runelite-client/pom.xml (1 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/externalplugins/MicrobotPluginManager.java (6 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/ui/MicrobotPluginHubPanel.java (3 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/bank/Rs2Bank.java (1 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/reflection/Rs2Reflection.java (2 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingConfig.java (7 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingOverlay.java (1 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingPlugin.java (2 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingScript.java (7 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/EggEvent.java (1 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/EntlingsEvent.java (1 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/FlowersEvent.java (1 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/FoxEvent.java (1 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/HivesEvent.java (1 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/LeprechaunEvent.java (1 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/RitualEvent.java (1 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/RootEvent.java (1 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/StrugglingSaplingEvent.java (1 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/enums/ForestryEvents.java (1 hunks)
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/enums/WoodcuttingScriptState.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (12)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/RootEvent.java (4)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/combat/Rs2Combat.java (1)
  • Rs2Combat (27-244)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/gameobject/Rs2GameObject.java (1)
  • Rs2GameObject (36-2002)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/player/Rs2Player.java (1)
  • Rs2Player (67-2062)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/Global.java (1)
  • Global (10-171)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/EggEvent.java (5)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/dialogues/Rs2Dialogue.java (1)
  • Rs2Dialogue (18-725)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/gameobject/Rs2GameObject.java (1)
  • Rs2GameObject (36-2002)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/inventory/Rs2Inventory.java (1)
  • Rs2Inventory (60-2244)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/player/Rs2Player.java (1)
  • Rs2Player (67-2062)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/Global.java (1)
  • Global (10-171)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingOverlay.java (2)
runelite-api/src/main/java/net/runelite/api/Perspective.java (1)
  • Perspective (63-1014)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingScript.java (1)
  • AutoWoodcuttingScript (36-396)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/FoxEvent.java (1)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/player/Rs2Player.java (1)
  • Rs2Player (67-2062)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/LeprechaunEvent.java (2)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/Global.java (1)
  • Global (10-171)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/player/Rs2Player.java (1)
  • Rs2Player (67-2062)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/HivesEvent.java (4)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/inventory/Rs2Inventory.java (1)
  • Rs2Inventory (60-2244)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/keyboard/Rs2Keyboard.java (1)
  • Rs2Keyboard (15-174)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/player/Rs2Player.java (1)
  • Rs2Player (67-2062)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/Global.java (1)
  • Global (10-171)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/StrugglingSaplingEvent.java (5)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/gameobject/Rs2GameObject.java (1)
  • Rs2GameObject (36-2002)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/inventory/Rs2Inventory.java (1)
  • Rs2Inventory (60-2244)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/player/Rs2Player.java (1)
  • Rs2Player (67-2062)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingScript.java (1)
  • AutoWoodcuttingScript (36-396)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/gameobject/ObjectID.java (1)
  • ObjectID (10-33)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/EntlingsEvent.java (1)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/player/Rs2Player.java (1)
  • Rs2Player (67-2062)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/RitualEvent.java (2)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/player/Rs2Player.java (1)
  • Rs2Player (67-2062)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/Global.java (1)
  • Global (10-171)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/externalplugins/MicrobotPluginManager.java (1)
runelite-client/src/main/java/net/runelite/client/RuneLiteProperties.java (1)
  • RuneLiteProperties (35-162)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingScript.java (7)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/combat/Rs2Combat.java (1)
  • Rs2Combat (27-244)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/equipment/Rs2Equipment.java (1)
  • Rs2Equipment (27-485)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/gameobject/Rs2GameObject.java (1)
  • Rs2GameObject (36-2002)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/grounditem/LootingParameters.java (1)
  • LootingParameters (3-115)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/inventory/Rs2Inventory.java (1)
  • Rs2Inventory (60-2244)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/player/Rs2Player.java (1)
  • Rs2Player (67-2062)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/keyboard/Rs2Keyboard.java (1)
  • Rs2Keyboard (15-174)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingPlugin.java (1)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/gameobject/Rs2GameObject.java (1)
  • Rs2GameObject (36-2002)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build
🔇 Additional comments (12)
runelite-client/pom.xml (1)

44-44: Shaded artifact version verified; please update docs

I’ve confirmed that the Maven shade plugin in runelite-client/pom.xml is correctly producing target/microbot-1.9.8.8.jar using the updated <microbot.version> property. No other outputFile entries were found, so the artifact naming is consistent.

• Please review and update any downstream artifacts or documentation (e.g., README) that reference the Microbot version.
• Once documentation is aligned, this change can be finalized.

runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/bank/Rs2Bank.java (1)

2214-2221: emptyLogBasket helper verified and approved
The LOG_BASKET_CLOSED and LOG_BASKET_OPEN constants are present in runelite-api/src/main/java/net/runelite/api/gameval/ItemID.java (lines 71485 and 71490), so the new emptyLogBasket() implementation correctly reuses the existing empty(...) helper.

runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/enums/WoodcuttingScriptState.java (1)

3-7: Enum addition is clear and self-explanatory.

Names align with usage in state machines and overlays.

runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/enums/ForestryEvents.java (1)

3-13: ForestryEvents enum looks good; covers the expected event set.

Names are consistent and easy to render in UI. No issues spotted.

runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/FoxEvent.java (1)

3-3: Import Path Validated – No Changes Required

I’ve confirmed that your runelite-api module includes both
net.runelite.api.NpcID and net.runelite.api.gameval.NpcID (and the corresponding ObjectID in each package), so all import net.runelite.api.gameval.NpcID; (and ObjectID) usages are valid and will compile as-is:

  • runelite-api/src/main/java/net/runelite/api/gameval/NpcID.java
  • runelite-api/src/main/java/net/runelite/api/gameval/ObjectID.java

Since net.runelite.api.gameval is present and populated with the expected classes, no modifications to the import paths are necessary.

runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/LeprechaunEvent.java (1)

3-5: Verify import paths (gameval NpcID/ObjectID)

Double-check the availability of net.runelite.api.gameval.NpcID and net.runelite.api.gameval.ObjectID in your repo. If not present, use net.runelite.api.NpcID / net.runelite.api.ObjectID.

You can reuse the script from my FoxEvent comment to verify.

runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/RootEvent.java (1)

52-54: Verify special-attack energy requirement (1000) is correct

setSpecState(true, 1000) requires 100% special energy (RuneLite varp is x10). Most axes use less. Confirm this value matches the equipped axe; otherwise the special may never fire. Consider setSpecState(true) or making the requirement configurable.

runelite-client/src/main/java/net/runelite/client/plugins/microbot/ui/MicrobotPluginHubPanel.java (3)

574-610: LGTM! Improved manifest retrieval and error handling

The refactored reloadPluginList() method now properly uses the cached manifest from MicrobotPluginManager instead of downloading directly, and provides better error handling with a retry option specifically for plugin counts.


137-167: Well-implemented image scaling for plugin icons

The scaleImageToFit method properly scales images while maintaining aspect ratio and uses high-quality rendering hints for better visual results.


723-726: Good integration with manifest cache updates

The subscription to ExternalPluginsChanged events ensures the UI stays synchronized with manifest cache changes from MicrobotPluginManager.

runelite-client/src/main/java/net/runelite/client/plugins/microbot/externalplugins/MicrobotPluginManager.java (1)

397-420: Ensure consistent identifiers between installed list and PluginDescriptor.name()
The syncPlugins() method builds a map of loaded plugins keyed by the human-readable PluginDescriptor.name() (e.g. “Entity Hider”) but then checks membership against entries from getInstalledPlugins(), which—based on the missing plugins.json in the repo and typical installation logic—almost certainly contains jar/filename identifiers (e.g. entity-hider, artifact IDs, or class names), not the descriptor’s display names. This mismatch means plugins will be spuriously removed or retained.

Please verify:

  • That microbot-plugins/plugins.json (or wherever getInstalledPlugins() reads from) stores the exact descriptor names (PluginDescriptor.name()), including whitespace and casing.
  • If it stores filenames or other IDs, update syncPlugins() to either
    • normalize filenames to descriptor names (e.g., read the plugin jar’s annotation to map file → descriptor), or
    • switch to using a consistent internal identifier (for example an explicit id field in the descriptor) for both installation and lookup.

Location to investigate:
• runelite-client/src/main/java/net/runelite/client/plugins/microbot/externalplugins/MicrobotPluginManager.java (around lines 397–420)

runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingPlugin.java (1)

69-77: Startup order: register forestry events before starting the script is good—retain this.

Events are added (when enabled) before autoWoodcuttingScript.run(config), so the script can react to event-driven state immediately. Looks good.

Comment on lines +234 to +243
try (Response response = okHttpClient.newCall(request).execute())
{
if (!response.isSuccessful())
{
log.error("Error downloading plugin: {}, code: {}", manifest.getInternalName(), response.code());
return;
}

byte[] jarData = response.body().bytes();

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

Null-check Response.body() to avoid NPE during plugin download.

response.body() can be null; calling .bytes() would NPE and mask the actual HTTP failure.

-                try (Response response = okHttpClient.newCall(request).execute())
+                try (Response response = okHttpClient.newCall(request).execute())
                 {
                     if (!response.isSuccessful())
                     {
                         log.error("Error downloading plugin: {}, code: {}", manifest.getInternalName(), response.code());
                         return;
                     }
 
-                    byte[] jarData = response.body().bytes();
+                    if (response.body() == null) {
+                        log.error("Empty response body when downloading plugin: {}", manifest.getInternalName());
+                        return;
+                    }
+                    byte[] jarData = response.body().bytes();
📝 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
try (Response response = okHttpClient.newCall(request).execute())
{
if (!response.isSuccessful())
{
log.error("Error downloading plugin: {}, code: {}", manifest.getInternalName(), response.code());
return;
}
byte[] jarData = response.body().bytes();
try (Response response = okHttpClient.newCall(request).execute())
{
if (!response.isSuccessful())
{
log.error("Error downloading plugin: {}, code: {}", manifest.getInternalName(), response.code());
return;
}
if (response.body() == null)
{
log.error("Empty response body when downloading plugin: {}", manifest.getInternalName());
return;
}
byte[] jarData = response.body().bytes();
🤖 Prompt for AI Agents
In
runelite-client/src/main/java/net/runelite/client/plugins/microbot/externalplugins/MicrobotPluginManager.java
around lines 234 to 243, the code calls response.body().bytes() without
null-checking the body; add a null-check for response.body() after verifying
response.isSuccessful(), log an appropriate error (including manifest name and
response.code() and/or response.message()) and return if the body is null to
avoid an NPE, otherwise proceed to read bytes.

Comment on lines +62 to 66
if (Microbot.isLoggedIn() && !firstRun) {
resetStats();
firstRun = true;
}
try {
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: avoid NPE before first login and reset on logout.

startTime is null until the first logged-in render. Later stats code assumes it’s non-null. On startup while logged out this throws and floods logs (caught by try/catch). Also, if you log out, firstRun never resets.

     public Dimension render(Graphics2D graphics) {
-        if (Microbot.isLoggedIn() && !firstRun) {
+        if (!Microbot.isLoggedIn()) {
+            // ensure we re-initialize after re-login
+            firstRun = false;
+        } else if (!firstRun) {
             resetStats();
             firstRun = true;
         }
📝 Committable suggestion

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

Suggested change
if (Microbot.isLoggedIn() && !firstRun) {
resetStats();
firstRun = true;
}
try {
public Dimension render(Graphics2D graphics) {
- if (Microbot.isLoggedIn() && !firstRun) {
- resetStats();
- firstRun = true;
if (!Microbot.isLoggedIn()) {
// ensure we re-initialize after re-login
firstRun = false;
} else if (!firstRun) {
resetStats();
firstRun = true;
}
try {
🤖 Prompt for AI Agents
In
runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingOverlay.java
around lines 62-66, avoid a NullPointerException from startTime being null
before the first login and ensure firstRun resets on logout: only reference or
reset stats/startTime when Microbot.isLoggedIn() is true (initialize startTime
at first successful login), and when Microbot.isLoggedIn() becomes false clear
firstRun and startTime so subsequent logins reinitialize; update the conditional
logic to check login state before using startTime and to reset
firstRun/startTime on logout.

Comment on lines +126 to +136
// Calculate XP per hour
long secondsElapsed = Duration.between(startTime, Instant.now()).getSeconds();
if (secondsElapsed > 0) {
double xpPerHour = (double) xpGained / secondsElapsed * 3600;
panelComponent.getChildren().add(LineComponent.builder()
.left("XP/Hour:")
.right(NumberFormat.getInstance().format((long) xpPerHour))
.rightColor(NORMAL_TEXT_COLOR)
.build());
}

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

Guard XP/hour computation when startTime is not initialized.

When not logged in (or before first run), startTime can be null.

-            long secondsElapsed = Duration.between(startTime, Instant.now()).getSeconds();
-            if (secondsElapsed > 0) {
+            Long secondsElapsed = (startTime != null) ? Duration.between(startTime, Instant.now()).getSeconds() : null;
+            if (secondsElapsed != null && secondsElapsed > 0) {
                 double xpPerHour = (double) xpGained / secondsElapsed * 3600;
                 panelComponent.getChildren().add(LineComponent.builder()
                         .left("XP/Hour:")
                         .right(NumberFormat.getInstance().format((long) xpPerHour))
                         .rightColor(NORMAL_TEXT_COLOR)
                         .build());
             }
📝 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
// Calculate XP per hour
long secondsElapsed = Duration.between(startTime, Instant.now()).getSeconds();
if (secondsElapsed > 0) {
double xpPerHour = (double) xpGained / secondsElapsed * 3600;
panelComponent.getChildren().add(LineComponent.builder()
.left("XP/Hour:")
.right(NumberFormat.getInstance().format((long) xpPerHour))
.rightColor(NORMAL_TEXT_COLOR)
.build());
}
// Calculate XP per hour
Long secondsElapsed = (startTime != null)
? Duration.between(startTime, Instant.now()).getSeconds()
: null;
if (secondsElapsed != null && secondsElapsed > 0) {
double xpPerHour = (double) xpGained / secondsElapsed * 3600;
panelComponent.getChildren().add(LineComponent.builder()
.left("XP/Hour:")
.right(NumberFormat.getInstance().format((long) xpPerHour))
.rightColor(NORMAL_TEXT_COLOR)
.build());
}
🤖 Prompt for AI Agents
In
runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingOverlay.java
around lines 126 to 136, the XP/hour computation calls
Duration.between(startTime, Instant.now()) without ensuring startTime is
initialized which can cause an NPE; guard this by checking startTime != null
before computing Duration (and only compute xpPerHour if startTime is non-null
and secondsElapsed > 0), or alternatively initialize startTime when the plugin
starts so the Duration call is always safe.

Comment on lines +145 to +150
panelComponent.getChildren().add(LineComponent.builder()
.left("Time Running:")
.right(formatDuration(Duration.between(startTime, Instant.now())))
.rightColor(NORMAL_TEXT_COLOR)
.build());

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 Time Running formatting when startTime is null.

Avoid Duration.between(null, ...) NPE.

-            panelComponent.getChildren().add(LineComponent.builder()
-                    .left("Time Running:")
-                    .right(formatDuration(Duration.between(startTime, Instant.now())))
-                    .rightColor(NORMAL_TEXT_COLOR)
-                    .build());
+            panelComponent.getChildren().add(LineComponent.builder()
+                    .left("Time Running:")
+                    .right(startTime != null ? formatDuration(Duration.between(startTime, Instant.now())) : "00:00:00")
+                    .rightColor(NORMAL_TEXT_COLOR)
+                    .build());
📝 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
panelComponent.getChildren().add(LineComponent.builder()
.left("Time Running:")
.right(formatDuration(Duration.between(startTime, Instant.now())))
.rightColor(NORMAL_TEXT_COLOR)
.build());
panelComponent.getChildren().add(LineComponent.builder()
.left("Time Running:")
.right(startTime != null
? formatDuration(Duration.between(startTime, Instant.now()))
: "00:00:00")
.rightColor(NORMAL_TEXT_COLOR)
.build());
🤖 Prompt for AI Agents
In
runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingOverlay.java
around lines 145-150, guard against a null startTime before calling
Duration.between to avoid an NPE: check if startTime is null and, if so, set the
right text to a safe placeholder (e.g., "-" or "Not started") and the rightColor
as before; otherwise compute formatDuration(Duration.between(startTime,
Instant.now())). Ensure the conditional supplies the same LineComponent builder
fields so the panel rendering remains consistent.

Comment on lines +100 to +103
if (msg.equals("you can't light a fire here.")) {
autoWoodcuttingScript.cannotLightFire = true;
}

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix: exact string match for “You can't light a fire here.” is case-sensitive.

OSRS chat starts with capital “You”. The lowercase comparison will fail, leaving cannotLightFire unset and causing the burn logic not to relocate.

-        if (msg.equals("you can't light a fire here.")) {
+        if (msg.equalsIgnoreCase("You can't light a fire here.")) {
             autoWoodcuttingScript.cannotLightFire = true;
         }

Optionally, use a case-insensitive contains to be resilient across locales/punctuation:

-        if (msg.equals("you can't light a fire here.")) {
+        if (msg.toLowerCase(Locale.ROOT).contains("can't light a fire here")) {
📝 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 (msg.equals("you can't light a fire here.")) {
autoWoodcuttingScript.cannotLightFire = true;
}
if (msg.equalsIgnoreCase("You can't light a fire here.")) {
autoWoodcuttingScript.cannotLightFire = true;
}

Comment on lines +81 to +84
.filter(nest -> pheasants.stream()
.noneMatch(pheasant -> pheasant.getWorldLocation() == nest.getWorldLocation()))
.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

Fix WorldPoint comparison: use equals() instead of ==

== compares references, not values, so the “nest without nearby pheasants” filter is incorrect and may misclassify nests. Use equals.

-                    .filter(nest -> pheasants.stream()
-                            .noneMatch(pheasant -> pheasant.getWorldLocation() == nest.getWorldLocation()))
+                    .filter(nest -> pheasants.stream()
+                            .noneMatch(pheasant -> pheasant.getWorldLocation().equals(nest.getWorldLocation())))
📝 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
.filter(nest -> pheasants.stream()
.noneMatch(pheasant -> pheasant.getWorldLocation() == nest.getWorldLocation()))
.collect(Collectors.toList());
.filter(nest -> pheasants.stream()
.noneMatch(pheasant -> pheasant.getWorldLocation().equals(nest.getWorldLocation())))
.collect(Collectors.toList());
🤖 Prompt for AI Agents
In
runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/EggEvent.java
around lines 81 to 84, the filter uses '==' to compare WorldPoint instances
which compares references; replace this with a value comparison using equals to
correctly detect matching locations and avoid misclassification — e.g., change
the predicate to compare
pheasant.getWorldLocation().equals(nest.getWorldLocation()) or use
Objects.equals(pheasant.getWorldLocation(), nest.getWorldLocation()) to guard
against nulls.

Comment on lines +19 to +27
public boolean validate() {
var flowers = Rs2Npc.getNpcs(npc ->
npc.getName() != null && isFloweringBush(npc.getId())
).collect(Collectors.toList());
if (flowers.isEmpty()) {
return false; // No flowering bushes found, cannot proceed with the event
}
return false;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix the validate() method to return true when flowers are found

The validate() method always returns false even when flowering bushes are found. This prevents the event from ever executing.

 @Override
 public boolean validate() {
     var flowers = Rs2Npc.getNpcs(npc ->
             npc.getName() != null && isFloweringBush(npc.getId())
     ).collect(Collectors.toList());
     if (flowers.isEmpty()) {
         return false; // No flowering bushes found, cannot proceed with the event
     }
-    return false;
+    return true;
 }
📝 Committable suggestion

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

Suggested change
public boolean validate() {
var flowers = Rs2Npc.getNpcs(npc ->
npc.getName() != null && isFloweringBush(npc.getId())
).collect(Collectors.toList());
if (flowers.isEmpty()) {
return false; // No flowering bushes found, cannot proceed with the event
}
return false;
}
@Override
public boolean validate() {
var flowers = Rs2Npc.getNpcs(npc ->
npc.getName() != null && isFloweringBush(npc.getId())
).collect(Collectors.toList());
if (flowers.isEmpty()) {
return false; // No flowering bushes found, cannot proceed with the event
}
return true;
}
🤖 Prompt for AI Agents
In
runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/FlowersEvent.java
around lines 19 to 27, the validate() method collects flowering bushes into
'flowers' but always returns false; change the final return so the method
returns true when flowers are found (e.g., return true inside the non-empty
branch or simply return !flowers.isEmpty()) so the event can proceed when
flowering bushes exist.

Comment on lines +31 to +36
while (this.validate()) {
var trap = Rs2Npc.getNpc(NpcID.GATHERING_EVENT_POACHERS_TRAP);
if (trap == null) {
continue; // If the trap is not found, we cannot proceed with the event
}
Microbot.log("FoxEvent: Interacting with the trap to disarm it.", Level.INFO);
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 tight spin when trap is absent; throttle or wait for spawn

As written, the loop busy-spins if the trap isn’t present, pegging a CPU core. Add a short sleep or a bounded wait before continuing.

Apply this diff:

-            if (trap == null) {
-                continue; // If the trap is not found, we cannot proceed with the event
-            }
+            if (trap == null) {
+                // If the trap is not found, wait briefly before retrying to avoid busy-spin
+                net.runelite.client.plugins.microbot.util.Global.sleep(150);
+                continue;
+            }

If you prefer a bounded wait:

+            if (trap == null) {
+                net.runelite.client.plugins.microbot.util.Global.sleepUntil(
+                        () -> net.runelite.client.plugins.microbot.util.npc.Rs2Npc.getNpc(
+                                net.runelite.api.gameval.NpcID.GATHERING_EVENT_POACHERS_TRAP) != null, 2000);
+                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
while (this.validate()) {
var trap = Rs2Npc.getNpc(NpcID.GATHERING_EVENT_POACHERS_TRAP);
if (trap == null) {
continue; // If the trap is not found, we cannot proceed with the event
}
Microbot.log("FoxEvent: Interacting with the trap to disarm it.", Level.INFO);
while (this.validate()) {
var trap = Rs2Npc.getNpc(NpcID.GATHERING_EVENT_POACHERS_TRAP);
if (trap == null) {
// If the trap is not found, wait briefly before retrying to avoid busy-spin
net.runelite.client.plugins.microbot.util.Global.sleep(150);
continue;
}
Microbot.log("FoxEvent: Interacting with the trap to disarm it.", Level.INFO);
🤖 Prompt for AI Agents
In
runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/FoxEvent.java
around lines 31 to 36, the while loop busy-spins when Rs2Npc.getNpc(...) returns
null; change the loop so that when trap == null you pause briefly (e.g.,
Thread.sleep(100-500 ms)) or implement a bounded wait/retry with a short timeout
before continuing, and handle InterruptedException appropriately; this will
throttle CPU usage while still allowing the trap to be detected without
spinning.

Comment on lines +36 to +40
while (this.validate()) {
var endOfRainbow = Rs2ObjectCache.getClosestObjectById(ObjectID.GATHERING_EVENT_WOODCUTTING_LEPRECHAUN_RAINBOW);
if (endOfRainbow.isEmpty()) {
continue; // If the end of the rainbow is not found, we cannot proceed with the event
}
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

Throttle loop when rainbow object isn’t available

Without a delay, continue leads to a tight spin. Add a short sleep.

-            if (endOfRainbow.isEmpty()) {
-                continue; // If the end of the rainbow is not found, we cannot proceed with the event
-            }
+            if (endOfRainbow.isEmpty()) {
+                // Avoid busy-spin when the rainbow hasn’t spawned yet
+                net.runelite.client.plugins.microbot.util.Global.sleep(150);
+                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
while (this.validate()) {
var endOfRainbow = Rs2ObjectCache.getClosestObjectById(ObjectID.GATHERING_EVENT_WOODCUTTING_LEPRECHAUN_RAINBOW);
if (endOfRainbow.isEmpty()) {
continue; // If the end of the rainbow is not found, we cannot proceed with the event
}
while (this.validate()) {
var endOfRainbow = Rs2ObjectCache.getClosestObjectById(ObjectID.GATHERING_EVENT_WOODCUTTING_LEPRECHAUN_RAINBOW);
if (endOfRainbow.isEmpty()) {
// Avoid busy-spin when the rainbow hasn’t spawned yet
net.runelite.client.plugins.microbot.util.Global.sleep(150);
continue;
}
🤖 Prompt for AI Agents
In
runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/LeprechaunEvent.java
around lines 36-40, the while loop does a tight spin when endOfRainbow.isEmpty()
because it immediately continues without delay; add a short sleep (e.g.,
100–200ms) before continuing to throttle the loop and avoid high CPU usage, and
handle InterruptedException appropriately (restore interrupt status or break if
needed).

Comment on lines +69 to +70
sleepUntil(() -> !Rs2Player.isInteracting(), 40000);
}
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

Wait condition uses actor interaction; use animation state instead

Rs2Player.isInteracting() reflects actor targeting, which won’t change for object chopping. This makes the wait return immediately. Wait for animation to stop instead.

-                Rs2Player.waitForAnimation(5000);
-                sleepUntil(() -> !Rs2Player.isInteracting(), 40000);
+                Rs2Player.waitForAnimation(5000);
+                sleepUntil(() -> !Rs2Player.isAnimating(), 40000);

Apply the same change in both special and regular root branches.

Also applies to: 85-86

🤖 Prompt for AI Agents
In
runelite-client/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/RootEvent.java
around lines 69-70 and 85-86, the code currently waits on
Rs2Player.isInteracting() which checks actor targeting and can return
immediately for object chopping; change the wait condition to check the player's
animation state (e.g., wait until player's animation equals -1 or not equal to
the woodcutting animation id) so the sleepUntil only completes when the chopping
animation stops; apply this replacement in both the special and regular root
branches and ensure the timeout values remain the same.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants